Merge branch 'develop' into string-pl
This commit is contained in:
commit
7b7ad76fe4
25 changed files with 316 additions and 195 deletions
|
@ -79,7 +79,7 @@
|
||||||
"linkifyjs": "^2.1.9",
|
"linkifyjs": "^2.1.9",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"matrix-js-sdk": "12.0.0",
|
"matrix-js-sdk": "12.0.0",
|
||||||
"matrix-widget-api": "^0.1.0-beta.14",
|
"matrix-widget-api": "^0.1.0-beta.15",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"opus-recorder": "^8.0.3",
|
"opus-recorder": "^8.0.3",
|
||||||
"pako": "^2.0.3",
|
"pako": "^2.0.3",
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
"@sinonjs/fake-timers": "^7.0.2",
|
"@sinonjs/fake-timers": "^7.0.2",
|
||||||
"@types/classnames": "^2.2.11",
|
"@types/classnames": "^2.2.11",
|
||||||
"@types/counterpart": "^0.18.1",
|
"@types/counterpart": "^0.18.1",
|
||||||
"@types/diff-match-patch": "^1.0.5",
|
"@types/diff-match-patch": "^1.0.32",
|
||||||
"@types/flux": "^3.1.9",
|
"@types/flux": "^3.1.9",
|
||||||
"@types/jest": "^26.0.20",
|
"@types/jest": "^26.0.20",
|
||||||
"@types/linkifyjs": "^2.1.3",
|
"@types/linkifyjs": "^2.1.3",
|
||||||
|
|
|
@ -71,7 +71,7 @@ limitations under the License.
|
||||||
&::before {
|
&::before {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
mask-image: url('$(res)/img/e2e/normal.svg');
|
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||||
mask-size: 90%;
|
mask-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
|
|
|
@ -21,7 +21,7 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/e2e/normal.svg');
|
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: 90%;
|
mask-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_cryptoEvent_icon::after {
|
&.mx_cryptoEvent_icon::after {
|
||||||
|
|
|
@ -45,7 +45,7 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/e2e/normal.svg');
|
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: 90%;
|
mask-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// transparent-looking border surrounding the shield for when overlain over avatars
|
// transparent-looking border surrounding the shield for when overlain over avatars
|
||||||
|
@ -59,7 +59,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
// shrink the infill of the badge
|
// shrink the infill of the badge
|
||||||
&::before {
|
&::before {
|
||||||
mask-size: 65%;
|
mask-size: 60%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -345,7 +345,7 @@ $hover-select-border: 4px;
|
||||||
mask-image: url('$(res)/img/e2e/normal.svg');
|
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: 90%;
|
mask-size: 80%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ limitations under the License.
|
||||||
.mx_DialPad_button {
|
.mx_DialPad_button {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
background-color: $theme-button-bg-color;
|
background-color: $dialpad-button-bg-color;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
|
@ -27,9 +27,22 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DialPadContextMenu_dialled {
|
.mx_DialPadContextMenu_dialled {
|
||||||
height: 1em;
|
height: 1.5em;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
max-width: 150px;
|
||||||
|
border: none;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
.mx_DialPadContextMenu_dialled input {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 150px;
|
||||||
|
text-align: left;
|
||||||
|
direction: rtl;
|
||||||
|
padding: 8px 0px;
|
||||||
|
background-color: rgb(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DialPadContextMenu_dialPad {
|
.mx_DialPadContextMenu_dialPad {
|
||||||
|
|
|
@ -118,6 +118,9 @@ $voipcall-plinth-color: #394049;
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
$theme-button-bg-color: #e3e8f0;
|
$theme-button-bg-color: #e3e8f0;
|
||||||
|
$dialpad-button-bg-color: #6F7882;
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
|
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
|
||||||
$roomlist-filter-active-bg-color: $bg-color;
|
$roomlist-filter-active-bg-color: $bg-color;
|
||||||
|
|
|
@ -114,6 +114,8 @@ $voipcall-plinth-color: #394049;
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
$theme-button-bg-color: #e3e8f0;
|
$theme-button-bg-color: #e3e8f0;
|
||||||
|
$dialpad-button-bg-color: #6F7882;
|
||||||
|
;
|
||||||
|
|
||||||
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
|
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
|
||||||
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
|
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
|
||||||
|
|
|
@ -181,6 +181,8 @@ $voipcall-plinth-color: #F4F6FA;
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
$theme-button-bg-color: #e3e8f0;
|
$theme-button-bg-color: #e3e8f0;
|
||||||
|
$dialpad-button-bg-color: #e3e8f0;
|
||||||
|
|
||||||
|
|
||||||
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
||||||
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
|
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
|
||||||
|
|
|
@ -173,6 +173,8 @@ $voipcall-plinth-color: #F4F6FA;
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
$theme-button-bg-color: #e3e8f0;
|
$theme-button-bg-color: #e3e8f0;
|
||||||
|
$dialpad-button-bg-color: #e3e8f0;
|
||||||
|
|
||||||
|
|
||||||
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
|
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
|
||||||
$roomlist-filter-active-bg-color: #ffffff;
|
$roomlist-filter-active-bg-color: #ffffff;
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
|
|
||||||
|
|
||||||
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 SettingsStore from "./settings/SettingsStore";
|
|
||||||
import {SettingLevel} from "./settings/SettingLevel";
|
|
||||||
import {setMatrixCallAudioInput, setMatrixCallVideoInput} from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
hasAnyLabeledDevices: async function() {
|
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
||||||
return devices.some(d => !!d.label);
|
|
||||||
},
|
|
||||||
|
|
||||||
getDevices: function() {
|
|
||||||
// Only needed for Electron atm, though should work in modern browsers
|
|
||||||
// once permission has been granted to the webapp
|
|
||||||
return navigator.mediaDevices.enumerateDevices().then(function(devices) {
|
|
||||||
const audiooutput = [];
|
|
||||||
const audioinput = [];
|
|
||||||
const videoinput = [];
|
|
||||||
|
|
||||||
devices.forEach((device) => {
|
|
||||||
switch (device.kind) {
|
|
||||||
case 'audiooutput': audiooutput.push(device); break;
|
|
||||||
case 'audioinput': audioinput.push(device); break;
|
|
||||||
case 'videoinput': videoinput.push(device); break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// console.log("Loaded WebRTC Devices", mediaDevices);
|
|
||||||
return {
|
|
||||||
audiooutput,
|
|
||||||
audioinput,
|
|
||||||
videoinput,
|
|
||||||
};
|
|
||||||
}, (error) => { console.log('Unable to refresh WebRTC Devices: ', error); });
|
|
||||||
},
|
|
||||||
|
|
||||||
loadDevices: function() {
|
|
||||||
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
|
|
||||||
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
|
|
||||||
|
|
||||||
setMatrixCallAudioInput(audioDeviceId);
|
|
||||||
setMatrixCallVideoInput(videoDeviceId);
|
|
||||||
},
|
|
||||||
|
|
||||||
setAudioOutput: function(deviceId) {
|
|
||||||
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
|
|
||||||
},
|
|
||||||
|
|
||||||
setAudioInput: function(deviceId) {
|
|
||||||
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
|
|
||||||
setMatrixCallAudioInput(deviceId);
|
|
||||||
},
|
|
||||||
|
|
||||||
setVideoInput: function(deviceId) {
|
|
||||||
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
|
|
||||||
setMatrixCallVideoInput(deviceId);
|
|
||||||
},
|
|
||||||
|
|
||||||
getAudioOutput: function() {
|
|
||||||
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
|
|
||||||
},
|
|
||||||
|
|
||||||
getAudioInput: function() {
|
|
||||||
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audioinput");
|
|
||||||
},
|
|
||||||
|
|
||||||
getVideoInput: function() {
|
|
||||||
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_videoinput");
|
|
||||||
},
|
|
||||||
};
|
|
120
src/MediaDeviceHandler.ts
Normal file
120
src/MediaDeviceHandler.ts
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
|
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 SettingsStore from "./settings/SettingsStore";
|
||||||
|
import { SettingLevel } from "./settings/SettingLevel";
|
||||||
|
import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix";
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
|
interface IMediaDevices {
|
||||||
|
audioOutput: Array<MediaDeviceInfo>;
|
||||||
|
audioInput: Array<MediaDeviceInfo>;
|
||||||
|
videoInput: Array<MediaDeviceInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MediaDeviceHandlerEvent {
|
||||||
|
AudioOutputChanged = "audio_output_changed",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MediaDeviceHandler extends EventEmitter {
|
||||||
|
private static internalInstance;
|
||||||
|
|
||||||
|
public static get instance(): MediaDeviceHandler {
|
||||||
|
if (!MediaDeviceHandler.internalInstance) {
|
||||||
|
MediaDeviceHandler.internalInstance = new MediaDeviceHandler();
|
||||||
|
}
|
||||||
|
return MediaDeviceHandler.internalInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async hasAnyLabeledDevices(): Promise<boolean> {
|
||||||
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
return devices.some(d => Boolean(d.label));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getDevices(): Promise<IMediaDevices> {
|
||||||
|
// Only needed for Electron atm, though should work in modern browsers
|
||||||
|
// once permission has been granted to the webapp
|
||||||
|
|
||||||
|
try {
|
||||||
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
|
||||||
|
const audioOutput = [];
|
||||||
|
const audioInput = [];
|
||||||
|
const videoInput = [];
|
||||||
|
|
||||||
|
devices.forEach((device) => {
|
||||||
|
switch (device.kind) {
|
||||||
|
case 'audiooutput': audioOutput.push(device); break;
|
||||||
|
case 'audioinput': audioInput.push(device); break;
|
||||||
|
case 'videoinput': videoInput.push(device); break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { audioOutput, audioInput, videoInput };
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Unable to refresh WebRTC Devices: ', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves devices from the SettingsStore and tells the js-sdk to use them
|
||||||
|
*/
|
||||||
|
public static loadDevices(): void {
|
||||||
|
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
|
||||||
|
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
|
||||||
|
|
||||||
|
setMatrixCallAudioInput(audioDeviceId);
|
||||||
|
setMatrixCallVideoInput(videoDeviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setAudioOutput(deviceId: string): void {
|
||||||
|
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
|
||||||
|
this.emit(MediaDeviceHandlerEvent.AudioOutputChanged, deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will not change the device that a potential call uses. The call will
|
||||||
|
* need to be ended and started again for this change to take effect
|
||||||
|
* @param {string} deviceId
|
||||||
|
*/
|
||||||
|
public setAudioInput(deviceId: string): void {
|
||||||
|
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
|
||||||
|
setMatrixCallAudioInput(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will not change the device that a potential call uses. The call will
|
||||||
|
* need to be ended and started again for this change to take effect
|
||||||
|
* @param {string} deviceId
|
||||||
|
*/
|
||||||
|
public setVideoInput(deviceId: string): void {
|
||||||
|
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
|
||||||
|
setMatrixCallVideoInput(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getAudioOutput(): string {
|
||||||
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getAudioInput(): string {
|
||||||
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audioinput");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getVideoInput(): string {
|
||||||
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_videoinput");
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
|
|
||||||
import {Key} from '../../Keyboard';
|
import {Key} from '../../Keyboard';
|
||||||
import PageTypes from '../../PageTypes';
|
import PageTypes from '../../PageTypes';
|
||||||
import CallMediaHandler from '../../CallMediaHandler';
|
import MediaDeviceHandler from '../../MediaDeviceHandler';
|
||||||
import { fixupColorFonts } from '../../utils/FontManager';
|
import { fixupColorFonts } from '../../utils/FontManager';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
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
|
// stash the MatrixClient in case we log out before we are unmounted
|
||||||
this._matrixClient = this.props.matrixClient;
|
this._matrixClient = this.props.matrixClient;
|
||||||
|
|
||||||
CallMediaHandler.loadDevices();
|
MediaDeviceHandler.loadDevices();
|
||||||
|
|
||||||
fixupColorFonts();
|
fixupColorFonts();
|
||||||
|
|
||||||
|
|
|
@ -337,11 +337,10 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRoomClicked = (room: IRoom, ev: ButtonEvent) => {
|
private onRoomClicked = (room: IRoom, ev: ButtonEvent) => {
|
||||||
|
// If room was shift-clicked, remove it from the room directory
|
||||||
if (ev.shiftKey && !this.state.selectedCommunityId) {
|
if (ev.shiftKey && !this.state.selectedCommunityId) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.removeFromDirectory(room);
|
this.removeFromDirectory(room);
|
||||||
} else {
|
|
||||||
this.showRoom(room);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -568,11 +567,11 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
let avatarUrl = null;
|
let avatarUrl = null;
|
||||||
if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
|
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 [
|
return [
|
||||||
<div key={ `${room.room_id}_avatar` }
|
<div
|
||||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
key={ `${room.room_id}_avatar` }
|
||||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
|
||||||
className="mx_RoomDirectory_roomAvatar"
|
className="mx_RoomDirectory_roomAvatar"
|
||||||
>
|
>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
|
@ -584,42 +583,50 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
url={avatarUrl}
|
url={avatarUrl}
|
||||||
/>
|
/>
|
||||||
</div>,
|
</div>,
|
||||||
<div key={ `${room.room_id}_description` }
|
<div
|
||||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
key={ `${room.room_id}_description` }
|
||||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
|
||||||
className="mx_RoomDirectory_roomDescription"
|
className="mx_RoomDirectory_roomDescription"
|
||||||
>
|
>
|
||||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
<div
|
||||||
<div className="mx_RoomDirectory_topic"
|
className="mx_RoomDirectory_name"
|
||||||
onClick={ (ev) => { ev.stopPropagation(); } }
|
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||||
|
>
|
||||||
|
{ name }
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mx_RoomDirectory_topic"
|
||||||
|
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||||
dangerouslySetInnerHTML={{ __html: topic }}
|
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>,
|
||||||
<div key={ `${room.room_id}_memberCount` }
|
<div
|
||||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
key={ `${room.room_id}_memberCount` }
|
||||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
|
||||||
className="mx_RoomDirectory_roomMemberCount"
|
className="mx_RoomDirectory_roomMemberCount"
|
||||||
>
|
>
|
||||||
{ room.num_joined_members }
|
{ room.num_joined_members }
|
||||||
</div>,
|
</div>,
|
||||||
<div key={ `${room.room_id}_preview` }
|
<div
|
||||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
key={ `${room.room_id}_preview` }
|
||||||
|
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
|
||||||
className="mx_RoomDirectory_preview"
|
className="mx_RoomDirectory_preview"
|
||||||
>
|
>
|
||||||
{previewButton}
|
{ previewButton }
|
||||||
</div>,
|
</div>,
|
||||||
<div key={ `${room.room_id}_join` }
|
<div
|
||||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
key={ `${room.room_id}_join` }
|
||||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
|
||||||
className="mx_RoomDirectory_join"
|
className="mx_RoomDirectory_join"
|
||||||
>
|
>
|
||||||
{joinOrViewButton}
|
{ joinOrViewButton }
|
||||||
</div>,
|
</div>,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import React from 'react';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu';
|
import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu';
|
||||||
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
|
import Field from "../elements/Field";
|
||||||
import Dialpad from '../voip/DialPad';
|
import Dialpad from '../voip/DialPad';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
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});
|
this.setState({value: this.state.value + digit});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange = (ev) => {
|
||||||
|
this.setState({value: ev.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <ContextMenu {...this.props}>
|
return <ContextMenu {...this.props}>
|
||||||
<div className="mx_DialPadContextMenu_header">
|
<div className="mx_DialPadContextMenu_header">
|
||||||
<div>
|
<div>
|
||||||
<span className="mx_DialPadContextMenu_title">{_t("Dial pad")}</span>
|
<span className="mx_DialPadContextMenu_title">{_t("Dial pad")}</span>
|
||||||
</div>
|
</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>
|
||||||
<div className="mx_DialPadContextMenu_horizSep" />
|
<div className="mx_DialPadContextMenu_horizSep" />
|
||||||
<div className="mx_DialPadContextMenu_dialPad">
|
<div className="mx_DialPadContextMenu_dialPad">
|
||||||
|
|
|
@ -39,6 +39,9 @@ import ProgressBar from "../elements/ProgressBar";
|
||||||
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
|
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
|
||||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||||
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
|
import TruncatedList from "../elements/TruncatedList";
|
||||||
|
import EntityTile from "../rooms/EntityTile";
|
||||||
|
import BaseAvatar from "../avatars/BaseAvatar";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
@ -204,6 +207,17 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
setSelectedToAdd(new Set(selectedToAdd));
|
setSelectedToAdd(new Set(selectedToAdd));
|
||||||
} : null;
|
} : 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">
|
return <div className="mx_AddExistingToSpace">
|
||||||
<SearchBox
|
<SearchBox
|
||||||
className="mx_textinput_icon mx_textinput_search"
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
|
@ -216,16 +230,21 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
{ rooms.length > 0 ? (
|
{ rooms.length > 0 ? (
|
||||||
<div className="mx_AddExistingToSpace_section">
|
<div className="mx_AddExistingToSpace_section">
|
||||||
<h3>{ _t("Rooms") }</h3>
|
<h3>{ _t("Rooms") }</h3>
|
||||||
{ rooms.map(room => {
|
<TruncatedList
|
||||||
return <Entry
|
truncateAt={truncateAt}
|
||||||
|
createOverflowElement={overflowTile}
|
||||||
|
getChildren={(start, end) => rooms.slice(start, end).map(room =>
|
||||||
|
<Entry
|
||||||
key={room.roomId}
|
key={room.roomId}
|
||||||
room={room}
|
room={room}
|
||||||
checked={selectedToAdd.has(room)}
|
checked={selectedToAdd.has(room)}
|
||||||
onChange={onChange ? (checked) => {
|
onChange={onChange ? (checked) => {
|
||||||
onChange(checked, room);
|
onChange(checked, room);
|
||||||
} : null}
|
} : null}
|
||||||
/>;
|
/>,
|
||||||
}) }
|
)}
|
||||||
|
getChildCount={() => rooms.length}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : undefined }
|
) : undefined }
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,9 @@ import NotificationBadge from "../rooms/NotificationBadge";
|
||||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||||
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
|
import TruncatedList from "../elements/TruncatedList";
|
||||||
|
import EntityTile from "../rooms/EntityTile";
|
||||||
|
import BaseAvatar from "../avatars/BaseAvatar";
|
||||||
|
|
||||||
const AVATAR_SIZE = 30;
|
const AVATAR_SIZE = 30;
|
||||||
|
|
||||||
|
@ -196,6 +199,17 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
|
||||||
}).match(lcQuery);
|
}).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
|
return <BaseDialog
|
||||||
title={_t("Forward message")}
|
title={_t("Forward message")}
|
||||||
className="mx_ForwardDialog"
|
className="mx_ForwardDialog"
|
||||||
|
@ -228,7 +242,10 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
|
||||||
<AutoHideScrollbar className="mx_ForwardList_content">
|
<AutoHideScrollbar className="mx_ForwardList_content">
|
||||||
{ rooms.length > 0 ? (
|
{ rooms.length > 0 ? (
|
||||||
<div className="mx_ForwardList_results">
|
<div className="mx_ForwardList_results">
|
||||||
{ rooms.map(room =>
|
<TruncatedList
|
||||||
|
truncateAt={truncateAt}
|
||||||
|
createOverflowElement={overflowTile}
|
||||||
|
getChildren={(start, end) => rooms.slice(start, end).map(room =>
|
||||||
<Entry
|
<Entry
|
||||||
key={room.roomId}
|
key={room.roomId}
|
||||||
room={room}
|
room={room}
|
||||||
|
@ -236,7 +253,9 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
|
||||||
matrixClient={cli}
|
matrixClient={cli}
|
||||||
onFinished={onFinished}
|
onFinished={onFinished}
|
||||||
/>,
|
/>,
|
||||||
) }
|
)}
|
||||||
|
getChildCount={() => rooms.length}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : <span className="mx_ForwardList_noResults">
|
) : <span className="mx_ForwardList_noResults">
|
||||||
{ _t("No results") }
|
{ _t("No results") }
|
||||||
|
|
|
@ -63,7 +63,8 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
||||||
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
|
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
|
||||||
|
|
||||||
state: IState = {
|
state: IState = {
|
||||||
disabledButtonIds: [],
|
disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter(b => b.disabled)
|
||||||
|
.map(b => b.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|
|
@ -16,31 +16,29 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("views.elements.TruncatedList")
|
interface IProps {
|
||||||
export default class TruncatedList extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
// The number of elements to show before truncating. If negative, no truncation is done.
|
// The number of elements to show before truncating. If negative, no truncation is done.
|
||||||
truncateAt: PropTypes.number,
|
truncateAt?: number;
|
||||||
// The className to apply to the wrapping div
|
// The className to apply to the wrapping div
|
||||||
className: PropTypes.string,
|
className?: string;
|
||||||
// A function that returns the children to be rendered into the element.
|
// 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`).
|
// 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
|
// If omitted, the React child elements will be used. This parameter can be used
|
||||||
// to avoid creating unnecessary React elements.
|
// to avoid creating unnecessary React elements.
|
||||||
getChildren: PropTypes.func,
|
getChildren?: (start: number, end: number) => Array<React.ReactNode>;
|
||||||
// A function that should return the total number of child element available.
|
// A function that should return the total number of child element available.
|
||||||
// Required if getChildren is supplied.
|
// Required if getChildren is supplied.
|
||||||
getChildCount: PropTypes.func,
|
getChildCount?: () => number;
|
||||||
// A function which will be invoked when an overflow element is required.
|
// A function which will be invoked when an overflow element is required.
|
||||||
// This will be inserted after the children.
|
// This will be inserted after the children.
|
||||||
createOverflowElement: PropTypes.func,
|
createOverflowElement?: (overflowCount: number, totalCount: number) => React.ReactNode;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.elements.TruncatedList")
|
||||||
|
export default class TruncatedList extends React.Component<IProps> {
|
||||||
static defaultProps ={
|
static defaultProps ={
|
||||||
truncateAt: 2,
|
truncateAt: 2,
|
||||||
createOverflowElement(overflowCount, totalCount) {
|
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) {
|
if (this.props.getChildren && this.props.getChildCount) {
|
||||||
return this.props.getChildren(start, end);
|
return this.props.getChildren(start, end);
|
||||||
} else {
|
} else {
|
||||||
|
@ -63,7 +61,7 @@ export default class TruncatedList extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getChildCount() {
|
private getChildCount(): number {
|
||||||
if (this.props.getChildren && this.props.getChildCount) {
|
if (this.props.getChildren && this.props.getChildCount) {
|
||||||
return this.props.getChildCount();
|
return this.props.getChildCount();
|
||||||
} else {
|
} else {
|
||||||
|
@ -73,10 +71,10 @@ export default class TruncatedList extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
let overflowNode = null;
|
let overflowNode = null;
|
||||||
|
|
||||||
const totalChildren = this._getChildCount();
|
const totalChildren = this.getChildCount();
|
||||||
let upperBound = totalChildren;
|
let upperBound = totalChildren;
|
||||||
if (this.props.truncateAt >= 0) {
|
if (this.props.truncateAt >= 0) {
|
||||||
const overflowCount = totalChildren - this.props.truncateAt;
|
const overflowCount = totalChildren - this.props.truncateAt;
|
||||||
|
@ -87,7 +85,7 @@ export default class TruncatedList extends React.Component {
|
||||||
upperBound = this.props.truncateAt;
|
upperBound = this.props.truncateAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const childNodes = this._getChildren(0, upperBound);
|
const childNodes = this.getChildren(0, upperBound);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className}>
|
<div className={this.props.className}>
|
|
@ -30,7 +30,7 @@ import RecordingPlayback from "../voice_messages/RecordingPlayback";
|
||||||
import {MsgType} from "matrix-js-sdk/src/@types/event";
|
import {MsgType} from "matrix-js-sdk/src/@types/event";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
import CallMediaHandler from "../../../CallMediaHandler";
|
import MediaDeviceHandler from "../../../MediaDeviceHandler";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
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
|
// 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.
|
// change between this and recording, but at least we will have tried.
|
||||||
try {
|
try {
|
||||||
const devices = await CallMediaHandler.getDevices();
|
const devices = await MediaDeviceHandler.getDevices();
|
||||||
if (!devices?.['audioinput']?.length) {
|
if (!devices?.['audioInput']?.length) {
|
||||||
Modal.createTrackedDialog('No Microphone Error', '', ErrorDialog, {
|
Modal.createTrackedDialog('No Microphone Error', '', ErrorDialog, {
|
||||||
title: _t("No microphone found"),
|
title: _t("No microphone found"),
|
||||||
description: <>
|
description: <>
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {_t} from "../../../../../languageHandler";
|
import {_t} from "../../../../../languageHandler";
|
||||||
import SdkConfig from "../../../../../SdkConfig";
|
import SdkConfig from "../../../../../SdkConfig";
|
||||||
import CallMediaHandler from "../../../../../CallMediaHandler";
|
import MediaDeviceHandler from "../../../../../MediaDeviceHandler";
|
||||||
import Field from "../../../elements/Field";
|
import Field from "../../../elements/Field";
|
||||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||||
|
@ -41,7 +41,7 @@ export default class VoiceUserSettingsTab extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const canSeeDeviceLabels = await CallMediaHandler.hasAnyLabeledDevices();
|
const canSeeDeviceLabels = await MediaDeviceHandler.hasAnyLabeledDevices();
|
||||||
if (canSeeDeviceLabels) {
|
if (canSeeDeviceLabels) {
|
||||||
this._refreshMediaDevices();
|
this._refreshMediaDevices();
|
||||||
}
|
}
|
||||||
|
@ -49,10 +49,10 @@ export default class VoiceUserSettingsTab extends React.Component {
|
||||||
|
|
||||||
_refreshMediaDevices = async (stream) => {
|
_refreshMediaDevices = async (stream) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
mediaDevices: await CallMediaHandler.getDevices(),
|
mediaDevices: await MediaDeviceHandler.getDevices(),
|
||||||
activeAudioOutput: CallMediaHandler.getAudioOutput(),
|
activeAudioOutput: MediaDeviceHandler.getAudioOutput(),
|
||||||
activeAudioInput: CallMediaHandler.getAudioInput(),
|
activeAudioInput: MediaDeviceHandler.getAudioInput(),
|
||||||
activeVideoInput: CallMediaHandler.getVideoInput(),
|
activeVideoInput: MediaDeviceHandler.getVideoInput(),
|
||||||
});
|
});
|
||||||
if (stream) {
|
if (stream) {
|
||||||
// kill stream (after we've enumerated the devices, otherwise we'd get empty labels again)
|
// 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) => {
|
_setAudioOutput = (e) => {
|
||||||
CallMediaHandler.setAudioOutput(e.target.value);
|
MediaDeviceHandler.instance.setAudioOutput(e.target.value);
|
||||||
this.setState({
|
this.setState({
|
||||||
activeAudioOutput: e.target.value,
|
activeAudioOutput: e.target.value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_setAudioInput = (e) => {
|
_setAudioInput = (e) => {
|
||||||
CallMediaHandler.setAudioInput(e.target.value);
|
MediaDeviceHandler.instance.setAudioInput(e.target.value);
|
||||||
this.setState({
|
this.setState({
|
||||||
activeAudioInput: e.target.value,
|
activeAudioInput: e.target.value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_setVideoInput = (e) => {
|
_setVideoInput = (e) => {
|
||||||
CallMediaHandler.setVideoInput(e.target.value);
|
MediaDeviceHandler.instance.setVideoInput(e.target.value);
|
||||||
this.setState({
|
this.setState({
|
||||||
activeVideoInput: e.target.value,
|
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) {
|
if (audioOutputs.length > 0) {
|
||||||
const defaultDevice = getDefaultDevice(audioOutputs);
|
const defaultDevice = getDefaultDevice(audioOutputs);
|
||||||
speakerDropdown = (
|
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) {
|
if (audioInputs.length > 0) {
|
||||||
const defaultDevice = getDefaultDevice(audioInputs);
|
const defaultDevice = getDefaultDevice(audioInputs);
|
||||||
microphoneDropdown = (
|
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) {
|
if (videoInputs.length > 0) {
|
||||||
const defaultDevice = getDefaultDevice(videoInputs);
|
const defaultDevice = getDefaultDevice(videoInputs);
|
||||||
webcamDropdown = (
|
webcamDropdown = (
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed';
|
import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed';
|
||||||
import { logger } from 'matrix-js-sdk/src/logger';
|
import { logger } from 'matrix-js-sdk/src/logger';
|
||||||
import CallMediaHandler from "../../../CallMediaHandler";
|
import MediaDeviceHandler, { MediaDeviceHandlerEvent } from "../../../MediaDeviceHandler";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
feed: CallFeed,
|
feed: CallFeed,
|
||||||
|
@ -27,19 +27,25 @@ export default class AudioFeed extends React.Component<IProps> {
|
||||||
private element = createRef<HTMLAudioElement>();
|
private element = createRef<HTMLAudioElement>();
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
MediaDeviceHandler.instance.addListener(
|
||||||
|
MediaDeviceHandlerEvent.AudioOutputChanged,
|
||||||
|
this.onAudioOutputChanged,
|
||||||
|
);
|
||||||
this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream);
|
this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream);
|
||||||
this.playMedia();
|
this.playMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
MediaDeviceHandler.instance.removeListener(
|
||||||
|
MediaDeviceHandlerEvent.AudioOutputChanged,
|
||||||
|
this.onAudioOutputChanged,
|
||||||
|
);
|
||||||
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
|
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
|
||||||
this.stopMedia();
|
this.stopMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
private playMedia() {
|
private onAudioOutputChanged = (audioOutput: string) => {
|
||||||
const element = this.element.current;
|
const element = this.element.current;
|
||||||
const audioOutput = CallMediaHandler.getAudioOutput();
|
|
||||||
|
|
||||||
if (audioOutput) {
|
if (audioOutput) {
|
||||||
try {
|
try {
|
||||||
// This seems quite unreliable in Chrome, although I haven't yet managed to make a jsfiddle where
|
// 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);
|
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.muted = false;
|
||||||
element.srcObject = this.props.feed.stream;
|
element.srcObject = this.props.feed.stream;
|
||||||
element.autoplay = true;
|
element.autoplay = true;
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import * as Recorder from 'opus-recorder';
|
import * as Recorder from 'opus-recorder';
|
||||||
import encoderPath from 'opus-recorder/dist/encoderWorker.min.js';
|
import encoderPath from 'opus-recorder/dist/encoderWorker.min.js';
|
||||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import CallMediaHandler from "../CallMediaHandler";
|
import MediaDeviceHandler from "../MediaDeviceHandler";
|
||||||
import {SimpleObservable} from "matrix-widget-api";
|
import {SimpleObservable} from "matrix-widget-api";
|
||||||
import {clamp, percentageOf, percentageWithin} from "../utils/numbers";
|
import {clamp, percentageOf, percentageWithin} from "../utils/numbers";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
|
@ -97,7 +97,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
|
||||||
audio: {
|
audio: {
|
||||||
channelCount: CHANNELS,
|
channelCount: CHANNELS,
|
||||||
noiseSuppression: true, // browsers ignore constraints they can't honour
|
noiseSuppression: true, // browsers ignore constraints they can't honour
|
||||||
deviceId: CallMediaHandler.getAudioInput(),
|
deviceId: MediaDeviceHandler.getAudioInput(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.recorderContext = createAudioContext({
|
this.recorderContext = createAudioContext({
|
||||||
|
|
|
@ -1334,6 +1334,7 @@
|
||||||
|
|
||||||
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz":
|
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz":
|
||||||
version "3.2.3"
|
version "3.2.3"
|
||||||
|
uid cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4
|
||||||
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4"
|
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4"
|
||||||
|
|
||||||
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents":
|
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents":
|
||||||
|
@ -5772,10 +5773,10 @@ matrix-react-test-utils@^0.2.3:
|
||||||
"@babel/traverse" "^7.13.17"
|
"@babel/traverse" "^7.13.17"
|
||||||
walk "^2.3.14"
|
walk "^2.3.14"
|
||||||
|
|
||||||
matrix-widget-api@^0.1.0-beta.14:
|
matrix-widget-api@^0.1.0-beta.15:
|
||||||
version "0.1.0-beta.14"
|
version "0.1.0-beta.15"
|
||||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.14.tgz#e38beed71c5ebd62c1ac1d79ef262d7150b42c70"
|
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.15.tgz#b02511f93fe1a3634868b6e246d736107f182745"
|
||||||
integrity sha512-5tC6LO1vCblKg/Hfzf5U1eHPz1nHUZIobAm3gkEKV5vpYPgRpr8KdkLiGB78VZid0tB17CVtAb4VKI8CQ3lhAQ==
|
integrity sha512-sWmtb8ZarSbHVbk5ni7IHBR9jOh7m1+5R4soky0fEO9VKl+MN7skT0+qNux3J9WuUAu2D80dZW9xPUT9cxfxbg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/events" "^3.0.0"
|
"@types/events" "^3.0.0"
|
||||||
events "^3.2.0"
|
events "^3.2.0"
|
||||||
|
|
Loading…
Reference in a new issue