Enable support for image, video and audio files

pull/21833/head
Jaiwanth 2021-05-31 21:01:19 +05:30
parent 409213ceb4
commit 59c1b67b7d
8 changed files with 86 additions and 21 deletions

View File

@ -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>
);
}

View File

@ -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>;
}

View File

@ -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();

View File

@ -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>
);
}

View File

@ -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} />;

View File

@ -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}

View File

@ -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>;
}

View File

@ -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);
}