Compare commits

..

25 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
d2521bc67a feat(android) bump minimum API level to 24
Some of our dependencies (most notably WebRTC) have dropped it and we
can no longer claim to support API level 23).
2023-05-01 11:06:44 +02:00
damencho
38a293f8f6 fix(chat): Fixes long display name that overflow the message bubble. 2023-04-28 13:31:14 -05:00
damencho
13e8f992b5 fix(chat): White square when both scrolls are visible. 2023-04-28 13:31:14 -05:00
damencho
c384d0d3a9 Revert "fix(chat) Make the name fit the chat bubble"
This reverts commit e56c7070c2.
2023-04-28 13:31:14 -05:00
Дамян Минков
2b71fa512b feat: Adds conference duration to the amplitude stat. (#13294) 2023-04-28 11:09:13 -05:00
damencho
d381ceb040 feat: Shows html in notification warning. 2023-04-27 12:42:28 -05:00
damencho
e18c428f52 fix: Hide virtual background for unsupported browsers from vide preview. 2023-04-27 12:42:12 -05:00
Gabriel Borlea
9fc32dc59b fix: multiselect for invite people (#13287)
* fix: multiselect duplicates

* set multiselect height to 200px
2023-04-27 10:14:16 -05:00
robertpin
6f45622ef1 fix(dialog) Fix close animation moves whole body 2023-04-27 10:06:17 -05:00
robertpin
e56c7070c2 fix(chat) Make the name fit the chat bubble 2023-04-27 09:40:27 -05:00
damencho
0aef7a36aa fix: Fixes unresolved function. 2023-04-27 09:25:07 -05:00
damencho
c030cf941e feat: Implements amplitude events for messages count. 2023-04-27 09:24:59 -05:00
Robert Pintilii
f6760e4ac7 fix(chat) Focus input on chat open (#13285) 2023-04-27 17:23:47 +03:00
Robert Pintilii
9060c77307 ref(TS) Convert some native components to TS (#13266) 2023-04-27 08:44:20 +03:00
Дамян Минков
a1d018eef4 feat: Disables sending localstats in visitor mode. (#13279) 2023-04-27 07:38:57 +02:00
Jaya Allamsetty
3f724d8fb7 fix(visitors) Do not add tracks in redux to conference. 2023-04-26 16:51:19 -04:00
Jaya Allamsetty
49d69a5a02 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1623.0.0+c520877a...v1624.0.0+e3a8472f
2023-04-26 13:04:01 -04:00
damencho
6db9e42876 fix: Allows jicofo entering rooms without requiring a password.
The case where the main room is locked and everyone leaves it to a breakout room and then coming back allows jicofo entering without a password.
2023-04-26 09:04:55 -05:00
Jaya Allamsetty
ad3e8f9f53 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1620.0.0+7f0012f7...v1623.0.0+c520877a
2023-04-26 09:26:34 -04:00
Mihaela Dumitru
9f39caa247 feat(external-api) support assumed bandwidth bps config and command (#13164) 2023-04-26 15:32:53 +03:00
Avram Tudor
1402a63324 ref(keyboard-shortcuts) refactor keyboard shortcuts to use redux (#13260)
* ref(keyboard-shortcuts) refactor keyboard shortcuts to use redux

fix unsynced default value between config flag and local storage

* code review

* fix build
2023-04-26 11:21:42 +03:00
robertpin
a9863e65c3 fix(dial-in) Make text selectable on Dial In page 2023-04-25 19:41:27 -05:00
Robert Pintilii
646c58f7d1 ref(TS) Convert some native components to TS (#13264) 2023-04-25 13:50:52 +03:00
Дамян Минков
a78ea7ca9c fix: Updates the option for disabling iframe to show a warning. (#13263)
* fix: Updates the option for disabling iframe to show a warning.

It will give a timeout of 5 mins for the conference, before navigating away from it.

* squash: Fix lint error.

* squash: Fix mobile build.
2023-04-24 14:59:25 -05:00
Robert Pintilii
8b8565bf60 ref(TS) Convert some native components to TS (#13259) 2023-04-24 20:14:02 +03:00
179 changed files with 1752 additions and 1562 deletions

View File

@@ -19,7 +19,7 @@ buildscript {
ext {
buildToolsVersion = "31.0.0"
compileSdkVersion = 32
minSdkVersion = 23
minSdkVersion = 24
targetSdkVersion = 32
supportLibVersion = "28.0.0"

View File

@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=23.0.0
sdkVersion=8.0.0
appVersion=99.0.0
sdkVersion=99.0.0

2
app.js
View File

@@ -18,7 +18,6 @@ import './react/features/base/jitsi-local-storage/setup';
import conference from './conference';
import API from './modules/API';
import UI from './modules/UI/UI';
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
import translation from './modules/translation/translation';
// Initialize Olm as early as possible.
@@ -38,7 +37,6 @@ window.APP = {
'index.loaded': window.indexLoadedTime
},
keyboardshortcut,
translation,
UI
};

View File

@@ -140,6 +140,7 @@ import { downloadJSON } from './react/features/base/util/downloadJSON';
import { showDesktopPicker } from './react/features/desktop-picker/actions';
import { appendSuffix } from './react/features/display-name/functions';
import { maybeOpenFeedbackDialog, submitFeedback } from './react/features/feedback/actions';
import { initKeyboardShortcuts } from './react/features/keyboard-shortcuts/actions';
import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
import { hideNotification, showNotification, showWarningNotification } from './react/features/notifications/actions';
@@ -2308,10 +2309,7 @@ export default {
APP.UI.initConference();
if (!config.disableShortcuts) {
APP.keyboardshortcut.init();
}
dispatch(initKeyboardShortcuts());
dispatch(conferenceJoined(room));
const jwt = APP.store.getState()['features/base/jwt'];

View File

@@ -1406,6 +1406,7 @@ var config = {
disableAGC
disableAP
disableHPF
disableLocalStats
disableNS
enableTalkWhileMuted
forceJVB121Ratio

View File

@@ -74,6 +74,10 @@
a:active {
color: black;
}
&::-webkit-scrollbar-corner {
background: #3a3a3a;
}
}

View File

@@ -108,6 +108,10 @@
#largeVideoContainer {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
margin: 0 !important;
}
#largeVideoWrapper {

View File

@@ -57,6 +57,10 @@
line-height: 24px;
border-collapse: collapse;
* {
user-select: text;
}
thead {
text-align: left;
}

View File

@@ -84,8 +84,12 @@ Component "conference.jitmeet.example.com" "muc"
"polls";
--"token_verification";
"muc_rate_limit";
"muc_password_whitelist";
}
admins = { "focusUser@auth.jitmeet.example.com" }
muc_password_whitelist = {
"focusUser@auth.jitmeet.example.com"
}
muc_room_locking = false
muc_room_default_public_jids = true

6
globals.d.ts vendored
View File

@@ -10,12 +10,6 @@ declare global {
API: any;
conference: any;
debugLogs: any;
keyboardshortcut: {
registerShortcut: Function;
unregisterShortcut: Function;
openDialog: Function;
enable: Function;
}
};
const interfaceConfig: any;

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>23.0.0</string>
<string>99.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>23.0.0</string>
<string>99.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>23.0.0</string>
<string>99.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>23.0.0</string>
<string>99.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>8.0.0</string>
<string>99.0.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>8.0.0</string>
<string>99.0.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -675,6 +675,7 @@
"connectedTwoMembers": "{{first}} and {{second}} joined the meeting",
"dataChannelClosed": "Video quality impaired",
"dataChannelClosedDescription": "The bridge channel has been disconnected and thus video quality is limited to its lowest setting.",
"disabledIframe": "Embedding is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes.",
"disconnected": "disconnected",
"displayNotifications": "Display notifications for",
"dontRemindMe": "Do not remind me",

View File

@@ -115,7 +115,11 @@ import { muteAllParticipants } from '../../react/features/video-menu/actions';
import { setVideoQuality } from '../../react/features/video-quality/actions';
import { getJitsiMeetTransport } from '../transport';
import { API_ID, ENDPOINT_TEXT_MESSAGE_NAME } from './constants';
import {
API_ID,
ASSUMED_BANDWIDTH_BPS,
ENDPOINT_TEXT_MESSAGE_NAME
} from './constants';
const logger = Logger.getLogger(__filename);
@@ -310,6 +314,23 @@ function initCommands() {
APP.store.dispatch(sendTones(tones, duration, pause));
},
'set-assumed-bandwidth-bps': value => {
logger.debug('Set assumed bandwidth bps command received', value);
if (typeof value !== 'number' || isNaN(value)) {
logger.error('Assumed bandwidth bps must be a number.');
return;
}
const { conference } = APP.store.getState()['features/base/conference'];
if (conference) {
conference.setAssumedBandwidthBps(value < ASSUMED_BANDWIDTH_BPS
? ASSUMED_BANDWIDTH_BPS
: value);
}
},
'set-follow-me': value => {
logger.debug('Set follow me command received');

View File

@@ -15,3 +15,10 @@ export const API_ID = parseURLParams(window.location).jitsi_meet_external_api_id
* The payload name for the datachannel/endpoint text message event.
*/
export const ENDPOINT_TEXT_MESSAGE_NAME = 'endpoint-text-message';
/**
* The min value that can be set for the assumed bandwidth.
* Setting it to this value means not assuming any bandwidth,
* but rather allowing the estimations to take place.
*/
export const ASSUMED_BANDWIDTH_BPS = -1;

View File

@@ -59,6 +59,7 @@ const commands = {
sendEndpointTextMessage: 'send-endpoint-text-message',
sendParticipantToRoom: 'send-participant-to-room',
sendTones: 'send-tones',
setAssumedBandwidthBps: 'set-assumed-bandwidth-bps',
setFollowMe: 'set-follow-me',
setLargeVideoParticipant: 'set-large-video-participant',
setMediaEncryptionKey: 'set-media-encryption-key',

View File

@@ -11,11 +11,11 @@ import { setColorAlpha } from '../../react/features/base/util/helpers';
import { setDocumentUrl } from '../../react/features/etherpad/actions';
import { setFilmstripVisible } from '../../react/features/filmstrip/actions.any';
import {
joinLeaveNotificationsDisabled,
setNotificationsEnabled,
showNotification
} from '../../react/features/notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../react/features/notifications/constants';
import { joinLeaveNotificationsDisabled } from '../../react/features/notifications/functions';
import {
dockToolbox,
setToolboxEnabled,

View File

@@ -1,261 +0,0 @@
/* global APP */
import { jitsiLocalStorage } from '@jitsi/js-utils';
import Logger from '@jitsi/logger';
import {
ACTION_SHORTCUT_PRESSED as PRESSED,
ACTION_SHORTCUT_RELEASED as RELEASED,
createShortcutEvent
} from '../../react/features/analytics/AnalyticsEvents';
import { sendAnalytics } from '../../react/features/analytics/functions';
import { clickOnVideo } from '../../react/features/filmstrip/actions';
import { openSettingsDialog } from '../../react/features/settings/actions';
import { SETTINGS_TABS } from '../../react/features/settings/constants';
const logger = Logger.getLogger(__filename);
/**
* Map of shortcuts. When a shortcut is registered it enters the mapping.
* @type {Map}
*/
const _shortcuts = new Map();
/**
* Map of registered keyboard keys and translation keys describing the
* action performed by the key.
* @type {Map}
*/
const _shortcutsHelp = new Map();
/**
* The key used to save in local storage if keyboard shortcuts are enabled.
*/
const _enableShortcutsKey = 'enableShortcuts';
/**
* Prefer keyboard handling of these elements over global shortcuts.
* If a button is triggered using the Spacebar it should not trigger PTT.
* If an input element is focused and M is pressed it should not mute audio.
*/
const _elementsBlacklist = [
'input',
'textarea',
'button',
'[role=button]',
'[role=menuitem]',
'[role=radio]',
'[role=tab]',
'[role=option]',
'[role=switch]',
'[role=range]',
'[role=log]'
];
/**
* An element selector for elements that have their own keyboard handling.
*/
const _focusedElementsSelector = `:focus:is(${_elementsBlacklist.join(',')})`;
/**
* Maps keycode to character, id of popover for given function and function.
*/
const KeyboardShortcut = {
init() {
this._initGlobalShortcuts();
window.onkeyup = e => {
if (!this.getEnabled()) {
return;
}
const key = this._getKeyboardKey(e).toUpperCase();
const num = parseInt(key, 10);
if (!document.querySelector(_focusedElementsSelector)) {
if (_shortcuts.has(key)) {
_shortcuts.get(key).function(e);
} else if (!isNaN(num) && num >= 0 && num <= 9) {
APP.store.dispatch(clickOnVideo(num));
}
}
};
window.onkeydown = e => {
if (!this.getEnabled()) {
return;
}
const focusedElement = document.querySelector(_focusedElementsSelector);
if (!focusedElement) {
if (this._getKeyboardKey(e).toUpperCase() === ' ') {
if (APP.conference.isLocalAudioMuted()) {
sendAnalytics(createShortcutEvent(
'push.to.talk',
PRESSED));
logger.log('Talk shortcut pressed');
APP.conference.muteAudio(false);
}
}
} else if (this._getKeyboardKey(e).toUpperCase() === 'ESCAPE') {
// Allow to remove focus from selected elements using ESC key.
if (focusedElement && focusedElement.blur) {
focusedElement.blur();
}
}
};
},
/**
* Enables/Disables the keyboard shortcuts.
* @param {boolean} value - the new value.
*/
enable(value) {
jitsiLocalStorage.setItem(_enableShortcutsKey, value);
},
getEnabled() {
// Should be enabled if not explicitly set to false
// eslint-disable-next-line no-unneeded-ternary
return jitsiLocalStorage.getItem(_enableShortcutsKey) === 'false' ? false : true;
},
getShortcutsDescriptions() {
return _shortcutsHelp;
},
/**
* Opens the {@SettingsDialog} dialog on the Shortcuts page.
*
* @returns {void}
*/
openDialog() {
APP.store.dispatch(openSettingsDialog(SETTINGS_TABS.SHORTCUTS, false));
},
/**
* Registers a new shortcut.
*
* @param shortcutChar the shortcut character triggering the action
* @param shortcutAttr the "shortcut" html element attribute mapping an
* element to this shortcut and used to show the shortcut character on the
* element tooltip
* @param exec the function to be executed when the shortcut is pressed
* @param helpDescription the description of the shortcut that would appear
* in the help menu
* @param altKey whether or not the alt key must be pressed.
*/
registerShortcut(// eslint-disable-line max-params
shortcutChar,
shortcutAttr,
exec,
helpDescription,
altKey = false) {
_shortcuts.set(altKey ? `:${shortcutChar}` : shortcutChar, {
character: shortcutChar,
function: exec,
shortcutAttr,
altKey
});
if (helpDescription) {
this._addShortcutToHelp(altKey ? `:${shortcutChar}` : shortcutChar, helpDescription);
}
},
/**
* Unregisters a shortcut.
*
* @param shortcutChar unregisters the given shortcut, which means it will
* no longer be usable
* @param altKey whether or not shortcut is combo with alt key
*/
unregisterShortcut(shortcutChar, altKey = false) {
_shortcuts.delete(altKey ? `:${shortcutChar}` : shortcutChar);
_shortcutsHelp.delete(shortcutChar);
},
/**
* @param e a KeyboardEvent
* @returns {string} e.key or something close if not supported
*/
_getKeyboardKey(e) {
// If alt is pressed a different char can be returned so this takes
// the char from the code. It also prefixes with a colon to differentiate
// alt combo from simple keypress.
if (e.altKey) {
const key = e.code.replace('Key', '');
return `:${key}`;
}
// If e.key is a string, then it is assumed it already plainly states
// the key pressed. This may not be true in all cases, such as with Edge
// and "?", when the browser cannot properly map a key press event to a
// keyboard key. To be safe, when a key is "Unidentified" it must be
// further analyzed by jitsi to a key using e.which.
if (typeof e.key === 'string' && e.key !== 'Unidentified') {
return e.key;
}
if (e.type === 'keypress'
&& ((e.which >= 32 && e.which <= 126)
|| (e.which >= 160 && e.which <= 255))) {
return String.fromCharCode(e.which);
}
// try to fallback (0-9A-Za-z and QWERTY keyboard)
switch (e.which) {
case 27:
return 'Escape';
case 191:
return e.shiftKey ? '?' : '/';
}
if (e.shiftKey || e.type === 'keypress') {
return String.fromCharCode(e.which);
}
return String.fromCharCode(e.which).toLowerCase();
},
/**
* Adds the given shortcut to the help dialog.
*
* @param shortcutChar the shortcut character
* @param shortcutDescriptionKey the description of the shortcut
* @private
*/
_addShortcutToHelp(shortcutChar, shortcutDescriptionKey) {
_shortcutsHelp.set(shortcutChar, shortcutDescriptionKey);
},
/**
* Initialise global shortcuts.
* Global shortcuts are shortcuts for features that don't have a button or
* link associated with the action. In other words they represent actions
* triggered _only_ with a shortcut.
*/
_initGlobalShortcuts() {
this.registerShortcut('?', null, () => {
sendAnalytics(createShortcutEvent('help'));
this.openDialog();
}, 'keyboardShortcuts.toggleShortcuts');
// register SPACE shortcut in two steps to insure visibility of help
// message
this.registerShortcut(' ', null, () => {
sendAnalytics(createShortcutEvent('push.to.talk', RELEASED));
logger.log('Talk shortcut released');
APP.conference.muteAudio(true);
});
this._addShortcutToHelp('SPACE', 'keyboardShortcuts.pushToTalk');
/**
* FIXME: Currently focus keys are directly implemented below in
* onkeyup. They should be moved to the SmallVideo instead.
*/
this._addShortcutToHelp('0', 'keyboardShortcuts.focusLocal');
this._addShortcutToHelp('1-9', 'keyboardShortcuts.focusRemote');
}
};
export default KeyboardShortcut;

45
package-lock.json generated
View File

@@ -60,7 +60,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1620.0.0+7f0012f7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -12739,8 +12739,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1620.0.0+7f0012f7/lib-jitsi-meet.tgz",
"integrity": "sha512-F3vOUwD/ys5dGtswR3C5IK8JPDOG+71J+nhkEDLotkni/Zws8KHzf8Ye332UuetUjCTGJP3CbRFe9yJdYERe7g==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"integrity": "sha512-0LK5EMCvtsAEQkjkrWzyniTl8BKtshAZxI3e9hGnA/RVDuULLXre7GEWd2zCP3tCfdxclkhqt7skQpfNhCTdqg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -12758,7 +12758,7 @@
"patch-package": "6.5.1",
"promise.allsettled": "1.0.4",
"sdp-transform": "2.3.0",
"strophe.js": "1.6.0",
"strophe.js": "1.5.0",
"strophejs-plugin-disco": "0.0.2",
"strophejs-plugin-stream-management": "git+https://github.com/jitsi/strophejs-plugin-stream-management#679be5902097ed612fb5062b5549f3f32b6f5f47",
"uuid": "8.1.0",
@@ -17615,18 +17615,27 @@
}
},
"node_modules/strophe.js": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.6.0.tgz",
"integrity": "sha512-LE2B6nEJNUbF2Cl/p1tLIsXVJ9l86B/Z12HYYiO3n92VwYkhJ/5vJ+1ZMdwP9eN9GP8a3nbqfS5zE9umcK0FdA==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.5.0.tgz",
"integrity": "sha512-H5tE/tZxPR5xP3jhXyQwsjnMSwQMf7vrn9r1OkufTApyGHYe8WjzhsfxtL3AFhVu7vFjXPPZBrmUOTm1ccYgOA==",
"dependencies": {
"abab": "^2.0.3",
"karma-rollup-preprocessor": "^7.0.8"
},
"optionalDependencies": {
"@xmldom/xmldom": "0.8.3",
"@xmldom/xmldom": "0.8.2",
"ws": "^8.5.0"
}
},
"node_modules/strophe.js/node_modules/@xmldom/xmldom": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.2.tgz",
"integrity": "sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ==",
"optional": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/strophe.js/node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
@@ -29108,8 +29117,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1620.0.0+7f0012f7/lib-jitsi-meet.tgz",
"integrity": "sha512-F3vOUwD/ys5dGtswR3C5IK8JPDOG+71J+nhkEDLotkni/Zws8KHzf8Ye332UuetUjCTGJP3CbRFe9yJdYERe7g==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"integrity": "sha512-0LK5EMCvtsAEQkjkrWzyniTl8BKtshAZxI3e9hGnA/RVDuULLXre7GEWd2zCP3tCfdxclkhqt7skQpfNhCTdqg==",
"requires": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
@@ -29125,7 +29134,7 @@
"patch-package": "6.5.1",
"promise.allsettled": "1.0.4",
"sdp-transform": "2.3.0",
"strophe.js": "1.6.0",
"strophe.js": "1.5.0",
"strophejs-plugin-disco": "0.0.2",
"strophejs-plugin-stream-management": "git+https://github.com/jitsi/strophejs-plugin-stream-management#679be5902097ed612fb5062b5549f3f32b6f5f47",
"uuid": "8.1.0",
@@ -32836,16 +32845,22 @@
"dev": true
},
"strophe.js": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.6.0.tgz",
"integrity": "sha512-LE2B6nEJNUbF2Cl/p1tLIsXVJ9l86B/Z12HYYiO3n92VwYkhJ/5vJ+1ZMdwP9eN9GP8a3nbqfS5zE9umcK0FdA==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.5.0.tgz",
"integrity": "sha512-H5tE/tZxPR5xP3jhXyQwsjnMSwQMf7vrn9r1OkufTApyGHYe8WjzhsfxtL3AFhVu7vFjXPPZBrmUOTm1ccYgOA==",
"requires": {
"@xmldom/xmldom": "0.8.7",
"@xmldom/xmldom": "0.8.2",
"abab": "^2.0.3",
"karma-rollup-preprocessor": "^7.0.8",
"ws": "^8.5.0"
},
"dependencies": {
"@xmldom/xmldom": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.2.tgz",
"integrity": "sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ==",
"optional": true
},
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",

View File

@@ -65,7 +65,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1620.0.0+7f0012f7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -180,7 +180,7 @@
},
"overrides": {
"strophe.js@1.6.0": {
"@xmldom/xmldom": "0.8.7"
"@xmldom/xmldom": "0.8.7"
}
},
"engines": {

View File

@@ -129,14 +129,6 @@ export function appNavigate(uri?: string) {
dispatch(setLocationURL(locationURL));
dispatch(setConfig(config));
if (inIframe() && getState()['features/base/config'].disableIframeAPI) {
// in case iframeAPI is disabled redirect to the promotional page
dispatch(redirectToStaticPage('static/close3.html', `#jitsi_meet_external_api_id=${API_ID}`));
return;
}
dispatch(setRoom(room));
};
}

View File

@@ -5,6 +5,7 @@ import '../base/media/middleware';
import '../dynamic-branding/middleware';
import '../e2ee/middleware';
import '../external-api/middleware';
import '../keyboard-shortcuts/middleware';
import '../no-audio-signal/middleware';
import '../notifications/middleware';
import '../noise-detection/middleware';

View File

@@ -3,6 +3,7 @@ import '../base/tooltip/reducer';
import '../e2ee/reducer';
import '../face-landmarks/reducer';
import '../feedback/reducer';
import '../keyboard-shortcuts/reducer';
import '../no-audio-signal/reducer';
import '../noise-detection/reducer';
import '../participants-pane/reducer';

View File

@@ -43,6 +43,7 @@ import { IGifsState } from '../gifs/reducer';
import { IGoogleApiState } from '../google-api/reducer';
import { IInviteState } from '../invite/reducer';
import { IJaaSState } from '../jaas/reducer';
import { IKeyboardShortcutsState } from '../keyboard-shortcuts/types';
import { ILargeVideoState } from '../large-video/reducer';
import { ILobbyState } from '../lobby/reducer';
import { IMobileAudioModeState } from '../mobile/audio-mode/reducer';
@@ -133,6 +134,7 @@ export interface IReduxState {
'features/google-api': IGoogleApiState;
'features/invite': IInviteState;
'features/jaas': IJaaSState;
'features/keyboard-shortcuts': IKeyboardShortcutsState;
'features/large-video': ILargeVideoState;
'features/lobby': ILobbyState;
'features/mobile/audio-mode': IMobileAudioModeState;

View File

@@ -3,6 +3,7 @@ import { sendAnalytics } from '../../analytics/functions';
import { appNavigate } from '../../app/actions';
import { IReduxState, IStore } from '../../app/types';
import { endpointMessageReceived } from '../../subtitles/actions.any';
import { iAmVisitor } from '../../visitors/functions';
import { getReplaceParticipant } from '../config/functions';
import { disconnect } from '../connection/actions';
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
@@ -450,11 +451,12 @@ export function conferenceUniqueIdSet(conference: IJitsiConference) {
*/
export function _conferenceWillJoin(conference: IJitsiConference) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const localTracks
= getLocalTracks(getState()['features/base/tracks'])
= getLocalTracks(state['features/base/tracks'])
.map(t => t.jitsiTrack);
if (localTracks.length) {
if (localTracks.length && !iAmVisitor(state)) {
_addLocalTracksToConference(conference, localTracks);
}
@@ -806,7 +808,7 @@ export function setStartReactionsMuted(muted: boolean, updateBackend = false) {
export function setPassword(
conference: IJitsiConference | undefined,
method: Function | undefined,
password: string) {
password?: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
if (!conference) {
return;

View File

@@ -308,6 +308,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
muc: config.oldConfig.hosts.muc
},
focusUserJid: focusJid,
disableLocalStats: false,
bosh: config.oldConfig.bosh && appendURLParam(config.oldConfig.bosh, 'customusername', username),
websocket: config.oldConfig.websocket
&& appendURLParam(config.oldConfig.websocket, 'customusername', username),
@@ -338,6 +339,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
},
focusUserJid: focusJid,
disableFocus: true, // This flag disables sending the initial conference request
disableLocalStats: true,
bosh: config.bosh && appendURLParam(config.bosh, 'vnode', vnode),
websocket: config.websocket && appendURLParam(config.websocket, 'vnode', vnode)
};

View File

@@ -104,6 +104,7 @@ export interface IJitsiConference {
sendTextMessage: Function;
sendTones: Function;
sessionId: string;
setAssumedBandwidthBps: (value: number) => void;
setDesktopSharingFrameRate: Function;
setDisplayName: Function;
setLocalParticipantProperty: Function;

View File

@@ -35,7 +35,7 @@ export type DialogProps = {
/**
* The handler for the event when submitting the dialog.
*/
onSubmit: Function;
onSubmit?: Function;
/**
* Additional style to be applied on the dialog.

View File

@@ -29,7 +29,7 @@ interface IState {
* A react {@code Component} that implements an expanded label as tooltip-like
* component to explain the meaning of the {@code Label}.
*/
export default class ExpandedLabel<P extends IProps> extends Component<P, IState> {
export default abstract class ExpandedLabel<P extends IProps> extends Component<P, IState> {
/**
* Instantiates a new {@code ExpandedLabel} instance.
*
@@ -85,7 +85,7 @@ export default class ExpandedLabel<P extends IProps> extends Component<P, IState
*
* @returns {string}
*/
_getLabel: () => string;
abstract _getLabel(): string;
/**
* Defines the color of the expanded label. This function returns a default

View File

@@ -48,7 +48,7 @@ export interface IProps {
* The style (as in stylesheet) to be applied to this
* {@code AbstractContainer}.
*/
style?: StyleType;
style?: StyleType | StyleType[];
tabIndex?: number;

View File

@@ -15,6 +15,8 @@ interface IProps {
* prop of the native component.
*/
size?: 'large' | 'small' | 'medium';
style?: any;
}
/**

View File

@@ -163,7 +163,7 @@ class MultiSelectAutocomplete extends Component<IProps, IState> {
const autoFocus = this.props.shouldFocus || false;
const disabled = this.props.isDisabled || false;
const placeholder = this.props.placeholder || '';
const noMatchesFound = this.props.noMatchesFound || '';
const noMatchesFound = this.state.loading ? this.props.loadingMessage : this.props.noMatchesFound || '';
const errorDialog = this._renderError();
return (
@@ -200,7 +200,7 @@ class MultiSelectAutocomplete extends Component<IProps, IState> {
error: this.state.error && Boolean(filterValue),
filterValue,
isOpen: Boolean(this.state.items.length) && Boolean(filterValue),
items: filterValue ? this.state.items : [],
items: [],
loading: Boolean(filterValue)
});
if (filterValue) {

View File

@@ -9,7 +9,7 @@ export * from './functions.any';
* @param {StyleType} style - The passed style prop to the component.
* @returns {StyleType}
*/
export function getFixedPlatformStyle(style?: StyleType): StyleType {
export function getFixedPlatformStyle(style?: StyleType | StyleType[]) {
// There is nothing to do on mobile - yet.
return style ?? {};

View File

@@ -9,7 +9,7 @@ export * from './functions.any';
* @param {StyleType} style - The passed style prop to the component.
* @returns {StyleType}
*/
export function getFixedPlatformStyle(style?: StyleType) {
export function getFixedPlatformStyle(style?: StyleType | StyleType[]) {
if (Array.isArray(style)) {
const _style = {};

View File

@@ -24,6 +24,8 @@ interface IProps {
selectedItems?: MultiSelectItem[];
}
const MULTI_SELECT_HEIGHT = 200;
const useStyles = makeStyles()(theme => {
return {
container: {
@@ -41,7 +43,7 @@ const useStyles = makeStyles()(theme => {
borderRadius: `${Number(theme.shape.borderRadius)}px`,
...withPixelLineHeight(theme.typography.bodyShortRegular),
zIndex: 2,
maxHeight: '400px',
maxHeight: `${MULTI_SELECT_HEIGHT}px`,
overflowY: 'auto',
padding: '0'
},
@@ -128,7 +130,7 @@ const MultiSelect = ({
</div>
</div>
))
: <div>{noMatchesText}</div>
: <div className = { classes.listItem }>{noMatchesText}</div>
}
</div>
), [ items ]);

View File

@@ -1,5 +1,6 @@
/* eslint-disable lines-around-comment, max-len */
import { IParticipant } from '../base/participants/types';
import { navigate }
// @ts-ignore
from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
@@ -21,7 +22,7 @@ export * from './actions.any';
* type: OPEN_CHAT
* }}
*/
export function openChat(participant: Object, disablePolls?: boolean) {
export function openChat(participant: IParticipant | undefined | Object, disablePolls?: boolean) {
if (disablePolls) {
navigate(screen.conference.chat);
}

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { Platform } from 'react-native';
import { WithTranslation } from 'react-i18next';
import { Platform, ViewStyle } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { translate } from '../../../base/i18n/functions';
@@ -10,47 +11,42 @@ import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import styles from './styles';
type Props = {
interface IProps extends WithTranslation {
/**
* Callback to invoke on message send.
*/
onSend: Function,
onSend: Function;
}
/**
* Function to be used to translate i18n labels.
*/
t: Function
};
type State = {
interface IState {
/**
* Boolean to show if an extra padding needs to be added to the bar.
*/
addPadding: boolean,
addPadding: boolean;
/**
* The value of the input field.
*/
message: string,
message: string;
/**
* Boolean to show or hide the send button.
*/
showSend: boolean
};
showSend: boolean;
}
/**
* Implements the chat input bar with text field and action(s).
*/
class ChatInputBar extends Component<Props, State> {
class ChatInputBar extends Component<IProps, IState> {
/**
* Instantiates a new instance of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this.state = {
@@ -76,7 +72,7 @@ class ChatInputBar extends Component<Props, State> {
style = { [
styles.inputBar,
this.state.addPadding ? styles.extraBarPadding : null
] }>
] as ViewStyle[] }>
<Input
blurOnSubmit = { false }
customStyles = {{ container: styles.customInputContainer }}
@@ -98,30 +94,26 @@ class ChatInputBar extends Component<Props, State> {
);
}
_onChangeText: string => void;
/**
* Callback to handle the change of the value of the text field.
*
* @param {string} text - The current value of the field.
* @returns {void}
*/
_onChangeText(text) {
_onChangeText(text: string) {
this.setState({
message: text,
showSend: Boolean(text)
});
}
_onFocused: boolean => Function;
/**
* Constructs a callback to be used to update the padding of the field if necessary.
*
* @param {boolean} focused - True of the field is focused.
* @returns {Function}
*/
_onFocused(focused) {
_onFocused(focused: boolean) {
return () => {
Platform.OS === 'android' && this.setState({
addPadding: focused
@@ -129,8 +121,6 @@ class ChatInputBar extends Component<Props, State> {
};
}
_onSubmit: () => void;
/**
* Callback to handle the submit event of the text field.
*

View File

@@ -1,14 +1,15 @@
import React from 'react';
import { Text, View } from 'react-native';
import { Text, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Avatar from '../../../base/avatar/components/Avatar';
import { translate } from '../../../base/i18n/functions';
import Linkify from '../../../base/react/components/native/Linkify';
import { isGifMessage } from '../../../gifs/functions';
import { isGifMessage } from '../../../gifs/functions.native';
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
import { replaceNonUnicodeEmojis } from '../../functions';
import AbstractChatMessage, { type Props } from '../AbstractChatMessage';
import AbstractChatMessage, { IProps } from '../AbstractChatMessage';
import GifMessage from './GifMessage';
import PrivateMessageButton from './PrivateMessageButton';
@@ -18,7 +19,7 @@ import styles from './styles';
/**
* Renders a single chat message.
*/
class ChatMessage extends AbstractChatMessage<Props> {
class ChatMessage extends AbstractChatMessage<IProps> {
/**
* Implements {@code Component#render}.
*
@@ -31,18 +32,18 @@ class ChatMessage extends AbstractChatMessage<Props> {
// Style arrays that need to be updated in various scenarios, such as
// error messages or others.
const detailsWrapperStyle = [
styles.detailsWrapper
const detailsWrapperStyle: ViewStyle[] = [
styles.detailsWrapper as ViewStyle
];
const messageBubbleStyle = [
styles.messageBubble
const messageBubbleStyle: ViewStyle[] = [
styles.messageBubble as ViewStyle
];
if (localMessage) {
// This is a message sent by the local participant.
// The wrapper needs to be aligned to the right.
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper);
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper as ViewStyle);
// The bubble needs some additional styling
messageBubbleStyle.push(styles.localMessageBubble);
@@ -69,11 +70,11 @@ class ChatMessage extends AbstractChatMessage<Props> {
const messageText = replaceNonUnicodeEmojis(this._getMessageText());
return (
<View style = { styles.messageWrapper } >
<View style = { styles.messageWrapper as ViewStyle } >
{ this._renderAvatar() }
<View style = { detailsWrapperStyle }>
<View style = { messageBubbleStyle }>
<View style = { styles.textWrapper } >
<View style = { styles.textWrapper as ViewStyle } >
{ this._renderDisplayName() }
{ isGifMessage(messageText)
? <GifMessage message = { messageText } />
@@ -94,12 +95,6 @@ class ChatMessage extends AbstractChatMessage<Props> {
);
}
_getFormattedTimestamp: () => string;
_getMessageText: () => string;
_getPrivateNoticeMessage: () => string;
/**
* Renders the avatar of the sender.
*
@@ -171,7 +166,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
}
return (
<View style = { styles.replyContainer }>
<View style = { styles.replyContainer as ViewStyle }>
<PrivateMessageButton
isLobbyMessage = { lobbyChat }
participantID = { message.id }
@@ -204,9 +199,9 @@ class ChatMessage extends AbstractChatMessage<Props> {
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
return {
knocking: state['features/lobby'].knocking
};

View File

@@ -1,28 +1,29 @@
import React, { Component, ReactElement } from 'react';
import React, { Component } from 'react';
import { FlatList } from 'react-native';
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
import { IMessage } from '../../reducer';
import ChatMessage from './ChatMessage';
type Props = {
interface IProps {
/**
* The messages array to render.
*/
messages: Array<Object>
/**
* The messages array to render.
*/
messages: Array<IMessage>;
}
/**
* Implements a container to render all the chat messages in a conference.
*/
export default class ChatMessageGroup extends Component<Props> {
export default class ChatMessageGroup extends Component<IProps> {
/**
* Instantiates a new instance of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._keyExtractor = this._keyExtractor.bind(this);
@@ -44,29 +45,25 @@ export default class ChatMessageGroup extends Component<Props> {
);
}
_keyExtractor: Object => string;
/**
* Key extractor for the flatlist.
*
* @param {Object} item - The flatlist item that we need the key to be
* @param {Object} _item - The flatlist item that we need the key to be
* generated for.
* @param {number} index - The index of the element.
* @returns {string}
*/
_keyExtractor(item, index) {
_keyExtractor(_item: Object, index: number) {
return `key_${index}`;
}
_renderMessage: Object => ReactElement;
/**
* Renders a single chat message.
*
* @param {Object} message - The chat message to render.
* @returns {React$Element<*>}
*/
_renderMessage({ index, item: message }) {
_renderMessage({ index, item: message }: { index: number; item: IMessage; }) {
return (
<ChatMessage
message = { message }

View File

@@ -1,26 +1,26 @@
import React from 'react';
import { Image, View } from 'react-native';
import { Image, ImageStyle, View } from 'react-native';
import { GIF_PREFIX } from '../../../gifs/constants';
import styles from './styles';
type Props = {
interface IProps {
/**
* The formatted gif message.
*/
message: string
message: string;
}
const GifMessage = ({ message }: Props) => {
const GifMessage = ({ message }: IProps) => {
const url = message.substring(GIF_PREFIX.length, message.length - 1);
return (<View
style = { styles.gifContainer }>
<Image
source = {{ uri: url }}
style = { styles.gifImage } />
style = { styles.gifImage as ImageStyle } />
</View>);
};

View File

@@ -1,32 +1,32 @@
import React, { ReactElement } from 'react';
import { FlatList, Text, View } from 'react-native';
import React from 'react';
import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { translate } from '../../../base/i18n/functions';
import AbstractMessageContainer, { type Props as AbstractProps }
from '../AbstractMessageContainer';
import { IMessage } from '../../reducer';
import AbstractMessageContainer, { IProps as AbstractProps } from '../AbstractMessageContainer';
import ChatMessageGroup from './ChatMessageGroup';
import styles from './styles';
type Props = AbstractProps & {
interface IProps extends AbstractProps {
/**
* Function to be used to translate i18n labels.
*/
t: Function
};
t: Function;
}
/**
* Implements a container to render all the chat messages in a conference.
*/
class MessageContainer extends AbstractMessageContainer<Props> {
class MessageContainer extends AbstractMessageContainer<IProps, any> {
/**
* Instantiates a new instance of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._keyExtractor = this._keyExtractor.bind(this);
@@ -57,24 +57,18 @@ class MessageContainer extends AbstractMessageContainer<Props> {
);
}
_getMessagesGroupedBySender: () => Array<Array<Object>>;
_keyExtractor: Object => string;
/**
* Key extractor for the flatlist.
*
* @param {Object} item - The flatlist item that we need the key to be
* @param {Object} _item - The flatlist item that we need the key to be
* generated for.
* @param {number} index - The index of the element.
* @returns {string}
*/
_keyExtractor(item, index) {
_keyExtractor(_item: Object, index: number) {
return `key_${index}`;
}
_renderListEmptyComponent: () => ReactElement;
/**
* Renders a message when there are no messages in the chat yet.
*
@@ -84,23 +78,21 @@ class MessageContainer extends AbstractMessageContainer<Props> {
const { t } = this.props;
return (
<View style = { styles.emptyComponentWrapper }>
<Text style = { styles.emptyComponentText }>
<View style = { styles.emptyComponentWrapper as ViewStyle }>
<Text style = { styles.emptyComponentText as TextStyle }>
{ t('chat.noMessagesMessage') }
</Text>
</View>
);
}
_renderMessageGroup: Object => ReactElement;
/**
* Renders a single chat message.
*
* @param {Array<Object>} messages - The chat message to render.
* @returns {React$Element<*>}
*/
_renderMessageGroup({ item: messages }) {
_renderMessageGroup({ item: messages }: { item: IMessage[]; }) {
return <ChatMessageGroup messages = { messages } />;
}
}

View File

@@ -1,57 +1,49 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { CHAT_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
import { IconMessage, IconReply } from '../../../base/icons/svg';
import { getParticipantById } from '../../../base/participants/functions';
import { IParticipant } from '../../../base/participants/types';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { handleLobbyChatInitialized, openChat } from '../../../chat/actions.native';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { handleLobbyChatInitialized, openChat } from '../../actions.native';
export type Props = AbstractButtonProps & {
/**
* The Redux Dispatch function.
*/
dispatch: Function,
/**
* The ID of the participant that the message is to be sent.
*/
participantID: string,
/**
* True if the button is rendered as a reply button.
*/
reply: boolean,
/**
* Function to be used to translate i18n labels.
*/
t: Function,
/**
* True if the polls feature is disabled.
*/
_isPollsDisabled: boolean,
export interface IProps extends AbstractButtonProps {
/**
* True if message is a lobby chat message.
*/
_isLobbyMessage: boolean,
_isLobbyMessage: boolean;
/**
* True if the polls feature is disabled.
*/
_isPollsDisabled?: boolean;
/**
* The participant object retrieved from Redux.
*/
_participant: Object,
};
_participant?: IParticipant;
/**
* The ID of the participant that the message is to be sent.
*/
participantID: string;
/**
* True if the button is rendered as a reply button.
*/
reply: boolean;
}
/**
* Class to render a button that initiates the sending of a private message through chet.
* Class to render a button that initiates the sending of a private message through chat.
*/
class PrivateMessageButton extends AbstractButton<Props, any> {
class PrivateMessageButton extends AbstractButton<IProps, any> {
accessibilityLabel = 'toolbar.accessibilityLabel.privateMessage';
icon = IconMessage;
label = 'toolbar.privateMessage';
@@ -99,10 +91,10 @@ class PrivateMessageButton extends AbstractButton<Props, any> {
* Maps part of the Redux store to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
* @param {IProps} ownProps - The own props of the component.
* @returns {IProps}
*/
export function _mapStateToProps(state: Object, ownProps: Props) {
export function _mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { disablePolls } = state['features/base/config'];
const { visible = enabled, isLobbyMessage, participantID } = ownProps;

View File

@@ -90,6 +90,8 @@ class ChatInput extends Component<IProps, IState> {
if (isMobileBrowser()) {
// Ensure textarea is not focused when opening chat on mobile browser.
this._textArea?.current && this._textArea.current.blur();
} else {
this._focus();
}
}

View File

@@ -29,9 +29,9 @@ const styles = (theme: Theme) => {
chatMessage: {
display: 'inline-flex',
padding: '12px',
marginRight: '12px',
backgroundColor: theme.palette.ui02,
borderRadius: '4px 12px 12px 12px',
boxSizing: 'border-box' as const,
maxWidth: '100%',
marginTop: '4px',

View File

@@ -0,0 +1,12 @@
import { IFRAME_EMBED_ALLOWED_LOCATIONS as ADDITIONAL_LOCATIONS } from './extraConstants';
/**
* Timeout of the conference when iframe is disabled in minutes.
*/
export const IFRAME_DISABLED_TIMEOUT_MINUTES = 5;
/**
* A list of allowed location to embed iframe.
*/
/* eslint-disable-next-line no-extra-parens*/
export const IFRAME_EMBED_ALLOWED_LOCATIONS = ([] as string[]).concat(ADDITIONAL_LOCATIONS);

View File

@@ -0,0 +1,5 @@
/**
* Deploy-specific configuration constants.
*/
export const IFRAME_EMBED_ALLOWED_LOCATIONS = [];

View File

@@ -1,8 +1,11 @@
import i18n from 'i18next';
import { batch } from 'react-redux';
// @ts-expect-error
import { API_ID } from '../../../modules/API/constants';
import { appNavigate } from '../app/actions';
import { IStore } from '../app/types';
import { redirectToStaticPage } from '../app/actions.any';
import { IReduxState, IStore } from '../app/types';
import {
CONFERENCE_FAILED,
CONFERENCE_JOINED,
@@ -15,17 +18,22 @@ import { getURLWithoutParamsNormalized } from '../base/connection/utils';
import { hideDialog } from '../base/dialog/actions';
import { isDialogOpen } from '../base/dialog/functions';
import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
import { translateToHTML } from '../base/i18n/functions';
import i18next from '../base/i18n/i18next';
import { browser } from '../base/lib-jitsi-meet';
import { pinParticipant } from '../base/participants/actions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes';
import { BUTTON_TYPES } from '../base/ui/constants.any';
import { inIframe } from '../base/util/iframeUtils';
import { isCalendarEnabled } from '../calendar-sync/functions';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import FeedbackDialog from '../feedback/components/FeedbackDialog';
import { setFilmstripEnabled } from '../filmstrip/actions.any';
import { hideNotification, showNotification } from '../notifications/actions';
import { isVpaasMeeting } from '../jaas/functions';
import { hideNotification, showNotification, showWarningNotification } from '../notifications/actions';
import {
CALENDAR_NOTIFICATION_ID,
NOTIFICATION_ICON,
@@ -36,6 +44,7 @@ import { setToolboxEnabled } from '../toolbox/actions.any';
import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
import { dismissCalendarNotification, notifyKickedOut } from './actions';
import { IFRAME_DISABLED_TIMEOUT_MINUTES, IFRAME_EMBED_ALLOWED_LOCATIONS } from './constants';
let intervalID: any;
@@ -155,6 +164,49 @@ function _conferenceJoined({ dispatch, getState }: IStore) {
}
dispatch(showSalesforceNotification());
_checkIframe(getState(), dispatch);
}
/**
* Additional checks for embedding in iframe.
*
* @param {IReduxState} state - The current state of the app.
* @param {Function} dispatch - The Redux dispatch function.
* @private
* @returns {void}
*/
function _checkIframe(state: IReduxState, dispatch: IStore['dispatch']) {
let allowIframe = false;
if (document.referrer === '') {
// no iframe
allowIframe = true;
} else {
try {
allowIframe = IFRAME_EMBED_ALLOWED_LOCATIONS.includes(new URL(document.referrer).hostname);
} catch (e) {
// wrong URL in referrer
}
}
if (inIframe() && state['features/base/config'].disableIframeAPI && !browser.isElectron()
&& !isVpaasMeeting(state) && !allowIframe) {
// show sticky notification and redirect in 5 minutes
dispatch(showWarningNotification({
description: translateToHTML(
i18next.t.bind(i18next),
'notify.disabledIframe',
{
timeout: IFRAME_DISABLED_TIMEOUT_MINUTES
}
)
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
setTimeout(() => {
// redirect to the promotional page
dispatch(redirectToStaticPage('static/close3.html', `#jitsi_meet_external_api_id=${API_ID}`));
}, IFRAME_DISABLED_TIMEOUT_MINUTES * 60 * 1000);
}
}
/**

View File

@@ -1,5 +1,3 @@
/* eslint-disable lines-around-comment */
import React from 'react';
import { StyleProp, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
@@ -12,12 +10,10 @@ import {
getParticipantById,
isScreenShareParticipant
} from '../../../base/participants/functions';
// @ts-ignore
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
import {
getTrackByMediaTypeAndParticipant
} from '../../../base/tracks/functions.native';
// @ts-ignore
import indicatorStyles from '../../../filmstrip/components/native/styles';
import {
isTrackStreamingStatusInactive,
@@ -50,12 +46,12 @@ type IProps = AbstractProps & {
/**
* Whether the connection is inactive or not.
*/
_isConnectionStatusInactive: boolean;
_isConnectionStatusInactive?: boolean;
/**
* Whether the connection is interrupted or not.
*/
_isConnectionStatusInterrupted: boolean;
_isConnectionStatusInterrupted?: boolean;
/**
* Whether the current participant is a virtual screenshare.
@@ -70,7 +66,7 @@ type IProps = AbstractProps & {
/**
* Icon style override.
*/
iconStyle: any;
iconStyle?: any;
};
type IState = {
@@ -127,12 +123,10 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
_isVirtualScreenshareParticipant,
_isConnectionStatusInactive,
_isConnectionStatusInterrupted
// @ts-ignore
} = this.props;
const {
showIndicator,
stats
// @ts-ignore
} = this.state;
const { percent } = stats;
@@ -167,10 +161,8 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
indicatorStyles.indicatorContainer as StyleProp<ViewStyle>,
{ backgroundColor: indicatorColor }
] }>
{/* @ts-ignore */}
<BaseIndicator
icon = { IconConnection }
// @ts-ignore
iconStyle = { this.props.iconStyle || iconStyle } />
</View>
);
@@ -185,7 +177,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
* @param {IProps} ownProps - The own props of the component.
* @returns {IProps}
*/
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
export function _mapStateToProps(state: IReduxState, ownProps: any) {
const { participantId } = ownProps;
const tracks = state['features/base/tracks'];
const participant = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
@@ -212,5 +204,4 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
};
}
// @ts-ignore
export default connect(_mapStateToProps)(ConnectionIndicator);

View File

@@ -20,7 +20,7 @@ export interface IProps {
/**
* Implements an abstract class for the RaisedHandIndicator component.
*/
export default class AbstractRaisedHandIndicator<P extends IProps>
export default abstract class AbstractRaisedHandIndicator<P extends IProps>
extends Component<P> {
/**
@@ -41,7 +41,7 @@ export default class AbstractRaisedHandIndicator<P extends IProps>
*
* @returns {React$Element<*>}
*/
_renderIndicator: () => React.ReactElement;
abstract _renderIndicator(): React.ReactElement;
}

View File

@@ -1,5 +1,3 @@
// @flow
import React, { Component } from 'react';
import { IconMicSlash } from '../../../base/icons/svg';

View File

@@ -1,10 +1,9 @@
// @flow
import React, { PureComponent } from 'react';
import { FlatList } from 'react-native';
import { FlatList, ViewStyle, ViewToken } from 'react-native';
import { SafeAreaView, withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import { getLocalParticipant } from '../../../base/participants/functions';
import Platform from '../../../base/react/Platform.native';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
@@ -24,54 +23,54 @@ import styles from './styles';
// Immutable reference to avoid re-renders.
const NO_REMOTE_VIDEOS = [];
const NO_REMOTE_VIDEOS: any[] = [];
/**
* Filmstrip component's property types.
*/
type Props = {
interface IProps {
/**
* Application's aspect ratio.
*/
_aspectRatio: Symbol,
_aspectRatio: Symbol;
_clientWidth: number,
_clientHeight: number;
_clientHeight: number,
_clientWidth: number;
/**
* Whether or not to hide the self view.
*/
_disableSelfView: boolean,
_disableSelfView: boolean;
/**
* Whether or not the toolbox is displayed.
*/
_toolboxVisible: Boolean,
_localParticipantId: string,
_localParticipantId: string;
/**
* The participants in the conference.
*/
_participants: Array<any>,
_participants: Array<any>;
/**
* Whether or not the toolbox is displayed.
*/
_toolboxVisible: Boolean;
/**
* The indicator which determines whether the filmstrip is visible.
*/
_visible: boolean,
_visible: boolean;
/**
* Invoked to trigger state changes in Redux.
*/
dispatch: Function,
dispatch: IStore['dispatch'];
/**
* Object containing the safe area insets.
*/
insets: Object,
};
insets: Object;
}
/**
* Implements a React {@link Component} which represents the filmstrip on
@@ -79,7 +78,7 @@ type Props = {
*
* @augments Component
*/
class Filmstrip extends PureComponent<Props> {
class Filmstrip extends PureComponent<IProps> {
/**
* Whether the local participant should be rendered separately from the
* remote participants ie outside of their {@link ScrollView}.
@@ -96,7 +95,7 @@ class Filmstrip extends PureComponent<Props> {
*
* @inheritdoc
*/
constructor(props) {
constructor(props: IProps) {
super(props);
// XXX Our current design is to have the local participant separate from
@@ -129,15 +128,13 @@ class Filmstrip extends PureComponent<Props> {
this._renderThumbnail = this._renderThumbnail.bind(this);
}
_keyExtractor: string => string;
/**
* Returns a key for a passed item of the list.
*
* @param {string} item - The user ID.
* @returns {string} - The user ID.
*/
_keyExtractor(item) {
_keyExtractor(item: string) {
return item;
}
@@ -166,16 +163,14 @@ class Filmstrip extends PureComponent<Props> {
});
}
_getItemLayout: (?Array<string>, number) => {length: number, offset: number, index: number};
/**
* Optimization for FlatList. Returns the length, offset and index for an item.
*
* @param {Array<string>} data - The data array with user IDs.
* @param {Array<string>} _data - The data array with user IDs.
* @param {number} index - The index number of the item.
* @returns {Object}
*/
_getItemLayout(data, index) {
_getItemLayout(_data: string[] | null | undefined, index: number) {
const { _aspectRatio } = this.props;
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
const length = isNarrowAspectRatio ? styles.thumbnail.width : styles.thumbnail.height;
@@ -187,8 +182,6 @@ class Filmstrip extends PureComponent<Props> {
};
}
_onViewableItemsChanged: Object => void;
/**
* A handler for visible items changes.
*
@@ -196,7 +189,7 @@ class Filmstrip extends PureComponent<Props> {
* @param {Array<Object>} data.viewableItems - The visible items array.
* @returns {void}
*/
_onViewableItemsChanged({ viewableItems = [] }) {
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: ViewToken[]; }) {
const { _disableSelfView } = this.props;
if (!this._separateLocalThumbnail && !_disableSelfView && viewableItems[0]?.index === 0) {
@@ -209,8 +202,8 @@ class Filmstrip extends PureComponent<Props> {
return;
}
let startIndex = viewableItems[0].index;
let endIndex = viewableItems[viewableItems.length - 1].index;
let startIndex = Number(viewableItems[0].index);
let endIndex = Number(viewableItems[viewableItems.length - 1].index);
if (!this._separateLocalThumbnail && !_disableSelfView) {
// We are off by one in the remote participants array.
@@ -221,15 +214,13 @@ class Filmstrip extends PureComponent<Props> {
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
}
_renderThumbnail: Object => Object;
/**
* Creates React Element to display each participant in a thumbnail.
*
* @private
* @returns {ReactElement}
*/
_renderThumbnail({ item /* , index , separators */ }) {
_renderThumbnail({ item }: { item: string; }) {
return (
<Thumbnail
key = { item }
@@ -277,9 +268,9 @@ class Filmstrip extends PureComponent<Props> {
}
return (
<SafeAreaView
<SafeAreaView // @ts-ignore
edges = { [ bottomEdge && 'bottom', 'left', 'right' ].filter(Boolean) }
style = { filmstripStyle }>
style = { filmstripStyle as ViewStyle }>
{
this._separateLocalThumbnail
&& !isNarrowAspectRatio
@@ -317,9 +308,9 @@ class Filmstrip extends PureComponent<Props> {
*
* @param {Object} state - The redux state.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
const { enabled, remoteParticipants } = state['features/filmstrip'];
const disableSelfView = getHideSelfView(state);
const showRemoteVideos = shouldRemoteVideosBeVisible(state);
@@ -330,7 +321,7 @@ function _mapStateToProps(state) {
_clientHeight: responsiveUI.clientHeight,
_clientWidth: responsiveUI.clientWidth,
_disableSelfView: disableSelfView,
_localParticipantId: getLocalParticipant(state)?.id,
_localParticipantId: getLocalParticipant(state)?.id ?? '',
_participants: showRemoteVideos ? remoteParticipants : NO_REMOTE_VIDEOS,
_toolboxVisible: isToolboxVisible(state),
_visible: enabled && isFilmstripVisible(state)

View File

@@ -1,7 +1,5 @@
// @flow
import React from 'react';
import { View } from 'react-native';
import { View, ViewStyle } from 'react-native';
import Thumbnail from './Thumbnail';
import styles from './styles';
@@ -14,7 +12,7 @@ import styles from './styles';
*/
export default function LocalThumbnail() {
return (
<View style = { styles.localThumbnail }>
<View style = { styles.localThumbnail as ViewStyle }>
<Thumbnail />
</View>
);

View File

@@ -1,5 +1,3 @@
// @flow
import React from 'react';
import { IconPin } from '../../../base/icons/svg';

View File

@@ -1,12 +1,11 @@
// @flow
import React from 'react';
import { View } from 'react-native';
import { View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { IconRaiseHand } from '../../../base/icons/svg';
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
import AbstractRaisedHandIndicator, {
IProps,
_mapStateToProps
} from '../AbstractRaisedHandIndicator';
@@ -18,7 +17,7 @@ import styles from './styles';
*
* @augments Component
*/
class RaisedHandIndicator extends AbstractRaisedHandIndicator<Props> {
class RaisedHandIndicator extends AbstractRaisedHandIndicator<IProps> {
/**
* Renders the platform specific indicator element.
*
@@ -26,7 +25,7 @@ class RaisedHandIndicator extends AbstractRaisedHandIndicator<Props> {
*/
_renderIndicator() {
return (
<View style = { styles.raisedHandIndicator }>
<View style = { styles.raisedHandIndicator as ViewStyle }>
<BaseIndicator
icon = { IconRaiseHand }
iconStyle = { styles.raisedHandIcon } />

View File

@@ -1,5 +1,3 @@
// @flow
import React from 'react';
import { IconScreenshare } from '../../../base/icons/svg';

View File

@@ -1,8 +1,8 @@
import React, { PureComponent } from 'react';
import { Image, View } from 'react-native';
import { Image, ImageStyle, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { IReduxState, IStore } from '../../../app/types';
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
import { MEDIA_TYPE, VIDEO_TYPE } from '../../../base/media/constants';
import { pinParticipant } from '../../../base/participants/actions';
@@ -18,14 +18,16 @@ import {
} from '../../../base/participants/functions';
import { FakeParticipant } from '../../../base/participants/types';
import Container from '../../../base/react/components/native/Container';
import { StyleType } from '../../../base/styles/functions.any';
import { trackStreamingStatusChanged } from '../../../base/tracks/actions.native';
import {
getTrackByMediaTypeAndParticipant,
getVideoTrackByParticipant
} from '../../../base/tracks/functions.native';
import { ITrack } from '../../../base/tracks/types';
import ConnectionIndicator from '../../../connection-indicator/components/native/ConnectionIndicator';
import DisplayNameLabel from '../../../display-name/components/native/DisplayNameLabel';
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions.native';
import {
showConnectionStatus,
showContextMenuDetails,
@@ -44,42 +46,42 @@ import styles, { AVATAR_SIZE } from './styles';
/**
* Thumbnail component's property types.
*/
type Props = {
interface IProps {
/**
* Whether local audio (microphone) is muted or not.
*/
_audioMuted: boolean,
/**
* URL of GIF sent by this participant, null if there's none.
*/
_gifSrc: ?string,
_audioMuted: boolean;
/**
* The type of participant if the participant is fake.
*/
_fakeParticipant?: FakeParticipant,
_fakeParticipant?: FakeParticipant;
/**
* URL of GIF sent by this participant, null if there's none.
*/
_gifSrc?: string;
/**
* Indicates whether the participant is screen sharing.
*/
_isScreenShare: boolean,
_isScreenShare: boolean;
/**
* Indicates whether the thumbnail is for a virtual screenshare participant.
*/
_isVirtualScreenshare: boolean,
_isVirtualScreenshare: boolean;
/**
* Indicates whether the participant is local.
*/
_local: boolean,
_local?: boolean;
/**
* Shared video local participant owner.
*/
_localVideoOwner: boolean,
_localVideoOwner: boolean;
/**
* The ID of the participant obtain from the participant object in Redux.
@@ -87,71 +89,71 @@ type Props = {
* NOTE: Generally it should be the same as the participantID prop except the case where the passed
* participantID doesn't correspond to any of the existing participants.
*/
_participantId: string,
_participantId: string;
/**
* Indicates whether the participant is pinned or not.
*/
_pinned: boolean,
_pinned?: boolean;
/**
* Whether or not the participant has the hand raised.
*/
_raisedHand: boolean,
_raisedHand: boolean;
/**
* Whether to show the dominant speaker indicator or not.
*/
_renderDominantSpeakerIndicator: boolean,
_renderDominantSpeakerIndicator?: boolean;
/**
* Whether to show the moderator indicator or not.
*/
_renderModeratorIndicator: boolean,
_renderModeratorIndicator: boolean;
/**
* The video track that will be displayed in the thumbnail.
*/
_videoTrack: ?Object,
_videoTrack?: ITrack;
/**
* Invoked to trigger state changes in Redux.
*/
dispatch: Dispatch<any>,
dispatch: IStore['dispatch'];
/**
* The height of the thumnail.
* The height of the thumbnail.
*/
height: ?number,
height?: number;
/**
* The ID of the participant related to the thumbnail.
*/
participantID: ?string,
participantID?: string;
/**
* Whether to display or hide the display name of the participant in the thumbnail.
*/
renderDisplayName: ?boolean,
renderDisplayName?: boolean;
/**
* If true, it tells the thumbnail that it needs to behave differently. E.g. React differently to a single tap.
*/
tileView?: boolean
};
tileView?: boolean;
}
/**
* React component for video thumbnail.
*/
class Thumbnail extends PureComponent<Props> {
class Thumbnail extends PureComponent<IProps> {
/**
* Creates new Thumbnail component.
*
* @param {Props} props - The props of the component.
* @param {IProps} props - The props of the component.
* @returns {Thumbnail}
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._onClick = this._onClick.bind(this);
@@ -159,8 +161,6 @@ class Thumbnail extends PureComponent<Props> {
this.handleTrackStreamingStatusChanged = this.handleTrackStreamingStatusChanged.bind(this);
}
_onClick: () => void;
/**
* Thumbnail click handler.
*
@@ -176,8 +176,6 @@ class Thumbnail extends PureComponent<Props> {
}
}
_onThumbnailLongPress: () => void;
/**
* Thumbnail long press handler.
*
@@ -219,11 +217,11 @@ class Thumbnail extends PureComponent<Props> {
if (!_fakeParticipant || _isVirtualScreenshare) {
indicators.push(<View
key = 'top-left-indicators'
style = { styles.thumbnailTopLeftIndicatorContainer }>
style = { styles.thumbnailTopLeftIndicatorContainer as ViewStyle }>
{ !_isVirtualScreenshare && <ConnectionIndicator participantId = { participantId } /> }
{ !_isVirtualScreenshare && <RaisedHandIndicator participantId = { participantId } /> }
{ tileView && (isScreenShare || _isVirtualScreenshare) && (
<View style = { styles.screenShareIndicatorContainer }>
<View style = { styles.screenShareIndicatorContainer as ViewStyle }>
<ScreenShareIndicator />
</View>
) }
@@ -231,7 +229,9 @@ class Thumbnail extends PureComponent<Props> {
indicators.push(<Container
key = 'bottom-indicators'
style = { styles.thumbnailIndicatorContainer }>
<Container style = { (audioMuted || renderModeratorIndicator) && styles.bottomIndicatorsContainer }>
<Container
style = { ((audioMuted || renderModeratorIndicator) && styles.bottomIndicatorsContainer
) as StyleType }>
{ audioMuted && !_isVirtualScreenshare && <AudioMutedIndicator /> }
{ !tileView && _pinned && <PinnedIndicator />}
{ renderModeratorIndicator && !_isVirtualScreenshare && <ModeratorIndicator />}
@@ -275,7 +275,7 @@ class Thumbnail extends PureComponent<Props> {
* @inheritdoc
* @returns {void}
*/
componentDidUpdate(prevProps: Props) {
componentDidUpdate(prevProps: IProps) {
// TODO: after converting this component to a react function component,
// use a custom hook to update local track streaming status.
const { _videoTrack, dispatch } = this.props;
@@ -323,7 +323,7 @@ class Thumbnail extends PureComponent<Props> {
* @param {JitsiTrackStreamingStatus} streamingStatus - The updated track streaming status.
* @returns {void}
*/
handleTrackStreamingStatusChanged(jitsiTrack, streamingStatus) {
handleTrackStreamingStatusChanged(jitsiTrack: any, streamingStatus: string) {
this.props.dispatch(trackStreamingStatusChanged(jitsiTrack, streamingStatus));
}
@@ -363,11 +363,11 @@ class Thumbnail extends PureComponent<Props> {
styleOverrides,
_raisedHand && !_isVirtualScreenshare ? styles.thumbnailRaisedHand : null,
_renderDominantSpeakerIndicator && !_isVirtualScreenshare ? styles.thumbnailDominantSpeaker : null
] }
] as StyleType[] }
touchFeedback = { false }>
{ _gifSrc ? <Image
source = {{ uri: _gifSrc }}
style = { styles.thumbnailGif } />
style = { styles.thumbnailGif as ImageStyle } />
: <>
<ParticipantView
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
@@ -388,36 +388,36 @@ class Thumbnail extends PureComponent<Props> {
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @param {Props} ownProps - Properties of component.
* @param {IProps} ownProps - Properties of component.
* @returns {Object}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const { ownerId } = state['features/shared-video'];
const tracks = state['features/base/tracks'];
const { participantID, tileView } = ownProps;
const participant = getParticipantByIdOrUndefined(state, participantID);
const localParticipantId = getLocalParticipant(state).id;
const localParticipantId = getLocalParticipant(state)?.id;
const id = participant?.id;
const audioTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
const videoTrack = getVideoTrackByParticipant(state, participant);
const isScreenShare = videoTrack?.videoType === VIDEO_TYPE.DESKTOP;
const participantCount = getParticipantCount(state);
const renderDominantSpeakerIndicator = participant && participant.dominantSpeaker && participantCount > 2;
const renderDominantSpeakerIndicator = participant?.dominantSpeaker && participantCount > 2;
const _isEveryoneModerator = isEveryoneModerator(state);
const renderModeratorIndicator = tileView && !_isEveryoneModerator
&& participant?.role === PARTICIPANT_ROLE.MODERATOR;
const { gifUrl: gifSrc } = getGifForParticipant(state, id);
const { gifUrl: gifSrc } = getGifForParticipant(state, id ?? '');
const mode = getGifDisplayMode(state);
return {
_audioMuted: audioTrack?.muted ?? true,
_fakeParticipant: participant?.fakeParticipant,
_gifSrc: mode === 'chat' ? null : gifSrc,
_gifSrc: mode === 'chat' ? undefined : gifSrc,
_isScreenShare: isScreenShare,
_isVirtualScreenshare: isScreenShareParticipant(participant),
_local: participant?.local,
_localVideoOwner: Boolean(ownerId === localParticipantId),
_participantId: id,
_participantId: id ?? '',
_pinned: participant?.pinned,
_raisedHand: hasRaisedHand(participant),
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,

View File

@@ -1,16 +1,17 @@
// @flow
import React, { PureComponent } from 'react';
import {
FlatList,
GestureResponderEvent,
SafeAreaView,
TouchableWithoutFeedback
TouchableWithoutFeedback,
ViewToken
} from 'react-native';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { IReduxState, IStore } from '../../../app/types';
import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants/functions';
import { ILocalParticipant } from '../../../base/participants/types';
import { getHideSelfView } from '../../../base/settings/functions.any';
import { setVisibleRemoteParticipants } from '../../actions.web';
@@ -20,74 +21,74 @@ import styles from './styles';
/**
* The type of the React {@link Component} props of {@link TileView}.
*/
type Props = {
interface IProps {
/**
* Application's aspect ratio.
*/
_aspectRatio: Symbol,
_aspectRatio: Symbol;
/**
* The number of columns.
*/
_columns: number,
_columns: number;
/**
* Whether or not to hide the self view.
*/
_disableSelfView: boolean,
_disableSelfView: boolean;
/**
* Application's viewport height.
*/
_height: number,
_height: number;
/**
* The local participant.
*/
_localParticipant: Object,
_localParticipant?: ILocalParticipant;
/**
* The number of participants in the conference.
*/
_participantCount: number,
_participantCount: number;
/**
* An array with the IDs of the remote participants in the conference.
*/
_remoteParticipants: Array<string>,
_remoteParticipants: Array<string>;
/**
* The thumbnail height.
*/
_thumbnailHeight: number,
_thumbnailHeight?: number;
/**
* Application's viewport height.
*/
_width: number,
_width: number;
/**
* Invoked to update the receiver video quality.
*/
dispatch: Dispatch<any>,
dispatch: IStore['dispatch'];
/**
* Object containing the safe area insets.
*/
insets: Object,
insets: EdgeInsets;
/**
* Callback to invoke when tile view is tapped.
*/
onClick: Function
};
onClick: (e?: GestureResponderEvent) => void;
}
/**
* An empty array. The purpose of the constant is to use the same reference every time we need an empty array.
* This will prevent unnecessary re-renders.
*/
const EMPTY_ARRAY = [];
const EMPTY_ARRAY: any[] = [];
/**
* Implements a React {@link PureComponent} which displays thumbnails in a two
@@ -95,17 +96,17 @@ const EMPTY_ARRAY = [];
*
* @augments PureComponent
*/
class TileView extends PureComponent<Props> {
class TileView extends PureComponent<IProps> {
/**
* The styles for the content container of the FlatList.
*/
_contentContainerStyles: Object;
_contentContainerStyles: any;
/**
* The styles for the FlatList.
*/
_flatListStyles: Object;
_flatListStyles: any;
/**
* The FlatList's viewabilityConfig.
@@ -115,9 +116,9 @@ class TileView extends PureComponent<Props> {
/**
* Creates new TileView component.
*
* @param {Props} props - The props of the component.
* @param {IProps} props - The props of the component.
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._keyExtractor = this._keyExtractor.bind(this);
@@ -137,20 +138,16 @@ class TileView extends PureComponent<Props> {
};
}
_keyExtractor: string => string;
/**
* Returns a key for a passed item of the list.
*
* @param {string} item - The user ID.
* @returns {string} - The user ID.
*/
_keyExtractor(item) {
_keyExtractor(item: string) {
return item;
}
_onViewableItemsChanged: Object => void;
/**
* A handler for visible items changes.
*
@@ -158,7 +155,7 @@ class TileView extends PureComponent<Props> {
* @param {Array<Object>} data.viewableItems - The visible items array.
* @returns {void}
*/
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: Array<Object> }) {
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: ViewToken[]; }) {
const { _disableSelfView } = this.props;
if (viewableItems[0]?.index === 0 && !_disableSelfView) {
@@ -172,8 +169,8 @@ class TileView extends PureComponent<Props> {
}
// We are off by one in the remote participants array.
const startIndex = viewableItems[0].index - (_disableSelfView ? 0 : 1);
const endIndex = viewableItems[viewableItems.length - 1].index - (_disableSelfView ? 0 : 1);
const startIndex = Number(viewableItems[0].index) - (_disableSelfView ? 0 : 1);
const endIndex = Number(viewableItems[viewableItems.length - 1].index) - (_disableSelfView ? 0 : 1);
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
}
@@ -187,7 +184,7 @@ class TileView extends PureComponent<Props> {
render() {
const { _columns, _height, _thumbnailHeight, _width, onClick } = this.props;
const participants = this._getSortedParticipants();
const initialRowsToRender = Math.ceil(_height / (_thumbnailHeight + (2 * styles.thumbnail.margin)));
const initialRowsToRender = Math.ceil(_height / (Number(_thumbnailHeight) + (2 * styles.thumbnail.margin)));
if (this._flatListStyles.minHeight !== _height || this._flatListStyles.minWidth !== _width) {
this._flatListStyles = {
@@ -250,15 +247,13 @@ class TileView extends PureComponent<Props> {
return [ _localParticipant?.id, ..._remoteParticipants ];
}
_renderThumbnail: Object => Object;
/**
* Creates React Element to display each participant in a thumbnail.
*
* @private
* @returns {ReactElement}
*/
_renderThumbnail({ item/* , index , separators */ }) {
_renderThumbnail({ item }: { item: string; }) {
const { _thumbnailHeight } = this.props;
return (
@@ -278,18 +273,18 @@ class TileView extends PureComponent<Props> {
* @param {Object} state - The redux state.
* @param {Object} ownProps - Component props.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const responsiveUi = state['features/base/responsive-ui'];
const { remoteParticipants, tileViewDimensions } = state['features/filmstrip'];
const disableSelfView = getHideSelfView(state);
const { height } = tileViewDimensions.thumbnailSize;
const { columns } = tileViewDimensions;
const { height } = tileViewDimensions?.thumbnailSize ?? {};
const { columns } = tileViewDimensions ?? {};
return {
_aspectRatio: responsiveUi.aspectRatio,
_columns: columns,
_columns: columns ?? 1,
_disableSelfView: disableSelfView,
_height: responsiveUi.clientHeight - (ownProps.insets?.top || 0),
_insets: ownProps.insets,

View File

@@ -15,6 +15,7 @@ import { translate } from '../../../base/i18n/functions';
import Icon from '../../../base/icons/components/Icon';
import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
import { getHideSelfView } from '../../../base/settings/functions.any';
import { registerShortcut, unregisterShortcut } from '../../../keyboard-shortcuts/actions';
import { showToolbox } from '../../../toolbox/actions.web';
import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.web';
import { LAYOUTS } from '../../../video-layout/constants';
@@ -295,12 +296,12 @@ class Filmstrip extends PureComponent <IProps, IState> {
* @inheritdoc
*/
componentDidMount() {
APP.keyboardshortcut.registerShortcut(
'F',
'filmstripPopover',
this._onShortcutToggleFilmstrip,
'keyboardShortcuts.toggleFilmstrip'
);
this.props.dispatch(registerShortcut({
character: 'F',
helpDescription: 'keyboardShortcuts.toggleFilmstrip',
handler: this._onShortcutToggleFilmstrip
}));
document.addEventListener('mouseup', this._onDragMouseUp);
// @ts-ignore
@@ -313,7 +314,8 @@ class Filmstrip extends PureComponent <IProps, IState> {
* @inheritdoc
*/
componentWillUnmount() {
APP.keyboardshortcut.unregisterShortcut('F');
this.props.dispatch(unregisterShortcut('F'));
document.removeEventListener('mouseup', this._onDragMouseUp);
// @ts-ignore

View File

@@ -184,6 +184,7 @@ interface IDimensions {
}
interface IFilmstripDimensions {
columns?: number;
filmstripHeight?: number;
filmstripWidth?: number;
gridDimensions?: {

View File

@@ -1,4 +1,4 @@
import { GiphyContent, GiphyGridView, GiphyMediaType } from '@giphy/react-native-sdk';
import { GiphyContent, GiphyGridView, GiphyMediaType, GiphyRating } from '@giphy/react-native-sdk';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
@@ -9,7 +9,7 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import Input from '../../../base/ui/components/native/Input';
import { sendMessage } from '../../../chat/actions.any';
import { goBack } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { formatGifUrlMessage, getGifRating, getGifUrl, getGiphyProxyUrl } from '../../functions';
import { formatGifUrlMessage, getGifRating, getGifUrl, getGiphyProxyUrl } from '../../functions.native';
import GifsMenuFooter from './GifsMenuFooter';
import styles from './styles';
@@ -18,7 +18,7 @@ const GifsMenu = () => {
const [ searchQuery, setSearchQuery ] = useState('');
const dispatch = useDispatch();
const { t } = useTranslation();
const rating = useSelector(getGifRating);
const rating = useSelector(getGifRating) as GiphyRating;
const proxyUrl = useSelector(getGiphyProxyUrl);
const options = {

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Image, Text, View } from 'react-native';
import { Image, Text, TextStyle, View, ViewStyle } from 'react-native';
// @ts-ignore
import styles from './styles';
@@ -15,9 +14,10 @@ const GifsMenuFooter = (): JSX.Element => {
const { t } = useTranslation();
return (
<View style = { styles.credit }>
<Text
style = { styles.creditText }>{ t('poweredby') }</Text>
<View style = { styles.credit as ViewStyle }>
<Text style = { styles.creditText as TextStyle }>
{ t('poweredby') }
</Text>
<Image
source = { require('../../../../../images/GIPHY_logo.png') } />
</View>

View File

@@ -1,33 +0,0 @@
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
/**
* {@code AbstractGoogleSignInButton} Component's property types.
*/
interface IProps extends WithTranslation {
/**
* The callback to invoke when the button is clicked.
*/
onClick: (e?: React.MouseEvent) => void;
/**
* True if the user is signed in, so it needs to render a different label
* and maybe different style (for the future).
*/
signedIn?: boolean;
/**
* The text to display within {@code GoogleSignInButton}.
*/
text?: string;
}
/**
* Abstract class of the {@code GoogleSignInButton} to share platform
* independent code.
*
* @inheritdoc
*/
export default class AbstractGoogleSignInButton extends Component<IProps> {
}

View File

@@ -1,13 +1,11 @@
// @flow
import React from 'react';
import { Image, TouchableOpacity } from 'react-native';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { GestureResponderEvent, Image, ImageStyle, TouchableOpacity, ViewStyle } from 'react-native';
import { translate } from '../../base/i18n/functions';
import Button from '../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../base/ui/constants.native';
import AbstractGoogleSignInButton from './AbstractGoogleSignInButton';
import styles from './styles';
// eslint-disable-next-line
@@ -21,12 +19,31 @@ const GOOGLE_BRAND_IMAGE = require('../../../../images/btn_google_signin_dark_no
* this way), hence the custom button implementation.
*/
interface IProps extends WithTranslation {
/**
* The callback to invoke when the button is clicked.
*/
onClick: (e?: React.MouseEvent<HTMLButtonElement> | GestureResponderEvent) => void;
/**
* True if the user is signed in, so it needs to render a different label
* and maybe different style (for the future).
*/
signedIn?: boolean;
/**
* The text to display within {@code GoogleSignInButton}.
*/
text?: string;
}
/**
* A React Component showing a button to sign in with Google.
*
* @augments Component
*/
class GoogleSignInButton extends AbstractGoogleSignInButton {
class GoogleSignInButton extends Component<IProps> {
/**
* Implements React's {@link Component#render()}.
@@ -51,11 +68,11 @@ class GoogleSignInButton extends AbstractGoogleSignInButton {
return (
<TouchableOpacity
onPress = { onClick }
style = { styles.signInButton } >
style = { styles.signInButton as ViewStyle } >
<Image
resizeMode = { 'contain' }
source = { GOOGLE_BRAND_IMAGE }
style = { styles.signInImage } />
style = { styles.signInImage as ImageStyle } />
</TouchableOpacity>
);
}

View File

@@ -1,15 +1,33 @@
import React from 'react';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { translate } from '../../base/i18n/functions';
import AbstractGoogleSignInButton from './AbstractGoogleSignInButton';
interface IProps extends WithTranslation {
/**
* The callback to invoke when the button is clicked.
*/
onClick: (e?: React.MouseEvent) => void;
/**
* True if the user is signed in, so it needs to render a different label
* and maybe different style (for the future).
*/
signedIn?: boolean;
/**
* The text to display within {@code GoogleSignInButton}.
*/
text?: string;
}
/**
* A React Component showing a button to sign in with Google.
*
* @augments Component
*/
class GoogleSignInButton extends AbstractGoogleSignInButton {
class GoogleSignInButton extends Component<IProps> {
/**
* Implements React's {@link Component#render()}.

View File

@@ -1,5 +1,3 @@
// @flow
import { createStyleSheet } from '../../base/styles/functions.any';
/**

View File

@@ -68,7 +68,7 @@ export interface IProps {
/**
* Whether or not to allow sip invites.
*/
_sipInviteEnabled: boolean;
_sipInviteEnabled: boolean;
/**
* The Redux dispatch function.
@@ -92,7 +92,7 @@ export interface IState {
/**
* The list of invite items.
*/
inviteItems: Array<IInviteSelectItem>;
inviteItems: Array<IInvitee | IInviteSelectItem>;
}
/**

View File

@@ -1,7 +1,6 @@
/* eslint-disable lines-around-comment */
import _ from 'lodash';
import React, { ReactElement } from 'react';
import { WithTranslation } from 'react-i18next';
import {
ActivityIndicator,
FlatList,
@@ -23,7 +22,6 @@ import {
IconSearch,
IconShare
} from '../../../../base/icons/svg';
// @ts-ignore
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
import AvatarListItem from '../../../../base/react/components/native/AvatarListItem';
import { Item } from '../../../../base/react/types';
@@ -33,19 +31,16 @@ import HeaderNavigationButton
from '../../../../mobile/navigation/components/HeaderNavigationButton';
import { beginShareRoom } from '../../../../share-room/actions';
import { INVITE_TYPES } from '../../../constants';
import { IInviteSelectItem, IInvitee } from '../../../types';
import AbstractAddPeopleDialog, {
// @ts-ignore
type Props as AbstractProps,
// @ts-ignore
type State as AbstractState,
type IProps as AbstractProps,
type IState as AbstractState,
_mapStateToProps as _abstractMapStateToProps
} from '../AbstractAddPeopleDialog';
// @ts-ignore
import styles, { AVATAR_SIZE } from './styles';
interface IProps extends AbstractProps {
interface IProps extends AbstractProps, WithTranslation {
/**
* True if the invite dialog should be open, false otherwise.
@@ -55,12 +50,7 @@ interface IProps extends AbstractProps {
/**
* Default prop for navigation between screen components(React Navigation).
*/
navigation: Object;
/**
* Function used to translate i18n labels.
*/
t: Function;
navigation: any;
/**
* Theme used for styles.
@@ -95,9 +85,7 @@ interface IState extends AbstractState {
/**
* Implements a special dialog to invite people from a directory service.
*/
class AddPeopleDialog
// @ts-ignore
extends AbstractAddPeopleDialog<IProps, IState> {
class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
/**
* Default state object to reset the state to when needed.
*/
@@ -119,7 +107,7 @@ class AddPeopleDialog
searchTimeout: number;
/**
* Contrustor of the component.
* Constructor of the component.
*
* @inheritdoc
*/
@@ -151,7 +139,6 @@ class AddPeopleDialog
componentDidMount() {
const { navigation, t } = this.props;
// @ts-ignore
navigation.setOptions({
headerRight: () => (
<HeaderNavigationButton
@@ -171,7 +158,6 @@ class AddPeopleDialog
componentDidUpdate(prevProps: IProps) {
const { navigation, t } = this.props;
// @ts-ignore
navigation.setOptions({
// eslint-disable-next-line react/no-multi-comp
headerRight: () => (
@@ -197,12 +183,9 @@ class AddPeopleDialog
*/
render() {
const {
// @ts-ignore
_addPeopleEnabled,
// @ts-ignore
_dialOutEnabled
} = this.props;
// @ts-ignore
const { inviteItems, selectableItems } = this.state;
let placeholderKey = 'searchPlaceholder';
@@ -302,7 +285,6 @@ class AddPeopleDialog
});
// Clear search results
// @ts-ignore
this._onTypeQuery('');
}
@@ -314,10 +296,9 @@ class AddPeopleDialog
_onInvite() {
// @ts-ignore
this._invite(this.state.inviteItems)
.then((invitesLeftToSend: ArrayLike<any>) => {
.then((invitesLeftToSend: IInvitee[]) => {
if (invitesLeftToSend.length) {
this.setState({
// @ts-ignore
inviteItems: invitesLeftToSend
});
this._showFailedInviteAlert();
@@ -333,26 +314,22 @@ class AddPeopleDialog
*/
_onPressItem(item: Item) {
return () => {
// @ts-ignore
const { inviteItems } = this.state;
const finderKey = item.type === INVITE_TYPES.PHONE ? 'number' : 'user_id';
if (inviteItems.find(
// @ts-ignore
_.matchesProperty(finderKey, item[finderKey]))) {
_.matchesProperty(finderKey, item[finderKey as keyof typeof item]))) {
// Item is already selected, need to unselect it.
this.setState({
// @ts-ignore
inviteItems: inviteItems.filter(
// @ts-ignore
(element: any) => item[finderKey] !== element[finderKey])
(element: any) => item[finderKey as keyof typeof item] !== element[finderKey])
});
} else {
// Item is not selected yet, need to add to the list.
const items: Array<Object> = inviteItems.concat(item);
// @ts-ignore
const items = inviteItems.concat(item);
this.setState({
// @ts-ignore
inviteItems: _.sortBy(items, [ 'name', 'number' ])
});
}
@@ -365,12 +342,10 @@ class AddPeopleDialog
* @returns {void}
*/
_onShareMeeting() {
// @ts-ignore
if (this.state.inviteItems.length > 0) {
// The use probably intended to invite people.
this._onInvite();
} else {
// @ts-ignore
this.props.dispatch(beginShareRoom());
}
}
@@ -388,7 +363,6 @@ class AddPeopleDialog
});
clearTimeout(this.searchTimeout);
// @ts-ignore
this.searchTimeout = setTimeout(() => {
this.setState({
searchInprogress: true
@@ -462,9 +436,8 @@ class AddPeopleDialog
*/
_renderItem(flatListItem: any, index: number): ReactElement | null {
const { item } = flatListItem;
// @ts-ignore
const { inviteItems } = this.state;
let selected = false;
let selected: IInvitee | IInviteSelectItem | undefined | boolean = false;
const renderableItem = this._getRenderableItem(flatListItem);
if (!renderableItem) {
@@ -570,7 +543,6 @@ class AddPeopleDialog
* @returns {void}
*/
_showFailedInviteAlert() {
// @ts-ignore
this.props.dispatch(openDialog(AlertDialog, {
contentKey: {
key: 'inviteDialog.alertText'
@@ -583,14 +555,15 @@ class AddPeopleDialog
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {any} _ownProps - Component's own props.
* @returns {{
* _isVisible: boolean
* }}
*/
function _mapStateToProps(state: IReduxState) {
function _mapStateToProps(state: IReduxState, _ownProps: any) {
return {
..._abstractMapStateToProps(state)
};
}
// @ts-ignore
export default translate(connect(_mapStateToProps)(AddPeopleDialog));

View File

@@ -325,7 +325,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
const users = response.filter(item => userTypes.includes(item.type));
const userDisplayItems = [];
const userDisplayItems: any = [];
for (const user of users) {
const { name, phone } = user;

View File

@@ -1,11 +1,11 @@
// @flow
import { Route } from '@react-navigation/native';
import React, { PureComponent } from 'react';
import { Linking, View } from 'react-native';
import { WithTranslation } from 'react-i18next';
import { Linking, View, ViewStyle } from 'react-native';
import { WebView } from 'react-native-webview';
import { connect } from 'react-redux';
import { type Dispatch } from 'redux';
import { IStore } from '../../../../app/types';
import { openDialog } from '../../../../base/dialog/actions';
import { translate } from '../../../../base/i18n/functions';
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
@@ -15,38 +15,32 @@ import { getDialInfoPageURLForURIString } from '../../../functions';
import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
import styles, { INDICATOR_COLOR } from './styles';
interface IProps extends WithTranslation {
type Props = {
dispatch: Dispatch<any>,
dispatch: IStore['dispatch'];
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object,
navigation: any;
/**
* Default prop for navigating between screen components(React Navigation).
*/
route: Object,
/**
* Translation function.
*/
t: Function
};
route: Route<'', { summaryUrl: string; }>;
}
/**
* Implements a React native component that displays the dial in info page for a specific room.
*/
class DialInSummary extends PureComponent<Props> {
class DialInSummary extends PureComponent<IProps> {
/**
* Initializes a new instance.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._onError = this._onError.bind(this);
@@ -86,15 +80,13 @@ class DialInSummary extends PureComponent<Props> {
onShouldStartLoadWithRequest = { this._onNavigate }
renderLoading = { this._renderLoading }
setSupportMultipleWindows = { false }
source = {{ uri: getDialInfoPageURLForURIString(summaryUrl) }}
source = {{ uri: getDialInfoPageURLForURIString(summaryUrl) ?? '' }}
startInLoadingState = { true }
style = { styles.webView } />
</JitsiScreen>
);
}
_onError: () => void;
/**
* Callback to handle the error if the page fails to load.
*
@@ -104,8 +96,6 @@ class DialInSummary extends PureComponent<Props> {
this.props.dispatch(openDialog(DialInSummaryErrorDialog));
}
_onNavigate: Object => Boolean;
/**
* Callback to intercept navigation inside the webview and make the native app handle the dial requests.
*
@@ -114,7 +104,7 @@ class DialInSummary extends PureComponent<Props> {
* @param {any} request - The request object.
* @returns {boolean}
*/
_onNavigate(request) {
_onNavigate(request: { url: string; }) {
const { url } = request;
const { route } = this.props;
const summaryUrl = route.params?.summaryUrl;
@@ -126,8 +116,6 @@ class DialInSummary extends PureComponent<Props> {
return url === getDialInfoPageURLForURIString(summaryUrl);
}
_renderLoading: () => React$Component<any>;
/**
* Renders the loading indicator.
*
@@ -135,7 +123,7 @@ class DialInSummary extends PureComponent<Props> {
*/
_renderLoading() {
return (
<View style = { styles.indicatorWrapper }>
<View style = { styles.indicatorWrapper as ViewStyle }>
<LoadingIndicator
color = { INDICATOR_COLOR }
size = 'large' />

View File

@@ -1,6 +1,5 @@
// @flow
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import AlertDialog from '../../../../base/dialog/components/native/AlertDialog';
@@ -9,7 +8,7 @@ import { translate } from '../../../../base/i18n/functions';
/**
* Dialog to inform the user that we couldn't fetch the dial-in info page.
*/
class DialInSummaryErrorDialog extends Component<{}> {
class DialInSummaryErrorDialog extends Component<WithTranslation> {
/**
* Implements React's {@link Component#render()}.
*
@@ -22,8 +21,6 @@ class DialInSummaryErrorDialog extends Component<{}> {
contentKey = 'info.dialInSummaryError' />
);
}
_onSubmit: () => boolean;
}
export default translate(connect()(DialInSummaryErrorDialog));

View File

@@ -1,5 +1,3 @@
// @flow
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
export const INDICATOR_COLOR = BaseTheme.palette.ui07;

View File

@@ -31,7 +31,11 @@ const useStyles = makeStyles()((theme: Theme) => {
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
borderRadius: 6
borderRadius: 6,
'& *': {
userSelect: 'text'
}
},
confNameLabel: {
...withPixelLineHeight(theme.typography.heading6),

View File

@@ -0,0 +1,24 @@
/**
* The type of the action which signals that the keyboard shortcuts should be initialized.
*/
export const INIT_KEYBOARD_SHORTCUTS = 'INIT_KEYBOARD_SHORTCUTS';
/**
* The type of the action which signals that a keyboard shortcut should be registered.
*/
export const REGISTER_KEYBOARD_SHORTCUT = 'REGISTER_KEYBOARD_SHORTCUT';
/**
* The type of the action which signals that a keyboard shortcut should be unregistered.
*/
export const UNREGISTER_KEYBOARD_SHORTCUT = 'UNREGISTER_KEYBOARD_SHORTCUT';
/**
* The type of the action which signals that a keyboard shortcut should be enabled.
*/
export const ENABLE_KEYBOARD_SHORTCUTS = 'ENABLE_KEYBOARD_SHORTCUTS';
/**
* The type of the action which signals that a keyboard shortcut should be disabled.
*/
export const DISABLE_KEYBOARD_SHORTCUTS = 'DISABLE_KEYBOARD_SHORTCUTS';

View File

@@ -0,0 +1,60 @@
import { AnyAction } from 'redux';
import {
DISABLE_KEYBOARD_SHORTCUTS,
ENABLE_KEYBOARD_SHORTCUTS,
REGISTER_KEYBOARD_SHORTCUT,
UNREGISTER_KEYBOARD_SHORTCUT
} from './actionTypes';
import { IKeyboardShortcut } from './types';
/**
* Action to register a new shortcut.
*
* @param {IKeyboardShortcut} shortcut - The shortcut to register.
* @returns {AnyAction}
*/
export const registerShortcut = (shortcut: IKeyboardShortcut): AnyAction => {
return {
type: REGISTER_KEYBOARD_SHORTCUT,
shortcut
};
};
/**
* Action to unregister a shortcut.
*
* @param {string} character - The character of the shortcut to unregister.
* @param {boolean} altKey - Whether the shortcut used altKey.
* @returns {AnyAction}
*/
export const unregisterShortcut = (character: string, altKey = false): AnyAction => {
return {
altKey,
type: UNREGISTER_KEYBOARD_SHORTCUT,
character
};
};
/**
* Action to enable keyboard shortcuts.
*
* @returns {AnyAction}
*/
export const enableKeyboardShortcuts = (): AnyAction => {
return {
type: ENABLE_KEYBOARD_SHORTCUTS
};
};
/**
* Action to enable keyboard shortcuts.
*
* @returns {AnyAction}
*/
export const disableKeyboardShortcuts = (): AnyAction => {
return {
type: DISABLE_KEYBOARD_SHORTCUTS
};
};

View File

@@ -0,0 +1 @@
export * from './actions.any';

View File

@@ -0,0 +1,119 @@
import { batch } from 'react-redux';
import { ACTION_SHORTCUT_PRESSED, ACTION_SHORTCUT_RELEASED, createShortcutEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
import { IStore } from '../app/types';
import { clickOnVideo } from '../filmstrip/actions.web';
import { openSettingsDialog } from '../settings/actions.web';
import { SETTINGS_TABS } from '../settings/constants';
import { registerShortcut } from './actions.any';
import { areKeyboardShortcutsEnabled, getKeyboardShortcuts } from './functions';
import logger from './logger';
import { getKeyboardKey, getPriorityFocusedElement } from './utils';
export * from './actions.any';
/**
* Initialise global shortcuts.
* Global shortcuts are shortcuts for features that don't have a button or
* link associated with the action. In other words they represent actions
* triggered _only_ with a shortcut.
*
* @returns {Function}
*/
const initGlobalKeyboardShortcuts = () =>
(dispatch: IStore['dispatch']) => {
batch(() => {
dispatch(registerShortcut({
character: '?',
helpDescription: 'keyboardShortcuts.toggleShortcuts',
handler: () => {
sendAnalytics(createShortcutEvent('help'));
dispatch(openSettingsDialog(SETTINGS_TABS.SHORTCUTS, false));
}
}));
// register SPACE shortcut in two steps to insure visibility of help message
dispatch(registerShortcut({
character: ' ',
helpCharacter: 'SPACE',
helpDescription: 'keyboardShortcuts.pushToTalk',
handler: () => {
sendAnalytics(createShortcutEvent('push.to.talk', ACTION_SHORTCUT_RELEASED));
logger.log('Talk shortcut released');
APP.conference.muteAudio(true);
}
}));
dispatch(registerShortcut({
character: '0',
helpDescription: 'keyboardShortcuts.focusLocal',
handler: () => {
dispatch(clickOnVideo(0));
}
}));
Array(9).fill(1)
.forEach((_, index) => {
const num = index + 1;
dispatch(registerShortcut({
character: `${num}`,
// only show help hint for the first shortcut
helpCharacter: num === 1 ? '1-9' : undefined,
helpDescription: num === 1 ? 'keyboardShortcuts.focusRemote' : undefined,
handler: () => {
dispatch(clickOnVideo(num));
}
}));
});
});
};
/**
* Initializes keyboard shortcuts.
*
* @returns {Function}
*/
export const initKeyboardShortcuts = () =>
(dispatch: IStore['dispatch'], getState: IStore['getState']) => {
dispatch(initGlobalKeyboardShortcuts());
window.onkeyup = (e: KeyboardEvent) => {
const state = getState();
const enabled = areKeyboardShortcutsEnabled(state);
const shortcuts = getKeyboardShortcuts(state);
if (!enabled || getPriorityFocusedElement()) {
return;
}
const key = getKeyboardKey(e).toUpperCase();
if (shortcuts.has(key)) {
shortcuts.get(key)?.handler(e);
}
};
window.onkeydown = (e: KeyboardEvent) => {
const state = getState();
const enabled = areKeyboardShortcutsEnabled(state);
if (!enabled) {
return;
}
const focusedElement = getPriorityFocusedElement();
const key = getKeyboardKey(e).toUpperCase();
if (key === ' ' && !focusedElement) {
sendAnalytics(createShortcutEvent('push.to.talk', ACTION_SHORTCUT_PRESSED));
logger.log('Talk shortcut pressed');
APP.conference.muteAudio(false);
} else if (key === 'ESCAPE') {
focusedElement?.blur();
}
};
};

View File

@@ -0,0 +1,31 @@
import { IReduxState } from '../app/types';
/**
* Returns whether or not the keyboard shortcuts are enabled.
*
* @param {Object} state - The redux state.
* @returns {boolean} - Whether or not the keyboard shortcuts are enabled.
*/
export function areKeyboardShortcutsEnabled(state: IReduxState) {
return state['features/keyboard-shortcuts'].enabled;
}
/**
* Returns the keyboard shortcuts map.
*
* @param {Object} state - The redux state.
* @returns {Map} - The keyboard shortcuts map.
*/
export function getKeyboardShortcuts(state: IReduxState) {
return state['features/keyboard-shortcuts'].shortcuts;
}
/**
* Returns the keyboard shortcuts help descriptions.
*
* @param {Object} state - The redux state.
* @returns {Map} - The keyboard shortcuts help descriptions.
*/
export function getKeyboardShortcutsHelpDescriptions(state: IReduxState) {
return state['features/keyboard-shortcuts'].shortcutsHelp;
}

View File

@@ -0,0 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/keyboard-shortcuts');

View File

@@ -0,0 +1,39 @@
import { IStore } from '../app/types';
import { SET_CONFIG } from '../base/config/actionTypes';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { CAPTURE_EVENTS } from '../remote-control/actionTypes';
import { disableKeyboardShortcuts, enableKeyboardShortcuts } from './actions';
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: any) => {
const { dispatch } = store;
switch (action.type) {
case CAPTURE_EVENTS:
if (action.isCapturingEvents) {
dispatch(disableKeyboardShortcuts());
} else {
dispatch(enableKeyboardShortcuts());
}
return next(action);
case SET_CONFIG: {
const result = next(action);
const state = store.getState();
const { disableShortcuts } = state['features/base/config'];
if (disableShortcuts !== undefined) {
if (disableShortcuts) {
dispatch(disableKeyboardShortcuts());
} else {
dispatch(enableKeyboardShortcuts());
}
}
return result;
}
}
return next(action);
});

View File

@@ -0,0 +1,72 @@
import PersistenceRegistry from '../base/redux/PersistenceRegistry';
import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
DISABLE_KEYBOARD_SHORTCUTS,
ENABLE_KEYBOARD_SHORTCUTS,
REGISTER_KEYBOARD_SHORTCUT,
UNREGISTER_KEYBOARD_SHORTCUT
} from './actionTypes';
import { IKeyboardShortcutsState } from './types';
/**
* The redux subtree of this feature.
*/
const STORE_NAME = 'features/keyboard-shortcuts';
const defaultState = {
enabled: true,
shortcuts: new Map(),
shortcutsHelp: new Map()
};
PersistenceRegistry.register(STORE_NAME, {
enabled: true
});
ReducerRegistry.register<IKeyboardShortcutsState>(STORE_NAME,
(state = defaultState, action): IKeyboardShortcutsState => {
switch (action.type) {
case ENABLE_KEYBOARD_SHORTCUTS:
return {
...state,
enabled: true
};
case DISABLE_KEYBOARD_SHORTCUTS:
return {
...state,
enabled: false
};
case REGISTER_KEYBOARD_SHORTCUT: {
const shortcutKey = action.shortcut.alt ? `:${action.shortcut.character}` : action.shortcut.character;
return {
...state,
shortcuts: new Map(state.shortcuts)
.set(shortcutKey, action.shortcut),
shortcutsHelp: action.shortcut.helpDescription
? new Map(state.shortcutsHelp)
.set(action.shortcut.helpCharacter ?? shortcutKey, action.shortcut.helpDescription)
: state.shortcutsHelp
};
}
case UNREGISTER_KEYBOARD_SHORTCUT: {
const shortcutKey = action.alt ? `:${action.character}` : action.character;
const shortcuts = new Map(state.shortcuts);
shortcuts.delete(shortcutKey);
const shortcutsHelp = new Map(state.shortcutsHelp);
shortcutsHelp.delete(shortcutKey);
return {
...state,
shortcuts,
shortcutsHelp
};
}
}
return state;
});

View File

@@ -0,0 +1,23 @@
export interface IKeyboardShortcut {
// whether or not the alt key must be pressed
alt?: boolean;
// the character to be pressed that triggers the action
character: string;
// the function to be executed when the shortcut is pressed
handler: Function;
// character to be displayed in the help dialog shortcuts list
helpCharacter?: string;
// help description of the shortcut, to be displayed in the help dialog
helpDescription?: string;
}
export interface IKeyboardShortcutsState {
enabled: boolean;
shortcuts: Map<string, IKeyboardShortcut>;
shortcutsHelp: Map<string, string>;
}

View File

@@ -0,0 +1,75 @@
/**
* Prefer keyboard handling of these elements over global shortcuts.
* If a button is triggered using the Spacebar it should not trigger PTT.
* If an input element is focused and M is pressed it should not mute audio.
*/
const _elementsBlacklist = [
'input',
'textarea',
'button',
'[role=button]',
'[role=menuitem]',
'[role=radio]',
'[role=tab]',
'[role=option]',
'[role=switch]',
'[role=range]',
'[role=log]'
];
/**
* Returns the currently focused element if it is not blacklisted.
*
* @returns {HTMLElement|null} - The currently focused element.
*/
export const getPriorityFocusedElement = (): HTMLElement | null =>
document.querySelector(`:focus:is(${_elementsBlacklist.join(',')})`);
/**
* Returns the keyboard key from a KeyboardEvent.
*
* @param {KeyboardEvent} e - The KeyboardEvent.
* @returns {string} - The keyboard key.
*/
export const getKeyboardKey = (e: KeyboardEvent): string => {
// @ts-ignore
const { altKey, code, key, shiftKey, type, which } = e;
// If alt is pressed a different char can be returned so this takes
// the char from the code. It also prefixes with a colon to differentiate
// alt combo from simple keypress.
if (altKey) {
const replacedKey = code.replace('Key', '');
return `:${replacedKey}`;
}
// If e.key is a string, then it is assumed it already plainly states
// the key pressed. This may not be true in all cases, such as with Edge
// and "?", when the browser cannot properly map a key press event to a
// keyboard key. To be safe, when a key is "Unidentified" it must be
// further analyzed by jitsi to a key using e.which.
if (typeof key === 'string' && key !== 'Unidentified') {
return key;
}
if (type === 'keypress'
&& ((which >= 32 && which <= 126)
|| (which >= 160 && which <= 255))) {
return String.fromCharCode(which);
}
// try to fallback (0-9A-Za-z and QWERTY keyboard)
switch (which) {
case 27:
return 'Escape';
case 191:
return shiftKey ? '?' : '/';
}
if (shiftKey || type === 'keypress') {
return String.fromCharCode(which);
}
return String.fromCharCode(which).toLowerCase();
};

View File

@@ -1,77 +1,76 @@
// @flow
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { IReduxState, IStore } from '../../app/types';
import { JitsiTrackEvents } from '../../base/lib-jitsi-meet';
import ParticipantView from '../../base/participants/components/ParticipantView.native';
import { getParticipantById, isLocalScreenshareParticipant } from '../../base/participants/functions';
import { trackStreamingStatusChanged } from '../../base/tracks/actions.native';
import { getVideoTrackByParticipant, isLocalVideoTrackDesktop } from '../../base/tracks/functions.native';
import { ITrack } from '../../base/tracks/types';
import { AVATAR_SIZE } from './styles';
/**
* The type of the React {@link Component} props of {@link LargeVideo}.
*/
type Props = {
interface IProps {
/**
* Whether video should be disabled.
*/
_disableVideo: boolean,
_disableVideo: boolean;
/**
* Application's viewport height.
*/
_height: number,
_height: number;
/**
* The ID of the participant (to be) depicted by LargeVideo.
*
* @private
*/
_participantId: string,
_participantId: string;
/**
* The video track that will be displayed in the thumbnail.
*/
_videoTrack: ?Object,
_videoTrack?: ITrack;
/**
* Application's viewport height.
*/
_width: number,
_width: number;
/**
* Invoked to trigger state changes in Redux.
*/
dispatch: Dispatch<any>,
dispatch: IStore['dispatch'];
/**
* Callback to invoke when the {@code LargeVideo} is clicked/pressed.
*/
onClick: Function,
};
onClick?: Function;
}
/**
* The type of the React {@link Component} state of {@link LargeVideo}.
*/
type State = {
interface IState {
/**
* Size for the Avatar. It will be dynamically adjusted based on the
* available size.
*/
avatarSize: number,
avatarSize: number;
/**
* Whether the connectivity indicator will be shown or not. It will be true
* by default, but it may be turned off if there is not enough space.
*/
useConnectivityInfoLabel: boolean
};
useConnectivityInfoLabel: boolean;
}
const DEFAULT_STATE = {
avatarSize: AVATAR_SIZE,
@@ -84,14 +83,14 @@ const DEFAULT_STATE = {
*
* @augments Component
*/
class LargeVideo extends PureComponent<Props, State> {
class LargeVideo extends PureComponent<IProps, IState> {
/**
* Creates new LargeVideo component.
*
* @param {Props} props - The props of the component.
* @param {IProps} props - The props of the component.
* @returns {LargeVideo}
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this.handleTrackStreamingStatusChanged = this.handleTrackStreamingStatusChanged.bind(this);
@@ -108,7 +107,7 @@ class LargeVideo extends PureComponent<Props, State> {
*
* @inheritdoc
*/
static getDerivedStateFromProps(props: Props) {
static getDerivedStateFromProps(props: IProps) {
const { _height, _width } = props;
// Get the size, rounded to the nearest even number.
@@ -152,7 +151,7 @@ class LargeVideo extends PureComponent<Props, State> {
* @inheritdoc
* @returns {void}
*/
componentDidUpdate(prevProps: Props) {
componentDidUpdate(prevProps: IProps) {
// TODO: after converting this component to a react function component,
// use a custom hook to update local track streaming status.
const { _videoTrack, dispatch } = this.props;
@@ -200,7 +199,7 @@ class LargeVideo extends PureComponent<Props, State> {
* @param {JitsiTrackStreamingStatus} streamingStatus - The updated track streaming status.
* @returns {void}
*/
handleTrackStreamingStatusChanged(jitsiTrack, streamingStatus) {
handleTrackStreamingStatusChanged(jitsiTrack: any, streamingStatus: string) {
this.props.dispatch(trackStreamingStatusChanged(jitsiTrack, streamingStatus));
}
@@ -240,11 +239,11 @@ class LargeVideo extends PureComponent<Props, State> {
*
* @param {Object} state - Redux state.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
const { participantId } = state['features/large-video'];
const participant = getParticipantById(state, participantId);
const participant = getParticipantById(state, participantId ?? '');
const { clientHeight: height, clientWidth: width } = state['features/base/responsive-ui'];
const videoTrack = getVideoTrackByParticipant(state, participant);
let disableVideo = false;
@@ -258,7 +257,7 @@ function _mapStateToProps(state) {
return {
_disableVideo: disableVideo,
_height: height,
_participantId: participantId,
_participantId: participantId ?? '',
_videoTrack: videoTrack,
_width: width
};

View File

@@ -6,7 +6,7 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import ChatInputBar from '../../../chat/components/native/ChatInputBar';
import MessageContainer from '../../../chat/components/native/MessageContainer';
import AbstractLobbyScreen, {
Props as AbstractProps,
IProps as AbstractProps,
_mapStateToProps as abstractMapStateToProps
} from '../AbstractLobbyScreen';
@@ -30,6 +30,7 @@ class LobbyChatScreen extends
return (
<JitsiScreen style = { styles.lobbyChatWrapper }>
{/* @ts-ignore */}
<MessageContainer messages = { _lobbyChatMessages } />
<ChatInputBar onSend = { this._onSendMessage } />
</JitsiScreen>

View File

@@ -1,7 +1,8 @@
import React, { ReactElement } from 'react';
import { Text, View } from 'react-native';
import React from 'react';
import { Text, TextStyle, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { getConferenceName } from '../../../base/conference/functions';
import { translate } from '../../../base/i18n/functions';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
@@ -20,29 +21,28 @@ import { preJoinStyles } from '../../../prejoin/components/native/styles';
import AudioMuteButton from '../../../toolbox/components/AudioMuteButton';
import VideoMuteButton from '../../../toolbox/components/VideoMuteButton';
import AbstractLobbyScreen, {
Props as AbstractProps,
IProps as AbstractProps,
_mapStateToProps as abstractMapStateToProps } from '../AbstractLobbyScreen';
import styles from './styles';
type Props = AbstractProps & {
interface IProps extends AbstractProps {
/**
* The current aspect ratio of the screen.
*/
_aspectRatio: Symbol,
_aspectRatio: Symbol;
/**
* The room name.
*/
_roomName: string
_roomName: string;
}
/**
* Implements a waiting screen that represents the participant being in the lobby.
*/
class LobbyScreen extends AbstractLobbyScreen<Props> {
class LobbyScreen extends AbstractLobbyScreen<IProps> {
/**
* Implements {@code PureComponent#render}.
*
@@ -70,7 +70,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
style = { contentWrapperStyles }>
<BrandingImageBackground />
<View style = { largeVideoContainerStyles }>
<View style = { preJoinStyles.displayRoomNameBackdrop }>
<View style = { preJoinStyles.displayRoomNameBackdrop as ViewStyle }>
<Text
numberOfLines = { 1 }
style = { preJoinStyles.preJoinRoomName }>
@@ -79,7 +79,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
</View>
<LargeVideo />
</View>
<View style = { contentContainerStyles }>
<View style = { contentContainerStyles as ViewStyle }>
{ this._renderToolbarButtons() }
{ this._renderContent() }
</View>
@@ -87,32 +87,6 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
);
}
_getScreenTitleKey: () => string;
_onAskToJoin: () => void;
_onCancel: () => boolean;
_onChangeDisplayName: Object => void;
_onChangeEmail: Object => void;
_onChangePassword: Object => void;
_onEnableEdit: () => void;
_onJoinWithPassword: () => void;
_onSwitchToKnockMode: () => void;
_onSwitchToPasswordMode: () => void;
_renderContent: () => ReactElement;
_renderToolbarButtons: () => ReactElement;
_onNavigateToLobbyChat: () => void;
/**
* Navigates to the lobby chat screen.
*
@@ -137,7 +111,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
<LoadingIndicator
color = { BaseTheme.palette.icon01 }
style = { styles.loadingIndicator } />
<Text style = { styles.joiningMessage }>
<Text style = { styles.joiningMessage as TextStyle }>
{ this.props.t('lobby.joiningMessage') }
</Text>
{ this._renderStandardButtons() }
@@ -183,7 +157,6 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
return (
<Input
autoCapitalize = 'none'
autoCompleteType = 'off'
customStyles = {{ input: styles.customInput }}
error = { _passwordJoinFailed }
onChange = { this._onChangePassword }
@@ -225,7 +198,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
*/
_renderToolbarButtons() {
return (
<View style = { preJoinStyles.toolboxContainer }>
<View style = { preJoinStyles.toolboxContainer as ViewStyle }>
<AudioMuteButton
styles = { preJoinStyles.buttonStylesBorderless } />
<VideoMuteButton
@@ -244,7 +217,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
const { displayName } = this.state;
return (
<View style = { styles.formWrapper }>
<View style = { styles.formWrapper as ViewStyle }>
{
_knocking && _isLobbyChatActive
&& <Button
@@ -282,14 +255,14 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @param {IProps} ownProps - The own props of the component.
* @returns {{
* _aspectRatio: Symbol
* }}
*/
function _mapStateToProps(state: Object, ownProps: Props) {
function _mapStateToProps(state: IReduxState) {
return {
...abstractMapStateToProps(state, ownProps),
...abstractMapStateToProps(state),
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
_roomName: getConferenceName(state)
};

View File

@@ -1,6 +1,5 @@
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
export default {
lobbyChatWrapper: {

View File

@@ -82,6 +82,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: IProps) =
name = { screen.welcome.main }
options = { welcomeScreenOptions } />
<RootStack.Screen
// @ts-ignore
component = { DialInSummary }
name = { screen.dialInSummary }
options = { dialInSummaryScreenOptions } />

View File

@@ -10,7 +10,7 @@ export const lobbyNavigationContainerRef = React.createRef<NavigationContainerRe
* @param {Object} params - Params to pass to the destination route.
* @returns {Function}
*/
export function navigate(name: string, params: Object) {
export function navigate(name: string, params?: Object) {
return lobbyNavigationContainerRef.current?.navigate(name, params);
}

Some files were not shown because too many files have changed in this diff Show More