mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-06-02 22:17:47 +00:00
Compare commits
22 Commits
7221
...
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 |
@@ -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;
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ]);
|
||||
|
||||
@@ -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',
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ 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';
|
||||
@@ -31,7 +33,7 @@ import { isCalendarEnabled } from '../calendar-sync/functions';
|
||||
import FeedbackDialog from '../feedback/components/FeedbackDialog';
|
||||
import { setFilmstripEnabled } from '../filmstrip/actions.any';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
import { hideNotification, showNotification } from '../notifications/actions';
|
||||
import { hideNotification, showNotification, showWarningNotification } from '../notifications/actions';
|
||||
import {
|
||||
CALENDAR_NOTIFICATION_ID,
|
||||
NOTIFICATION_ICON,
|
||||
@@ -190,9 +192,14 @@ function _checkIframe(state: IReduxState, dispatch: IStore['dispatch']) {
|
||||
if (inIframe() && state['features/base/config'].disableIframeAPI && !browser.isElectron()
|
||||
&& !isVpaasMeeting(state) && !allowIframe) {
|
||||
// show sticky notification and redirect in 5 minutes
|
||||
dispatch(showNotification({
|
||||
descriptionKey: 'notify.disabledIframe',
|
||||
descriptionArguments: { timeout: IFRAME_DISABLED_TIMEOUT_MINUTES }
|
||||
dispatch(showWarningNotification({
|
||||
description: translateToHTML(
|
||||
i18next.t.bind(i18next),
|
||||
'notify.disabledIframe',
|
||||
{
|
||||
timeout: IFRAME_DISABLED_TIMEOUT_MINUTES
|
||||
}
|
||||
)
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,12 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text } from 'react-native-paper';
|
||||
import { connect } from 'react-redux';
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { RAISE_HAND_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
@@ -15,6 +13,7 @@ import {
|
||||
getLocalParticipant,
|
||||
hasRaisedHand
|
||||
} from '../../../base/participants/functions';
|
||||
import { ILocalParticipant } from '../../../base/participants/types';
|
||||
import { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
@@ -24,43 +23,33 @@ import styles from './styles';
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link RaiseHandButton}.
|
||||
*/
|
||||
type Props = AbstractButtonProps & {
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* Whether this button is enabled or not.
|
||||
*/
|
||||
_enabled: boolean,
|
||||
_enabled: boolean;
|
||||
|
||||
/**
|
||||
* The local participant.
|
||||
*/
|
||||
_localParticipant: Object,
|
||||
_localParticipant?: ILocalParticipant;
|
||||
|
||||
/**
|
||||
* Whether the participant raused their hand or not.
|
||||
*/
|
||||
_raisedHand: boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Used for translation.
|
||||
*/
|
||||
t: Function,
|
||||
_raisedHand: boolean;
|
||||
|
||||
/**
|
||||
* Used to close the overflow menu after raise hand is clicked.
|
||||
*/
|
||||
onCancel: Function
|
||||
};
|
||||
onCancel: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a button to raise or lower hand.
|
||||
*/
|
||||
class RaiseHandButton extends Component<Props, *> {
|
||||
class RaiseHandButton extends Component<IProps> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
|
||||
label = 'toolbar.raiseYourHand';
|
||||
toggledLabel = 'toolbar.lowerYourHand';
|
||||
@@ -68,10 +57,10 @@ class RaiseHandButton extends Component<Props, *> {
|
||||
/**
|
||||
* Initializes a new {@code RaiseHandButton} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* @param {IProps} props - The React {@code Component} props to initialize
|
||||
* the new {@code RaiseHandButton} instance with.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
@@ -80,12 +69,6 @@ class RaiseHandButton extends Component<Props, *> {
|
||||
this._getLabel = this._getLabel.bind(this);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
_toggleRaisedHand: () => void;
|
||||
|
||||
_getLabel: () => string;
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
@@ -163,9 +146,9 @@ class RaiseHandButton extends Component<Props, *> {
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
const enabled = getFeatureFlag(state, RAISE_HAND_ENABLED, true);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { Text, TouchableHighlight } from 'react-native';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { ColorValue, GestureResponderEvent, Text, TouchableHighlight, ViewStyle } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { createReactionMenuEvent } from '../../../analytics/AnalyticsEvents';
|
||||
@@ -11,71 +10,65 @@ import { StyleType } from '../../../base/styles/functions.native';
|
||||
import { addReactionToBuffer } from '../../actions.any';
|
||||
import { REACTIONS } from '../../constants';
|
||||
|
||||
|
||||
export type ReactionStyles = {
|
||||
|
||||
/**
|
||||
* Style for the gif button.
|
||||
*/
|
||||
gifButton: StyleType,
|
||||
|
||||
/**
|
||||
* Style for the button.
|
||||
*/
|
||||
style: StyleType,
|
||||
|
||||
/**
|
||||
* Underlay color for the button.
|
||||
*/
|
||||
underlayColor: StyleType,
|
||||
|
||||
/**
|
||||
* Style for the emoji text on the button.
|
||||
*/
|
||||
emoji: StyleType,
|
||||
|
||||
/**
|
||||
* Style for the label text on the button.
|
||||
*/
|
||||
text?: StyleType,
|
||||
interface IReactionStyles {
|
||||
|
||||
/**
|
||||
* Style for text container. Used on raise hand button.
|
||||
*/
|
||||
container?: StyleType
|
||||
container?: StyleType;
|
||||
|
||||
/**
|
||||
* Style for the emoji text on the button.
|
||||
*/
|
||||
emoji: StyleType;
|
||||
|
||||
/**
|
||||
* Style for the gif button.
|
||||
*/
|
||||
gifButton: StyleType;
|
||||
|
||||
/**
|
||||
* Style for the button.
|
||||
*/
|
||||
style: StyleType;
|
||||
|
||||
/**
|
||||
* Style for the label text on the button.
|
||||
*/
|
||||
text?: StyleType;
|
||||
|
||||
/**
|
||||
* Underlay color for the button.
|
||||
*/
|
||||
underlayColor: ColorValue;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ReactionButton}.
|
||||
*/
|
||||
type Props = {
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Component children.
|
||||
*/
|
||||
children?: ReactNode,
|
||||
children?: React.ReactNode;
|
||||
|
||||
/**
|
||||
* External click handler.
|
||||
*/
|
||||
onClick?: Function,
|
||||
|
||||
/**
|
||||
* Collection of styles for the button.
|
||||
*/
|
||||
styles: ReactionStyles,
|
||||
onClick?: (e?: GestureResponderEvent) => void;
|
||||
|
||||
/**
|
||||
* The reaction to be sent.
|
||||
*/
|
||||
reaction: string,
|
||||
reaction?: string;
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
* Collection of styles for the button.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
styles: IReactionStyles;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a button to send a reaction.
|
||||
@@ -88,11 +81,13 @@ function ReactionButton({
|
||||
styles,
|
||||
reaction,
|
||||
t
|
||||
}: Props) {
|
||||
}: IProps) {
|
||||
const dispatch = useDispatch();
|
||||
const _onClick = useCallback(() => {
|
||||
dispatch(addReactionToBuffer(reaction));
|
||||
sendAnalytics(createReactionMenuEvent(reaction));
|
||||
if (reaction) {
|
||||
dispatch(addReactionToBuffer(reaction));
|
||||
sendAnalytics(createReactionMenuEvent(reaction));
|
||||
}
|
||||
}, [ reaction ]);
|
||||
|
||||
return (
|
||||
@@ -100,9 +95,9 @@ function ReactionButton({
|
||||
accessibilityLabel = { t(`toolbar.accessibilityLabel.${reaction}`) }
|
||||
accessibilityRole = 'button'
|
||||
onPress = { onClick || _onClick }
|
||||
style = { [ styles.style, children && styles?.gifButton ] }
|
||||
style = { [ styles.style, children && styles?.gifButton ] as ViewStyle[] }
|
||||
underlayColor = { styles.underlayColor }>
|
||||
{children ?? <Text style = { styles.emoji }>{REACTIONS[reaction].emoji}</Text>}
|
||||
{children ?? <Text style = { styles.emoji }>{REACTIONS[reaction ?? ''].emoji}</Text>}
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { Image, View } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
|
||||
import { isGifEnabled } from '../../../gifs/functions';
|
||||
import { isGifEnabled } from '../../../gifs/functions.native';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { REACTIONS } from '../../constants';
|
||||
@@ -16,18 +15,18 @@ import ReactionButton from './ReactionButton';
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ReactionMenu}.
|
||||
*/
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Used to close the overflow menu after raise hand is clicked.
|
||||
*/
|
||||
onCancel: Function,
|
||||
onCancel: Function;
|
||||
|
||||
/**
|
||||
* Whether or not it's displayed in the overflow menu.
|
||||
*/
|
||||
overflowMenu: boolean
|
||||
};
|
||||
overflowMenu: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animated reaction emoji.
|
||||
@@ -37,8 +36,8 @@ type Props = {
|
||||
function ReactionMenu({
|
||||
onCancel,
|
||||
overflowMenu
|
||||
}: Props) {
|
||||
const _styles = useSelector(state => ColorSchemeRegistry.get(state, 'Toolbox'));
|
||||
}: IProps) {
|
||||
const _styles: any = useSelector((state: IReduxState) => ColorSchemeRegistry.get(state, 'Toolbox'));
|
||||
const gifEnabled = useSelector(isGifEnabled);
|
||||
|
||||
const openGifMenu = useCallback(() => {
|
||||
@@ -62,7 +61,7 @@ function ReactionMenu({
|
||||
<ReactionButton
|
||||
onClick = { openGifMenu }
|
||||
styles = { _styles.reactionButton }>
|
||||
<Image
|
||||
<Image // @ts-ignore
|
||||
height = { 22 }
|
||||
source = { require('../../../../../images/GIPHY_icon.png') } />
|
||||
</ReactionButton>
|
||||
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { ComponentType, PureComponent } from 'react';
|
||||
import { SafeAreaView, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
|
||||
import { hideDialog } from '../../../base/dialog/actions';
|
||||
import { isDialogOpen } from '../../../base/dialog/functions';
|
||||
@@ -15,38 +14,38 @@ import ReactionMenu from './ReactionMenu';
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ReactionMenuDialog}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* True if the dialog is currently visible, false otherwise.
|
||||
*/
|
||||
_isOpen: boolean,
|
||||
|
||||
/**
|
||||
* The width of the screen.
|
||||
*/
|
||||
_width: number,
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The height of the screen.
|
||||
*/
|
||||
_height: number,
|
||||
_height: number;
|
||||
|
||||
/**
|
||||
* True if the dialog is currently visible, false otherwise.
|
||||
*/
|
||||
_isOpen: boolean;
|
||||
|
||||
/**
|
||||
* Number of conference participants.
|
||||
*/
|
||||
_participantCount: number,
|
||||
_participantCount: number;
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType;
|
||||
|
||||
/**
|
||||
* The width of the screen.
|
||||
*/
|
||||
_width: number;
|
||||
|
||||
/**
|
||||
* Used for hiding the dialog when the selection was completed.
|
||||
*/
|
||||
dispatch: Function
|
||||
};
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
/**
|
||||
* The exported React {@code Component}. We need it to execute
|
||||
@@ -55,19 +54,19 @@ type Props = {
|
||||
* XXX It does not break our coding style rule to not utilize globals for state,
|
||||
* because it is merely another name for {@code export}'s {@code default}.
|
||||
*/
|
||||
let ReactionMenu_; // eslint-disable-line prefer-const
|
||||
let ReactionMenu_: ComponentType<any>; // eslint-disable-line prefer-const
|
||||
|
||||
/**
|
||||
* Implements a React {@code Component} with some extra actions in addition to
|
||||
* those in the toolbar.
|
||||
*/
|
||||
class ReactionMenuDialog extends PureComponent<Props> {
|
||||
class ReactionMenuDialog extends PureComponent<IProps> {
|
||||
/**
|
||||
* Initializes a new {@code ReactionMenuDialog} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
@@ -103,8 +102,6 @@ class ReactionMenuDialog extends PureComponent<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_onCancel: () => boolean;
|
||||
|
||||
/**
|
||||
* Hides this {@code ReactionMenuDialog}.
|
||||
*
|
||||
@@ -127,11 +124,11 @@ class ReactionMenuDialog extends PureComponent<Props> {
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
_isOpen: isDialogOpen(state, ReactionMenu_),
|
||||
_isOpen: isDialogOpen(state, ReactionMenu_), // @ts-ignore
|
||||
_styles: ColorSchemeRegistry.get(state, 'Toolbox').reactionDialog,
|
||||
_width: state['features/base/responsive-ui'].clientWidth,
|
||||
_height: state['features/base/responsive-ui'].clientHeight,
|
||||
@@ -1,8 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { openDialog } from '../../../base/dialog/actions';
|
||||
import { isDialogOpen } from '../../../base/dialog/functions';
|
||||
import { RAISE_HAND_ENABLED } from '../../../base/flags/constants';
|
||||
@@ -19,28 +17,23 @@ import ReactionMenuDialog from './ReactionMenuDialog';
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ReactionsMenuButton}.
|
||||
*/
|
||||
type Props = AbstractButtonProps & {
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* Whether the participant raised their hand or not.
|
||||
*/
|
||||
_raisedHand: boolean,
|
||||
_raisedHand: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the reactions menu is open.
|
||||
*/
|
||||
_reactionsOpen: boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
_reactionsOpen: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a button to raise or lower hand.
|
||||
*/
|
||||
class ReactionsMenuButton extends AbstractButton<Props, *> {
|
||||
class ReactionsMenuButton extends AbstractButton<IProps> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.reactionsMenu';
|
||||
icon = IconRaiseHand;
|
||||
label = 'toolbar.openReactionsMenu';
|
||||
@@ -75,9 +68,9 @@ class ReactionsMenuButton extends AbstractButton<Props, *> {
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component instance.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps): Object {
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
const enabled = getFeatureFlag(state, RAISE_HAND_ENABLED, true);
|
||||
const { visible = enabled } = ownProps;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { createLiveStreamingDialogEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
@@ -10,7 +11,7 @@ import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractStartLiveStreamDialog}.
|
||||
*/
|
||||
export interface IProps {
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The {@code JitsiConference} for the current conference.
|
||||
@@ -39,10 +40,7 @@ export interface IProps {
|
||||
*/
|
||||
dispatch: Function;
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function;
|
||||
navigation?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface IProps extends WithTranslation {
|
||||
*/
|
||||
_liveStreaming: LiveStreamingProps;
|
||||
|
||||
classes: any;
|
||||
classes?: any;
|
||||
|
||||
/**
|
||||
* Callback invoked when the entered stream key has changed.
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Text, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { _abstractMapStateToProps } from '../../../../base/dialog/functions';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import { StyleType } from '../../../../base/styles/functions.native';
|
||||
import { setGoogleAPIState } from '../../../../google-api/actions';
|
||||
import GoogleSignInButton from '../../../../google-api/components/GoogleSignInButton.native';
|
||||
import {
|
||||
GOOGLE_API_STATES,
|
||||
GOOGLE_SCOPE_YOUTUBE
|
||||
} from '../../../../google-api/constants';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import googleApi from '../../../../google-api/googleApi.native';
|
||||
import logger from '../../../logger';
|
||||
|
||||
@@ -21,52 +22,47 @@ import styles from './styles';
|
||||
/**
|
||||
* Prop type of the component {@code GoogleSigninForm}.
|
||||
*/
|
||||
type Props = {
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Style of the dialogs feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
_dialogStyles: any;
|
||||
|
||||
/**
|
||||
* The Redux dispatch Function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
dispatch: Function;
|
||||
|
||||
/**
|
||||
* The current state of the Google api as defined in {@code constants.js}.
|
||||
*/
|
||||
googleAPIState: number,
|
||||
googleAPIState: number;
|
||||
|
||||
/**
|
||||
* The recently received Google response.
|
||||
*/
|
||||
googleResponse: Object,
|
||||
googleResponse: any;
|
||||
|
||||
/**
|
||||
* A callback to be invoked when an authenticated user changes, so
|
||||
* then we can get (or clear) the YouTube stream key.
|
||||
*/
|
||||
onUserChanged: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
onUserChanged: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to render a google sign in form, or a google stream picker dialog.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class GoogleSigninForm extends Component<Props> {
|
||||
class GoogleSigninForm extends Component<IProps> {
|
||||
/**
|
||||
* Instantiates a new {@code GoogleSigninForm} component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._logGoogleError = this._logGoogleError.bind(this);
|
||||
@@ -86,7 +82,7 @@ class GoogleSigninForm extends Component<Props> {
|
||||
scopes: [ GOOGLE_SCOPE_YOUTUBE ]
|
||||
});
|
||||
|
||||
googleApi.signInSilently().then(response => {
|
||||
googleApi.signInSilently().then((response: any) => {
|
||||
this._setApiState(response
|
||||
? GOOGLE_API_STATES.SIGNED_IN
|
||||
: GOOGLE_API_STATES.LOADED,
|
||||
@@ -95,7 +91,7 @@ class GoogleSigninForm extends Component<Props> {
|
||||
this._setApiState(GOOGLE_API_STATES.LOADED);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error: Error) => {
|
||||
this._logGoogleError(error);
|
||||
this._setApiState(GOOGLE_API_STATES.NOT_AVAILABLE);
|
||||
});
|
||||
@@ -109,9 +105,7 @@ class GoogleSigninForm extends Component<Props> {
|
||||
render() {
|
||||
const { _dialogStyles, t } = this.props;
|
||||
const { googleAPIState, googleResponse } = this.props;
|
||||
const signedInUser = googleResponse
|
||||
&& googleResponse.user
|
||||
&& googleResponse.user.email;
|
||||
const signedInUser = googleResponse?.user?.email;
|
||||
|
||||
if (googleAPIState === GOOGLE_API_STATES.NOT_AVAILABLE
|
||||
|| googleAPIState === GOOGLE_API_STATES.NEEDS_LOADING
|
||||
@@ -124,8 +118,8 @@ class GoogleSigninForm extends Component<Props> {
|
||||
: t('liveStreaming.signInCTA');
|
||||
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
<View style = { styles.helpText }>
|
||||
<View style = { styles.formWrapper as ViewStyle }>
|
||||
<View style = { styles.helpText as ViewStyle }>
|
||||
<Text
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
@@ -142,8 +136,6 @@ class GoogleSigninForm extends Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_logGoogleError: Object => void;
|
||||
|
||||
/**
|
||||
* A helper function to log developer related errors.
|
||||
*
|
||||
@@ -151,14 +143,12 @@ class GoogleSigninForm extends Component<Props> {
|
||||
* @param {Object} error - The error to be logged.
|
||||
* @returns {void}
|
||||
*/
|
||||
_logGoogleError(error) {
|
||||
_logGoogleError(error: Error) {
|
||||
// NOTE: This is a developer error message, not intended for the
|
||||
// user to see.
|
||||
logger.error('Google API error. Possible cause: bad config.', error);
|
||||
}
|
||||
|
||||
_onGoogleButtonPress: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user presses the Google button,
|
||||
* regardless of being logged in or out.
|
||||
@@ -169,7 +159,7 @@ class GoogleSigninForm extends Component<Props> {
|
||||
_onGoogleButtonPress() {
|
||||
const { googleResponse } = this.props;
|
||||
|
||||
if (googleResponse && googleResponse.user) {
|
||||
if (googleResponse?.user) {
|
||||
// the user is signed in
|
||||
this._onSignOut();
|
||||
} else {
|
||||
@@ -177,8 +167,6 @@ class GoogleSigninForm extends Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
_onSignIn: () => void;
|
||||
|
||||
/**
|
||||
* Initiates a sign in if the user is not signed in yet.
|
||||
*
|
||||
@@ -186,13 +174,11 @@ class GoogleSigninForm extends Component<Props> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSignIn() {
|
||||
googleApi.signIn().then(response => {
|
||||
googleApi.signIn().then((response: any) => {
|
||||
this._setApiState(GOOGLE_API_STATES.SIGNED_IN, response);
|
||||
}, this._logGoogleError);
|
||||
}
|
||||
|
||||
_onSignOut: () => void;
|
||||
|
||||
/**
|
||||
* Initiates a sign out if the user is signed in.
|
||||
*
|
||||
@@ -200,7 +186,7 @@ class GoogleSigninForm extends Component<Props> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSignOut() {
|
||||
googleApi.signOut().then(response => {
|
||||
googleApi.signOut().then((response: any) => {
|
||||
this._setApiState(GOOGLE_API_STATES.LOADED, response);
|
||||
}, this._logGoogleError);
|
||||
}
|
||||
@@ -213,7 +199,7 @@ class GoogleSigninForm extends Component<Props> {
|
||||
* @param {?Object} googleResponse - The response from the API.
|
||||
* @returns {void}
|
||||
*/
|
||||
_setApiState(apiState, googleResponse) {
|
||||
_setApiState(apiState: number, googleResponse?: Object) {
|
||||
this.props.onUserChanged(googleResponse);
|
||||
this.props.dispatch(setGoogleAPIState(apiState, googleResponse));
|
||||
}
|
||||
@@ -230,7 +216,7 @@ class GoogleSigninForm extends Component<Props> {
|
||||
* googleResponse: Object
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { googleAPIState, googleResponse } = state['features/google-api'];
|
||||
|
||||
return {
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { openDialog } from '../../../../base/dialog/actions';
|
||||
import { LIVE_STREAMING_ENABLED } from '../../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../../base/flags/functions';
|
||||
@@ -9,12 +8,15 @@ import { translate } from '../../../../base/i18n/functions';
|
||||
import { navigate }
|
||||
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../../mobile/navigation/routes';
|
||||
import AbstractLiveStreamButton,
|
||||
{ _mapStateToProps as _abstractMapStateToProps } from '../AbstractLiveStreamButton';
|
||||
import type { Props } from '../AbstractStartLiveStreamDialog';
|
||||
import AbstractLiveStreamButton, {
|
||||
IProps as AbstractProps,
|
||||
_mapStateToProps as _abstractMapStateToProps
|
||||
} from '../AbstractLiveStreamButton';
|
||||
import { IProps } from '../AbstractStartLiveStreamDialog';
|
||||
|
||||
import StopLiveStreamDialog from './StopLiveStreamDialog';
|
||||
|
||||
type Props = IProps & AbstractProps;
|
||||
|
||||
/**
|
||||
* Button for opening the live stream settings screen.
|
||||
@@ -48,7 +50,7 @@ class LiveStreamButton extends AbstractLiveStreamButton<Props> {
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function mapStateToProps(state: Object, ownProps: Object) {
|
||||
export function mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const enabled = getFeatureFlag(state, LIVE_STREAMING_ENABLED, true);
|
||||
const abstractProps = _abstractMapStateToProps(state, ownProps);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
|
||||
import { StyleType } from '../../../../base/styles/functions.any';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import googleApi from '../../../../google-api/googleApi.native';
|
||||
import HeaderNavigationButton
|
||||
from '../../../../mobile/navigation/components/HeaderNavigationButton';
|
||||
import { goBack }
|
||||
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { setLiveStreamKey } from '../../../actions';
|
||||
import AbstractStartLiveStreamDialog,
|
||||
{ type Props, _mapStateToProps } from '../AbstractStartLiveStreamDialog';
|
||||
import AbstractStartLiveStreamDialog, { IProps, _mapStateToProps } from '../AbstractStartLiveStreamDialog';
|
||||
|
||||
import GoogleSigninForm from './GoogleSigninForm';
|
||||
import StreamKeyForm from './StreamKeyForm';
|
||||
@@ -23,13 +23,13 @@ import styles from './styles';
|
||||
* A React Component for requesting a YouTube stream key to use for live
|
||||
* streaming of the current conference.
|
||||
*/
|
||||
class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
|
||||
class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<IProps> {
|
||||
/**
|
||||
* Constructor of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
@@ -60,8 +60,6 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
|
||||
});
|
||||
}
|
||||
|
||||
_onStartPress: () => void;
|
||||
|
||||
/**
|
||||
* Starts live stream session and goes back to the previous screen.
|
||||
*
|
||||
@@ -78,7 +76,7 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<JitsiScreen style = { styles.startLiveStreamContainer }>
|
||||
<JitsiScreen style = { styles.startLiveStreamContainer as StyleType }>
|
||||
<GoogleSigninForm
|
||||
onUserChanged = { this._onUserChanged } />
|
||||
<StreamKeyPicker
|
||||
@@ -87,20 +85,12 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
|
||||
<StreamKeyForm
|
||||
onChange = { this._onStreamKeyChangeNative }
|
||||
value = {
|
||||
this.state.streamKey || this.props._streamKey
|
||||
this.state.streamKey || this.props._streamKey || ''
|
||||
} />
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
_onCancel: () => boolean;
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
|
||||
_onStreamKeyChange: string => void;
|
||||
|
||||
_onStreamKeyChangeNative: string => void;
|
||||
|
||||
/**
|
||||
* Callback to handle stream key changes.
|
||||
*
|
||||
@@ -113,13 +103,11 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
|
||||
* @param {string} streamKey - The new key value.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStreamKeyChangeNative(streamKey) {
|
||||
_onStreamKeyChangeNative(streamKey: string) {
|
||||
this.props.dispatch(setLiveStreamKey(streamKey));
|
||||
this._onStreamKeyChange(streamKey);
|
||||
}
|
||||
|
||||
_onStreamKeyPick: string => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user selects a stream from the picker.
|
||||
*
|
||||
@@ -127,14 +115,12 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
|
||||
* @param {string} streamKey - The key of the selected stream.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStreamKeyPick(streamKey) {
|
||||
_onStreamKeyPick(streamKey: string) {
|
||||
this.setState({
|
||||
streamKey
|
||||
});
|
||||
}
|
||||
|
||||
_onUserChanged: Object => void;
|
||||
|
||||
/**
|
||||
* A callback to be invoked when an authenticated user changes, so
|
||||
* then we can get (or clear) the YouTube stream key.
|
||||
@@ -145,12 +131,12 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
|
||||
* @param {Object} response - The retrieved signin response.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onUserChanged(response) {
|
||||
_onUserChanged(response: Object) {
|
||||
if (response) {
|
||||
googleApi.getTokens()
|
||||
.then(tokens => {
|
||||
.then((tokens: any) => {
|
||||
googleApi.getYouTubeLiveStreams(tokens.accessToken)
|
||||
.then(broadcasts => {
|
||||
.then((broadcasts: any) => {
|
||||
this.setState({
|
||||
broadcasts
|
||||
});
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
@@ -30,8 +28,6 @@ class StopLiveStreamDialog extends AbstractStopLiveStreamDialog {
|
||||
onSubmit = { this._onSubmit } />
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(StopLiveStreamDialog));
|
||||
@@ -1,42 +1,41 @@
|
||||
import React from 'react';
|
||||
import { Linking, Text, View } from 'react-native';
|
||||
import { Linking, Text, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { _abstractMapStateToProps } from '../../../../base/dialog/functions';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import { StyleType } from '../../../../base/styles/functions.native';
|
||||
import Button from '../../../../base/ui/components/native/Button';
|
||||
import Input from '../../../../base/ui/components/native/Input';
|
||||
import { BUTTON_TYPES } from '../../../../base/ui/constants.native';
|
||||
import AbstractStreamKeyForm, {
|
||||
type Props as AbstractProps
|
||||
IProps as AbstractProps
|
||||
} from '../AbstractStreamKeyForm';
|
||||
import { getLiveStreaming } from '../functions';
|
||||
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* Style of the dialogs feature.
|
||||
*/
|
||||
_dialogStyles: StyleType
|
||||
};
|
||||
_dialogStyles: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* A React Component for entering a key for starting a YouTube live stream.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
class StreamKeyForm extends AbstractStreamKeyForm<IProps> {
|
||||
/**
|
||||
* Initializes a new {@code StreamKeyForm} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* @param {IProps} props - The React {@code Component} props to initialize
|
||||
* the new {@code StreamKeyForm} instance with.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
@@ -57,7 +56,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style = { styles.formWrapper }>
|
||||
<View style = { styles.formWrapper as ViewStyle }>
|
||||
<Input
|
||||
customStyles = {{
|
||||
input: styles.streamKeyInput,
|
||||
@@ -65,7 +64,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
onChange = { this._onInputChange }
|
||||
placeholder = { t('liveStreaming.enterStreamKey') }
|
||||
value = { this.props.value } />
|
||||
<View style = { styles.formValidationItem }>
|
||||
<View style = { styles.formValidationItem as ViewStyle }>
|
||||
{
|
||||
this.state.showValidationError && <Text
|
||||
style = { [
|
||||
@@ -77,7 +76,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
<View style = { styles.formButtonsWrapper }>
|
||||
<View style = { styles.formButtonsWrapper as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'liveStreaming.streamIdHelp'
|
||||
labelKey = 'liveStreaming.streamIdHelp'
|
||||
@@ -101,10 +100,6 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_onInputChange: Object => void;
|
||||
|
||||
_onOpenGooglePrivacyPolicy: () => void;
|
||||
|
||||
/**
|
||||
* Opens the Google Privacy Policy web page.
|
||||
*
|
||||
@@ -115,8 +110,6 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
Linking.openURL(this.props._liveStreaming.dataPrivacyURL);
|
||||
}
|
||||
|
||||
_onOpenHelp: () => void;
|
||||
|
||||
/**
|
||||
* Opens the information link on how to manually locate a YouTube broadcast
|
||||
* stream key.
|
||||
@@ -132,8 +125,6 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
_onOpenYoutubeTerms: () => void;
|
||||
|
||||
/**
|
||||
* Opens the YouTube terms and conditions web page.
|
||||
*
|
||||
@@ -155,7 +146,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
* _liveStreaming: LiveStreamingProps
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_liveStreaming: getLiveStreaming(state)
|
||||
@@ -1,52 +1,46 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import {
|
||||
Linking,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
View
|
||||
View,
|
||||
ViewStyle
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../../../base/dialog/functions';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import { StyleType } from '../../../../base/styles/functions.native';
|
||||
import { YOUTUBE_LIVE_DASHBOARD_URL } from '../constants';
|
||||
|
||||
import styles, { ACTIVE_OPACITY, TOUCHABLE_UNDERLAY } from './styles';
|
||||
|
||||
type Props = {
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Style of the dialogs feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
_dialogStyles: any;
|
||||
|
||||
/**
|
||||
* The list of broadcasts the user can pick from.
|
||||
*/
|
||||
broadcasts: ?Array<Object>,
|
||||
broadcasts?: Array<{ key: string; title: string; }>;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user picked a broadcast. To be invoked
|
||||
* with a single key (string).
|
||||
*/
|
||||
onChange: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
onChange: Function;
|
||||
}
|
||||
|
||||
type State = {
|
||||
interface IState {
|
||||
|
||||
/**
|
||||
* The key of the currently selected stream.
|
||||
*/
|
||||
streamKey: ?string
|
||||
/**
|
||||
* The key of the currently selected stream.
|
||||
*/
|
||||
streamKey?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,14 +51,14 @@ type State = {
|
||||
* a later point to unify mobile and web logic for this functionality. But it's
|
||||
* out of the scope for now of the mobile live streaming functionality.
|
||||
*/
|
||||
class StreamKeyPicker extends Component<Props, State> {
|
||||
class StreamKeyPicker extends Component<IProps, IState> {
|
||||
|
||||
/**
|
||||
* Instantiates a new instance of StreamKeyPicker.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -89,7 +83,7 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
|
||||
if (!broadcasts.length) {
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
<View style = { styles.formWrapper as ViewStyle }>
|
||||
<TouchableOpacity
|
||||
onPress = { this._onOpenYoutubeDashboard }>
|
||||
<Text
|
||||
@@ -106,8 +100,8 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
<View style = { styles.streamKeyPickerCta }>
|
||||
<View style = { styles.formWrapper as ViewStyle }>
|
||||
<View style = { styles.streamKeyPickerCta as ViewStyle }>
|
||||
<Text
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
@@ -116,7 +110,7 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
{ this.props.t('liveStreaming.choose') }
|
||||
</Text>
|
||||
</View>
|
||||
<View style = { styles.streamKeyPickerWrapper } >
|
||||
<View style = { styles.streamKeyPickerWrapper as ViewStyle } >
|
||||
{ broadcasts.map((broadcast, index) =>
|
||||
(<TouchableHighlight
|
||||
activeOpacity = { ACTIVE_OPACITY }
|
||||
@@ -126,7 +120,7 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
styles.streamKeyPickerItem,
|
||||
this.state.streamKey === broadcast.key
|
||||
? styles.streamKeyPickerItemHighlight : null
|
||||
] }
|
||||
] as ViewStyle[] }
|
||||
underlayColor = { TOUCHABLE_UNDERLAY }>
|
||||
<Text
|
||||
style = { [
|
||||
@@ -142,8 +136,6 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
_onOpenYoutubeDashboard: () => void;
|
||||
|
||||
/**
|
||||
* Opens the link which should display the YouTube broadcast live stream
|
||||
* key.
|
||||
@@ -155,8 +147,6 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
Linking.openURL(YOUTUBE_LIVE_DASHBOARD_URL);
|
||||
}
|
||||
|
||||
_onStreamPick: string => Function;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user picks a stream from the list.
|
||||
*
|
||||
@@ -164,7 +154,7 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
* @param {string} streamKey - The key of the stream selected.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onStreamPick(streamKey) {
|
||||
_onStreamPick(streamKey: string) {
|
||||
return () => {
|
||||
this.setState({
|
||||
streamKey
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { createStyleSheet } from '../../../../base/styles/functions.native';
|
||||
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
@@ -83,6 +83,8 @@ export interface IProps extends WithTranslation {
|
||||
* The redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
navigation: any;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -386,6 +388,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
|
||||
* {@code StartRecordingDialog} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {any} _ownProps - Component's own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _appKey: string,
|
||||
@@ -399,7 +402,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
|
||||
* _token: string
|
||||
* }}
|
||||
*/
|
||||
export function mapStateToProps(state: IReduxState) {
|
||||
export function mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const {
|
||||
transcription,
|
||||
recordingService,
|
||||
|
||||
@@ -92,7 +92,7 @@ export interface IProps extends WithTranslation {
|
||||
/**
|
||||
* Whether or not we should only record the local streams.
|
||||
*/
|
||||
localRecordingOnlySelf: boolean;
|
||||
localRecordingOnlySelf?: boolean;
|
||||
|
||||
/**
|
||||
* The function will be called when there are changes related to the
|
||||
@@ -103,7 +103,7 @@ export interface IProps extends WithTranslation {
|
||||
/**
|
||||
* Callback to change the local recording only self setting.
|
||||
*/
|
||||
onLocalRecordingSelfChange: () => void;
|
||||
onLocalRecordingSelfChange?: () => void;
|
||||
|
||||
/**
|
||||
* Callback to be invoked on sharing setting change.
|
||||
|
||||
@@ -1,35 +1,31 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import { IconHighlight } from '../../../../base/icons/svg';
|
||||
import Label from '../../../../base/label/components/native/Label';
|
||||
import BaseTheme from '../../../../base/ui/components/BaseTheme';
|
||||
import AbstractHighlightButton, {
|
||||
type Props as AbstractProps,
|
||||
IProps as AbstractProps,
|
||||
_abstractMapStateToProps
|
||||
} from '../AbstractHighlightButton';
|
||||
import styles from '../styles.native';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
_disabled: boolean,
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
_disabled: boolean;
|
||||
|
||||
/**
|
||||
* Flag controlling visibility of the component.
|
||||
*/
|
||||
_visible: boolean,
|
||||
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
_visible: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* React {@code Component} responsible for displaying an action that
|
||||
* allows users to highlight a meeting moment.
|
||||
*/
|
||||
export class HighlightButton extends AbstractHighlightButton<Props> {
|
||||
export class HighlightButton extends AbstractHighlightButton<IProps> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
@@ -52,7 +48,6 @@ export class HighlightButton extends AbstractHighlightButton<Props> {
|
||||
<Label
|
||||
icon = { IconHighlight }
|
||||
iconColor = { BaseTheme.palette.field01 }
|
||||
id = 'highlightMeetingLabel'
|
||||
style = { styles.highlightButton }
|
||||
text = { t('recording.highlight') }
|
||||
textStyle = { styles.highlightButtonText } />
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Text, View, ViewStyle } from 'react-native';
|
||||
import { batch, useDispatch } from 'react-redux';
|
||||
|
||||
import { hideSheet } from '../../../../base/dialog/actions';
|
||||
@@ -28,7 +28,7 @@ const HighlightDialog = () => {
|
||||
<Text style = { styles.highlightDialogText }>
|
||||
{ t('recording.highlightMomentSucessDescription') }
|
||||
</Text>
|
||||
<View style = { styles.highlightDialogButtonsContainer } >
|
||||
<View style = { styles.highlightDialogButtonsContainer as ViewStyle } >
|
||||
<Button
|
||||
accessibilityLabel = 'dialog.Cancel'
|
||||
labelKey = 'dialog.Cancel'
|
||||
@@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { Platform } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { openDialog } from '../../../../base/dialog/actions';
|
||||
import { IOS_RECORDING_ENABLED, RECORDING_ENABLED } from '../../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../../base/flags/functions';
|
||||
@@ -10,12 +9,15 @@ import { translate } from '../../../../base/i18n/functions';
|
||||
import { navigate }
|
||||
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../../mobile/navigation/routes';
|
||||
import type { Props } from '../../LiveStream/AbstractStartLiveStreamDialog';
|
||||
import AbstractRecordButton,
|
||||
{ _mapStateToProps as _abstractMapStateToProps } from '../AbstractRecordButton';
|
||||
import { IProps } from '../../LiveStream/AbstractStartLiveStreamDialog';
|
||||
import AbstractRecordButton, {
|
||||
IProps as AbstractProps,
|
||||
_mapStateToProps as _abstractMapStateToProps
|
||||
} from '../AbstractRecordButton';
|
||||
|
||||
import StopRecordingDialog from './StopRecordingDialog';
|
||||
|
||||
type Props = IProps & AbstractProps;
|
||||
|
||||
/**
|
||||
* Button for opening a screen where a recording session can be started.
|
||||
@@ -49,10 +51,10 @@ class RecordButton extends AbstractRecordButton<Props> {
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function mapStateToProps(state: Object, ownProps: Object) {
|
||||
export function mapStateToProps(state: IReduxState) {
|
||||
const enabled = getFeatureFlag(state, RECORDING_ENABLED, true);
|
||||
const iosEnabled = Platform.OS !== 'ios' || getFeatureFlag(state, IOS_RECORDING_ENABLED, false);
|
||||
const abstractProps = _abstractMapStateToProps(state, ownProps);
|
||||
const abstractProps = _abstractMapStateToProps(state);
|
||||
|
||||
return {
|
||||
...abstractProps,
|
||||
@@ -9,7 +9,7 @@ import { goBack } from
|
||||
'../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { RECORDING_TYPES } from '../../../constants';
|
||||
import AbstractStartRecordingDialog, {
|
||||
type Props,
|
||||
IProps,
|
||||
mapStateToProps
|
||||
} from '../AbstractStartRecordingDialog';
|
||||
import styles from '../styles.native';
|
||||
@@ -23,14 +23,14 @@ import StartRecordingDialogContent from './StartRecordingDialogContent';
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
|
||||
class StartRecordingDialog extends AbstractStartRecordingDialog {
|
||||
|
||||
/**
|
||||
* Constructor of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onStartPress = this._onStartPress.bind(this);
|
||||
@@ -66,7 +66,7 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
super.componentDidUpdate(prevProps);
|
||||
|
||||
const { navigation, t } = this.props;
|
||||
@@ -83,8 +83,6 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
|
||||
});
|
||||
}
|
||||
|
||||
_onStartPress: () => void;
|
||||
|
||||
/**
|
||||
* Starts recording session and goes back to the previous screen.
|
||||
*
|
||||
@@ -94,8 +92,6 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
|
||||
this._onSubmit() && goBack();
|
||||
}
|
||||
|
||||
isStartRecordingDisabled: () => boolean;
|
||||
|
||||
/**
|
||||
* Disables start recording button.
|
||||
*
|
||||
@@ -154,11 +150,6 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
_areIntegrationsEnabled: () => boolean;
|
||||
_onSubmit: () => boolean;
|
||||
_onSelectedRecordingServiceChanged: (string) => void;
|
||||
_onSharingSettingChanged: () => void;
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(StartRecordingDialog));
|
||||
@@ -1,12 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import ConfirmDialog from '../../../../base/dialog/components/native/ConfirmDialog';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import AbstractStopRecordingDialog, {
|
||||
type Props,
|
||||
IProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractStopRecordingDialog';
|
||||
|
||||
@@ -16,7 +14,7 @@ import AbstractStopRecordingDialog, {
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class StopRecordingDialog extends AbstractStopRecordingDialog<Props> {
|
||||
class StopRecordingDialog extends AbstractStopRecordingDialog<IProps> {
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
@@ -30,8 +28,6 @@ class StopRecordingDialog extends AbstractStopRecordingDialog<Props> {
|
||||
onSubmit = { this._onSubmit } />
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(StopRecordingDialog));
|
||||
@@ -1,23 +1,21 @@
|
||||
// @flow
|
||||
|
||||
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
|
||||
import { schemeColor } from '../../../base/color-scheme/functions';
|
||||
import { BoxModel } from '../../../base/styles/components/styles/BoxModel';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
export const DROPBOX_LOGO = require('../../../../../images/dropboxLogo_square.png');
|
||||
export const ICON_CLOUD = require('../../../../../images/icon-cloud.png');
|
||||
export const ICON_INFO = require('../../../../../images/icon-info.png');
|
||||
export const ICON_USERS = require('../../../../../images/icon-users.png');
|
||||
export const LOCAL_RECORDING = require('../../../../../images/downloadLocalRecording.png');
|
||||
export const TRACK_COLOR = BaseTheme.palette.ui07;
|
||||
|
||||
/* eslint-enable @typescript-eslint/no-var-requires */
|
||||
|
||||
// XXX The "standard" {@code BoxModel.padding} has been deemed insufficient in
|
||||
// the special case(s) of the recording feature below.
|
||||
const _PADDING = BoxModel.padding * 1.5;
|
||||
|
||||
|
||||
const header = {
|
||||
alignItems: 'center',
|
||||
flex: 0,
|
||||
@@ -109,11 +109,12 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
|
||||
* Maps redux state to component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @param {any} ownProps - Component's own props.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
function mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
...abstractMapStateToProps(state, ownProps),
|
||||
_screenshotCaptureEnabled: isScreenshotCaptureEnabled(state, true, false)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ import {
|
||||
LOCAL_RECORDING
|
||||
} from '../styles.web';
|
||||
|
||||
const EMPTY_FUNCTION = () => {
|
||||
// empty
|
||||
};
|
||||
|
||||
/**
|
||||
* The start recording dialog content for the mobile application.
|
||||
@@ -359,10 +362,10 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent<IP
|
||||
{t('recording.onlyRecordSelf')}
|
||||
</Text>
|
||||
<Switch
|
||||
checked = { localRecordingOnlySelf }
|
||||
checked = { Boolean(localRecordingOnlySelf) }
|
||||
className = 'recording-switch'
|
||||
disabled = { isValidating }
|
||||
onChange = { onLocalRecordingSelfChange } />
|
||||
onChange = { onLocalRecordingSelfChange ?? EMPTY_FUNCTION } />
|
||||
</Container>
|
||||
</Container>
|
||||
)}
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import ExpandedLabel, { Props as AbstractProps } from '../../../base/label/components/native/ExpandedLabel';
|
||||
import ExpandedLabel, { IProps as AbstractProps } from '../../../base/label/components/native/ExpandedLabel';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { getSessionStatusToShow } from '../../functions';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* The status of the highermost priority session.
|
||||
*/
|
||||
_status: ?string,
|
||||
_status?: string;
|
||||
|
||||
/**
|
||||
* The recording mode this indicator should display.
|
||||
*/
|
||||
mode: string,
|
||||
mode: string;
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
t: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* A react {@code Component} that implements an expanded label as tooltip-like
|
||||
* component to explain the meaning of the {@code RecordingLabel}.
|
||||
*/
|
||||
class RecordingExpandedLabel extends ExpandedLabel<Props> {
|
||||
class RecordingExpandedLabel extends ExpandedLabel<IProps> {
|
||||
|
||||
/**
|
||||
* Returns the label specific text of this {@code ExpandedLabel}.
|
||||
@@ -70,13 +69,13 @@ class RecordingExpandedLabel extends ExpandedLabel<Props> {
|
||||
* {@code RecordingExpandedLabel}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The component's own props.
|
||||
* @param {IProps} ownProps - The component's own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _status: ?string
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { mode } = ownProps;
|
||||
|
||||
return {
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Label from '../../../base/label/components/native/Label';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { StyleType } from '../../../base/styles/functions.any';
|
||||
import AbstractRecordingLabel, {
|
||||
_mapStateToProps
|
||||
} from '../AbstractRecordingLabel';
|
||||
@@ -26,7 +25,7 @@ class RecordingLabel extends AbstractRecordingLabel {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderLabel() {
|
||||
let status = 'on';
|
||||
let status: 'on' | 'in_progress' | 'off' = 'on';
|
||||
|
||||
switch (this.props._status) {
|
||||
case JitsiRecordingConstants.status.PENDING:
|
||||
@@ -40,12 +39,10 @@ class RecordingLabel extends AbstractRecordingLabel {
|
||||
return (
|
||||
<Label
|
||||
status = { status }
|
||||
style = { styles.indicatorStyle }
|
||||
text = { this.props.t(this._getLabelKey()) } />
|
||||
style = { styles.indicatorStyle as StyleType }
|
||||
text = { this.props.t(this._getLabelKey() ?? '') } />
|
||||
);
|
||||
}
|
||||
|
||||
_getLabelKey: () => ?string;
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(RecordingLabel));
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { createStyleSheet } from '../../../base/styles/functions.native';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
|
||||
@@ -698,9 +698,6 @@ export function resume() {
|
||||
|
||||
logger.log('Resuming remote control controller.');
|
||||
|
||||
// FIXME: Once the keyboard shortcuts are using react/redux.
|
||||
APP.keyboardshortcut.enable(false);
|
||||
|
||||
area.mousemove((event: React.MouseEvent) => {
|
||||
dispatch(mouseMoved(event));
|
||||
});
|
||||
@@ -747,9 +744,6 @@ export function pause() {
|
||||
|
||||
logger.log('Pausing remote control controller.');
|
||||
|
||||
// FIXME: Once the keyboard shortcuts are using react/redux.
|
||||
APP.keyboardshortcut.enable(true);
|
||||
|
||||
const area = getRemoteConrolEventCaptureArea();
|
||||
|
||||
if (area) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
// @ts-expect-error
|
||||
import keyboardShortcut from '../../../modules/keyboardshortcut/keyboardshortcut';
|
||||
import { IStore } from '../app/types';
|
||||
import {
|
||||
setFollowMe,
|
||||
@@ -11,6 +9,7 @@ import {
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import i18next from '../base/i18n/i18next';
|
||||
import { updateSettings } from '../base/settings/actions';
|
||||
import { disableKeyboardShortcuts, enableKeyboardShortcuts } from '../keyboard-shortcuts/actions';
|
||||
import { toggleBackgroundEffect } from '../virtual-background/actions';
|
||||
import virtualBackgroundLogger from '../virtual-background/logger';
|
||||
|
||||
@@ -247,7 +246,11 @@ export function submitShortcutsTab(newState: any) {
|
||||
const currentState = getShortcutsTabProps(getState());
|
||||
|
||||
if (newState.keyboardShortcutsEnabled !== currentState.keyboardShortcutsEnabled) {
|
||||
keyboardShortcut.enable(newState.keyboardShortcutsEnabled);
|
||||
if (newState.keyboardShortcutsEnabled) {
|
||||
dispatch(enableKeyboardShortcuts());
|
||||
} else {
|
||||
dispatch(disableKeyboardShortcuts());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { withStyles } from '@mui/styles';
|
||||
import React from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
// @ts-expect-error
|
||||
import keyboardShortcut from '../../../../../modules/keyboardshortcut/keyboardshortcut';
|
||||
import AbstractDialogTab, {
|
||||
IProps as AbstractDialogTabProps } from '../../../base/dialog/components/web/AbstractDialogTab';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
@@ -30,6 +28,11 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
|
||||
* Wether the keyboard shortcuts are enabled or not.
|
||||
*/
|
||||
keyboardShortcutsEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The keyboard shortcuts descriptions.
|
||||
*/
|
||||
keyboardShortcutsHelpDescriptions: Map<string, string>;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => {
|
||||
@@ -145,11 +148,12 @@ class ShortcutsTab extends AbstractDialogTab<IProps, any> {
|
||||
const {
|
||||
classes,
|
||||
displayShortcuts,
|
||||
keyboardShortcutsHelpDescriptions,
|
||||
keyboardShortcutsEnabled,
|
||||
t
|
||||
} = this.props;
|
||||
const shortcutDescriptions: Map<string, string> = displayShortcuts
|
||||
? keyboardShortcut.getShortcutsDescriptions()
|
||||
? keyboardShortcutsHelpDescriptions
|
||||
: new Map();
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,6 +13,7 @@ import Checkbox from '../../../../base/ui/components/web/Checkbox';
|
||||
import ContextMenu from '../../../../base/ui/components/web/ContextMenu';
|
||||
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
|
||||
import ContextMenuItemGroup from '../../../../base/ui/components/web/ContextMenuItemGroup';
|
||||
import { checkBlurSupport } from '../../../../virtual-background/functions';
|
||||
import { openSettingsDialog } from '../../../actions';
|
||||
import { SETTINGS_TABS } from '../../../constants';
|
||||
import { createLocalVideoTracks } from '../../../functions.web';
|
||||
@@ -286,6 +287,8 @@ const VideoSettingsContent = ({
|
||||
}
|
||||
}, [ videoDeviceIds ]);
|
||||
|
||||
const virtualBackgroundSupported = checkBlurSupport();
|
||||
|
||||
return (
|
||||
<ContextMenu
|
||||
aria-labelledby = 'video-settings-button'
|
||||
@@ -298,11 +301,11 @@ const VideoSettingsContent = ({
|
||||
{trackData.map((data, i) => _renderPreviewEntry(data, i))}
|
||||
</ContextMenuItemGroup>
|
||||
<ContextMenuItemGroup>
|
||||
<ContextMenuItem
|
||||
{ virtualBackgroundSupported && <ContextMenuItem
|
||||
accessibilityLabel = 'virtualBackground.title'
|
||||
icon = { IconImage }
|
||||
onClick = { selectBackground }
|
||||
text = { t('virtualBackground.title') } />
|
||||
text = { t('virtualBackground.title') } /> }
|
||||
<div
|
||||
className = { classes.checkboxContainer }
|
||||
onClick = { stopPropagation }>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// @ts-expect-error
|
||||
import keyboardShortcut from '../../../modules/keyboardshortcut/keyboardshortcut';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { createLocalTrack } from '../base/lib-jitsi-meet/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { areKeyboardShortcutsEnabled, getKeyboardShortcutsHelpDescriptions } from '../keyboard-shortcuts/functions';
|
||||
import { isPrejoinPageVisible } from '../prejoin/functions';
|
||||
|
||||
export * from './functions.any';
|
||||
@@ -84,6 +83,7 @@ export function getShortcutsTabProps(stateful: IStateful, isDisplayedOnWelcomePa
|
||||
|
||||
return {
|
||||
displayShortcuts: !isDisplayedOnWelcomePage && !isPrejoinPageVisible(state),
|
||||
keyboardShortcutsEnabled: keyboardShortcut.getEnabled()
|
||||
keyboardShortcutsEnabled: areKeyboardShortcutsEnabled(state),
|
||||
keyboardShortcutsHelpDescriptions: getKeyboardShortcutsHelpDescriptions(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { MEDIA_TYPE } from '../../base/media/constants';
|
||||
import AbstractAudioMuteButton from '../../base/toolbox/components/AbstractAudioMuteButton';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../base/toolbox/components/AbstractButton';
|
||||
import { isLocalTrackMuted } from '../../base/tracks/functions';
|
||||
import { registerShortcut, unregisterShortcut } from '../../keyboard-shortcuts/actions';
|
||||
import { muteLocal } from '../../video-menu/actions';
|
||||
import { isAudioMuteButtonDisabled } from '../functions';
|
||||
|
||||
@@ -61,12 +62,15 @@ class AudioMuteButton extends AbstractAudioMuteButton<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
typeof APP === 'undefined'
|
||||
|| APP.keyboardshortcut.registerShortcut(
|
||||
'M',
|
||||
null,
|
||||
this._onKeyboardShortcut,
|
||||
'keyboardShortcuts.mute');
|
||||
if (typeof APP === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch(registerShortcut({
|
||||
character: 'M',
|
||||
helpDescription: 'keyboardShortcuts.mute',
|
||||
handler: this._onKeyboardShortcut
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,8 +80,11 @@ class AudioMuteButton extends AbstractAudioMuteButton<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
typeof APP === 'undefined'
|
||||
|| APP.keyboardshortcut.unregisterShortcut('M');
|
||||
if (typeof APP === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch(unregisterShortcut('M'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ import { MEDIA_TYPE } from '../../base/media/constants';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../base/toolbox/components/AbstractButton';
|
||||
import AbstractVideoMuteButton from '../../base/toolbox/components/AbstractVideoMuteButton';
|
||||
import { isLocalTrackMuted } from '../../base/tracks/functions';
|
||||
import { registerShortcut, unregisterShortcut } from '../../keyboard-shortcuts/actions';
|
||||
import { handleToggleVideoMuted } from '../actions.any';
|
||||
import { isVideoMuteButtonDisabled } from '../functions';
|
||||
|
||||
@@ -62,12 +63,15 @@ class VideoMuteButton extends AbstractVideoMuteButton<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
typeof APP === 'undefined'
|
||||
|| APP.keyboardshortcut.registerShortcut(
|
||||
'V',
|
||||
null,
|
||||
this._onKeyboardShortcut,
|
||||
'keyboardShortcuts.videoMute');
|
||||
if (typeof APP === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch(registerShortcut({
|
||||
character: 'V',
|
||||
helpDescription: 'keyboardShortcuts.videoMute',
|
||||
handler: this._onKeyboardShortcut
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,8 +81,11 @@ class VideoMuteButton extends AbstractVideoMuteButton<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
typeof APP === 'undefined'
|
||||
|| APP.keyboardshortcut.unregisterShortcut('V');
|
||||
if (typeof APP === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch(unregisterShortcut('V'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,8 +3,6 @@ import React, { Component, RefObject } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { batch, connect } from 'react-redux';
|
||||
|
||||
// @ts-expect-error
|
||||
import keyboardShortcut from '../../../../../modules/keyboardshortcut/keyboardshortcut';
|
||||
import { isSpeakerStatsDisabled } from '../../../../features/speaker-stats/functions';
|
||||
import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
@@ -42,7 +40,9 @@ import { setGifMenuVisibility } from '../../../gifs/actions';
|
||||
import { isGifEnabled } from '../../../gifs/functions.web';
|
||||
import InviteButton from '../../../invite/components/add-people-dialog/web/InviteButton';
|
||||
import { isVpaasMeeting } from '../../../jaas/functions';
|
||||
import { registerShortcut, unregisterShortcut } from '../../../keyboard-shortcuts/actions';
|
||||
import KeyboardShortcutsButton from '../../../keyboard-shortcuts/components/web/KeyboardShortcutsButton';
|
||||
import { areKeyboardShortcutsEnabled } from '../../../keyboard-shortcuts/functions';
|
||||
import NoiseSuppressionButton from '../../../noise-suppression/components/NoiseSuppressionButton';
|
||||
import {
|
||||
close as closeParticipantsPane,
|
||||
@@ -288,6 +288,11 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
_sharingVideo?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the shortcut buttons are enabled.
|
||||
*/
|
||||
_shortcutsEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the tile view is enabled.
|
||||
*/
|
||||
@@ -450,11 +455,11 @@ class Toolbox extends Component<IProps> {
|
||||
|
||||
KEYBOARD_SHORTCUTS.forEach(shortcut => {
|
||||
if (typeof shortcut === 'object') {
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
shortcut.character,
|
||||
null,
|
||||
shortcut.exec,
|
||||
shortcut.helpDescription);
|
||||
dispatch(registerShortcut({
|
||||
character: shortcut.character,
|
||||
handler: shortcut.exec,
|
||||
helpDescription: shortcut.helpDescription
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -476,12 +481,12 @@ class Toolbox extends Component<IProps> {
|
||||
});
|
||||
|
||||
REACTION_SHORTCUTS.forEach(shortcut => {
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
shortcut.character,
|
||||
null,
|
||||
shortcut.exec,
|
||||
shortcut.helpDescription,
|
||||
shortcut.altKey);
|
||||
dispatch(registerShortcut({
|
||||
alt: shortcut.altKey,
|
||||
character: shortcut.character,
|
||||
handler: shortcut.exec,
|
||||
helpDescription: shortcut.helpDescription
|
||||
}));
|
||||
});
|
||||
|
||||
if (_gifsEnabled) {
|
||||
@@ -492,12 +497,11 @@ class Toolbox extends Component<IProps> {
|
||||
});
|
||||
};
|
||||
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
'G',
|
||||
null,
|
||||
onGifShortcut,
|
||||
t('keyboardShortcuts.giphyMenu')
|
||||
);
|
||||
dispatch(registerShortcut({
|
||||
character: 'G',
|
||||
handler: onGifShortcut,
|
||||
helpDescription: 'keyboardShortcuts.giphyMenu'
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,13 +542,15 @@ class Toolbox extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
[ 'A', 'C', 'D', 'R', 'S' ].forEach(letter =>
|
||||
APP.keyboardshortcut.unregisterShortcut(letter));
|
||||
dispatch(unregisterShortcut(letter)));
|
||||
|
||||
if (this.props._reactionsEnabled) {
|
||||
Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
|
||||
.forEach(letter =>
|
||||
APP.keyboardshortcut.unregisterShortcut(letter, true));
|
||||
dispatch(unregisterShortcut(letter, true)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,6 +719,7 @@ class Toolbox extends Component<IProps> {
|
||||
_multiStreamModeEnabled,
|
||||
_reactionsEnabled,
|
||||
_screenSharing,
|
||||
_shortcutsEnabled,
|
||||
_whiteboardEnabled
|
||||
} = this.props;
|
||||
|
||||
@@ -874,7 +881,7 @@ class Toolbox extends Component<IProps> {
|
||||
group: 4
|
||||
};
|
||||
|
||||
const shortcuts = !_isMobile && keyboardShortcut.getEnabled() && {
|
||||
const shortcuts = !_isMobile && _shortcutsEnabled && {
|
||||
key: 'shortcuts',
|
||||
Content: KeyboardShortcutsButton,
|
||||
group: 4
|
||||
@@ -1585,6 +1592,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
_raisedHand: hasRaisedHand(localParticipant),
|
||||
_reactionsEnabled: isReactionsEnabled(state),
|
||||
_screenSharing: isScreenVideoShared(state),
|
||||
_shortcutsEnabled: areKeyboardShortcutsEnabled(state),
|
||||
_tileViewEnabled: shouldDisplayTileView(state),
|
||||
_toolbarButtons: toolbarButtons,
|
||||
_virtualSource: state['features/virtual-background'].virtualSource,
|
||||
|
||||
157
resources/prosody-plugins/mod_measure_message_count.lua
Normal file
157
resources/prosody-plugins/mod_measure_message_count.lua
Normal file
@@ -0,0 +1,157 @@
|
||||
-- Measure the number of messages used in a meeting. Sends amplitude event.
|
||||
-- Needs to be activated under the muc component where the limit needs to be applied (main muc and breakout muc)
|
||||
-- Copyright (C) 2023-present 8x8, Inc.
|
||||
|
||||
local jid = require 'util.jid';
|
||||
local http = require 'net.http';
|
||||
local cjson_safe = require 'cjson.safe'
|
||||
|
||||
local amplitude_endpoint = module:get_option_string('amplitude_endpoint', 'https://api2.amplitude.com/2/httpapi');
|
||||
local amplitude_api_key = module:get_option_string('amplitude_api_key');
|
||||
|
||||
if not amplitude_api_key then
|
||||
module:log("warn", "No 'amplitude_api_key' option set, disabling amplitude reporting");
|
||||
return
|
||||
end
|
||||
|
||||
local muc_domain_base = module:get_option_string('muc_mapper_domain_base');
|
||||
local isBreakoutRoom = module.host == 'breakout.' .. muc_domain_base;
|
||||
|
||||
local util = module:require 'util';
|
||||
local is_healthcheck_room = util.is_healthcheck_room;
|
||||
local extract_subdomain = util.extract_subdomain;
|
||||
|
||||
module:log('info', 'Loading measure message count');
|
||||
|
||||
local shard_name = module:context(muc_domain_base):get_option_string('shard_name');
|
||||
local region_name = module:context(muc_domain_base):get_option_string('region_name');
|
||||
local release_number = module:context(muc_domain_base):get_option_string('release_number');
|
||||
local http_headers = {
|
||||
['User-Agent'] = 'Prosody ('..prosody.version..'; '..prosody.platform..')',
|
||||
['Content-Type'] = 'application/json'
|
||||
};
|
||||
|
||||
local inspect = require "inspect"
|
||||
|
||||
function table.clone(t)
|
||||
return {table.unpack(t)}
|
||||
end
|
||||
|
||||
local function event_cb(content_, code_, response_, request_)
|
||||
if code_ == 200 or code_ == 204 then
|
||||
module:log('debug', 'URL Callback: Code %s, Content %s, Request (host %s, path %s, body %s), Response: %s',
|
||||
code_, content_, request_.host, request_.path, inspect(request_.body), inspect(response_));
|
||||
else
|
||||
module:log('warn', 'URL Callback non successful: Code %s, Content %s, Request (%s), Response: %s',
|
||||
code_, content_, inspect(request_), inspect(response_));
|
||||
end
|
||||
end
|
||||
|
||||
function send_event(room)
|
||||
local user_properties = {
|
||||
shard_name = shard_name;
|
||||
region_name = region_name;
|
||||
release_number = release_number;
|
||||
};
|
||||
|
||||
local node = jid.split(room.jid);
|
||||
local subdomain, room_name = extract_subdomain(node);
|
||||
user_properties.tenant = subdomain or '/';
|
||||
user_properties.conference_name = room_name or node;
|
||||
|
||||
local event_properties = {
|
||||
messages_count = room._muc_messages_count or 0;
|
||||
polls_count = room._muc_polls_count or 0;
|
||||
};
|
||||
|
||||
if room.created_timestamp then
|
||||
event_properties.duration = (os.time() * 1000 - room.created_timestamp) / 1000;
|
||||
end
|
||||
|
||||
local event = {
|
||||
api_key = amplitude_api_key;
|
||||
events = {
|
||||
{
|
||||
user_id = room._data.meetingId;
|
||||
device_id = room._data.meetingId;
|
||||
event_type = 'conference_ended';
|
||||
event_properties = event_properties;
|
||||
user_properties = user_properties;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
local request = http.request(amplitude_endpoint, {
|
||||
headers = http_headers,
|
||||
method = "POST",
|
||||
body = cjson_safe.encode(event)
|
||||
}, event_cb);
|
||||
end
|
||||
|
||||
function on_message(event)
|
||||
local stanza = event.stanza;
|
||||
local body = stanza:get_child('body');
|
||||
|
||||
if not body then
|
||||
-- we ignore messages without body - lobby, polls ...
|
||||
return;
|
||||
end
|
||||
|
||||
local session = event.origin;
|
||||
if not session or not session.jitsi_web_query_room then
|
||||
return false;
|
||||
end
|
||||
|
||||
-- get room name with tenant and find room.
|
||||
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s/%s',
|
||||
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
|
||||
return false;
|
||||
end
|
||||
|
||||
if not room._muc_messages_count then
|
||||
room._muc_messages_count = 0;
|
||||
end
|
||||
|
||||
room._muc_messages_count = room._muc_messages_count + 1;
|
||||
end
|
||||
|
||||
-- Conference ended, send stats
|
||||
function room_destroyed(event)
|
||||
local room, session = event.room, event.origin;
|
||||
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
|
||||
if isBreakoutRoom then
|
||||
return;
|
||||
end
|
||||
send_event(room);
|
||||
end
|
||||
|
||||
function poll_created(event)
|
||||
local session = event.event.origin;
|
||||
|
||||
-- get room name with tenant and find room.
|
||||
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s/%s',
|
||||
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
|
||||
return false;
|
||||
end
|
||||
|
||||
if not room._muc_polls_count then
|
||||
room._muc_polls_count = 0;
|
||||
end
|
||||
|
||||
room._muc_polls_count = room._muc_polls_count + 1;
|
||||
end
|
||||
|
||||
module:hook('message/full', on_message); -- private messages
|
||||
module:hook('message/bare', on_message); -- room messages
|
||||
|
||||
module:hook('muc-room-destroyed', room_destroyed, -1);
|
||||
|
||||
module:hook('poll-created', poll_created);
|
||||
Reference in New Issue
Block a user