Merge branch 'develop' into string-pl

This commit is contained in:
Aaron Raimist 2021-06-23 17:54:42 -05:00
commit 7b7ad76fe4
25 changed files with 316 additions and 195 deletions

View file

@ -22,7 +22,7 @@ import { MatrixClient } from 'matrix-js-sdk/src/client';
import {Key} from '../../Keyboard';
import PageTypes from '../../PageTypes';
import CallMediaHandler from '../../CallMediaHandler';
import MediaDeviceHandler from '../../MediaDeviceHandler';
import { fixupColorFonts } from '../../utils/FontManager';
import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher';
@ -167,7 +167,7 @@ class LoggedInView extends React.Component<IProps, IState> {
// stash the MatrixClient in case we log out before we are unmounted
this._matrixClient = this.props.matrixClient;
CallMediaHandler.loadDevices();
MediaDeviceHandler.loadDevices();
fixupColorFonts();

View file

@ -337,11 +337,10 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
}
private onRoomClicked = (room: IRoom, ev: ButtonEvent) => {
// If room was shift-clicked, remove it from the room directory
if (ev.shiftKey && !this.state.selectedCommunityId) {
ev.preventDefault();
this.removeFromDirectory(room);
} else {
this.showRoom(room);
}
};
@ -568,11 +567,11 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
let avatarUrl = null;
if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
// We use onMouseDown instead of onClick, so that we can avoid text getting selected
return [
<div key={ `${room.room_id}_avatar` }
onClick={(ev) => this.onRoomClicked(room, ev)}
// cancel onMouseDown otherwise shift-clicking highlights text
onMouseDown={(ev) => {ev.preventDefault();}}
<div
key={ `${room.room_id}_avatar` }
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomAvatar"
>
<BaseAvatar
@ -584,42 +583,50 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
url={avatarUrl}
/>
</div>,
<div key={ `${room.room_id}_description` }
onClick={(ev) => this.onRoomClicked(room, ev)}
// cancel onMouseDown otherwise shift-clicking highlights text
onMouseDown={(ev) => {ev.preventDefault();}}
<div
key={ `${room.room_id}_description` }
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomDescription"
>
<div className="mx_RoomDirectory_name">{ name }</div>&nbsp;
<div className="mx_RoomDirectory_topic"
onClick={ (ev) => { ev.stopPropagation(); } }
<div
className="mx_RoomDirectory_name"
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
>
{ name }
</div>&nbsp;
<div
className="mx_RoomDirectory_topic"
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
dangerouslySetInnerHTML={{ __html: topic }}
/>
<div className="mx_RoomDirectory_alias">{ getDisplayAliasForRoom(room) }</div>
<div
className="mx_RoomDirectory_alias"
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
>
{ getDisplayAliasForRoom(room) }
</div>
</div>,
<div key={ `${room.room_id}_memberCount` }
onClick={(ev) => this.onRoomClicked(room, ev)}
// cancel onMouseDown otherwise shift-clicking highlights text
onMouseDown={(ev) => {ev.preventDefault();}}
<div
key={ `${room.room_id}_memberCount` }
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomMemberCount"
>
{ room.num_joined_members }
</div>,
<div key={ `${room.room_id}_preview` }
onClick={(ev) => this.onRoomClicked(room, ev)}
<div
key={ `${room.room_id}_preview` }
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
// cancel onMouseDown otherwise shift-clicking highlights text
onMouseDown={(ev) => {ev.preventDefault();}}
className="mx_RoomDirectory_preview"
>
{previewButton}
{ previewButton }
</div>,
<div key={ `${room.room_id}_join` }
onClick={(ev) => this.onRoomClicked(room, ev)}
// cancel onMouseDown otherwise shift-clicking highlights text
onMouseDown={(ev) => {ev.preventDefault();}}
<div
key={ `${room.room_id}_join` }
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_join"
>
{joinOrViewButton}
{ joinOrViewButton }
</div>,
];
}

View file

@ -18,6 +18,7 @@ import React from 'react';
import { _t } from '../../../languageHandler';
import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu';
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import Field from "../elements/Field";
import Dialpad from '../voip/DialPad';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@ -44,13 +45,21 @@ export default class DialpadContextMenu extends React.Component<IProps, IState>
this.setState({value: this.state.value + digit});
}
onChange = (ev) => {
this.setState({value: ev.target.value});
}
render() {
return <ContextMenu {...this.props}>
<div className="mx_DialPadContextMenu_header">
<div>
<span className="mx_DialPadContextMenu_title">{_t("Dial pad")}</span>
</div>
<div className="mx_DialPadContextMenu_dialled">{this.state.value}</div>
<Field className="mx_DialPadContextMenu_dialled"
value={this.state.value} autoFocus={true}
onChange={this.onChange}
/>
</div>
<div className="mx_DialPadContextMenu_horizSep" />
<div className="mx_DialPadContextMenu_dialPad">

View file

@ -39,6 +39,9 @@ import ProgressBar from "../elements/ProgressBar";
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import QueryMatcher from "../../../autocomplete/QueryMatcher";
import TruncatedList from "../elements/TruncatedList";
import EntityTile from "../rooms/EntityTile";
import BaseAvatar from "../avatars/BaseAvatar";
interface IProps extends IDialogProps {
matrixClient: MatrixClient;
@ -204,6 +207,17 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
setSelectedToAdd(new Set(selectedToAdd));
} : null;
const [truncateAt, setTruncateAt] = useState(20);
function overflowTile(overflowCount, totalCount) {
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
} name={text} presenceState="online" suppressOnHover={true}
onClick={() => setTruncateAt(totalCount)} />
);
}
return <div className="mx_AddExistingToSpace">
<SearchBox
className="mx_textinput_icon mx_textinput_search"
@ -216,16 +230,21 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
{ rooms.length > 0 ? (
<div className="mx_AddExistingToSpace_section">
<h3>{ _t("Rooms") }</h3>
{ rooms.map(room => {
return <Entry
key={room.roomId}
room={room}
checked={selectedToAdd.has(room)}
onChange={onChange ? (checked) => {
onChange(checked, room);
} : null}
/>;
}) }
<TruncatedList
truncateAt={truncateAt}
createOverflowElement={overflowTile}
getChildren={(start, end) => rooms.slice(start, end).map(room =>
<Entry
key={room.roomId}
room={room}
checked={selectedToAdd.has(room)}
onChange={onChange ? (checked) => {
onChange(checked, room);
} : null}
/>,
)}
getChildCount={() => rooms.length}
/>
</div>
) : undefined }

View file

@ -40,6 +40,9 @@ import NotificationBadge from "../rooms/NotificationBadge";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import QueryMatcher from "../../../autocomplete/QueryMatcher";
import TruncatedList from "../elements/TruncatedList";
import EntityTile from "../rooms/EntityTile";
import BaseAvatar from "../avatars/BaseAvatar";
const AVATAR_SIZE = 30;
@ -196,6 +199,17 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
}).match(lcQuery);
}
const [truncateAt, setTruncateAt] = useState(20);
function overflowTile(overflowCount, totalCount) {
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
} name={text} presenceState="online" suppressOnHover={true}
onClick={() => setTruncateAt(totalCount)} />
);
}
return <BaseDialog
title={_t("Forward message")}
className="mx_ForwardDialog"
@ -228,15 +242,20 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
<AutoHideScrollbar className="mx_ForwardList_content">
{ rooms.length > 0 ? (
<div className="mx_ForwardList_results">
{ rooms.map(room =>
<Entry
key={room.roomId}
room={room}
event={event}
matrixClient={cli}
onFinished={onFinished}
/>,
) }
<TruncatedList
truncateAt={truncateAt}
createOverflowElement={overflowTile}
getChildren={(start, end) => rooms.slice(start, end).map(room =>
<Entry
key={room.roomId}
room={room}
event={event}
matrixClient={cli}
onFinished={onFinished}
/>,
)}
getChildCount={() => rooms.length}
/>
</div>
) : <span className="mx_ForwardList_noResults">
{ _t("No results") }

View file

@ -63,7 +63,8 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
state: IState = {
disabledButtonIds: [],
disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter(b => b.disabled)
.map(b => b.id),
};
constructor(props) {

View file

@ -16,31 +16,29 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.TruncatedList")
export default class TruncatedList extends React.Component {
static propTypes = {
// The number of elements to show before truncating. If negative, no truncation is done.
truncateAt: PropTypes.number,
// The className to apply to the wrapping div
className: PropTypes.string,
// A function that returns the children to be rendered into the element.
// function getChildren(start: number, end: number): Array<React.Node>
// The start element is included, the end is not (as in `slice`).
// If omitted, the React child elements will be used. This parameter can be used
// to avoid creating unnecessary React elements.
getChildren: PropTypes.func,
// A function that should return the total number of child element available.
// Required if getChildren is supplied.
getChildCount: PropTypes.func,
// A function which will be invoked when an overflow element is required.
// This will be inserted after the children.
createOverflowElement: PropTypes.func,
};
interface IProps {
// The number of elements to show before truncating. If negative, no truncation is done.
truncateAt?: number;
// The className to apply to the wrapping div
className?: string;
// A function that returns the children to be rendered into the element.
// The start element is included, the end is not (as in `slice`).
// If omitted, the React child elements will be used. This parameter can be used
// to avoid creating unnecessary React elements.
getChildren?: (start: number, end: number) => Array<React.ReactNode>;
// A function that should return the total number of child element available.
// Required if getChildren is supplied.
getChildCount?: () => number;
// A function which will be invoked when an overflow element is required.
// This will be inserted after the children.
createOverflowElement?: (overflowCount: number, totalCount: number) => React.ReactNode;
}
@replaceableComponent("views.elements.TruncatedList")
export default class TruncatedList extends React.Component<IProps> {
static defaultProps ={
truncateAt: 2,
createOverflowElement(overflowCount, totalCount) {
@ -50,7 +48,7 @@ export default class TruncatedList extends React.Component {
},
};
_getChildren(start, end) {
private getChildren(start: number, end: number): Array<React.ReactNode> {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildren(start, end);
} else {
@ -63,7 +61,7 @@ export default class TruncatedList extends React.Component {
}
}
_getChildCount() {
private getChildCount(): number {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildCount();
} else {
@ -73,10 +71,10 @@ export default class TruncatedList extends React.Component {
}
}
render() {
public render() {
let overflowNode = null;
const totalChildren = this._getChildCount();
const totalChildren = this.getChildCount();
let upperBound = totalChildren;
if (this.props.truncateAt >= 0) {
const overflowCount = totalChildren - this.props.truncateAt;
@ -87,7 +85,7 @@ export default class TruncatedList extends React.Component {
upperBound = this.props.truncateAt;
}
}
const childNodes = this._getChildren(0, upperBound);
const childNodes = this.getChildren(0, upperBound);
return (
<div className={this.props.className}>

View file

@ -30,7 +30,7 @@ import RecordingPlayback from "../voice_messages/RecordingPlayback";
import {MsgType} from "matrix-js-sdk/src/@types/event";
import Modal from "../../../Modal";
import ErrorDialog from "../dialogs/ErrorDialog";
import CallMediaHandler from "../../../CallMediaHandler";
import MediaDeviceHandler from "../../../MediaDeviceHandler";
interface IProps {
room: Room;
@ -129,8 +129,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
// Do a sanity test to ensure we're about to grab a valid microphone reference. Things might
// change between this and recording, but at least we will have tried.
try {
const devices = await CallMediaHandler.getDevices();
if (!devices?.['audioinput']?.length) {
const devices = await MediaDeviceHandler.getDevices();
if (!devices?.['audioInput']?.length) {
Modal.createTrackedDialog('No Microphone Error', '', ErrorDialog, {
title: _t("No microphone found"),
description: <>

View file

@ -18,7 +18,7 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig";
import CallMediaHandler from "../../../../../CallMediaHandler";
import MediaDeviceHandler from "../../../../../MediaDeviceHandler";
import Field from "../../../elements/Field";
import AccessibleButton from "../../../elements/AccessibleButton";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
@ -41,7 +41,7 @@ export default class VoiceUserSettingsTab extends React.Component {
}
async componentDidMount() {
const canSeeDeviceLabels = await CallMediaHandler.hasAnyLabeledDevices();
const canSeeDeviceLabels = await MediaDeviceHandler.hasAnyLabeledDevices();
if (canSeeDeviceLabels) {
this._refreshMediaDevices();
}
@ -49,10 +49,10 @@ export default class VoiceUserSettingsTab extends React.Component {
_refreshMediaDevices = async (stream) => {
this.setState({
mediaDevices: await CallMediaHandler.getDevices(),
activeAudioOutput: CallMediaHandler.getAudioOutput(),
activeAudioInput: CallMediaHandler.getAudioInput(),
activeVideoInput: CallMediaHandler.getVideoInput(),
mediaDevices: await MediaDeviceHandler.getDevices(),
activeAudioOutput: MediaDeviceHandler.getAudioOutput(),
activeAudioInput: MediaDeviceHandler.getAudioInput(),
activeVideoInput: MediaDeviceHandler.getVideoInput(),
});
if (stream) {
// kill stream (after we've enumerated the devices, otherwise we'd get empty labels again)
@ -100,21 +100,21 @@ export default class VoiceUserSettingsTab extends React.Component {
};
_setAudioOutput = (e) => {
CallMediaHandler.setAudioOutput(e.target.value);
MediaDeviceHandler.instance.setAudioOutput(e.target.value);
this.setState({
activeAudioOutput: e.target.value,
});
};
_setAudioInput = (e) => {
CallMediaHandler.setAudioInput(e.target.value);
MediaDeviceHandler.instance.setAudioInput(e.target.value);
this.setState({
activeAudioInput: e.target.value,
});
};
_setVideoInput = (e) => {
CallMediaHandler.setVideoInput(e.target.value);
MediaDeviceHandler.instance.setVideoInput(e.target.value);
this.setState({
activeVideoInput: e.target.value,
});
@ -171,7 +171,7 @@ export default class VoiceUserSettingsTab extends React.Component {
}
};
const audioOutputs = this.state.mediaDevices.audiooutput.slice(0);
const audioOutputs = this.state.mediaDevices.audioOutput.slice(0);
if (audioOutputs.length > 0) {
const defaultDevice = getDefaultDevice(audioOutputs);
speakerDropdown = (
@ -183,7 +183,7 @@ export default class VoiceUserSettingsTab extends React.Component {
);
}
const audioInputs = this.state.mediaDevices.audioinput.slice(0);
const audioInputs = this.state.mediaDevices.audioInput.slice(0);
if (audioInputs.length > 0) {
const defaultDevice = getDefaultDevice(audioInputs);
microphoneDropdown = (
@ -195,7 +195,7 @@ export default class VoiceUserSettingsTab extends React.Component {
);
}
const videoInputs = this.state.mediaDevices.videoinput.slice(0);
const videoInputs = this.state.mediaDevices.videoInput.slice(0);
if (videoInputs.length > 0) {
const defaultDevice = getDefaultDevice(videoInputs);
webcamDropdown = (

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, {createRef} from 'react';
import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed';
import { logger } from 'matrix-js-sdk/src/logger';
import CallMediaHandler from "../../../CallMediaHandler";
import MediaDeviceHandler, { MediaDeviceHandlerEvent } from "../../../MediaDeviceHandler";
interface IProps {
feed: CallFeed,
@ -27,19 +27,25 @@ export default class AudioFeed extends React.Component<IProps> {
private element = createRef<HTMLAudioElement>();
componentDidMount() {
MediaDeviceHandler.instance.addListener(
MediaDeviceHandlerEvent.AudioOutputChanged,
this.onAudioOutputChanged,
);
this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream);
this.playMedia();
}
componentWillUnmount() {
MediaDeviceHandler.instance.removeListener(
MediaDeviceHandlerEvent.AudioOutputChanged,
this.onAudioOutputChanged,
);
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
this.stopMedia();
}
private playMedia() {
private onAudioOutputChanged = (audioOutput: string) => {
const element = this.element.current;
const audioOutput = CallMediaHandler.getAudioOutput();
if (audioOutput) {
try {
// This seems quite unreliable in Chrome, although I haven't yet managed to make a jsfiddle where
@ -52,7 +58,11 @@ export default class AudioFeed extends React.Component<IProps> {
logger.warn("Couldn't set requested audio output device: using default", e);
}
}
}
private playMedia() {
const element = this.element.current;
this.onAudioOutputChanged(MediaDeviceHandler.getAudioOutput());
element.muted = false;
element.srcObject = this.props.feed.stream;
element.autoplay = true;