Make attachments uploadable to a thread (#7064)

This commit is contained in:
Germain 2021-11-03 08:43:24 +00:00 committed by GitHub
parent 4b66d4a891
commit 801eb068d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 66 additions and 16 deletions

View file

@ -23,6 +23,12 @@ limitations under the License.
} }
} }
.mx_ThreadView {
.mx_UploadBar {
padding-left: 0;
}
}
.mx_UploadBar_filename { .mx_UploadBar_filename {
margin-top: 5px; margin-top: 5px;
color: $muted-fg-color; color: $muted-fg-color;

View file

@ -46,6 +46,7 @@ import SettingsStore from "./settings/SettingsStore";
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics"; import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { IEventRelation } from "matrix-js-sdk/src";
const MAX_WIDTH = 800; const MAX_WIDTH = 800;
const MAX_HEIGHT = 600; const MAX_HEIGHT = 600;
@ -436,7 +437,12 @@ export default class ContentMessages {
} }
} }
async sendContentListToRoom(files: File[], roomId: string, matrixClient: MatrixClient) { async sendContentListToRoom(
files: File[],
roomId: string,
relation: IEventRelation | null,
matrixClient: MatrixClient,
) {
if (matrixClient.isGuest()) { if (matrixClient.isGuest()) {
dis.dispatch({ action: 'require_registration' }); dis.dispatch({ action: 'require_registration' });
return; return;
@ -512,11 +518,20 @@ export default class ContentMessages {
uploadAll = true; uploadAll = true;
} }
} }
promBefore = this.sendContentToRoom(file, roomId, matrixClient, promBefore); promBefore = this.sendContentToRoom(file, roomId, relation, matrixClient, promBefore);
} }
} }
getCurrentUploads() { getCurrentUploads(relation?: IEventRelation) {
return this.inprogress.filter(upload => {
const noRelation = !relation && !upload.relation;
const matchingRelation = relation && upload.relation
&& relation.rel_type === upload.relation.rel_type
&& relation.event_id === upload.relation.event_id;
return (noRelation || matchingRelation) && !upload.canceled;
});
return this.inprogress.filter(u => !u.canceled); return this.inprogress.filter(u => !u.canceled);
} }
@ -535,7 +550,13 @@ export default class ContentMessages {
} }
} }
private sendContentToRoom(file: File, roomId: string, matrixClient: MatrixClient, promBefore: Promise<any>) { private sendContentToRoom(
file: File,
roomId: string,
relation: IEventRelation,
matrixClient: MatrixClient,
promBefore: Promise<any>,
) {
const startTime = CountlyAnalytics.getTimestamp(); const startTime = CountlyAnalytics.getTimestamp();
const content: IContent = { const content: IContent = {
body: file.name || 'Attachment', body: file.name || 'Attachment',
@ -545,6 +566,10 @@ export default class ContentMessages {
msgtype: "", // set later msgtype: "", // set later
}; };
if (relation) {
content["m.relates_to"] = relation;
}
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) { if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
decorateStartSendingTime(content); decorateStartSendingTime(content);
} }
@ -590,7 +615,8 @@ export default class ContentMessages {
const upload: IUpload = { const upload: IUpload = {
fileName: file.name || 'Attachment', fileName: file.name || 'Attachment',
roomId: roomId, roomId,
relation,
total: file.size, total: file.size,
loaded: 0, loaded: 0,
promise: prom, promise: prom,

View file

@ -786,7 +786,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
break; break;
case 'picture_snapshot': case 'picture_snapshot':
ContentMessages.sharedInstance().sendContentListToRoom( ContentMessages.sharedInstance().sendContentListToRoom(
[payload.file], this.state.room.roomId, this.context); [payload.file], this.state.room.roomId, null, this.context);
break; break;
case 'notifier_enabled': case 'notifier_enabled':
case Action.UploadStarted: case Action.UploadStarted:
@ -1292,7 +1292,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
ContentMessages.sharedInstance().sendContentListToRoom( ContentMessages.sharedInstance().sendContentListToRoom(
ev.dataTransfer.files, this.state.room.roomId, this.context, ev.dataTransfer.files, this.state.room.roomId, null, this.context,
); );
dis.fire(Action.FocusSendMessageComposer); dis.fire(Action.FocusSendMessageComposer);

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import { MatrixEvent, Room } from 'matrix-js-sdk/src'; import { IEventRelation, MatrixEvent, Room } from 'matrix-js-sdk/src';
import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread'; import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
import { RelationType } from 'matrix-js-sdk/src/@types/event'; import { RelationType } from 'matrix-js-sdk/src/@types/event';
@ -37,6 +37,8 @@ import { MatrixClientPeg } from '../../MatrixClientPeg';
import { E2EStatus } from '../../utils/ShieldUtils'; import { E2EStatus } from '../../utils/ShieldUtils';
import EditorStateTransfer from '../../utils/EditorStateTransfer'; import EditorStateTransfer from '../../utils/EditorStateTransfer';
import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext'; import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';
import ContentMessages from '../../ContentMessages';
import UploadBar from './UploadBar';
import { ChevronFace, ContextMenuTooltipButton } from './ContextMenu'; import { ChevronFace, ContextMenuTooltipButton } from './ContextMenu';
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
import IconizedContextMenu, { import IconizedContextMenu, {
@ -304,6 +306,12 @@ export default class ThreadView extends React.Component<IProps, IState> {
const highlightedEventId = this.props.initialEventHighlighted const highlightedEventId = this.props.initialEventHighlighted
? this.props.initialEvent?.getId() ? this.props.initialEvent?.getId()
: null; : null;
const threadRelation: IEventRelation = {
rel_type: RelationType.Thread,
event_id: this.state.thread?.id,
};
return ( return (
<RoomContext.Provider value={{ <RoomContext.Provider value={{
...this.context, ...this.context,
@ -343,13 +351,14 @@ export default class ThreadView extends React.Component<IProps, IState> {
/> />
) } ) }
{ ContentMessages.sharedInstance().getCurrentUploads(threadRelation).length > 0 && (
<UploadBar room={this.props.room} relation={threadRelation} />
) }
{ this.state?.thread?.timelineSet && (<MessageComposer { this.state?.thread?.timelineSet && (<MessageComposer
room={this.props.room} room={this.props.room}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
relation={{ relation={threadRelation}
rel_type: RelationType.Thread,
event_id: this.state.thread.id,
}}
replyToEvent={this.state.replyToEvent} replyToEvent={this.state.replyToEvent}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus} e2eStatus={this.props.e2eStatus}

View file

@ -27,9 +27,11 @@ import AccessibleButton from "../views/elements/AccessibleButton";
import { IUpload } from "../../models/IUpload"; import { IUpload } from "../../models/IUpload";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import { IEventRelation } from 'matrix-js-sdk/src';
interface IProps { interface IProps {
room: Room; room: Room;
relation?: IEventRelation;
} }
interface IState { interface IState {
@ -64,7 +66,7 @@ export default class UploadBar extends React.Component<IProps, IState> {
} }
private getUploadsInRoom(): IUpload[] { private getUploadsInRoom(): IUpload[] {
const uploads = ContentMessages.sharedInstance().getCurrentUploads(); const uploads = ContentMessages.sharedInstance().getCurrentUploads(this.props.relation);
return uploads.filter(u => u.roomId === this.props.room.roomId); return uploads.filter(u => u.roomId === this.props.room.roomId);
} }

View file

@ -128,6 +128,7 @@ const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition, narr
interface IUploadButtonProps { interface IUploadButtonProps {
roomId: string; roomId: string;
relation?: IEventRelation | null;
} }
class UploadButton extends React.Component<IUploadButtonProps> { class UploadButton extends React.Component<IUploadButtonProps> {
@ -169,7 +170,7 @@ class UploadButton extends React.Component<IUploadButtonProps> {
} }
ContentMessages.sharedInstance().sendContentListToRoom( ContentMessages.sharedInstance().sendContentListToRoom(
tfiles, this.props.roomId, MatrixClientPeg.get(), tfiles, this.props.roomId, this.props.relation, MatrixClientPeg.get(),
); );
// This is the onChange handler for a file form control, but we're // This is the onChange handler for a file form control, but we're
@ -479,7 +480,11 @@ export default class MessageComposer extends React.Component<IProps, IState> {
); );
} }
buttons.push( buttons.push(
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />, <UploadButton
key="controls_upload"
roomId={this.props.room.roomId}
relation={this.props.relation}
/>,
); );
buttons.push( buttons.push(
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} menuPosition={menuPosition} narrowMode={this.state.narrowMode} />, <EmojiButton key="emoji_button" addEmoji={this.addEmoji} menuPosition={menuPosition} narrowMode={this.state.narrowMode} />,

View file

@ -615,7 +615,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// it puts the filename in as text/plain which we want to ignore. // it puts the filename in as text/plain which we want to ignore.
if (clipboardData.files.length && !clipboardData.types.includes("text/rtf")) { if (clipboardData.files.length && !clipboardData.types.includes("text/rtf")) {
ContentMessages.sharedInstance().sendContentListToRoom( ContentMessages.sharedInstance().sendContentListToRoom(
Array.from(clipboardData.files), this.props.room.roomId, this.props.mxClient, Array.from(clipboardData.files), this.props.room.roomId, this.props.relation, this.props.mxClient,
); );
return true; // to skip internal onPaste handler return true; // to skip internal onPaste handler
} }

View file

@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IEventRelation } from "matrix-js-sdk/src";
import { IAbortablePromise } from "matrix-js-sdk/src/@types/partials"; import { IAbortablePromise } from "matrix-js-sdk/src/@types/partials";
export interface IUpload { export interface IUpload {
fileName: string; fileName: string;
roomId: string; roomId: string;
relation?: IEventRelation;
total: number; total: number;
loaded: number; loaded: number;
promise: IAbortablePromise<any>; promise: IAbortablePromise<any>;