mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-16 15:49:08 +00:00
Compare commits
21 Commits
3960
...
acessibili
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
855eeafc79 | ||
|
|
06ae1861ee | ||
|
|
e0afd8fadb | ||
|
|
953f838a2a | ||
|
|
bf5f1f0168 | ||
|
|
cdf977ff3f | ||
|
|
ee1dc9dd8e | ||
|
|
7bec68e492 | ||
|
|
97fff02c15 | ||
|
|
b372b2ccf2 | ||
|
|
d00ead932b | ||
|
|
bb2b1b58ec | ||
|
|
ceeea7314c | ||
|
|
9d6a93119b | ||
|
|
08be68cda4 | ||
|
|
3a2081ffed | ||
|
|
57d14d9517 | ||
|
|
c5e693f14a | ||
|
|
a9da047d3a | ||
|
|
171b065db1 | ||
|
|
9da0b7fee3 |
1
debian/jitsi-meet-turnserver.links
vendored
1
debian/jitsi-meet-turnserver.links
vendored
@@ -1 +0,0 @@
|
||||
/usr/share/jitsi-meet-turnserver/jitsi-meet.conf /etc/nginx/modules-enabled/60-jitsi-meet.conf
|
||||
15
debian/jitsi-meet-turnserver.postinst
vendored
15
debian/jitsi-meet-turnserver.postinst
vendored
@@ -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
|
||||
|
||||
3
debian/jitsi-meet-turnserver.postrm
vendored
3
debian/jitsi-meet-turnserver.postrm
vendored
@@ -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
43
doc/README.md
Normal 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)
|
||||
@@ -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">
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 nell’immagine",
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
19
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<*>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -39,6 +39,7 @@ export default {
|
||||
statusBarContent: ColorPalette.white,
|
||||
text: ColorPalette.white
|
||||
},
|
||||
'Modal': {},
|
||||
'LargeVideo': {
|
||||
background: 'rgb(42, 58, 75)'
|
||||
},
|
||||
|
||||
6
react/features/base/i18n/actionTypes.js
Normal file
6
react/features/base/i18n/actionTypes.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* See {@link _setLanguage}.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const SET_LANGUAGE = 'SET_LANGUAGE';
|
||||
33
react/features/base/i18n/actions.js
Normal file
33
react/features/base/i18n/actions.js
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
69
react/features/base/i18n/middleware.js
Normal file
69
react/features/base/i18n/middleware.js
Normal 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);
|
||||
}
|
||||
30
react/features/base/i18n/reducer.js
Normal file
30
react/features/base/i18n/reducer.js
Normal 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;
|
||||
}
|
||||
});
|
||||
6
react/features/base/modal/actionTypes.js
Normal file
6
react/features/base/modal/actionTypes.js
Normal 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';
|
||||
19
react/features/base/modal/actions.js
Normal file
19
react/features/base/modal/actions.js
Normal 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
|
||||
};
|
||||
}
|
||||
151
react/features/base/modal/components/JitsiModal.js
Normal file
151
react/features/base/modal/components/JitsiModal.js
Normal 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);
|
||||
3
react/features/base/modal/components/index.native.js
Normal file
3
react/features/base/modal/components/index.native.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export { default as JitsiModal } from './JitsiModal';
|
||||
6
react/features/base/modal/components/index.web.js
Normal file
6
react/features/base/modal/components/index.web.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
export const JitsiModal = Component;
|
||||
|
||||
15
react/features/base/modal/components/styles.js
Normal file
15
react/features/base/modal/components/styles.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// @flow
|
||||
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../color-scheme';
|
||||
|
||||
export default {
|
||||
safeArea: {
|
||||
flex: 1
|
||||
}
|
||||
};
|
||||
|
||||
ColorSchemeRegistry.register('Modal', {
|
||||
page: {
|
||||
backgroundColor: schemeColor('background')
|
||||
}
|
||||
});
|
||||
7
react/features/base/modal/index.js
Normal file
7
react/features/base/modal/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import './reducer';
|
||||
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
17
react/features/base/modal/reducer.js
Normal file
17
react/features/base/modal/reducer.js
Normal 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;
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 } />,
|
||||
|
||||
@@ -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);
|
||||
|
||||
54
react/features/help/components/HelpView.js
Normal file
54
react/features/help/components/HelpView.js
Normal 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);
|
||||
3
react/features/help/components/index.js
Normal file
3
react/features/help/components/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export { default as HelpView } from './HelpView';
|
||||
3
react/features/help/constants.js
Normal file
3
react/features/help/constants.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export const HELP_VIEW_MODAL_ID = 'helpView';
|
||||
4
react/features/help/index.js
Normal file
4
react/features/help/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export * from './components';
|
||||
export * from './constants';
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user