Enable support for image, video and audio files
							parent
							
								
									409213ceb4
								
							
						
					
					
						commit
						59c1b67b7d
					
				|  | @ -34,6 +34,7 @@ export default class MAudioBody extends React.Component { | |||
|             error: null, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     onPlayToggle() { | ||||
|         this.setState({ | ||||
|             playing: !this.state.playing, | ||||
|  | @ -41,6 +42,7 @@ export default class MAudioBody extends React.Component { | |||
|     } | ||||
| 
 | ||||
|     _getContentUrl() { | ||||
|         if (this.props.mediaSrc) return this.props.mediaSrc; | ||||
|         const media = mediaFromContent(this.props.mxEvent.getContent()); | ||||
|         if (media.isEncrypted) { | ||||
|             return this.state.decryptedUrl; | ||||
|  | @ -49,6 +51,11 @@ export default class MAudioBody extends React.Component { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     getFileBody() { | ||||
|         if (this.props.mediaSrc) return null; | ||||
|         return <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />; | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         const content = this.props.mxEvent.getContent(); | ||||
|         if (content.file !== undefined && this.state.decryptedUrl === null) { | ||||
|  | @ -101,11 +108,11 @@ export default class MAudioBody extends React.Component { | |||
|         } | ||||
| 
 | ||||
|         const contentUrl = this._getContentUrl(); | ||||
| 
 | ||||
|         const fileBody = this.getFileBody(); | ||||
|         return ( | ||||
|             <span className="mx_MAudioBody"> | ||||
|                 <audio src={contentUrl} controls /> | ||||
|                 <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} /> | ||||
|                 { fileBody } | ||||
|             </span> | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
|  | @ -242,7 +242,7 @@ export default class MFileBody extends React.Component { | |||
|                 <span className="mx_MFileBody"> | ||||
|                     {placeholder} | ||||
|                     <div className="mx_MFileBody_download"> | ||||
|                         <div style={{display: "none"}}> | ||||
|                         <div style={{ display: "none" }}> | ||||
|                             { /* | ||||
|                               * Add dummy copy of the "a" tag | ||||
|                               * We'll use it to learn how the download link | ||||
|  | @ -309,7 +309,7 @@ export default class MFileBody extends React.Component { | |||
|             if (this.props.tileShape === "file_grid") { | ||||
|                 return ( | ||||
|                     <span className="mx_MFileBody"> | ||||
|                         {placeholder} | ||||
|                         { placeholder } | ||||
|                         <div className="mx_MFileBody_download"> | ||||
|                             <a className="mx_MFileBody_downloadLink" {...downloadProps}> | ||||
|                                 { fileName } | ||||
|  | @ -323,7 +323,7 @@ export default class MFileBody extends React.Component { | |||
|             } else { | ||||
|                 return ( | ||||
|                     <span className="mx_MFileBody"> | ||||
|                         {placeholder} | ||||
|                         { placeholder } | ||||
|                         <div className="mx_MFileBody_download"> | ||||
|                             <a {...downloadProps}> | ||||
|                                 <span className="mx_MFileBody_download_icon" /> | ||||
|  | @ -336,7 +336,7 @@ export default class MFileBody extends React.Component { | |||
|         } else { | ||||
|             const extra = text ? (': ' + text) : ''; | ||||
|             return <span className="mx_MFileBody"> | ||||
|                 {placeholder} | ||||
|                 { placeholder } | ||||
|                 { _t("Invalid file%(extra)s", { extra: extra }) } | ||||
|             </span>; | ||||
|         } | ||||
|  |  | |||
|  | @ -90,7 +90,7 @@ export default class MImageBody extends React.Component { | |||
| 
 | ||||
|     showImage() { | ||||
|         localStorage.setItem("mx_ShowImage_" + this.props.mxEvent.getId(), "true"); | ||||
|         this.setState({showImage: true}); | ||||
|         this.setState({ showImage: true }); | ||||
|         this._downloadImage(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -172,6 +172,7 @@ export default class MImageBody extends React.Component { | |||
|     } | ||||
| 
 | ||||
|     _getContentUrl() { | ||||
|         if (this.props.mediaSrc) return this.props.mediaSrc; | ||||
|         const media = mediaFromContent(this.props.mxEvent.getContent()); | ||||
|         if (media.isEncrypted) { | ||||
|             return this.state.decryptedUrl; | ||||
|  | @ -296,7 +297,7 @@ export default class MImageBody extends React.Component { | |||
|         if (showImage) { | ||||
|             // Don't download anything becaue we don't want to display anything.
 | ||||
|             this._downloadImage(); | ||||
|             this.setState({showImage: true}); | ||||
|             this.setState({ showImage: true }); | ||||
|         } | ||||
| 
 | ||||
|         this._afterComponentDidMount(); | ||||
|  | @ -345,7 +346,7 @@ export default class MImageBody extends React.Component { | |||
|                     imageElement = <HiddenImagePlaceholder />; | ||||
|                 } else { | ||||
|                     imageElement = ( | ||||
|                         <img style={{display: 'none'}} src={thumbUrl} ref={this._image} | ||||
|                         <img style={{ display: 'none' }} src={thumbUrl} ref={this._image} | ||||
|                             alt={content.body} | ||||
|                             onError={this.onImageError} | ||||
|                             onLoad={this.onImageLoad} | ||||
|  | @ -449,6 +450,7 @@ export default class MImageBody extends React.Component { | |||
| 
 | ||||
|     // Overidden by MStickerBody
 | ||||
|     getFileBody() { | ||||
|         if (this.props.mediaSrc) return null; | ||||
|         return <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />; | ||||
|     } | ||||
| 
 | ||||
|  | @ -466,7 +468,7 @@ export default class MImageBody extends React.Component { | |||
| 
 | ||||
|         const contentUrl = this._getContentUrl(); | ||||
|         let thumbUrl; | ||||
|         if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) { | ||||
|         if ((this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) || this.props.mediaSrc) { | ||||
|             thumbUrl = contentUrl; | ||||
|         } else { | ||||
|             thumbUrl = this._getThumbUrl(); | ||||
|  |  | |||
|  | @ -29,6 +29,8 @@ interface IProps { | |||
|     mxEvent: any; | ||||
|     /* called when the video has loaded */ | ||||
|     onHeightChanged: () => void; | ||||
|     /* used to refer to the local file while exporting */ | ||||
|     mediaSrc?: string; | ||||
| } | ||||
| 
 | ||||
| interface IState { | ||||
|  | @ -76,6 +78,7 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> { | |||
|     } | ||||
| 
 | ||||
|     private getContentUrl(): string|null { | ||||
|         if (this.props.mediaSrc) return this.props.mediaSrc; | ||||
|         const media = mediaFromContent(this.props.mxEvent.getContent()); | ||||
|         if (media.isEncrypted) { | ||||
|             return this.state.decryptedUrl; | ||||
|  | @ -90,6 +93,9 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> { | |||
|     } | ||||
| 
 | ||||
|     private getThumbUrl(): string|null { | ||||
|         // there's no need of thumbnail when the content is local
 | ||||
|         if (this.props.mediaSrc) return null; | ||||
| 
 | ||||
|         const content = this.props.mxEvent.getContent(); | ||||
|         const media = mediaFromContent(content); | ||||
|         if (media.isEncrypted) { | ||||
|  | @ -184,6 +190,11 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> { | |||
|         this.props.onHeightChanged(); | ||||
|     } | ||||
| 
 | ||||
|     private getFileBody = () => { | ||||
|         if (this.props.mediaSrc) return null; | ||||
|         return <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />; | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const content = this.props.mxEvent.getContent(); | ||||
|         const autoplay = SettingsStore.getValue("autoplayGifsAndVideos"); | ||||
|  | @ -197,7 +208,7 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> { | |||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         // Important: If we aren't autoplaying and we haven't decrypred it yet, show a video with a poster.
 | ||||
|         // Important: If we aren't autoplaying and we haven't decrypted it yet, show a video with a poster.
 | ||||
|         if (content.file !== undefined && this.state.decryptedUrl === null && autoplay) { | ||||
|             // Need to decrypt the attachment
 | ||||
|             // The attachment is decrypted in componentDidMount.
 | ||||
|  | @ -229,6 +240,8 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> { | |||
|                 preload = "none"; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const fileBody = this.getFileBody(); | ||||
|         return ( | ||||
|             <span className="mx_MVideoBody"> | ||||
|                 <video | ||||
|  | @ -246,7 +259,7 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> { | |||
|                     onPlay={this.videoOnPlay} | ||||
|                 > | ||||
|                 </video> | ||||
|                 <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} /> | ||||
|                 { fileBody } | ||||
|             </span> | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ import MVoiceMessageBody from "./MVoiceMessageBody"; | |||
| 
 | ||||
| interface IProps { | ||||
|     mxEvent: MatrixEvent; | ||||
|     mediaSrc?: string; | ||||
| } | ||||
| 
 | ||||
| @replaceableComponent("views.messages.MVoiceOrAudioBody") | ||||
|  | @ -30,7 +31,7 @@ export default class MVoiceOrAudioBody extends React.PureComponent<IProps> { | |||
|     public render() { | ||||
|         const isVoiceMessage = !!this.props.mxEvent.getContent()['org.matrix.msc2516.voice']; | ||||
|         const voiceMessagesEnabled = SettingsStore.getValue("feature_voice_messages"); | ||||
|         if (isVoiceMessage && voiceMessagesEnabled) { | ||||
|         if (isVoiceMessage && voiceMessagesEnabled && !this.props.mediaSrc) { | ||||
|             return <MVoiceMessageBody {...this.props} />; | ||||
|         } else { | ||||
|             return <MAudioBody {...this.props} />; | ||||
|  |  | |||
|  | @ -44,6 +44,9 @@ export default class MessageEvent extends React.Component { | |||
|         /* the shape of the tile, used */ | ||||
|         tileShape: PropTypes.string, | ||||
| 
 | ||||
|         /* to set source to local file path during export */ | ||||
|         mediaSrc: PropTypes.string, | ||||
| 
 | ||||
|         /* the maximum image height to use, if the event is an image */ | ||||
|         maxImageHeight: PropTypes.number, | ||||
| 
 | ||||
|  | @ -120,6 +123,7 @@ export default class MessageEvent extends React.Component { | |||
|             highlightLink={this.props.highlightLink} | ||||
|             showUrlPreview={this.props.showUrlPreview} | ||||
|             tileShape={this.props.tileShape} | ||||
|             mediaSrc={this.props.mediaSrc} | ||||
|             maxImageHeight={this.props.maxImageHeight} | ||||
|             replacingEventId={this.props.replacingEventId} | ||||
|             editState={this.props.editState} | ||||
|  |  | |||
|  | @ -103,7 +103,7 @@ for (const evType of ALL_RULE_TYPES) { | |||
|     stateEventTileTypes[evType] = 'messages.TextualEvent'; | ||||
| } | ||||
| 
 | ||||
| export function getHandlerTile(ev) { | ||||
| export function getHandlerTile(ev: MatrixEvent) { | ||||
|     const type = ev.getType(); | ||||
| 
 | ||||
|     // don't show verification requests we're not involved in,
 | ||||
|  | @ -251,6 +251,9 @@ interface IProps { | |||
| 
 | ||||
|     isExporting?: boolean; | ||||
| 
 | ||||
|     // Used while exporting to refer to the local source rather than the online one
 | ||||
|     mediaSrc?: string; | ||||
| 
 | ||||
|     // show twelve hour timestamps
 | ||||
|     isTwelveHour?: boolean; | ||||
| 
 | ||||
|  | @ -342,7 +345,7 @@ export default class EventTile extends React.Component<IProps, IState> { | |||
|      * or 'sent' receipt, for example. | ||||
|      * @returns {boolean} | ||||
|      */ | ||||
|     private get isEligibleForSpecialReceipt() { | ||||
|     private get isEligibleForSpecialReceipt(): boolean { | ||||
|         // First, if there are other read receipts then just short-circuit this.
 | ||||
|         if (this.props.readReceipts && this.props.readReceipts.length > 0) return false; | ||||
|         if (!this.props.mxEvent) return false; | ||||
|  | @ -1150,6 +1153,7 @@ export default class EventTile extends React.Component<IProps, IState> { | |||
|                                 mxEvent={this.props.mxEvent} | ||||
|                                 replacingEventId={this.props.replacingEventId} | ||||
|                                 editState={this.props.editState} | ||||
|                                 mediaSrc={this.props.mediaSrc} | ||||
|                                 highlights={this.props.highlights} | ||||
|                                 highlightLink={this.props.highlightLink} | ||||
|                                 showUrlPreview={this.props.showUrlPreview} | ||||
|  | @ -1332,8 +1336,8 @@ class SentReceipt extends React.PureComponent<ISentReceiptProps, ISentReceiptSta | |||
| 
 | ||||
|         return <span className="mx_EventTile_readAvatars"> | ||||
|             <span className={receiptClasses} onMouseEnter={this.onHoverStart} onMouseLeave={this.onHoverEnd}> | ||||
|                 {nonCssBadge} | ||||
|                 {tooltip} | ||||
|                 { nonCssBadge } | ||||
|                 { tooltip } | ||||
|             </span> | ||||
|         </span>; | ||||
|     } | ||||
|  |  | |||
|  | @ -20,11 +20,13 @@ import exportJS from "./exportJS"; | |||
| export default class HTMLExporter extends Exporter { | ||||
|     protected zip: JSZip; | ||||
|     protected avatars: Map<string, boolean>; | ||||
|     protected permalinkCreator: RoomPermalinkCreator; | ||||
| 
 | ||||
|     constructor(res: MatrixEvent[], room: Room) { | ||||
|         super(res, room); | ||||
|         this.zip = new JSZip(); | ||||
|         this.avatars = new Map<string, boolean>(); | ||||
|         this.permalinkCreator = new RoomPermalinkCreator(this.room); | ||||
|     } | ||||
| 
 | ||||
|     protected wrapHTML(content: string, room: Room) { | ||||
|  | @ -152,11 +154,12 @@ export default class HTMLExporter extends Exporter { | |||
|         return wantsDateSeparator(prevEvent.getDate(), event.getDate()); | ||||
|     } | ||||
| 
 | ||||
|     protected async createMessageBody(mxEv: MatrixEvent, joined = false) { | ||||
|         const eventTile = <li id={mxEv.getId()}> | ||||
| 
 | ||||
|     protected getEventTile(mxEv: MatrixEvent, continuation: boolean, mediaSrc?: string) { | ||||
|         return <li id={mxEv.getId()}> | ||||
|             <EventTile | ||||
|                 mxEvent={mxEv} | ||||
|                 continuation={joined} | ||||
|                 continuation={continuation} | ||||
|                 isRedacted={mxEv.isRedacted()} | ||||
|                 replacingEventId={mxEv.replacingEventId()} | ||||
|                 isExporting={true} | ||||
|  | @ -166,8 +169,9 @@ export default class HTMLExporter extends Exporter { | |||
|                 checkUnmounting={() => false} | ||||
|                 isTwelveHour={false} | ||||
|                 last={false} | ||||
|                 mediaSrc={mediaSrc} | ||||
|                 lastInSection={false} | ||||
|                 permalinkCreator={new RoomPermalinkCreator(this.room)} | ||||
|                 permalinkCreator={this.permalinkCreator} | ||||
|                 lastSuccessful={false} | ||||
|                 isSelectedEvent={false} | ||||
|                 getRelationsForEvent={null} | ||||
|  | @ -177,6 +181,36 @@ export default class HTMLExporter extends Exporter { | |||
|                 showReadReceipts={false} | ||||
|             /> | ||||
|         </li> | ||||
|     } | ||||
| 
 | ||||
|     protected async createMessageBody(mxEv: MatrixEvent, joined = false) { | ||||
|         let eventTile: JSX.Element; | ||||
|         switch (mxEv.getContent().msgtype) { | ||||
|             case "m.image": { | ||||
|                 const blob = await this.getMediaBlob(mxEv); | ||||
|                 const filePath = `images/${mxEv.getId()}.${blob.type.replace("image/", "")}`; | ||||
|                 eventTile = this.getEventTile(mxEv, joined, filePath); | ||||
|                 this.zip.file(filePath, blob); | ||||
|                 break; | ||||
|             } | ||||
|             case "m.video": { | ||||
|                 const blob = await this.getMediaBlob(mxEv); | ||||
|                 const filePath = `videos/${mxEv.getId()}.${blob.type.replace("video/", "")}`; | ||||
|                 eventTile = this.getEventTile(mxEv, joined, filePath); | ||||
|                 this.zip.file(filePath, blob); | ||||
|                 break; | ||||
|             } | ||||
|             case "m.audio": { | ||||
|                 const blob = await this.getMediaBlob(mxEv); | ||||
|                 const filePath = `audio/${mxEv.getId()}.${blob.type.replace("audio/", "")}`; | ||||
|                 eventTile = this.getEventTile(mxEv, joined, filePath); | ||||
|                 this.zip.file(filePath, blob); | ||||
|                 break; | ||||
|             } | ||||
|             default: | ||||
|                 eventTile = this.getEventTile(mxEv, joined); | ||||
|                 break; | ||||
|         } | ||||
|         return renderToStaticMarkup(eventTile); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Jaiwanth
						Jaiwanth