Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F33116038
D7902.1768486291.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
D7902.1768486291.diff
View Options
diff --git a/web/chat/chat-input-bar.react.js b/web/chat/chat-input-bar.react.js
--- a/web/chat/chat-input-bar.react.js
+++ b/web/chat/chat-input-bar.react.js
@@ -232,24 +232,28 @@
const { pendingUploads, cancelPendingUpload } = this.props.inputState;
const multimediaPreviews = pendingUploads.map(pendingUpload => {
- let mediaSource;
- if (
- pendingUpload.mediaType !== 'encrypted_photo' &&
- pendingUpload.mediaType !== 'encrypted_video'
- ) {
+ const { uri, mediaType, thumbHash, dimensions } = pendingUpload;
+ let mediaSource = { thumbHash, dimensions };
+ if (mediaType !== 'encrypted_photo' && mediaType !== 'encrypted_video') {
mediaSource = {
- type: pendingUpload.mediaType,
- uri: pendingUpload.uri,
+ ...mediaSource,
+ type: mediaType,
+ uri,
+ thumbnailURI: null,
};
} else {
+ const { encryptionKey } = pendingUpload;
invariant(
- pendingUpload.encryptionKey,
+ encryptionKey,
'encryptionKey should be set for encrypted media',
);
mediaSource = {
- type: pendingUpload.mediaType,
- holder: pendingUpload.uri,
- encryptionKey: pendingUpload.encryptionKey,
+ ...mediaSource,
+ type: mediaType,
+ holder: uri,
+ encryptionKey,
+ thumbnailHolder: null,
+ thumbnailEncryptionKey: null,
};
}
return (
diff --git a/web/chat/multimedia-message.react.js b/web/chat/multimedia-message.react.js
--- a/web/chat/multimedia-message.react.js
+++ b/web/chat/multimedia-message.react.js
@@ -39,17 +39,28 @@
const pendingUpload = pendingUploads
? pendingUploads.find(upload => upload.localID === singleMedia.id)
: null;
+ const thumbHash = singleMedia.thumbHash ?? singleMedia.thumbnailThumbHash;
let mediaSource;
if (singleMedia.type === 'photo' || singleMedia.type === 'video') {
- mediaSource = {
- type: singleMedia.type,
- uri: singleMedia.uri,
- };
+ const { type, uri, thumbnailURI, dimensions } = singleMedia;
+ mediaSource = { type, uri, thumbHash, thumbnailURI, dimensions };
} else {
+ const {
+ type,
+ holder,
+ encryptionKey,
+ thumbnailHolder,
+ thumbnailEncryptionKey,
+ dimensions,
+ } = singleMedia;
mediaSource = {
- type: singleMedia.type,
- holder: singleMedia.holder,
- encryptionKey: singleMedia.encryptionKey,
+ type,
+ holder,
+ encryptionKey,
+ thumbnailHolder,
+ thumbnailEncryptionKey,
+ dimensions,
+ thumbHash,
};
}
diff --git a/web/media/media.css b/web/media/media.css
--- a/web/media/media.css
+++ b/web/media/media.css
@@ -16,6 +16,8 @@
}
span.multimedia > .multimediaImage > img,
span.multimedia > .multimediaImage > video {
+ /* this should be in sync with the MAX_THUMBNAIL_HEIGHT */
+ /* in multimedia.react.js */
max-height: 200px;
max-width: 100%;
}
diff --git a/web/media/multimedia.react.js b/web/media/multimedia.react.js
--- a/web/media/multimedia.react.js
+++ b/web/media/multimedia.react.js
@@ -12,23 +12,40 @@
import { useModalContext } from 'lib/components/modal-provider.react.js';
import { fetchableMediaURI } from 'lib/media/media-utils.js';
-import type { MediaType, EncryptedMediaType } from 'lib/types/media-types.js';
+import type {
+ Dimensions,
+ EncryptedMediaType,
+ MediaType,
+} from 'lib/types/media-types.js';
import EncryptedMultimedia from './encrypted-multimedia.react.js';
+import LoadableVideo from './loadable-video.react.js';
+import { usePlaceholder } from './media-utils.js';
import css from './media.css';
import MultimediaModal from './multimedia-modal.react.js';
import Button from '../components/button.react.js';
import { type PendingMultimediaUpload } from '../input/input-state.js';
+// this should be in sync with the max-height value
+// for span.multimedia > .multimediaImage in media.css
+const MAX_THUMBNAIL_HEIGHT = 200;
+
type MediaSource =
| {
+type: MediaType,
+uri: string,
+ +dimensions: ?Dimensions,
+ +thumbHash: ?string,
+ +thumbnailURI: ?string,
}
| {
+type: EncryptedMediaType,
+holder: string,
+encryptionKey: string,
+ +dimensions: ?Dimensions,
+ +thumbHash: ?string,
+ +thumbnailHolder: ?string,
+ +thumbnailEncryptionKey: ?string,
};
type Props = {
@@ -81,7 +98,19 @@
const { pushModal } = useModalContext();
const handleClick = React.useCallback(() => {
- pushModal(<MultimediaModal media={mediaSource} />);
+ let media;
+ if (
+ mediaSource.type === 'encrypted_photo' ||
+ mediaSource.type === 'encrypted_video'
+ ) {
+ const { type, holder, encryptionKey } = mediaSource;
+ media = { type, holder, encryptionKey };
+ } else {
+ const { type, uri } = mediaSource;
+ invariant(uri, 'uri is missing for media modal');
+ media = { type, uri };
+ }
+ pushModal(<MultimediaModal media={media} />);
}, [pushModal, mediaSource]);
let progressIndicator, errorIndicator, removeButton;
@@ -122,23 +151,60 @@
const imageContainerClasses = [css.multimediaImage, multimediaImageCSSClass];
imageContainerClasses.push(css.clickable);
+ const thumbHash = mediaSource.thumbHash ?? pendingUpload?.thumbHash;
+ const { encryptionKey, thumbnailEncryptionKey } = mediaSource;
+ const thumbHashEncryptionKey = thumbnailEncryptionKey ?? encryptionKey;
+ const placeholderImage = usePlaceholder(thumbHash, thumbHashEncryptionKey);
+
+ const { dimensions } = mediaSource;
+ const elementStyle = React.useMemo(() => {
+ if (!dimensions) {
+ return undefined;
+ }
+ const { width, height } = dimensions;
+ // Resize the image to fit in max width while preserving aspect ratio
+ const calculatedWidth =
+ Math.min(MAX_THUMBNAIL_HEIGHT, height) * (width / height);
+ return {
+ background: placeholderImage
+ ? `center / cover url(${placeholderImage})`
+ : undefined,
+ width: `${calculatedWidth}px`,
+ // height is limited by the max-height style in media.css
+ height: `${height}px`,
+ };
+ }, [dimensions, placeholderImage]);
+
// Media element is the actual image or video element (or encrypted version)
let mediaElement;
if (mediaSource.type === 'photo') {
const uri = fetchableMediaURI(mediaSource.uri);
- mediaElement = <img src={uri} />;
+ mediaElement = <img src={uri} style={elementStyle} />;
} else if (mediaSource.type === 'video') {
const uri = fetchableMediaURI(mediaSource.uri);
+ const { thumbnailURI } = mediaSource;
+ invariant(thumbnailURI, 'video missing thumbnail');
mediaElement = (
- <video controls>
- <source src={uri} />
- </video>
+ <LoadableVideo
+ uri={uri}
+ thumbnailSource={{ thumbnailURI }}
+ thumbHashDataURL={placeholderImage}
+ elementStyle={elementStyle}
+ />
);
} else if (
mediaSource.type === 'encrypted_photo' ||
mediaSource.type === 'encrypted_video'
) {
- mediaElement = <EncryptedMultimedia {...mediaSource} />;
+ const { type, holder } = mediaSource;
+ invariant(encryptionKey, 'encryptionKey undefined for encrypted media');
+ mediaElement = (
+ <EncryptedMultimedia
+ type={type}
+ holder={holder}
+ encryptionKey={encryptionKey}
+ />
+ );
}
// Media node is the container for the media element (button if photo)
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Jan 15, 2:11 PM (10 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5938302
Default Alt Text
D7902.1768486291.diff (7 KB)
Attached To
Mode
D7902: [web] Display thumbhash in chat
Attached
Detach File
Event Timeline
Log In to Comment