Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
commit
24f0c760ce
70 changed files with 1590 additions and 263 deletions
|
@ -22,6 +22,7 @@ import MemberAvatar from '../avatars/MemberAvatar';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { useStateToggle } from "../../../hooks/useStateToggle";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import { Layout } from '../../../settings/Layout';
|
||||
|
||||
interface IProps {
|
||||
// An array of member events to summarise
|
||||
|
@ -38,6 +39,8 @@ interface IProps {
|
|||
children: ReactNode[];
|
||||
// Called when the event list expansion is toggled
|
||||
onToggle?(): void;
|
||||
// The layout currently used
|
||||
layout?: Layout;
|
||||
}
|
||||
|
||||
const EventListSummary: React.FC<IProps> = ({
|
||||
|
@ -48,6 +51,7 @@ const EventListSummary: React.FC<IProps> = ({
|
|||
startExpanded,
|
||||
summaryMembers = [],
|
||||
summaryText,
|
||||
layout,
|
||||
}) => {
|
||||
const [expanded, toggleExpanded] = useStateToggle(startExpanded);
|
||||
|
||||
|
@ -63,7 +67,7 @@ const EventListSummary: React.FC<IProps> = ({
|
|||
// If we are only given few events then just pass them through
|
||||
if (events.length < threshold) {
|
||||
return (
|
||||
<li className="mx_EventListSummary" data-scroll-tokens={eventIds} data-expanded={true}>
|
||||
<li className="mx_EventListSummary" data-scroll-tokens={eventIds} data-expanded={true} data-layout={layout}>
|
||||
{ children }
|
||||
</li>
|
||||
);
|
||||
|
@ -92,7 +96,7 @@ const EventListSummary: React.FC<IProps> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<li className="mx_EventListSummary" data-scroll-tokens={eventIds} data-expanded={expanded + ""}>
|
||||
<li className="mx_EventListSummary" data-scroll-tokens={eventIds} data-expanded={expanded + ""} data-layout={layout}>
|
||||
<AccessibleButton className="mx_EventListSummary_toggle" onClick={toggleExpanded} aria-expanded={expanded}>
|
||||
{ expanded ? _t('collapse') : _t('expand') }
|
||||
</AccessibleButton>
|
||||
|
@ -103,6 +107,7 @@ const EventListSummary: React.FC<IProps> = ({
|
|||
|
||||
EventListSummary.defaultProps = {
|
||||
startExpanded: false,
|
||||
layout: Layout.Group,
|
||||
};
|
||||
|
||||
export default EventListSummary;
|
||||
|
|
|
@ -25,12 +25,15 @@ import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
|
|||
import { isValid3pidInvite } from "../../../RoomInvite";
|
||||
import EventListSummary from "./EventListSummary";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { Layout } from '../../../settings/Layout';
|
||||
|
||||
interface IProps extends Omit<ComponentProps<typeof EventListSummary>, "summaryText" | "summaryMembers"> {
|
||||
// The maximum number of names to show in either each summary e.g. 2 would result "A, B and 234 others left"
|
||||
summaryLength?: number;
|
||||
// The maximum number of avatars to display in the summary
|
||||
avatarsMaxLength?: number;
|
||||
// The currently selected layout
|
||||
layout: Layout;
|
||||
}
|
||||
|
||||
interface IUserEvents {
|
||||
|
@ -67,6 +70,7 @@ export default class MemberEventListSummary extends React.Component<IProps> {
|
|||
summaryLength: 1,
|
||||
threshold: 3,
|
||||
avatarsMaxLength: 5,
|
||||
layout: Layout.Group,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
|
@ -453,6 +457,7 @@ export default class MemberEventListSummary extends React.Component<IProps> {
|
|||
startExpanded={this.props.startExpanded}
|
||||
children={this.props.children}
|
||||
summaryMembers={[...latestUserAvatarMember.values()]}
|
||||
layout={this.props.layout}
|
||||
summaryText={this.generateSummary(aggregate.names, orderedTransitionSequences)} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { CallErrorCode, CallState } from 'matrix-js-sdk/src/webrtc/call';
|
|||
import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip';
|
||||
import classNames from 'classnames';
|
||||
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -69,6 +70,18 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
|||
this.setState({ callState: newState });
|
||||
};
|
||||
|
||||
private renderCallBackButton(text: string): JSX.Element {
|
||||
return (
|
||||
<AccessibleButton
|
||||
className="mx_CallEvent_content_button mx_CallEvent_content_button_callBack"
|
||||
onClick={this.props.callEventGrouper.callBack}
|
||||
kind="primary"
|
||||
>
|
||||
<span> { text } </span>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
private renderContent(state: CallState | CustomCallState): JSX.Element {
|
||||
if (state === CallState.Ringing) {
|
||||
const silenceClass = classNames({
|
||||
|
@ -103,8 +116,18 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
|||
}
|
||||
if (state === CallState.Ended) {
|
||||
const hangupReason = this.props.callEventGrouper.hangupReason;
|
||||
const gotRejected = this.props.callEventGrouper.gotRejected;
|
||||
const rejectParty = this.props.callEventGrouper.rejectParty;
|
||||
|
||||
if ([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason) {
|
||||
if (gotRejected) {
|
||||
const weDeclinedCall = MatrixClientPeg.get().getUserId() === rejectParty;
|
||||
return (
|
||||
<div className="mx_CallEvent_content">
|
||||
{ weDeclinedCall ? _t("You declined this call") : _t("They declined this call") }
|
||||
{ this.renderCallBackButton(weDeclinedCall ? _t("Call back") : _t("Call again")) }
|
||||
</div>
|
||||
);
|
||||
} else if (([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason)) {
|
||||
// workaround for https://github.com/vector-im/element-web/issues/5178
|
||||
// it seems Android randomly sets a reason of "user hangup" which is
|
||||
// interpreted as an error code :(
|
||||
|
@ -116,6 +139,13 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
|||
{ _t("This call has ended") }
|
||||
</div>
|
||||
);
|
||||
} else if (hangupReason === CallErrorCode.InviteTimeout) {
|
||||
return (
|
||||
<div className="mx_CallEvent_content">
|
||||
{ _t("They didn't pick up") }
|
||||
{ this.renderCallBackButton(_t("Call again")) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let reason;
|
||||
|
@ -133,8 +163,6 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
|||
// (as opposed to an error code they gave but we don't know about,
|
||||
// in which case we show the error code)
|
||||
reason = _t("An unknown error occurred");
|
||||
} else if (hangupReason === CallErrorCode.InviteTimeout) {
|
||||
reason = _t("No answer");
|
||||
} else if (hangupReason === CallErrorCode.UserBusy) {
|
||||
reason = _t("The user you called is busy.");
|
||||
} else {
|
||||
|
@ -163,13 +191,7 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
|||
return (
|
||||
<div className="mx_CallEvent_content">
|
||||
{ _t("You missed this call") }
|
||||
<AccessibleButton
|
||||
className="mx_CallEvent_content_button mx_CallEvent_content_button_callBack"
|
||||
onClick={this.props.callEventGrouper.callBack}
|
||||
kind="primary"
|
||||
>
|
||||
<span> { _t("Call back") } </span>
|
||||
</AccessibleButton>
|
||||
{ this.renderCallBackButton(_t("Call back")) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -186,11 +208,17 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
|||
const sender = event.sender ? event.sender.name : event.getSender();
|
||||
const isVoice = this.props.callEventGrouper.isVoice;
|
||||
const callType = isVoice ? _t("Voice call") : _t("Video call");
|
||||
const content = this.renderContent(this.state.callState);
|
||||
const callState = this.state.callState;
|
||||
const hangupReason = this.props.callEventGrouper.hangupReason;
|
||||
const content = this.renderContent(callState);
|
||||
const className = classNames({
|
||||
mx_CallEvent: true,
|
||||
mx_CallEvent_voice: isVoice,
|
||||
mx_CallEvent_video: !isVoice,
|
||||
mx_CallEvent_missed: (
|
||||
callState === CustomCallState.Missed ||
|
||||
(callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout)
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -896,6 +896,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2E_STATE.UNKNOWN,
|
||||
mx_EventTile_bad: isEncryptionFailure,
|
||||
mx_EventTile_emote: msgtype === 'm.emote',
|
||||
mx_EventTile_noSender: this.props.hideSender,
|
||||
});
|
||||
|
||||
// If the tile is in the Sending state, don't speak the message.
|
||||
|
@ -1165,8 +1166,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
/>
|
||||
{ keyRequestInfo }
|
||||
{ actionBar }
|
||||
{ this.props.layout === Layout.IRC && (reactionsRow) }
|
||||
</div>
|
||||
{ reactionsRow }
|
||||
{ this.props.layout !== Layout.IRC && (reactionsRow) }
|
||||
{ msgOption }
|
||||
</>)
|
||||
);
|
||||
|
|
|
@ -146,7 +146,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
|||
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
|
||||
document.addEventListener("mousemove", this.onMoving);
|
||||
document.addEventListener("mouseup", this.onEndMoving);
|
||||
window.addEventListener("resize", this.snap);
|
||||
window.addEventListener("resize", this.onResize);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
|||
MatrixClientPeg.get().removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||
document.removeEventListener("mousemove", this.onMoving);
|
||||
document.removeEventListener("mouseup", this.onEndMoving);
|
||||
window.removeEventListener("resize", this.snap);
|
||||
window.removeEventListener("resize", this.onResize);
|
||||
if (this.roomStoreToken) {
|
||||
this.roomStoreToken.remove();
|
||||
}
|
||||
|
@ -164,6 +164,10 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
|||
SettingsStore.unwatchSetting(this.settingsWatcherRef);
|
||||
}
|
||||
|
||||
private onResize = (): void => {
|
||||
this.snap(false);
|
||||
};
|
||||
|
||||
private animationCallback = () => {
|
||||
// If the PiP isn't being dragged and there is only a tiny difference in
|
||||
// the desiredTranslation and translation, quit the animationCallback
|
||||
|
@ -207,7 +211,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
private snap = () => {
|
||||
private snap(animate?: boolean): void {
|
||||
const translationX = this.desiredTranslationX;
|
||||
const translationY = this.desiredTranslationY;
|
||||
// We subtract the PiP size from the window size in order to calculate
|
||||
|
@ -236,10 +240,17 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
|||
this.desiredTranslationY = PADDING.top;
|
||||
}
|
||||
|
||||
// We start animating here because we want the PiP to move when we're
|
||||
// resizing the window
|
||||
this.scheduledUpdate.mark();
|
||||
};
|
||||
if (animate) {
|
||||
// We start animating here because we want the PiP to move when we're
|
||||
// resizing the window
|
||||
this.scheduledUpdate.mark();
|
||||
} else {
|
||||
this.setState({
|
||||
translationX: this.desiredTranslationX,
|
||||
translationY: this.desiredTranslationY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onRoomViewStoreUpdate = () => {
|
||||
if (RoomViewStore.getRoomId() === this.state.roomId) return;
|
||||
|
@ -310,7 +321,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
|||
|
||||
private onEndMoving = () => {
|
||||
this.moving = false;
|
||||
this.snap();
|
||||
this.snap(true);
|
||||
};
|
||||
|
||||
public render() {
|
||||
|
@ -333,6 +344,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
|||
secondaryCall={this.state.secondaryCall}
|
||||
pipMode={true}
|
||||
onMouseDownOnHeader={this.onStartMoving}
|
||||
onResize={this.onResize}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import classnames from 'classnames';
|
||||
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import React, { createRef } from 'react';
|
||||
import React from 'react';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed';
|
||||
import { logger } from 'matrix-js-sdk/src/logger';
|
||||
|
@ -48,7 +48,7 @@ interface IState {
|
|||
|
||||
@replaceableComponent("views.voip.VideoFeed")
|
||||
export default class VideoFeed extends React.Component<IProps, IState> {
|
||||
private element = createRef<HTMLVideoElement>();
|
||||
private element: HTMLVideoElement;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
@ -66,10 +66,10 @@ export default class VideoFeed extends React.Component<IProps, IState> {
|
|||
|
||||
componentWillUnmount() {
|
||||
this.updateFeed(this.props.feed, null);
|
||||
this.element.current?.removeEventListener('resize', this.onResize);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
this.element?.addEventListener('resize', this.onResize);
|
||||
this.updateFeed(prevProps.feed, this.props.feed);
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,13 @@ export default class VideoFeed extends React.Component<IProps, IState> {
|
|||
};
|
||||
}
|
||||
|
||||
private setElementRef = (element: HTMLVideoElement): void => {
|
||||
if (!element) element.removeEventListener('resize', this.onResize);
|
||||
|
||||
this.element = element;
|
||||
element.addEventListener('resize', this.onResize);
|
||||
};
|
||||
|
||||
private updateFeed(oldFeed: CallFeed, newFeed: CallFeed) {
|
||||
if (oldFeed === newFeed) return;
|
||||
|
||||
|
@ -94,7 +101,7 @@ export default class VideoFeed extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private playMedia() {
|
||||
const element = this.element.current;
|
||||
const element = this.element;
|
||||
if (!element) return;
|
||||
// We play audio in AudioFeed, not here
|
||||
element.muted = true;
|
||||
|
@ -117,7 +124,7 @@ export default class VideoFeed extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private stopMedia() {
|
||||
const element = this.element.current;
|
||||
const element = this.element;
|
||||
if (!element) return;
|
||||
|
||||
element.pause();
|
||||
|
@ -175,7 +182,7 @@ export default class VideoFeed extends React.Component<IProps, IState> {
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<video className={classnames(videoClasses)} ref={this.element} />
|
||||
<video className={classnames(videoClasses)} ref={this.setElementRef} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue