mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-11 01:00:20 +00:00
Compare commits
1 Commits
android-ap
...
android-23
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d53dce1eb |
@@ -19,7 +19,7 @@ buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "31.0.0"
|
||||
compileSdkVersion = 32
|
||||
minSdkVersion = 24
|
||||
minSdkVersion = 23
|
||||
targetSdkVersion = 32
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
|
||||
@@ -26,5 +26,5 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
||||
appVersion=99.0.0
|
||||
sdkVersion=99.0.0
|
||||
appVersion=23.0.0
|
||||
sdkVersion=8.0.0
|
||||
|
||||
2
app.js
2
app.js
@@ -18,6 +18,7 @@ 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.
|
||||
@@ -37,6 +38,7 @@ window.APP = {
|
||||
'index.loaded': window.indexLoadedTime
|
||||
},
|
||||
|
||||
keyboardshortcut,
|
||||
translation,
|
||||
UI
|
||||
};
|
||||
|
||||
@@ -140,7 +140,6 @@ 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';
|
||||
@@ -2309,7 +2308,10 @@ export default {
|
||||
|
||||
APP.UI.initConference();
|
||||
|
||||
dispatch(initKeyboardShortcuts());
|
||||
if (!config.disableShortcuts) {
|
||||
APP.keyboardshortcut.init();
|
||||
}
|
||||
|
||||
dispatch(conferenceJoined(room));
|
||||
|
||||
const jwt = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
@@ -1406,7 +1406,6 @@ var config = {
|
||||
disableAGC
|
||||
disableAP
|
||||
disableHPF
|
||||
disableLocalStats
|
||||
disableNS
|
||||
enableTalkWhileMuted
|
||||
forceJVB121Ratio
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
a:active {
|
||||
color: black;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-corner {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -108,10 +108,6 @@
|
||||
#largeVideoContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#largeVideoWrapper {
|
||||
|
||||
@@ -57,10 +57,6 @@
|
||||
line-height: 24px;
|
||||
border-collapse: collapse;
|
||||
|
||||
* {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
thead {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -84,12 +84,8 @@ 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
6
globals.d.ts
vendored
@@ -10,6 +10,12 @@ declare global {
|
||||
API: any;
|
||||
conference: any;
|
||||
debugLogs: any;
|
||||
keyboardshortcut: {
|
||||
registerShortcut: Function;
|
||||
unregisterShortcut: Function;
|
||||
openDialog: Function;
|
||||
enable: Function;
|
||||
}
|
||||
};
|
||||
const interfaceConfig: any;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>23.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>23.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>23.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>23.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>8.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>99.0.0</string>
|
||||
<string>8.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -675,7 +675,6 @@
|
||||
"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",
|
||||
|
||||
@@ -115,11 +115,7 @@ import { muteAllParticipants } from '../../react/features/video-menu/actions';
|
||||
import { setVideoQuality } from '../../react/features/video-quality/actions';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
|
||||
import {
|
||||
API_ID,
|
||||
ASSUMED_BANDWIDTH_BPS,
|
||||
ENDPOINT_TEXT_MESSAGE_NAME
|
||||
} from './constants';
|
||||
import { API_ID, ENDPOINT_TEXT_MESSAGE_NAME } from './constants';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
@@ -314,23 +310,6 @@ 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');
|
||||
|
||||
|
||||
@@ -15,10 +15,3 @@ 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;
|
||||
|
||||
1
modules/API/external/external_api.js
vendored
1
modules/API/external/external_api.js
vendored
@@ -59,7 +59,6 @@ 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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
261
modules/keyboardshortcut/keyboardshortcut.js
Normal file
261
modules/keyboardshortcut/keyboardshortcut.js
Normal file
@@ -0,0 +1,261 @@
|
||||
/* 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
45
package-lock.json
generated
@@ -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/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1620.0.0+7f0012f7/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/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-0LK5EMCvtsAEQkjkrWzyniTl8BKtshAZxI3e9hGnA/RVDuULLXre7GEWd2zCP3tCfdxclkhqt7skQpfNhCTdqg==",
|
||||
"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==",
|
||||
"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.5.0",
|
||||
"strophe.js": "1.6.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,27 +17615,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/strophe.js": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.5.0.tgz",
|
||||
"integrity": "sha512-H5tE/tZxPR5xP3jhXyQwsjnMSwQMf7vrn9r1OkufTApyGHYe8WjzhsfxtL3AFhVu7vFjXPPZBrmUOTm1ccYgOA==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.6.0.tgz",
|
||||
"integrity": "sha512-LE2B6nEJNUbF2Cl/p1tLIsXVJ9l86B/Z12HYYiO3n92VwYkhJ/5vJ+1ZMdwP9eN9GP8a3nbqfS5zE9umcK0FdA==",
|
||||
"dependencies": {
|
||||
"abab": "^2.0.3",
|
||||
"karma-rollup-preprocessor": "^7.0.8"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@xmldom/xmldom": "0.8.2",
|
||||
"@xmldom/xmldom": "0.8.3",
|
||||
"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",
|
||||
@@ -29117,8 +29108,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-0LK5EMCvtsAEQkjkrWzyniTl8BKtshAZxI3e9hGnA/RVDuULLXre7GEWd2zCP3tCfdxclkhqt7skQpfNhCTdqg==",
|
||||
"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==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
@@ -29134,7 +29125,7 @@
|
||||
"patch-package": "6.5.1",
|
||||
"promise.allsettled": "1.0.4",
|
||||
"sdp-transform": "2.3.0",
|
||||
"strophe.js": "1.5.0",
|
||||
"strophe.js": "1.6.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",
|
||||
@@ -32845,22 +32836,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"strophe.js": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.5.0.tgz",
|
||||
"integrity": "sha512-H5tE/tZxPR5xP3jhXyQwsjnMSwQMf7vrn9r1OkufTApyGHYe8WjzhsfxtL3AFhVu7vFjXPPZBrmUOTm1ccYgOA==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.6.0.tgz",
|
||||
"integrity": "sha512-LE2B6nEJNUbF2Cl/p1tLIsXVJ9l86B/Z12HYYiO3n92VwYkhJ/5vJ+1ZMdwP9eN9GP8a3nbqfS5zE9umcK0FdA==",
|
||||
"requires": {
|
||||
"@xmldom/xmldom": "0.8.2",
|
||||
"@xmldom/xmldom": "0.8.7",
|
||||
"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",
|
||||
|
||||
@@ -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/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1620.0.0+7f0012f7/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": {
|
||||
|
||||
@@ -129,6 +129,14 @@ 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));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ 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';
|
||||
|
||||
@@ -3,7 +3,6 @@ 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';
|
||||
|
||||
@@ -43,7 +43,6 @@ 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';
|
||||
@@ -134,7 +133,6 @@ 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;
|
||||
|
||||
@@ -3,7 +3,6 @@ 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';
|
||||
@@ -451,12 +450,11 @@ export function conferenceUniqueIdSet(conference: IJitsiConference) {
|
||||
*/
|
||||
export function _conferenceWillJoin(conference: IJitsiConference) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const localTracks
|
||||
= getLocalTracks(state['features/base/tracks'])
|
||||
= getLocalTracks(getState()['features/base/tracks'])
|
||||
.map(t => t.jitsiTrack);
|
||||
|
||||
if (localTracks.length && !iAmVisitor(state)) {
|
||||
if (localTracks.length) {
|
||||
_addLocalTracksToConference(conference, localTracks);
|
||||
}
|
||||
|
||||
@@ -808,7 +806,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;
|
||||
|
||||
@@ -308,7 +308,6 @@ 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),
|
||||
@@ -339,7 +338,6 @@ 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)
|
||||
};
|
||||
|
||||
@@ -104,7 +104,6 @@ export interface IJitsiConference {
|
||||
sendTextMessage: Function;
|
||||
sendTones: Function;
|
||||
sessionId: string;
|
||||
setAssumedBandwidthBps: (value: number) => void;
|
||||
setDesktopSharingFrameRate: Function;
|
||||
setDisplayName: Function;
|
||||
setLocalParticipantProperty: Function;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 abstract class ExpandedLabel<P extends IProps> extends Component<P, IState> {
|
||||
export default class ExpandedLabel<P extends IProps> extends Component<P, IState> {
|
||||
/**
|
||||
* Instantiates a new {@code ExpandedLabel} instance.
|
||||
*
|
||||
@@ -85,7 +85,7 @@ export default abstract class ExpandedLabel<P extends IProps> extends Component<
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
abstract _getLabel(): string;
|
||||
_getLabel: () => string;
|
||||
|
||||
/**
|
||||
* Defines the color of the expanded label. This function returns a default
|
||||
|
||||
@@ -48,7 +48,7 @@ export interface IProps {
|
||||
* The style (as in stylesheet) to be applied to this
|
||||
* {@code AbstractContainer}.
|
||||
*/
|
||||
style?: StyleType | StyleType[];
|
||||
style?: StyleType;
|
||||
|
||||
tabIndex?: number;
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ interface IProps {
|
||||
* prop of the native component.
|
||||
*/
|
||||
size?: 'large' | 'small' | 'medium';
|
||||
|
||||
style?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.state.loading ? this.props.loadingMessage : this.props.noMatchesFound || '';
|
||||
const noMatchesFound = 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: [],
|
||||
items: filterValue ? this.state.items : [],
|
||||
loading: Boolean(filterValue)
|
||||
});
|
||||
if (filterValue) {
|
||||
|
||||
@@ -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 ?? {};
|
||||
|
||||
@@ -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) {
|
||||
if (Array.isArray(style)) {
|
||||
const _style = {};
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ interface IProps {
|
||||
selectedItems?: MultiSelectItem[];
|
||||
}
|
||||
|
||||
const MULTI_SELECT_HEIGHT = 200;
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
@@ -43,7 +41,7 @@ const useStyles = makeStyles()(theme => {
|
||||
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
zIndex: 2,
|
||||
maxHeight: `${MULTI_SELECT_HEIGHT}px`,
|
||||
maxHeight: '400px',
|
||||
overflowY: 'auto',
|
||||
padding: '0'
|
||||
},
|
||||
@@ -130,7 +128,7 @@ const MultiSelect = ({
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
: <div className = { classes.listItem }>{noMatchesText}</div>
|
||||
: <div>{noMatchesText}</div>
|
||||
}
|
||||
</div>
|
||||
), [ items ]);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable lines-around-comment, max-len */
|
||||
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { navigate }
|
||||
// @ts-ignore
|
||||
from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
@@ -22,7 +21,7 @@ export * from './actions.any';
|
||||
* type: OPEN_CHAT
|
||||
* }}
|
||||
*/
|
||||
export function openChat(participant: IParticipant | undefined | Object, disablePolls?: boolean) {
|
||||
export function openChat(participant: Object, disablePolls?: boolean) {
|
||||
if (disablePolls) {
|
||||
navigate(screen.conference.chat);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Platform, ViewStyle } from 'react-native';
|
||||
import { Platform } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
@@ -11,42 +10,47 @@ import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Callback to invoke on message send.
|
||||
*/
|
||||
onSend: Function;
|
||||
}
|
||||
onSend: Function,
|
||||
|
||||
interface IState {
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* 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<IProps, IState> {
|
||||
class ChatInputBar extends Component<Props, State> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -72,7 +76,7 @@ class ChatInputBar extends Component<IProps, IState> {
|
||||
style = { [
|
||||
styles.inputBar,
|
||||
this.state.addPadding ? styles.extraBarPadding : null
|
||||
] as ViewStyle[] }>
|
||||
] }>
|
||||
<Input
|
||||
blurOnSubmit = { false }
|
||||
customStyles = {{ container: styles.customInputContainer }}
|
||||
@@ -94,26 +98,30 @@ class ChatInputBar extends Component<IProps, IState> {
|
||||
);
|
||||
}
|
||||
|
||||
_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: string) {
|
||||
_onChangeText(text) {
|
||||
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: boolean) {
|
||||
_onFocused(focused) {
|
||||
return () => {
|
||||
Platform.OS === 'android' && this.setState({
|
||||
addPadding: focused
|
||||
@@ -121,6 +129,8 @@ class ChatInputBar extends Component<IProps, IState> {
|
||||
};
|
||||
}
|
||||
|
||||
_onSubmit: () => void;
|
||||
|
||||
/**
|
||||
* Callback to handle the submit event of the text field.
|
||||
*
|
||||
@@ -1,15 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Text, View, ViewStyle } from 'react-native';
|
||||
import { Text, View } 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.native';
|
||||
import { isGifMessage } from '../../../gifs/functions';
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import { replaceNonUnicodeEmojis } from '../../functions';
|
||||
import AbstractChatMessage, { IProps } from '../AbstractChatMessage';
|
||||
import AbstractChatMessage, { type Props } from '../AbstractChatMessage';
|
||||
|
||||
import GifMessage from './GifMessage';
|
||||
import PrivateMessageButton from './PrivateMessageButton';
|
||||
@@ -19,7 +18,7 @@ import styles from './styles';
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*/
|
||||
class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
class ChatMessage extends AbstractChatMessage<Props> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
@@ -32,18 +31,18 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
|
||||
// Style arrays that need to be updated in various scenarios, such as
|
||||
// error messages or others.
|
||||
const detailsWrapperStyle: ViewStyle[] = [
|
||||
styles.detailsWrapper as ViewStyle
|
||||
const detailsWrapperStyle = [
|
||||
styles.detailsWrapper
|
||||
];
|
||||
const messageBubbleStyle: ViewStyle[] = [
|
||||
styles.messageBubble as ViewStyle
|
||||
const messageBubbleStyle = [
|
||||
styles.messageBubble
|
||||
];
|
||||
|
||||
if (localMessage) {
|
||||
// This is a message sent by the local participant.
|
||||
|
||||
// The wrapper needs to be aligned to the right.
|
||||
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper as ViewStyle);
|
||||
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper);
|
||||
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(styles.localMessageBubble);
|
||||
@@ -70,11 +69,11 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
const messageText = replaceNonUnicodeEmojis(this._getMessageText());
|
||||
|
||||
return (
|
||||
<View style = { styles.messageWrapper as ViewStyle } >
|
||||
<View style = { styles.messageWrapper } >
|
||||
{ this._renderAvatar() }
|
||||
<View style = { detailsWrapperStyle }>
|
||||
<View style = { messageBubbleStyle }>
|
||||
<View style = { styles.textWrapper as ViewStyle } >
|
||||
<View style = { styles.textWrapper } >
|
||||
{ this._renderDisplayName() }
|
||||
{ isGifMessage(messageText)
|
||||
? <GifMessage message = { messageText } />
|
||||
@@ -95,6 +94,12 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
);
|
||||
}
|
||||
|
||||
_getFormattedTimestamp: () => string;
|
||||
|
||||
_getMessageText: () => string;
|
||||
|
||||
_getPrivateNoticeMessage: () => string;
|
||||
|
||||
/**
|
||||
* Renders the avatar of the sender.
|
||||
*
|
||||
@@ -166,7 +171,7 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.replyContainer as ViewStyle }>
|
||||
<View style = { styles.replyContainer }>
|
||||
<PrivateMessageButton
|
||||
isLobbyMessage = { lobbyChat }
|
||||
participantID = { message.id }
|
||||
@@ -199,9 +204,9 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {IProps}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
knocking: state['features/lobby'].knocking
|
||||
};
|
||||
@@ -1,29 +1,28 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
import { IMessage } from '../../reducer';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
|
||||
interface IProps {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The messages array to render.
|
||||
*/
|
||||
messages: Array<IMessage>;
|
||||
/**
|
||||
* The messages array to render.
|
||||
*/
|
||||
messages: Array<Object>
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a container to render all the chat messages in a conference.
|
||||
*/
|
||||
export default class ChatMessageGroup extends Component<IProps> {
|
||||
export default class ChatMessageGroup extends Component<Props> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
@@ -45,25 +44,29 @@ export default class ChatMessageGroup extends Component<IProps> {
|
||||
);
|
||||
}
|
||||
|
||||
_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: Object, index: number) {
|
||||
_keyExtractor(item, index) {
|
||||
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 }: { index: number; item: IMessage; }) {
|
||||
_renderMessage({ index, item: message }) {
|
||||
return (
|
||||
<ChatMessage
|
||||
message = { message }
|
||||
@@ -1,26 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Image, ImageStyle, View } from 'react-native';
|
||||
import { Image, View } from 'react-native';
|
||||
|
||||
import { GIF_PREFIX } from '../../../gifs/constants';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The formatted gif message.
|
||||
*/
|
||||
message: string;
|
||||
message: string
|
||||
}
|
||||
|
||||
const GifMessage = ({ message }: IProps) => {
|
||||
const GifMessage = ({ message }: Props) => {
|
||||
const url = message.substring(GIF_PREFIX.length, message.length - 1);
|
||||
|
||||
return (<View
|
||||
style = { styles.gifContainer }>
|
||||
<Image
|
||||
source = {{ uri: url }}
|
||||
style = { styles.gifImage as ImageStyle } />
|
||||
style = { styles.gifImage } />
|
||||
</View>);
|
||||
};
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import React from 'react';
|
||||
import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IMessage } from '../../reducer';
|
||||
import AbstractMessageContainer, { IProps as AbstractProps } from '../AbstractMessageContainer';
|
||||
import AbstractMessageContainer, { type Props as AbstractProps }
|
||||
from '../AbstractMessageContainer';
|
||||
|
||||
import ChatMessageGroup from './ChatMessageGroup';
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
type Props = 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<IProps, any> {
|
||||
class MessageContainer extends AbstractMessageContainer<Props> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
@@ -57,18 +57,24 @@ class MessageContainer extends AbstractMessageContainer<IProps, any> {
|
||||
);
|
||||
}
|
||||
|
||||
_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: Object, index: number) {
|
||||
_keyExtractor(item, index) {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
_renderListEmptyComponent: () => ReactElement;
|
||||
|
||||
/**
|
||||
* Renders a message when there are no messages in the chat yet.
|
||||
*
|
||||
@@ -78,21 +84,23 @@ class MessageContainer extends AbstractMessageContainer<IProps, any> {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.emptyComponentWrapper as ViewStyle }>
|
||||
<Text style = { styles.emptyComponentText as TextStyle }>
|
||||
<View style = { styles.emptyComponentWrapper }>
|
||||
<Text style = { styles.emptyComponentText }>
|
||||
{ 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 }: { item: IMessage[]; }) {
|
||||
_renderMessageGroup({ item: messages }) {
|
||||
return <ChatMessageGroup messages = { messages } />;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,57 @@
|
||||
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 interface IProps extends AbstractButtonProps {
|
||||
export type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* True if message is a lobby chat message.
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
_isLobbyMessage: boolean;
|
||||
|
||||
/**
|
||||
* True if the polls feature is disabled.
|
||||
*/
|
||||
_isPollsDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* The participant object retrieved from Redux.
|
||||
*/
|
||||
_participant?: IParticipant;
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant that the message is to be sent.
|
||||
*/
|
||||
participantID: string;
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* True if the button is rendered as a reply button.
|
||||
*/
|
||||
reply: boolean;
|
||||
}
|
||||
reply: boolean,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* True if the polls feature is disabled.
|
||||
*/
|
||||
_isPollsDisabled: boolean,
|
||||
|
||||
/**
|
||||
* True if message is a lobby chat message.
|
||||
*/
|
||||
_isLobbyMessage: boolean,
|
||||
|
||||
/**
|
||||
* The participant object retrieved from Redux.
|
||||
*/
|
||||
_participant: Object,
|
||||
};
|
||||
|
||||
/**
|
||||
* Class to render a button that initiates the sending of a private message through chat.
|
||||
* Class to render a button that initiates the sending of a private message through chet.
|
||||
*/
|
||||
class PrivateMessageButton extends AbstractButton<IProps, any> {
|
||||
class PrivateMessageButton extends AbstractButton<Props, any> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.privateMessage';
|
||||
icon = IconMessage;
|
||||
label = 'toolbar.privateMessage';
|
||||
@@ -91,10 +99,10 @@ class PrivateMessageButton extends AbstractButton<IProps, any> {
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {IProps}
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
const { visible = enabled, isLobbyMessage, participantID } = ownProps;
|
||||
@@ -90,8 +90,6 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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);
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Deploy-specific configuration constants.
|
||||
*/
|
||||
|
||||
export const IFRAME_EMBED_ALLOWED_LOCATIONS = [];
|
||||
@@ -1,11 +1,8 @@
|
||||
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 { redirectToStaticPage } from '../app/actions.any';
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import { IStore } from '../app/types';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
@@ -18,22 +15,17 @@ 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 { isVpaasMeeting } from '../jaas/functions';
|
||||
import { hideNotification, showNotification, showWarningNotification } from '../notifications/actions';
|
||||
import { hideNotification, showNotification } from '../notifications/actions';
|
||||
import {
|
||||
CALENDAR_NOTIFICATION_ID,
|
||||
NOTIFICATION_ICON,
|
||||
@@ -44,7 +36,6 @@ 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;
|
||||
@@ -164,49 +155,6 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
import { StyleProp, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -10,10 +12,12 @@ 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,
|
||||
@@ -46,12 +50,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.
|
||||
@@ -66,7 +70,7 @@ type IProps = AbstractProps & {
|
||||
/**
|
||||
* Icon style override.
|
||||
*/
|
||||
iconStyle?: any;
|
||||
iconStyle: any;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
@@ -123,10 +127,12 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
|
||||
_isVirtualScreenshareParticipant,
|
||||
_isConnectionStatusInactive,
|
||||
_isConnectionStatusInterrupted
|
||||
// @ts-ignore
|
||||
} = this.props;
|
||||
const {
|
||||
showIndicator,
|
||||
stats
|
||||
// @ts-ignore
|
||||
} = this.state;
|
||||
const { percent } = stats;
|
||||
|
||||
@@ -161,8 +167,10 @@ 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>
|
||||
);
|
||||
@@ -177,7 +185,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
const { participantId } = ownProps;
|
||||
const tracks = state['features/base/tracks'];
|
||||
const participant = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
|
||||
@@ -204,4 +212,5 @@ export function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
export default connect(_mapStateToProps)(ConnectionIndicator);
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface IProps {
|
||||
/**
|
||||
* Implements an abstract class for the RaisedHandIndicator component.
|
||||
*/
|
||||
export default abstract class AbstractRaisedHandIndicator<P extends IProps>
|
||||
export default class AbstractRaisedHandIndicator<P extends IProps>
|
||||
extends Component<P> {
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ export default abstract class AbstractRaisedHandIndicator<P extends IProps>
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
abstract _renderIndicator(): React.ReactElement;
|
||||
_renderIndicator: () => React.ReactElement;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { IconMicSlash } from '../../../base/icons/svg';
|
||||
@@ -1,9 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { FlatList, ViewStyle, ViewToken } from 'react-native';
|
||||
import { FlatList } 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';
|
||||
@@ -23,54 +24,54 @@ import styles from './styles';
|
||||
|
||||
|
||||
// Immutable reference to avoid re-renders.
|
||||
const NO_REMOTE_VIDEOS: any[] = [];
|
||||
const NO_REMOTE_VIDEOS = [];
|
||||
|
||||
/**
|
||||
* Filmstrip component's property types.
|
||||
*/
|
||||
interface IProps {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
_aspectRatio: Symbol;
|
||||
_aspectRatio: Symbol,
|
||||
|
||||
_clientHeight: number;
|
||||
_clientWidth: number,
|
||||
|
||||
_clientWidth: number;
|
||||
_clientHeight: number,
|
||||
|
||||
/**
|
||||
* Whether or not to hide the self view.
|
||||
*/
|
||||
_disableSelfView: boolean;
|
||||
|
||||
_localParticipantId: string;
|
||||
|
||||
/**
|
||||
* The participants in the conference.
|
||||
*/
|
||||
_participants: Array<any>;
|
||||
_disableSelfView: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the toolbox is displayed.
|
||||
*/
|
||||
_toolboxVisible: Boolean;
|
||||
_toolboxVisible: Boolean,
|
||||
|
||||
_localParticipantId: string,
|
||||
|
||||
/**
|
||||
* The participants in the conference.
|
||||
*/
|
||||
_participants: Array<any>,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is visible.
|
||||
*/
|
||||
_visible: boolean;
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to trigger state changes in Redux.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Object containing the safe area insets.
|
||||
*/
|
||||
insets: Object;
|
||||
}
|
||||
insets: Object,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which represents the filmstrip on
|
||||
@@ -78,7 +79,7 @@ interface IProps {
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class Filmstrip extends PureComponent<IProps> {
|
||||
class Filmstrip extends PureComponent<Props> {
|
||||
/**
|
||||
* Whether the local participant should be rendered separately from the
|
||||
* remote participants ie outside of their {@link ScrollView}.
|
||||
@@ -95,7 +96,7 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// XXX Our current design is to have the local participant separate from
|
||||
@@ -128,13 +129,15 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
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: string) {
|
||||
_keyExtractor(item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -163,14 +166,16 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
});
|
||||
}
|
||||
|
||||
_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: string[] | null | undefined, index: number) {
|
||||
_getItemLayout(data, index) {
|
||||
const { _aspectRatio } = this.props;
|
||||
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
|
||||
const length = isNarrowAspectRatio ? styles.thumbnail.width : styles.thumbnail.height;
|
||||
@@ -182,6 +187,8 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
};
|
||||
}
|
||||
|
||||
_onViewableItemsChanged: Object => void;
|
||||
|
||||
/**
|
||||
* A handler for visible items changes.
|
||||
*
|
||||
@@ -189,7 +196,7 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
* @param {Array<Object>} data.viewableItems - The visible items array.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: ViewToken[]; }) {
|
||||
_onViewableItemsChanged({ viewableItems = [] }) {
|
||||
const { _disableSelfView } = this.props;
|
||||
|
||||
if (!this._separateLocalThumbnail && !_disableSelfView && viewableItems[0]?.index === 0) {
|
||||
@@ -202,8 +209,8 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
return;
|
||||
}
|
||||
|
||||
let startIndex = Number(viewableItems[0].index);
|
||||
let endIndex = Number(viewableItems[viewableItems.length - 1].index);
|
||||
let startIndex = viewableItems[0].index;
|
||||
let endIndex = viewableItems[viewableItems.length - 1].index;
|
||||
|
||||
if (!this._separateLocalThumbnail && !_disableSelfView) {
|
||||
// We are off by one in the remote participants array.
|
||||
@@ -214,13 +221,15 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
|
||||
}
|
||||
|
||||
_renderThumbnail: Object => Object;
|
||||
|
||||
/**
|
||||
* Creates React Element to display each participant in a thumbnail.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderThumbnail({ item }: { item: string; }) {
|
||||
_renderThumbnail({ item /* , index , separators */ }) {
|
||||
return (
|
||||
<Thumbnail
|
||||
key = { item }
|
||||
@@ -268,9 +277,9 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView // @ts-ignore
|
||||
<SafeAreaView
|
||||
edges = { [ bottomEdge && 'bottom', 'left', 'right' ].filter(Boolean) }
|
||||
style = { filmstripStyle as ViewStyle }>
|
||||
style = { filmstripStyle }>
|
||||
{
|
||||
this._separateLocalThumbnail
|
||||
&& !isNarrowAspectRatio
|
||||
@@ -308,9 +317,9 @@ class Filmstrip extends PureComponent<IProps> {
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
function _mapStateToProps(state) {
|
||||
const { enabled, remoteParticipants } = state['features/filmstrip'];
|
||||
const disableSelfView = getHideSelfView(state);
|
||||
const showRemoteVideos = shouldRemoteVideosBeVisible(state);
|
||||
@@ -321,7 +330,7 @@ function _mapStateToProps(state: IReduxState) {
|
||||
_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)
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import Thumbnail from './Thumbnail';
|
||||
import styles from './styles';
|
||||
@@ -12,7 +14,7 @@ import styles from './styles';
|
||||
*/
|
||||
export default function LocalThumbnail() {
|
||||
return (
|
||||
<View style = { styles.localThumbnail as ViewStyle }>
|
||||
<View style = { styles.localThumbnail }>
|
||||
<Thumbnail />
|
||||
</View>
|
||||
);
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IconPin } from '../../../base/icons/svg';
|
||||
@@ -1,11 +1,12 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { View } 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';
|
||||
|
||||
@@ -17,7 +18,7 @@ import styles from './styles';
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class RaisedHandIndicator extends AbstractRaisedHandIndicator<IProps> {
|
||||
class RaisedHandIndicator extends AbstractRaisedHandIndicator<Props> {
|
||||
/**
|
||||
* Renders the platform specific indicator element.
|
||||
*
|
||||
@@ -25,7 +26,7 @@ class RaisedHandIndicator extends AbstractRaisedHandIndicator<IProps> {
|
||||
*/
|
||||
_renderIndicator() {
|
||||
return (
|
||||
<View style = { styles.raisedHandIndicator as ViewStyle }>
|
||||
<View style = { styles.raisedHandIndicator }>
|
||||
<BaseIndicator
|
||||
icon = { IconRaiseHand }
|
||||
iconStyle = { styles.raisedHandIcon } />
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IconScreenshare } from '../../../base/icons/svg';
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Image, ImageStyle, View, ViewStyle } from 'react-native';
|
||||
import { Image, View } 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,16 +18,14 @@ 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.native';
|
||||
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
|
||||
import {
|
||||
showConnectionStatus,
|
||||
showContextMenuDetails,
|
||||
@@ -46,42 +44,42 @@ import styles, { AVATAR_SIZE } from './styles';
|
||||
/**
|
||||
* Thumbnail component's property types.
|
||||
*/
|
||||
interface IProps {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether local audio (microphone) is muted or not.
|
||||
*/
|
||||
_audioMuted: boolean;
|
||||
|
||||
/**
|
||||
* The type of participant if the participant is fake.
|
||||
*/
|
||||
_fakeParticipant?: FakeParticipant;
|
||||
_audioMuted: boolean,
|
||||
|
||||
/**
|
||||
* URL of GIF sent by this participant, null if there's none.
|
||||
*/
|
||||
_gifSrc?: string;
|
||||
_gifSrc: ?string,
|
||||
|
||||
/**
|
||||
* The type of participant if the participant is fake.
|
||||
*/
|
||||
_fakeParticipant?: FakeParticipant,
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -89,71 +87,71 @@ interface IProps {
|
||||
* 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?: ITrack;
|
||||
_videoTrack: ?Object,
|
||||
|
||||
/**
|
||||
* Invoked to trigger state changes in Redux.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* The height of the thumbnail.
|
||||
* The height of the thumnail.
|
||||
*/
|
||||
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<IProps> {
|
||||
class Thumbnail extends PureComponent<Props> {
|
||||
|
||||
/**
|
||||
* Creates new Thumbnail component.
|
||||
*
|
||||
* @param {IProps} props - The props of the component.
|
||||
* @param {Props} props - The props of the component.
|
||||
* @returns {Thumbnail}
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
@@ -161,6 +159,8 @@ class Thumbnail extends PureComponent<IProps> {
|
||||
this.handleTrackStreamingStatusChanged = this.handleTrackStreamingStatusChanged.bind(this);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
/**
|
||||
* Thumbnail click handler.
|
||||
*
|
||||
@@ -176,6 +176,8 @@ class Thumbnail extends PureComponent<IProps> {
|
||||
}
|
||||
}
|
||||
|
||||
_onThumbnailLongPress: () => void;
|
||||
|
||||
/**
|
||||
* Thumbnail long press handler.
|
||||
*
|
||||
@@ -217,11 +219,11 @@ class Thumbnail extends PureComponent<IProps> {
|
||||
if (!_fakeParticipant || _isVirtualScreenshare) {
|
||||
indicators.push(<View
|
||||
key = 'top-left-indicators'
|
||||
style = { styles.thumbnailTopLeftIndicatorContainer as ViewStyle }>
|
||||
style = { styles.thumbnailTopLeftIndicatorContainer }>
|
||||
{ !_isVirtualScreenshare && <ConnectionIndicator participantId = { participantId } /> }
|
||||
{ !_isVirtualScreenshare && <RaisedHandIndicator participantId = { participantId } /> }
|
||||
{ tileView && (isScreenShare || _isVirtualScreenshare) && (
|
||||
<View style = { styles.screenShareIndicatorContainer as ViewStyle }>
|
||||
<View style = { styles.screenShareIndicatorContainer }>
|
||||
<ScreenShareIndicator />
|
||||
</View>
|
||||
) }
|
||||
@@ -229,9 +231,7 @@ class Thumbnail extends PureComponent<IProps> {
|
||||
indicators.push(<Container
|
||||
key = 'bottom-indicators'
|
||||
style = { styles.thumbnailIndicatorContainer }>
|
||||
<Container
|
||||
style = { ((audioMuted || renderModeratorIndicator) && styles.bottomIndicatorsContainer
|
||||
) as StyleType }>
|
||||
<Container style = { (audioMuted || renderModeratorIndicator) && styles.bottomIndicatorsContainer }>
|
||||
{ audioMuted && !_isVirtualScreenshare && <AudioMutedIndicator /> }
|
||||
{ !tileView && _pinned && <PinnedIndicator />}
|
||||
{ renderModeratorIndicator && !_isVirtualScreenshare && <ModeratorIndicator />}
|
||||
@@ -275,7 +275,7 @@ class Thumbnail extends PureComponent<IProps> {
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
// 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<IProps> {
|
||||
* @param {JitsiTrackStreamingStatus} streamingStatus - The updated track streaming status.
|
||||
* @returns {void}
|
||||
*/
|
||||
handleTrackStreamingStatusChanged(jitsiTrack: any, streamingStatus: string) {
|
||||
handleTrackStreamingStatusChanged(jitsiTrack, streamingStatus) {
|
||||
this.props.dispatch(trackStreamingStatusChanged(jitsiTrack, streamingStatus));
|
||||
}
|
||||
|
||||
@@ -363,11 +363,11 @@ class Thumbnail extends PureComponent<IProps> {
|
||||
styleOverrides,
|
||||
_raisedHand && !_isVirtualScreenshare ? styles.thumbnailRaisedHand : null,
|
||||
_renderDominantSpeakerIndicator && !_isVirtualScreenshare ? styles.thumbnailDominantSpeaker : null
|
||||
] as StyleType[] }
|
||||
] }
|
||||
touchFeedback = { false }>
|
||||
{ _gifSrc ? <Image
|
||||
source = {{ uri: _gifSrc }}
|
||||
style = { styles.thumbnailGif as ImageStyle } />
|
||||
style = { styles.thumbnailGif } />
|
||||
: <>
|
||||
<ParticipantView
|
||||
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
|
||||
@@ -388,36 +388,36 @@ class Thumbnail extends PureComponent<IProps> {
|
||||
* Function that maps parts of Redux state tree into component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @param {IProps} ownProps - Properties of component.
|
||||
* @param {Props} ownProps - Properties of component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
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?.dominantSpeaker && participantCount > 2;
|
||||
const renderDominantSpeakerIndicator = participant && 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' ? undefined : gifSrc,
|
||||
_gifSrc: mode === 'chat' ? null : 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,
|
||||
@@ -1,17 +1,16 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
FlatList,
|
||||
GestureResponderEvent,
|
||||
SafeAreaView,
|
||||
TouchableWithoutFeedback,
|
||||
ViewToken
|
||||
TouchableWithoutFeedback
|
||||
} from 'react-native';
|
||||
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { 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';
|
||||
|
||||
@@ -21,74 +20,74 @@ import styles from './styles';
|
||||
/**
|
||||
* The type of the React {@link Component} props of {@link TileView}.
|
||||
*/
|
||||
interface IProps {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* 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?: ILocalParticipant;
|
||||
_localParticipant: Object,
|
||||
|
||||
/**
|
||||
* 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: IStore['dispatch'];
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Object containing the safe area insets.
|
||||
*/
|
||||
insets: EdgeInsets;
|
||||
insets: Object,
|
||||
|
||||
/**
|
||||
* Callback to invoke when tile view is tapped.
|
||||
*/
|
||||
onClick: (e?: GestureResponderEvent) => void;
|
||||
}
|
||||
onClick: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* 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: any[] = [];
|
||||
const EMPTY_ARRAY = [];
|
||||
|
||||
/**
|
||||
* Implements a React {@link PureComponent} which displays thumbnails in a two
|
||||
@@ -96,17 +95,17 @@ const EMPTY_ARRAY: any[] = [];
|
||||
*
|
||||
* @augments PureComponent
|
||||
*/
|
||||
class TileView extends PureComponent<IProps> {
|
||||
class TileView extends PureComponent<Props> {
|
||||
|
||||
/**
|
||||
* The styles for the content container of the FlatList.
|
||||
*/
|
||||
_contentContainerStyles: any;
|
||||
_contentContainerStyles: Object;
|
||||
|
||||
/**
|
||||
* The styles for the FlatList.
|
||||
*/
|
||||
_flatListStyles: any;
|
||||
_flatListStyles: Object;
|
||||
|
||||
/**
|
||||
* The FlatList's viewabilityConfig.
|
||||
@@ -116,9 +115,9 @@ class TileView extends PureComponent<IProps> {
|
||||
/**
|
||||
* Creates new TileView component.
|
||||
*
|
||||
* @param {IProps} props - The props of the component.
|
||||
* @param {Props} props - The props of the component.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
@@ -138,16 +137,20 @@ class TileView extends PureComponent<IProps> {
|
||||
};
|
||||
}
|
||||
|
||||
_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: string) {
|
||||
_keyExtractor(item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
_onViewableItemsChanged: Object => void;
|
||||
|
||||
/**
|
||||
* A handler for visible items changes.
|
||||
*
|
||||
@@ -155,7 +158,7 @@ class TileView extends PureComponent<IProps> {
|
||||
* @param {Array<Object>} data.viewableItems - The visible items array.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: ViewToken[]; }) {
|
||||
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: Array<Object> }) {
|
||||
const { _disableSelfView } = this.props;
|
||||
|
||||
if (viewableItems[0]?.index === 0 && !_disableSelfView) {
|
||||
@@ -169,8 +172,8 @@ class TileView extends PureComponent<IProps> {
|
||||
}
|
||||
|
||||
// We are off by one in the remote participants array.
|
||||
const startIndex = Number(viewableItems[0].index) - (_disableSelfView ? 0 : 1);
|
||||
const endIndex = Number(viewableItems[viewableItems.length - 1].index) - (_disableSelfView ? 0 : 1);
|
||||
const startIndex = viewableItems[0].index - (_disableSelfView ? 0 : 1);
|
||||
const endIndex = viewableItems[viewableItems.length - 1].index - (_disableSelfView ? 0 : 1);
|
||||
|
||||
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
|
||||
}
|
||||
@@ -184,7 +187,7 @@ class TileView extends PureComponent<IProps> {
|
||||
render() {
|
||||
const { _columns, _height, _thumbnailHeight, _width, onClick } = this.props;
|
||||
const participants = this._getSortedParticipants();
|
||||
const initialRowsToRender = Math.ceil(_height / (Number(_thumbnailHeight) + (2 * styles.thumbnail.margin)));
|
||||
const initialRowsToRender = Math.ceil(_height / (_thumbnailHeight + (2 * styles.thumbnail.margin)));
|
||||
|
||||
if (this._flatListStyles.minHeight !== _height || this._flatListStyles.minWidth !== _width) {
|
||||
this._flatListStyles = {
|
||||
@@ -247,13 +250,15 @@ class TileView extends PureComponent<IProps> {
|
||||
return [ _localParticipant?.id, ..._remoteParticipants ];
|
||||
}
|
||||
|
||||
_renderThumbnail: Object => Object;
|
||||
|
||||
/**
|
||||
* Creates React Element to display each participant in a thumbnail.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderThumbnail({ item }: { item: string; }) {
|
||||
_renderThumbnail({ item/* , index , separators */ }) {
|
||||
const { _thumbnailHeight } = this.props;
|
||||
|
||||
return (
|
||||
@@ -273,18 +278,18 @@ class TileView extends PureComponent<IProps> {
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Object} ownProps - Component props.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
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 ?? 1,
|
||||
_columns: columns,
|
||||
_disableSelfView: disableSelfView,
|
||||
_height: responsiveUi.clientHeight - (ownProps.insets?.top || 0),
|
||||
_insets: ownProps.insets,
|
||||
@@ -15,7 +15,6 @@ 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';
|
||||
@@ -296,12 +295,12 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.props.dispatch(registerShortcut({
|
||||
character: 'F',
|
||||
helpDescription: 'keyboardShortcuts.toggleFilmstrip',
|
||||
handler: this._onShortcutToggleFilmstrip
|
||||
}));
|
||||
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
'F',
|
||||
'filmstripPopover',
|
||||
this._onShortcutToggleFilmstrip,
|
||||
'keyboardShortcuts.toggleFilmstrip'
|
||||
);
|
||||
document.addEventListener('mouseup', this._onDragMouseUp);
|
||||
|
||||
// @ts-ignore
|
||||
@@ -314,8 +313,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
this.props.dispatch(unregisterShortcut('F'));
|
||||
|
||||
APP.keyboardshortcut.unregisterShortcut('F');
|
||||
document.removeEventListener('mouseup', this._onDragMouseUp);
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -184,7 +184,6 @@ interface IDimensions {
|
||||
}
|
||||
|
||||
interface IFilmstripDimensions {
|
||||
columns?: number;
|
||||
filmstripHeight?: number;
|
||||
filmstripWidth?: number;
|
||||
gridDimensions?: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GiphyContent, GiphyGridView, GiphyMediaType, GiphyRating } from '@giphy/react-native-sdk';
|
||||
import { GiphyContent, GiphyGridView, GiphyMediaType } 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.native';
|
||||
import { formatGifUrlMessage, getGifRating, getGifUrl, getGiphyProxyUrl } from '../../functions';
|
||||
|
||||
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) as GiphyRating;
|
||||
const rating = useSelector(getGifRating);
|
||||
const proxyUrl = useSelector(getGiphyProxyUrl);
|
||||
|
||||
const options = {
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Image, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
@@ -14,10 +15,9 @@ const GifsMenuFooter = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style = { styles.credit as ViewStyle }>
|
||||
<Text style = { styles.creditText as TextStyle }>
|
||||
{ t('poweredby') }
|
||||
</Text>
|
||||
<View style = { styles.credit }>
|
||||
<Text
|
||||
style = { styles.creditText }>{ t('poweredby') }</Text>
|
||||
<Image
|
||||
source = { require('../../../../../images/GIPHY_logo.png') } />
|
||||
</View>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
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> {
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { GestureResponderEvent, Image, ImageStyle, TouchableOpacity, ViewStyle } from 'react-native';
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Image, TouchableOpacity } 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
|
||||
@@ -19,31 +21,12 @@ 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 Component<IProps> {
|
||||
class GoogleSignInButton extends AbstractGoogleSignInButton {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
@@ -68,11 +51,11 @@ class GoogleSignInButton extends Component<IProps> {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress = { onClick }
|
||||
style = { styles.signInButton as ViewStyle } >
|
||||
style = { styles.signInButton } >
|
||||
<Image
|
||||
resizeMode = { 'contain' }
|
||||
source = { GOOGLE_BRAND_IMAGE }
|
||||
style = { styles.signInImage as ImageStyle } />
|
||||
style = { styles.signInImage } />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
@@ -1,33 +1,15 @@
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
|
||||
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;
|
||||
}
|
||||
import AbstractGoogleSignInButton from './AbstractGoogleSignInButton';
|
||||
|
||||
/**
|
||||
* A React Component showing a button to sign in with Google.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class GoogleSignInButton extends Component<IProps> {
|
||||
class GoogleSignInButton extends AbstractGoogleSignInButton {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { createStyleSheet } from '../../base/styles/functions.any';
|
||||
|
||||
/**
|
||||
@@ -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<IInvitee | IInviteSelectItem>;
|
||||
inviteItems: Array<IInviteSelectItem>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
@@ -22,6 +23,7 @@ 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';
|
||||
@@ -31,16 +33,19 @@ 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, {
|
||||
type IProps as AbstractProps,
|
||||
type IState as AbstractState,
|
||||
// @ts-ignore
|
||||
type Props as AbstractProps,
|
||||
// @ts-ignore
|
||||
type State as AbstractState,
|
||||
_mapStateToProps as _abstractMapStateToProps
|
||||
} from '../AbstractAddPeopleDialog';
|
||||
|
||||
// @ts-ignore
|
||||
import styles, { AVATAR_SIZE } from './styles';
|
||||
|
||||
interface IProps extends AbstractProps, WithTranslation {
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* True if the invite dialog should be open, false otherwise.
|
||||
@@ -50,7 +55,12 @@ interface IProps extends AbstractProps, WithTranslation {
|
||||
/**
|
||||
* Default prop for navigation between screen components(React Navigation).
|
||||
*/
|
||||
navigation: any;
|
||||
navigation: Object;
|
||||
|
||||
/**
|
||||
* Function used to translate i18n labels.
|
||||
*/
|
||||
t: Function;
|
||||
|
||||
/**
|
||||
* Theme used for styles.
|
||||
@@ -85,7 +95,9 @@ interface IState extends AbstractState {
|
||||
/**
|
||||
* Implements a special dialog to invite people from a directory service.
|
||||
*/
|
||||
class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
class AddPeopleDialog
|
||||
// @ts-ignore
|
||||
extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
/**
|
||||
* Default state object to reset the state to when needed.
|
||||
*/
|
||||
@@ -107,7 +119,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
searchTimeout: number;
|
||||
|
||||
/**
|
||||
* Constructor of the component.
|
||||
* Contrustor of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
@@ -139,6 +151,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
componentDidMount() {
|
||||
const { navigation, t } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
navigation.setOptions({
|
||||
headerRight: () => (
|
||||
<HeaderNavigationButton
|
||||
@@ -158,6 +171,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
const { navigation, t } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
navigation.setOptions({
|
||||
// eslint-disable-next-line react/no-multi-comp
|
||||
headerRight: () => (
|
||||
@@ -183,9 +197,12 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
// @ts-ignore
|
||||
_addPeopleEnabled,
|
||||
// @ts-ignore
|
||||
_dialOutEnabled
|
||||
} = this.props;
|
||||
// @ts-ignore
|
||||
const { inviteItems, selectableItems } = this.state;
|
||||
|
||||
let placeholderKey = 'searchPlaceholder';
|
||||
@@ -285,6 +302,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
});
|
||||
|
||||
// Clear search results
|
||||
// @ts-ignore
|
||||
this._onTypeQuery('');
|
||||
}
|
||||
|
||||
@@ -296,9 +314,10 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
_onInvite() {
|
||||
// @ts-ignore
|
||||
this._invite(this.state.inviteItems)
|
||||
.then((invitesLeftToSend: IInvitee[]) => {
|
||||
.then((invitesLeftToSend: ArrayLike<any>) => {
|
||||
if (invitesLeftToSend.length) {
|
||||
this.setState({
|
||||
// @ts-ignore
|
||||
inviteItems: invitesLeftToSend
|
||||
});
|
||||
this._showFailedInviteAlert();
|
||||
@@ -314,22 +333,26 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
*/
|
||||
_onPressItem(item: Item) {
|
||||
return () => {
|
||||
// @ts-ignore
|
||||
const { inviteItems } = this.state;
|
||||
const finderKey = item.type === INVITE_TYPES.PHONE ? 'number' : 'user_id';
|
||||
|
||||
if (inviteItems.find(
|
||||
_.matchesProperty(finderKey, item[finderKey as keyof typeof item]))) {
|
||||
// @ts-ignore
|
||||
_.matchesProperty(finderKey, item[finderKey]))) {
|
||||
// Item is already selected, need to unselect it.
|
||||
this.setState({
|
||||
// @ts-ignore
|
||||
inviteItems: inviteItems.filter(
|
||||
(element: any) => item[finderKey as keyof typeof item] !== element[finderKey])
|
||||
// @ts-ignore
|
||||
(element: any) => item[finderKey] !== element[finderKey])
|
||||
});
|
||||
} else {
|
||||
// Item is not selected yet, need to add to the list.
|
||||
// @ts-ignore
|
||||
const items = inviteItems.concat(item);
|
||||
const items: Array<Object> = inviteItems.concat(item);
|
||||
|
||||
this.setState({
|
||||
// @ts-ignore
|
||||
inviteItems: _.sortBy(items, [ 'name', 'number' ])
|
||||
});
|
||||
}
|
||||
@@ -342,10 +365,12 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
* @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());
|
||||
}
|
||||
}
|
||||
@@ -363,6 +388,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
});
|
||||
|
||||
clearTimeout(this.searchTimeout);
|
||||
// @ts-ignore
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.setState({
|
||||
searchInprogress: true
|
||||
@@ -436,8 +462,9 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
*/
|
||||
_renderItem(flatListItem: any, index: number): ReactElement | null {
|
||||
const { item } = flatListItem;
|
||||
// @ts-ignore
|
||||
const { inviteItems } = this.state;
|
||||
let selected: IInvitee | IInviteSelectItem | undefined | boolean = false;
|
||||
let selected = false;
|
||||
const renderableItem = this._getRenderableItem(flatListItem);
|
||||
|
||||
if (!renderableItem) {
|
||||
@@ -543,6 +570,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_showFailedInviteAlert() {
|
||||
// @ts-ignore
|
||||
this.props.dispatch(openDialog(AlertDialog, {
|
||||
contentKey: {
|
||||
key: 'inviteDialog.alertText'
|
||||
@@ -555,15 +583,14 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
* 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, _ownProps: any) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state)
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
export default translate(connect(_mapStateToProps)(AddPeopleDialog));
|
||||
|
||||
@@ -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: any = [];
|
||||
const userDisplayItems = [];
|
||||
|
||||
for (const user of users) {
|
||||
const { name, phone } = user;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Route } from '@react-navigation/native';
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Linking, View, ViewStyle } from 'react-native';
|
||||
import { Linking, View } 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,32 +15,38 @@ import { getDialInfoPageURLForURIString } from '../../../functions';
|
||||
import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
|
||||
import styles, { INDICATOR_COLOR } from './styles';
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
dispatch: IStore['dispatch'];
|
||||
type Props = {
|
||||
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
navigation: any;
|
||||
navigation: Object,
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
route: Route<'', { summaryUrl: string; }>;
|
||||
}
|
||||
route: Object,
|
||||
|
||||
/**
|
||||
* Translation function.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React native component that displays the dial in info page for a specific room.
|
||||
*/
|
||||
class DialInSummary extends PureComponent<IProps> {
|
||||
class DialInSummary extends PureComponent<Props> {
|
||||
|
||||
/**
|
||||
* Initializes a new instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onError = this._onError.bind(this);
|
||||
@@ -80,13 +86,15 @@ class DialInSummary extends PureComponent<IProps> {
|
||||
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.
|
||||
*
|
||||
@@ -96,6 +104,8 @@ class DialInSummary extends PureComponent<IProps> {
|
||||
this.props.dispatch(openDialog(DialInSummaryErrorDialog));
|
||||
}
|
||||
|
||||
_onNavigate: Object => Boolean;
|
||||
|
||||
/**
|
||||
* Callback to intercept navigation inside the webview and make the native app handle the dial requests.
|
||||
*
|
||||
@@ -104,7 +114,7 @@ class DialInSummary extends PureComponent<IProps> {
|
||||
* @param {any} request - The request object.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onNavigate(request: { url: string; }) {
|
||||
_onNavigate(request) {
|
||||
const { url } = request;
|
||||
const { route } = this.props;
|
||||
const summaryUrl = route.params?.summaryUrl;
|
||||
@@ -116,6 +126,8 @@ class DialInSummary extends PureComponent<IProps> {
|
||||
return url === getDialInfoPageURLForURIString(summaryUrl);
|
||||
}
|
||||
|
||||
_renderLoading: () => React$Component<any>;
|
||||
|
||||
/**
|
||||
* Renders the loading indicator.
|
||||
*
|
||||
@@ -123,7 +135,7 @@ class DialInSummary extends PureComponent<IProps> {
|
||||
*/
|
||||
_renderLoading() {
|
||||
return (
|
||||
<View style = { styles.indicatorWrapper as ViewStyle }>
|
||||
<View style = { styles.indicatorWrapper }>
|
||||
<LoadingIndicator
|
||||
color = { INDICATOR_COLOR }
|
||||
size = 'large' />
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import AlertDialog from '../../../../base/dialog/components/native/AlertDialog';
|
||||
@@ -8,7 +9,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<WithTranslation> {
|
||||
class DialInSummaryErrorDialog extends Component<{}> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -21,6 +22,8 @@ class DialInSummaryErrorDialog extends Component<WithTranslation> {
|
||||
contentKey = 'info.dialInSummaryError' />
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
}
|
||||
|
||||
export default translate(connect()(DialInSummaryErrorDialog));
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
export const INDICATOR_COLOR = BaseTheme.palette.ui07;
|
||||
@@ -31,11 +31,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRadius: 6,
|
||||
|
||||
'& *': {
|
||||
userSelect: 'text'
|
||||
}
|
||||
borderRadius: 6
|
||||
},
|
||||
confNameLabel: {
|
||||
...withPixelLineHeight(theme.typography.heading6),
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* 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';
|
||||
@@ -1,60 +0,0 @@
|
||||
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
|
||||
};
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './actions.any';
|
||||
@@ -1,119 +0,0 @@
|
||||
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();
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/keyboard-shortcuts');
|
||||
@@ -1,39 +0,0 @@
|
||||
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);
|
||||
});
|
||||
@@ -1,72 +0,0 @@
|
||||
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;
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
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>;
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
};
|
||||
@@ -1,76 +1,77 @@
|
||||
// @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}.
|
||||
*/
|
||||
interface IProps {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* 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?: ITrack;
|
||||
_videoTrack: ?Object,
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
_width: number;
|
||||
_width: number,
|
||||
|
||||
/**
|
||||
* Invoked to trigger state changes in Redux.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
interface IState {
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* 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,
|
||||
@@ -83,14 +84,14 @@ const DEFAULT_STATE = {
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class LargeVideo extends PureComponent<IProps, IState> {
|
||||
class LargeVideo extends PureComponent<Props, State> {
|
||||
/**
|
||||
* Creates new LargeVideo component.
|
||||
*
|
||||
* @param {IProps} props - The props of the component.
|
||||
* @param {Props} props - The props of the component.
|
||||
* @returns {LargeVideo}
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.handleTrackStreamingStatusChanged = this.handleTrackStreamingStatusChanged.bind(this);
|
||||
@@ -107,7 +108,7 @@ class LargeVideo extends PureComponent<IProps, IState> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
static getDerivedStateFromProps(props: IProps) {
|
||||
static getDerivedStateFromProps(props: Props) {
|
||||
const { _height, _width } = props;
|
||||
|
||||
// Get the size, rounded to the nearest even number.
|
||||
@@ -151,7 +152,7 @@ class LargeVideo extends PureComponent<IProps, IState> {
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
// 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;
|
||||
@@ -199,7 +200,7 @@ class LargeVideo extends PureComponent<IProps, IState> {
|
||||
* @param {JitsiTrackStreamingStatus} streamingStatus - The updated track streaming status.
|
||||
* @returns {void}
|
||||
*/
|
||||
handleTrackStreamingStatusChanged(jitsiTrack: any, streamingStatus: string) {
|
||||
handleTrackStreamingStatusChanged(jitsiTrack, streamingStatus) {
|
||||
this.props.dispatch(trackStreamingStatusChanged(jitsiTrack, streamingStatus));
|
||||
}
|
||||
|
||||
@@ -239,11 +240,11 @@ class LargeVideo extends PureComponent<IProps, IState> {
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
function _mapStateToProps(state) {
|
||||
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;
|
||||
@@ -257,7 +258,7 @@ function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
_disableVideo: disableVideo,
|
||||
_height: height,
|
||||
_participantId: participantId ?? '',
|
||||
_participantId: participantId,
|
||||
_videoTrack: videoTrack,
|
||||
_width: width
|
||||
};
|
||||
@@ -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, {
|
||||
IProps as AbstractProps,
|
||||
Props as AbstractProps,
|
||||
_mapStateToProps as abstractMapStateToProps
|
||||
} from '../AbstractLobbyScreen';
|
||||
|
||||
@@ -30,7 +30,6 @@ class LobbyChatScreen extends
|
||||
|
||||
return (
|
||||
<JitsiScreen style = { styles.lobbyChatWrapper }>
|
||||
{/* @ts-ignore */}
|
||||
<MessageContainer messages = { _lobbyChatMessages } />
|
||||
<ChatInputBar onSend = { this._onSendMessage } />
|
||||
</JitsiScreen>
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { Text, View } 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';
|
||||
@@ -21,28 +20,29 @@ import { preJoinStyles } from '../../../prejoin/components/native/styles';
|
||||
import AudioMuteButton from '../../../toolbox/components/AudioMuteButton';
|
||||
import VideoMuteButton from '../../../toolbox/components/VideoMuteButton';
|
||||
import AbstractLobbyScreen, {
|
||||
IProps as AbstractProps,
|
||||
Props as AbstractProps,
|
||||
_mapStateToProps as abstractMapStateToProps } from '../AbstractLobbyScreen';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
type Props = 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<IProps> {
|
||||
class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
@@ -70,7 +70,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
style = { contentWrapperStyles }>
|
||||
<BrandingImageBackground />
|
||||
<View style = { largeVideoContainerStyles }>
|
||||
<View style = { preJoinStyles.displayRoomNameBackdrop as ViewStyle }>
|
||||
<View style = { preJoinStyles.displayRoomNameBackdrop }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { preJoinStyles.preJoinRoomName }>
|
||||
@@ -79,7 +79,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
</View>
|
||||
<LargeVideo />
|
||||
</View>
|
||||
<View style = { contentContainerStyles as ViewStyle }>
|
||||
<View style = { contentContainerStyles }>
|
||||
{ this._renderToolbarButtons() }
|
||||
{ this._renderContent() }
|
||||
</View>
|
||||
@@ -87,6 +87,32 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
);
|
||||
}
|
||||
|
||||
_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.
|
||||
*
|
||||
@@ -111,7 +137,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
<LoadingIndicator
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
style = { styles.loadingIndicator } />
|
||||
<Text style = { styles.joiningMessage as TextStyle }>
|
||||
<Text style = { styles.joiningMessage }>
|
||||
{ this.props.t('lobby.joiningMessage') }
|
||||
</Text>
|
||||
{ this._renderStandardButtons() }
|
||||
@@ -157,6 +183,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
return (
|
||||
<Input
|
||||
autoCapitalize = 'none'
|
||||
autoCompleteType = 'off'
|
||||
customStyles = {{ input: styles.customInput }}
|
||||
error = { _passwordJoinFailed }
|
||||
onChange = { this._onChangePassword }
|
||||
@@ -198,7 +225,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
*/
|
||||
_renderToolbarButtons() {
|
||||
return (
|
||||
<View style = { preJoinStyles.toolboxContainer as ViewStyle }>
|
||||
<View style = { preJoinStyles.toolboxContainer }>
|
||||
<AudioMuteButton
|
||||
styles = { preJoinStyles.buttonStylesBorderless } />
|
||||
<VideoMuteButton
|
||||
@@ -217,7 +244,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
const { displayName } = this.state;
|
||||
|
||||
return (
|
||||
<View style = { styles.formWrapper as ViewStyle }>
|
||||
<View style = { styles.formWrapper }>
|
||||
{
|
||||
_knocking && _isLobbyChatActive
|
||||
&& <Button
|
||||
@@ -255,14 +282,14 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @returns {{
|
||||
* _aspectRatio: Symbol
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
...abstractMapStateToProps(state, ownProps),
|
||||
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
||||
_roomName: getConferenceName(state)
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
lobbyChatWrapper: {
|
||||
@@ -82,7 +82,6 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: IProps) =
|
||||
name = { screen.welcome.main }
|
||||
options = { welcomeScreenOptions } />
|
||||
<RootStack.Screen
|
||||
// @ts-ignore
|
||||
component = { DialInSummary }
|
||||
name = { screen.dialInSummary }
|
||||
options = { dialInSummaryScreenOptions } />
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user