Make attachments uploadable to a thread (#7064)
This commit is contained in:
parent
4b66d4a891
commit
801eb068d6
8 changed files with 66 additions and 16 deletions
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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} />,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
Loading…
Reference in a new issue