mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-05 14:22:28 +00:00
Compare commits
14 Commits
saghul-pat
...
4732
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87a110b9c3 | ||
|
|
a7db7ecaff | ||
|
|
79bb98dab3 | ||
|
|
d22792c9e3 | ||
|
|
41e6af3464 | ||
|
|
f50fd7b7bd | ||
|
|
43761fc398 | ||
|
|
4c39d83ff1 | ||
|
|
e525c2b2ec | ||
|
|
f69a31d9c6 | ||
|
|
67930edae2 | ||
|
|
c11a94f7d7 | ||
|
|
bfd093b0ba | ||
|
|
861935c9d7 |
@@ -115,7 +115,7 @@ import {
|
||||
submitFeedback
|
||||
} from './react/features/feedback';
|
||||
import { showNotification } from './react/features/notifications';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
|
||||
import { mediaPermissionPromptVisibilityChanged, toggleSlowGUMOverlay } from './react/features/overlay';
|
||||
import { suspendDetected } from './react/features/power-monitor';
|
||||
import {
|
||||
initPrejoin,
|
||||
@@ -502,6 +502,11 @@ export default {
|
||||
);
|
||||
}
|
||||
|
||||
JitsiMeetJS.mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.SLOW_GET_USER_MEDIA,
|
||||
() => APP.store.dispatch(toggleSlowGUMOverlay(true))
|
||||
);
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
|
||||
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
|
||||
@@ -519,8 +524,10 @@ export default {
|
||||
|
||||
return createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
timeout
|
||||
}, true)
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
.then(([ audioStream ]) =>
|
||||
[ desktopStream, audioStream ])
|
||||
.catch(error => {
|
||||
@@ -536,8 +543,10 @@ export default {
|
||||
return requestedAudio
|
||||
? createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
timeout
|
||||
}, true)
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
: [];
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -551,8 +560,10 @@ export default {
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
timeout
|
||||
}, true)
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
.catch(err => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
@@ -574,8 +585,10 @@ export default {
|
||||
return (
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
timeout
|
||||
}, true));
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
}));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
@@ -598,8 +611,9 @@ export default {
|
||||
return requestedVideo
|
||||
? createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
timeout
|
||||
}, true)
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
: [];
|
||||
})
|
||||
.catch(err => {
|
||||
@@ -619,6 +633,7 @@ export default {
|
||||
// the user inputs their credentials, but the dialog would be
|
||||
// overshadowed by the overlay.
|
||||
tryCreateLocalTracks.then(tracks => {
|
||||
APP.store.dispatch(toggleSlowGUMOverlay(false));
|
||||
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
|
||||
|
||||
return tracks;
|
||||
@@ -882,7 +897,7 @@ export default {
|
||||
showUI && APP.store.dispatch(notifyMicError(error));
|
||||
};
|
||||
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, false)
|
||||
createLocalTracksF({ devices: [ 'audio' ] })
|
||||
.then(([ audioTrack ]) => audioTrack)
|
||||
.catch(error => {
|
||||
maybeShowErrorDialog(error);
|
||||
@@ -996,7 +1011,7 @@ export default {
|
||||
//
|
||||
// FIXME when local track creation is moved to react/redux
|
||||
// it should take care of the use case described above
|
||||
createLocalTracksF({ devices: [ 'video' ] }, false)
|
||||
createLocalTracksF({ devices: [ 'video' ] })
|
||||
.then(([ videoTrack ]) => videoTrack)
|
||||
.catch(error => {
|
||||
// FIXME should send some feedback to the API on error ?
|
||||
|
||||
@@ -240,6 +240,12 @@ var config = {
|
||||
// 90: 2
|
||||
// },
|
||||
|
||||
// Provides a way to translate the legacy bridge signaling messages, 'LastNChangedEvent',
|
||||
// 'SelectedEndpointsChangedEvent' and 'ReceiverVideoConstraint' into the new 'ReceiverVideoConstraints' message
|
||||
// that invokes the new bandwidth allocation algorithm in the bridge which is described here
|
||||
// - https://github.com/jitsi/jitsi-videobridge/blob/master/doc/allocation.md.
|
||||
// useNewBandwidthAllocationStrategy: false,
|
||||
|
||||
// Specify the settings for video quality optimizations on the client.
|
||||
// videoQuality: {
|
||||
// // Provides a way to prevent a video codec from being negotiated on the JVB connection. The codec specified
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/**
|
||||
* Mixins that mimic the way Atlaskit fills the screen with modals at low screen widths.
|
||||
*/
|
||||
@mixin full-size-modal-positioner() {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin full-size-modal-dialog() {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the @atlaskit/flag container up a little bit so it does not cover the
|
||||
* toolbar with the first notification.
|
||||
@@ -56,4 +74,43 @@
|
||||
.toolbox-button-wth-dialog > div:nth-child(2) {
|
||||
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 46px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following selectors keep the chat modal full-size anywhere between 100px
|
||||
* and 580px for desktop or 680px for mobile.
|
||||
*/
|
||||
@media (min-width: 100px) and (max-width: 320px) {
|
||||
.smiley-input {
|
||||
display: none;
|
||||
}
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 580px) {
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 580px) and (max-width: 680px) {
|
||||
.mobile-browser {
|
||||
&.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
&.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
109
css/_chat.scss
109
css/_chat.scss
@@ -32,6 +32,13 @@
|
||||
width: $sidebarWidth;
|
||||
word-wrap: break-word;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > :first-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
@@ -122,16 +129,61 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
padding: 0 16px 24px;
|
||||
|
||||
&.populated {
|
||||
#chat-input {
|
||||
border: 1px solid #619CF4;
|
||||
|
||||
.send-button {
|
||||
background: #1B67EC;
|
||||
cursor: pointer;
|
||||
|
||||
path {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
border: 1px solid $chatInputSeparatorColor;
|
||||
display: flex;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
|
||||
* {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.send-button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 3px;
|
||||
|
||||
path {
|
||||
fill: $chatInputSeparatorColor;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-browser {
|
||||
.send-button {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
@@ -161,10 +213,47 @@
|
||||
#nickname {
|
||||
text-align: center;
|
||||
color: #9d9d9d;
|
||||
font-size: 18px;
|
||||
margin-top: 30px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
font-size: 16px;
|
||||
margin: auto 0;
|
||||
padding: 0 16px;
|
||||
|
||||
input {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
label {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
height: 40px;
|
||||
background: #1B67EC;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
color: #757575;
|
||||
background: #11336E;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-browser {
|
||||
#nickname {
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sideToolbarContainer {
|
||||
@@ -411,6 +500,16 @@
|
||||
#chatconversation {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
padding: 0 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.touchmove-hack {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,21 +53,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin full-size-modal-positioner() {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin full-size-modal-dialog() {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
.welcome {
|
||||
display: block;
|
||||
@@ -165,25 +150,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 580px) {
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 580px) and (max-width: 680px) {
|
||||
.mobile-browser {
|
||||
&.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
&.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,4 +33,12 @@
|
||||
bottom: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__spinner-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
|
||||
- (void)sendHangUp;
|
||||
- (void)sendSetAudioMuted:(BOOL)muted;
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)sendEndpointTextMessage:(NSString*)message :(NSString*)to;
|
||||
- (void)toggleScreenShare;
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completion;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
|
||||
|
||||
@end
|
||||
|
||||
@@ -153,7 +153,7 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:setAudioMutedAction body:data];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message {
|
||||
- (void)sendEndpointTextMessage:(NSString*)message :(NSString*)to {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"to"] = to;
|
||||
data[@"message"] = message;
|
||||
@@ -185,7 +185,7 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:closeChatAction body:nil];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"to"] = to;
|
||||
data[@"message"] = message;
|
||||
|
||||
@@ -38,11 +38,11 @@
|
||||
- (void)leave;
|
||||
- (void)hangUp;
|
||||
- (void)setAudioMuted:(BOOL)muted;
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)toggleScreenShare;
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler;
|
||||
- (void)openChat:(NSString * _Nullable)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
|
||||
@end
|
||||
|
||||
@@ -125,9 +125,9 @@ static void initializeViewsMap() {
|
||||
[externalAPI sendSetAudioMuted:muted];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message {
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendEndpointTextMessage:to :message];
|
||||
[externalAPI sendEndpointTextMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)toggleScreenShare {
|
||||
@@ -135,7 +135,7 @@ static void initializeViewsMap() {
|
||||
[externalAPI toggleScreenShare];
|
||||
}
|
||||
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler {
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI retrieveParticipantsInfo:completionHandler];
|
||||
}
|
||||
@@ -150,9 +150,9 @@ static void initializeViewsMap() {
|
||||
[externalAPI closeChat];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendChatMessage:to :message];
|
||||
[externalAPI sendChatMessage:message :to];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"today": "Today"
|
||||
},
|
||||
"chat": {
|
||||
"enter": "Enter chat room",
|
||||
"error": "Error: your message was not sent. Reason: {{error}}",
|
||||
"fieldPlaceHolder": "Type your message here",
|
||||
"messagebox": "Type a message",
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconCheck, IconCopy } from '../../base/icons';
|
||||
import { copyText } from '../../base/util';
|
||||
import { translate } from '../i18n';
|
||||
import { copyText } from '../util';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -53,6 +53,11 @@ type Props = {
|
||||
*/
|
||||
hideCancelButton: boolean,
|
||||
|
||||
/**
|
||||
* If true, no footer will be displayed.
|
||||
*/
|
||||
disableFooter?: boolean,
|
||||
|
||||
i18n: Object,
|
||||
|
||||
/**
|
||||
@@ -174,6 +179,10 @@ class StatelessDialog extends Component<Props> {
|
||||
this._renderCancelButton()
|
||||
].filter(Boolean);
|
||||
|
||||
if (this.props.disableFooter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalFooter showKeyline = { propsFromModalFooter.showKeyline } >
|
||||
{
|
||||
|
||||
@@ -11,6 +11,16 @@ export function isMobileBrowser() {
|
||||
return Platform.OS === 'android' || Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the current environment is an ios mobile device.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the chrome extensions defined in the config file are installed or not.
|
||||
*
|
||||
|
||||
@@ -73,6 +73,7 @@ export { default as IconOpenInNew } from './open_in_new.svg';
|
||||
export { default as IconOutlook } from './office365.svg';
|
||||
export { default as IconPhone } from './phone.svg';
|
||||
export { default as IconPin } from './enlarge.svg';
|
||||
export { default as IconPlane } from './paper-plane.svg';
|
||||
export { default as IconPresentation } from './presentation.svg';
|
||||
export { default as IconRaisedHand } from './raised-hand.svg';
|
||||
export { default as IconRec } from './rec.svg';
|
||||
|
||||
3
react/features/base/icons/svg/paper-plane.svg
Normal file
3
react/features/base/icons/svg/paper-plane.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6667 1.66663L1.66669 10.8333L7.6326 11.8276L8.33335 17.0833L10.644 13.2323L16.6667 17.9166V1.66663ZM8.73722 10.3221L6.35041 9.92426L15 4.63839V14.5089L11.3161 11.6436L12.5 7.49996L8.73722 10.3221Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 369 B |
@@ -127,7 +127,6 @@ export function createLocalTracksA(options = {}) {
|
||||
options.facingMode || CAMERA_FACING_MODE.USER,
|
||||
micDeviceId: options.micDeviceId
|
||||
},
|
||||
/* firePermissionPromptIsShownEvent */ false,
|
||||
store)
|
||||
.then(
|
||||
localTracks => {
|
||||
|
||||
@@ -63,16 +63,25 @@ export async function createLocalPresenterTrack(options, desktopHeight) {
|
||||
* @param {string|null} [options.micDeviceId] - Microphone device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
|
||||
* @param {boolean} [firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* should check for a {@code getUserMedia} permission prompt and fire a
|
||||
* corresponding event.
|
||||
* @param {boolean} [options.fireSlowPromiseEvent] - Whether lib-jitsi-meet
|
||||
* should check for a slow {@code getUserMedia} request and fire a
|
||||
* corresponding event.
|
||||
* @param {Object} store - The redux store in the context of which the function
|
||||
* is to execute and from which state such as {@code config} is to be retrieved.
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
*/
|
||||
export function createLocalTracksF(options = {}, firePermissionPromptIsShownEvent, store) {
|
||||
export function createLocalTracksF(options = {}, store) {
|
||||
let { cameraDeviceId, micDeviceId } = options;
|
||||
const { desktopSharingSourceDevice, desktopSharingSources, timeout } = options;
|
||||
const {
|
||||
desktopSharingSourceDevice,
|
||||
desktopSharingSources,
|
||||
firePermissionPromptIsShownEvent,
|
||||
fireSlowPromiseEvent,
|
||||
timeout
|
||||
} = options;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
// TODO The app's settings should go in the redux store and then the
|
||||
@@ -114,11 +123,12 @@ export function createLocalTracksF(options = {}, firePermissionPromptIsShownEven
|
||||
devices: options.devices.slice(0),
|
||||
effects,
|
||||
firefox_fake_device, // eslint-disable-line camelcase
|
||||
firePermissionPromptIsShownEvent,
|
||||
fireSlowPromiseEvent,
|
||||
micDeviceId,
|
||||
resolution,
|
||||
timeout
|
||||
},
|
||||
firePermissionPromptIsShownEvent)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Failed to create local tracks', options.devices, err);
|
||||
|
||||
@@ -161,7 +171,10 @@ export function createPrejoinTracks() {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({ devices: initialDevices }, true)
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
.catch(err => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
@@ -169,7 +182,10 @@ export function createPrejoinTracks() {
|
||||
errors.audioAndVideoError = err;
|
||||
|
||||
return (
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, true));
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
@@ -190,7 +206,10 @@ export function createPrejoinTracks() {
|
||||
|
||||
// Try video only...
|
||||
return requestedVideo
|
||||
? createLocalTracksF({ devices: [ 'video' ] }, true)
|
||||
? createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
: [];
|
||||
})
|
||||
.catch(err => {
|
||||
|
||||
@@ -31,24 +31,15 @@ export function assignIfDefined(target: Object, source: Object) {
|
||||
* @param {string} textToCopy - Text to be copied.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function copyText(textToCopy: string) {
|
||||
const fakeTextArea = document.createElement('textarea');
|
||||
export async function copyText(textToCopy: string) {
|
||||
let result;
|
||||
|
||||
// $FlowFixMe
|
||||
document.body.appendChild(fakeTextArea);
|
||||
fakeTextArea.value = textToCopy;
|
||||
fakeTextArea.select();
|
||||
|
||||
try {
|
||||
result = document.execCommand('copy');
|
||||
result = await navigator.clipboard.writeText(textToCopy);
|
||||
} catch (err) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
document.body.removeChild(fakeTextArea);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,10 @@ export * from './actions.any';
|
||||
*/
|
||||
export function openChat(participant: Object) {
|
||||
return function(dispatch: (Object) => Object) {
|
||||
dispatch({ participant,
|
||||
type: OPEN_CHAT });
|
||||
VideoLayout.onResize();
|
||||
dispatch({
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,5 +41,8 @@ export function toggleChat() {
|
||||
} else {
|
||||
dispatch(openChat());
|
||||
}
|
||||
|
||||
// Recompute the large video size whenever we toggle the chat, as it takes chat state into account.
|
||||
VideoLayout.onResize();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import AbstractChat, {
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
@@ -14,8 +13,10 @@ import ChatDialog from './ChatDialog';
|
||||
import Header from './ChatDialogHeader';
|
||||
import ChatInput from './ChatInput';
|
||||
import DisplayNameForm from './DisplayNameForm';
|
||||
import KeyboardAvoider from './KeyboardAvoider';
|
||||
import MessageContainer from './MessageContainer';
|
||||
import MessageRecipient from './MessageRecipient';
|
||||
import TouchmoveHack from './TouchmoveHack';
|
||||
|
||||
/**
|
||||
* React Component for holding the chat feature in a side panel that slides in
|
||||
@@ -50,7 +51,6 @@ class Chat extends AbstractChat<Props> {
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._renderPanelContent = this._renderPanelContent.bind(this);
|
||||
this._onChatInputResize = this._onChatInputResize.bind(this);
|
||||
this._onToggleChat = this._onToggleChat.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,13 +112,16 @@ class Chat extends AbstractChat<Props> {
|
||||
_renderChat() {
|
||||
return (
|
||||
<>
|
||||
<MessageContainer
|
||||
messages = { this.props._messages }
|
||||
ref = { this._messageContainerRef } />
|
||||
<TouchmoveHack isModal = { this.props._isModal }>
|
||||
<MessageContainer
|
||||
messages = { this.props._messages }
|
||||
ref = { this._messageContainerRef } />
|
||||
</TouchmoveHack>
|
||||
<MessageRecipient />
|
||||
<ChatInput
|
||||
onResize = { this._onChatInputResize }
|
||||
onSend = { this._onSendMessage } />
|
||||
<KeyboardAvoider />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -132,9 +135,7 @@ class Chat extends AbstractChat<Props> {
|
||||
*/
|
||||
_renderChatHeader() {
|
||||
return (
|
||||
<Header
|
||||
className = 'chat-header'
|
||||
onCancel = { this._onToggleChat } />
|
||||
<Header className = 'chat-header' />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -198,18 +199,6 @@ class Chat extends AbstractChat<Props> {
|
||||
}
|
||||
|
||||
_onSendMessage: (string) => void;
|
||||
|
||||
_onToggleChat: () => void;
|
||||
|
||||
/**
|
||||
* Toggles the chat window.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onToggleChat() {
|
||||
this.props.dispatch(toggleChat());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(Chat));
|
||||
|
||||
@@ -24,6 +24,7 @@ function ChatDialog({ children }: Props) {
|
||||
<Dialog
|
||||
customHeader = { Header }
|
||||
disableEnter = { true }
|
||||
disableFooter = { true }
|
||||
hideCancelButton = { true }
|
||||
submitDisabled = { true }
|
||||
titleKey = 'chat.title'>
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconClose } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { closeChat } from '../../actions.any';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -42,6 +42,6 @@ function Header({ onCancel, className, t }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = { onCancel: closeChat };
|
||||
const mapDispatchToProps = { onCancel: toggleChat };
|
||||
|
||||
export default translate(connect(null, mapDispatchToProps)(Header));
|
||||
|
||||
@@ -6,6 +6,7 @@ import TextareaAutosize from 'react-textarea-autosize';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconPlane } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import SmileysPanel from './SmileysPanel';
|
||||
@@ -81,6 +82,7 @@ class ChatInput extends Component<Props, State> {
|
||||
this._onDetectSubmit = this._onDetectSubmit.bind(this);
|
||||
this._onMessageChange = this._onMessageChange.bind(this);
|
||||
this._onSmileySelect = this._onSmileySelect.bind(this);
|
||||
this._onSubmitMessage = this._onSubmitMessage.bind(this);
|
||||
this._onToggleSmileysPanel = this._onToggleSmileysPanel.bind(this);
|
||||
this._setTextAreaRef = this._setTextAreaRef.bind(this);
|
||||
}
|
||||
@@ -109,30 +111,39 @@ class ChatInput extends Component<Props, State> {
|
||||
? 'show-smileys' : 'hide-smileys'} smileys-panel`;
|
||||
|
||||
return (
|
||||
<div id = 'chat-input' >
|
||||
<div className = 'smiley-input'>
|
||||
<div id = 'smileysarea'>
|
||||
<div id = 'smileys'>
|
||||
<Emoji
|
||||
onClick = { this._onToggleSmileysPanel }
|
||||
text = ':)' />
|
||||
<div className = { `chat-input-container${this.state.message.trim().length ? ' populated' : ''}` }>
|
||||
<div id = 'chat-input' >
|
||||
<div className = 'smiley-input'>
|
||||
<div id = 'smileysarea'>
|
||||
<div id = 'smileys'>
|
||||
<Emoji
|
||||
onClick = { this._onToggleSmileysPanel }
|
||||
text = ':)' />
|
||||
</div>
|
||||
</div>
|
||||
<div className = { smileysPanelClassName }>
|
||||
<SmileysPanel
|
||||
onSmileySelect = { this._onSmileySelect } />
|
||||
</div>
|
||||
</div>
|
||||
<div className = { smileysPanelClassName }>
|
||||
<SmileysPanel
|
||||
onSmileySelect = { this._onSmileySelect } />
|
||||
<div className = 'usrmsg-form'>
|
||||
<TextareaAutosize
|
||||
id = 'usermsg'
|
||||
inputRef = { this._setTextAreaRef }
|
||||
maxRows = { 5 }
|
||||
onChange = { this._onMessageChange }
|
||||
onHeightChange = { this.props.onResize }
|
||||
onKeyDown = { this._onDetectSubmit }
|
||||
placeholder = { this.props.t('chat.messagebox') }
|
||||
value = { this.state.message } />
|
||||
</div>
|
||||
<div className = 'send-button-container'>
|
||||
<div
|
||||
className = 'send-button'
|
||||
onClick = { this._onSubmitMessage }>
|
||||
<Icon src = { IconPlane } />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className = 'usrmsg-form'>
|
||||
<TextareaAutosize
|
||||
id = 'usermsg'
|
||||
inputRef = { this._setTextAreaRef }
|
||||
maxRows = { 5 }
|
||||
onChange = { this._onMessageChange }
|
||||
onHeightChange = { this.props.onResize }
|
||||
onKeyDown = { this._onDetectSubmit }
|
||||
placeholder = { this.props.t('chat.messagebox') }
|
||||
value = { this.state.message } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -148,6 +159,24 @@ class ChatInput extends Component<Props, State> {
|
||||
this._textArea && this._textArea.focus();
|
||||
}
|
||||
|
||||
|
||||
_onSubmitMessage: () => void;
|
||||
|
||||
/**
|
||||
* Submits the message to the chat window.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmitMessage() {
|
||||
const trimmed = this.state.message.trim();
|
||||
|
||||
if (trimmed) {
|
||||
this.props.onSend(trimmed);
|
||||
|
||||
this.setState({ message: '' });
|
||||
}
|
||||
|
||||
}
|
||||
_onDetectSubmit: (Object) => void;
|
||||
|
||||
/**
|
||||
@@ -163,13 +192,7 @@ class ChatInput extends Component<Props, State> {
|
||||
&& event.shiftKey === false) {
|
||||
event.preventDefault();
|
||||
|
||||
const trimmed = this.state.message.trim();
|
||||
|
||||
if (trimmed) {
|
||||
this.props.onSend(trimmed);
|
||||
|
||||
this.setState({ message: '' });
|
||||
}
|
||||
this._onSubmitMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { updateSettings } from '../../../base/settings';
|
||||
|
||||
import KeyboardAvoider from './KeyboardAvoider';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@DisplayNameForm}.
|
||||
*/
|
||||
@@ -70,16 +72,24 @@ class DisplayNameForm extends Component<Props, State> {
|
||||
|
||||
return (
|
||||
<div id = 'nickname'>
|
||||
<span>{ this.props.t('chat.nickname.title') }</span>
|
||||
<form onSubmit = { this._onSubmit }>
|
||||
<FieldTextStateless
|
||||
autoFocus = { true }
|
||||
compact = { true }
|
||||
id = 'nickinput'
|
||||
label = { t('chat.nickname.title') }
|
||||
onChange = { this._onDisplayNameChange }
|
||||
placeholder = { t('chat.nickname.popover') }
|
||||
shouldFitContainer = { true }
|
||||
type = 'text'
|
||||
value = { this.state.displayName } />
|
||||
</form>
|
||||
<div
|
||||
className = { `enter-chat${this.state.displayName.trim() ? '' : ' disabled'}` }
|
||||
onClick = { this._onSubmit }>
|
||||
{ t('chat.enter') }
|
||||
</div>
|
||||
<KeyboardAvoider />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
60
react/features/chat/components/web/KeyboardAvoider.js
Normal file
60
react/features/chat/components/web/KeyboardAvoider.js
Normal file
@@ -0,0 +1,60 @@
|
||||
// @flow
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { isIosMobileBrowser } from '../../../base/environment/utils';
|
||||
|
||||
const Avoider = styled.div`
|
||||
height: ${props => props.elementHeight}px;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Component that renders an element to lift the chat input above the Safari keyboard,
|
||||
* computing the appropriate height comparisons based on the {@code visualViewport}.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function KeyboardAvoider() {
|
||||
if (!isIosMobileBrowser()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [ elementHeight, setElementHeight ] = useState(0);
|
||||
const [ storedHeight, setStoredHeight ] = useState(window.innerHeight);
|
||||
|
||||
/**
|
||||
* Handles the resizing of the visual viewport in order to compute
|
||||
* the {@code KeyboardAvoider}'s height.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleViewportResize() {
|
||||
const { innerWidth, visualViewport: { width, height } } = window;
|
||||
|
||||
// Compare the widths to make sure the {@code visualViewport} didn't resize due to zooming.
|
||||
if (width === innerWidth) {
|
||||
if (height < storedHeight) {
|
||||
setElementHeight(storedHeight - height);
|
||||
} else {
|
||||
setElementHeight(0);
|
||||
}
|
||||
setStoredHeight(height);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Call the handler in case the keyboard is open when the {@code KeyboardAvoider} is mounted.
|
||||
handleViewportResize();
|
||||
|
||||
window.visualViewport.addEventListener('resize', handleViewportResize);
|
||||
|
||||
return () => {
|
||||
window.visualViewport.removeEventListener('resize', handleViewportResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <Avoider elementHeight = { elementHeight } />;
|
||||
}
|
||||
|
||||
export default KeyboardAvoider;
|
||||
67
react/features/chat/components/web/TouchmoveHack.js
Normal file
67
react/features/chat/components/web/TouchmoveHack.js
Normal file
@@ -0,0 +1,67 @@
|
||||
// @flow
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The component(s) that need to be scrollable on mobile.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Whether the component is rendered within a modal.
|
||||
*/
|
||||
isModal: boolean
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Component that disables {@code touchmove} propagation below it.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function TouchmoveHack({ children, isModal }: Props) {
|
||||
if (!isModal || !isMobileBrowser()) {
|
||||
return children;
|
||||
}
|
||||
|
||||
const touchMoveElementRef = useRef(null);
|
||||
|
||||
/**
|
||||
* Atlaskit's {@code Modal} uses a third party library to disable touchmove events
|
||||
* which makes scrolling inside dialogs impossible. We therefore employ this hack
|
||||
* to intercept and stop the propagation of touchmove events from this wrapper that
|
||||
* is placed around the chat conversation from the {@code ChatDialog}.
|
||||
*
|
||||
* @param {Event} event - The touchmove event fired within the component.
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleTouchMove(event: TouchEvent) {
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (touchMoveElementRef && touchMoveElementRef.current) {
|
||||
touchMoveElementRef.current.addEventListener('touchmove', handleTouchMove, true);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (touchMoveElementRef && touchMoveElementRef.current) {
|
||||
touchMoveElementRef.current.removeEventListener('touchmove', handleTouchMove, true);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'touchmove-hack'
|
||||
ref = { touchMoveElementRef }>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TouchmoveHack;
|
||||
@@ -58,6 +58,14 @@ const DEFAULT_STATE = {
|
||||
*/
|
||||
defaultBranding: true,
|
||||
|
||||
/**
|
||||
* Url for a custom page for DID numbers list.
|
||||
*
|
||||
* @public
|
||||
* @type {string}
|
||||
*/
|
||||
didPageUrl: '',
|
||||
|
||||
/**
|
||||
* The custom invite domain.
|
||||
*
|
||||
@@ -101,6 +109,7 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
defaultBranding,
|
||||
didPageUrl,
|
||||
inviteDomain,
|
||||
logoClickUrl,
|
||||
logoImageUrl
|
||||
@@ -110,6 +119,7 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
defaultBranding,
|
||||
didPageUrl,
|
||||
inviteDomain,
|
||||
logoClickUrl,
|
||||
logoImageUrl,
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// @flow
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { createInviteDialogEvent, sendAnalytics } from '../../../../analytics';
|
||||
import { getRoomName } from '../../../../base/conference';
|
||||
import { getInviteURL } from '../../../../base/connection';
|
||||
import { Dialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant } from '../../../../base/participants';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { isVpaasMeeting } from '../../../../billing-counter/functions';
|
||||
import EmbedMeetingTrigger from '../../../../embed-meeting/components/EmbedMeetingTrigger';
|
||||
@@ -26,11 +24,6 @@ declare var interfaceConfig: Object;
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name of the current conference. Used as part of inviting users.
|
||||
*/
|
||||
_conferenceName: string,
|
||||
|
||||
/**
|
||||
* The object representing the dialIn feature.
|
||||
*/
|
||||
@@ -41,6 +34,11 @@ type Props = {
|
||||
*/
|
||||
_embedMeetingVisible: boolean,
|
||||
|
||||
/**
|
||||
* The meeting invitation text.
|
||||
*/
|
||||
_invitationText: string,
|
||||
|
||||
/**
|
||||
* Whether or not invite contacts should be visible.
|
||||
*/
|
||||
@@ -57,14 +55,9 @@ type Props = {
|
||||
_liveStreamViewURL: string,
|
||||
|
||||
/**
|
||||
* The redux representation of the local participant.
|
||||
* The default phone number.
|
||||
*/
|
||||
_localParticipantName: ?string,
|
||||
|
||||
/**
|
||||
* The current location url of the conference.
|
||||
*/
|
||||
_locationUrl: Object,
|
||||
_phoneNumber: ?string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
@@ -83,17 +76,15 @@ type Props = {
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function AddPeopleDialog({
|
||||
_conferenceName,
|
||||
_dialIn,
|
||||
_embedMeetingVisible,
|
||||
_invitationText,
|
||||
_inviteContactsVisible,
|
||||
_inviteUrl,
|
||||
_liveStreamViewURL,
|
||||
_localParticipantName,
|
||||
_locationUrl,
|
||||
_phoneNumber,
|
||||
t,
|
||||
updateNumbers }: Props) {
|
||||
const [ phoneNumber, setPhoneNumber ] = useState(undefined);
|
||||
|
||||
/**
|
||||
* Updates the dial-in numbers.
|
||||
@@ -119,27 +110,6 @@ function AddPeopleDialog({
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Updates the phone number in the state once the dial-in numbers are fetched.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!phoneNumber && _dialIn && _dialIn.numbers) {
|
||||
setPhoneNumber(_getDefaultPhoneNumber(_dialIn.numbers));
|
||||
}
|
||||
}, [ _dialIn ]);
|
||||
|
||||
const invite = getInviteText({
|
||||
_conferenceName,
|
||||
_localParticipantName,
|
||||
_inviteUrl,
|
||||
_locationUrl,
|
||||
_dialIn,
|
||||
_liveStreamViewURL,
|
||||
phoneNumber,
|
||||
t
|
||||
});
|
||||
const inviteSubject = t('addPeople.inviteMoreMailSubject', {
|
||||
appName: interfaceConfig.APP_NAME
|
||||
});
|
||||
@@ -156,7 +126,7 @@ function AddPeopleDialog({
|
||||
<CopyMeetingLinkSection url = { _inviteUrl } />
|
||||
<InviteByEmailSection
|
||||
inviteSubject = { inviteSubject }
|
||||
inviteText = { invite } />
|
||||
inviteText = { _invitationText } />
|
||||
{ _embedMeetingVisible && <EmbedMeetingTrigger /> }
|
||||
<div className = 'invite-more-dialog separator' />
|
||||
{
|
||||
@@ -165,11 +135,7 @@ function AddPeopleDialog({
|
||||
}
|
||||
{
|
||||
_dialIn.numbers
|
||||
&& <DialInSection
|
||||
conferenceName = { _conferenceName }
|
||||
dialIn = { _dialIn }
|
||||
locationUrl = { _locationUrl }
|
||||
phoneNumber = { phoneNumber } />
|
||||
&& <DialInSection phoneNumber = { _phoneNumber } />
|
||||
}
|
||||
</div>
|
||||
</Dialog>
|
||||
@@ -181,29 +147,32 @@ function AddPeopleDialog({
|
||||
* {@code AddPeopleDialog} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const currentLiveStreamingSession
|
||||
= getActiveSession(state, JitsiRecordingConstants.mode.STREAM);
|
||||
const { iAmRecorder } = state['features/base/config'];
|
||||
const addPeopleEnabled = isAddPeopleEnabled(state);
|
||||
const dialOutEnabled = isDialOutEnabled(state);
|
||||
const hideInviteContacts = iAmRecorder || (!addPeopleEnabled && !dialOutEnabled);
|
||||
const dialIn = state['features/invite'];
|
||||
const phoneNumber = dialIn && dialIn.numbers ? _getDefaultPhoneNumber(dialIn.numbers) : undefined;
|
||||
|
||||
return {
|
||||
_conferenceName: getRoomName(state),
|
||||
_dialIn: state['features/invite'],
|
||||
_dialIn: dialIn,
|
||||
_embedMeetingVisible: !isVpaasMeeting(state),
|
||||
_invitationText: getInviteText({ state,
|
||||
phoneNumber,
|
||||
t: ownProps.t }),
|
||||
_inviteContactsVisible: interfaceConfig.ENABLE_DIAL_OUT && !hideInviteContacts,
|
||||
_inviteUrl: getInviteURL(state),
|
||||
_liveStreamViewURL:
|
||||
currentLiveStreamingSession
|
||||
&& currentLiveStreamingSession.liveStreamViewURL,
|
||||
_localParticipantName: localParticipant?.name,
|
||||
_locationUrl: state['features/base/connection'].locationURL
|
||||
_phoneNumber: phoneNumber
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,26 +3,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { getDialInfoPageURL } from '../../../functions';
|
||||
|
||||
import DialInNumber from './DialInNumber';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name of the current conference. Used as part of inviting users.
|
||||
*/
|
||||
conferenceName: string,
|
||||
|
||||
/**
|
||||
* The object representing the dialIn feature.
|
||||
*/
|
||||
dialIn: Object,
|
||||
_dialIn: Object,
|
||||
|
||||
/**
|
||||
* The current location url of the conference.
|
||||
* The url of the page containing the dial-in numbers list.
|
||||
*/
|
||||
locationUrl: Object,
|
||||
_dialInfoPageUrl: string,
|
||||
|
||||
/**
|
||||
* The phone number to dial to begin the process of dialing into a
|
||||
@@ -45,25 +41,19 @@ type Props = {
|
||||
* @returns {null|ReactElement}
|
||||
*/
|
||||
function DialInSection({
|
||||
conferenceName,
|
||||
dialIn,
|
||||
locationUrl,
|
||||
_dialIn,
|
||||
_dialInfoPageUrl,
|
||||
phoneNumber,
|
||||
t
|
||||
}: Props) {
|
||||
return (
|
||||
<div className = 'invite-more-dialog dial-in-display'>
|
||||
<DialInNumber
|
||||
conferenceID = { dialIn.conferenceID }
|
||||
conferenceID = { _dialIn.conferenceID }
|
||||
phoneNumber = { phoneNumber } />
|
||||
<a
|
||||
className = 'more-numbers'
|
||||
href = {
|
||||
getDialInfoPageURL(
|
||||
conferenceName,
|
||||
locationUrl
|
||||
)
|
||||
}
|
||||
href = { _dialInfoPageUrl }
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
{ t('info.moreNumbers') }
|
||||
@@ -72,4 +62,20 @@ function DialInSection({
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(DialInSection);
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code DialInLink} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_dialIn: state['features/invite'],
|
||||
_dialInfoPageUrl: getDialInfoPageURL(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(DialInSection));
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import { getActiveSession } from '../../features/recording/functions';
|
||||
import { getRoomName } from '../base/conference';
|
||||
import { getInviteURL } from '../base/connection';
|
||||
import { i18next } from '../base/i18n';
|
||||
import { isLocalParticipantModerator } from '../base/participants';
|
||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
|
||||
import { toState } from '../base/redux';
|
||||
import { doGetJSON, parseURIString } from '../base/util';
|
||||
import { isVpaasMeeting } from '../billing-counter/functions';
|
||||
@@ -239,43 +243,44 @@ export function getInviteResultsForQuery(
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getInviteText({
|
||||
_conferenceName,
|
||||
_localParticipantName,
|
||||
_inviteUrl,
|
||||
_locationUrl,
|
||||
_dialIn,
|
||||
_liveStreamViewURL,
|
||||
state,
|
||||
phoneNumber,
|
||||
t
|
||||
}: Object) {
|
||||
const inviteURL = _decodeRoomURI(_inviteUrl);
|
||||
const dialIn = state['features/invite'];
|
||||
const inviteUrl = getInviteURL(state);
|
||||
const currentLiveStreamingSession = getActiveSession(state, JitsiRecordingConstants.mode.STREAM);
|
||||
const liveStreamViewURL
|
||||
= currentLiveStreamingSession
|
||||
&& currentLiveStreamingSession.liveStreamViewURL;
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const localParticipantName = localParticipant?.name;
|
||||
|
||||
let invite = _localParticipantName
|
||||
? t('info.inviteURLFirstPartPersonal', { name: _localParticipantName })
|
||||
const inviteURL = _decodeRoomURI(inviteUrl);
|
||||
|
||||
let invite = localParticipantName
|
||||
? t('info.inviteURLFirstPartPersonal', { name: localParticipantName })
|
||||
: t('info.inviteURLFirstPartGeneral');
|
||||
|
||||
invite += t('info.inviteURLSecondPart', {
|
||||
url: inviteURL
|
||||
});
|
||||
|
||||
if (_liveStreamViewURL) {
|
||||
if (liveStreamViewURL) {
|
||||
const liveStream = t('info.inviteLiveStream', {
|
||||
url: _liveStreamViewURL
|
||||
url: liveStreamViewURL
|
||||
});
|
||||
|
||||
invite = `${invite}\n${liveStream}`;
|
||||
}
|
||||
|
||||
if (shouldDisplayDialIn(_dialIn)) {
|
||||
if (shouldDisplayDialIn(dialIn)) {
|
||||
const dial = t('info.invitePhone', {
|
||||
number: phoneNumber,
|
||||
conferenceID: _dialIn.conferenceID
|
||||
conferenceID: dialIn.conferenceID
|
||||
});
|
||||
const moreNumbers = t('info.invitePhoneAlternatives', {
|
||||
url: getDialInfoPageURL(
|
||||
_conferenceName,
|
||||
_locationUrl
|
||||
),
|
||||
url: getDialInfoPageURL(state),
|
||||
silentUrl: `${inviteURL}#config.startSilent=true`
|
||||
});
|
||||
|
||||
@@ -514,9 +519,7 @@ export function getShareInfoText(
|
||||
.catch(error =>
|
||||
logger.error('Error fetching numbers or conferenceID', error))
|
||||
.then(defaultDialInNumber => {
|
||||
let dialInfoPageUrl = getDialInfoPageURL(
|
||||
room,
|
||||
state['features/base/connection'].locationURL);
|
||||
let dialInfoPageUrl = getDialInfoPageURL(state);
|
||||
|
||||
if (useHtml) {
|
||||
dialInfoPageUrl
|
||||
@@ -537,28 +540,19 @@ export function getShareInfoText(
|
||||
/**
|
||||
* Generates the URL for the static dial in info page.
|
||||
*
|
||||
* @param {string} conferenceName - The conference name.
|
||||
* @param {Object} locationURL - The current location URL, the object coming
|
||||
* from state ['features/base/connection'].locationURL.
|
||||
* @param {Object} state - The state from the Redux store.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDialInfoPageURL(
|
||||
conferenceName: string,
|
||||
locationURL: Object) {
|
||||
const origin = locationURL.origin;
|
||||
const pathParts = locationURL.pathname.split('/');
|
||||
export function getDialInfoPageURL(state: Object) {
|
||||
const { didPageUrl } = state['features/dynamic-branding'];
|
||||
const conferenceName = getRoomName(state);
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { href } = locationURL;
|
||||
const room = _decodeRoomURI(conferenceName);
|
||||
|
||||
pathParts.length = pathParts.length - 1;
|
||||
const url = didPageUrl || `${href.substring(0, href.lastIndexOf('/'))}/static/dialInInfo.html`;
|
||||
|
||||
const newPath = pathParts.reduce((accumulator, currentValue) => {
|
||||
if (currentValue) {
|
||||
return `${accumulator}/${currentValue}`;
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
}, '');
|
||||
|
||||
return `${origin}${newPath}/static/dialInInfo.html?room=${_decodeRoomURI(conferenceName)}`;
|
||||
return `${url}?room=${room}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/pa
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import { isTestModeEnabled } from '../base/testing';
|
||||
import { NOTIFICATION_TYPE, showNotification } from '../notifications';
|
||||
import { isPrejoinPageEnabled } from '../prejoin/functions';
|
||||
import { shouldAutoKnock } from '../prejoin/functions';
|
||||
|
||||
import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED } from './actionTypes';
|
||||
import {
|
||||
@@ -100,8 +100,7 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
|
||||
|
||||
dispatch(openLobbyScreen());
|
||||
|
||||
if (isPrejoinPageEnabled(state) && !state['features/lobby'].knocking) {
|
||||
// prejoin is enabled, so we knock automatically
|
||||
if (shouldAutoKnock(state)) {
|
||||
dispatch(startKnocking());
|
||||
}
|
||||
|
||||
|
||||
@@ -11,22 +11,16 @@ import { getDialInfoPageURL, shouldDisplayDialIn } from '../../invite';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name of the current conference.
|
||||
*/
|
||||
_room: string,
|
||||
|
||||
/**
|
||||
* The current location url of the conference.
|
||||
*/
|
||||
_locationURL: string,
|
||||
|
||||
|
||||
/**
|
||||
* The redux state representing the dial-in numbers feature.
|
||||
*/
|
||||
_dialIn: Object,
|
||||
|
||||
/**
|
||||
* The url of the page containing the dial-in numbers list.
|
||||
*/
|
||||
_dialInfoPageUrl: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
@@ -47,7 +41,7 @@ class DialInLink extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _room, _locationURL, _dialIn, t } = this.props;
|
||||
const { _dialIn, _dialInfoPageUrl, t } = this.props;
|
||||
|
||||
if (!shouldDisplayDialIn(_dialIn)) {
|
||||
return null;
|
||||
@@ -56,12 +50,7 @@ class DialInLink extends Component<Props> {
|
||||
return (
|
||||
<div>{t('toolbar.noAudioSignalDialInDesc')}
|
||||
<a
|
||||
href = {
|
||||
getDialInfoPageURL(
|
||||
_room,
|
||||
_locationURL
|
||||
)
|
||||
}
|
||||
href = { _dialInfoPageUrl }
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
{t('toolbar.noAudioSignalDialInLinkDesc')}
|
||||
@@ -77,18 +66,12 @@ class DialInLink extends Component<Props> {
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _room: string,
|
||||
* _locationURL: string,
|
||||
* _dialIn: Object,
|
||||
* }}
|
||||
*/
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
|
||||
return {
|
||||
_room: state['features/base/conference'].room,
|
||||
_locationURL: state['features/base/connection'].locationURL,
|
||||
_dialIn: state['features/invite']
|
||||
_dialIn: state['features/invite'],
|
||||
_dialInfoPageUrl: getDialInfoPageURL(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,17 @@
|
||||
export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
|
||||
= 'MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that the overlay for slow gUM is visible or not.
|
||||
*
|
||||
* {
|
||||
* type: TOGGLE_SLOW_GUM_OVERLAY,
|
||||
* isVisible: {boolean},
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const TOGGLE_SLOW_GUM_OVERLAY = 'TOGGLE_SLOW_GUM_OVERLAY';
|
||||
|
||||
/**
|
||||
* Adjust the state of the fatal error which shows/hides the reload screen. See
|
||||
* action methods's description for more info about each of the fields.
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import {
|
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
SET_FATAL_ERROR
|
||||
SET_FATAL_ERROR,
|
||||
TOGGLE_SLOW_GUM_OVERLAY
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -26,6 +27,24 @@ export function mediaPermissionPromptVisibilityChanged(isVisible: boolean, brows
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: SLOW_GET_USER_MEDIA_OVERLAY,
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function toggleSlowGUMOverlay(isVisible: boolean) {
|
||||
return {
|
||||
type: TOGGLE_SLOW_GUM_OVERLAY,
|
||||
isVisible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The action indicates that an unrecoverable error has occurred and the reload
|
||||
* screen will be displayed or hidden.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractSlowGUMOverlay}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The function to translate human-readable text.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} for slow gUM overlay. Shown when
|
||||
* a slow gUM promise resolution is detected
|
||||
*/
|
||||
export default class AbstractSlowGUMOverlay extends Component<Props> {
|
||||
/**
|
||||
* Determines whether this overlay needs to be rendered (according to a
|
||||
* specific redux state). Called by {@link OverlayContainer}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} - If this overlay needs to be rendered, {@code true};
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
static needsRender(state: Object) {
|
||||
return state['features/overlay'].isSlowGUMOverlayVisible;
|
||||
}
|
||||
}
|
||||
36
react/features/overlay/components/web/SlowGUMOverlay.js
Normal file
36
react/features/overlay/components/web/SlowGUMOverlay.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// @flow
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import AbstractSlowGUMOverlay from './AbstractSlowGUMOverlay';
|
||||
import OverlayFrame from './OverlayFrame';
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} for slow gUM overlay. Shown when
|
||||
* a slow gUM promise resolution is detected
|
||||
*/
|
||||
class SlowGUMOverlay extends AbstractSlowGUMOverlay {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
// const { t } = this.props;
|
||||
|
||||
return (
|
||||
<OverlayFrame>
|
||||
<div className = { 'overlay__spinner-container' }>
|
||||
<Spinner
|
||||
invertColor = { true }
|
||||
size = { 'large' } />
|
||||
</div>
|
||||
</OverlayFrame>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(SlowGUMOverlay);
|
||||
@@ -5,3 +5,4 @@ export { default as OverlayFrame } from './OverlayFrame';
|
||||
export { default as PageReloadOverlay } from './PageReloadOverlay';
|
||||
export { default as SuspendedOverlay } from './SuspendedOverlay';
|
||||
export { default as UserMediaPermissionsOverlay } from './UserMediaPermissionsOverlay';
|
||||
export { default as SlowGUMOverlay } from './SlowGUMOverlay';
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import {
|
||||
PageReloadOverlay,
|
||||
SlowGUMOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
} from './components/web';
|
||||
@@ -17,6 +18,7 @@ export function getOverlays(): Array<Object> {
|
||||
return [
|
||||
PageReloadOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
UserMediaPermissionsOverlay,
|
||||
SlowGUMOverlay
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import { assign, ReducerRegistry, set } from '../base/redux';
|
||||
|
||||
import {
|
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
SET_FATAL_ERROR
|
||||
SET_FATAL_ERROR,
|
||||
TOGGLE_SLOW_GUM_OVERLAY
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -28,6 +29,8 @@ ReducerRegistry.register('features/overlay', (state = { }, action) => {
|
||||
case SET_FATAL_ERROR:
|
||||
return _setFatalError(state, action);
|
||||
|
||||
case TOGGLE_SLOW_GUM_OVERLAY:
|
||||
return _toggleSlowGUMOverlay(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -52,6 +55,24 @@ function _mediaPermissionPromptVisibilityChanged(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific redux action TOGGLE_SLOW_GUM_OVERLAY of
|
||||
* the feature overlay.
|
||||
*
|
||||
* @param {Object} state - The redux state of the feature overlay.
|
||||
* @param {Action} action - The redux action to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature overlay after the reduction of
|
||||
* the specified action.
|
||||
*/
|
||||
function _toggleSlowGUMOverlay(
|
||||
state,
|
||||
{ isVisible }) {
|
||||
return assign(state, {
|
||||
isSlowGUMOverlayVisible: isVisible
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code LoadConfigOverlay} overlay visible or not.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,6 @@ declare var JitsiMeetJS: Object;
|
||||
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { getRoomName } from '../base/conference';
|
||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
||||
import { createLocalTrack } from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
@@ -262,10 +261,7 @@ export function makePrecallTest(conferenceOptions: Object) {
|
||||
*/
|
||||
export function openDialInPage() {
|
||||
return function(dispatch: Function, getState: Function) {
|
||||
const state = getState();
|
||||
const locationURL = state['features/base/connection'].locationURL;
|
||||
const roomName = getRoomName(state);
|
||||
const dialInPage = getDialInfoPageURL(roomName, locationURL);
|
||||
const dialInPage = getDialInfoPageURL(getState());
|
||||
|
||||
openURLInBrowser(dialInPage, true);
|
||||
};
|
||||
|
||||
@@ -162,3 +162,16 @@ export function isPrejoinPageEnabled(state: Object): boolean {
|
||||
export function isPrejoinPageVisible(state: Object): boolean {
|
||||
return isPrejoinPageEnabled(state) && state['features/prejoin']?.showPrejoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we should auto-knock in case lobby is enabled for the room.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldAutoKnock(state: Object): boolean {
|
||||
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
|
||||
return (isPrejoinPageEnabled(state) || (iAmRecorder && iAmSipGateway))
|
||||
&& !state['features/lobby'].knocking;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import createTFLiteModule from './vendor/tflite/tflite';
|
||||
import createTFLiteSIMDModule from './vendor/tflite/tflite-simd';
|
||||
|
||||
const models = {
|
||||
'96': '/libs/segm_lite_v681.tflite',
|
||||
'144': '/libs/segm_full_v679.tflite'
|
||||
'96': 'libs/segm_lite_v681.tflite',
|
||||
'144': 'libs/segm_full_v679.tflite'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -34,6 +34,10 @@ export async function createBlurEffect() {
|
||||
models['144']
|
||||
);
|
||||
|
||||
if (!modelResponse.ok) {
|
||||
throw new Error('Failed to download tflite model!');
|
||||
}
|
||||
|
||||
const model = await modelResponse.arrayBuffer();
|
||||
|
||||
tflite.HEAPU8.set(new Uint8Array(model), modelBufferOffset);
|
||||
|
||||
@@ -216,7 +216,7 @@ function onConnectionSuccess() {
|
||||
devices.push('video');
|
||||
}
|
||||
|
||||
if (autoCreateLocalAudio) {
|
||||
if (localAudio || autoCreateLocalAudio) {
|
||||
devices.push('audio');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user