mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-09 08:10:18 +00:00
Compare commits
24 Commits
7219
...
android-ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2521bc67a | ||
|
|
38a293f8f6 | ||
|
|
13e8f992b5 | ||
|
|
c384d0d3a9 | ||
|
|
2b71fa512b | ||
|
|
d381ceb040 | ||
|
|
e18c428f52 | ||
|
|
9fc32dc59b | ||
|
|
6f45622ef1 | ||
|
|
e56c7070c2 | ||
|
|
0aef7a36aa | ||
|
|
c030cf941e | ||
|
|
f6760e4ac7 | ||
|
|
9060c77307 | ||
|
|
a1d018eef4 | ||
|
|
3f724d8fb7 | ||
|
|
49d69a5a02 | ||
|
|
6db9e42876 | ||
|
|
ad3e8f9f53 | ||
|
|
9f39caa247 | ||
|
|
1402a63324 | ||
|
|
a9863e65c3 | ||
|
|
646c58f7d1 | ||
|
|
a78ea7ca9c |
@@ -19,7 +19,7 @@ buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "31.0.0"
|
||||
compileSdkVersion = 32
|
||||
minSdkVersion = 23
|
||||
minSdkVersion = 24
|
||||
targetSdkVersion = 32
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
|
||||
2
app.js
2
app.js
@@ -18,7 +18,6 @@ import './react/features/base/jitsi-local-storage/setup';
|
||||
import conference from './conference';
|
||||
import API from './modules/API';
|
||||
import UI from './modules/UI/UI';
|
||||
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
|
||||
import translation from './modules/translation/translation';
|
||||
|
||||
// Initialize Olm as early as possible.
|
||||
@@ -38,7 +37,6 @@ window.APP = {
|
||||
'index.loaded': window.indexLoadedTime
|
||||
},
|
||||
|
||||
keyboardshortcut,
|
||||
translation,
|
||||
UI
|
||||
};
|
||||
|
||||
@@ -140,6 +140,7 @@ import { downloadJSON } from './react/features/base/util/downloadJSON';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker/actions';
|
||||
import { appendSuffix } from './react/features/display-name/functions';
|
||||
import { maybeOpenFeedbackDialog, submitFeedback } from './react/features/feedback/actions';
|
||||
import { initKeyboardShortcuts } from './react/features/keyboard-shortcuts/actions';
|
||||
import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
|
||||
import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
|
||||
import { hideNotification, showNotification, showWarningNotification } from './react/features/notifications/actions';
|
||||
@@ -2308,10 +2309,7 @@ export default {
|
||||
|
||||
APP.UI.initConference();
|
||||
|
||||
if (!config.disableShortcuts) {
|
||||
APP.keyboardshortcut.init();
|
||||
}
|
||||
|
||||
dispatch(initKeyboardShortcuts());
|
||||
dispatch(conferenceJoined(room));
|
||||
|
||||
const jwt = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
@@ -1406,6 +1406,7 @@ var config = {
|
||||
disableAGC
|
||||
disableAP
|
||||
disableHPF
|
||||
disableLocalStats
|
||||
disableNS
|
||||
enableTalkWhileMuted
|
||||
forceJVB121Ratio
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
a:active {
|
||||
color: black;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-corner {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -108,6 +108,10 @@
|
||||
#largeVideoContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#largeVideoWrapper {
|
||||
|
||||
@@ -57,6 +57,10 @@
|
||||
line-height: 24px;
|
||||
border-collapse: collapse;
|
||||
|
||||
* {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
thead {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -84,8 +84,12 @@ Component "conference.jitmeet.example.com" "muc"
|
||||
"polls";
|
||||
--"token_verification";
|
||||
"muc_rate_limit";
|
||||
"muc_password_whitelist";
|
||||
}
|
||||
admins = { "focusUser@auth.jitmeet.example.com" }
|
||||
muc_password_whitelist = {
|
||||
"focusUser@auth.jitmeet.example.com"
|
||||
}
|
||||
muc_room_locking = false
|
||||
muc_room_default_public_jids = true
|
||||
|
||||
|
||||
6
globals.d.ts
vendored
6
globals.d.ts
vendored
@@ -10,12 +10,6 @@ declare global {
|
||||
API: any;
|
||||
conference: any;
|
||||
debugLogs: any;
|
||||
keyboardshortcut: {
|
||||
registerShortcut: Function;
|
||||
unregisterShortcut: Function;
|
||||
openDialog: Function;
|
||||
enable: Function;
|
||||
}
|
||||
};
|
||||
const interfaceConfig: any;
|
||||
|
||||
|
||||
@@ -675,6 +675,7 @@
|
||||
"connectedTwoMembers": "{{first}} and {{second}} joined the meeting",
|
||||
"dataChannelClosed": "Video quality impaired",
|
||||
"dataChannelClosedDescription": "The bridge channel has been disconnected and thus video quality is limited to its lowest setting.",
|
||||
"disabledIframe": "Embedding is only meant for demo purposes, so this call will disconnect in {{timeout}} minutes.",
|
||||
"disconnected": "disconnected",
|
||||
"displayNotifications": "Display notifications for",
|
||||
"dontRemindMe": "Do not remind me",
|
||||
|
||||
@@ -115,7 +115,11 @@ import { muteAllParticipants } from '../../react/features/video-menu/actions';
|
||||
import { setVideoQuality } from '../../react/features/video-quality/actions';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
|
||||
import { API_ID, ENDPOINT_TEXT_MESSAGE_NAME } from './constants';
|
||||
import {
|
||||
API_ID,
|
||||
ASSUMED_BANDWIDTH_BPS,
|
||||
ENDPOINT_TEXT_MESSAGE_NAME
|
||||
} from './constants';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
@@ -310,6 +314,23 @@ function initCommands() {
|
||||
|
||||
APP.store.dispatch(sendTones(tones, duration, pause));
|
||||
},
|
||||
'set-assumed-bandwidth-bps': value => {
|
||||
logger.debug('Set assumed bandwidth bps command received', value);
|
||||
|
||||
if (typeof value !== 'number' || isNaN(value)) {
|
||||
logger.error('Assumed bandwidth bps must be a number.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { conference } = APP.store.getState()['features/base/conference'];
|
||||
|
||||
if (conference) {
|
||||
conference.setAssumedBandwidthBps(value < ASSUMED_BANDWIDTH_BPS
|
||||
? ASSUMED_BANDWIDTH_BPS
|
||||
: value);
|
||||
}
|
||||
},
|
||||
'set-follow-me': value => {
|
||||
logger.debug('Set follow me command received');
|
||||
|
||||
|
||||
@@ -15,3 +15,10 @@ export const API_ID = parseURLParams(window.location).jitsi_meet_external_api_id
|
||||
* The payload name for the datachannel/endpoint text message event.
|
||||
*/
|
||||
export const ENDPOINT_TEXT_MESSAGE_NAME = 'endpoint-text-message';
|
||||
|
||||
/**
|
||||
* The min value that can be set for the assumed bandwidth.
|
||||
* Setting it to this value means not assuming any bandwidth,
|
||||
* but rather allowing the estimations to take place.
|
||||
*/
|
||||
export const ASSUMED_BANDWIDTH_BPS = -1;
|
||||
|
||||
1
modules/API/external/external_api.js
vendored
1
modules/API/external/external_api.js
vendored
@@ -59,6 +59,7 @@ const commands = {
|
||||
sendEndpointTextMessage: 'send-endpoint-text-message',
|
||||
sendParticipantToRoom: 'send-participant-to-room',
|
||||
sendTones: 'send-tones',
|
||||
setAssumedBandwidthBps: 'set-assumed-bandwidth-bps',
|
||||
setFollowMe: 'set-follow-me',
|
||||
setLargeVideoParticipant: 'set-large-video-participant',
|
||||
setMediaEncryptionKey: 'set-media-encryption-key',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
/* global APP */
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from '@jitsi/logger';
|
||||
|
||||
import {
|
||||
ACTION_SHORTCUT_PRESSED as PRESSED,
|
||||
ACTION_SHORTCUT_RELEASED as RELEASED,
|
||||
createShortcutEvent
|
||||
} from '../../react/features/analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../react/features/analytics/functions';
|
||||
import { clickOnVideo } from '../../react/features/filmstrip/actions';
|
||||
import { openSettingsDialog } from '../../react/features/settings/actions';
|
||||
import { SETTINGS_TABS } from '../../react/features/settings/constants';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Map of shortcuts. When a shortcut is registered it enters the mapping.
|
||||
* @type {Map}
|
||||
*/
|
||||
const _shortcuts = new Map();
|
||||
|
||||
/**
|
||||
* Map of registered keyboard keys and translation keys describing the
|
||||
* action performed by the key.
|
||||
* @type {Map}
|
||||
*/
|
||||
const _shortcutsHelp = new Map();
|
||||
|
||||
/**
|
||||
* The key used to save in local storage if keyboard shortcuts are enabled.
|
||||
*/
|
||||
const _enableShortcutsKey = 'enableShortcuts';
|
||||
|
||||
/**
|
||||
* Prefer keyboard handling of these elements over global shortcuts.
|
||||
* If a button is triggered using the Spacebar it should not trigger PTT.
|
||||
* If an input element is focused and M is pressed it should not mute audio.
|
||||
*/
|
||||
const _elementsBlacklist = [
|
||||
'input',
|
||||
'textarea',
|
||||
'button',
|
||||
'[role=button]',
|
||||
'[role=menuitem]',
|
||||
'[role=radio]',
|
||||
'[role=tab]',
|
||||
'[role=option]',
|
||||
'[role=switch]',
|
||||
'[role=range]',
|
||||
'[role=log]'
|
||||
];
|
||||
|
||||
/**
|
||||
* An element selector for elements that have their own keyboard handling.
|
||||
*/
|
||||
const _focusedElementsSelector = `:focus:is(${_elementsBlacklist.join(',')})`;
|
||||
|
||||
/**
|
||||
* Maps keycode to character, id of popover for given function and function.
|
||||
*/
|
||||
const KeyboardShortcut = {
|
||||
|
||||
init() {
|
||||
this._initGlobalShortcuts();
|
||||
|
||||
window.onkeyup = e => {
|
||||
if (!this.getEnabled()) {
|
||||
return;
|
||||
}
|
||||
const key = this._getKeyboardKey(e).toUpperCase();
|
||||
const num = parseInt(key, 10);
|
||||
|
||||
if (!document.querySelector(_focusedElementsSelector)) {
|
||||
if (_shortcuts.has(key)) {
|
||||
_shortcuts.get(key).function(e);
|
||||
} else if (!isNaN(num) && num >= 0 && num <= 9) {
|
||||
APP.store.dispatch(clickOnVideo(num));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.onkeydown = e => {
|
||||
if (!this.getEnabled()) {
|
||||
return;
|
||||
}
|
||||
const focusedElement = document.querySelector(_focusedElementsSelector);
|
||||
|
||||
if (!focusedElement) {
|
||||
if (this._getKeyboardKey(e).toUpperCase() === ' ') {
|
||||
if (APP.conference.isLocalAudioMuted()) {
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'push.to.talk',
|
||||
PRESSED));
|
||||
logger.log('Talk shortcut pressed');
|
||||
APP.conference.muteAudio(false);
|
||||
}
|
||||
}
|
||||
} else if (this._getKeyboardKey(e).toUpperCase() === 'ESCAPE') {
|
||||
// Allow to remove focus from selected elements using ESC key.
|
||||
if (focusedElement && focusedElement.blur) {
|
||||
focusedElement.blur();
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables/Disables the keyboard shortcuts.
|
||||
* @param {boolean} value - the new value.
|
||||
*/
|
||||
enable(value) {
|
||||
jitsiLocalStorage.setItem(_enableShortcutsKey, value);
|
||||
},
|
||||
|
||||
getEnabled() {
|
||||
// Should be enabled if not explicitly set to false
|
||||
// eslint-disable-next-line no-unneeded-ternary
|
||||
return jitsiLocalStorage.getItem(_enableShortcutsKey) === 'false' ? false : true;
|
||||
},
|
||||
|
||||
getShortcutsDescriptions() {
|
||||
return _shortcutsHelp;
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the {@SettingsDialog} dialog on the Shortcuts page.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
openDialog() {
|
||||
APP.store.dispatch(openSettingsDialog(SETTINGS_TABS.SHORTCUTS, false));
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a new shortcut.
|
||||
*
|
||||
* @param shortcutChar the shortcut character triggering the action
|
||||
* @param shortcutAttr the "shortcut" html element attribute mapping an
|
||||
* element to this shortcut and used to show the shortcut character on the
|
||||
* element tooltip
|
||||
* @param exec the function to be executed when the shortcut is pressed
|
||||
* @param helpDescription the description of the shortcut that would appear
|
||||
* in the help menu
|
||||
* @param altKey whether or not the alt key must be pressed.
|
||||
*/
|
||||
registerShortcut(// eslint-disable-line max-params
|
||||
shortcutChar,
|
||||
shortcutAttr,
|
||||
exec,
|
||||
helpDescription,
|
||||
altKey = false) {
|
||||
_shortcuts.set(altKey ? `:${shortcutChar}` : shortcutChar, {
|
||||
character: shortcutChar,
|
||||
function: exec,
|
||||
shortcutAttr,
|
||||
altKey
|
||||
});
|
||||
|
||||
if (helpDescription) {
|
||||
this._addShortcutToHelp(altKey ? `:${shortcutChar}` : shortcutChar, helpDescription);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters a shortcut.
|
||||
*
|
||||
* @param shortcutChar unregisters the given shortcut, which means it will
|
||||
* no longer be usable
|
||||
* @param altKey whether or not shortcut is combo with alt key
|
||||
*/
|
||||
unregisterShortcut(shortcutChar, altKey = false) {
|
||||
_shortcuts.delete(altKey ? `:${shortcutChar}` : shortcutChar);
|
||||
_shortcutsHelp.delete(shortcutChar);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param e a KeyboardEvent
|
||||
* @returns {string} e.key or something close if not supported
|
||||
*/
|
||||
_getKeyboardKey(e) {
|
||||
// If alt is pressed a different char can be returned so this takes
|
||||
// the char from the code. It also prefixes with a colon to differentiate
|
||||
// alt combo from simple keypress.
|
||||
if (e.altKey) {
|
||||
const key = e.code.replace('Key', '');
|
||||
|
||||
return `:${key}`;
|
||||
}
|
||||
|
||||
// If e.key is a string, then it is assumed it already plainly states
|
||||
// the key pressed. This may not be true in all cases, such as with Edge
|
||||
// and "?", when the browser cannot properly map a key press event to a
|
||||
// keyboard key. To be safe, when a key is "Unidentified" it must be
|
||||
// further analyzed by jitsi to a key using e.which.
|
||||
if (typeof e.key === 'string' && e.key !== 'Unidentified') {
|
||||
return e.key;
|
||||
}
|
||||
if (e.type === 'keypress'
|
||||
&& ((e.which >= 32 && e.which <= 126)
|
||||
|| (e.which >= 160 && e.which <= 255))) {
|
||||
return String.fromCharCode(e.which);
|
||||
}
|
||||
|
||||
// try to fallback (0-9A-Za-z and QWERTY keyboard)
|
||||
switch (e.which) {
|
||||
case 27:
|
||||
return 'Escape';
|
||||
case 191:
|
||||
return e.shiftKey ? '?' : '/';
|
||||
}
|
||||
if (e.shiftKey || e.type === 'keypress') {
|
||||
return String.fromCharCode(e.which);
|
||||
}
|
||||
|
||||
return String.fromCharCode(e.which).toLowerCase();
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the given shortcut to the help dialog.
|
||||
*
|
||||
* @param shortcutChar the shortcut character
|
||||
* @param shortcutDescriptionKey the description of the shortcut
|
||||
* @private
|
||||
*/
|
||||
_addShortcutToHelp(shortcutChar, shortcutDescriptionKey) {
|
||||
_shortcutsHelp.set(shortcutChar, shortcutDescriptionKey);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialise global shortcuts.
|
||||
* Global shortcuts are shortcuts for features that don't have a button or
|
||||
* link associated with the action. In other words they represent actions
|
||||
* triggered _only_ with a shortcut.
|
||||
*/
|
||||
_initGlobalShortcuts() {
|
||||
this.registerShortcut('?', null, () => {
|
||||
sendAnalytics(createShortcutEvent('help'));
|
||||
this.openDialog();
|
||||
}, 'keyboardShortcuts.toggleShortcuts');
|
||||
|
||||
// register SPACE shortcut in two steps to insure visibility of help
|
||||
// message
|
||||
this.registerShortcut(' ', null, () => {
|
||||
sendAnalytics(createShortcutEvent('push.to.talk', RELEASED));
|
||||
logger.log('Talk shortcut released');
|
||||
APP.conference.muteAudio(true);
|
||||
});
|
||||
this._addShortcutToHelp('SPACE', 'keyboardShortcuts.pushToTalk');
|
||||
|
||||
/**
|
||||
* FIXME: Currently focus keys are directly implemented below in
|
||||
* onkeyup. They should be moved to the SmallVideo instead.
|
||||
*/
|
||||
this._addShortcutToHelp('0', 'keyboardShortcuts.focusLocal');
|
||||
this._addShortcutToHelp('1-9', 'keyboardShortcuts.focusRemote');
|
||||
}
|
||||
};
|
||||
|
||||
export default KeyboardShortcut;
|
||||
45
package-lock.json
generated
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/v1620.0.0+7f0012f7/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -12739,8 +12739,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1620.0.0+7f0012f7/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-F3vOUwD/ys5dGtswR3C5IK8JPDOG+71J+nhkEDLotkni/Zws8KHzf8Ye332UuetUjCTGJP3CbRFe9yJdYERe7g==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-0LK5EMCvtsAEQkjkrWzyniTl8BKtshAZxI3e9hGnA/RVDuULLXre7GEWd2zCP3tCfdxclkhqt7skQpfNhCTdqg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -12758,7 +12758,7 @@
|
||||
"patch-package": "6.5.1",
|
||||
"promise.allsettled": "1.0.4",
|
||||
"sdp-transform": "2.3.0",
|
||||
"strophe.js": "1.6.0",
|
||||
"strophe.js": "1.5.0",
|
||||
"strophejs-plugin-disco": "0.0.2",
|
||||
"strophejs-plugin-stream-management": "git+https://github.com/jitsi/strophejs-plugin-stream-management#679be5902097ed612fb5062b5549f3f32b6f5f47",
|
||||
"uuid": "8.1.0",
|
||||
@@ -17615,18 +17615,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/strophe.js": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.6.0.tgz",
|
||||
"integrity": "sha512-LE2B6nEJNUbF2Cl/p1tLIsXVJ9l86B/Z12HYYiO3n92VwYkhJ/5vJ+1ZMdwP9eN9GP8a3nbqfS5zE9umcK0FdA==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.5.0.tgz",
|
||||
"integrity": "sha512-H5tE/tZxPR5xP3jhXyQwsjnMSwQMf7vrn9r1OkufTApyGHYe8WjzhsfxtL3AFhVu7vFjXPPZBrmUOTm1ccYgOA==",
|
||||
"dependencies": {
|
||||
"abab": "^2.0.3",
|
||||
"karma-rollup-preprocessor": "^7.0.8"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@xmldom/xmldom": "0.8.3",
|
||||
"@xmldom/xmldom": "0.8.2",
|
||||
"ws": "^8.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strophe.js/node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.2.tgz",
|
||||
"integrity": "sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strophe.js/node_modules/ws": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||
@@ -29108,8 +29117,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1620.0.0+7f0012f7/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-F3vOUwD/ys5dGtswR3C5IK8JPDOG+71J+nhkEDLotkni/Zws8KHzf8Ye332UuetUjCTGJP3CbRFe9yJdYERe7g==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-0LK5EMCvtsAEQkjkrWzyniTl8BKtshAZxI3e9hGnA/RVDuULLXre7GEWd2zCP3tCfdxclkhqt7skQpfNhCTdqg==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
@@ -29125,7 +29134,7 @@
|
||||
"patch-package": "6.5.1",
|
||||
"promise.allsettled": "1.0.4",
|
||||
"sdp-transform": "2.3.0",
|
||||
"strophe.js": "1.6.0",
|
||||
"strophe.js": "1.5.0",
|
||||
"strophejs-plugin-disco": "0.0.2",
|
||||
"strophejs-plugin-stream-management": "git+https://github.com/jitsi/strophejs-plugin-stream-management#679be5902097ed612fb5062b5549f3f32b6f5f47",
|
||||
"uuid": "8.1.0",
|
||||
@@ -32836,16 +32845,22 @@
|
||||
"dev": true
|
||||
},
|
||||
"strophe.js": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.6.0.tgz",
|
||||
"integrity": "sha512-LE2B6nEJNUbF2Cl/p1tLIsXVJ9l86B/Z12HYYiO3n92VwYkhJ/5vJ+1ZMdwP9eN9GP8a3nbqfS5zE9umcK0FdA==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.5.0.tgz",
|
||||
"integrity": "sha512-H5tE/tZxPR5xP3jhXyQwsjnMSwQMf7vrn9r1OkufTApyGHYe8WjzhsfxtL3AFhVu7vFjXPPZBrmUOTm1ccYgOA==",
|
||||
"requires": {
|
||||
"@xmldom/xmldom": "0.8.7",
|
||||
"@xmldom/xmldom": "0.8.2",
|
||||
"abab": "^2.0.3",
|
||||
"karma-rollup-preprocessor": "^7.0.8",
|
||||
"ws": "^8.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.2.tgz",
|
||||
"integrity": "sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ==",
|
||||
"optional": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1620.0.0+7f0012f7/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -180,7 +180,7 @@
|
||||
},
|
||||
"overrides": {
|
||||
"strophe.js@1.6.0": {
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -129,14 +129,6 @@ export function appNavigate(uri?: string) {
|
||||
|
||||
dispatch(setLocationURL(locationURL));
|
||||
dispatch(setConfig(config));
|
||||
|
||||
if (inIframe() && getState()['features/base/config'].disableIframeAPI) {
|
||||
// in case iframeAPI is disabled redirect to the promotional page
|
||||
dispatch(redirectToStaticPage('static/close3.html', `#jitsi_meet_external_api_id=${API_ID}`));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(setRoom(room));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import '../base/media/middleware';
|
||||
import '../dynamic-branding/middleware';
|
||||
import '../e2ee/middleware';
|
||||
import '../external-api/middleware';
|
||||
import '../keyboard-shortcuts/middleware';
|
||||
import '../no-audio-signal/middleware';
|
||||
import '../notifications/middleware';
|
||||
import '../noise-detection/middleware';
|
||||
|
||||
@@ -3,6 +3,7 @@ import '../base/tooltip/reducer';
|
||||
import '../e2ee/reducer';
|
||||
import '../face-landmarks/reducer';
|
||||
import '../feedback/reducer';
|
||||
import '../keyboard-shortcuts/reducer';
|
||||
import '../no-audio-signal/reducer';
|
||||
import '../noise-detection/reducer';
|
||||
import '../participants-pane/reducer';
|
||||
|
||||
@@ -43,6 +43,7 @@ import { IGifsState } from '../gifs/reducer';
|
||||
import { IGoogleApiState } from '../google-api/reducer';
|
||||
import { IInviteState } from '../invite/reducer';
|
||||
import { IJaaSState } from '../jaas/reducer';
|
||||
import { IKeyboardShortcutsState } from '../keyboard-shortcuts/types';
|
||||
import { ILargeVideoState } from '../large-video/reducer';
|
||||
import { ILobbyState } from '../lobby/reducer';
|
||||
import { IMobileAudioModeState } from '../mobile/audio-mode/reducer';
|
||||
@@ -133,6 +134,7 @@ export interface IReduxState {
|
||||
'features/google-api': IGoogleApiState;
|
||||
'features/invite': IInviteState;
|
||||
'features/jaas': IJaaSState;
|
||||
'features/keyboard-shortcuts': IKeyboardShortcutsState;
|
||||
'features/large-video': ILargeVideoState;
|
||||
'features/lobby': ILobbyState;
|
||||
'features/mobile/audio-mode': IMobileAudioModeState;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { sendAnalytics } from '../../analytics/functions';
|
||||
import { appNavigate } from '../../app/actions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { endpointMessageReceived } from '../../subtitles/actions.any';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { getReplaceParticipant } from '../config/functions';
|
||||
import { disconnect } from '../connection/actions';
|
||||
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
|
||||
@@ -450,11 +451,12 @@ export function conferenceUniqueIdSet(conference: IJitsiConference) {
|
||||
*/
|
||||
export function _conferenceWillJoin(conference: IJitsiConference) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const localTracks
|
||||
= getLocalTracks(getState()['features/base/tracks'])
|
||||
= getLocalTracks(state['features/base/tracks'])
|
||||
.map(t => t.jitsiTrack);
|
||||
|
||||
if (localTracks.length) {
|
||||
if (localTracks.length && !iAmVisitor(state)) {
|
||||
_addLocalTracksToConference(conference, localTracks);
|
||||
}
|
||||
|
||||
@@ -806,7 +808,7 @@ export function setStartReactionsMuted(muted: boolean, updateBackend = false) {
|
||||
export function setPassword(
|
||||
conference: IJitsiConference | undefined,
|
||||
method: Function | undefined,
|
||||
password: string) {
|
||||
password?: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!conference) {
|
||||
return;
|
||||
|
||||
@@ -308,6 +308,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
muc: config.oldConfig.hosts.muc
|
||||
},
|
||||
focusUserJid: focusJid,
|
||||
disableLocalStats: false,
|
||||
bosh: config.oldConfig.bosh && appendURLParam(config.oldConfig.bosh, 'customusername', username),
|
||||
websocket: config.oldConfig.websocket
|
||||
&& appendURLParam(config.oldConfig.websocket, 'customusername', username),
|
||||
@@ -338,6 +339,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
},
|
||||
focusUserJid: focusJid,
|
||||
disableFocus: true, // This flag disables sending the initial conference request
|
||||
disableLocalStats: true,
|
||||
bosh: config.bosh && appendURLParam(config.bosh, 'vnode', vnode),
|
||||
websocket: config.websocket && appendURLParam(config.websocket, 'vnode', vnode)
|
||||
};
|
||||
|
||||
@@ -104,6 +104,7 @@ 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 class ExpandedLabel<P extends IProps> extends Component<P, IState> {
|
||||
export default abstract class ExpandedLabel<P extends IProps> extends Component<P, IState> {
|
||||
/**
|
||||
* Instantiates a new {@code ExpandedLabel} instance.
|
||||
*
|
||||
@@ -85,7 +85,7 @@ export default class ExpandedLabel<P extends IProps> extends Component<P, IState
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getLabel: () => string;
|
||||
abstract _getLabel(): string;
|
||||
|
||||
/**
|
||||
* Defines the color of the expanded label. This function returns a default
|
||||
|
||||
@@ -15,6 +15,8 @@ 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.props.noMatchesFound || '';
|
||||
const noMatchesFound = this.state.loading ? this.props.loadingMessage : this.props.noMatchesFound || '';
|
||||
const errorDialog = this._renderError();
|
||||
|
||||
return (
|
||||
@@ -200,7 +200,7 @@ class MultiSelectAutocomplete extends Component<IProps, IState> {
|
||||
error: this.state.error && Boolean(filterValue),
|
||||
filterValue,
|
||||
isOpen: Boolean(this.state.items.length) && Boolean(filterValue),
|
||||
items: filterValue ? this.state.items : [],
|
||||
items: [],
|
||||
loading: Boolean(filterValue)
|
||||
});
|
||||
if (filterValue) {
|
||||
|
||||
@@ -24,6 +24,8 @@ interface IProps {
|
||||
selectedItems?: MultiSelectItem[];
|
||||
}
|
||||
|
||||
const MULTI_SELECT_HEIGHT = 200;
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
@@ -41,7 +43,7 @@ const useStyles = makeStyles()(theme => {
|
||||
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
zIndex: 2,
|
||||
maxHeight: '400px',
|
||||
maxHeight: `${MULTI_SELECT_HEIGHT}px`,
|
||||
overflowY: 'auto',
|
||||
padding: '0'
|
||||
},
|
||||
@@ -128,7 +130,7 @@ const MultiSelect = ({
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
: <div>{noMatchesText}</div>
|
||||
: <div className = { classes.listItem }>{noMatchesText}</div>
|
||||
}
|
||||
</div>
|
||||
), [ items ]);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable lines-around-comment, max-len */
|
||||
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { navigate }
|
||||
// @ts-ignore
|
||||
from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
@@ -21,7 +22,7 @@ export * from './actions.any';
|
||||
* type: OPEN_CHAT
|
||||
* }}
|
||||
*/
|
||||
export function openChat(participant: Object, disablePolls?: boolean) {
|
||||
export function openChat(participant: IParticipant | undefined | Object, disablePolls?: boolean) {
|
||||
if (disablePolls) {
|
||||
navigate(screen.conference.chat);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Platform, ViewStyle } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
@@ -10,47 +11,42 @@ import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Callback to invoke on message send.
|
||||
*/
|
||||
onSend: Function,
|
||||
onSend: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
interface IState {
|
||||
|
||||
/**
|
||||
* Boolean to show if an extra padding needs to be added to the bar.
|
||||
*/
|
||||
addPadding: boolean,
|
||||
addPadding: boolean;
|
||||
|
||||
/**
|
||||
* The value of the input field.
|
||||
*/
|
||||
message: string,
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* Boolean to show or hide the send button.
|
||||
*/
|
||||
showSend: boolean
|
||||
};
|
||||
showSend: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the chat input bar with text field and action(s).
|
||||
*/
|
||||
class ChatInputBar extends Component<Props, State> {
|
||||
class ChatInputBar extends Component<IProps, IState> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -76,7 +72,7 @@ class ChatInputBar extends Component<Props, State> {
|
||||
style = { [
|
||||
styles.inputBar,
|
||||
this.state.addPadding ? styles.extraBarPadding : null
|
||||
] }>
|
||||
] as ViewStyle[] }>
|
||||
<Input
|
||||
blurOnSubmit = { false }
|
||||
customStyles = {{ container: styles.customInputContainer }}
|
||||
@@ -98,30 +94,26 @@ class ChatInputBar extends Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
_onChangeText: string => void;
|
||||
|
||||
/**
|
||||
* Callback to handle the change of the value of the text field.
|
||||
*
|
||||
* @param {string} text - The current value of the field.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChangeText(text) {
|
||||
_onChangeText(text: string) {
|
||||
this.setState({
|
||||
message: text,
|
||||
showSend: Boolean(text)
|
||||
});
|
||||
}
|
||||
|
||||
_onFocused: boolean => Function;
|
||||
|
||||
/**
|
||||
* Constructs a callback to be used to update the padding of the field if necessary.
|
||||
*
|
||||
* @param {boolean} focused - True of the field is focused.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onFocused(focused) {
|
||||
_onFocused(focused: boolean) {
|
||||
return () => {
|
||||
Platform.OS === 'android' && this.setState({
|
||||
addPadding: focused
|
||||
@@ -129,8 +121,6 @@ class ChatInputBar extends Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
_onSubmit: () => void;
|
||||
|
||||
/**
|
||||
* Callback to handle the submit event of the text field.
|
||||
*
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Text, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Linkify from '../../../base/react/components/native/Linkify';
|
||||
import { isGifMessage } from '../../../gifs/functions';
|
||||
import { isGifMessage } from '../../../gifs/functions.native';
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import { replaceNonUnicodeEmojis } from '../../functions';
|
||||
import AbstractChatMessage, { type Props } from '../AbstractChatMessage';
|
||||
import AbstractChatMessage, { IProps } from '../AbstractChatMessage';
|
||||
|
||||
import GifMessage from './GifMessage';
|
||||
import PrivateMessageButton from './PrivateMessageButton';
|
||||
@@ -18,7 +19,7 @@ import styles from './styles';
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*/
|
||||
class ChatMessage extends AbstractChatMessage<Props> {
|
||||
class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
@@ -31,18 +32,18 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
|
||||
// Style arrays that need to be updated in various scenarios, such as
|
||||
// error messages or others.
|
||||
const detailsWrapperStyle = [
|
||||
styles.detailsWrapper
|
||||
const detailsWrapperStyle: ViewStyle[] = [
|
||||
styles.detailsWrapper as ViewStyle
|
||||
];
|
||||
const messageBubbleStyle = [
|
||||
styles.messageBubble
|
||||
const messageBubbleStyle: ViewStyle[] = [
|
||||
styles.messageBubble as ViewStyle
|
||||
];
|
||||
|
||||
if (localMessage) {
|
||||
// This is a message sent by the local participant.
|
||||
|
||||
// The wrapper needs to be aligned to the right.
|
||||
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper);
|
||||
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper as ViewStyle);
|
||||
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(styles.localMessageBubble);
|
||||
@@ -69,11 +70,11 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
const messageText = replaceNonUnicodeEmojis(this._getMessageText());
|
||||
|
||||
return (
|
||||
<View style = { styles.messageWrapper } >
|
||||
<View style = { styles.messageWrapper as ViewStyle } >
|
||||
{ this._renderAvatar() }
|
||||
<View style = { detailsWrapperStyle }>
|
||||
<View style = { messageBubbleStyle }>
|
||||
<View style = { styles.textWrapper } >
|
||||
<View style = { styles.textWrapper as ViewStyle } >
|
||||
{ this._renderDisplayName() }
|
||||
{ isGifMessage(messageText)
|
||||
? <GifMessage message = { messageText } />
|
||||
@@ -94,12 +95,6 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_getFormattedTimestamp: () => string;
|
||||
|
||||
_getMessageText: () => string;
|
||||
|
||||
_getPrivateNoticeMessage: () => string;
|
||||
|
||||
/**
|
||||
* Renders the avatar of the sender.
|
||||
*
|
||||
@@ -171,7 +166,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.replyContainer }>
|
||||
<View style = { styles.replyContainer as ViewStyle }>
|
||||
<PrivateMessageButton
|
||||
isLobbyMessage = { lobbyChat }
|
||||
participantID = { message.id }
|
||||
@@ -204,9 +199,9 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
knocking: state['features/lobby'].knocking
|
||||
};
|
||||
@@ -1,28 +1,29 @@
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
import { IMessage } from '../../reducer';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The messages array to render.
|
||||
*/
|
||||
messages: Array<Object>
|
||||
/**
|
||||
* The messages array to render.
|
||||
*/
|
||||
messages: Array<IMessage>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a container to render all the chat messages in a conference.
|
||||
*/
|
||||
export default class ChatMessageGroup extends Component<Props> {
|
||||
export default class ChatMessageGroup extends Component<IProps> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
@@ -44,29 +45,25 @@ export default class ChatMessageGroup extends Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_keyExtractor: Object => string;
|
||||
|
||||
/**
|
||||
* Key extractor for the flatlist.
|
||||
*
|
||||
* @param {Object} item - The flatlist item that we need the key to be
|
||||
* @param {Object} _item - The flatlist item that we need the key to be
|
||||
* generated for.
|
||||
* @param {number} index - The index of the element.
|
||||
* @returns {string}
|
||||
*/
|
||||
_keyExtractor(item, index) {
|
||||
_keyExtractor(_item: Object, index: number) {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
_renderMessage: Object => ReactElement;
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*
|
||||
* @param {Object} message - The chat message to render.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderMessage({ index, item: message }) {
|
||||
_renderMessage({ index, item: message }: { index: number; item: IMessage; }) {
|
||||
return (
|
||||
<ChatMessage
|
||||
message = { message }
|
||||
@@ -1,26 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Image, View } from 'react-native';
|
||||
import { Image, ImageStyle, View } from 'react-native';
|
||||
|
||||
import { GIF_PREFIX } from '../../../gifs/constants';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The formatted gif message.
|
||||
*/
|
||||
message: string
|
||||
message: string;
|
||||
}
|
||||
|
||||
const GifMessage = ({ message }: Props) => {
|
||||
const GifMessage = ({ message }: IProps) => {
|
||||
const url = message.substring(GIF_PREFIX.length, message.length - 1);
|
||||
|
||||
return (<View
|
||||
style = { styles.gifContainer }>
|
||||
<Image
|
||||
source = {{ uri: url }}
|
||||
style = { styles.gifImage } />
|
||||
style = { styles.gifImage as ImageStyle } />
|
||||
</View>);
|
||||
};
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import AbstractMessageContainer, { type Props as AbstractProps }
|
||||
from '../AbstractMessageContainer';
|
||||
import { IMessage } from '../../reducer';
|
||||
import AbstractMessageContainer, { IProps as AbstractProps } from '../AbstractMessageContainer';
|
||||
|
||||
import ChatMessageGroup from './ChatMessageGroup';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
t: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a container to render all the chat messages in a conference.
|
||||
*/
|
||||
class MessageContainer extends AbstractMessageContainer<Props> {
|
||||
class MessageContainer extends AbstractMessageContainer<IProps, any> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
@@ -57,24 +57,18 @@ class MessageContainer extends AbstractMessageContainer<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_getMessagesGroupedBySender: () => Array<Array<Object>>;
|
||||
|
||||
_keyExtractor: Object => string;
|
||||
|
||||
/**
|
||||
* Key extractor for the flatlist.
|
||||
*
|
||||
* @param {Object} item - The flatlist item that we need the key to be
|
||||
* @param {Object} _item - The flatlist item that we need the key to be
|
||||
* generated for.
|
||||
* @param {number} index - The index of the element.
|
||||
* @returns {string}
|
||||
*/
|
||||
_keyExtractor(item, index) {
|
||||
_keyExtractor(_item: Object, index: number) {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
_renderListEmptyComponent: () => ReactElement;
|
||||
|
||||
/**
|
||||
* Renders a message when there are no messages in the chat yet.
|
||||
*
|
||||
@@ -84,23 +78,21 @@ class MessageContainer extends AbstractMessageContainer<Props> {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.emptyComponentWrapper }>
|
||||
<Text style = { styles.emptyComponentText }>
|
||||
<View style = { styles.emptyComponentWrapper as ViewStyle }>
|
||||
<Text style = { styles.emptyComponentText as TextStyle }>
|
||||
{ t('chat.noMessagesMessage') }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderMessageGroup: Object => ReactElement;
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*
|
||||
* @param {Array<Object>} messages - The chat message to render.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderMessageGroup({ item: messages }) {
|
||||
_renderMessageGroup({ item: messages }: { item: IMessage[]; }) {
|
||||
return <ChatMessageGroup messages = { messages } />;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,49 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { CHAT_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconMessage, IconReply } from '../../../base/icons/svg';
|
||||
import { getParticipantById } from '../../../base/participants/functions';
|
||||
import { IParticipant } from '../../../base/participants/types';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { handleLobbyChatInitialized, openChat } from '../../../chat/actions.native';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { handleLobbyChatInitialized, openChat } from '../../actions.native';
|
||||
|
||||
export type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant that the message is to be sent.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* True if the button is rendered as a reply button.
|
||||
*/
|
||||
reply: boolean,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* True if the polls feature is disabled.
|
||||
*/
|
||||
_isPollsDisabled: boolean,
|
||||
export interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* True if message is a lobby chat message.
|
||||
*/
|
||||
_isLobbyMessage: boolean,
|
||||
_isLobbyMessage: boolean;
|
||||
|
||||
/**
|
||||
* True if the polls feature is disabled.
|
||||
*/
|
||||
_isPollsDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* The participant object retrieved from Redux.
|
||||
*/
|
||||
_participant: Object,
|
||||
};
|
||||
_participant?: IParticipant;
|
||||
|
||||
/**
|
||||
* The ID of the participant that the message is to be sent.
|
||||
*/
|
||||
participantID: string;
|
||||
|
||||
/**
|
||||
* True if the button is rendered as a reply button.
|
||||
*/
|
||||
reply: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to render a button that initiates the sending of a private message through chet.
|
||||
* Class to render a button that initiates the sending of a private message through chat.
|
||||
*/
|
||||
class PrivateMessageButton extends AbstractButton<Props, any> {
|
||||
class PrivateMessageButton extends AbstractButton<IProps, any> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.privateMessage';
|
||||
icon = IconMessage;
|
||||
label = 'toolbar.privateMessage';
|
||||
@@ -99,10 +91,10 @@ class PrivateMessageButton extends AbstractButton<Props, any> {
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @returns {Props}
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
const { visible = enabled, isLobbyMessage, participantID } = ownProps;
|
||||
@@ -90,6 +90,8 @@ class ChatInput extends Component<IProps, IState> {
|
||||
if (isMobileBrowser()) {
|
||||
// Ensure textarea is not focused when opening chat on mobile browser.
|
||||
this._textArea?.current && this._textArea.current.blur();
|
||||
} else {
|
||||
this._focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
|
||||
12
react/features/conference/constants.ts
Normal file
12
react/features/conference/constants.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { IFRAME_EMBED_ALLOWED_LOCATIONS as ADDITIONAL_LOCATIONS } from './extraConstants';
|
||||
|
||||
/**
|
||||
* Timeout of the conference when iframe is disabled in minutes.
|
||||
*/
|
||||
export const IFRAME_DISABLED_TIMEOUT_MINUTES = 5;
|
||||
|
||||
/**
|
||||
* A list of allowed location to embed iframe.
|
||||
*/
|
||||
/* eslint-disable-next-line no-extra-parens*/
|
||||
export const IFRAME_EMBED_ALLOWED_LOCATIONS = ([] as string[]).concat(ADDITIONAL_LOCATIONS);
|
||||
5
react/features/conference/extraConstants.ts
Normal file
5
react/features/conference/extraConstants.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Deploy-specific configuration constants.
|
||||
*/
|
||||
|
||||
export const IFRAME_EMBED_ALLOWED_LOCATIONS = [];
|
||||
@@ -1,8 +1,11 @@
|
||||
import i18n from 'i18next';
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
// @ts-expect-error
|
||||
import { API_ID } from '../../../modules/API/constants';
|
||||
import { appNavigate } from '../app/actions';
|
||||
import { IStore } from '../app/types';
|
||||
import { redirectToStaticPage } from '../app/actions.any';
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
@@ -15,17 +18,22 @@ import { getURLWithoutParamsNormalized } from '../base/connection/utils';
|
||||
import { hideDialog } from '../base/dialog/actions';
|
||||
import { isDialogOpen } from '../base/dialog/functions';
|
||||
import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
|
||||
import { translateToHTML } from '../base/i18n/functions';
|
||||
import i18next from '../base/i18n/i18next';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { pinParticipant } from '../base/participants/actions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes';
|
||||
import { BUTTON_TYPES } from '../base/ui/constants.any';
|
||||
import { inIframe } from '../base/util/iframeUtils';
|
||||
import { isCalendarEnabled } from '../calendar-sync/functions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import FeedbackDialog from '../feedback/components/FeedbackDialog';
|
||||
import { setFilmstripEnabled } from '../filmstrip/actions.any';
|
||||
import { hideNotification, showNotification } from '../notifications/actions';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
import { hideNotification, showNotification, showWarningNotification } from '../notifications/actions';
|
||||
import {
|
||||
CALENDAR_NOTIFICATION_ID,
|
||||
NOTIFICATION_ICON,
|
||||
@@ -36,6 +44,7 @@ import { setToolboxEnabled } from '../toolbox/actions.any';
|
||||
|
||||
import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
|
||||
import { dismissCalendarNotification, notifyKickedOut } from './actions';
|
||||
import { IFRAME_DISABLED_TIMEOUT_MINUTES, IFRAME_EMBED_ALLOWED_LOCATIONS } from './constants';
|
||||
|
||||
|
||||
let intervalID: any;
|
||||
@@ -155,6 +164,49 @@ function _conferenceJoined({ dispatch, getState }: IStore) {
|
||||
}
|
||||
|
||||
dispatch(showSalesforceNotification());
|
||||
_checkIframe(getState(), dispatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional checks for embedding in iframe.
|
||||
*
|
||||
* @param {IReduxState} state - The current state of the app.
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _checkIframe(state: IReduxState, dispatch: IStore['dispatch']) {
|
||||
let allowIframe = false;
|
||||
|
||||
if (document.referrer === '') {
|
||||
// no iframe
|
||||
allowIframe = true;
|
||||
} else {
|
||||
try {
|
||||
allowIframe = IFRAME_EMBED_ALLOWED_LOCATIONS.includes(new URL(document.referrer).hostname);
|
||||
} catch (e) {
|
||||
// wrong URL in referrer
|
||||
}
|
||||
}
|
||||
|
||||
if (inIframe() && state['features/base/config'].disableIframeAPI && !browser.isElectron()
|
||||
&& !isVpaasMeeting(state) && !allowIframe) {
|
||||
// show sticky notification and redirect in 5 minutes
|
||||
dispatch(showWarningNotification({
|
||||
description: translateToHTML(
|
||||
i18next.t.bind(i18next),
|
||||
'notify.disabledIframe',
|
||||
{
|
||||
timeout: IFRAME_DISABLED_TIMEOUT_MINUTES
|
||||
}
|
||||
)
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
setTimeout(() => {
|
||||
// redirect to the promotional page
|
||||
dispatch(redirectToStaticPage('static/close3.html', `#jitsi_meet_external_api_id=${API_ID}`));
|
||||
}, IFRAME_DISABLED_TIMEOUT_MINUTES * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ import { translate } from '../../../base/i18n/functions';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
|
||||
import { getHideSelfView } from '../../../base/settings/functions.any';
|
||||
import { registerShortcut, unregisterShortcut } from '../../../keyboard-shortcuts/actions';
|
||||
import { showToolbox } from '../../../toolbox/actions.web';
|
||||
import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
import { LAYOUTS } from '../../../video-layout/constants';
|
||||
@@ -295,12 +296,12 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
'F',
|
||||
'filmstripPopover',
|
||||
this._onShortcutToggleFilmstrip,
|
||||
'keyboardShortcuts.toggleFilmstrip'
|
||||
);
|
||||
this.props.dispatch(registerShortcut({
|
||||
character: 'F',
|
||||
helpDescription: 'keyboardShortcuts.toggleFilmstrip',
|
||||
handler: this._onShortcutToggleFilmstrip
|
||||
}));
|
||||
|
||||
document.addEventListener('mouseup', this._onDragMouseUp);
|
||||
|
||||
// @ts-ignore
|
||||
@@ -313,7 +314,8 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
APP.keyboardshortcut.unregisterShortcut('F');
|
||||
this.props.dispatch(unregisterShortcut('F'));
|
||||
|
||||
document.removeEventListener('mouseup', this._onDragMouseUp);
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -68,7 +68,7 @@ export interface IProps {
|
||||
/**
|
||||
* Whether or not to allow sip invites.
|
||||
*/
|
||||
_sipInviteEnabled: boolean;
|
||||
_sipInviteEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
@@ -92,7 +92,7 @@ export interface IState {
|
||||
/**
|
||||
* The list of invite items.
|
||||
*/
|
||||
inviteItems: Array<IInviteSelectItem>;
|
||||
inviteItems: Array<IInvitee | IInviteSelectItem>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
@@ -23,7 +22,6 @@ import {
|
||||
IconSearch,
|
||||
IconShare
|
||||
} from '../../../../base/icons/svg';
|
||||
// @ts-ignore
|
||||
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
|
||||
import AvatarListItem from '../../../../base/react/components/native/AvatarListItem';
|
||||
import { Item } from '../../../../base/react/types';
|
||||
@@ -33,19 +31,16 @@ import HeaderNavigationButton
|
||||
from '../../../../mobile/navigation/components/HeaderNavigationButton';
|
||||
import { beginShareRoom } from '../../../../share-room/actions';
|
||||
import { INVITE_TYPES } from '../../../constants';
|
||||
import { IInviteSelectItem, IInvitee } from '../../../types';
|
||||
import AbstractAddPeopleDialog, {
|
||||
// @ts-ignore
|
||||
type Props as AbstractProps,
|
||||
// @ts-ignore
|
||||
type State as AbstractState,
|
||||
type IProps as AbstractProps,
|
||||
type IState as AbstractState,
|
||||
_mapStateToProps as _abstractMapStateToProps
|
||||
} from '../AbstractAddPeopleDialog';
|
||||
|
||||
// @ts-ignore
|
||||
import styles, { AVATAR_SIZE } from './styles';
|
||||
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
interface IProps extends AbstractProps, WithTranslation {
|
||||
|
||||
/**
|
||||
* True if the invite dialog should be open, false otherwise.
|
||||
@@ -55,12 +50,7 @@ interface IProps extends AbstractProps {
|
||||
/**
|
||||
* Default prop for navigation between screen components(React Navigation).
|
||||
*/
|
||||
navigation: Object;
|
||||
|
||||
/**
|
||||
* Function used to translate i18n labels.
|
||||
*/
|
||||
t: Function;
|
||||
navigation: any;
|
||||
|
||||
/**
|
||||
* Theme used for styles.
|
||||
@@ -95,9 +85,7 @@ interface IState extends AbstractState {
|
||||
/**
|
||||
* Implements a special dialog to invite people from a directory service.
|
||||
*/
|
||||
class AddPeopleDialog
|
||||
// @ts-ignore
|
||||
extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
/**
|
||||
* Default state object to reset the state to when needed.
|
||||
*/
|
||||
@@ -119,7 +107,7 @@ class AddPeopleDialog
|
||||
searchTimeout: number;
|
||||
|
||||
/**
|
||||
* Contrustor of the component.
|
||||
* Constructor of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
@@ -151,7 +139,6 @@ class AddPeopleDialog
|
||||
componentDidMount() {
|
||||
const { navigation, t } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
navigation.setOptions({
|
||||
headerRight: () => (
|
||||
<HeaderNavigationButton
|
||||
@@ -171,7 +158,6 @@ class AddPeopleDialog
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
const { navigation, t } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
navigation.setOptions({
|
||||
// eslint-disable-next-line react/no-multi-comp
|
||||
headerRight: () => (
|
||||
@@ -197,12 +183,9 @@ class AddPeopleDialog
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
// @ts-ignore
|
||||
_addPeopleEnabled,
|
||||
// @ts-ignore
|
||||
_dialOutEnabled
|
||||
} = this.props;
|
||||
// @ts-ignore
|
||||
const { inviteItems, selectableItems } = this.state;
|
||||
|
||||
let placeholderKey = 'searchPlaceholder';
|
||||
@@ -302,7 +285,6 @@ class AddPeopleDialog
|
||||
});
|
||||
|
||||
// Clear search results
|
||||
// @ts-ignore
|
||||
this._onTypeQuery('');
|
||||
}
|
||||
|
||||
@@ -314,10 +296,9 @@ class AddPeopleDialog
|
||||
_onInvite() {
|
||||
// @ts-ignore
|
||||
this._invite(this.state.inviteItems)
|
||||
.then((invitesLeftToSend: ArrayLike<any>) => {
|
||||
.then((invitesLeftToSend: IInvitee[]) => {
|
||||
if (invitesLeftToSend.length) {
|
||||
this.setState({
|
||||
// @ts-ignore
|
||||
inviteItems: invitesLeftToSend
|
||||
});
|
||||
this._showFailedInviteAlert();
|
||||
@@ -333,26 +314,22 @@ class AddPeopleDialog
|
||||
*/
|
||||
_onPressItem(item: Item) {
|
||||
return () => {
|
||||
// @ts-ignore
|
||||
const { inviteItems } = this.state;
|
||||
const finderKey = item.type === INVITE_TYPES.PHONE ? 'number' : 'user_id';
|
||||
|
||||
if (inviteItems.find(
|
||||
// @ts-ignore
|
||||
_.matchesProperty(finderKey, item[finderKey]))) {
|
||||
_.matchesProperty(finderKey, item[finderKey as keyof typeof item]))) {
|
||||
// Item is already selected, need to unselect it.
|
||||
this.setState({
|
||||
// @ts-ignore
|
||||
inviteItems: inviteItems.filter(
|
||||
// @ts-ignore
|
||||
(element: any) => item[finderKey] !== element[finderKey])
|
||||
(element: any) => item[finderKey as keyof typeof item] !== element[finderKey])
|
||||
});
|
||||
} else {
|
||||
// Item is not selected yet, need to add to the list.
|
||||
const items: Array<Object> = inviteItems.concat(item);
|
||||
// @ts-ignore
|
||||
const items = inviteItems.concat(item);
|
||||
|
||||
this.setState({
|
||||
// @ts-ignore
|
||||
inviteItems: _.sortBy(items, [ 'name', 'number' ])
|
||||
});
|
||||
}
|
||||
@@ -365,12 +342,10 @@ class AddPeopleDialog
|
||||
* @returns {void}
|
||||
*/
|
||||
_onShareMeeting() {
|
||||
// @ts-ignore
|
||||
if (this.state.inviteItems.length > 0) {
|
||||
// The use probably intended to invite people.
|
||||
this._onInvite();
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.props.dispatch(beginShareRoom());
|
||||
}
|
||||
}
|
||||
@@ -388,7 +363,6 @@ class AddPeopleDialog
|
||||
});
|
||||
|
||||
clearTimeout(this.searchTimeout);
|
||||
// @ts-ignore
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.setState({
|
||||
searchInprogress: true
|
||||
@@ -462,9 +436,8 @@ class AddPeopleDialog
|
||||
*/
|
||||
_renderItem(flatListItem: any, index: number): ReactElement | null {
|
||||
const { item } = flatListItem;
|
||||
// @ts-ignore
|
||||
const { inviteItems } = this.state;
|
||||
let selected = false;
|
||||
let selected: IInvitee | IInviteSelectItem | undefined | boolean = false;
|
||||
const renderableItem = this._getRenderableItem(flatListItem);
|
||||
|
||||
if (!renderableItem) {
|
||||
@@ -570,7 +543,6 @@ class AddPeopleDialog
|
||||
* @returns {void}
|
||||
*/
|
||||
_showFailedInviteAlert() {
|
||||
// @ts-ignore
|
||||
this.props.dispatch(openDialog(AlertDialog, {
|
||||
contentKey: {
|
||||
key: 'inviteDialog.alertText'
|
||||
@@ -583,14 +555,15 @@ class AddPeopleDialog
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {any} _ownProps - Component's own props.
|
||||
* @returns {{
|
||||
* _isVisible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state)
|
||||
};
|
||||
}
|
||||
// @ts-ignore
|
||||
|
||||
export default translate(connect(_mapStateToProps)(AddPeopleDialog));
|
||||
|
||||
@@ -325,7 +325,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
|
||||
const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
|
||||
const users = response.filter(item => userTypes.includes(item.type));
|
||||
const userDisplayItems = [];
|
||||
const userDisplayItems: any = [];
|
||||
|
||||
for (const user of users) {
|
||||
const { name, phone } = user;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import { Route } from '@react-navigation/native';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Linking, View } from 'react-native';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Linking, View, ViewStyle } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import { connect } from 'react-redux';
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import { IStore } from '../../../../app/types';
|
||||
import { openDialog } from '../../../../base/dialog/actions';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
|
||||
@@ -15,38 +15,32 @@ import { getDialInfoPageURLForURIString } from '../../../functions';
|
||||
import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
|
||||
import styles, { INDICATOR_COLOR } from './styles';
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
type Props = {
|
||||
|
||||
dispatch: Dispatch<any>,
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
navigation: Object,
|
||||
navigation: any;
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
route: Object,
|
||||
|
||||
/**
|
||||
* Translation function.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
route: Route<'', { summaryUrl: string; }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React native component that displays the dial in info page for a specific room.
|
||||
*/
|
||||
class DialInSummary extends PureComponent<Props> {
|
||||
class DialInSummary extends PureComponent<IProps> {
|
||||
|
||||
/**
|
||||
* Initializes a new instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onError = this._onError.bind(this);
|
||||
@@ -86,15 +80,13 @@ class DialInSummary extends PureComponent<Props> {
|
||||
onShouldStartLoadWithRequest = { this._onNavigate }
|
||||
renderLoading = { this._renderLoading }
|
||||
setSupportMultipleWindows = { false }
|
||||
source = {{ uri: getDialInfoPageURLForURIString(summaryUrl) }}
|
||||
source = {{ uri: getDialInfoPageURLForURIString(summaryUrl) ?? '' }}
|
||||
startInLoadingState = { true }
|
||||
style = { styles.webView } />
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
_onError: () => void;
|
||||
|
||||
/**
|
||||
* Callback to handle the error if the page fails to load.
|
||||
*
|
||||
@@ -104,8 +96,6 @@ class DialInSummary extends PureComponent<Props> {
|
||||
this.props.dispatch(openDialog(DialInSummaryErrorDialog));
|
||||
}
|
||||
|
||||
_onNavigate: Object => Boolean;
|
||||
|
||||
/**
|
||||
* Callback to intercept navigation inside the webview and make the native app handle the dial requests.
|
||||
*
|
||||
@@ -114,7 +104,7 @@ class DialInSummary extends PureComponent<Props> {
|
||||
* @param {any} request - The request object.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onNavigate(request) {
|
||||
_onNavigate(request: { url: string; }) {
|
||||
const { url } = request;
|
||||
const { route } = this.props;
|
||||
const summaryUrl = route.params?.summaryUrl;
|
||||
@@ -126,8 +116,6 @@ class DialInSummary extends PureComponent<Props> {
|
||||
return url === getDialInfoPageURLForURIString(summaryUrl);
|
||||
}
|
||||
|
||||
_renderLoading: () => React$Component<any>;
|
||||
|
||||
/**
|
||||
* Renders the loading indicator.
|
||||
*
|
||||
@@ -135,7 +123,7 @@ class DialInSummary extends PureComponent<Props> {
|
||||
*/
|
||||
_renderLoading() {
|
||||
return (
|
||||
<View style = { styles.indicatorWrapper }>
|
||||
<View style = { styles.indicatorWrapper as ViewStyle }>
|
||||
<LoadingIndicator
|
||||
color = { INDICATOR_COLOR }
|
||||
size = 'large' />
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import AlertDialog from '../../../../base/dialog/components/native/AlertDialog';
|
||||
@@ -9,7 +8,7 @@ import { translate } from '../../../../base/i18n/functions';
|
||||
/**
|
||||
* Dialog to inform the user that we couldn't fetch the dial-in info page.
|
||||
*/
|
||||
class DialInSummaryErrorDialog extends Component<{}> {
|
||||
class DialInSummaryErrorDialog extends Component<WithTranslation> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -22,8 +21,6 @@ class DialInSummaryErrorDialog extends Component<{}> {
|
||||
contentKey = 'info.dialInSummaryError' />
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
}
|
||||
|
||||
export default translate(connect()(DialInSummaryErrorDialog));
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
export const INDICATOR_COLOR = BaseTheme.palette.ui07;
|
||||
@@ -31,7 +31,11 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRadius: 6
|
||||
borderRadius: 6,
|
||||
|
||||
'& *': {
|
||||
userSelect: 'text'
|
||||
}
|
||||
},
|
||||
confNameLabel: {
|
||||
...withPixelLineHeight(theme.typography.heading6),
|
||||
|
||||
24
react/features/keyboard-shortcuts/actionTypes.ts
Normal file
24
react/features/keyboard-shortcuts/actionTypes.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* The type of the action which signals that the keyboard shortcuts should be initialized.
|
||||
*/
|
||||
export const INIT_KEYBOARD_SHORTCUTS = 'INIT_KEYBOARD_SHORTCUTS';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a keyboard shortcut should be registered.
|
||||
*/
|
||||
export const REGISTER_KEYBOARD_SHORTCUT = 'REGISTER_KEYBOARD_SHORTCUT';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a keyboard shortcut should be unregistered.
|
||||
*/
|
||||
export const UNREGISTER_KEYBOARD_SHORTCUT = 'UNREGISTER_KEYBOARD_SHORTCUT';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a keyboard shortcut should be enabled.
|
||||
*/
|
||||
export const ENABLE_KEYBOARD_SHORTCUTS = 'ENABLE_KEYBOARD_SHORTCUTS';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a keyboard shortcut should be disabled.
|
||||
*/
|
||||
export const DISABLE_KEYBOARD_SHORTCUTS = 'DISABLE_KEYBOARD_SHORTCUTS';
|
||||
60
react/features/keyboard-shortcuts/actions.any.ts
Normal file
60
react/features/keyboard-shortcuts/actions.any.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import {
|
||||
DISABLE_KEYBOARD_SHORTCUTS,
|
||||
ENABLE_KEYBOARD_SHORTCUTS,
|
||||
REGISTER_KEYBOARD_SHORTCUT,
|
||||
UNREGISTER_KEYBOARD_SHORTCUT
|
||||
} from './actionTypes';
|
||||
import { IKeyboardShortcut } from './types';
|
||||
|
||||
/**
|
||||
* Action to register a new shortcut.
|
||||
*
|
||||
* @param {IKeyboardShortcut} shortcut - The shortcut to register.
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export const registerShortcut = (shortcut: IKeyboardShortcut): AnyAction => {
|
||||
return {
|
||||
type: REGISTER_KEYBOARD_SHORTCUT,
|
||||
shortcut
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Action to unregister a shortcut.
|
||||
*
|
||||
* @param {string} character - The character of the shortcut to unregister.
|
||||
* @param {boolean} altKey - Whether the shortcut used altKey.
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export const unregisterShortcut = (character: string, altKey = false): AnyAction => {
|
||||
return {
|
||||
altKey,
|
||||
type: UNREGISTER_KEYBOARD_SHORTCUT,
|
||||
character
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Action to enable keyboard shortcuts.
|
||||
*
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export const enableKeyboardShortcuts = (): AnyAction => {
|
||||
return {
|
||||
type: ENABLE_KEYBOARD_SHORTCUTS
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Action to enable keyboard shortcuts.
|
||||
*
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export const disableKeyboardShortcuts = (): AnyAction => {
|
||||
return {
|
||||
type: DISABLE_KEYBOARD_SHORTCUTS
|
||||
};
|
||||
};
|
||||
1
react/features/keyboard-shortcuts/actions.native.ts
Normal file
1
react/features/keyboard-shortcuts/actions.native.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './actions.any';
|
||||
119
react/features/keyboard-shortcuts/actions.web.ts
Normal file
119
react/features/keyboard-shortcuts/actions.web.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { ACTION_SHORTCUT_PRESSED, ACTION_SHORTCUT_RELEASED, createShortcutEvent } from '../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../analytics/functions';
|
||||
import { IStore } from '../app/types';
|
||||
import { clickOnVideo } from '../filmstrip/actions.web';
|
||||
import { openSettingsDialog } from '../settings/actions.web';
|
||||
import { SETTINGS_TABS } from '../settings/constants';
|
||||
|
||||
import { registerShortcut } from './actions.any';
|
||||
import { areKeyboardShortcutsEnabled, getKeyboardShortcuts } from './functions';
|
||||
import logger from './logger';
|
||||
import { getKeyboardKey, getPriorityFocusedElement } from './utils';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Initialise global shortcuts.
|
||||
* Global shortcuts are shortcuts for features that don't have a button or
|
||||
* link associated with the action. In other words they represent actions
|
||||
* triggered _only_ with a shortcut.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
const initGlobalKeyboardShortcuts = () =>
|
||||
(dispatch: IStore['dispatch']) => {
|
||||
batch(() => {
|
||||
dispatch(registerShortcut({
|
||||
character: '?',
|
||||
helpDescription: 'keyboardShortcuts.toggleShortcuts',
|
||||
handler: () => {
|
||||
sendAnalytics(createShortcutEvent('help'));
|
||||
dispatch(openSettingsDialog(SETTINGS_TABS.SHORTCUTS, false));
|
||||
}
|
||||
}));
|
||||
|
||||
// register SPACE shortcut in two steps to insure visibility of help message
|
||||
dispatch(registerShortcut({
|
||||
character: ' ',
|
||||
helpCharacter: 'SPACE',
|
||||
helpDescription: 'keyboardShortcuts.pushToTalk',
|
||||
handler: () => {
|
||||
sendAnalytics(createShortcutEvent('push.to.talk', ACTION_SHORTCUT_RELEASED));
|
||||
logger.log('Talk shortcut released');
|
||||
APP.conference.muteAudio(true);
|
||||
}
|
||||
}));
|
||||
|
||||
dispatch(registerShortcut({
|
||||
character: '0',
|
||||
helpDescription: 'keyboardShortcuts.focusLocal',
|
||||
handler: () => {
|
||||
dispatch(clickOnVideo(0));
|
||||
}
|
||||
}));
|
||||
|
||||
Array(9).fill(1)
|
||||
.forEach((_, index) => {
|
||||
const num = index + 1;
|
||||
|
||||
dispatch(registerShortcut({
|
||||
character: `${num}`,
|
||||
|
||||
// only show help hint for the first shortcut
|
||||
helpCharacter: num === 1 ? '1-9' : undefined,
|
||||
helpDescription: num === 1 ? 'keyboardShortcuts.focusRemote' : undefined,
|
||||
handler: () => {
|
||||
dispatch(clickOnVideo(num));
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes keyboard shortcuts.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export const initKeyboardShortcuts = () =>
|
||||
(dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
dispatch(initGlobalKeyboardShortcuts());
|
||||
|
||||
window.onkeyup = (e: KeyboardEvent) => {
|
||||
const state = getState();
|
||||
const enabled = areKeyboardShortcutsEnabled(state);
|
||||
const shortcuts = getKeyboardShortcuts(state);
|
||||
|
||||
if (!enabled || getPriorityFocusedElement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = getKeyboardKey(e).toUpperCase();
|
||||
|
||||
if (shortcuts.has(key)) {
|
||||
shortcuts.get(key)?.handler(e);
|
||||
}
|
||||
};
|
||||
|
||||
window.onkeydown = (e: KeyboardEvent) => {
|
||||
const state = getState();
|
||||
const enabled = areKeyboardShortcutsEnabled(state);
|
||||
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusedElement = getPriorityFocusedElement();
|
||||
const key = getKeyboardKey(e).toUpperCase();
|
||||
|
||||
if (key === ' ' && !focusedElement) {
|
||||
sendAnalytics(createShortcutEvent('push.to.talk', ACTION_SHORTCUT_PRESSED));
|
||||
logger.log('Talk shortcut pressed');
|
||||
APP.conference.muteAudio(false);
|
||||
} else if (key === 'ESCAPE') {
|
||||
focusedElement?.blur();
|
||||
}
|
||||
};
|
||||
};
|
||||
31
react/features/keyboard-shortcuts/functions.ts
Normal file
31
react/features/keyboard-shortcuts/functions.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
|
||||
/**
|
||||
* Returns whether or not the keyboard shortcuts are enabled.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} - Whether or not the keyboard shortcuts are enabled.
|
||||
*/
|
||||
export function areKeyboardShortcutsEnabled(state: IReduxState) {
|
||||
return state['features/keyboard-shortcuts'].enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keyboard shortcuts map.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Map} - The keyboard shortcuts map.
|
||||
*/
|
||||
export function getKeyboardShortcuts(state: IReduxState) {
|
||||
return state['features/keyboard-shortcuts'].shortcuts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keyboard shortcuts help descriptions.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Map} - The keyboard shortcuts help descriptions.
|
||||
*/
|
||||
export function getKeyboardShortcutsHelpDescriptions(state: IReduxState) {
|
||||
return state['features/keyboard-shortcuts'].shortcutsHelp;
|
||||
}
|
||||
3
react/features/keyboard-shortcuts/logger.ts
Normal file
3
react/features/keyboard-shortcuts/logger.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/keyboard-shortcuts');
|
||||
39
react/features/keyboard-shortcuts/middleware.ts
Normal file
39
react/features/keyboard-shortcuts/middleware.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { SET_CONFIG } from '../base/config/actionTypes';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { CAPTURE_EVENTS } from '../remote-control/actionTypes';
|
||||
|
||||
import { disableKeyboardShortcuts, enableKeyboardShortcuts } from './actions';
|
||||
|
||||
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: any) => {
|
||||
const { dispatch } = store;
|
||||
|
||||
switch (action.type) {
|
||||
case CAPTURE_EVENTS:
|
||||
if (action.isCapturingEvents) {
|
||||
dispatch(disableKeyboardShortcuts());
|
||||
} else {
|
||||
dispatch(enableKeyboardShortcuts());
|
||||
}
|
||||
|
||||
return next(action);
|
||||
case SET_CONFIG: {
|
||||
const result = next(action);
|
||||
|
||||
const state = store.getState();
|
||||
const { disableShortcuts } = state['features/base/config'];
|
||||
|
||||
if (disableShortcuts !== undefined) {
|
||||
if (disableShortcuts) {
|
||||
dispatch(disableKeyboardShortcuts());
|
||||
} else {
|
||||
dispatch(enableKeyboardShortcuts());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
72
react/features/keyboard-shortcuts/reducer.ts
Normal file
72
react/features/keyboard-shortcuts/reducer.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import PersistenceRegistry from '../base/redux/PersistenceRegistry';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
DISABLE_KEYBOARD_SHORTCUTS,
|
||||
ENABLE_KEYBOARD_SHORTCUTS,
|
||||
REGISTER_KEYBOARD_SHORTCUT,
|
||||
UNREGISTER_KEYBOARD_SHORTCUT
|
||||
} from './actionTypes';
|
||||
import { IKeyboardShortcutsState } from './types';
|
||||
|
||||
/**
|
||||
* The redux subtree of this feature.
|
||||
*/
|
||||
const STORE_NAME = 'features/keyboard-shortcuts';
|
||||
|
||||
const defaultState = {
|
||||
enabled: true,
|
||||
shortcuts: new Map(),
|
||||
shortcutsHelp: new Map()
|
||||
};
|
||||
|
||||
PersistenceRegistry.register(STORE_NAME, {
|
||||
enabled: true
|
||||
});
|
||||
|
||||
ReducerRegistry.register<IKeyboardShortcutsState>(STORE_NAME,
|
||||
(state = defaultState, action): IKeyboardShortcutsState => {
|
||||
switch (action.type) {
|
||||
case ENABLE_KEYBOARD_SHORTCUTS:
|
||||
return {
|
||||
...state,
|
||||
enabled: true
|
||||
};
|
||||
case DISABLE_KEYBOARD_SHORTCUTS:
|
||||
return {
|
||||
...state,
|
||||
enabled: false
|
||||
};
|
||||
case REGISTER_KEYBOARD_SHORTCUT: {
|
||||
const shortcutKey = action.shortcut.alt ? `:${action.shortcut.character}` : action.shortcut.character;
|
||||
|
||||
return {
|
||||
...state,
|
||||
shortcuts: new Map(state.shortcuts)
|
||||
.set(shortcutKey, action.shortcut),
|
||||
shortcutsHelp: action.shortcut.helpDescription
|
||||
? new Map(state.shortcutsHelp)
|
||||
.set(action.shortcut.helpCharacter ?? shortcutKey, action.shortcut.helpDescription)
|
||||
: state.shortcutsHelp
|
||||
};
|
||||
}
|
||||
case UNREGISTER_KEYBOARD_SHORTCUT: {
|
||||
const shortcutKey = action.alt ? `:${action.character}` : action.character;
|
||||
const shortcuts = new Map(state.shortcuts);
|
||||
|
||||
shortcuts.delete(shortcutKey);
|
||||
|
||||
const shortcutsHelp = new Map(state.shortcutsHelp);
|
||||
|
||||
shortcutsHelp.delete(shortcutKey);
|
||||
|
||||
return {
|
||||
...state,
|
||||
shortcuts,
|
||||
shortcutsHelp
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
23
react/features/keyboard-shortcuts/types.ts
Normal file
23
react/features/keyboard-shortcuts/types.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export interface IKeyboardShortcut {
|
||||
|
||||
// whether or not the alt key must be pressed
|
||||
alt?: boolean;
|
||||
|
||||
// the character to be pressed that triggers the action
|
||||
character: string;
|
||||
|
||||
// the function to be executed when the shortcut is pressed
|
||||
handler: Function;
|
||||
|
||||
// character to be displayed in the help dialog shortcuts list
|
||||
helpCharacter?: string;
|
||||
|
||||
// help description of the shortcut, to be displayed in the help dialog
|
||||
helpDescription?: string;
|
||||
}
|
||||
|
||||
export interface IKeyboardShortcutsState {
|
||||
enabled: boolean;
|
||||
shortcuts: Map<string, IKeyboardShortcut>;
|
||||
shortcutsHelp: Map<string, string>;
|
||||
}
|
||||
75
react/features/keyboard-shortcuts/utils.ts
Normal file
75
react/features/keyboard-shortcuts/utils.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Prefer keyboard handling of these elements over global shortcuts.
|
||||
* If a button is triggered using the Spacebar it should not trigger PTT.
|
||||
* If an input element is focused and M is pressed it should not mute audio.
|
||||
*/
|
||||
const _elementsBlacklist = [
|
||||
'input',
|
||||
'textarea',
|
||||
'button',
|
||||
'[role=button]',
|
||||
'[role=menuitem]',
|
||||
'[role=radio]',
|
||||
'[role=tab]',
|
||||
'[role=option]',
|
||||
'[role=switch]',
|
||||
'[role=range]',
|
||||
'[role=log]'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the currently focused element if it is not blacklisted.
|
||||
*
|
||||
* @returns {HTMLElement|null} - The currently focused element.
|
||||
*/
|
||||
export const getPriorityFocusedElement = (): HTMLElement | null =>
|
||||
document.querySelector(`:focus:is(${_elementsBlacklist.join(',')})`);
|
||||
|
||||
/**
|
||||
* Returns the keyboard key from a KeyboardEvent.
|
||||
*
|
||||
* @param {KeyboardEvent} e - The KeyboardEvent.
|
||||
* @returns {string} - The keyboard key.
|
||||
*/
|
||||
export const getKeyboardKey = (e: KeyboardEvent): string => {
|
||||
// @ts-ignore
|
||||
const { altKey, code, key, shiftKey, type, which } = e;
|
||||
|
||||
// If alt is pressed a different char can be returned so this takes
|
||||
// the char from the code. It also prefixes with a colon to differentiate
|
||||
// alt combo from simple keypress.
|
||||
if (altKey) {
|
||||
const replacedKey = code.replace('Key', '');
|
||||
|
||||
return `:${replacedKey}`;
|
||||
}
|
||||
|
||||
// If e.key is a string, then it is assumed it already plainly states
|
||||
// the key pressed. This may not be true in all cases, such as with Edge
|
||||
// and "?", when the browser cannot properly map a key press event to a
|
||||
// keyboard key. To be safe, when a key is "Unidentified" it must be
|
||||
// further analyzed by jitsi to a key using e.which.
|
||||
if (typeof key === 'string' && key !== 'Unidentified') {
|
||||
return key;
|
||||
}
|
||||
|
||||
if (type === 'keypress'
|
||||
&& ((which >= 32 && which <= 126)
|
||||
|| (which >= 160 && which <= 255))) {
|
||||
return String.fromCharCode(which);
|
||||
}
|
||||
|
||||
// try to fallback (0-9A-Za-z and QWERTY keyboard)
|
||||
switch (which) {
|
||||
case 27:
|
||||
return 'Escape';
|
||||
case 191:
|
||||
return shiftKey ? '?' : '/';
|
||||
}
|
||||
|
||||
if (shiftKey || type === 'keypress') {
|
||||
return String.fromCharCode(which);
|
||||
}
|
||||
|
||||
return String.fromCharCode(which).toLowerCase();
|
||||
};
|
||||
@@ -1,77 +1,76 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { JitsiTrackEvents } from '../../base/lib-jitsi-meet';
|
||||
import ParticipantView from '../../base/participants/components/ParticipantView.native';
|
||||
import { getParticipantById, isLocalScreenshareParticipant } from '../../base/participants/functions';
|
||||
import { trackStreamingStatusChanged } from '../../base/tracks/actions.native';
|
||||
import { getVideoTrackByParticipant, isLocalVideoTrackDesktop } from '../../base/tracks/functions.native';
|
||||
import { ITrack } from '../../base/tracks/types';
|
||||
|
||||
import { AVATAR_SIZE } from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} props of {@link LargeVideo}.
|
||||
*/
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Whether video should be disabled.
|
||||
*/
|
||||
_disableVideo: boolean,
|
||||
_disableVideo: boolean;
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
_height: number,
|
||||
_height: number;
|
||||
|
||||
/**
|
||||
* The ID of the participant (to be) depicted by LargeVideo.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_participantId: string,
|
||||
_participantId: string;
|
||||
|
||||
/**
|
||||
* The video track that will be displayed in the thumbnail.
|
||||
*/
|
||||
_videoTrack: ?Object,
|
||||
_videoTrack?: ITrack;
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
_width: number,
|
||||
_width: number;
|
||||
|
||||
/**
|
||||
* Invoked to trigger state changes in Redux.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Callback to invoke when the {@code LargeVideo} is clicked/pressed.
|
||||
*/
|
||||
onClick: Function,
|
||||
};
|
||||
onClick?: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} state of {@link LargeVideo}.
|
||||
*/
|
||||
type State = {
|
||||
interface IState {
|
||||
|
||||
/**
|
||||
* Size for the Avatar. It will be dynamically adjusted based on the
|
||||
* available size.
|
||||
*/
|
||||
avatarSize: number,
|
||||
avatarSize: number;
|
||||
|
||||
/**
|
||||
* Whether the connectivity indicator will be shown or not. It will be true
|
||||
* by default, but it may be turned off if there is not enough space.
|
||||
*/
|
||||
useConnectivityInfoLabel: boolean
|
||||
};
|
||||
useConnectivityInfoLabel: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
avatarSize: AVATAR_SIZE,
|
||||
@@ -84,14 +83,14 @@ const DEFAULT_STATE = {
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class LargeVideo extends PureComponent<Props, State> {
|
||||
class LargeVideo extends PureComponent<IProps, IState> {
|
||||
/**
|
||||
* Creates new LargeVideo component.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @param {IProps} props - The props of the component.
|
||||
* @returns {LargeVideo}
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.handleTrackStreamingStatusChanged = this.handleTrackStreamingStatusChanged.bind(this);
|
||||
@@ -108,7 +107,7 @@ class LargeVideo extends PureComponent<Props, State> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
static getDerivedStateFromProps(props: Props) {
|
||||
static getDerivedStateFromProps(props: IProps) {
|
||||
const { _height, _width } = props;
|
||||
|
||||
// Get the size, rounded to the nearest even number.
|
||||
@@ -152,7 +151,7 @@ class LargeVideo extends PureComponent<Props, State> {
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
// TODO: after converting this component to a react function component,
|
||||
// use a custom hook to update local track streaming status.
|
||||
const { _videoTrack, dispatch } = this.props;
|
||||
@@ -200,7 +199,7 @@ class LargeVideo extends PureComponent<Props, State> {
|
||||
* @param {JitsiTrackStreamingStatus} streamingStatus - The updated track streaming status.
|
||||
* @returns {void}
|
||||
*/
|
||||
handleTrackStreamingStatusChanged(jitsiTrack, streamingStatus) {
|
||||
handleTrackStreamingStatusChanged(jitsiTrack: any, streamingStatus: string) {
|
||||
this.props.dispatch(trackStreamingStatusChanged(jitsiTrack, streamingStatus));
|
||||
}
|
||||
|
||||
@@ -240,11 +239,11 @@ class LargeVideo extends PureComponent<Props, State> {
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { participantId } = state['features/large-video'];
|
||||
const participant = getParticipantById(state, participantId);
|
||||
const participant = getParticipantById(state, participantId ?? '');
|
||||
const { clientHeight: height, clientWidth: width } = state['features/base/responsive-ui'];
|
||||
const videoTrack = getVideoTrackByParticipant(state, participant);
|
||||
let disableVideo = false;
|
||||
@@ -258,7 +257,7 @@ function _mapStateToProps(state) {
|
||||
return {
|
||||
_disableVideo: disableVideo,
|
||||
_height: height,
|
||||
_participantId: participantId,
|
||||
_participantId: participantId ?? '',
|
||||
_videoTrack: videoTrack,
|
||||
_width: width
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import ChatInputBar from '../../../chat/components/native/ChatInputBar';
|
||||
import MessageContainer from '../../../chat/components/native/MessageContainer';
|
||||
import AbstractLobbyScreen, {
|
||||
Props as AbstractProps,
|
||||
IProps as AbstractProps,
|
||||
_mapStateToProps as abstractMapStateToProps
|
||||
} from '../AbstractLobbyScreen';
|
||||
|
||||
@@ -30,6 +30,7 @@ class LobbyChatScreen extends
|
||||
|
||||
return (
|
||||
<JitsiScreen style = { styles.lobbyChatWrapper }>
|
||||
{/* @ts-ignore */}
|
||||
<MessageContainer messages = { _lobbyChatMessages } />
|
||||
<ChatInputBar onSend = { this._onSendMessage } />
|
||||
</JitsiScreen>
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import { Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { getConferenceName } from '../../../base/conference/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
@@ -20,29 +21,28 @@ import { preJoinStyles } from '../../../prejoin/components/native/styles';
|
||||
import AudioMuteButton from '../../../toolbox/components/AudioMuteButton';
|
||||
import VideoMuteButton from '../../../toolbox/components/VideoMuteButton';
|
||||
import AbstractLobbyScreen, {
|
||||
Props as AbstractProps,
|
||||
IProps as AbstractProps,
|
||||
_mapStateToProps as abstractMapStateToProps } from '../AbstractLobbyScreen';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = AbstractProps & {
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* The current aspect ratio of the screen.
|
||||
*/
|
||||
_aspectRatio: Symbol,
|
||||
_aspectRatio: Symbol;
|
||||
|
||||
/**
|
||||
* The room name.
|
||||
*/
|
||||
_roomName: string
|
||||
_roomName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a waiting screen that represents the participant being in the lobby.
|
||||
*/
|
||||
class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
@@ -70,7 +70,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
style = { contentWrapperStyles }>
|
||||
<BrandingImageBackground />
|
||||
<View style = { largeVideoContainerStyles }>
|
||||
<View style = { preJoinStyles.displayRoomNameBackdrop }>
|
||||
<View style = { preJoinStyles.displayRoomNameBackdrop as ViewStyle }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { preJoinStyles.preJoinRoomName }>
|
||||
@@ -79,7 +79,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
</View>
|
||||
<LargeVideo />
|
||||
</View>
|
||||
<View style = { contentContainerStyles }>
|
||||
<View style = { contentContainerStyles as ViewStyle }>
|
||||
{ this._renderToolbarButtons() }
|
||||
{ this._renderContent() }
|
||||
</View>
|
||||
@@ -87,32 +87,6 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_getScreenTitleKey: () => string;
|
||||
|
||||
_onAskToJoin: () => void;
|
||||
|
||||
_onCancel: () => boolean;
|
||||
|
||||
_onChangeDisplayName: Object => void;
|
||||
|
||||
_onChangeEmail: Object => void;
|
||||
|
||||
_onChangePassword: Object => void;
|
||||
|
||||
_onEnableEdit: () => void;
|
||||
|
||||
_onJoinWithPassword: () => void;
|
||||
|
||||
_onSwitchToKnockMode: () => void;
|
||||
|
||||
_onSwitchToPasswordMode: () => void;
|
||||
|
||||
_renderContent: () => ReactElement;
|
||||
|
||||
_renderToolbarButtons: () => ReactElement;
|
||||
|
||||
_onNavigateToLobbyChat: () => void;
|
||||
|
||||
/**
|
||||
* Navigates to the lobby chat screen.
|
||||
*
|
||||
@@ -137,7 +111,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
<LoadingIndicator
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
style = { styles.loadingIndicator } />
|
||||
<Text style = { styles.joiningMessage }>
|
||||
<Text style = { styles.joiningMessage as TextStyle }>
|
||||
{ this.props.t('lobby.joiningMessage') }
|
||||
</Text>
|
||||
{ this._renderStandardButtons() }
|
||||
@@ -183,7 +157,6 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
return (
|
||||
<Input
|
||||
autoCapitalize = 'none'
|
||||
autoCompleteType = 'off'
|
||||
customStyles = {{ input: styles.customInput }}
|
||||
error = { _passwordJoinFailed }
|
||||
onChange = { this._onChangePassword }
|
||||
@@ -225,7 +198,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
*/
|
||||
_renderToolbarButtons() {
|
||||
return (
|
||||
<View style = { preJoinStyles.toolboxContainer }>
|
||||
<View style = { preJoinStyles.toolboxContainer as ViewStyle }>
|
||||
<AudioMuteButton
|
||||
styles = { preJoinStyles.buttonStylesBorderless } />
|
||||
<VideoMuteButton
|
||||
@@ -244,7 +217,7 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
const { displayName } = this.state;
|
||||
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
<View style = { styles.formWrapper as ViewStyle }>
|
||||
{
|
||||
_knocking && _isLobbyChatActive
|
||||
&& <Button
|
||||
@@ -282,14 +255,14 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {{
|
||||
* _aspectRatio: Symbol
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
...abstractMapStateToProps(state, ownProps),
|
||||
...abstractMapStateToProps(state),
|
||||
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
||||
_roomName: getConferenceName(state)
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
lobbyChatWrapper: {
|
||||
@@ -82,6 +82,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: IProps) =
|
||||
name = { screen.welcome.main }
|
||||
options = { welcomeScreenOptions } />
|
||||
<RootStack.Screen
|
||||
// @ts-ignore
|
||||
component = { DialInSummary }
|
||||
name = { screen.dialInSummary }
|
||||
options = { dialInSummaryScreenOptions } />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useContext, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Animated, Text, View } from 'react-native';
|
||||
import { Animated, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import {
|
||||
@@ -19,7 +19,6 @@ import { NOTIFICATION_ICON, NOTIFICATION_TYPE } from '../../constants';
|
||||
import { INotificationProps } from '../../types';
|
||||
import { NotificationsTransitionContext } from '../NotificationsTransition';
|
||||
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
@@ -153,7 +152,7 @@ const Notification = ({
|
||||
if (descriptionArray?.length) {
|
||||
return (
|
||||
<>
|
||||
<Text style = { styles.contentTextTitle }>
|
||||
<Text style = { styles.contentTextTitle as TextStyle }>
|
||||
{titleText}
|
||||
</Text>
|
||||
{
|
||||
@@ -170,7 +169,7 @@ const Notification = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.contentTextTitle }>
|
||||
<Text style = { styles.contentTextTitle as TextStyle }>
|
||||
{titleText}
|
||||
</Text>
|
||||
);
|
||||
@@ -186,12 +185,12 @@ const Notification = ({
|
||||
{
|
||||
opacity: notificationOpacityAnimation
|
||||
}
|
||||
] }>
|
||||
] as ViewStyle[] }>
|
||||
<View
|
||||
style = { icon === NOTIFICATION_ICON.PARTICIPANTS
|
||||
style = { (icon === NOTIFICATION_ICON.PARTICIPANTS
|
||||
? styles.contentColumn
|
||||
: styles.interactiveContentColumn }>
|
||||
<View style = { styles.iconContainer }>
|
||||
: styles.interactiveContentColumn) as ViewStyle }>
|
||||
<View style = { styles.iconContainer as ViewStyle }>
|
||||
<Icon
|
||||
color = { ICON_COLOR[appearance as keyof typeof ICON_COLOR] }
|
||||
size = { 24 }
|
||||
@@ -202,7 +201,7 @@ const Notification = ({
|
||||
style = { styles.contentContainer }>
|
||||
{_renderContent()}
|
||||
</View>
|
||||
<View style = { styles.btnContainer }>
|
||||
<View style = { styles.btnContainer as ViewStyle }>
|
||||
{mapAppearanceToButtons()}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -10,8 +10,6 @@ import { areThereNotifications } from '../../functions';
|
||||
import NotificationsTransition from '../NotificationsTransition';
|
||||
|
||||
import Notification from './Notification';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
const contentColumn = {
|
||||
@@ -5,7 +5,6 @@ import Button from '../../../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../../../base/ui/constants.native';
|
||||
import { createBreakoutRoom } from '../../../../../breakout-rooms/actions';
|
||||
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,6 @@ import Button from '../../../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../../../base/ui/constants.native';
|
||||
import { autoAssignToBreakoutRooms } from '../../../../../breakout-rooms/actions';
|
||||
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { TouchableOpacity, ViewStyle } from 'react-native';
|
||||
import { Text } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
@@ -13,17 +13,18 @@ import { IconCloseLarge, IconRingGroup } from '../../../../../base/icons/svg';
|
||||
import { isLocalParticipantModerator } from '../../../../../base/participants/functions';
|
||||
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../../../../breakout-rooms/actions';
|
||||
import { getBreakoutRoomsConfig } from '../../../../../breakout-rooms/functions';
|
||||
import { IRoom } from '../../../../../breakout-rooms/types';
|
||||
import styles from '../../../native/styles';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The room for which the menu is open.
|
||||
*/
|
||||
room: Object
|
||||
room: IRoom;
|
||||
}
|
||||
|
||||
const BreakoutRoomContextMenu = ({ room }: Props) => {
|
||||
const BreakoutRoomContextMenu = ({ room }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const { hideJoinRoomButton } = useSelector(getBreakoutRoomsConfig);
|
||||
@@ -53,7 +54,7 @@ const BreakoutRoomContextMenu = ({ room }: Props) => {
|
||||
!hideJoinRoomButton && (
|
||||
<TouchableOpacity
|
||||
onPress = { onJoinRoom }
|
||||
style = { styles.contextMenuItem }>
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconRingGroup } />
|
||||
@@ -65,7 +66,7 @@ const BreakoutRoomContextMenu = ({ room }: Props) => {
|
||||
&& (room?.participants && Object.keys(room.participants).length > 0
|
||||
? <TouchableOpacity
|
||||
onPress = { onCloseBreakoutRoom }
|
||||
style = { styles.contextMenuItem }>
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconCloseLarge } />
|
||||
@@ -73,7 +74,7 @@ const BreakoutRoomContextMenu = ({ room }: Props) => {
|
||||
</TouchableOpacity>
|
||||
: <TouchableOpacity
|
||||
onPress = { onRemoveBreakoutRoom }
|
||||
style = { styles.contextMenuItem }>
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconCloseLarge } />
|
||||
@@ -1,27 +1,27 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../../app/types';
|
||||
import { isLocalParticipantModerator, isParticipantModerator } from '../../../../../base/participants/functions';
|
||||
import { IRoom } from '../../../../../breakout-rooms/types';
|
||||
import { showRoomParticipantMenu } from '../../../../actions.native';
|
||||
import ParticipantItem from '../../../native/ParticipantItem';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Participant to be displayed.
|
||||
*/
|
||||
item: Object,
|
||||
item: any;
|
||||
|
||||
/**
|
||||
* The room the participant is in.
|
||||
*/
|
||||
room: Object
|
||||
};
|
||||
room: IRoom;
|
||||
}
|
||||
|
||||
const BreakoutRoomParticipantItem = ({ item, room }: Props) => {
|
||||
const { defaultRemoteDisplayName } = useSelector(state => state['features/base/config']);
|
||||
const BreakoutRoomParticipantItem = ({ item, room }: IProps) => {
|
||||
const { defaultRemoteDisplayName = '' } = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const moderator = useSelector(isLocalParticipantModerator);
|
||||
const dispatch = useDispatch();
|
||||
const onPress = useCallback(() => {
|
||||
@@ -4,6 +4,7 @@ import { FlatList } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { openSheet } from '../../../../../base/dialog/actions';
|
||||
import { IRoom } from '../../../../../breakout-rooms/types';
|
||||
import { participantMatchesSearch } from '../../../../functions';
|
||||
import CollapsibleList from '../../../native/CollapsibleList';
|
||||
import styles from '../../../native/styles';
|
||||
@@ -11,17 +12,17 @@ import styles from '../../../native/styles';
|
||||
import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
|
||||
import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Room to display.
|
||||
*/
|
||||
room: Object,
|
||||
room: IRoom;
|
||||
|
||||
/**
|
||||
* Participants search string.
|
||||
*/
|
||||
searchString: string
|
||||
searchString: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,12 +31,11 @@ type Props = {
|
||||
* @param {Object} item - The participant.
|
||||
* @returns {string} - The user ID.
|
||||
*/
|
||||
function _keyExtractor(item: Object) {
|
||||
function _keyExtractor(item: any) {
|
||||
return item.jid;
|
||||
}
|
||||
|
||||
|
||||
export const CollapsibleRoom = ({ room, searchString }: Props) => {
|
||||
export const CollapsibleRoom = ({ room, searchString }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const _openContextMenu = useCallback(() => {
|
||||
@@ -50,7 +50,7 @@ export const CollapsibleRoom = ({ room, searchString }: Props) => {
|
||||
// a certain height percentage for every section in order for all to fit
|
||||
// inside the participants pane container
|
||||
const containerStyle
|
||||
= roomParticipantsNr > 2 && styles.collapsibleRoomContainer;
|
||||
= roomParticipantsNr > 2 ? styles.collapsibleRoomContainer : undefined;
|
||||
|
||||
return (
|
||||
<CollapsibleList
|
||||
@@ -62,11 +62,12 @@ export const CollapsibleRoom = ({ room, searchString }: Props) => {
|
||||
data = { Object.values(room.participants || {}) }
|
||||
horizontal = { false }
|
||||
keyExtractor = { _keyExtractor }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
// eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
|
||||
renderItem = { ({ item: participant }) => participantMatchesSearch(participant, searchString)
|
||||
&& <BreakoutRoomParticipantItem
|
||||
? <BreakoutRoomParticipantItem
|
||||
item = { participant }
|
||||
room = { room } /> }
|
||||
room = { room } />
|
||||
: null }
|
||||
scrollEnabled = { true }
|
||||
showsHorizontalScrollIndicator = { false }
|
||||
windowSize = { 2 } />
|
||||
@@ -7,7 +7,6 @@ import Button from '../../../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../../../base/ui/constants.native';
|
||||
import { moveToRoom } from '../../../../../breakout-rooms/actions';
|
||||
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import BaseTheme from '../../../../../base/ui/components/BaseTheme';
|
||||
import BaseTheme from '../../../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
const baseButton = {
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
@@ -1,37 +1,35 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import { GestureResponderEvent, Text, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native';
|
||||
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
|
||||
import { StyleType } from '../../../base/styles/functions.native';
|
||||
import styles from '../breakout-rooms/components/native/styles';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The children to be displayed within this list.
|
||||
*/
|
||||
children: React$Node,
|
||||
children: React.ReactNode;
|
||||
|
||||
/**
|
||||
* Additional style to be appended to the CollapsibleList container.
|
||||
*/
|
||||
containerStyle?: StyleType,
|
||||
containerStyle?: StyleType;
|
||||
|
||||
/**
|
||||
* Callback to invoke when the {@code CollapsibleList} is long pressed.
|
||||
*/
|
||||
onLongPress?: Function,
|
||||
onLongPress?: (e?: GestureResponderEvent) => void;
|
||||
|
||||
/**
|
||||
* Collapsible list title.
|
||||
*/
|
||||
title: Object
|
||||
title: Object;
|
||||
}
|
||||
|
||||
const CollapsibleList = ({ children, containerStyle, onLongPress, title }: Props) => {
|
||||
const CollapsibleList = ({ children, containerStyle, onLongPress, title }: IProps) => {
|
||||
const [ collapsed, setCollapsed ] = useState(false);
|
||||
const _toggleCollapsed = useCallback(() => {
|
||||
setCollapsed(!collapsed);
|
||||
@@ -42,15 +40,15 @@ const CollapsibleList = ({ children, containerStyle, onLongPress, title }: Props
|
||||
<TouchableOpacity
|
||||
onLongPress = { onLongPress }
|
||||
onPress = { _toggleCollapsed }
|
||||
style = { styles.collapsibleList }>
|
||||
style = { styles.collapsibleList as ViewStyle }>
|
||||
<TouchableOpacity
|
||||
onPress = { _toggleCollapsed }
|
||||
style = { styles.arrowIcon }>
|
||||
style = { styles.arrowIcon as ViewStyle }>
|
||||
<Icon
|
||||
size = { 18 }
|
||||
src = { collapsed ? IconArrowDown : IconArrowUp } />
|
||||
</TouchableOpacity>
|
||||
<Text style = { styles.listTile }>
|
||||
<Text style = { styles.listTile as TextStyle }>
|
||||
{
|
||||
title
|
||||
}
|
||||
@@ -1,38 +1,42 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import { TouchableOpacity, View, ViewStyle } from 'react-native';
|
||||
import { Text } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCloseLarge } from '../../../base/icons/svg';
|
||||
import { IParticipant } from '../../../base/participants/types';
|
||||
import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
|
||||
import { getKnockingParticipantsById } from '../../../lobby/functions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Participant reference.
|
||||
*/
|
||||
participant: Object
|
||||
};
|
||||
participant: IParticipant;
|
||||
}
|
||||
|
||||
const ContextMenuLobbyParticipantReject = ({ participant: p }: Props) => {
|
||||
const ContextMenuLobbyParticipantReject = ({ participant: p }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const knockParticipantsIDArr = useSelector(getKnockingParticipantsById);
|
||||
const knockParticipantIsAvailable = knockParticipantsIDArr.find(knockPartId => knockPartId === p.id);
|
||||
const displayName = p.name;
|
||||
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false), [ dispatch ]));
|
||||
const reject = useCallback(() => {
|
||||
dispatch(setKnockingParticipantApproval(p.id, false));
|
||||
},
|
||||
[ dispatch ]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// eslint-disable-next-line react/no-multi-comp
|
||||
const renderMenuHeader = () => (
|
||||
<View
|
||||
style = { styles.contextMenuItemSectionAvatar }>
|
||||
style = { styles.contextMenuItemSectionAvatar as ViewStyle }>
|
||||
<Avatar
|
||||
participantId = { p.id }
|
||||
size = { 24 } />
|
||||
@@ -47,11 +51,10 @@ const ContextMenuLobbyParticipantReject = ({ participant: p }: Props) => {
|
||||
addScrollViewPadding = { false }
|
||||
/* eslint-disable-next-line react/jsx-no-bind */
|
||||
renderHeader = { renderMenuHeader }
|
||||
showSlidingView = { Boolean(knockParticipantIsAvailable) }
|
||||
style = { styles.contextMenuMore }>
|
||||
showSlidingView = { Boolean(knockParticipantIsAvailable) }>
|
||||
<TouchableOpacity
|
||||
onPress = { reject }
|
||||
style = { styles.contextMenuItem }>
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconCloseLarge } />
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import { TouchableOpacity, View, ViewStyle } from 'react-native';
|
||||
import { Divider, Text } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import {
|
||||
requestDisableAudioModeration,
|
||||
requestDisableVideoModeration,
|
||||
@@ -33,7 +34,7 @@ export const ContextMenuMore = () => {
|
||||
}, [ dispatch ]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isModerationSupported = useSelector(isAvModerationSupported);
|
||||
const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
|
||||
const allModerators = useSelector(isEveryoneModerator);
|
||||
const participantCount = useSelector(getParticipantCount);
|
||||
|
||||
@@ -52,28 +53,29 @@ export const ContextMenuMore = () => {
|
||||
showSlidingView = { true }>
|
||||
<TouchableOpacity
|
||||
onPress = { muteAllVideo }
|
||||
style = { styles.contextMenuItem }>
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconVideoOff } />
|
||||
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
|
||||
</TouchableOpacity>
|
||||
{isModerationSupported && ((participantCount === 1 || !allModerators)) && <>
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider } />
|
||||
<View style = { styles.contextMenuItem }>
|
||||
<View style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.allow')}</Text>
|
||||
</View>
|
||||
{isAudioModerationEnabled
|
||||
? <TouchableOpacity
|
||||
onPress = { disableAudioModeration }
|
||||
style = { styles.contextMenuItem }>
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Text style = { styles.contextMenuItemTextNoIcon }>
|
||||
{t('participantsPane.actions.audioModeration')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
: <TouchableOpacity
|
||||
onPress = { enableAudioModeration }
|
||||
style = { styles.contextMenuItem }>
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconCheck } />
|
||||
@@ -84,14 +86,14 @@ export const ContextMenuMore = () => {
|
||||
{isVideoModerationEnabled
|
||||
? <TouchableOpacity
|
||||
onPress = { disableVideoModeration }
|
||||
style = { styles.contextMenuItem }>
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Text style = { styles.contextMenuItemTextNoIcon }>
|
||||
{t('participantsPane.actions.videoModeration')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
: <TouchableOpacity
|
||||
onPress = { enableVideoModeration }
|
||||
style = { styles.contextMenuItem }>
|
||||
style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconCheck } />
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { IParticipant } from '../../../base/participants/types';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
|
||||
@@ -8,23 +9,22 @@ import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
|
||||
import ParticipantItem from './ParticipantItem';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Participant reference.
|
||||
*/
|
||||
participant: Object
|
||||
};
|
||||
participant: IParticipant;
|
||||
}
|
||||
|
||||
export const LobbyParticipantItem = ({ participant: p }: Props) => {
|
||||
export const LobbyParticipantItem = ({ participant: p }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true), [ dispatch ]));
|
||||
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false), [ dispatch ]));
|
||||
const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true)), [ dispatch ]);
|
||||
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false)), [ dispatch ]);
|
||||
|
||||
return (
|
||||
<ParticipantItem
|
||||
displayName = { p.name }
|
||||
displayName = { p.name ?? '' }
|
||||
isKnockingParticipant = { true }
|
||||
key = { p.id }
|
||||
participantID = { p.id } >
|
||||
@@ -1,8 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ScrollView, Text, View } from 'react-native';
|
||||
import { ScrollView, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
@@ -14,7 +12,6 @@ import CollapsibleList from './CollapsibleList';
|
||||
import { LobbyParticipantItem } from './LobbyParticipantItem';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
const LobbyParticipantList = () => {
|
||||
const lobbyEnabled = useSelector(getLobbyEnabled);
|
||||
const participants = useSelector(getKnockingParticipants);
|
||||
@@ -30,8 +27,8 @@ const LobbyParticipantList = () => {
|
||||
}
|
||||
|
||||
const title = (
|
||||
<View style = { styles.lobbyListDetails } >
|
||||
<Text style = { styles.lobbyListDescription }>
|
||||
<View style = { styles.lobbyListDetails as ViewStyle } >
|
||||
<Text style = { styles.lobbyListDescription as TextStyle }>
|
||||
{ t('participantsPane.headings.waitingLobby',
|
||||
{ count: participants.length }) }
|
||||
</Text>
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
hasRaisedHand,
|
||||
isParticipantModerator
|
||||
} from '../../../base/participants/functions';
|
||||
import { FakeParticipant } from '../../../base/participants/types';
|
||||
import { FakeParticipant, IParticipant } from '../../../base/participants/types';
|
||||
import {
|
||||
isParticipantAudioMuted,
|
||||
isParticipantVideoMuted
|
||||
@@ -20,88 +21,85 @@ import { getParticipantAudioMediaState, getParticipantVideoMediaState } from '..
|
||||
|
||||
import ParticipantItem from './ParticipantItem';
|
||||
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Media state for audio.
|
||||
*/
|
||||
_audioMediaState: MediaState,
|
||||
_audioMediaState: MediaState;
|
||||
|
||||
/**
|
||||
* Whether or not to disable the moderator indicator.
|
||||
*/
|
||||
_disableModeratorIndicator: boolean,
|
||||
_disableModeratorIndicator?: boolean;
|
||||
|
||||
/**
|
||||
* The display name of the participant.
|
||||
*/
|
||||
_displayName: string,
|
||||
_displayName: string;
|
||||
|
||||
/**
|
||||
* The type of fake participant.
|
||||
*/
|
||||
_fakeParticipant: FakeParticipant,
|
||||
_fakeParticipant: FakeParticipant;
|
||||
|
||||
/**
|
||||
* Whether or not the user is a moderator.
|
||||
*/
|
||||
_isModerator: boolean,
|
||||
_isModerator: boolean;
|
||||
|
||||
/**
|
||||
* True if the participant is the local participant.
|
||||
*/
|
||||
_local: boolean,
|
||||
_local: boolean;
|
||||
|
||||
/**
|
||||
* Shared video local participant owner.
|
||||
*/
|
||||
_localVideoOwner: boolean,
|
||||
_localVideoOwner: boolean;
|
||||
|
||||
/**
|
||||
* The participant ID.
|
||||
*/
|
||||
_participantID: string,
|
||||
_participantID: string;
|
||||
|
||||
/**
|
||||
* True if the participant have raised hand.
|
||||
*/
|
||||
_raisedHand: boolean,
|
||||
_raisedHand: boolean;
|
||||
|
||||
/**
|
||||
* Media state for video.
|
||||
*/
|
||||
_videoMediaState: MediaState,
|
||||
_videoMediaState: MediaState;
|
||||
|
||||
/**
|
||||
* The redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
dispatch: Function;
|
||||
|
||||
/**
|
||||
* The participant.
|
||||
*/
|
||||
participant: ?Object
|
||||
};
|
||||
participant?: IParticipant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the MeetingParticipantItem component.
|
||||
*/
|
||||
class MeetingParticipantItem extends PureComponent<Props> {
|
||||
class MeetingParticipantItem extends PureComponent<IProps> {
|
||||
|
||||
/**
|
||||
* Creates new MeetingParticipantItem instance.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @param {IProps} props - The props of the component.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onPress = this._onPress.bind(this);
|
||||
}
|
||||
|
||||
_onPress: () => void;
|
||||
|
||||
/**
|
||||
* Handles MeetingParticipantItem press events.
|
||||
*
|
||||
@@ -166,12 +164,12 @@ class MeetingParticipantItem extends PureComponent<Props> {
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The own props of the component.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function mapStateToProps(state, ownProps): Object {
|
||||
function mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { participant } = ownProps;
|
||||
const { ownerId } = state['features/shared-video'];
|
||||
const localParticipantId = getLocalParticipant(state).id;
|
||||
const localParticipantId = getLocalParticipant(state)?.id;
|
||||
const _isAudioMuted = Boolean(participant && isParticipantAudioMuted(participant, state));
|
||||
const _isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
const audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
|
||||
@@ -78,7 +78,7 @@ interface IProps extends WithTranslation {
|
||||
/**
|
||||
* The remote participants.
|
||||
*/
|
||||
_sortedRemoteParticipants: Map<string, string>;
|
||||
_sortedRemoteParticipants: string[];
|
||||
|
||||
/**
|
||||
* The current visitors count if any.
|
||||
@@ -229,7 +229,7 @@ class MeetingParticipantList extends PureComponent<IProps> {
|
||||
= isLocalModerator
|
||||
? containerStyleModerator : styles.notLocalModeratorContainer;
|
||||
const finalContainerStyle
|
||||
= _participantsCount > 6 && containerStyle;
|
||||
= _participantsCount > 6 ? containerStyle : undefined;
|
||||
const { color, shareDialogVisible } = _inviteOthersControl;
|
||||
const _visitorsLabelText = _visitorsCount > 0
|
||||
? t('participantsPane.headings.visitors', { count: _visitorsCount })
|
||||
@@ -289,7 +289,7 @@ class MeetingParticipantList extends PureComponent<IProps> {
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState): Object {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const _participantsCount = getParticipantCountWithFake(state);
|
||||
const { remoteParticipants } = state['features/filmstrip'];
|
||||
const { shareDialogVisible } = state['features/share-room'];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import { GestureResponderEvent, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native';
|
||||
import { Text } from 'react-native-paper';
|
||||
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
@@ -10,62 +9,62 @@ import { AudioStateIcons, MEDIA_STATE, type MediaState, VideoStateIcons } from '
|
||||
import { RaisedHandIndicator } from './RaisedHandIndicator';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Media state for audio.
|
||||
*/
|
||||
audioMediaState?: MediaState,
|
||||
audioMediaState?: MediaState;
|
||||
|
||||
/**
|
||||
* React children.
|
||||
*/
|
||||
children?: Node,
|
||||
children?: React.ReactNode;
|
||||
|
||||
/**
|
||||
* Whether or not to disable the moderator indicator.
|
||||
*/
|
||||
disableModeratorIndicator?: boolean,
|
||||
disableModeratorIndicator?: boolean;
|
||||
|
||||
/**
|
||||
* The name of the participant. Used for showing lobby names.
|
||||
*/
|
||||
displayName: string,
|
||||
displayName: string;
|
||||
|
||||
/**
|
||||
* Is the participant waiting?
|
||||
*/
|
||||
isKnockingParticipant: boolean,
|
||||
isKnockingParticipant?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the user is a moderator.
|
||||
*/
|
||||
isModerator?: boolean,
|
||||
isModerator?: boolean;
|
||||
|
||||
/**
|
||||
* True if the participant is local.
|
||||
*/
|
||||
local?: boolean,
|
||||
local?: boolean;
|
||||
|
||||
/**
|
||||
* Callback to be invoked on pressing the participant item.
|
||||
*/
|
||||
onPress?: Function,
|
||||
onPress?: (e?: GestureResponderEvent) => void;
|
||||
|
||||
/**
|
||||
* The ID of the participant.
|
||||
*/
|
||||
participantID: string,
|
||||
participantID: string;
|
||||
|
||||
/**
|
||||
* True if the participant have raised hand.
|
||||
*/
|
||||
raisedHand?: boolean,
|
||||
raisedHand?: boolean;
|
||||
|
||||
/**
|
||||
* Media state for video.
|
||||
*/
|
||||
videoMediaState?: MediaState
|
||||
videoMediaState?: MediaState;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,15 +84,15 @@ function ParticipantItem({
|
||||
raisedHand,
|
||||
audioMediaState = MEDIA_STATE.NONE,
|
||||
videoMediaState = MEDIA_STATE.NONE
|
||||
}: Props) {
|
||||
}: IProps) {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style = { styles.participantContainer } >
|
||||
<View style = { styles.participantContainer as ViewStyle } >
|
||||
<TouchableOpacity
|
||||
onPress = { onPress }
|
||||
style = { styles.participantContent }>
|
||||
style = { styles.participantContent as ViewStyle }>
|
||||
<Avatar
|
||||
displayName = { displayName }
|
||||
participantId = { participantID }
|
||||
@@ -103,23 +102,23 @@ function ParticipantItem({
|
||||
styles.participantDetailsContainer,
|
||||
raisedHand && styles.participantDetailsContainerRaisedHand
|
||||
] }>
|
||||
<View style = { styles.participantNameContainer }>
|
||||
<View style = { styles.participantNameContainer as ViewStyle }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.participantName }>
|
||||
style = { styles.participantName as TextStyle }>
|
||||
{ displayName }
|
||||
{ local && ` (${t('chat.you')})` }
|
||||
</Text>
|
||||
</View>
|
||||
{ isModerator && !disableModeratorIndicator
|
||||
&& <Text style = { styles.moderatorLabel }>{ t('videothumbnail.moderator') }</Text>
|
||||
&& <Text style = { styles.moderatorLabel as TextStyle }>{ t('videothumbnail.moderator') }</Text>
|
||||
}
|
||||
</View>
|
||||
{
|
||||
!isKnockingParticipant
|
||||
&& <>
|
||||
{ raisedHand && <RaisedHandIndicator /> }
|
||||
<View style = { styles.participantStatesContainer }>
|
||||
<View style = { styles.participantStatesContainer as ViewStyle }>
|
||||
<View style = { styles.participantStateVideo }>{ VideoStateIcons[videoMediaState] }</View>
|
||||
<View>{ AudioStateIcons[audioMediaState] }</View>
|
||||
</View>
|
||||
@@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants/functions';
|
||||
import { equals } from '../../../base/redux/functions';
|
||||
@@ -24,7 +23,6 @@ import MeetingParticipantList from './MeetingParticipantList';
|
||||
import ParticipantsPaneFooter from './ParticipantsPaneFooter';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Participants pane.
|
||||
*
|
||||
@@ -33,12 +31,12 @@ import styles from './styles';
|
||||
const ParticipantsPane = () => {
|
||||
const [ searchString, setSearchString ] = useState('');
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const { conference } = useSelector(state => state['features/base/conference']);
|
||||
const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
|
||||
const _isBreakoutRoomsSupported = conference?.getBreakoutRooms()?.isSupported();
|
||||
const currentRoomId = useSelector(getCurrentRoomId);
|
||||
const rooms: Array<Object> = Object.values(useSelector(getBreakoutRooms, equals))
|
||||
.filter((room: Object) => room.id !== currentRoomId)
|
||||
.sort((p1: Object, p2: Object) => (p1?.name || '').localeCompare(p2?.name || ''));
|
||||
const rooms = Object.values(useSelector(getBreakoutRooms, equals))
|
||||
.filter(room => room.id !== currentRoomId)
|
||||
.sort((p1, p2) => (p1?.name || '').localeCompare(p2?.name || ''));
|
||||
const inBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||
const showAddBreakoutRoom = useSelector(isAddBreakoutRoomButtonVisible);
|
||||
const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
|
||||
@@ -46,7 +44,7 @@ const ParticipantsPane = () => {
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
footerComponent = { isLocalModerator && ParticipantsPaneFooter }
|
||||
footerComponent = { isLocalModerator ? ParticipantsPaneFooter : undefined }
|
||||
style = { styles.participantsPaneContainer }>
|
||||
<LobbyParticipantList />
|
||||
<MeetingParticipantList
|
||||
@@ -1,7 +1,4 @@
|
||||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconUsers } from '../../../base/icons/svg';
|
||||
@@ -10,19 +7,11 @@ import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the participants panel.
|
||||
*/
|
||||
class ParticipantsPaneButton extends AbstractButton<Props, *> {
|
||||
class ParticipantsPaneButton extends AbstractButton<AbstractButtonProps> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.participants';
|
||||
icon = IconUsers;
|
||||
label = 'toolbar.participants';
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { openDialog, openSheet } from '../../../base/dialog/actions';
|
||||
@@ -32,7 +32,7 @@ const ParticipantsPaneFooter = (): JSX.Element => {
|
||||
const showMuteAll = useSelector(isMuteAllVisible);
|
||||
|
||||
return (
|
||||
<View style = { styles.participantsPaneFooter }>
|
||||
<View style = { styles.participantsPaneFooter as ViewStyle }>
|
||||
{
|
||||
showMuteAll && (
|
||||
<Button
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import React, { PureComponent } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
@@ -85,8 +85,8 @@ class RoomParticipantMenu extends PureComponent<IProps> {
|
||||
<BottomSheet
|
||||
renderHeader = { this._renderMenuHeader }
|
||||
showSlidingView = { true }>
|
||||
<View style = { styles.contextMenuItem }>
|
||||
<Text style = { styles.contextMenuItemText }>
|
||||
<View style = { styles.contextMenuItem as ViewStyle }>
|
||||
<Text style = { styles.contextMenuItemText as ViewStyle }>
|
||||
{t('breakoutRooms.actions.sendToBreakoutRoom')}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -120,11 +120,11 @@ class RoomParticipantMenu extends PureComponent<IProps> {
|
||||
<View
|
||||
style = { [
|
||||
bottomSheetStyles.sheet,
|
||||
styles.participantNameContainer ] }>
|
||||
styles.participantNameContainer ] as ViewStyle[] }>
|
||||
<Avatar
|
||||
displayName = { participantName }
|
||||
size = { AVATAR_SIZE } />
|
||||
<Text style = { styles.participantNameLabel }>
|
||||
<Text style = { styles.participantNameLabel as TextStyle }>
|
||||
{ participantName }
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { ComponentType, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GestureResponderEvent } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { createPollEvent } from '../../analytics/AnalyticsEvents';
|
||||
@@ -33,13 +34,13 @@ export type AnswerInfo = {
|
||||
*/
|
||||
export type AbstractProps = {
|
||||
answers: Array<AnswerInfo>;
|
||||
changeVote: (e: React.MouseEvent) => void;
|
||||
changeVote: (e?: React.MouseEvent<HTMLButtonElement> | GestureResponderEvent) => void;
|
||||
creatorName: string;
|
||||
haveVoted: boolean;
|
||||
question: string;
|
||||
showDetails: boolean;
|
||||
t: Function;
|
||||
toggleIsDetailed: (e: React.MouseEvent) => void;
|
||||
toggleIsDetailed: (e?: React.MouseEvent<HTMLButtonElement> | GestureResponderEvent) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
@@ -29,25 +29,25 @@ const PollAnswer = (props: AbstractProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text style = { dialogStyles.questionText } >{ poll.question }</Text>
|
||||
<Text style = { dialogStyles.questionOwnerText } >{
|
||||
<Text style = { dialogStyles.questionText as TextStyle } >{ poll.question }</Text>
|
||||
<Text style = { dialogStyles.questionOwnerText as TextStyle } >{
|
||||
t('polls.by', { name: localParticipant?.name })
|
||||
}
|
||||
</Text>
|
||||
<View style = { chatStyles.answerContent }>
|
||||
<View style = { chatStyles.answerContent as ViewStyle }>
|
||||
{poll.answers.map((answer, index) => (
|
||||
<View
|
||||
key = { index }
|
||||
style = { chatStyles.switchRow } >
|
||||
style = { chatStyles.switchRow as ViewStyle } >
|
||||
<Switch
|
||||
checked = { checkBoxStates[index] }
|
||||
/* eslint-disable-next-line react/jsx-no-bind */
|
||||
onChange = { state => setCheckbox(index, state) } />
|
||||
<Text style = { chatStyles.switchLabel }>{answer.name}</Text>
|
||||
<Text style = { chatStyles.switchLabel as TextStyle }>{answer.name}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<View style = { chatStyles.buttonRow }>
|
||||
<View style = { chatStyles.buttonRow as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.skip'
|
||||
labelKey = 'polls.answer.skip'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { FlatList, Platform, View } from 'react-native';
|
||||
import { FlatList, Platform, View, ViewStyle } from 'react-native';
|
||||
import { TextInput } from 'react-native-gesture-handler';
|
||||
import { Divider } from 'react-native-paper';
|
||||
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
@@ -8,12 +9,10 @@ import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import styles
|
||||
from '../../../settings/components/native/styles';
|
||||
import { ANSWERS_LIMIT, CHAR_LIMIT } from '../../constants';
|
||||
import AbstractPollCreate from '../AbstractPollCreate';
|
||||
import type { AbstractProps } from '../AbstractPollCreate';
|
||||
import AbstractPollCreate, { AbstractProps } from '../AbstractPollCreate';
|
||||
|
||||
import { chatStyles, dialogStyles } from './styles';
|
||||
|
||||
|
||||
const PollCreate = (props: AbstractProps) => {
|
||||
const {
|
||||
addAnswer,
|
||||
@@ -28,13 +27,13 @@ const PollCreate = (props: AbstractProps) => {
|
||||
t
|
||||
} = props;
|
||||
|
||||
const answerListRef = useRef(null);
|
||||
const answerListRef = useRef<FlatList>(null);
|
||||
|
||||
/*
|
||||
* This ref stores the Array of answer input fields, allowing us to focus on them.
|
||||
* This array is maintained by registerFieldRef and the useEffect below.
|
||||
*/
|
||||
const answerInputs = useRef([]);
|
||||
const answerInputs = useRef<TextInput[]>([]);
|
||||
const registerFieldRef = useCallback((i, input) => {
|
||||
if (input === null) {
|
||||
return;
|
||||
@@ -51,7 +50,7 @@ const PollCreate = (props: AbstractProps) => {
|
||||
* This state allows us to requestFocus asynchronously, without having to worry
|
||||
* about whether a newly created input field has been rendered yet or not.
|
||||
*/
|
||||
const [ lastFocus, requestFocus ] = useState(null);
|
||||
const [ lastFocus, requestFocus ] = useState<number | null>(null);
|
||||
const { PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -70,7 +69,7 @@ const PollCreate = (props: AbstractProps) => {
|
||||
|
||||
const onQuestionKeyDown = useCallback(() => {
|
||||
answerInputs.current[0].focus();
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Called on keypress in answer fields
|
||||
const onAnswerKeyDown = useCallback((index: number, ev) => {
|
||||
@@ -84,7 +83,7 @@ const PollCreate = (props: AbstractProps) => {
|
||||
}, [ answers, addAnswer, removeAnswer, requestFocus ]);
|
||||
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
const createRemoveOptionButton = onPress => (
|
||||
const createRemoveOptionButton = (onPress: () => void) => (
|
||||
<Button
|
||||
labelKey = 'polls.create.removeOption'
|
||||
labelStyle = { dialogStyles.optionRemoveButtonText }
|
||||
@@ -95,12 +94,12 @@ const PollCreate = (props: AbstractProps) => {
|
||||
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
const renderListItem = ({ index }: { index: number }) =>
|
||||
const renderListItem = ({ index }: { index: number; }) =>
|
||||
|
||||
// padding to take into account the two default options
|
||||
(
|
||||
<View
|
||||
style = { dialogStyles.optionContainer }>
|
||||
style = { dialogStyles.optionContainer as ViewStyle }>
|
||||
<Input
|
||||
blurOnSubmit = { false }
|
||||
label = { t('polls.create.pollOption', { index: index + 1 }) }
|
||||
@@ -124,8 +123,8 @@ const PollCreate = (props: AbstractProps) => {
|
||||
? chatStyles.pollCreateButtonsContainerAndroid : chatStyles.pollCreateButtonsContainerIos;
|
||||
|
||||
return (
|
||||
<View style = { chatStyles.pollCreateContainer }>
|
||||
<View style = { chatStyles.pollCreateSubContainer }>
|
||||
<View style = { chatStyles.pollCreateContainer as ViewStyle }>
|
||||
<View style = { chatStyles.pollCreateSubContainer as ViewStyle }>
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
blurOnSubmit = { false }
|
||||
@@ -140,15 +139,15 @@ const PollCreate = (props: AbstractProps) => {
|
||||
// This is set to help the touch event not be propagated to any subviews.
|
||||
pointerEvents = { 'auto' }
|
||||
value = { question } />
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.fieldSeparator } />
|
||||
<FlatList
|
||||
blurOnSubmit = { true }
|
||||
data = { answers }
|
||||
extraData = { answers }
|
||||
keyExtractor = { (item, index) => index.toString() }
|
||||
ref = { answerListRef }
|
||||
renderItem = { renderListItem } />
|
||||
<View style = { pollCreateButtonsContainerStyles }>
|
||||
<View style = { pollCreateButtonsContainerStyles as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'polls.create.addOption'
|
||||
disabled = { answers.length >= ANSWERS_LIMIT }
|
||||
@@ -161,7 +160,7 @@ const PollCreate = (props: AbstractProps) => {
|
||||
style = { chatStyles.pollCreateAddButton }
|
||||
type = { SECONDARY } />
|
||||
<View
|
||||
style = { chatStyles.buttonRow }>
|
||||
style = { chatStyles.buttonRow as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'polls.create.cancel'
|
||||
labelKey = 'polls.create.cancel'
|
||||
@@ -1,7 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { shouldShowResults } from '../../functions';
|
||||
@@ -10,21 +8,21 @@ import PollAnswer from './PollAnswer';
|
||||
import PollResults from './PollResults';
|
||||
import { chatStyles } from './styles';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Id of the poll.
|
||||
*/
|
||||
pollId: string,
|
||||
pollId: string;
|
||||
|
||||
}
|
||||
|
||||
const PollItem = ({ pollId }: Props) => {
|
||||
const PollItem = ({ pollId }: IProps) => {
|
||||
const showResults = useSelector(shouldShowResults(pollId));
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { chatStyles.pollItemContainer }>
|
||||
style = { chatStyles.pollItemContainer as ViewStyle }>
|
||||
{ showResults
|
||||
? <PollResults
|
||||
key = { pollId }
|
||||
@@ -1,7 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
@@ -12,7 +10,6 @@ import type { AbstractProps, AnswerInfo } from '../AbstractPollResults';
|
||||
|
||||
import { chatStyles, dialogStyles, resultsStyles } from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Component that renders the poll results.
|
||||
*
|
||||
@@ -39,10 +36,10 @@ const PollResults = (props: AbstractProps) => {
|
||||
* @returns {React.Node}
|
||||
*/
|
||||
const renderHeader = (answer: string, percentage: number, nbVotes: number) => (
|
||||
<View style = { resultsStyles.answerHeader }>
|
||||
<Text style = { resultsStyles.answer }>{ answer }</Text>
|
||||
<View style = { resultsStyles.answerHeader as ViewStyle }>
|
||||
<Text style = { resultsStyles.answer as TextStyle }>{ answer }</Text>
|
||||
<View>
|
||||
<Text style = { resultsStyles.answer }>({nbVotes}) {percentage}%</Text>
|
||||
<Text style = { resultsStyles.answer as TextStyle }>({nbVotes}) {percentage}%</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -58,21 +55,22 @@ const PollResults = (props: AbstractProps) => {
|
||||
|
||||
if (showDetails) {
|
||||
return (
|
||||
<View style = { resultsStyles.answerContainer }>
|
||||
<View style = { resultsStyles.answerContainer as ViewStyle }>
|
||||
{ renderHeader(name, percentage, voterCount) }
|
||||
<View style = { resultsStyles.barContainer }>
|
||||
<View style = { [ resultsStyles.bar, { width: `${percentage}%` } ] } />
|
||||
<View style = { resultsStyles.barContainer as ViewStyle }>
|
||||
<View style = { [ resultsStyles.bar, { width: `${percentage}%` } ] as ViewStyle[] } />
|
||||
</View>
|
||||
{ voters && voterCount > 0
|
||||
&& <View style = { resultsStyles.voters }>
|
||||
{voters.map(({ id, name: voterName }) =>
|
||||
(<Text
|
||||
key = { id }
|
||||
style = { resultsStyles.voter }>
|
||||
{ voterName }
|
||||
</Text>)
|
||||
)}
|
||||
</View>}
|
||||
&& <View style = { resultsStyles.voters as ViewStyle }>
|
||||
{/* @ts-ignore */}
|
||||
{voters.map(({ id, name: voterName }) =>
|
||||
(<Text
|
||||
key = { id }
|
||||
style = { resultsStyles.voter as TextStyle }>
|
||||
{ voterName }
|
||||
</Text>)
|
||||
)}
|
||||
</View>}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -81,10 +79,10 @@ const PollResults = (props: AbstractProps) => {
|
||||
// else, we display a simple list
|
||||
// We add a progress bar by creating an empty view of width equal to percentage.
|
||||
return (
|
||||
<View style = { resultsStyles.answerContainer }>
|
||||
<View style = { resultsStyles.answerContainer as ViewStyle }>
|
||||
{ renderHeader(answer.name, percentage, voterCount) }
|
||||
<View style = { resultsStyles.barContainer }>
|
||||
<View style = { [ resultsStyles.bar, { width: `${percentage}%` } ] } />
|
||||
<View style = { resultsStyles.barContainer as ViewStyle }>
|
||||
<View style = { [ resultsStyles.bar, { width: `${percentage}%` } ] as ViewStyle[] } />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -96,13 +94,15 @@ const PollResults = (props: AbstractProps) => {
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
return (
|
||||
<View>
|
||||
<Text style = { dialogStyles.questionText } >{ question }</Text>
|
||||
<Text style = { dialogStyles.questionOwnerText } >{ t('polls.by', { name: localParticipant.name }) }</Text>
|
||||
<Text style = { dialogStyles.questionText as TextStyle } >{ question }</Text>
|
||||
<Text style = { dialogStyles.questionOwnerText as TextStyle } >
|
||||
{ t('polls.by', { name: localParticipant?.name }) }
|
||||
</Text>
|
||||
<FlatList
|
||||
data = { answers }
|
||||
keyExtractor = { (item, index) => index.toString() }
|
||||
renderItem = { answer => renderRow(answer.item) } />
|
||||
<View style = { chatStyles.bottomLinks }>
|
||||
<View style = { chatStyles.bottomLinks as ViewStyle }>
|
||||
<Button
|
||||
labelKey = {
|
||||
showDetails
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user