Compare commits

...

12 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
8c68f78db3 rn,responsive-ui: refactor dimensions detection
Use a dimensions detecting root component. The Dimensions module does not
measure the app's view size, but the Window, which may not be the same, for
example on iOS when PiP is used.

Also refactor the aspect ratio wrap component since it can be taken directly
from the store.

Last, remove the use of DimensionsDetector on LargeVideo and TileView since they
occupy the full-screen anyway.

Fixes PiP mode on iOS.
2020-06-02 13:30:07 +02:00
Marius Bardan
d93b219c7f lang: update RO translations 2020-06-02 10:19:06 +02:00
Marius Bardan
10cd150a07 lang: update RO translations 2020-06-02 10:19:06 +02:00
Jaya Allamsetty
a31f3c0c76 fix(config): Add missing capScreenshareBitrate to config.js 2020-05-29 14:04:30 -04:00
Simon Honegger
af39186a5f fix: typeof returns a string, so this condition was always true 2020-05-29 19:53:05 +02:00
Saúl Ibarra Corretgé
d4d1d0aa70 dev: add plugin for circular dependency detection
Example run: https://gist.github.com/saghul/e5e12edc108bdedbcbe65a3d7528235f
2020-05-29 10:37:09 +02:00
Saúl Ibarra Corretgé
3a88f4939c misc: break import cycle 2020-05-29 10:37:09 +02:00
Saúl Ibarra Corretgé
fe221fe4be deps: run npm audit fix 2020-05-29 10:37:09 +02:00
Jaya Allamsetty
1caaa47f5e chore(deps): update lib-jitsi-meet
fix(safari): Disable VAD processing on Safari - cfbb511bce
2020-05-28 17:19:07 -04:00
Hristo Terezov
a2c4d17e4d fix(record):web/mobile match disable functionality 2020-05-28 15:39:49 -05:00
Hristo Terezov
ce1de9e1e7 feat(recording): Disable buttons on active session 2020-05-28 15:39:49 -05:00
Hristo Terezov
3e7abf3da0 feat(subtitles): Disable for guests. 2020-05-28 13:43:18 -05:00
30 changed files with 801 additions and 1031 deletions

View File

@@ -1261,7 +1261,7 @@ export default {
items[key] = param[1];
}
if (typeof items.e2eekey !== undefined) {
if (typeof items.e2eekey !== 'undefined') {
APP.store.dispatch(setE2EEKey(items.e2eekey));
// Clean URL in browser history.

View File

@@ -54,6 +54,13 @@ var config = {
// Disables the auto-play behavior of *all* newly created video element.
// This is useful when the client runs on a host with limited resources.
// noAutoPlayVideo: false
// Enable / disable 500 Kbps bitrate cap on desktop tracks. When enabled,
// simulcast is turned off for the desktop share. If presenter is turned
// on while screensharing is in progress, the max bitrate is automatically
// adjusted to 2.5 Mbps. This takes a value between 0 and 1 which determines
// the probability for this to be enabled.
// capScreenshareBitrate: 1 // 0 to disable
},
// Disables ICE/UDP by filtering out local and remote UDP candidates in

View File

@@ -1,38 +1,45 @@
{
"en": "Engleză",
"af": "Afrikaans",
"az": "",
"ar": "Arabă",
"bg": "Bulgară",
"ca": "Catalană",
"cs": "Cehă",
"da": "Daneză",
"de": "Germană",
"el": "Greacă",
"enGB": "Engleză (Regatul Unit)",
"eo": "Esperanto",
"es": "Spaniolă",
"esUS": "Spaniolă (America Latină)",
"et": "Estonă",
"eu": "Bască",
"fi": "Finlandeză",
"fr": "Franceză",
"hy": "Armeniana",
"frCA": "Franceză (Canada)",
"he": "Ebraică",
"hr": "Croată",
"hu": "Maghiară",
"hy": "Armeană",
"id": "Indoneziană",
"it": "Italiană",
"ja": "Japoneză",
"ko": "Koreană",
"nb": "",
"oc": "Occitan",
"lt": "Lituaniană",
"nl": "Olandeză",
"oc": "Occitană",
"pl": "Poloneză",
"ptBR": "Portugheză (Brazilia)",
"ru": "Rusă",
"ro": "Română",
"sc": "Sardă",
"sk": "Slovacă",
"sl": "Slovenă",
"sv": "Suedeză",
"th": "Thailandeză",
"tr": "Turcă",
"uk": "Ucraineană",
"vi": "Vietnameză",
"zhCN": "Chineză (China)",
"zhTW": "Chineză (Taiwan)",
"nl": "Olandeză",
"hu": "Maghiară",
"hr": "Croată",
"frCA": "Franceză (Canadia)",
"fi": "Finlandeză",
"et": "Estoniană",
"esUS": "Spaniolă (America Latină)",
"enGB": "Engleză (Regatul Unit)",
"da": "Daneză",
"că": "Catalană"
"zhTW": "Chineză (Taiwan)"
}

View File

@@ -21,7 +21,8 @@
"bluetooth": "Bluetooth",
"headphones": "Căști",
"phone": "Telefon",
"speaker": "Difuzor"
"speaker": "Difuzor",
"none": ""
},
"audioOnly": {
"audioOnly": "Doar audio"
@@ -51,7 +52,12 @@
"popover": "Alegeți un pseudonim",
"title": "Introduceți un pseudonim pentru a conversa"
},
"title": "Apel video"
"title": "Apel video",
"you": "",
"privateNotice": "",
"noMessagesMessage": "",
"messageTo": "",
"fieldPlaceHolder": ""
},
"connectingOverlay": {
"joiningRoom": "Sunteți conectat la conversația dumneavoastră ..."
@@ -66,7 +72,11 @@
"DISCONNECTED": "Deconectat",
"DISCONNECTING": "Se deconectează",
"ERROR": "Eroare",
"RECONNECTING": "A apărut o eroare de rețea. Reconectare..."
"RECONNECTING": "A apărut o eroare de rețea. Reconectare...",
"LOW_BANDWIDTH": "",
"GOT_SESSION_ID": "",
"GET_SESSION_ID_ERROR": "",
"FETCH_SESSION_ID": ""
},
"connectionindicator": {
"address": "Adresă:",
@@ -97,7 +107,8 @@
"status": "Conexiune:",
"transport": "Mod Transport:",
"transport_plural": "Moduri Transport:",
"turn": " (turn)"
"turn": " (turn)",
"e2e_rtt": ""
},
"dateUtils": {
"earlier": "Mai devreme",
@@ -109,6 +120,8 @@
"description": "Nu s-a întâmplat nimic? Am încercat să vă deschidem conversația în {{app}} aplicația pentru desktop. Încercați din nou sau deschideți {{app}} aplicația web.",
"descriptionWithoutWeb": "",
"downloadApp": "Descărcați aplicația",
"ifDoNotHaveApp": "Dacă nu aveti încă aplicația atunci:",
"ifHaveApp": "Dacă aveti deja aplicația:",
"launchWebButton": "Deschideți în browser",
"openApp": "Continuați spre aplicație",
"title": "Deschidere apel video în {{app}}...",
@@ -256,13 +269,24 @@
"WaitForHostMsgWOk": "Conferința {{room}} nu a început. Daca sunteți moderatorul, apăsați butonul OK pentru autentificare. Dacă nu, așteptați ca moderatorul să înceapă conferința.",
"WaitingForHost": "Așteptare moderator conferință ...",
"Yes": "Da",
"yourEntireScreen": "Întregul ecran"
"yourEntireScreen": "Întregul ecran",
"sendPrivateMessageTitle": "",
"sendPrivateMessageOk": "",
"sendPrivateMessageCancel": "",
"sendPrivateMessage": "",
"screenSharingAudio": "",
"muteEveryoneStartMuted": "",
"muteEveryoneSelf": "",
"muteEveryoneTitle": "",
"muteEveryoneDialog": "",
"muteEveryoneElseTitle": "",
"muteEveryoneElseDialog": ""
},
"dialOut": {
"statusMessage": "Este {{status}}"
},
"feedback": {
"average": "Mediu",
"average": "Medie",
"bad": "Rău",
"detailsLabel": "Spuneți-ne mai multe despre experiența dumneavoastră.",
"good": "Bine",
@@ -335,7 +359,8 @@
"toggleFilmstrip": "Afișați sau ascundeți imagini video",
"toggleScreensharing": "Comutați între cameră și partajare ecran",
"toggleShortcuts": "Arătați sau ascundeți comenzi rapide tastatură",
"videoMute": "Porniți sau opriți camera"
"videoMute": "Porniți sau opriți camera",
"videoQuality": ""
},
"liveStreaming": {
"busy": "Lucrăm la eliberarea resurselor de transmitere. Vă rugam să încercați din nou în câteva minute.",
@@ -363,7 +388,11 @@
"signOut": "Deconectare",
"start": "Începeți o transmitere live",
"streamIdHelp": "Ce înseamnă acest lucru?",
"unavailableTitle": "Transmitere live indisponibilă"
"unavailableTitle": "Transmitere live indisponibilă",
"onBy": "",
"offBy": "",
"googlePrivacyPolicy": "Politica de confidențialitate Google",
"youtubeTerms": "Termeni și condiții Youtube"
},
"localRecording": {
"clientState": {
@@ -426,9 +455,10 @@
"unmute": "",
"newDeviceCameraTitle": "A fost detectată o cameră noua",
"newDeviceAudioTitle": "A fost detectat un dispozitiv audio nou",
"newDeviceAction": "Utilizați"
"newDeviceAction": "Utilizați",
"suboptimalBrowserWarning": "Folosind acest browser nu veți beneficia de cea mai bună experiență pentru aceste apeluri video. În timp ce lucrăm la asta, vă recomandăm să folosiți unul din <a href='{{recommendedBrowserPageLink}}' target='_blank'>browser-ele suportate</a>."
},
"passwordSetRemotely": "Setat de un alt membru",
"passwordSetRemotely": "Setată de un alt membru",
"passwordDigitsOnly": "Până la {{number}} cifre",
"poweredby": "cu sprijinul",
"presenceStatus": {
@@ -474,7 +504,9 @@
"signIn": "Conectare",
"signOut": "Deconectare",
"unavailable": "Oops! Serviciul {{serviceName}} este indisponibil momentan. Se lucrează la remedierea acestei probleme. Vă rugam să încercați mai tărziu.",
"unavailableTitle": "Înregistrare indisponibilă"
"unavailableTitle": "Înregistrare indisponibilă",
"onBy": "{{name}} a pornit înregistrarea",
"offBy": "{{name}} a oprit înregistrarea"
},
"sectionList": {
"pullToRefresh": "Trageți pentru a reîmprospătă"
@@ -499,8 +531,10 @@
"selectCamera": "Cameră",
"selectMic": "Microfon",
"startAudioMuted": "Toată lumea începe cu sunetul dezactivat",
"startVideoMuted": "Toată lumea începe cu sunetul dezactivat",
"title": "Setări"
"startVideoMuted": "Toată lumea începe cu fară video",
"title": "Setări",
"speakers": "Difuzoare",
"microphones": "Microfoane"
},
"settingsView": {
"alertOk": "OK",
@@ -515,7 +549,11 @@
"serverURL": "Server URL",
"startWithAudioMuted": "Începeți cu sunetul dezactivat",
"startWithVideoMuted": "Începeți cu video dezactivat",
"version": "Version"
"version": "Version",
"showAdvanced": "Arată setările avansate",
"disableP2P": "Dezactivează modul Peer-To-Peer",
"disableCallIntegration": "Dezactivează integrarea cu apelurile native",
"advanced": "Avansat"
},
"share": {
"dialInfoText": "\n\n=====\n\nVreti doar să apelați telefonic?\n\n{{defaultDialInNumber}}Faceți click pe acest link pentru a vizualiza numerele pentru apelare în acest apel video\n{{dialInfoPageUrl}}",
@@ -525,10 +563,10 @@
"speakerStats": {
"hours": "{{count}}h",
"minutes": "{{count}}m",
"name": "Name",
"name": "Nume",
"seconds": "{{count}}s",
"speakerStats": "Parametrii difuzorului",
"speakerTime": "Durată participant"
"speakerStats": "Statistici participanți",
"speakerTime": "Durată vorbire participant"
},
"startupoverlay": {
"policyText": " ",
@@ -572,9 +610,14 @@
"tileView": "Afișați/ascundeți miniatura video",
"toggleCamera": "Afișați / ascundeți camera",
"videomute": "Activați / dezactivați înregistrarea",
"videoblur": ""
"videoblur": "",
"privateMessage": "Trimite un mesaj privat",
"muteEveryone": "Oprește microfonul tuturor",
"moreOptions": "Arată mai multe opțiuni",
"help": "Ajutor",
"download": "Descarcă aplicațiile noastre"
},
"addPeople": "Adaugați persoane în apel",
"addPeople": "Adăugați persoane în apel",
"audioOnlyOff": "Dezactivați modul 'doar audio'",
"audioOnlyOn": "Activați modul 'doar audio'",
"audioRoute": "Selectați dispozitivul pentru sunet",
@@ -664,9 +707,6 @@
"lowDefinition": "Calitate redusă",
"onlyAudioAvailable": "Doar audio este disponibil",
"onlyAudioSupported": "În acest navigator este suportat doar 'mod audio'.",
"p2pEnabled": "Peer-to-Peer activat",
"p2pVideoQualityDescription": "În modul peer-to-peer pentru calitatea apelurilor primite puteți alege doar între calitate superioară sau 'mod audio'. Alte setări nu pot fi activate până când modul peer-to-peer nu este dezactivat.",
"recHighDefinitionOnly": "Este de preferat o calitate superioară.",
"sd": "SD",
"standardDefinition": "Calitate standard"
},
@@ -698,7 +738,7 @@
"go": "ÎNCEPEȚI",
"join": "ACCESARE",
"info": "Informații",
"privacy": "Securitate",
"privacy": "Confidențialitate",
"recentList": "Recent",
"recentListDelete": "Ștergeți",
"recentListEmpty": "Lista dumneavoastră recentă este momentan goală. Discutați cu echipa dumneavoastră și veți găsi toate conversațiile aici.",
@@ -707,13 +747,16 @@
"roomnameHint": "Introduceți numele sau adresa web a ședinței la care doriți să vă conectați. Puteți asocia un nume, dar transmiteți și celorlalți participanți acest nume.",
"sendFeedback": "Lăsați-ne feedback",
"terms": "Termeni",
"title": "Video-conferință securizata, cu opțiuni multiple și complet gratuită "
"title": "Video-conferință securizată, cu multiple funcționalități și complet gratuită"
},
"lonelyMeetingExperience": {
"button": "Invită alte persoane",
"youAreAlone": "Ești singura persoană din acest apel"
"documentSharing": {
"title": ""
},
"helpView": {
"header": "Centru de ajutor"
}
"defaultNickname": "",
"chromeExtensionBanner": {
"dontShowAgain": "",
"buttonText": "",
"installExtensionText": ""
},
"raisedHand": "Ar dori să vorbească"
}

View File

@@ -216,6 +216,7 @@
"kickParticipantTitle": "Kick this participant?",
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
"liveStreaming": "Live Streaming",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Not possible while recording is active",
"liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
"liveStreamingDisabledTooltip": "Start live stream disabled.",
"lockMessage": "Failed to lock the conference.",
@@ -249,6 +250,7 @@
"popupError": "Your browser is blocking pop-up windows from this site. Please enable pop-ups in your browser's security settings and try again.",
"popupErrorTitle": "Pop-up blocked",
"recording": "Recording",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Not possible while a live stream is active",
"recordingDisabledForGuestTooltip": "Guests can't start recordings.",
"recordingDisabledTooltip": "Start recording disabled.",
"rejoinNow": "Rejoin now",

719
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,7 @@
"js-md5": "0.6.1",
"js-utils": "github:jitsi/js-utils#cf11996bd866fdb47326c59a5d3bc24be17282d4",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#4e5397685287d8d87f29591546f7c902eac22bf0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#cfbb511bcec24df44879a1fc271498e2d80e8477",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.13",
"moment": "2.19.4",
@@ -110,6 +110,7 @@
"@babel/runtime": "7.5.5",
"babel-eslint": "10.0.1",
"babel-loader": "8.0.4",
"circular-dependency-plugin": "5.2.0",
"clean-css": "3.4.25",
"css-loader": "0.28.7",
"eslint": "5.6.1",
@@ -128,7 +129,7 @@
"string-replace-loader": "2.1.1",
"style-loader": "0.19.0",
"unorm": "1.6.0",
"webpack": "4.27.1",
"webpack": "4.43.0",
"webpack-bundle-analyzer": "3.4.1",
"webpack-cli": "3.3.11",
"webpack-dev-server": "3.11.0"

View File

@@ -9,7 +9,7 @@ import { DialogContainer } from '../../base/dialog';
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
import '../../base/jwt';
import { Platform } from '../../base/react';
import '../../base/responsive-ui';
import { DimensionsDetector, clientResized } from '../../base/responsive-ui';
import { updateSettings } from '../../base/settings';
import '../../google-api';
import '../../mobile/audio-mode';
@@ -78,6 +78,9 @@ export class App extends AbstractApp {
// This will effectively kill the app. In accord with the Web, do not
// kill the app.
this._maybeDisableExceptionsManager();
// Bind event handler so it is only bound once per instance.
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
}
/**
@@ -107,6 +110,21 @@ export class App extends AbstractApp {
});
}
/**
* Overrides the parent method to inject {@link DimensionsDetector} as
* the top most component.
*
* @override
*/
_createMainElement(component, props) {
return (
<DimensionsDetector
onDimensionsChanged = { this._onDimensionsChanged }>
{ super._createMainElement(component, props) }
</DimensionsDetector>
);
}
/**
* Attempts to disable the use of React Native
* {@link ExceptionsManager#handleException} on platforms and in
@@ -144,6 +162,22 @@ export class App extends AbstractApp {
}
}
_onDimensionsChanged: (width: number, height: number) => void;
/**
* Updates the known available size for the app to occupy.
*
* @param {number} width - The component's current width.
* @param {number} height - The component's current height.
* @private
* @returns {void}
*/
_onDimensionsChanged(width: number, height: number) {
const { dispatch } = this.state.store;
dispatch(clientResized(width, height));
}
/**
* Renders the platform specific dialog container.
*

View File

@@ -1,8 +1,8 @@
import { combineReducers } from 'redux';
import { CONFERENCE_FAILED, CONFERENCE_LEFT } from '../conference';
import { CONFERENCE_FAILED, CONFERENCE_LEFT } from '../conference/actionTypes';
import { ReducerRegistry } from '../redux';
import { TRACK_REMOVED } from '../tracks';
import { TRACK_REMOVED } from '../tracks/actionTypes';
import {
SET_AUDIO_AVAILABLE,

View File

@@ -1,70 +0,0 @@
// @flow
import React, { Component } from 'react';
import { connect } from '../../redux';
import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from '../constants';
/**
* The type of the React {@code Component} props of {@link AspectRatioAware}.
*/
type Props = {
aspectRatio: ASPECT_RATIO_NARROW | ASPECT_RATIO_WIDE
};
/**
* Determines whether a specific React {@code Component} decorated into an
* {@link AspectRatioAware} has {@link ASPECT_RATIO_NARROW} as the value of its
* {@code aspectRatio} React prop.
*
* @param {AspectRatioAware} component - An {@link AspectRatioAware} which may
* have an {@code aspectRatio} React prop.
* @returns {boolean}
*/
export function isNarrowAspectRatio(component: React$Component<*>) {
return component.props.aspectRatio === ASPECT_RATIO_NARROW;
}
/**
* Decorates a specific React {@code Component} class into an
* {@link AspectRatioAware} which provides the React prop {@code aspectRatio}
* updated on each redux state change.
*
* @param {Class<React$Component>} WrappedComponent - A React {@code Component}
* class to be wrapped.
* @returns {AspectRatioAwareWrapper}
*/
export function makeAspectRatioAware(
WrappedComponent: Class<React$Component<*>>
): Class<React$Component<*>> {
/**
* Renders {@code WrappedComponent} with the React prop {@code aspectRatio}.
*/
class AspectRatioAware extends Component<Props> {
/**
* Implement's React render method to wrap the nested component.
*
* @returns {React$Element}
*/
render(): React$Element<*> {
return <WrappedComponent { ...this.props } />;
}
}
return connect(_mapStateToProps)(AspectRatioAware);
}
/**
* Maps (parts of) the redux state to {@link AspectRatioAware} props.
*
* @param {Object} state - The whole redux state.
* @private
* @returns {{
* aspectRatio: Symbol
* }}
*/
function _mapStateToProps(state) {
return {
aspectRatio: state['features/base/responsive-ui'].aspectRatio
};
}

View File

@@ -1,13 +1,9 @@
// @flow
import React, { PureComponent } from 'react';
import { View } from 'react-native';
import { StyleSheet, View } from 'react-native';
import styles from './styles';
/**
* AspectRatioDetector component's property types.
*/
type Props = {
/**
@@ -64,7 +60,7 @@ export default class DimensionsDetector extends PureComponent<Props> {
return (
<View
onLayout = { this._onLayout }
style = { styles.dimensionsDetector } >
style = { StyleSheet.absoluteFillObject } >
{ this.props.children }
</View>
);

View File

@@ -1,2 +1 @@
export * from './AspectRatioAware';
export { default as DimensionsDetector } from './DimensionsDetector';

View File

@@ -1,14 +0,0 @@
import { createStyleSheet } from '../../styles';
/**
* The styles of the feature base/responsive-ui.
*/
export default createStyleSheet({
/**
* The style of {@link DimensionsDetector} used on react-native.
*/
dimensionsDetector: {
alignSelf: 'stretch',
flex: 1
}
});

View File

@@ -1,16 +1,10 @@
// @flow
import { Dimensions } from 'react-native';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
import { MiddlewareRegistry } from '../../base/redux';
import { CLIENT_RESIZED } from './actionTypes';
import { setAspectRatio, setReducedUI } from './actions';
/**
* Dimensions change handler.
*/
let handler;
/**
* Middleware that handles widnow dimension changes and updates the aspect ratio and
@@ -19,65 +13,19 @@ let handler;
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
MiddlewareRegistry.register(({ dispatch }) => next => action => {
const result = next(action);
switch (action.type) {
case APP_WILL_UNMOUNT: {
_appWillUnmount();
case CLIENT_RESIZED: {
const { clientWidth: width, clientHeight: height } = action;
dispatch(setAspectRatio(width, height));
dispatch(setReducedUI(width, height));
break;
}
case APP_WILL_MOUNT:
_appWillMount(store);
break;
}
return result;
});
/**
* Notifies this feature that the action {@link APP_WILL_MOUNT} is being
* dispatched within a specific redux {@code store}.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @private
* @returns {void}
*/
function _appWillMount(store) {
handler = dim => {
_onDimensionsChange(dim, store);
};
Dimensions.addEventListener('change', handler);
}
/**
* Notifies this feature that the action {@link APP_WILL_UNMOUNT} is being
* dispatched within a specific redux {@code store}.
*
* @private
* @returns {void}
*/
function _appWillUnmount() {
Dimensions.removeEventListener('change', handler);
handler = undefined;
}
/**
* Handles window dimension changes.
*
* @param {Object} dimensions - The new dimensions.
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _onDimensionsChange(dimensions, store) {
const { width, height } = dimensions.window;
const { dispatch } = store;
dispatch(setAspectRatio(width, height));
dispatch(setReducedUI(width, height));
}

View File

@@ -23,7 +23,6 @@ const DEFAULT_STATE = {
ReducerRegistry.register('features/base/responsive-ui', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case CLIENT_RESIZED: {
return {
...state,
clientWidth: action.clientWidth,

View File

@@ -8,10 +8,7 @@ import { appNavigate } from '../../../app';
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
import { connect } from '../../../base/redux';
import {
isNarrowAspectRatio,
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { TestConnectionInfo } from '../../../base/testing';
import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
import { Chat } from '../../../chat';
@@ -45,10 +42,13 @@ import styles, { NAVBAR_GRADIENT_COLORS } from './styles';
*/
type Props = AbstractProps & {
/**
* Application's aspect ratio.
*/
_aspectRatio: Symbol,
/**
* Wherther the calendar feature is enabled or not.
*
* @private
*/
_calendarEnabled: boolean,
@@ -57,15 +57,11 @@ type Props = AbstractProps & {
* conference which includes establishing the XMPP connection and then
* joining the room. If truthy, then an activity/loading indicator will be
* rendered.
*
* @private
*/
_connecting: boolean,
/**
* Set to {@code true} when the filmstrip is currently visible.
*
* @private
*/
_filmstripVisible: boolean,
@@ -76,34 +72,17 @@ type Props = AbstractProps & {
/**
* Whether Picture-in-Picture is enabled.
*
* @private
*/
_pictureInPictureEnabled: boolean,
/**
* The indicator which determines whether the UI is reduced (to accommodate
* smaller display areas).
*
* @private
*/
_reducedUI: boolean,
/**
* The handler which dispatches the (redux) action {@link setToolboxVisible}
* to show/hide the {@link Toolbox}.
*
* @param {boolean} visible - {@code true} to show the {@code Toolbox} or
* {@code false} to hide it.
* @private
* @returns {void}
*/
_setToolboxVisible: Function,
/**
* The indicator which determines whether the Toolbox is visible.
*
* @private
*/
_toolboxVisible: boolean,
@@ -249,6 +228,7 @@ class Conference extends AbstractConference<Props, *> {
*/
_renderContent() {
const {
_aspectRatio,
_connecting,
_filmstripVisible,
_largeVideoParticipantId,
@@ -257,7 +237,8 @@ class Conference extends AbstractConference<Props, *> {
_toolboxVisible
} = this.props;
const showGradient = _toolboxVisible;
const applyGradientStretching = _filmstripVisible && isNarrowAspectRatio(this) && !_shouldDisplayTileView;
const applyGradientStretching
= _filmstripVisible && _aspectRatio === ASPECT_RATIO_NARROW && !_shouldDisplayTileView;
if (_reducedUI) {
return this._renderContentForReducedUi();
@@ -393,7 +374,9 @@ class Conference extends AbstractConference<Props, *> {
// flex layout. The only option that seemed to limit the notification's
// size was explicit 'width' value which is not better than the margin
// added here.
if (this.props._filmstripVisible && !isNarrowAspectRatio(this)) {
const { _aspectRatio, _filmstripVisible } = this.props;
if (_filmstripVisible && _aspectRatio !== ASPECT_RATIO_NARROW) {
notificationsStyle.marginRight = FILMSTRIP_SIZE;
}
@@ -433,7 +416,7 @@ function _mapStateToProps(state) {
joining,
leaving
} = state['features/base/conference'];
const { reducedUI } = state['features/base/responsive-ui'];
const { aspectRatio, reducedUI } = state['features/base/responsive-ui'];
// XXX There is a window of time between the successful establishment of the
// XMPP connection and the subsequent commencement of joining the MUC during
@@ -449,61 +432,15 @@ function _mapStateToProps(state) {
return {
...abstractMapStateToProps(state),
/**
* Wherther the calendar feature is enabled or not.
*
* @private
* @type {boolean}
*/
_aspectRatio: aspectRatio,
_calendarEnabled: isCalendarEnabled(state),
/**
* The indicator which determines that we are still connecting to the
* conference which includes establishing the XMPP connection and then
* joining the room. If truthy, then an activity/loading indicator will
* be rendered.
*
* @private
* @type {boolean}
*/
_connecting: Boolean(connecting_),
/**
* Is {@code true} when the filmstrip is currently visible.
*/
_filmstripVisible: isFilmstripVisible(state),
/**
* The ID of the participant currently on stage.
*/
_largeVideoParticipantId: state['features/large-video'].participantId,
/**
* Whether Picture-in-Picture is enabled.
*
* @private
* @type {boolean}
*/
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
/**
* The indicator which determines whether the UI is reduced (to
* accommodate smaller display areas).
*
* @private
* @type {boolean}
*/
_reducedUI: reducedUI,
/**
* The indicator which determines whether the Toolbox is visible.
*
* @private
* @type {boolean}
*/
_toolboxVisible: isToolboxVisible(state)
};
}
export default connect(_mapStateToProps)(makeAspectRatioAware(Conference));
export default connect(_mapStateToProps)(Conference);

View File

@@ -5,10 +5,7 @@ import { TouchableOpacity, View } from 'react-native';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { connect } from '../../../base/redux';
import {
isNarrowAspectRatio,
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { ASPECT_RATIO_WIDE } from '../../../base/responsive-ui/constants';
import {
RecordingExpandedLabel
} from '../../../recording';
@@ -29,14 +26,19 @@ import styles from './styles';
type Props = AbstractLabelsProps & {
/**
* Function to translate i18n labels.
* Application's aspect ratio.
*/
t: Function,
_aspectRatio: Symbol,
/**
* True if the labels should be visible, false otherwise.
*/
_visible: boolean
_visible: boolean,
/**
* Function to translate i18n labels.
*/
t: Function
};
type State = {
@@ -149,12 +151,13 @@ class Labels extends AbstractLabels<Props, State> {
* @inheritdoc
*/
render() {
if (!this.props._visible) {
const { _aspectRatio, _filmstripVisible, _visible } = this.props;
if (!_visible) {
return null;
}
const wide = !isNarrowAspectRatio(this);
const { _filmstripVisible } = this.props;
const wide = _aspectRatio === ASPECT_RATIO_WIDE;
return (
<View
@@ -354,8 +357,9 @@ class Labels extends AbstractLabels<Props, State> {
function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
_visible: !shouldDisplayNotifications(state)
};
}
export default connect(_mapStateToProps)(makeAspectRatioAware(Labels));
export default connect(_mapStateToProps)(Labels);

View File

@@ -41,8 +41,7 @@ class Labels extends AbstractLabels<Props, State> {
*/
static getDerivedStateFromProps(props: Props, prevState: State) {
return {
filmstripBecomingVisible: !prevState.filmstripBecomingVisible
&& props._filmstripVisible
filmstripBecomingVisible: !prevState.filmstripBecomingVisible && props._filmstripVisible
};
}

View File

@@ -5,10 +5,7 @@ import { ScrollView } from 'react-native';
import { Container, Platform } from '../../../base/react';
import { connect } from '../../../base/redux';
import {
isNarrowAspectRatio,
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { isFilmstripVisible } from '../../functions';
import LocalThumbnail from './LocalThumbnail';
@@ -20,24 +17,23 @@ import styles from './styles';
*/
type Props = {
/**
* Application's aspect ratio.
*/
_aspectRatio: Symbol,
/**
* The indicator which determines whether the filmstrip is enabled.
*
* @private
*/
_enabled: boolean,
/**
* The participants in the conference.
*
* @private
*/
_participants: Array<any>,
/**
* The indicator which determines whether the filmstrip is visible.
*
* @private
*/
_visible: boolean
};
@@ -90,40 +86,36 @@ class Filmstrip extends Component<Props> {
* @returns {ReactElement}
*/
render() {
if (!this.props._enabled) {
const { _aspectRatio, _enabled, _participants, _visible } = this.props;
if (!_enabled) {
return null;
}
const isNarrowAspectRatio_ = isNarrowAspectRatio(this);
const filmstripStyle
= isNarrowAspectRatio_
? styles.filmstripNarrow
: styles.filmstripWide;
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
const filmstripStyle = isNarrowAspectRatio ? styles.filmstripNarrow : styles.filmstripWide;
return (
<Container
style = { filmstripStyle }
visible = { this.props._visible }>
visible = { _visible }>
{
this._separateLocalThumbnail
&& !isNarrowAspectRatio_
&& !isNarrowAspectRatio
&& <LocalThumbnail />
}
<ScrollView
horizontal = { isNarrowAspectRatio_ }
horizontal = { isNarrowAspectRatio }
showsHorizontalScrollIndicator = { false }
showsVerticalScrollIndicator = { false }
style = { styles.scrollView } >
{
!this._separateLocalThumbnail
&& !isNarrowAspectRatio_
!this._separateLocalThumbnail && !isNarrowAspectRatio
&& <LocalThumbnail />
}
{
this._sort(
this.props._participants,
isNarrowAspectRatio_)
this._sort(_participants, isNarrowAspectRatio)
.map(p => (
<Thumbnail
key = { p.id }
@@ -131,14 +123,12 @@ class Filmstrip extends Component<Props> {
}
{
!this._separateLocalThumbnail
&& isNarrowAspectRatio_
!this._separateLocalThumbnail && isNarrowAspectRatio
&& <LocalThumbnail />
}
</ScrollView>
{
this._separateLocalThumbnail
&& isNarrowAspectRatio_
this._separateLocalThumbnail && isNarrowAspectRatio
&& <LocalThumbnail />
}
</Container>
@@ -150,13 +140,13 @@ class Filmstrip extends Component<Props> {
*
* @param {Participant[]} participants - The array of {@code Participant}s
* to sort in display order.
* @param {boolean} isNarrowAspectRatio_ - Indicates if the aspect ratio is
* @param {boolean} isNarrowAspectRatio - Indicates if the aspect ratio is
* wide or narrow.
* @private
* @returns {Participant[]} A new array containing the elements of the
* specified {@code participants} array sorted in display order.
*/
_sort(participants, isNarrowAspectRatio_) {
_sort(participants, isNarrowAspectRatio) {
// XXX Array.prototype.sort() is not appropriate because (1) it operates
// in place and (2) it is not necessarily stable.
@@ -164,7 +154,7 @@ class Filmstrip extends Component<Props> {
...participants
];
if (isNarrowAspectRatio_) {
if (isNarrowAspectRatio) {
// When the narrow aspect ratio is used, we want to have the remote
// participants from right to left with the newest added/joined to
// the leftmost side. The local participant is the leftmost item.
@@ -180,42 +170,18 @@ class Filmstrip extends Component<Props> {
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* _participants: Participant[],
* _visible: boolean
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
const participants = state['features/base/participants'];
const { enabled } = state['features/filmstrip'];
return {
/**
* The indicator which determines whether the filmstrip is enabled.
*
* @private
* @type {boolean}
*/
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
_enabled: enabled,
/**
* The remote participants in the conference.
*
* @private
* @type {Participant[]}
*/
_participants: participants.filter(p => !p.local),
/**
* The indicator which determines whether the filmstrip is visible. The
* mobile/react-native Filmstrip is visible when there are at least 2
* participants in the conference (including the local one).
*
* @private
* @type {boolean}
*/
_visible: isFilmstripVisible(state)
};
}
export default connect(_mapStateToProps)(makeAspectRatioAware(Filmstrip));
export default connect(_mapStateToProps)(Filmstrip);

View File

@@ -13,11 +13,7 @@ import {
setMaxReceiverVideoQuality
} from '../../../base/conference';
import { connect } from '../../../base/redux';
import {
DimensionsDetector,
isNarrowAspectRatio,
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import Thumbnail from './Thumbnail';
import styles from './styles';
@@ -27,11 +23,26 @@ import styles from './styles';
*/
type Props = {
/**
* Application's aspect ratio.
*/
_aspectRatio: Symbol,
/**
* Application's viewport height.
*/
_height: number,
/**
* The participants in the conference.
*/
_participants: Array<Object>,
/**
* Application's viewport height.
*/
_width: number,
/**
* Invoked to update the receiver video quality.
*/
@@ -43,22 +54,6 @@ type Props = {
onClick: Function
};
/**
* The type of the React {@link Component} state of {@link TileView}.
*/
type State = {
/**
* The available width for {@link TileView} to occupy.
*/
height: number,
/**
* The available height for {@link TileView} to occupy.
*/
width: number
};
/**
* The margin for each side of the tile view. Taken away from the available
* height and width for the tile container to display in.
@@ -82,25 +77,7 @@ const TILE_ASPECT_RATIO = 1;
*
* @extends Component
*/
class TileView extends Component<Props, State> {
state = {
height: 0,
width: 0
};
/**
* Initializes a new {@code TileView} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
super(props);
// Bind event handler so it is only bound once per instance.
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
}
class TileView extends Component<Props> {
/**
* Implements React's {@link Component#componentDidMount}.
*
@@ -126,32 +103,27 @@ class TileView extends Component<Props, State> {
* @returns {ReactElement}
*/
render() {
const { onClick } = this.props;
const { height, width } = this.state;
const rowElements = this._groupIntoRows(
this._renderThumbnails(), this._getColumnCount());
const { _height, _width, onClick } = this.props;
const rowElements = this._groupIntoRows(this._renderThumbnails(), this._getColumnCount());
return (
<DimensionsDetector
onDimensionsChanged = { this._onDimensionsChanged }>
<ScrollView
style = {{
...styles.tileView,
height,
width
}}>
<TouchableWithoutFeedback onPress = { onClick }>
<View
style = {{
...styles.tileViewRows,
minHeight: height,
minWidth: width
}}>
{ rowElements }
</View>
</TouchableWithoutFeedback>
</ScrollView>
</DimensionsDetector>
<ScrollView
style = {{
...styles.tileView,
height: _height,
width: _width
}}>
<TouchableWithoutFeedback onPress = { onClick }>
<View
style = {{
...styles.tileViewRows,
minHeight: _height,
minWidth: _width
}}>
{ rowElements }
</View>
</TouchableWithoutFeedback>
</ScrollView>
);
}
@@ -167,7 +139,7 @@ class TileView extends Component<Props, State> {
// For narrow view, tiles should stack on top of each other for a lonely
// call and a 1:1 call. Otherwise tiles should be grouped into rows of
// two.
if (isNarrowAspectRatio(this)) {
if (this.props._aspectRatio === ASPECT_RATIO_NARROW) {
return participantCount >= 3 ? 2 : 1;
}
@@ -209,19 +181,17 @@ class TileView extends Component<Props, State> {
* @returns {Object}
*/
_getTileDimensions() {
const { _participants } = this.props;
const { height, width } = this.state;
const { _height, _participants, _width } = this.props;
const columns = this._getColumnCount();
const participantCount = _participants.length;
const heightToUse = height - (MARGIN * 2);
const widthToUse = width - (MARGIN * 2);
const heightToUse = _height - (MARGIN * 2);
const widthToUse = _width - (MARGIN * 2);
let tileWidth;
// If there is going to be at least two rows, ensure that at least two
// rows display fully on screen.
if (participantCount / columns > 1) {
tileWidth
= Math.min(widthToUse / columns, heightToUse / 2);
tileWidth = Math.min(widthToUse / columns, heightToUse / 2);
} else {
tileWidth = Math.min(widthToUse / columns, heightToUse);
}
@@ -247,8 +217,7 @@ class TileView extends Component<Props, State> {
for (let i = 0; i < thumbnails.length; i++) {
if (i % rowLength === 0) {
const thumbnailsInRow
= thumbnails.slice(i, i + rowLength);
const thumbnailsInRow = thumbnails.slice(i, i + rowLength);
rowElements.push(
<View
@@ -263,23 +232,6 @@ class TileView extends Component<Props, State> {
return rowElements;
}
_onDimensionsChanged: (width: number, height: number) => void;
/**
* Updates the known available state for {@link TileView} to occupy.
*
* @param {number} width - The component's current width.
* @param {number} height - The component's current height.
* @private
* @returns {void}
*/
_onDimensionsChanged(width: number, height: number) {
this.setState({
height,
width
});
}
/**
* Creates React Elements to display each participant in a thumbnail. Each
* tile will be.
@@ -326,14 +278,17 @@ class TileView extends Component<Props, State> {
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* _participants: Participant[]
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
const responsiveUi = state['features/base/responsive-ui'];
return {
_participants: state['features/base/participants']
_aspectRatio: responsiveUi.aspectRatio,
_height: responsiveUi.clientHeight,
_participants: state['features/base/participants'],
_width: responsiveUi.clientWidth
};
}
export default connect(_mapStateToProps)(makeAspectRatioAware(TileView));
export default connect(_mapStateToProps)(TileView);

View File

@@ -1,11 +1,10 @@
// @flow
import React, { Component } from 'react';
import React, { PureComponent } from 'react';
import { ColorSchemeRegistry } from '../../base/color-scheme';
import { ParticipantView } from '../../base/participants';
import { connect } from '../../base/redux';
import { DimensionsDetector } from '../../base/responsive-ui';
import { StyleType } from '../../base/styles';
import { AVATAR_SIZE } from './styles';
@@ -15,6 +14,11 @@ import { AVATAR_SIZE } from './styles';
*/
type Props = {
/**
* Application's viewport height.
*/
_height: number,
/**
* The ID of the participant (to be) depicted by LargeVideo.
*
@@ -27,6 +31,11 @@ type Props = {
*/
_styles: StyleType,
/**
* Application's viewport height.
*/
_width: number,
/**
* Callback to invoke when the {@code LargeVideo} is clicked/pressed.
*/
@@ -62,50 +71,33 @@ const DEFAULT_STATE = {
*
* @extends Component
*/
class LargeVideo extends Component<Props, State> {
class LargeVideo extends PureComponent<Props, State> {
state = {
...DEFAULT_STATE
};
/** Initializes a new {@code LargeVideo} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
}
_onDimensionsChanged: (width: number, height: number) => void;
/**
* Handle this component's dimension changes. In case we deem it's too
* Handles dimension changes. In case we deem it's too
* small, the connectivity indicator won't be rendered and the avatar
* will occupy the entirety of the available screen state.
*
* @param {number} width - The component's current width.
* @param {number} height - The component's current height.
* @private
* @returns {void}
* @inheritdoc
*/
_onDimensionsChanged(width: number, height: number) {
static getDerivedStateFromProps(props: Props) {
const { _height, _width } = props;
// Get the size, rounded to the nearest even number.
const size = 2 * Math.round(Math.min(height, width) / 2);
let nextState;
const size = 2 * Math.round(Math.min(_height, _width) / 2);
if (size < AVATAR_SIZE * 1.5) {
nextState = {
return {
avatarSize: size - 15, // Leave some margin.
useConnectivityInfoLabel: false
};
} else {
nextState = DEFAULT_STATE;
}
this.setState(nextState);
return DEFAULT_STATE;
}
/**
@@ -126,18 +118,15 @@ class LargeVideo extends Component<Props, State> {
} = this.props;
return (
<DimensionsDetector
onDimensionsChanged = { this._onDimensionsChanged }>
<ParticipantView
avatarSize = { avatarSize }
onPress = { onClick }
participantId = { _participantId }
style = { _styles.largeVideo }
testHintId = 'org.jitsi.meet.LargeVideo'
useConnectivityInfoLabel = { useConnectivityInfoLabel }
zOrder = { 0 }
zoomEnabled = { true } />
</DimensionsDetector>
<ParticipantView
avatarSize = { avatarSize }
onPress = { onClick }
participantId = { _participantId }
style = { _styles.largeVideo }
testHintId = 'org.jitsi.meet.LargeVideo'
useConnectivityInfoLabel = { useConnectivityInfoLabel }
zOrder = { 0 }
zoomEnabled = { true } />
);
}
}
@@ -147,15 +136,16 @@ class LargeVideo extends Component<Props, State> {
*
* @param {Object} state - Redux state.
* @private
* @returns {{
* _participantId: string,
* _styles: StyleType
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
const { clientHeight: height, clientWidth: width } = state['features/base/responsive-ui'];
return {
_height: height,
_participantId: state['features/large-video'].participantId,
_styles: ColorSchemeRegistry.get(state, 'LargeVideo')
_styles: ColorSchemeRegistry.get(state, 'LargeVideo'),
_width: width
};
}

View File

@@ -1,6 +1,7 @@
// @flow
import { openDialog } from '../../../base/dialog';
import { IconLiveStreaming } from '../../../base/icons';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { getLocalParticipant } from '../../../base/participants';
import {
@@ -25,6 +26,16 @@ export type Props = AbstractButtonProps & {
*/
_isLiveStreamRunning: boolean,
/**
* True if the button needs to be disabled.
*/
_disabled: Boolean,
/**
* The tooltip to display when hovering over the button.
*/
_tooltip: ?String,
/**
* The redux {@code dispatch} function.
*/
@@ -39,12 +50,22 @@ export type Props = AbstractButtonProps & {
/**
* An abstract class of a button for starting and stopping live streaming.
*/
export default class AbstractLiveStreamButton<P: Props>
extends AbstractButton<P, *> {
export default class AbstractLiveStreamButton<P: Props> extends AbstractButton<P, *> {
accessibilityLabel = 'dialog.accessibilityLabel.liveStreaming';
icon = IconLiveStreaming;
label = 'dialog.startLiveStreaming';
toggledLabel = 'dialog.stopLiveStreaming';
/**
* Returns the tooltip that should be displayed when the button is disabled.
*
* @private
* @returns {string}
*/
_getTooltip() {
return this.props._tooltip || '';
}
/**
* Handles clicking / pressing the button.
*
@@ -60,6 +81,16 @@ export default class AbstractLiveStreamButton<P: Props>
));
}
/**
* Returns a boolean value indicating if this button is disabled or not.
*
* @protected
* @returns {boolean}
*/
_isDisabled() {
return this.props._disabled;
}
/**
* Indicates whether this button is in toggled state or not.
*
@@ -80,6 +111,7 @@ export default class AbstractLiveStreamButton<P: Props>
* @param {Props} ownProps - The own props of the Component.
* @private
* @returns {{
* _disabled: boolean,
* _isLiveStreamRunning: boolean,
* visible: boolean
* }}
@@ -87,9 +119,10 @@ export default class AbstractLiveStreamButton<P: Props>
export function _mapStateToProps(state: Object, ownProps: Props) {
let { visible } = ownProps;
// a button can be disabled/enabled only if enableFeaturesBasedOnToken
// is on
let disabledByFeatures;
// A button can be disabled/enabled only if enableFeaturesBasedOnToken
// is on or if the recording is running.
let _disabled;
let _tooltip = '';
if (typeof visible === 'undefined') {
// If the containing component provides the visible prop, that is one
@@ -105,14 +138,33 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
if (enableFeaturesBasedOnToken) {
visible = visible && String(features.livestreaming) === 'true';
disabledByFeatures = String(features.livestreaming) === 'disabled';
_disabled = String(features.livestreaming) === 'disabled';
if (!visible && !_disabled) {
_disabled = true;
visible = true;
// button and tooltip
if (state['features/base/jwt'].isGuest) {
_tooltip = 'dialog.liveStreamingDisabledForGuestTooltip';
} else {
_tooltip = 'dialog.liveStreamingDisabledTooltip';
}
}
}
}
// disable the button if the recording is running.
if (getActiveSession(state, JitsiRecordingConstants.mode.FILE)) {
_disabled = true;
_tooltip = 'dialog.liveStreamingDisabledBecauseOfActiveRecordingTooltip';
}
return {
_disabled,
_isLiveStreamRunning: Boolean(
getActiveSession(state, JitsiRecordingConstants.mode.STREAM)),
disabledByFeatures,
_tooltip,
visible
};
}

View File

@@ -2,19 +2,8 @@
import { LIVE_STREAMING_ENABLED, getFeatureFlag } from '../../../../base/flags';
import { translate } from '../../../../base/i18n';
import { IconLiveStreaming } from '../../../../base/icons';
import { connect } from '../../../../base/redux';
import AbstractLiveStreamButton, {
_mapStateToProps as _abstractMapStateToProps,
type Props
} from '../AbstractLiveStreamButton';
/**
* An implementation of a button for starting and stopping live streaming.
*/
class LiveStreamButton extends AbstractLiveStreamButton<Props> {
icon = IconLiveStreaming;
}
import AbstractLiveStreamButton, { _mapStateToProps as _abstractMapStateToProps } from '../AbstractLiveStreamButton';
/**
* Maps (parts of) the redux state to the associated props for this component.
@@ -35,4 +24,4 @@ export function mapStateToProps(state: Object, ownProps: Object) {
};
}
export default translate(connect(mapStateToProps)(LiveStreamButton));
export default translate(connect(mapStateToProps)(AbstractLiveStreamButton));

View File

@@ -1,61 +1,14 @@
// @flow
import { translate } from '../../../../base/i18n';
import { IconLiveStreaming } from '../../../../base/icons';
import { connect } from '../../../../base/redux';
import AbstractLiveStreamButton, {
_mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
type Props
} from '../AbstractLiveStreamButton';
declare var interfaceConfig: Object;
type Props = AbstractProps & {
/**
* True if the button should be disabled, false otherwise.
*
* NOTE: On web, if the feature is not disabled on purpose, then we still
* show the button but disabled and with a tooltip rendered on it,
* explaining why it's not available.
*/
_disabled: boolean,
/**
* Tooltip for the button when it's disabled in a certain way.
*/
_liveStreamDisabledTooltipKey: ?string
}
/**
* An implementation of a button for starting and stopping live streaming.
*/
class LiveStreamButton extends AbstractLiveStreamButton<Props> {
icon = IconLiveStreaming;
/**
* Returns the tooltip that should be displayed when the button is disabled.
*
* @private
* @returns {string}
*/
_getTooltip() {
return this.props._liveStreamDisabledTooltipKey || '';
}
/**
* Helper function to be implemented by subclasses, which must return a
* boolean value indicating if this button is disabled or not.
*
* @override
* @protected
* @returns {boolean}
*/
_isDisabled() {
return this.props._disabled;
}
}
/**
* Maps (parts of) the redux state to the associated props for the
* {@code LiveStreamButton} component.
@@ -74,36 +27,14 @@ function _mapStateToProps(state: Object, ownProps: Props) {
const abstractProps = _abstractMapStateToProps(state, ownProps);
let { visible } = ownProps;
const _disabledByFeatures = abstractProps.disabledByFeatures;
let _disabled = false;
let _liveStreamDisabledTooltipKey;
if (!abstractProps.visible
&& _disabledByFeatures !== undefined && !_disabledByFeatures) {
_disabled = true;
// button and tooltip
if (state['features/base/jwt'].isGuest) {
_liveStreamDisabledTooltipKey
= 'dialog.liveStreamingDisabledForGuestTooltip';
} else {
_liveStreamDisabledTooltipKey
= 'dialog.liveStreamingDisabledTooltip';
}
}
if (typeof visible === 'undefined') {
visible = interfaceConfig.TOOLBAR_BUTTONS.includes('livestreaming')
&& (abstractProps.visible
|| Boolean(_liveStreamDisabledTooltipKey));
visible = interfaceConfig.TOOLBAR_BUTTONS.includes('livestreaming') && abstractProps.visible;
}
return {
...abstractProps,
_disabled,
_liveStreamDisabledTooltipKey,
visible
};
}
export default translate(connect(_mapStateToProps)(LiveStreamButton));
export default translate(connect(_mapStateToProps)(AbstractLiveStreamButton));

View File

@@ -5,6 +5,7 @@ import {
sendAnalytics
} from '../../../analytics';
import { openDialog } from '../../../base/dialog';
import { IconToggleRecording } from '../../../base/icons';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import {
getLocalParticipant,
@@ -24,11 +25,21 @@ import { StartRecordingDialog, StopRecordingDialog } from './_';
*/
export type Props = AbstractButtonProps & {
/**
* True if the button needs to be disabled.
*/
_disabled: Boolean,
/**
* True if there is a running active recording, false otherwise.
*/
_isRecordingRunning: boolean,
/**
* The tooltip to display when hovering over the button.
*/
_tooltip: ?String,
/**
* The redux {@code dispatch} function.
*/
@@ -43,12 +54,22 @@ export type Props = AbstractButtonProps & {
/**
* An abstract implementation of a button for starting and stopping recording.
*/
export default class AbstractRecordButton<P: Props>
extends AbstractButton<P, *> {
export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.recording';
icon = IconToggleRecording;
label = 'dialog.startRecording';
toggledLabel = 'dialog.stopRecording';
/**
* Returns the tooltip that should be displayed when the button is disabled.
*
* @private
* @returns {string}
*/
_getTooltip() {
return this.props._tooltip || '';
}
/**
* Handles clicking / pressing the button.
*
@@ -80,7 +101,7 @@ export default class AbstractRecordButton<P: Props>
* @returns {boolean}
*/
_isDisabled() {
return false;
return this.props._disabled;
}
/**
@@ -103,17 +124,18 @@ export default class AbstractRecordButton<P: Props>
* @param {Props} ownProps - The own props of the Component.
* @private
* @returns {{
* _disabled: boolean,
* _isRecordingRunning: boolean,
* disabledByFeatures: boolean,
* visible: boolean
* }}
*/
export function _mapStateToProps(state: Object, ownProps: Props): Object {
let { visible } = ownProps;
// a button can be disabled/enabled only if enableFeaturesBasedOnToken
// is on
let disabledByFeatures;
// a button can be disabled/enabled if enableFeaturesBasedOnToken
// is on or if the livestreaming is running.
let _disabled;
let _tooltip = '';
if (typeof visible === 'undefined') {
// If the containing component provides the visible prop, that is one
@@ -126,19 +148,35 @@ export function _mapStateToProps(state: Object, ownProps: Props): Object {
} = state['features/base/config'];
const { features = {} } = getLocalParticipant(state);
visible = isModerator
&& fileRecordingsEnabled;
visible = isModerator && fileRecordingsEnabled;
if (enableFeaturesBasedOnToken) {
visible = visible && String(features.recording) === 'true';
disabledByFeatures = String(features.recording) === 'disabled';
_disabled = String(features.recording) === 'disabled';
if (!visible && !_disabled) {
_disabled = true;
visible = true;
// button and tooltip
if (state['features/base/jwt'].isGuest) {
_tooltip = 'dialog.recordingDisabledForGuestTooltip';
} else {
_tooltip = 'dialog.recordingDisabledTooltip';
}
}
}
}
// disable the button if the livestreaming is running.
if (getActiveSession(state, JitsiRecordingConstants.mode.STREAM)) {
_disabled = true;
_tooltip = 'dialog.recordingDisabledBecauseOfActiveLiveStreamingTooltip';
}
return {
_isRecordingRunning:
Boolean(getActiveSession(state, JitsiRecordingConstants.mode.FILE)),
disabledByFeatures,
_disabled,
_isRecordingRunning: Boolean(getActiveSession(state, JitsiRecordingConstants.mode.FILE)),
_tooltip,
visible
};
}

View File

@@ -4,19 +4,8 @@ import { Platform } from 'react-native';
import { IOS_RECORDING_ENABLED, RECORDING_ENABLED, getFeatureFlag } from '../../../../base/flags';
import { translate } from '../../../../base/i18n';
import { IconToggleRecording } from '../../../../base/icons';
import { connect } from '../../../../base/redux';
import AbstractRecordButton, {
_mapStateToProps as _abstractMapStateToProps,
type Props
} from '../AbstractRecordButton';
/**
* An implementation of a button for starting and stopping recording.
*/
class RecordButton extends AbstractRecordButton<Props> {
icon = IconToggleRecording;
}
import AbstractRecordButton, { _mapStateToProps as _abstractMapStateToProps } from '../AbstractRecordButton';
/**
* Maps (parts of) the redux state to the associated props for this component.
@@ -38,4 +27,4 @@ export function mapStateToProps(state: Object, ownProps: Object) {
};
}
export default translate(connect(mapStateToProps)(RecordButton));
export default translate(connect(mapStateToProps)(AbstractRecordButton));

View File

@@ -1,61 +1,14 @@
// @flow
import { translate } from '../../../../base/i18n';
import { IconToggleRecording } from '../../../../base/icons';
import { connect } from '../../../../base/redux';
import AbstractRecordButton, {
_mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
type Props
} from '../AbstractRecordButton';
declare var interfaceConfig: Object;
type Props = AbstractProps & {
/**
* True if the button should be disabled, false otherwise.
*
* NOTE: On web, if the feature is not disabled on purpose, then we still
* show the button but disabled and with a tooltip rendered on it,
* explaining why it's not available.
*/
_disabled: boolean,
/**
* Tooltip for the button when it's disabled in a certain way.
*/
_fileRecordingsDisabledTooltipKey: ?string
}
/**
* An implementation of a button for starting and stopping recording.
*/
class RecordButton extends AbstractRecordButton<Props> {
icon = IconToggleRecording;
/**
* Returns the tooltip that should be displayed when the button is disabled.
*
* @private
* @returns {string}
*/
_getTooltip() {
return this.tooltip || '';
}
/**
* Helper function to be implemented by subclasses, which must return a
* boolean value indicating if this button is disabled or not.
*
* @override
* @protected
* @returns {boolean}
*/
_isDisabled() {
return this.props._disabled;
}
}
/**
* Maps (parts of) the redux state to the associated props for the
* {@code RecordButton} component.
@@ -74,36 +27,14 @@ export function _mapStateToProps(state: Object, ownProps: Props): Object {
const abstractProps = _abstractMapStateToProps(state, ownProps);
let { visible } = ownProps;
const _disabledByFeatures = abstractProps.disabledByFeatures;
let _disabled = false;
let _fileRecordingsDisabledTooltipKey;
if (!abstractProps.visible
&& _disabledByFeatures !== undefined && !_disabledByFeatures) {
_disabled = true;
// button and tooltip
if (state['features/base/jwt'].isGuest) {
_fileRecordingsDisabledTooltipKey
= 'dialog.recordingDisabledForGuestTooltip';
} else {
_fileRecordingsDisabledTooltipKey
= 'dialog.recordingDisabledTooltip';
}
}
if (typeof visible === 'undefined') {
visible = interfaceConfig.TOOLBAR_BUTTONS.includes('recording')
&& (abstractProps.visible
|| Boolean(_fileRecordingsDisabledTooltipKey));
visible = interfaceConfig.TOOLBAR_BUTTONS.includes('recording') && abstractProps.visible;
}
return {
...abstractProps,
visible,
_disabled,
_fileRecordingsDisabledTooltipKey
visible
};
}
export default translate(connect(_mapStateToProps)(RecordButton));
export default translate(connect(_mapStateToProps)(AbstractRecordButton));

View File

@@ -83,8 +83,9 @@ export class AbstractClosedCaptionButton
*/
export function _abstractMapStateToProps(state: Object, ownProps: Object) {
const { _requestingSubtitles } = state['features/subtitles'];
const { isGuest = true } = state['features/base/jwt'];
const { transcribingEnabled } = state['features/base/config'];
const { visible = Boolean(transcribingEnabled) } = ownProps;
const { visible = Boolean(transcribingEnabled && !isGuest) } = ownProps;
return {
_requestingSubtitles,

View File

@@ -32,7 +32,8 @@ class ClosedCaptionButton
*/
export function mapStateToProps(state: Object, ownProps: Object) {
const { transcribingEnabled } = state['features/base/config'];
const enabled = getFeatureFlag(state, CLOSE_CAPTIONS_ENABLED, true) && transcribingEnabled;
const { isGuest = true } = state['features/base/jwt'];
const enabled = getFeatureFlag(state, CLOSE_CAPTIONS_ENABLED, true) && transcribingEnabled && !isGuest;
const { visible = enabled } = ownProps;
return {

View File

@@ -1,5 +1,6 @@
/* global __dirname */
const CircularDependencyPlugin = require('circular-dependency-plugin');
const process = require('process');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
@@ -11,6 +12,7 @@ const devServerProxyTarget
= process.env.WEBPACK_DEV_SERVER_PROXY_TARGET || 'https://alpha.jitsi.net';
const analyzeBundle = process.argv.indexOf('--analyze-bundle') !== -1;
const detectCircularDeps = process.argv.indexOf('--detect-circular-deps') !== -1;
const minimize
= process.argv.indexOf('-p') !== -1
@@ -155,6 +157,12 @@ const config = {
&& new BundleAnalyzerPlugin({
analyzerMode: 'disabled',
generateStatsFile: true
}),
detectCircularDeps
&& new CircularDependencyPlugin({
allowAsyncCycles: false,
exclude: /node_modules/,
failOnError: false
})
].filter(Boolean),
resolve: {