Merge branch 'develop' into travis/remove-skinning
This commit is contained in:
commit
4057833036
74 changed files with 1412 additions and 1717 deletions
|
@ -491,7 +491,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
* when we are at the sync stage
|
||||
*/
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
const thread = room?.threads.get(this.props.mxEvent.getId());
|
||||
const thread = room?.threads?.get(this.props.mxEvent.getId());
|
||||
|
||||
return thread || null;
|
||||
}
|
||||
|
@ -510,12 +510,22 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private renderThreadInfo(): React.ReactNode {
|
||||
if (this.state.thread?.id === this.props.mxEvent.getId()) {
|
||||
return <ThreadSummary mxEvent={this.props.mxEvent} thread={this.state.thread} />;
|
||||
}
|
||||
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Search && this.props.mxEvent.threadRootId) {
|
||||
if (this.props.highlightLink) {
|
||||
return (
|
||||
<a className="mx_ThreadSummaryIcon" href={this.props.highlightLink}>
|
||||
{ _t("From a thread") }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<p className="mx_ThreadSummaryIcon">{ _t("From a thread") }</p>
|
||||
);
|
||||
} else if (this.state.thread?.id === this.props.mxEvent.getId()) {
|
||||
return <ThreadSummary mxEvent={this.props.mxEvent} thread={this.state.thread} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -978,6 +988,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
let isContinuation = this.props.continuation;
|
||||
if (this.context.timelineRenderingType !== TimelineRenderingType.Room &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Search &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Thread &&
|
||||
this.props.layout !== Layout.Bubble
|
||||
) {
|
||||
isContinuation = false;
|
||||
|
@ -1024,16 +1035,17 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
? undefined
|
||||
: this.props.mxEvent.getId();
|
||||
|
||||
let avatar;
|
||||
let sender;
|
||||
let avatarSize;
|
||||
let needsSenderProfile;
|
||||
let avatar: JSX.Element;
|
||||
let sender: JSX.Element;
|
||||
let avatarSize: number;
|
||||
let needsSenderProfile: boolean;
|
||||
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Notification ||
|
||||
this.context.timelineRenderingType === TimelineRenderingType.ThreadsList
|
||||
) {
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Notification) {
|
||||
avatarSize = 24;
|
||||
needsSenderProfile = true;
|
||||
} else if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
|
||||
avatarSize = 36;
|
||||
needsSenderProfile = true;
|
||||
} else if (eventType === EventType.RoomCreate || isBubbleMessage) {
|
||||
avatarSize = 0;
|
||||
needsSenderProfile = false;
|
||||
|
@ -1281,9 +1293,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
</div>,
|
||||
<div className="mx_EventTile_senderDetails" key="mx_EventTile_senderDetails">
|
||||
{ avatar }
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ sender }
|
||||
</a>
|
||||
{ sender }
|
||||
</div>,
|
||||
<div className={lineClasses} key="mx_EventTile_line">
|
||||
{ replyChain }
|
||||
|
@ -1301,7 +1311,9 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
permalinkCreator: this.props.permalinkCreator,
|
||||
}) }
|
||||
{ actionBar }
|
||||
{ timestamp }
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ timestamp }
|
||||
</a>
|
||||
</div>,
|
||||
reactionsRow,
|
||||
]);
|
||||
|
|
|
@ -204,6 +204,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
const buttons: JSX.Element[] = [];
|
||||
|
||||
if (this.props.inRoom &&
|
||||
this.props.onCallPlaced &&
|
||||
!this.context.tombstone &&
|
||||
SettingsStore.getValue("showCallButtonsInComposer")
|
||||
) {
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React, { ComponentType, createRef, ReactComponentElement, RefObject } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
||||
import * as fbEmitter from "fbemitter";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
|
@ -221,8 +222,8 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => {
|
|||
showCreateRoom
|
||||
? (<>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Create new room")}
|
||||
iconClassName="mx_RoomList_iconCreateNewRoom"
|
||||
label={_t("New room")}
|
||||
iconClassName="mx_RoomList_iconNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -234,6 +235,19 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => {
|
|||
tooltip={canAddRooms ? undefined
|
||||
: _t("You do not have permissions to create new rooms in this space")}
|
||||
/>
|
||||
{ SettingsStore.getValue("feature_video_rooms") && <IconizedContextMenuOption
|
||||
label={_t("New video room")}
|
||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
showCreateNewRoom(activeSpace, RoomType.ElementVideo);
|
||||
}}
|
||||
disabled={!canAddRooms}
|
||||
tooltip={canAddRooms ? undefined
|
||||
: _t("You do not have permissions to create new rooms in this space")}
|
||||
/> }
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Add existing room")}
|
||||
iconClassName="mx_RoomList_iconAddExistingRoom"
|
||||
|
@ -253,17 +267,32 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => {
|
|||
</IconizedContextMenuOptionList>;
|
||||
} else if (menuDisplayed) {
|
||||
contextMenuContent = <IconizedContextMenuOptionList first>
|
||||
{ showCreateRoom && <IconizedContextMenuOption
|
||||
label={_t("Create new room")}
|
||||
iconClassName="mx_RoomList_iconCreateNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.dispatch({ action: "view_create_room" });
|
||||
PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e);
|
||||
}}
|
||||
/> }
|
||||
{ showCreateRoom && <>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("New room")}
|
||||
iconClassName="mx_RoomList_iconNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.dispatch({ action: "view_create_room" });
|
||||
PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e);
|
||||
}}
|
||||
/>
|
||||
{ SettingsStore.getValue("feature_video_rooms") && <IconizedContextMenuOption
|
||||
label={_t("New video room")}
|
||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_create_room",
|
||||
type: RoomType.ElementVideo,
|
||||
});
|
||||
}}
|
||||
/> }
|
||||
</> }
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore public rooms")}
|
||||
iconClassName="mx_RoomList_iconExplore"
|
||||
|
|
|
@ -16,11 +16,12 @@ limitations under the License.
|
|||
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/client";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { useEventEmitterState, useTypedEventEmitter, useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
|
||||
|
@ -127,6 +128,7 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
|
|||
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
|
||||
return SpaceStore.instance.allRoomsInHome;
|
||||
});
|
||||
const videoRoomsEnabled = useFeatureEnabled("feature_video_rooms");
|
||||
const pendingActions = usePendingActions();
|
||||
|
||||
const filterCondition = RoomListStore.instance.getFirstNameFilterCondition();
|
||||
|
@ -195,19 +197,31 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
|
|||
/>;
|
||||
}
|
||||
|
||||
let createNewRoomOption: JSX.Element;
|
||||
let newRoomOptions: JSX.Element;
|
||||
if (activeSpace?.currentState.maySendStateEvent(EventType.RoomAvatar, cli.getUserId())) {
|
||||
createNewRoomOption = <IconizedContextMenuOption
|
||||
iconClassName="mx_RoomListHeader_iconCreateRoom"
|
||||
label={_t("Create new room")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showCreateNewRoom(activeSpace);
|
||||
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e);
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>;
|
||||
newRoomOptions = <>
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_RoomListHeader_iconNewRoom"
|
||||
label={_t("New room")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showCreateNewRoom(activeSpace);
|
||||
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e);
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>
|
||||
{ videoRoomsEnabled && <IconizedContextMenuOption
|
||||
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
||||
label={_t("New video room")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showCreateNewRoom(activeSpace, RoomType.ElementVideo);
|
||||
closePlusMenu();
|
||||
}}
|
||||
/> }
|
||||
</>;
|
||||
}
|
||||
|
||||
contextMenu = <IconizedContextMenu
|
||||
|
@ -217,7 +231,7 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
|
|||
>
|
||||
<IconizedContextMenuOptionList first>
|
||||
{ inviteOption }
|
||||
{ createNewRoomOption }
|
||||
{ newRoomOptions }
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore rooms")}
|
||||
iconClassName="mx_RoomListHeader_iconExplore"
|
||||
|
@ -262,12 +276,11 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
|
|||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
} else if (plusMenuDisplayed) {
|
||||
let startChatOpt: JSX.Element;
|
||||
let createRoomOpt: JSX.Element;
|
||||
let newRoomOpts: JSX.Element;
|
||||
let joinRoomOpt: JSX.Element;
|
||||
|
||||
if (canCreateRooms) {
|
||||
startChatOpt = (
|
||||
newRoomOpts = <>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Start new chat")}
|
||||
iconClassName="mx_RoomListHeader_iconStartChat"
|
||||
|
@ -278,11 +291,9 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
|
|||
closePlusMenu();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
createRoomOpt = (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Create new room")}
|
||||
iconClassName="mx_RoomListHeader_iconCreateRoom"
|
||||
label={_t("New room")}
|
||||
iconClassName="mx_RoomListHeader_iconNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -291,7 +302,20 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
|
|||
closePlusMenu();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
{ videoRoomsEnabled && <IconizedContextMenuOption
|
||||
label={_t("New video room")}
|
||||
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_create_room",
|
||||
type: RoomType.ElementVideo,
|
||||
});
|
||||
closePlusMenu();
|
||||
}}
|
||||
/> }
|
||||
</>;
|
||||
}
|
||||
if (canExploreRooms) {
|
||||
joinRoomOpt = (
|
||||
|
@ -314,8 +338,7 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
|
|||
compact
|
||||
>
|
||||
<IconizedContextMenuOptionList first>
|
||||
{ startChatOpt }
|
||||
{ createRoomOpt }
|
||||
{ newRoomOpts }
|
||||
{ joinRoomOpt }
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
|
|
|
@ -32,10 +32,7 @@ import { _t } from "../../../languageHandler";
|
|||
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
||||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
import FacePile from "../elements/FacePile";
|
||||
import { RoomNotifState } from "../../../RoomNotifs";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import NotificationBadge from "./NotificationBadge";
|
||||
|
@ -54,17 +51,16 @@ import IconizedContextMenu, {
|
|||
IconizedContextMenuOptionList,
|
||||
IconizedContextMenuRadio,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import VoiceChannelStore, { VoiceChannelEvent, IJitsiParticipant } from "../../../stores/VoiceChannelStore";
|
||||
import { getConnectedMembers } from "../../../utils/VoiceChannelUtils";
|
||||
import VideoChannelStore, { VideoChannelEvent, IJitsiParticipant } from "../../../stores/VideoChannelStore";
|
||||
import { getConnectedMembers } from "../../../utils/VideoChannelUtils";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
|
||||
enum VoiceConnectionState {
|
||||
enum VideoStatus {
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected,
|
||||
}
|
||||
|
||||
|
@ -82,10 +78,10 @@ interface IState {
|
|||
notificationsMenuPosition: PartialDOMRect;
|
||||
generalMenuPosition: PartialDOMRect;
|
||||
messagePreview?: string;
|
||||
voiceConnectionState: VoiceConnectionState;
|
||||
// Active voice channel members, according to room state
|
||||
voiceMembers: RoomMember[];
|
||||
// Active voice channel members, according to Jitsi
|
||||
videoStatus: VideoStatus;
|
||||
// Active video channel members, according to room state
|
||||
videoMembers: RoomMember[];
|
||||
// Active video channel members, according to Jitsi
|
||||
jitsiParticipants: IJitsiParticipant[];
|
||||
}
|
||||
|
||||
|
@ -104,27 +100,28 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
private roomTileRef = createRef<HTMLDivElement>();
|
||||
private notificationState: NotificationState;
|
||||
private roomProps: RoomEchoChamber;
|
||||
private isVoiceRoom: boolean;
|
||||
private isVideoRoom: boolean;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const videoConnected = VideoChannelStore.instance.roomId === this.props.room.roomId;
|
||||
|
||||
this.state = {
|
||||
selected: RoomViewStore.instance.getRoomId() === this.props.room.roomId,
|
||||
notificationsMenuPosition: null,
|
||||
generalMenuPosition: null,
|
||||
// generatePreview() will return nothing if the user has previews disabled
|
||||
messagePreview: "",
|
||||
voiceConnectionState: VoiceChannelStore.instance.roomId === this.props.room.roomId ?
|
||||
VoiceConnectionState.Connected : VoiceConnectionState.Disconnected,
|
||||
voiceMembers: [],
|
||||
jitsiParticipants: [],
|
||||
videoStatus: videoConnected ? VideoStatus.Connected : VideoStatus.Disconnected,
|
||||
videoMembers: getConnectedMembers(this.props.room.currentState),
|
||||
jitsiParticipants: videoConnected ? VideoChannelStore.instance.participants : [],
|
||||
};
|
||||
this.generatePreview();
|
||||
|
||||
this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room);
|
||||
this.roomProps = EchoChamber.forRoom(this.props.room);
|
||||
this.isVoiceRoom = SettingsStore.getValue("feature_voice_rooms") && this.props.room.isCallRoom();
|
||||
this.isVideoRoom = SettingsStore.getValue("feature_video_rooms") && this.props.room.isElementVideoRoom();
|
||||
}
|
||||
|
||||
private onRoomNameUpdate = (room: Room) => {
|
||||
|
@ -163,8 +160,9 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
||||
this.onRoomPreviewChanged,
|
||||
);
|
||||
prevProps.room?.currentState?.off(RoomStateEvent.Events, this.updateVoiceMembers);
|
||||
this.props.room?.currentState?.on(RoomStateEvent.Events, this.updateVoiceMembers);
|
||||
prevProps.room?.currentState?.off(RoomStateEvent.Events, this.updateVideoMembers);
|
||||
this.props.room?.currentState?.on(RoomStateEvent.Events, this.updateVideoMembers);
|
||||
this.updateVideoStatus();
|
||||
prevProps.room?.off(RoomEvent.Name, this.onRoomNameUpdate);
|
||||
this.props.room?.on(RoomEvent.Name, this.onRoomNameUpdate);
|
||||
}
|
||||
|
@ -175,7 +173,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
if (this.state.selected) {
|
||||
this.scrollIntoView();
|
||||
}
|
||||
this.updateVoiceMembers();
|
||||
|
||||
RoomViewStore.instance.addRoomListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||
|
@ -186,7 +183,13 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
this.notificationState.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
||||
this.props.room.on(RoomEvent.Name, this.onRoomNameUpdate);
|
||||
this.props.room.currentState.on(RoomStateEvent.Events, this.updateVoiceMembers);
|
||||
this.props.room.currentState.on(RoomStateEvent.Events, this.updateVideoMembers);
|
||||
|
||||
VideoChannelStore.instance.on(VideoChannelEvent.Connect, this.updateVideoStatus);
|
||||
VideoChannelStore.instance.on(VideoChannelEvent.Disconnect, this.updateVideoStatus);
|
||||
if (VideoChannelStore.instance.roomId === this.props.room.roomId) {
|
||||
VideoChannelStore.instance.on(VideoChannelEvent.Participants, this.updateJitsiParticipants);
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
@ -200,6 +203,9 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
this.notificationState.off(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||
this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
||||
|
||||
VideoChannelStore.instance.off(VideoChannelEvent.Connect, this.updateVideoStatus);
|
||||
VideoChannelStore.instance.off(VideoChannelEvent.Disconnect, this.updateVideoStatus);
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload) => {
|
||||
|
@ -250,11 +256,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
metricsTrigger: "RoomList",
|
||||
metricsViaKeyboard: ev.type !== "click",
|
||||
});
|
||||
|
||||
// Connect to the voice channel if this is a voice room
|
||||
if (this.isVoiceRoom && this.state.voiceConnectionState === VoiceConnectionState.Disconnected) {
|
||||
await this.connectVoice();
|
||||
}
|
||||
};
|
||||
|
||||
private onActiveRoomUpdate = (isActive: boolean) => {
|
||||
|
@ -579,87 +580,24 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
private updateVoiceMembers = () => {
|
||||
this.setState({ voiceMembers: getConnectedMembers(this.props.room.currentState) });
|
||||
private updateVideoMembers = () => {
|
||||
this.setState({ videoMembers: getConnectedMembers(this.props.room.currentState) });
|
||||
};
|
||||
|
||||
private updateVideoStatus = () => {
|
||||
if (VideoChannelStore.instance.roomId === this.props.room?.roomId) {
|
||||
this.setState({ videoStatus: VideoStatus.Connected });
|
||||
VideoChannelStore.instance.on(VideoChannelEvent.Participants, this.updateJitsiParticipants);
|
||||
} else {
|
||||
this.setState({ videoStatus: VideoStatus.Disconnected });
|
||||
VideoChannelStore.instance.off(VideoChannelEvent.Participants, this.updateJitsiParticipants);
|
||||
}
|
||||
};
|
||||
|
||||
private updateJitsiParticipants = (participants: IJitsiParticipant[]) => {
|
||||
this.setState({ jitsiParticipants: participants });
|
||||
};
|
||||
|
||||
private renderVoiceChannel(): React.ReactElement | null {
|
||||
let faces;
|
||||
if (this.state.voiceConnectionState === VoiceConnectionState.Connected) {
|
||||
faces = this.state.jitsiParticipants.map(p =>
|
||||
<BaseAvatar
|
||||
key={p.participantId}
|
||||
name={p.displayName ?? p.formattedDisplayName}
|
||||
idName={p.participantId}
|
||||
// This comes directly from Jitsi, so we shouldn't apply custom media routing to it
|
||||
url={p.avatarURL}
|
||||
width={24}
|
||||
height={24}
|
||||
/>,
|
||||
);
|
||||
} else if (this.state.voiceMembers.length) {
|
||||
faces = this.state.voiceMembers.map(m =>
|
||||
<MemberAvatar
|
||||
key={m.userId}
|
||||
member={m}
|
||||
width={24}
|
||||
height={24}
|
||||
/>,
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: The below "join" button will eventually show up on text rooms
|
||||
// with an active voice channel, but that isn't implemented yet
|
||||
return <div className="mx_RoomTile_voiceChannel">
|
||||
<FacePile faces={faces} overflow={false} />
|
||||
{ this.isVoiceRoom ? null : (
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
className="mx_RoomTile_connectVoiceButton"
|
||||
onClick={this.connectVoice.bind(this)}
|
||||
>
|
||||
{ _t("Join") }
|
||||
</AccessibleButton>
|
||||
) }
|
||||
</div>;
|
||||
}
|
||||
|
||||
private async connectVoice() {
|
||||
this.setState({ voiceConnectionState: VoiceConnectionState.Connecting });
|
||||
// TODO: Actually wait for the widget to be ready, instead of guessing.
|
||||
// This hack is only in place until we find out for sure whether design
|
||||
// wants the room view to open when connecting voice, or if this should
|
||||
// somehow connect in the background. Until then, it's not worth the
|
||||
// effort to solve this properly.
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const waitForConnect = VoiceChannelStore.instance.connect(this.props.room.roomId);
|
||||
// Participant data comes down the event channel quickly, so prepare in advance
|
||||
VoiceChannelStore.instance.on(VoiceChannelEvent.Participants, this.updateJitsiParticipants);
|
||||
try {
|
||||
await waitForConnect;
|
||||
this.setState({ voiceConnectionState: VoiceConnectionState.Connected });
|
||||
|
||||
VoiceChannelStore.instance.once(VoiceChannelEvent.Disconnect, () => {
|
||||
this.setState({
|
||||
voiceConnectionState: VoiceConnectionState.Disconnected,
|
||||
jitsiParticipants: [],
|
||||
}),
|
||||
VoiceChannelStore.instance.off(VoiceChannelEvent.Participants, this.updateJitsiParticipants);
|
||||
});
|
||||
} catch (e) {
|
||||
// If it failed, clean up our advance preparations
|
||||
logger.error("Failed to connect voice", e);
|
||||
VoiceChannelStore.instance.off(VoiceChannelEvent.Participants, this.updateJitsiParticipants);
|
||||
}
|
||||
}
|
||||
|
||||
public render(): React.ReactElement {
|
||||
const classes = classNames({
|
||||
'mx_RoomTile': true,
|
||||
|
@ -687,34 +625,44 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
let subtitle;
|
||||
if (this.isVoiceRoom) {
|
||||
switch (this.state.voiceConnectionState) {
|
||||
case VoiceConnectionState.Disconnected:
|
||||
subtitle = (
|
||||
<div className="mx_RoomTile_subtitle mx_RoomTile_voiceIndicator">
|
||||
{ _t("Voice room") }
|
||||
</div>
|
||||
);
|
||||
if (this.isVideoRoom) {
|
||||
let videoText: string;
|
||||
let videoActive: boolean;
|
||||
let participantCount: number;
|
||||
|
||||
switch (this.state.videoStatus) {
|
||||
case VideoStatus.Disconnected:
|
||||
videoText = _t("Video");
|
||||
videoActive = false;
|
||||
participantCount = this.state.videoMembers.length;
|
||||
break;
|
||||
case VoiceConnectionState.Connecting:
|
||||
subtitle = (
|
||||
<div className="mx_RoomTile_subtitle mx_RoomTile_voiceIndicator">
|
||||
{ _t("Connecting...") }
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case VoiceConnectionState.Connected:
|
||||
subtitle = (
|
||||
<div
|
||||
className={
|
||||
"mx_RoomTile_subtitle mx_RoomTile_voiceIndicator " +
|
||||
"mx_RoomTile_voiceIndicator_active"
|
||||
}
|
||||
>
|
||||
{ _t("Connected") }
|
||||
</div>
|
||||
);
|
||||
case VideoStatus.Connected:
|
||||
videoText = _t("Connected");
|
||||
videoActive = true;
|
||||
participantCount = this.state.jitsiParticipants.length;
|
||||
}
|
||||
|
||||
subtitle = (
|
||||
<div className="mx_RoomTile_subtitle">
|
||||
<span
|
||||
className={classNames({
|
||||
"mx_RoomTile_videoIndicator": true,
|
||||
"mx_RoomTile_videoIndicator_active": videoActive,
|
||||
})}
|
||||
>
|
||||
{ videoText }
|
||||
</span>
|
||||
{ participantCount ? <>
|
||||
{ " · " }
|
||||
<span
|
||||
className="mx_RoomTile_videoParticipants"
|
||||
aria-label={_t("%(count)s participants", { count: participantCount })}
|
||||
>
|
||||
{ participantCount }
|
||||
</span>
|
||||
</> : null }
|
||||
</div>
|
||||
);
|
||||
} else if (this.showMessagePreview && this.state.messagePreview) {
|
||||
subtitle = (
|
||||
<div
|
||||
|
@ -795,15 +743,10 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
displayBadge={this.props.isMinimized}
|
||||
tooltipProps={{ tabIndex: isActive ? 0 : -1 }}
|
||||
/>
|
||||
<div className="mx_RoomTile_details">
|
||||
<div className="mx_RoomTile_primaryDetails">
|
||||
{ titleContainer }
|
||||
{ badge }
|
||||
{ this.renderGeneralMenu() }
|
||||
{ this.renderNotificationsMenu(isActive) }
|
||||
</div>
|
||||
{ this.renderVoiceChannel() }
|
||||
</div>
|
||||
{ titleContainer }
|
||||
{ badge }
|
||||
{ this.renderGeneralMenu() }
|
||||
{ this.renderNotificationsMenu(isActive) }
|
||||
</Button>
|
||||
}
|
||||
</RovingTabIndexWrapper>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue