Compare commits

...

21 Commits

Author SHA1 Message Date
paweldomas
855eeafc79 feat(accessibility): set html 'lang' attribute
...to match the currently selected language. Starts with
'en' by default.
2020-04-03 18:23:21 -05:00
Ruben Kerkhof
06ae1861ee log: fix showing verbatim %s
So turn 'hover in %s 1a5cd940' into 'hover in 1a5cd940'
2020-04-03 15:41:30 +02:00
Alexander Neumann
e0afd8fadb lang: fix german translation
This commit fixes a few translation errors, especially in the
kickParticipantTitle.
2020-04-03 15:39:38 +02:00
Michael Telatynski
953f838a2a Improve accessibility of Buttons in Webapp (#5432)
* Expose toggle buttons better via ARIA

* Wire up the divs/li as role=button as per ARIA patterns

* Add flow annotations to appease the linter

* For role=button use aria-pressed not aria-checked
2020-04-03 08:19:32 -05:00
ljo
bf5f1f0168 lang: improve sv translation 2020-04-03 15:13:26 +02:00
Saúl Ibarra Corretgé
cdf977ff3f doc: add documentation index 2020-04-03 14:57:40 +02:00
Joris Bodin
ee1dc9dd8e lang: update translation for fr, de, es, it for mobile app 2020-04-03 12:14:21 +02:00
Ali Karpuzoglu
7bec68e492 doc: fix typo 2020-04-03 12:13:20 +02:00
Bettenbuk Zoltan
97fff02c15 web: use password i18n placeholder 2020-04-03 11:22:11 +02:00
Дамян Минков
b372b2ccf2 Fix turn file link (#5601)
* debian: Does not add a link of turnserver will not be configured. Fixes #5596.

* debian: Partially reverts 990c77bd.

* debian: Skip filename duplication, use variable.
2020-04-02 18:20:41 -05:00
Bettenbuk Zoltan
d00ead932b feat: better room lock prompt 2020-04-02 17:27:22 +02:00
Saúl Ibarra Corretgé
bb2b1b58ec conference: add room name to browser title
Re-do of
86ebfe8dad
2020-04-02 16:33:39 +02:00
GK2
ceeea7314c debian: fix typo 2020-04-02 15:27:19 +02:00
Andrei Gavrilescu
9d6a93119b feat(screenSharing): Add support for audio screen sharing on electron 2020-04-02 16:18:10 +03:00
GK2
08be68cda4 debian: fix install-letsencrypt-cert.sh to create misssing directory
Update install-letsencrypt-cert.sh to fix missing cron.weekly
REFS : https://github.com/jitsi/jitsi-meet/issues/5576
2020-04-02 15:10:27 +02:00
Bettenbuk Zoltan
3a2081ffed ref: change Chat to JitsiModal 2020-04-02 12:40:12 +02:00
Bettenbuk Zoltan
57d14d9517 feat: help centre 2020-04-02 12:40:12 +02:00
Saúl Ibarra Corretgé
c5e693f14a rn,invite: use custom clear button also on iOS
Otherwise it won't be visible if Dark Mode is enabled.
2020-04-02 11:46:29 +02:00
Saúl Ibarra Corretgé
a9da047d3a rn,invite: fix bottom padding on AddPeopleDialog on Android 2020-04-02 11:46:29 +02:00
bgrozev
171b065db1 Revert "Add room name to browser tab title" (#5560)
Because it displays "undefined" in the title.

This reverts commit 86ebfe8dad.
2020-04-01 22:03:14 -05:00
Jaya Allamsetty
9da0b7fee3 deps(ljm): Bump adapter.js to 7.5.0 and validate ice candidate info 2020-04-01 18:28:30 -04:00
51 changed files with 923 additions and 145 deletions

View File

@@ -1 +0,0 @@
/usr/share/jitsi-meet-turnserver/jitsi-meet.conf /etc/nginx/modules-enabled/60-jitsi-meet.conf

View File

@@ -46,14 +46,22 @@ case "$1" in
# check whether other enabled hosts has listen 443
if cat ${site} | grep -v "^[[:space:]]*#" | grep listen | grep -q "^.*[[:space:]:]443[;[:space:]].*" ; then
# nothing to do
echo "------------------------------------------------"
echo ""
echo "turnserver not configured as other nginx sites use port 443"
echo ""
echo "------------------------------------------------"
db_stop
exit 0
fi
done
# if there was a turn config backup it so we can configure
# we cannot recognize at the moment is this a user config or default config when installing coturn
if [[ -f $TURN_CONFIG ]] && ! grep -q "jitsi-meet coturn config" "$TURN_CONFIG" ; then
mv $TURN_CONFIG $TURN_CONFIG.bak
fi
# detect dpkg-reconfigure, just delete old links
db_get jitsi-meet-turnserver/jvb-hostname
JVB_HOSTNAME_OLD=$RET
@@ -67,17 +75,21 @@ case "$1" in
db_get jitsi-meet/jvb-serve || true
if [ ! -f $NGINX_CONFIG -o "$RET" = "true" ] ; then
# nothing to do
echo "------------------------------------------------"
echo ""
echo "turnserver not configured as no nginx found to multiplex traffic"
echo ""
echo "------------------------------------------------"
db_stop
exit 0
fi
if [[ -f $TURN_CONFIG ]] ; then
echo "------------------------------------------------"
echo ""
echo "turnserver is already configured on this machine, skipping."
echo ""
echo "------------------------------------------------"
db_stop
exit 0
fi
@@ -130,7 +142,8 @@ case "$1" in
invoke-rc.d coturn restart || true
NGINX_STREAM_CONFIG="/etc/nginx/modules-enabled/60-jitsi-meet.conf"
if [ -f $NGINX_STREAM_CONFIG ] && [ -f $NGINX_CONFIG ] ; then
if [ ! -f $NGINX_STREAM_CONFIG ] && [ -f $NGINX_CONFIG ] ; then
ln -s /usr/share/jitsi-meet-turnserver/jitsi-meet.conf $NGINX_STREAM_CONFIG
sed -i "s/listen 443 ssl/listen 4444 ssl http2/g" $NGINX_CONFIG
sed -i "s/listen \[\:\:\]\:443 ssl/listen \[\:\:\]\:4444 ssl http2/g" $NGINX_CONFIG
invoke-rc.d nginx reload || true

View File

@@ -24,6 +24,7 @@ set -e
case "$1" in
remove)
rm -rf /etc/nginx/modules-enabled/60-jitsi-meet.conf
if [ -x "/etc/init.d/nginx" ]; then
invoke-rc.d nginx reload || true
fi
@@ -32,6 +33,8 @@ case "$1" in
fi
;;
purge)
rm -rf /etc/nginx/modules-enabled/60-jitsi-meet.conf
rm -rf /etc/turnserver.conf
if [ -x "/etc/init.d/nginx" ]; then
invoke-rc.d nginx reload || true
fi

43
doc/README.md Normal file
View File

@@ -0,0 +1,43 @@
# Documentation
This document is the entrypoint to different guides, divided in three groups:
* User guide: these documents are designed to help users of the service, to better
understand all the available features and how to use them.
* Developer guide: these documents are designed to help developers who want to either
integrate the Jitsi Meet API / SDK in their products or want to improve Jitsi Meet
itself by developing new features or fixing bugs.
* DevOps guide: these documents are designed for DevOps folks, system administrators
or anyone who wishes to deploy and operate their own Jitsi Meet instance.
## User guide
Work in progress.
## Developer guide
### Web
* [iframe API](https://github.com/jitsi/jitsi-meet/blob/master/doc/api.md)
* [Jitsi Meet development](https://github.com/jitsi/jitsi-meet/blob/master/doc/development.md)
### Mobile
* [Building the mobile apps](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md)
* [SDK usage examples](https://github.com/jitsi/jitsi-meet-sdk-samples)
* [Enabling Dropbox support](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile-dropbox.md)
* [Enabling Google authentication](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile-google-auth.md)
## DevOps guide
* [Quick install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md)
* [Docker install](https://github.com/jitsi/docker-jitsi-meet/blob/master/README.md)
* [Google Calendar, MS Calendar, Dropbox integrations](https://github.com/jitsi/jitsi-meet/blob/master/doc/integrations.md)
* [Video tutorials on deployment and scalability](https://jitsi.org/tutorials/)
* [Configuring a video SIP gateway](https://github.com/jitsi/jitsi-meet/blob/master/doc/sipgw-config.md)
* [Enabling speaker stats](https://github.com/jitsi/jitsi-meet/blob/master/doc/speakerstats-prosody.md)
* [Enabling TURN](https://github.com/jitsi/jitsi-meet/blob/master/doc/turn.md)
* [Networking FAQ](https://github.com/jitsi/jitsi-meet/blob/master/doc/faq.md)
* [Cloud APIs](https://github.com/jitsi/jitsi-meet/blob/master/doc/cloud-api.md)

View File

@@ -1,4 +1,4 @@
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
<html itemscope itemtype="http://schema.org/Product" lang="en" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
<head>
<!--#include virtual="head.html" -->
<meta charset="utf-8">

View File

@@ -19,7 +19,7 @@ There are 2 ways to integrate the SDK into your project:
Follow the instructions [here](https://github.com/jitsi/jitsi-meet-ios-sdk-releases/blob/master/README.md).
### Builduing it yourself
### Building it yourself
1. Install all required [dependencies](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).

View File

@@ -33,7 +33,7 @@
"error": {
"appConfiguration": "Kalenderintegration ist nicht richtig konfiguriert.",
"generic": "Ein Fehler ist aufgetreten. Prüfen Sie Ihre Kalendereinstellungen oder versuchen Sie, den Kalender zu aktualisieren.",
"notSignedIn": "Ein Fehler ist während der Authentifizierung zur Anzeige von Kalenderertermine aufgetreten. Prüfen Sie Ihre Kalendereinstellungen oder versuchen Sie, sich erneut anzumelden."
"notSignedIn": "Ein Fehler ist während der Authentifizierung zur Anzeige von Kalenderterminen aufgetreten. Prüfen Sie Ihre Kalendereinstellungen oder versuchen Sie, sich erneut anzumelden."
},
"join": "Teilnehmen",
"joinTooltip": "Am Meeting teilnehmen",
@@ -191,7 +191,7 @@
"kickMessage": "Sie können sich für weitere Einzelheiten an {{participantDisplayName}}} wenden.",
"kickParticipantButton": "Entfernen",
"kickParticipantDialog": "Wollen Sie diesen Teilnehmer wirklich entfernen?",
"kickParticipantTitle": "Teilnehmer stummschalten?",
"kickParticipantTitle": "Teilnehmer entfernen?",
"kickTitle": "Autsch! {{participantDisplayName}} hat Sie aus dem Meeting geworfen.",
"liveStreaming": "Livestreaming",
"liveStreamingDisabledForGuestTooltip": "Gäste können kein Livestreaming starten.",
@@ -241,7 +241,7 @@
"removePassword": "$t(lockRoomPassword) entfernen",
"removeSharedVideoMsg": "Sind Sie sicher, dass Sie das geteilte Video entfernen möchten?",
"removeSharedVideoTitle": "Freigegebenes Video entfernen",
"reservationError": "Fehler im Reservationssystem",
"reservationError": "Fehler im Reservierungssystem",
"reservationErrorMsg": "Fehler, Nummer: {{code}}, Nachricht: {{msg}}",
"retry": "Wiederholen",
"screenSharingFailedToInstall": "Oh! Die Erweiterung für die Bildschirmfreigabe konnte nicht installiert werden.",
@@ -315,7 +315,7 @@
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Entschuldigung, leider wird das Einwählen derzeit nicht unterstützt.",
"dialInNumber": "Einwählen:",
"dialInSummaryError": "Fehler beim Abrufen der Einwählinformationen. Versuchen Sie es später erneut.",
"dialInSummaryError": "Fehler beim Abrufen der Einwahlinformationen. Versuchen Sie es später erneut.",
"dialInTollFree": "Gebührenfrei",
"genericError": "Es ist leider etwas schiefgegangen.",
"inviteLiveStream": "Klicken Sie auf {{url}} um den Livestream dieser Konferenz zu öffnen",
@@ -328,11 +328,11 @@
"moreNumbers": "Weitere Telefonnummern",
"noNumbers": "Keine Telefonnummern verfügbar.",
"noPassword": "Kein",
"noRoom": "Keine Konferenz für die Einwähl-Informationen angegeben.",
"noRoom": "Keine Konferenz für die Einwahlinformationen angegeben.",
"numbers": "Einwählnummern",
"password": "$t(lockRoomPasswordUppercase):",
"title": "Teilen",
"tooltip": "Freigabe-Link und Einwählinformationen für dieses Meeting",
"tooltip": "Freigabe-Link und Einwahlinformationen für dieses Meeting",
"label": "Meeting-Informationen"
},
"inviteDialog": {
@@ -367,7 +367,7 @@
"videoQuality": "Anrufqualität verwalten"
},
"liveStreaming": {
"busy": "Es werden Resourcen zum Streamen bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
"busy": "Es werden Ressourcen zum Streamen bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
"busyTitle": "Alle Streaming-Instanzen sind in Gebrauch",
"changeSignIn": "Konten wechseln.",
"choose": "Livestream auswählen",
@@ -405,7 +405,7 @@
"dialogTitle": "Lokale Aufzeichnungssteuerelemente",
"duration": "Dauer",
"durationNA": "N. v.",
"encoding": "Codierung",
"encoding": "Kodierung",
"label": "LOR",
"labelToolTip": "Lokale Aufzeichnung ist aktiviert",
"localRecording": "Lokale Aufzeichnung",
@@ -769,5 +769,9 @@
"sendFeedback": "Feedback senden",
"terms": "AGB",
"title": "Sichere, mit umfassenden Funktionen ausgestattete und vollkommen kostenlose Videokonferenzen"
},
"lonelyMeetingExperience": {
"button": "Weitere einladen",
"youAreAlone": "Sie sind der einzige in diesem Meeting"
}
}

View File

@@ -628,6 +628,7 @@
"logout": "Cerrar sesión",
"lowerYourHand": "Bajar la mano",
"moreActions": "Más acciones",
"moreOptions": "Más opciones",
"mute": "Activar / Desactivar Silencio",
"noAudioSignalTitle": "¡No hay entrada proveniente de su micrófono!",
"noAudioSignalDesc": "Si no lo silenció a propósito desde la configuración del sistema o el dispositivo, considere cambiar el dispositivo.",
@@ -752,5 +753,9 @@
"sendFeedback": "Enviar comentarios",
"terms": "Términos",
"title": "Seguro, lleno de funcionalidades y videoconferencias completamente gratuitas"
},
"lonelyMeetingExperience": {
"button": "Invitar a otros",
"youAreAlone": "Eres el único en la reunión"
}
}

View File

@@ -628,6 +628,7 @@
"logout": "Déconnexion",
"lowerYourHand": "Baisser la main",
"moreActions": "Plus d'actions",
"moreOptions": "Plus d'options",
"mute": "Muet / Actif",
"noAudioSignalTitle": "Il n'y a pas d'entrée provenant de votre micro !",
"noAudioSignalDesc": "Si vous n'avez pas délibérément coupé le son des paramètres du système ou du matériel, envisagez de changer le périphérique.",
@@ -752,5 +753,12 @@
"sendFeedback": "Envoyer votre avis",
"terms": "Termes",
"title": "Vidéoconférence Sécurisée, entièrement en vedette et gratuite"
},
"lonelyMeetingExperience": {
"button": "Inviter d'autres personnes",
"youAreAlone": "Vous êtes le seul participant de la réunion"
},
"helpView": {
"header": "Centre d'aide"
}
}

View File

@@ -594,6 +594,7 @@
"logout": "Logout",
"lowerYourHand": "Abbassa la mano",
"moreActions": "Più azioni",
"moreOptions": "Più opzioni",
"mute": "Microfono Attiva / Disattiva",
"openChat": "Apri una chat",
"pip": "Abilita visualizzazione immagine nellimmagine",
@@ -707,5 +708,9 @@
"sendFeedback": "Invia feedback",
"terms": "Termini di utilizzo",
"title": "Il sistema di conferenza sicuro, funzionale e completamente gratuito."
},
"lonelyMeetingExperience": {
"button": "Invita gli altri",
"youAreAlone": "Sei l'unico in riunione"
}
}

View File

@@ -569,9 +569,9 @@
"shareYourScreen": "Slå av eller på skärmdelning",
"shortcuts": "Stäng eller öppna genvägar",
"show": "",
"speakerStats": "Stäng eller öppna högstalarstatistik",
"speakerStats": "Stäng eller öppna talarstatistik",
"tileView": "Öppna eller stäng panelvyn",
"toggleCamera": "Byta kamera",
"toggleCamera": "Växla kamera",
"videomute": "Sätt på eller stäng av mikrofonen",
"videoblur": ""
},
@@ -606,7 +606,7 @@
"sharedvideo": "Dela en Youtube-video",
"shareRoom": "Bjud in någon",
"shortcuts": "Visa genvägar",
"speakerStats": "Högtalarspecifikationer",
"speakerStats": "Talarstatistik",
"startScreenSharing": "Starta skärmdelning",
"startSubtitles": "Starta undertextning",
"stopScreenSharing": "Avsluta skämdelning",

View File

@@ -220,7 +220,7 @@
"muteParticipantDialog": "Are you sure you want to mute this participant? You won't be able to unmute them, but they can unmute themselves at any time.",
"muteParticipantTitle": "Mute this participant?",
"Ok": "Ok",
"passwordLabel": "$t(lockRoomPasswordUppercase)",
"passwordLabel": "The meeting has been locked by a participant. Please enter the $t(lockRoomPassword) to join.",
"passwordNotSupported": "Setting a meeting $t(lockRoomPassword) is not supported.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) not supported",
"passwordRequired": "$t(lockRoomPasswordUppercase) required",
@@ -244,6 +244,7 @@
"reservationError": "Reservation system error",
"reservationErrorMsg": "Error code: {{code}}, message: {{msg}}",
"retry": "Retry",
"screenSharingAudio": "Share audio",
"screenSharingFailedToInstall": "Oops! Your screen sharing extension failed to install.",
"screenSharingFailedToInstallTitle": "Screen sharing extension failed to install",
"screenSharingFirefoxPermissionDeniedError": "Something went wrong while we were trying to share your screen. Please make sure that you have given us permission to do so. ",
@@ -757,6 +758,7 @@
"connectCalendarButton": "Connect your calendar",
"connectCalendarText": "Connect your calendar to view all your meetings in {{app}}. Plus, add {{provider}} meetings to your calendar and start them with one click.",
"enterRoomTitle": "Start a new meeting",
"getHelp": "Get help",
"roomNameAllowedChars": "Meeting name should not contain any of these characters: ?, &, :, ', \", %, #.",
"go": "GO",
"goSmall": "GO",
@@ -776,5 +778,8 @@
"lonelyMeetingExperience": {
"button": "Invite others",
"youAreAlone": "You are the only one in the meeting"
},
"helpView": {
"header": "Help centre"
}
}

View File

@@ -184,7 +184,7 @@ export default class LargeVideoManager {
this.newStreamData = null;
logger.info('hover in %s', id);
logger.info(`hover in ${id}`);
this.state = videoType;
// eslint-disable-next-line no-shadow
const container = this.getCurrentContainer();

19
package-lock.json generated
View File

@@ -10653,8 +10653,8 @@
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-utils": {
"version": "github:jitsi/js-utils#91c5e53ca5fa42907c88d56bc78254e6e56e058d",
"from": "github:jitsi/js-utils#91c5e53ca5fa42907c88d56bc78254e6e56e058d",
"version": "github:jitsi/js-utils#0b2cef90613a74777fefd98d4ee3eda3879809ab",
"from": "github:jitsi/js-utils#0b2cef90613a74777fefd98d4ee3eda3879809ab",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3",
@@ -10883,8 +10883,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#a7950f8ebb489225c2e8bf41fe65f330b3de0874",
"from": "github:jitsi/lib-jitsi-meet#a7950f8ebb489225c2e8bf41fe65f330b3de0874",
"version": "github:jitsi/lib-jitsi-meet#960eea3c5087ce07e9135fad70268c7d338e0de5",
"from": "github:jitsi/lib-jitsi-meet#960eea3c5087ce07e9135fad70268c7d338e0de5",
"requires": {
"@jitsi/sdp-interop": "0.1.14",
"@jitsi/sdp-simulcast": "0.2.2",
@@ -10897,7 +10897,7 @@
"strophe.js": "1.3.4",
"strophejs-plugin-disco": "0.0.2",
"strophejs-plugin-stream-management": "github:jitsi/strophejs-plugin-stream-management#cec7608601c1bc098543823fc658e3ddf758c009",
"webrtc-adapter": "github:webrtc/adapter#1eec19782b4058d186341263e7d049cea3e3290a"
"webrtc-adapter": "7.5.0"
},
"dependencies": {
"js-md5": {
@@ -18999,11 +18999,12 @@
}
},
"webrtc-adapter": {
"version": "github:webrtc/adapter#1eec19782b4058d186341263e7d049cea3e3290a",
"from": "github:webrtc/adapter#1eec19782b4058d186341263e7d049cea3e3290a",
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.5.0.tgz",
"integrity": "sha512-cUqlw310uLLSYvO8FTNCVmGWSMlMt6vuSDkcYL1nW+RUvAILJ3jEIvAUgFQU5EFGnU+mf9/No14BFv3U+hoxBQ==",
"requires": {
"rtcpeerconnection-shim": "^1.1.13",
"sdp": "^2.3.0"
"rtcpeerconnection-shim": "^1.2.15",
"sdp": "^2.12.0"
}
},
"websocket-driver": {

View File

@@ -56,7 +56,7 @@
"js-utils": "github:jitsi/js-utils#0b2cef90613a74777fefd98d4ee3eda3879809ab",
"jsrsasign": "8.0.12",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#a7950f8ebb489225c2e8bf41fe65f330b3de0874",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#960eea3c5087ce07e9135fad70268c7d338e0de5",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.13",
"moment": "2.19.4",

View File

@@ -89,10 +89,22 @@ export class AbstractApp extends BaseApp<Props, *> {
return (
<Fragment>
<OverlayContainer />
{ this._createExtraPlatformSpecificElement() }
</Fragment>
);
}
/**
* Renders platform specific extra elements to be added alongside with the main element, if need be.
*
* NOTE: Overridden by child components.
*
* @returns {React$Element}
*/
_createExtraPlatformSpecificElement() {
return null;
}
_createMainElement: (React$Element<*>, Object) => ?React$Element<*>;
/**

View File

@@ -12,6 +12,7 @@ import { Platform } from '../../base/react';
import '../../base/responsive-ui';
import { updateSettings } from '../../base/settings';
import '../../google-api';
import { HelpView } from '../../help';
import '../../mobile/audio-mode';
import '../../mobile/back-button';
import '../../mobile/background';
@@ -107,6 +108,17 @@ export class App extends AbstractApp {
});
}
/**
* Renders platform specific extra elements to be added alongside with the main element, if need be.
*
* @inheritdoc
*/
_createExtraPlatformSpecificElement() {
return (
<HelpView />
);
}
/**
* Attempts to disable the use of React Native
* {@link ExceptionsManager#handleException} on platforms and in

View File

@@ -39,6 +39,7 @@ export default {
statusBarContent: ColorPalette.white,
text: ColorPalette.white
},
'Modal': {},
'LargeVideo': {
background: 'rgb(42, 58, 75)'
},

View File

@@ -0,0 +1,6 @@
/**
* See {@link _setLanguage}.
*
* @type {string}
*/
export const SET_LANGUAGE = 'SET_LANGUAGE';

View File

@@ -0,0 +1,33 @@
// @flow
import { i18next } from './index';
import { SET_LANGUAGE } from './actionTypes';
/**
* Makes an attempt to change the language.
*
* @param {string} newLanguage - The new language code which identifies
* the language to be set.
* @returns {Function}
*/
export function changeLanguage(newLanguage: string): Function {
return function(dispatch: (Object) => Object) {
return i18next.changeLanguage(newLanguage)
.then(() => {
dispatch(_setLanguage(i18next.language));
});
};
}
/**
* Sets the current language on the Redux store.
*
* @param {string} language - The new language code that will be stored.
* @returns {Object}
* @private
*/
export function _setLanguage(language: string): Object {
return {
type: SET_LANGUAGE,
language
};
}

View File

@@ -1,6 +1,10 @@
export * from './actions';
export * from './dateUtil';
export * from './functions';
// TODO Eventually (e.g. when the non-React Web app is rewritten into React), it
// should not be necessary to export i18next.
export { default as i18next, DEFAULT_LANGUAGE, LANGUAGES } from './i18next';
import './middleware';
import './reducer';

View File

@@ -0,0 +1,69 @@
/* @flow */
/* global document */
import i18next from 'i18next';
import { APP_WILL_MOUNT } from '../app';
import { MiddlewareRegistry } from '../redux';
import { SET_LANGUAGE } from './actionTypes';
import { _setLanguage } from './actions';
/**
* The Redux middleware of the feature base/i18n.
*
* @param {Store} store - The Redux store.
* @returns {Function}
* @private
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case APP_WILL_MOUNT:
return _appWillMount(store, next, action);
case SET_LANGUAGE:
return handleSetLanguage(store, next, action);
}
return next(action);
});
/**
* Side effects for {@link APP_WILL_MOUNT}.
*
* @param {Store} store - The Redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The Redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The Redux action {@code APP_WILL_MOUNT} which is
* being dispatched in the specified {@code store}.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified {@code action}.
*/
function _appWillMount({ dispatch }, next, action) {
if (i18next.language) {
dispatch(_setLanguage(i18next.language));
} else {
i18next.on('initialized', () => {
dispatch(_setLanguage(i18next.language));
});
}
return next(action);
}
/**
* Side effects for the {@link SET_LANGUAGE} action.
*
* @param {Store} store - The Redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The Redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The Redux action {@code SET_LANGUAGE} which is
* being dispatched in the specified {@code store}.
* @returns {*}
*/
function handleSetLanguage(store, next, action) {
document && document.documentElement && document.documentElement.setAttribute('lang', action.language);
return next(action);
}

View File

@@ -0,0 +1,30 @@
// @flow
import { ReducerRegistry, set } from '../redux';
import {
SET_LANGUAGE
} from './actionTypes';
import { DEFAULT_LANGUAGE } from './i18next';
/**
* The default/initial redux state of the feature base/i18n.
*
* @type {{
* language: string
* }}
*/
const DEFAULT_STATE = {
language: DEFAULT_LANGUAGE
};
ReducerRegistry.register(
'features/base/i18n',
(state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_LANGUAGE:
return set(state, 'language', action.language);
default:
return state;
}
});

View File

@@ -0,0 +1,6 @@
// @flow
/**
* Action type to set the ID of the active modal (or undefined if needs to be hidden).
*/
export const SET_ACTIVE_MODAL_ID = 'SET_ACTIVE_MODAL_ID';

View File

@@ -0,0 +1,19 @@
// @flow
import { SET_ACTIVE_MODAL_ID } from './actionTypes';
/**
* Action to set the ID of the active modal (or undefined if needs to be hidden).
*
* @param {string} activeModalId - The new modal ID or undefined.
* @returns {{
* activeModalId: string,
* type: SET_ACTIVE_MODAL_ID
* }}
*/
export function setActiveModalId(activeModalId: ?string) {
return {
activeModalId,
type: SET_ACTIVE_MODAL_ID
};
}

View File

@@ -0,0 +1,151 @@
// @flow
import React, { PureComponent } from 'react';
import { SafeAreaView, View } from 'react-native';
import { ColorSchemeRegistry } from '../../color-scheme';
import { HeaderWithNavigation, SlidingView } from '../../react';
import { connect } from '../../redux';
import { StyleType } from '../../styles';
import { setActiveModalId } from '../actions';
import styles from './styles';
type Props = {
/**
* The color schemed style of the common header component.
*/
_headerStyles: StyleType,
/**
* True if the modal should be shown, false otherwise.
*/
_show: boolean,
/**
* The color schemed style of the modal.
*/
_styles: StyleType,
/**
* The children component(s) of the Modal, to be rendered.
*/
children: React$Node,
/**
* The Redux Dispatch function.
*/
dispatch: Function,
/**
* The i18n label key of the header title.
*/
headerLabelKey: string,
/**
* The ID of the modal that is being rendered. This is used to show/hide the modal.
*/
modalId: string,
/**
* Callback to be invoked when the modal closes.
*/
onClose?: Function,
/**
* The position from where the modal should be opened. This is derived from the
* props of the {@code SlidingView} with the same name.
*/
position?: string
};
/**
* Implements a custom Jitsi Modal that doesn't use the built in native
* Modal component of React Native.
*/
class JitsiModal extends PureComponent<Props> {
static defaultProps = {
position: 'bottom'
};
/**
* Instantiates a new component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onRequestClose = this._onRequestClose.bind(this);
}
/**
* Implements {@code PureComponent#render}.
*
* @inheritdoc
*/
render() {
const { _headerStyles, _show, _styles, children, headerLabelKey, position } = this.props;
return (
<SlidingView
onHide = { this._onRequestClose }
position = { position }
show = { _show }>
<View
style = { [
_headerStyles.page,
_styles.page
] }>
<HeaderWithNavigation
headerLabelKey = { headerLabelKey }
onPressBack = { this._onRequestClose } />
<SafeAreaView style = { styles.safeArea }>
{ children }
</SafeAreaView>
</View>
</SlidingView>
);
}
_onRequestClose: () => boolean;
/**
* Callback to be invoked when the SlidingView requests closing.
*
* @returns {boolean}
*/
_onRequestClose() {
const { _show, dispatch, onClose } = this.props;
if (_show) {
if (typeof onClose === 'function') {
onClose();
}
dispatch(setActiveModalId());
return true;
}
return false;
}
}
/**
* Maps part of the Redix state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
*/
function _mapStateToProps(state, ownProps): $Shape<Props> {
return {
_headerStyles: ColorSchemeRegistry.get(state, 'Header'),
_show: state['features/base/modal'].activeModalId === ownProps.modalId,
_styles: ColorSchemeRegistry.get(state, 'Modal')
};
}
export default connect(_mapStateToProps)(JitsiModal);

View File

@@ -0,0 +1,3 @@
// @flow
export { default as JitsiModal } from './JitsiModal';

View File

@@ -0,0 +1,6 @@
// @flow
import { Component } from 'react';
export const JitsiModal = Component;

View File

@@ -0,0 +1,15 @@
// @flow
import { ColorSchemeRegistry, schemeColor } from '../../color-scheme';
export default {
safeArea: {
flex: 1
}
};
ColorSchemeRegistry.register('Modal', {
page: {
backgroundColor: schemeColor('background')
}
});

View File

@@ -0,0 +1,7 @@
// @flow
import './reducer';
export * from './actions';
export * from './actionTypes';
export * from './components';

View File

@@ -0,0 +1,17 @@
// @flow
import { ReducerRegistry } from '../redux';
import { SET_ACTIVE_MODAL_ID } from './actionTypes';
ReducerRegistry.register('features/base/modal', (state = {}, action) => {
switch (action.type) {
case SET_ACTIVE_MODAL_ID:
return {
...state,
activeModalId: action.activeModalId
};
}
return state;
});

View File

@@ -230,13 +230,14 @@ export default class AbstractButton<P: Props, S: *> extends Component<P, S> {
/**
* Helper function to be implemented by subclasses, which must return a
* {@code boolean} value indicating if this button is toggled or not.
* {@code boolean} value indicating if this button is toggled or not or
* undefined if the button is not toggleable.
*
* @protected
* @returns {boolean}
* @returns {?boolean}
*/
_isToggled() {
return false;
return undefined;
}
_onClick: (*) => void;

View File

@@ -12,6 +12,69 @@ import type { Props } from './AbstractToolboxItem';
* Web implementation of {@code AbstractToolboxItem}.
*/
export default class ToolboxItem extends AbstractToolboxItem<Props> {
/**
* Initializes a new {@code ToolboxItem} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onKeyDown = this._onKeyDown.bind(this);
this._onKeyUp = this._onKeyUp.bind(this);
}
_onKeyDown: (Object) => void;
/**
* Handles 'Enter' key on the button to trigger onClick for accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyDown(event) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
} else if (event.key === ' ') {
// Space triggers button onKeyUp but we need to prevent PTT here
event.preventDefault();
event.stopPropagation();
}
}
_onKeyUp: (Object) => void;
/**
* Handles ' ' (Space) key on the button to trigger onClick for
* accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyUp(event) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === ' ') {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
}
}
/**
* Handles rendering of the actual item. If the label is being shown, which
* is controlled with the `showLabel` prop, the item is rendered for its
@@ -27,14 +90,22 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
elementAfter,
onClick,
showLabel,
tooltipPosition
tooltipPosition,
toggled
} = this.props;
const className = showLabel ? 'overflow-menu-item' : 'toolbox-button';
const props = {
'aria-pressed': toggled,
'aria-disabled': disabled,
'aria-label': this.accessibilityLabel,
className: className + (disabled ? ' disabled' : ''),
onClick: disabled ? undefined : onClick
onClick: disabled ? undefined : onClick,
onKeyDown: this._onKeyDown,
onKeyUp: this._onKeyUp,
tabIndex: 0,
role: 'button'
};
const elementType = showLabel ? 'li' : 'div';
const useTooltip = this.tooltip && this.tooltip.length > 0;
let children = (

View File

@@ -1,18 +1,18 @@
// @flow
import React from 'react';
import { KeyboardAvoidingView, SafeAreaView } from 'react-native';
import { KeyboardAvoidingView } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n';
import { HeaderWithNavigation, SlidingView } from '../../../base/react';
import { JitsiModal } from '../../../base/modal';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import { CHAT_VIEW_MODAL_ID } from '../../constants';
import AbstractChat, {
_mapDispatchToProps,
_mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
_mapStateToProps,
type Props
} from '../AbstractChat';
import ChatInputBar from './ChatInputBar';
@@ -20,88 +20,31 @@ import MessageContainer from './MessageContainer';
import MessageRecipient from './MessageRecipient';
import styles from './styles';
type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType
};
/**
* Implements a React native component that renders the chat window (modal) of
* the mobile client.
*/
class Chat extends AbstractChat<Props> {
/**
* Instantiates a new instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onClose = this._onClose.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
const { _styles } = this.props;
return (
<SlidingView
onHide = { this._onClose }
position = 'bottom'
show = { this.props._isOpen } >
<JitsiModal
headerLabelKey = 'chat.title'
modalId = { CHAT_VIEW_MODAL_ID }>
<KeyboardAvoidingView
behavior = 'padding'
style = { styles.chatContainer }>
<HeaderWithNavigation
headerLabelKey = 'chat.title'
onPressBack = { this._onClose } />
<SafeAreaView style = { _styles.backdrop }>
<MessageContainer messages = { this.props._messages } />
<MessageRecipient />
<ChatInputBar onSend = { this.props._onSendMessage } />
</SafeAreaView>
<MessageContainer messages = { this.props._messages } />
<MessageRecipient />
<ChatInputBar onSend = { this.props._onSendMessage } />
</KeyboardAvoidingView>
</SlidingView>
</JitsiModal>
);
}
_onClose: () => boolean
/**
* Closes the chat window.
*
* @returns {boolean}
*/
_onClose() {
if (this.props._isOpen) {
this.props._onToggleChat();
return true;
}
return false;
}
}
/**
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
_styles: ColorSchemeRegistry.get(state, 'Chat')
};
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));

View File

@@ -1,6 +1,7 @@
// @flow
import { IconChat, IconChatUnread } from '../../../base/icons';
import { setActiveModalId } from '../../../base/modal';
import { getLocalParticipant } from '../../../base/participants';
import { connect } from '../../../base/redux';
import {
@@ -9,7 +10,7 @@ import {
} from '../../../base/toolbox';
import { openDisplayNamePrompt } from '../../../display-name';
import { toggleChat } from '../../actions';
import { CHAT_VIEW_MODAL_ID } from '../../constants';
import { getUnreadCount } from '../../functions';
type Props = AbstractButtonProps & {
@@ -93,7 +94,7 @@ function _mapDispatchToProps(dispatch: Function) {
* @returns {void}
*/
_displayChat() {
dispatch(toggleChat());
dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
},
/**

View File

@@ -5,7 +5,6 @@ import { TextInput, TouchableOpacity, View } from 'react-native';
import { translate } from '../../../base/i18n';
import { Icon, IconChatSend } from '../../../base/icons';
import { Platform } from '../../../base/react';
import styles from './styles';
@@ -136,7 +135,7 @@ class ChatInputBar extends Component<Props, State> {
*/
_onFocused(focused) {
return () => {
Platform.OS === 'android' && this.setState({
this.setState({
addPadding: focused
});
};

View File

@@ -1,5 +1,7 @@
// @flow
export const CHAT_VIEW_MODAL_ID = 'chatView';
/**
* The audio ID of the audio element for which the {@link playAudio} action is
* triggered when new chat message is received.

View File

@@ -1,5 +1,6 @@
// @flow
import { SET_ACTIVE_MODAL_ID } from '../base/modal';
import { ReducerRegistry } from '../base/redux';
import {
@@ -8,6 +9,7 @@ import {
SET_PRIVATE_MESSAGE_RECIPIENT,
TOGGLE_CHAT
} from './actionTypes';
import { CHAT_VIEW_MODAL_ID } from './constants';
const DEFAULT_STATE = {
isOpen: false,
@@ -56,6 +58,12 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
messages: []
};
case SET_ACTIVE_MODAL_ID:
if (action.activeModalId === CHAT_VIEW_MODAL_ID) {
return updateChatState(state);
}
break;
case SET_PRIVATE_MESSAGE_RECIPIENT:
return {
...state,
@@ -64,14 +72,24 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
};
case TOGGLE_CHAT:
return {
...state,
isOpen: !state.isOpen,
lastReadMessage: state.messages[
navigator.product === 'ReactNative' ? 0 : state.messages.length - 1],
privateMessageRecipient: state.isOpen ? undefined : state.privateMessageRecipient
};
return updateChatState(state);
}
return state;
});
/**
* Updates the chat status on opening the chat view.
*
* @param {Object} state - The Redux state of the feature.
* @returns {Object}
*/
function updateChatState(state) {
return {
...state,
isOpen: !state.isOpen,
lastReadMessage: state.messages[
navigator.product === 'ReactNative' ? 0 : state.messages.length - 1],
privateMessageRecipient: state.isOpen ? undefined : state.privateMessageRecipient
};
}

View File

@@ -8,6 +8,7 @@ import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
import { connect, disconnect } from '../../../base/connection';
import { translate } from '../../../base/i18n';
import { connect as reactReduxConnect } from '../../../base/redux';
import { getBackendSafeRoomName } from '../../../base/util';
import { Chat } from '../../../chat';
import { Filmstrip } from '../../../filmstrip';
import { CalleeInfoContainer } from '../../../invite';
@@ -78,6 +79,11 @@ type Props = AbstractProps & {
*/
_layoutClassName: string,
/**
* Name for this conference room.
*/
_roomName: string,
dispatch: Function,
t: Function
}
@@ -120,7 +126,7 @@ class Conference extends AbstractConference<Props, *> {
* @inheritdoc
*/
componentDidMount() {
document.title = `${APP.conference.roomName} | ${interfaceConfig.APP_NAME}`;
document.title = `${this.props._roomName} | ${interfaceConfig.APP_NAME}`;
this._start();
}
@@ -259,11 +265,13 @@ class Conference extends AbstractConference<Props, *> {
*/
function _mapStateToProps(state) {
const currentLayout = getCurrentLayout(state);
const roomName = getBackendSafeRoomName(state['features/base/conference'].room);
return {
...abstractMapStateToProps(state),
_iAmRecorder: state['features/base/config'].iAmRecorder,
_layoutClassName: LAYOUT_CLASSNAMES[currentLayout]
_layoutClassName: LAYOUT_CLASSNAMES[currentLayout],
_roomName: roomName
};
}

View File

@@ -74,6 +74,11 @@ type Props = {
*/
type State = {
/**
* The state of the audio screen share checkbox.
*/
screenShareAudio: boolean,
/**
* The currently higlighted DesktopCapturerSource.
*/
@@ -128,6 +133,7 @@ class DesktopPicker extends PureComponent<Props, State> {
_poller = null;
state = {
screenShareAudio: false,
selectedSource: {},
selectedTab: 0,
sources: {},
@@ -153,6 +159,7 @@ class DesktopPicker extends PureComponent<Props, State> {
// Bind event handlers so they are only bound once per instance.
this._onCloseModal = this._onCloseModal.bind(this);
this._onPreviewClick = this._onPreviewClick.bind(this);
this._onShareAudioChecked = this._onShareAudioChecked.bind(this);
this._onSubmit = this._onSubmit.bind(this);
this._onTabSelected = this._onTabSelected.bind(this);
this._updateSources = this._updateSources.bind(this);
@@ -241,7 +248,7 @@ class DesktopPicker extends PureComponent<Props, State> {
return selectedSource;
}
_onCloseModal: (?string, string) => void;
_onCloseModal: (?string, string, ?boolean) => void;
/**
* Dispatches an action to hide the DesktopPicker and invokes the passed in
@@ -251,10 +258,12 @@ class DesktopPicker extends PureComponent<Props, State> {
* the onSourceChoose callback.
* @param {string} type - The type of the DesktopCapturerSource to pass into
* the onSourceChoose callback.
* @param {boolean} screenShareAudio - Whether or not to add system audio to
* screen sharing session.
* @returns {void}
*/
_onCloseModal(id = '', type) {
this.props.onSourceChoose(id, type);
_onCloseModal(id = '', type, screenShareAudio = false) {
this.props.onSourceChoose(id, type, screenShareAudio);
this.props.dispatch(hideDialog());
}
@@ -285,9 +294,9 @@ class DesktopPicker extends PureComponent<Props, State> {
* @returns {void}
*/
_onSubmit() {
const { id, type } = this.state.selectedSource;
const { selectedSource: { id, type }, screenShareAudio } = this.state;
this._onCloseModal(id, type);
this._onCloseModal(id, type, screenShareAudio);
}
_onTabSelected: () => void;
@@ -306,12 +315,29 @@ class DesktopPicker extends PureComponent<Props, State> {
const { types, sources } = this.state;
this._selectedTabType = types[tabIndex];
// When we change tabs also reset the screenShareAudio state so we don't
// use the option from one tab when sharing from another.
this.setState({
screenShareAudio: false,
selectedSource: this._getSelectedSource(sources),
selectedTab: tabIndex
});
}
_onShareAudioChecked: (boolean) => void;
/**
* Set the screenSharingAudio state indicating whether or not to also share
* system audio.
*
* @param {boolean} checked - Share audio or not.
* @returns {void}
*/
_onShareAudioChecked(checked) {
this.setState({ screenShareAudio: checked });
}
/**
* Configures and renders the tabs for display.
*
@@ -328,7 +354,8 @@ class DesktopPicker extends PureComponent<Props, State> {
content: <DesktopPickerPane
key = { type }
onClick = { this._onPreviewClick }
onDoubleClick = { this._onCloseModal }
onDoubleClick = { this._onSubmit }
onShareAudioChecked = { this._onShareAudioChecked }
selectedSourceId = { selectedSource.id }
sources = { sources[type] }
type = { type } />,

View File

@@ -1,8 +1,12 @@
/* @flow */
import { Checkbox } from '@atlaskit/checkbox';
import Spinner from '@atlaskit/spinner';
import React, { Component } from 'react';
import { Platform } from '../../base/react';
import { translate } from '../../base/i18n';
import DesktopSourcePreview from './DesktopSourcePreview';
/**
@@ -20,6 +24,11 @@ type Props = {
*/
onDoubleClick: Function,
/**
* The handler to be invoked if the users checks the audio screen sharing checkbox.
*/
onShareAudioChecked: Function,
/**
* The id of the DesktopCapturerSource that is currently selected.
*/
@@ -33,7 +42,12 @@ type Props = {
/**
* The source type of the DesktopCapturerSources to display.
*/
type: string
type: string,
/**
* Used to obtain translations.
*/
t: Function
};
/**
@@ -42,6 +56,31 @@ type Props = {
* @extends Component
*/
class DesktopPickerPane extends Component<Props> {
/**
* Initializes a new DesktopPickerPane instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
super(props);
this._onShareAudioCheck = this._onShareAudioCheck.bind(this);
}
_onShareAudioCheck: (Object) => void;
/**
* Function to be called when the Checkbox is used.
*
* @param {boolean} checked - Checkbox status (checked or not).
* @returns {void}
*/
_onShareAudioCheck({ target: { checked } }) {
this.props.onShareAudioChecked(checked);
}
/**
* Implements React's {@link Component#render()}.
*
@@ -54,7 +93,8 @@ class DesktopPickerPane extends Component<Props> {
onDoubleClick,
selectedSourceId,
sources,
type
type,
t
} = this.props;
const classNames
@@ -77,12 +117,25 @@ class DesktopPickerPane extends Component<Props> {
</div>
);
let checkBox;
// Only display the share audio checkbox if we're on windows and on
// desktop sharing tab.
// App window and Mac OS screen sharing doesn't work with system audio.
if (type === 'screen' && Platform.OS === 'windows') {
checkBox = (<Checkbox
label = { t('dialog.screenSharingAudio') }
name = 'share-system-audio'
onChange = { this._onShareAudioCheck } />);
}
return (
<div className = { classNames }>
{ previews }
{ checkBox }
</div>
);
}
}
export default DesktopPickerPane;
export default translate(DesktopPickerPane);

View File

@@ -0,0 +1,54 @@
// @flow
import React, { PureComponent } from 'react';
import WebView from 'react-native-webview';
import { JitsiModal } from '../../base/modal';
import { connect } from '../../base/redux';
import { HELP_VIEW_MODAL_ID } from '../constants';
const DEFAULT_HELP_CENTRE_URL = 'https://web-cdn.jitsi.net/faq/meet-faq.html';
type Props = {
/**
* The URL to display in the Help Centre.
*/
_url: string
}
/**
* Implements a page that renders the help content for the app.
*/
class HelpView extends PureComponent<Props> {
/**
* Implements {@code PureComponent#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<JitsiModal
headerLabelKey = 'helpView.header'
modalId = { HELP_VIEW_MODAL_ID }>
<WebView source = {{ uri: this.props._url }} />
</JitsiModal>
);
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_url: state['features/base/config'].helpCentreURL || DEFAULT_HELP_CENTRE_URL
};
}
export default connect(_mapStateToProps)(HelpView);

View File

@@ -0,0 +1,3 @@
// @flow
export { default as HelpView } from './HelpView';

View File

@@ -0,0 +1,3 @@
// @flow
export const HELP_VIEW_MODAL_ID = 'helpView';

View File

@@ -0,0 +1,4 @@
// @flow
export * from './components';
export * from './constants';

View File

@@ -67,6 +67,11 @@ type Props = AbstractProps & {
type State = AbstractState & {
/**
* Boolean to show if an extra padding needs to be added to the bottom bar.
*/
bottomPadding: boolean,
/**
* State variable to keep track of the search field value.
*/
@@ -94,6 +99,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
defaultState = {
addToCallError: false,
addToCallInProgress: false,
bottomPadding: false,
fieldValue: '',
inviteItems: [],
searchInprogress: false,
@@ -194,16 +200,17 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
</View>
<TextInput
autoCorrect = { false }
autoFocus = { true }
clearButtonMode = 'always' // iOS only
autoFocus = { false }
onBlur = { this._onFocused(false) }
onChangeText = { this._onTypeQuery }
onFocus = { this._onFocused(true) }
placeholder = {
this.props.t(`inviteDialog.${placeholderKey}`)
}
ref = { this._setFieldRef }
style = { styles.searchField }
value = { this.state.fieldValue } />
{ this._renderAndroidClearButton() }
{ this._renderClearButton() }
</View>
{ Boolean(inviteItems.length) && <View style = { styles.invitedList }>
<FlatList
@@ -223,7 +230,11 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
renderItem = { this._renderItem } />
</View>
</SafeAreaView>
<SafeAreaView style = { [ styles.bottomBar, _headerStyles.headerOverlay ] }>
<SafeAreaView
style = { [
styles.bottomBar,
_headerStyles.headerOverlay,
this.state.bottomPadding ? styles.extraBarPadding : null ] }>
{ this._renderShareMeetingButton() }
</SafeAreaView>
</KeyboardAvoidingView>
@@ -317,6 +328,22 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
return false;
}
_onFocused: boolean => Function;
/**
* Constructs a callback to be used to update the padding of the field if necessary.
*
* @param {boolean} focused - True of the field is focused.
* @returns {Function}
*/
_onFocused(focused) {
return () => {
Platform.OS === 'android' && this.setState({
bottomPadding: focused
});
};
}
_onInvite: () => void
/**
@@ -433,14 +460,12 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
_query: (string) => Promise<Array<Object>>;
/**
* Renders a button to clear the text field on Android.
*
* NOTE: For the best platform experience we use the native solution on iOS.
* Renders a button to clear the text field.
*
* @returns {React#Element<*>}
*/
_renderAndroidClearButton() {
if (Platform.OS !== 'android' || !this.state.fieldValue.length) {
_renderClearButton() {
if (!this.state.fieldValue.length) {
return null;
}

View File

@@ -58,6 +58,13 @@ export default {
justifyContent: 'flex-start'
},
/**
* A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar).
*/
extraBarPadding: {
paddingBottom: 30
},
invitedList: {
padding: 3
},

View File

@@ -2,7 +2,7 @@
import { setFollowMe, setStartMutedPolicy } from '../base/conference';
import { openDialog } from '../base/dialog';
import { i18next } from '../base/i18n';
import { changeLanguage } from '../base/i18n';
import {
SET_AUDIO_SETTINGS_VISIBILITY,
@@ -89,7 +89,7 @@ export function submitMoreTab(newState: Object): Function {
}
if (newState.currentLanguage !== currentState.currentLanguage) {
i18next.changeLanguage(newState.currentLanguage);
dispatch(changeLanguage(newState.currentLanguage));
}
};
}

View File

@@ -41,6 +41,68 @@ class ToolbarButton extends AbstractToolbarButton<Props> {
tooltipPosition: 'top'
};
/**
* Initializes a new {@code ToolbarButton} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onKeyDown = this._onKeyDown.bind(this);
this._onKeyUp = this._onKeyUp.bind(this);
}
_onKeyDown: (Object) => void;
/**
* Handles 'Enter' key on the button to trigger onClick for accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyDown(event) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
} else if (event.key === ' ') {
// Space triggers button onKeyUp but we need to prevent default here
event.preventDefault();
}
}
_onKeyUp: (Object) => void;
/**
* Handles ' '(Space) key on the button to trigger onClick for
* accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyUp(event) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === ' ') {
event.preventDefault();
event.stopPropagation();
this.props.onClick();
}
}
/**
* Renders the button of this {@code ToolbarButton}.
*
@@ -53,8 +115,13 @@ class ToolbarButton extends AbstractToolbarButton<Props> {
return (
<div
aria-label = { this.props.accessibilityLabel }
aria-pressed = { this.props.toggled }
className = 'toolbox-button'
onClick = { this.props.onClick }>
onClick = { this.props.onClick }
onKeyDown = { this._onKeyDown }
onKeyUp = { this._onKeyUp }
role = 'button'
tabIndex = { 0 }>
{ this.props.tooltip
? <Tooltip
content = { this.props.tooltip }

View File

@@ -4,7 +4,8 @@ import React, { Component } from 'react';
import { SafeAreaView, ScrollView, Text } from 'react-native';
import { Avatar } from '../../base/avatar';
import { IconInfo, IconSettings } from '../../base/icons';
import { IconInfo, IconSettings, IconHelp } from '../../base/icons';
import { setActiveModalId } from '../../base/modal';
import {
getLocalParticipant,
getParticipantDisplayName
@@ -14,6 +15,7 @@ import {
SlidingView
} from '../../base/react';
import { connect } from '../../base/redux';
import { HELP_VIEW_MODAL_ID } from '../../help';
import { setSettingsViewVisible } from '../../settings';
import { setSideBarVisible } from '../actions';
@@ -25,11 +27,6 @@ import styles, { SIDEBAR_AVATAR_SIZE } from './styles';
*/
const PRIVACY_URL = 'https://jitsi.org/meet/privacy';
/**
* The URL at which the user may send feedback.
*/
const SEND_FEEDBACK_URL = 'mailto:support@jitsi.org';
/**
* The URL at which the terms (of service/use) are available to the user.
*/
@@ -72,6 +69,7 @@ class WelcomePageSideBar extends Component<Props> {
// Bind event handlers so they are only bound once per instance.
this._onHideSideBar = this._onHideSideBar.bind(this);
this._onOpenHelpPage = this._onOpenHelpPage.bind(this);
this._onOpenSettings = this._onOpenSettings.bind(this);
}
@@ -112,9 +110,9 @@ class WelcomePageSideBar extends Component<Props> {
label = 'welcomepage.privacy'
url = { PRIVACY_URL } />
<SideBarItem
icon = { IconInfo }
label = 'welcomepage.sendFeedback'
url = { SEND_FEEDBACK_URL } />
icon = { IconHelp }
label = 'welcomepage.getHelp'
onPress = { this._onOpenHelpPage } />
</ScrollView>
</SafeAreaView>
</SlidingView>
@@ -133,6 +131,20 @@ class WelcomePageSideBar extends Component<Props> {
this.props.dispatch(setSideBarVisible(false));
}
_onOpenHelpPage: () => void;
/**
* Shows the {@link HelpView}.
*
* @returns {void}
*/
_onOpenHelpPage() {
const { dispatch } = this.props;
dispatch(setSideBarVisible(false));
dispatch(setActiveModalId(HELP_VIEW_MODAL_ID));
}
_onOpenSettings: () => void;
/**

View File

@@ -29,6 +29,9 @@ if [ ! -f certbot-auto ] ; then
fi
CRON_FILE="/etc/cron.weekly/letsencrypt-renew"
if [ ! -d "/etc/cron.weekly" ] ; then
mkdir "/etc/cron.weekly"
fi
echo "#!/bin/bash" > $CRON_FILE
echo "/usr/local/sbin/certbot-auto renew >> /var/log/le-renew.log" >> $CRON_FILE