New group call experience: Room header and PiP designs (#9351)
* Update our cancel icon The cancel icon we're using in the app has drifted out of sync with the ones used in our designs. We also had two identical-looking icons, so this consolidates them into one. I've simultaneously updated our chevron icons, since in the case of the 'jump to unread' timeline button, it became clear that the weight of the new close icon did not match the thinner chevron. * Don't squish bottom/top-aligned tooltips near the edge of the screen * Close the timeline panel when returning to the fullscreen timeline view * Add layout switching capabilities to ElementCall * Bring the room header in line with the group call designs * Bring the PiP header in line with the group call designs * Fix lints * Clarify tooltip CSS calculations * Test PipView * Expand RoomHeader test coverage * Test PipView more
This commit is contained in:
parent
9a3ae2398e
commit
06dbea6255
43 changed files with 845 additions and 220 deletions
|
@ -493,7 +493,6 @@ export class EmailIdentityAuthEntry extends
|
|||
? _t("Resent!")
|
||||
: _t("Resend")}
|
||||
alignment={Alignment.Right}
|
||||
tooltipClassName="mx_Tooltip_noMargin"
|
||||
onHideTooltip={this.state.requested
|
||||
? () => this.setState({ requested: false })
|
||||
: undefined}
|
||||
|
|
|
@ -149,18 +149,24 @@ export default class Tooltip extends React.PureComponent<ITooltipProps, State> {
|
|||
break;
|
||||
case Alignment.Top:
|
||||
style.top = baseTop - spacing;
|
||||
style.left = horizontalCenter;
|
||||
style.transform = "translate(-50%, -100%)";
|
||||
// Attempt to center the tooltip on the element while clamping
|
||||
// its horizontal translation to keep it on screen
|
||||
// eslint-disable-next-line max-len
|
||||
style.transform = `translate(max(10px, min(calc(${horizontalCenter}px - 50%), calc(100vw - 100% - 10px))), -100%)`;
|
||||
break;
|
||||
case Alignment.Bottom:
|
||||
style.top = baseTop + parentBox.height + spacing;
|
||||
style.left = horizontalCenter;
|
||||
style.transform = "translate(-50%)";
|
||||
// Attempt to center the tooltip on the element while clamping
|
||||
// its horizontal translation to keep it on screen
|
||||
// eslint-disable-next-line max-len
|
||||
style.transform = `translate(max(10px, min(calc(${horizontalCenter}px - 50%), calc(100vw - 100% - 10px))))`;
|
||||
break;
|
||||
case Alignment.InnerBottom:
|
||||
style.top = baseTop + parentBox.height - 50;
|
||||
style.left = horizontalCenter;
|
||||
style.transform = "translate(-50%)";
|
||||
// Attempt to center the tooltip on the element while clamping
|
||||
// its horizontal translation to keep it on screen
|
||||
// eslint-disable-next-line max-len
|
||||
style.transform = `translate(max(10px, min(calc(${horizontalCenter}px - 50%), calc(100vw - 100% - 10px))))`;
|
||||
break;
|
||||
case Alignment.TopRight:
|
||||
style.top = baseTop - spacing;
|
||||
|
|
|
@ -23,6 +23,7 @@ import classNames from 'classnames';
|
|||
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import { Alignment } from "../elements/Tooltip";
|
||||
|
||||
interface IProps {
|
||||
// Whether this button is highlighted
|
||||
|
@ -54,6 +55,7 @@ export default class HeaderButton extends React.Component<IProps> {
|
|||
aria-selected={isHighlighted}
|
||||
role="tab"
|
||||
title={title}
|
||||
alignment={Alignment.Bottom}
|
||||
className={classes}
|
||||
onClick={onClick}
|
||||
/>;
|
||||
|
|
|
@ -282,7 +282,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
|||
<HeaderButton
|
||||
key="roomSummaryButton"
|
||||
name="roomSummaryButton"
|
||||
title={_t('Room Info')}
|
||||
title={_t('Room info')}
|
||||
isHighlighted={this.isPhase(ROOM_INFO_PHASES)}
|
||||
onClick={this.onRoomSummaryClicked}
|
||||
/>,
|
||||
|
|
|
@ -20,7 +20,7 @@ import classNames from 'classnames';
|
|||
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Tooltip from "../elements/Tooltip";
|
||||
import Tooltip, { Alignment } from "../elements/Tooltip";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||
|
||||
export enum E2EState {
|
||||
|
@ -49,10 +49,20 @@ interface IProps {
|
|||
size?: number;
|
||||
onClick?: () => void;
|
||||
hideTooltip?: boolean;
|
||||
tooltipAlignment?: Alignment;
|
||||
bordered?: boolean;
|
||||
}
|
||||
|
||||
const E2EIcon: React.FC<IProps> = ({ isUser, status, className, size, onClick, hideTooltip, bordered }) => {
|
||||
const E2EIcon: React.FC<IProps> = ({
|
||||
isUser,
|
||||
status,
|
||||
className,
|
||||
size,
|
||||
onClick,
|
||||
hideTooltip,
|
||||
tooltipAlignment,
|
||||
bordered,
|
||||
}) => {
|
||||
const [hover, setHover] = useState(false);
|
||||
|
||||
const classes = classNames({
|
||||
|
@ -80,7 +90,7 @@ const E2EIcon: React.FC<IProps> = ({ isUser, status, className, size, onClick, h
|
|||
|
||||
let tip;
|
||||
if (hover && !hideTooltip) {
|
||||
tip = <Tooltip label={e2eTitle ? _t(e2eTitle) : ""} />;
|
||||
tip = <Tooltip label={e2eTitle ? _t(e2eTitle) : ""} alignment={tooltipAlignment} />;
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
|
|
|
@ -24,7 +24,6 @@ import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
|||
import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import type { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { UserTab } from "../dialogs/UserTab";
|
||||
|
@ -32,7 +31,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||
import E2EIcon from './E2EIcon';
|
||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import RoomTopic from "../elements/RoomTopic";
|
||||
import RoomName from "../elements/RoomName";
|
||||
|
@ -57,14 +56,17 @@ import SdkConfig from "../../../SdkConfig";
|
|||
import { useEventEmitterState, useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { useWidgets } from "../right_panel/RoomSummaryCard";
|
||||
import { WidgetType } from "../../../widgets/WidgetType";
|
||||
import { useCall } from "../../../hooks/useCall";
|
||||
import { useCall, useLayout } from "../../../hooks/useCall";
|
||||
import { getJoinedNonFunctionalMembers } from "../../../utils/room/getJoinedNonFunctionalMembers";
|
||||
import { ElementCall } from "../../../models/Call";
|
||||
import { Call, ElementCall, Layout } from "../../../models/Call";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuOption,
|
||||
IconizedContextMenuOptionList,
|
||||
IconizedContextMenuRadio,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { CallDurationFromEvent } from "../voip/CallDuration";
|
||||
import { Alignment } from "../elements/Tooltip";
|
||||
|
||||
class DisabledWithReason {
|
||||
constructor(public readonly reason: string) { }
|
||||
|
@ -107,6 +109,7 @@ const VoiceCallButton: FC<VoiceCallButtonProps> = ({ room, busy, setBusy, behavi
|
|||
onClick={onClick}
|
||||
title={_t("Voice call")}
|
||||
tooltip={tooltip ?? _t("Voice call")}
|
||||
alignment={Alignment.Bottom}
|
||||
disabled={disabled || busy}
|
||||
/>;
|
||||
};
|
||||
|
@ -207,6 +210,7 @@ const VideoCallButton: FC<VideoCallButtonProps> = ({ room, busy, setBusy, behavi
|
|||
onClick={onClick}
|
||||
title={_t("Video call")}
|
||||
tooltip={tooltip ?? _t("Video call")}
|
||||
alignment={Alignment.Bottom}
|
||||
disabled={disabled || busy}
|
||||
/>
|
||||
{ menu }
|
||||
|
@ -318,6 +322,72 @@ const CallButtons: FC<CallButtonsProps> = ({ room }) => {
|
|||
}
|
||||
};
|
||||
|
||||
interface CallLayoutSelectorProps {
|
||||
call: ElementCall;
|
||||
}
|
||||
|
||||
const CallLayoutSelector: FC<CallLayoutSelectorProps> = ({ call }) => {
|
||||
const layout = useLayout(call);
|
||||
const [menuOpen, buttonRef, openMenu, closeMenu] = useContextMenu();
|
||||
|
||||
const onClick = useCallback((ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
openMenu();
|
||||
}, [openMenu]);
|
||||
|
||||
const onFreedomClick = useCallback((ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
closeMenu();
|
||||
call.setLayout(Layout.Tile);
|
||||
}, [closeMenu, call]);
|
||||
|
||||
const onSpotlightClick = useCallback((ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
closeMenu();
|
||||
call.setLayout(Layout.Spotlight);
|
||||
}, [closeMenu, call]);
|
||||
|
||||
let menu: JSX.Element | null = null;
|
||||
if (menuOpen) {
|
||||
const buttonRect = buttonRef.current!.getBoundingClientRect();
|
||||
menu = <IconizedContextMenu
|
||||
className="mx_RoomHeader_layoutMenu"
|
||||
{...aboveLeftOf(buttonRect)}
|
||||
onFinished={closeMenu}
|
||||
>
|
||||
<IconizedContextMenuOptionList>
|
||||
<IconizedContextMenuRadio
|
||||
iconClassName="mx_RoomHeader_freedomIcon"
|
||||
label={_t("Freedom")}
|
||||
active={layout === Layout.Tile}
|
||||
onClick={onFreedomClick}
|
||||
/>
|
||||
<IconizedContextMenuRadio
|
||||
iconClassName="mx_RoomHeader_spotlightIcon"
|
||||
label={_t("Spotlight")}
|
||||
active={layout === Layout.Spotlight}
|
||||
onClick={onSpotlightClick}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
}
|
||||
|
||||
return <>
|
||||
<AccessibleTooltipButton
|
||||
inputRef={buttonRef}
|
||||
className={classNames("mx_RoomHeader_button", {
|
||||
"mx_RoomHeader_layoutButton--freedom": layout === Layout.Tile,
|
||||
"mx_RoomHeader_layoutButton--spotlight": layout === Layout.Spotlight,
|
||||
})}
|
||||
onClick={onClick}
|
||||
title={_t("Layout type")}
|
||||
alignment={Alignment.Bottom}
|
||||
key="layout"
|
||||
/>
|
||||
{ menu }
|
||||
</>;
|
||||
};
|
||||
|
||||
export interface ISearchInfo {
|
||||
searchTerm: string;
|
||||
searchScope: SearchScope;
|
||||
|
@ -338,6 +408,8 @@ export interface IProps {
|
|||
excludedRightPanelPhaseButtons?: Array<RightPanelPhases>;
|
||||
showButtons?: boolean;
|
||||
enableRoomOptionsMenu?: boolean;
|
||||
viewingCall: boolean;
|
||||
activeCall: Call | null;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -356,6 +428,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
private readonly client = this.props.room.client;
|
||||
|
||||
constructor(props: IProps, context: IState) {
|
||||
super(props, context);
|
||||
|
@ -367,14 +440,12 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
this.client.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli?.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
this.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
const notiStore = RoomNotificationStateStore.instance.getRoomState(this.props.room);
|
||||
notiStore.removeListener(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
|
@ -401,7 +472,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
this.forceUpdate();
|
||||
}, 500, { leading: true, trailing: true });
|
||||
|
||||
private onContextMenuOpenClick = (ev: React.MouseEvent) => {
|
||||
private onContextMenuOpenClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const target = ev.target as HTMLButtonElement;
|
||||
|
@ -412,56 +483,98 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
this.setState({ contextMenuPosition: undefined });
|
||||
};
|
||||
|
||||
private renderButtons(): JSX.Element[] {
|
||||
const buttons: JSX.Element[] = [];
|
||||
private onHideCallClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: this.props.room.roomId,
|
||||
view_call: false,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
if (this.props.inRoom && !this.context.tombstone) {
|
||||
buttons.push(<CallButtons key="calls" room={this.props.room} />);
|
||||
private renderButtons(isVideoRoom: boolean): React.ReactNode {
|
||||
const startButtons: JSX.Element[] = [];
|
||||
|
||||
if (!this.props.viewingCall && this.props.inRoom && !this.context.tombstone) {
|
||||
startButtons.push(<CallButtons key="calls" room={this.props.room} />);
|
||||
}
|
||||
|
||||
if (this.props.onForgetClick) {
|
||||
const forgetButton = <AccessibleTooltipButton
|
||||
if (this.props.viewingCall && this.props.activeCall instanceof ElementCall) {
|
||||
startButtons.push(<CallLayoutSelector call={this.props.activeCall} />);
|
||||
}
|
||||
|
||||
if (!this.props.viewingCall && this.props.onForgetClick) {
|
||||
startButtons.push(<AccessibleTooltipButton
|
||||
className="mx_RoomHeader_button mx_RoomHeader_forgetButton"
|
||||
onClick={this.props.onForgetClick}
|
||||
title={_t("Forget room")}
|
||||
alignment={Alignment.Bottom}
|
||||
key="forget"
|
||||
/>;
|
||||
buttons.push(forgetButton);
|
||||
/>);
|
||||
}
|
||||
|
||||
if (this.props.onAppsClick) {
|
||||
const appsButton = <AccessibleTooltipButton
|
||||
if (!this.props.viewingCall && this.props.onAppsClick) {
|
||||
startButtons.push(<AccessibleTooltipButton
|
||||
className={classNames("mx_RoomHeader_button mx_RoomHeader_appsButton", {
|
||||
mx_RoomHeader_appsButton_highlight: this.props.appsShown,
|
||||
})}
|
||||
onClick={this.props.onAppsClick}
|
||||
title={this.props.appsShown ? _t("Hide Widgets") : _t("Show Widgets")}
|
||||
alignment={Alignment.Bottom}
|
||||
key="apps"
|
||||
/>;
|
||||
buttons.push(appsButton);
|
||||
/>);
|
||||
}
|
||||
|
||||
if (this.props.onSearchClick && this.props.inRoom) {
|
||||
const searchButton = <AccessibleTooltipButton
|
||||
if (!this.props.viewingCall && this.props.onSearchClick && this.props.inRoom) {
|
||||
startButtons.push(<AccessibleTooltipButton
|
||||
className="mx_RoomHeader_button mx_RoomHeader_searchButton"
|
||||
onClick={this.props.onSearchClick}
|
||||
title={_t("Search")}
|
||||
alignment={Alignment.Bottom}
|
||||
key="search"
|
||||
/>;
|
||||
buttons.push(searchButton);
|
||||
/>);
|
||||
}
|
||||
|
||||
if (this.props.onInviteClick && this.props.inRoom) {
|
||||
const inviteButton = <AccessibleTooltipButton
|
||||
if (this.props.onInviteClick && (!this.props.viewingCall || isVideoRoom) && this.props.inRoom) {
|
||||
startButtons.push(<AccessibleTooltipButton
|
||||
className="mx_RoomHeader_button mx_RoomHeader_inviteButton"
|
||||
onClick={this.props.onInviteClick}
|
||||
title={_t("Invite")}
|
||||
alignment={Alignment.Bottom}
|
||||
key="invite"
|
||||
/>;
|
||||
buttons.push(inviteButton);
|
||||
/>);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
const endButtons: JSX.Element[] = [];
|
||||
|
||||
if (this.props.viewingCall && !isVideoRoom) {
|
||||
if (this.props.activeCall === null) {
|
||||
endButtons.push(<AccessibleButton
|
||||
className="mx_RoomHeader_button mx_RoomHeader_closeButton"
|
||||
onClick={this.onHideCallClick}
|
||||
title={_t("Close call")}
|
||||
key="close"
|
||||
/>);
|
||||
} else {
|
||||
endButtons.push(<AccessibleTooltipButton
|
||||
className="mx_RoomHeader_button mx_RoomHeader_minimiseButton"
|
||||
onClick={this.onHideCallClick}
|
||||
title={_t("View chat timeline")}
|
||||
alignment={Alignment.Bottom}
|
||||
key="minimise"
|
||||
/>);
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
{ startButtons }
|
||||
<RoomHeaderButtons
|
||||
room={this.props.room}
|
||||
excludedRightPanelPhaseButtons={this.props.excludedRightPanelPhaseButtons}
|
||||
/>
|
||||
{ endButtons }
|
||||
</>;
|
||||
}
|
||||
|
||||
private renderName(oobName: string) {
|
||||
|
@ -480,7 +593,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
let settingsHint = false;
|
||||
const members = this.props.room ? this.props.room.getJoinedMembers() : undefined;
|
||||
if (members) {
|
||||
if (members.length === 1 && members[0].userId === MatrixClientPeg.get().credentials.userId) {
|
||||
if (members.length === 1 && members[0].userId === this.client.credentials.userId) {
|
||||
const nameEvent = this.props.room.currentState.getStateEvents('m.room.name', '');
|
||||
if (!nameEvent || !nameEvent.getContent().name) {
|
||||
settingsHint = true;
|
||||
|
@ -505,6 +618,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
onClick={this.onContextMenuOpenClick}
|
||||
isExpanded={!!this.state.contextMenuPosition}
|
||||
title={_t("Room options")}
|
||||
alignment={Alignment.Bottom}
|
||||
>
|
||||
{ roomName }
|
||||
{ this.props.room && <div className="mx_RoomHeader_chevron" /> }
|
||||
|
@ -519,6 +633,57 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const isVideoRoom = SettingsStore.getValue("feature_video_rooms") && calcIsVideoRoom(this.props.room);
|
||||
|
||||
let roomAvatar: JSX.Element | null = null;
|
||||
if (this.props.room) {
|
||||
roomAvatar = <DecoratedRoomAvatar
|
||||
room={this.props.room}
|
||||
avatarSize={24}
|
||||
oobData={this.props.oobData}
|
||||
viewAvatarOnClick={true}
|
||||
/>;
|
||||
}
|
||||
|
||||
const icon = this.props.viewingCall
|
||||
? <div className="mx_RoomHeader_icon mx_RoomHeader_icon_video" />
|
||||
: this.props.e2eStatus
|
||||
? <E2EIcon
|
||||
className="mx_RoomHeader_icon"
|
||||
status={this.props.e2eStatus}
|
||||
tooltipAlignment={Alignment.Bottom}
|
||||
/>
|
||||
// If we're expecting an E2EE status to come in, but it hasn't
|
||||
// yet been loaded, insert a blank div to reserve space
|
||||
: this.client.isRoomEncrypted(this.props.room.roomId) && this.client.isCryptoEnabled()
|
||||
? <div className="mx_RoomHeader_icon" />
|
||||
: null;
|
||||
|
||||
const buttons = this.props.showButtons ? this.renderButtons(isVideoRoom) : null;
|
||||
|
||||
if (this.props.viewingCall && !isVideoRoom) {
|
||||
return (
|
||||
<header className="mx_RoomHeader light-panel">
|
||||
<div
|
||||
className="mx_RoomHeader_wrapper"
|
||||
aria-owns={this.state.rightPanelOpen ? "mx_RightPanel" : undefined}
|
||||
>
|
||||
<div className="mx_RoomHeader_avatar">{ roomAvatar }</div>
|
||||
{ icon }
|
||||
<div className="mx_RoomHeader_name mx_RoomHeader_name--textonly mx_RoomHeader_name--small">
|
||||
{ _t("Video call") }
|
||||
</div>
|
||||
{ this.props.activeCall instanceof ElementCall && (
|
||||
<CallDurationFromEvent mxEvent={this.props.activeCall.groupCall} />
|
||||
) }
|
||||
{ /* Empty topic element to fill out space */ }
|
||||
<div className="mx_RoomHeader_topic" />
|
||||
{ buttons }
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
let searchStatus: JSX.Element | null = null;
|
||||
|
||||
// don't display the search count until the search completes and
|
||||
|
@ -543,29 +708,6 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
className="mx_RoomHeader_topic"
|
||||
/>;
|
||||
|
||||
let roomAvatar: JSX.Element | null = null;
|
||||
if (this.props.room) {
|
||||
roomAvatar = <DecoratedRoomAvatar
|
||||
room={this.props.room}
|
||||
avatarSize={24}
|
||||
oobData={this.props.oobData}
|
||||
viewAvatarOnClick={true}
|
||||
/>;
|
||||
}
|
||||
|
||||
let buttons: JSX.Element | null = null;
|
||||
if (this.props.showButtons) {
|
||||
buttons = <React.Fragment>
|
||||
<div className="mx_RoomHeader_buttons">
|
||||
{ this.renderButtons() }
|
||||
</div>
|
||||
<RoomHeaderButtons room={this.props.room} excludedRightPanelPhaseButtons={this.props.excludedRightPanelPhaseButtons} />
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
const e2eIcon = this.props.e2eStatus ? <E2EIcon status={this.props.e2eStatus} /> : undefined;
|
||||
|
||||
const isVideoRoom = SettingsStore.getValue("feature_video_rooms") && calcIsVideoRoom(this.props.room);
|
||||
const viewLabs = () => defaultDispatcher.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Labs,
|
||||
|
@ -581,7 +723,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
aria-owns={this.state.rightPanelOpen ? "mx_RightPanel" : undefined}
|
||||
>
|
||||
<div className="mx_RoomHeader_avatar">{ roomAvatar }</div>
|
||||
<div className="mx_RoomHeader_e2eIcon">{ e2eIcon }</div>
|
||||
{ icon }
|
||||
{ name }
|
||||
{ searchStatus }
|
||||
{ topicElement }
|
||||
|
|
|
@ -89,7 +89,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
selected: RoomViewStore.instance.getRoomId() === this.props.room.roomId,
|
||||
notificationsMenuPosition: null,
|
||||
generalMenuPosition: null,
|
||||
call: CallStore.instance.get(this.props.room.roomId),
|
||||
call: CallStore.instance.getCall(this.props.room.roomId),
|
||||
// generatePreview() will return nothing if the user has previews disabled
|
||||
messagePreview: "",
|
||||
};
|
||||
|
@ -159,7 +159,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
|
||||
// Recalculate the call for this room, since it could've changed between
|
||||
// construction and mounting
|
||||
this.setState({ call: CallStore.instance.get(this.props.room.roomId) });
|
||||
this.setState({ call: CallStore.instance.getCall(this.props.room.roomId) });
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
|
|
@ -32,7 +32,7 @@ const LegacyCallViewHeaderControls: React.FC<LegacyCallControlsProps> = ({ onExp
|
|||
{ onMaximize && <AccessibleTooltipButton
|
||||
className="mx_LegacyCallViewHeader_button mx_LegacyCallViewHeader_button_fullscreen"
|
||||
onClick={onMaximize}
|
||||
title={_t("Fill Screen")}
|
||||
title={_t("Fill screen")}
|
||||
/> }
|
||||
{ onPin && <AccessibleTooltipButton
|
||||
className="mx_LegacyCallViewHeader_button mx_LegacyCallViewHeader_button_pin"
|
||||
|
|
|
@ -201,7 +201,7 @@ export default class PictureInPictureDragger extends React.Component<IProps> {
|
|||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<aside
|
||||
className={this.props.className}
|
||||
style={style}
|
||||
ref={this.callViewWrapper}
|
||||
|
@ -211,7 +211,7 @@ export default class PictureInPictureDragger extends React.Component<IProps> {
|
|||
onStartMoving: this.onStartMoving,
|
||||
onResize: this.onResize,
|
||||
}) }
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import LegacyCallView from "./LegacyCallView";
|
|||
import { RoomViewStore } from '../../../stores/RoomViewStore';
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../../LegacyCallHandler';
|
||||
import PersistentApp from "../elements/PersistentApp";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import PictureInPictureDragger from './PictureInPictureDragger';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
|
@ -35,6 +34,7 @@ import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/Activ
|
|||
import WidgetStore, { IApp } from "../../../stores/WidgetStore";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
|
||||
import { CallStore } from "../../../stores/CallStore";
|
||||
|
||||
const SHOW_CALL_IN_STATES = [
|
||||
CallState.Connected,
|
||||
|
@ -116,7 +116,6 @@ function getPrimarySecondaryCallsForPip(roomId: string): [MatrixCall, MatrixCall
|
|||
*/
|
||||
|
||||
export default class PipView extends React.Component<IProps, IState> {
|
||||
private settingsWatcherRef: string;
|
||||
private movePersistedElement = createRef<() => void>();
|
||||
|
||||
constructor(props: IProps) {
|
||||
|
@ -157,7 +156,6 @@ export default class PipView extends React.Component<IProps, IState> {
|
|||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
|
||||
MatrixClientPeg.get().removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||
RoomViewStore.instance.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
SettingsStore.unwatchSetting(this.settingsWatcherRef);
|
||||
const room = MatrixClientPeg.get().getRoom(this.state.viewedRoomId);
|
||||
if (room) {
|
||||
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
|
||||
|
@ -278,6 +276,14 @@ export default class PipView extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
private onViewCall = (): void =>
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: this.state.persistentRoomId,
|
||||
view_call: true,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
|
||||
// Accepts a persistentWidgetId to be able to skip awaiting the setState for persistentWidgetId
|
||||
public updateShowWidgetInPip(
|
||||
persistentWidgetId = this.state.persistentWidgetId,
|
||||
|
@ -323,18 +329,19 @@ export default class PipView extends React.Component<IProps, IState> {
|
|||
mx_LegacyCallView_large: !pipMode,
|
||||
});
|
||||
const roomId = this.state.persistentRoomId;
|
||||
const roomForWidget = MatrixClientPeg.get().getRoom(roomId);
|
||||
const roomForWidget = MatrixClientPeg.get().getRoom(roomId)!;
|
||||
const viewingCallRoom = this.state.viewedRoomId === roomId;
|
||||
const isCall = CallStore.instance.getActiveCall(roomId) !== null;
|
||||
|
||||
pipContent = ({ onStartMoving, _onResize }) =>
|
||||
pipContent = ({ onStartMoving }) =>
|
||||
<div className={pipViewClasses}>
|
||||
<LegacyCallViewHeader
|
||||
onPipMouseDown={(event) => { onStartMoving(event); this.onStartMoving.bind(this)(); }}
|
||||
pipMode={pipMode}
|
||||
callRooms={[roomForWidget]}
|
||||
onExpand={!viewingCallRoom && this.onExpand}
|
||||
onPin={viewingCallRoom && this.onPin}
|
||||
onMaximize={viewingCallRoom && this.onMaximize}
|
||||
onExpand={!isCall && !viewingCallRoom ? this.onExpand : undefined}
|
||||
onPin={!isCall && viewingCallRoom ? this.onPin : undefined}
|
||||
onMaximize={isCall ? this.onViewCall : viewingCallRoom ? this.onMaximize : undefined}
|
||||
/>
|
||||
<PersistentApp
|
||||
persistentWidgetId={this.state.persistentWidgetId}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue