Start DM on first message (#8612)

This commit is contained in:
Michael Weimann 2022-08-04 08:19:52 +02:00 committed by GitHub
parent 0e0be08781
commit ed8ccb5d80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 482 additions and 65 deletions

View file

@ -0,0 +1,37 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import Spinner from "../views/elements/Spinner";
interface LargeLoaderProps {
text: string;
}
/**
* Loader component that displays a (almost centered) spinner and loading message.
*/
export const LargeLoader: React.FC<LargeLoaderProps> = ({ text }) => {
return (
<div className="mx_LargeLoader">
<Spinner w={45} h={45} />
<div className="mx_LargeLoader_text">
{ text }
</div>
</div>
);
};

View file

@ -20,7 +20,7 @@ limitations under the License.
// TODO: This component is enormous! There's several things which could stand-alone:
// - Search results component
import React, { createRef } from 'react';
import React, { createRef, ReactElement, ReactNode, RefObject, useContext } from 'react';
import classNames from 'classnames';
import { IRecommendedVersion, NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { IThreadBundledRelationship, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
@ -46,7 +46,7 @@ import ResizeNotifier from '../../utils/ResizeNotifier';
import ContentMessages from '../../ContentMessages';
import Modal from '../../Modal';
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
import dis from '../../dispatcher/dispatcher';
import dis, { defaultDispatcher } from '../../dispatcher/dispatcher';
import * as Rooms from '../../Rooms';
import eventSearch, { searchPagination } from '../../Searching';
import MainSplit from './MainSplit';
@ -110,7 +110,15 @@ import FileDropTarget from './FileDropTarget';
import Measured from '../views/elements/Measured';
import { FocusComposerPayload } from '../../dispatcher/payloads/FocusComposerPayload';
import { haveRendererForEvent } from "../../events/EventTileFactory";
import { LocalRoom, LocalRoomState } from '../../models/LocalRoom';
import { createRoomFromLocalRoom } from '../../utils/direct-messages';
import NewRoomIntro from '../views/rooms/NewRoomIntro';
import EncryptionEvent from '../views/messages/EncryptionEvent';
import { StaticNotificationState } from '../../stores/notifications/StaticNotificationState';
import { isLocalRoom } from '../../utils/localRoom/isLocalRoom';
import { ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
import { RoomStatusBarUnsentMessages } from './RoomStatusBarUnsentMessages';
import { LargeLoader } from './LargeLoader';
const DEBUG = false;
let debuglog = function(msg: string) {};
@ -222,6 +230,137 @@ export interface IRoomState {
narrow: boolean;
}
interface LocalRoomViewProps {
resizeNotifier: ResizeNotifier;
permalinkCreator: RoomPermalinkCreator;
roomView: RefObject<HTMLElement>;
onFileDrop: (dataTransfer: DataTransfer) => Promise<void>;
}
/**
* Local room view. Uses only the bits necessary to display a local room view like room header or composer.
*
* @param {LocalRoomViewProps} props Room view props
* @returns {ReactElement}
*/
function LocalRoomView(props: LocalRoomViewProps): ReactElement {
const context = useContext(RoomContext);
const room = context.room as LocalRoom;
const encryptionEvent = context.room.currentState.getStateEvents(EventType.RoomEncryption)[0];
let encryptionTile: ReactNode;
if (encryptionEvent) {
encryptionTile = <EncryptionEvent mxEvent={encryptionEvent} />;
}
const onRetryClicked = () => {
room.state = LocalRoomState.NEW;
defaultDispatcher.dispatch({
action: "local_room_event",
roomId: room.roomId,
});
};
let statusBar: ReactElement;
let composer: ReactElement;
if (room.isError) {
const buttons = (
<AccessibleButton onClick={onRetryClicked} className="mx_RoomStatusBar_unsentRetry">
{ _t("Retry") }
</AccessibleButton>
);
statusBar = <RoomStatusBarUnsentMessages
title={_t("Some of your messages have not been sent")}
notificationState={StaticNotificationState.RED_EXCLAMATION}
buttons={buttons}
/>;
} else {
composer = <MessageComposer
room={context.room}
resizeNotifier={props.resizeNotifier}
permalinkCreator={props.permalinkCreator}
/>;
}
return (
<div className="mx_RoomView mx_RoomView--local">
<ErrorBoundary>
<RoomHeader
room={context.room}
searchInfo={null}
inRoom={true}
onSearchClick={null}
onInviteClick={null}
onForgetClick={null}
e2eStatus={E2EStatus.Normal}
onAppsClick={null}
appsShown={false}
onCallPlaced={null}
excludedRightPanelPhaseButtons={[]}
showButtons={false}
enableRoomOptionsMenu={false}
/>
<main className="mx_RoomView_body" ref={props.roomView}>
<FileDropTarget parent={props.roomView.current} onFileDrop={props.onFileDrop} />
<div className="mx_RoomView_timeline">
<ScrollPanel
className="mx_RoomView_messagePanel"
resizeNotifier={props.resizeNotifier}
>
{ encryptionTile }
<NewRoomIntro />
</ScrollPanel>
</div>
{ statusBar }
{ composer }
</main>
</ErrorBoundary>
</div>
);
}
interface ILocalRoomCreateLoaderProps {
names: string;
resizeNotifier: ResizeNotifier;
}
/**
* Room create loader view displaying a message and a spinner.
*
* @param {ILocalRoomCreateLoaderProps} props Room view props
* @return {ReactElement}
*/
function LocalRoomCreateLoader(props: ILocalRoomCreateLoaderProps): ReactElement {
const context = useContext(RoomContext);
const text = _t("We're creating a room with %(names)s", { names: props.names });
return (
<div className="mx_RoomView mx_RoomView--local">
<ErrorBoundary>
<RoomHeader
room={context.room}
searchInfo={null}
inRoom={true}
onSearchClick={null}
onInviteClick={null}
onForgetClick={null}
e2eStatus={E2EStatus.Normal}
onAppsClick={null}
appsShown={false}
onCallPlaced={null}
excludedRightPanelPhaseButtons={[]}
showButtons={false}
enableRoomOptionsMenu={false}
/>
<div className="mx_RoomView_body">
<LargeLoader text={text} />
</div>
</ErrorBoundary>
</div>
);
}
export class RoomView extends React.Component<IRoomProps, IRoomState> {
private readonly dispatcherRef: string;
private readonly roomStoreToken: EventSubscription;
@ -765,6 +904,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
for (const watcher of this.settingWatchers) {
SettingsStore.unwatchSetting(watcher);
}
if (this.viewsLocalRoom) {
// clean up if this was a local room
this.props.mxClient.store.removeRoom(this.state.room.roomId);
}
}
private onRightPanelStoreUpdate = () => {
@ -873,6 +1017,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.onSearchClick();
break;
case 'local_room_event':
this.onLocalRoomEvent(payload.roomId);
break;
case Action.EditEvent: {
// Quit early if we're trying to edit events in wrong rendering context
if (payload.timelineRenderingType !== this.state.timelineRenderingType) return;
@ -925,6 +1073,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}
};
private onLocalRoomEvent(roomId: string) {
if (roomId !== this.state.room.roomId) return;
createRoomFromLocalRoom(this.props.mxClient, this.state.room as LocalRoom);
}
private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data) => {
if (this.unmounted) return;
@ -1494,7 +1647,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
searchResult={result}
searchHighlights={this.state.searchHighlights}
resultLink={resultLink}
permalinkCreator={this.getPermalinkCreatorForRoom(room)}
permalinkCreator={this.permalinkCreator}
onHeightChanged={onHeightChanged}
/>);
}
@ -1769,7 +1922,44 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.setState({ narrow });
};
private get viewsLocalRoom(): boolean {
return isLocalRoom(this.state.room);
}
private get permalinkCreator(): RoomPermalinkCreator {
return this.getPermalinkCreatorForRoom(this.state.room);
}
private renderLocalRoomCreateLoader(): ReactElement {
const names = this.state.room.getDefaultRoomName(this.props.mxClient.getUserId());
return <RoomContext.Provider value={this.state}>
<LocalRoomCreateLoader
names={names}
resizeNotifier={this.props.resizeNotifier}
/>
</RoomContext.Provider>;
}
private renderLocalRoomView(): ReactElement {
return <RoomContext.Provider value={this.state}>
<LocalRoomView
resizeNotifier={this.props.resizeNotifier}
permalinkCreator={this.permalinkCreator}
roomView={this.roomView}
onFileDrop={this.onFileDrop}
/>
</RoomContext.Provider>;
}
render() {
if (this.state.room instanceof LocalRoom) {
if (this.state.room.state === LocalRoomState.CREATING) {
return this.renderLocalRoomCreateLoader();
}
return this.renderLocalRoomView();
}
if (!this.state.room) {
const loading = !this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading;
if (loading) {
@ -2027,7 +2217,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
e2eStatus={this.state.e2eStatus}
resizeNotifier={this.props.resizeNotifier}
replyToEvent={this.state.replyToEvent}
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)}
permalinkCreator={this.permalinkCreator}
/>;
}
@ -2093,7 +2283,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
showUrlPreview={this.state.showUrlPreview}
className={this.messagePanelClassNames}
membersLoaded={this.state.membersLoaded}
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)}
permalinkCreator={this.permalinkCreator}
resizeNotifier={this.props.resizeNotifier}
showReactions={true}
layout={this.state.layout}
@ -2123,7 +2313,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
? <RightPanel
room={this.state.room}
resizeNotifier={this.props.resizeNotifier}
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)}
permalinkCreator={this.permalinkCreator}
e2eStatus={this.state.e2eStatus} />
: null;
@ -2237,6 +2427,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
appsShown={this.state.showApps}
onCallPlaced={onCallPlaced}
excludedRightPanelPhaseButtons={excludedRightPanelPhaseButtons}
showButtons={!this.viewsLocalRoom}
enableRoomOptionsMenu={!this.viewsLocalRoom}
/>
<MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
<div className={mainSplitContentClasses} ref={this.roomViewBody} data-layout={this.state.layout}>