mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-19 10:17:48 +00:00
Compare commits
12 Commits
4160
...
rn-refacto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c68f78db3 | ||
|
|
d93b219c7f | ||
|
|
10cd150a07 | ||
|
|
a31f3c0c76 | ||
|
|
af39186a5f | ||
|
|
d4d1d0aa70 | ||
|
|
3a88f4939c | ||
|
|
fe221fe4be | ||
|
|
1caaa47f5e | ||
|
|
a2c4d17e4d | ||
|
|
ce1de9e1e7 | ||
|
|
3e7abf3da0 |
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
|
||||
@@ -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ă"
|
||||
}
|
||||
|
||||
@@ -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
719
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './AspectRatioAware';
|
||||
export { default as DimensionsDetector } from './DimensionsDetector';
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user