mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-25 13:57:50 +00:00
Compare commits
51 Commits
3381
...
android-sd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9445cf99fd | ||
|
|
96b226de24 | ||
|
|
5101f69e4e | ||
|
|
61b66e0edf | ||
|
|
700051f809 | ||
|
|
d16e10baec | ||
|
|
11e5c14f83 | ||
|
|
ded58d77d1 | ||
|
|
8642c372c4 | ||
|
|
edbf591059 | ||
|
|
ded4291d6a | ||
|
|
a14fead0f3 | ||
|
|
466e1e3eb8 | ||
|
|
8a90f0dab1 | ||
|
|
d7e0aa3f61 | ||
|
|
37b343a797 | ||
|
|
149485905c | ||
|
|
7f1df5629e | ||
|
|
f42d0411b1 | ||
|
|
8d1d573266 | ||
|
|
d86b60ea72 | ||
|
|
dfe5fbb702 | ||
|
|
09f881c0f5 | ||
|
|
f1546008f9 | ||
|
|
d8df7fde84 | ||
|
|
1c809eb428 | ||
|
|
e94edcd4ae | ||
|
|
b48651396f | ||
|
|
e2044074ad | ||
|
|
f060ac9db1 | ||
|
|
5a53d7f32a | ||
|
|
4eec13da1c | ||
|
|
cb8282dfe5 | ||
|
|
5cd0b1a9be | ||
|
|
504fadaf71 | ||
|
|
7187e540a8 | ||
|
|
34dffbfc5e | ||
|
|
a9637f93c3 | ||
|
|
0e8b0a9c5c | ||
|
|
e66b596a0d | ||
|
|
6f320f463d | ||
|
|
02955ab57c | ||
|
|
a9d76a2577 | ||
|
|
dcf31baf3a | ||
|
|
1e346f10ab | ||
|
|
a114d55fac | ||
|
|
afde717ca4 | ||
|
|
fb5a45f714 | ||
|
|
f5ac18da18 | ||
|
|
103ae363f6 | ||
|
|
9bde673397 |
@@ -69,12 +69,12 @@ if [[ $MVN_HTTP == 0 ]]; then
|
||||
pushd ${MVN_REPO_PATH}
|
||||
if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ]]; then
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies"
|
||||
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
|
||||
fi
|
||||
popd
|
||||
|
||||
# Tag the release
|
||||
git tag -a android-sdk-${SDK_VERSION}
|
||||
git tag android-sdk-${SDK_VERSION}
|
||||
fi
|
||||
|
||||
# Done!
|
||||
|
||||
@@ -53,6 +53,7 @@ dependencies {
|
||||
|
||||
implementation project(':react-native-background-timer')
|
||||
implementation project(':react-native-calendar-events')
|
||||
implementation project(':react-native-community-async-storage')
|
||||
implementation(project(':react-native-fast-image')) {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
@@ -63,7 +64,6 @@ dependencies {
|
||||
implementation project(':react-native-vector-icons')
|
||||
implementation project(':react-native-webrtc')
|
||||
implementation project(':react-native-webview')
|
||||
implementation project(':@react-native-community_async-storage')
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
@@ -207,8 +207,7 @@ publishing {
|
||||
def groupId = it.moduleGroup
|
||||
def artifactId = it.moduleName
|
||||
|
||||
if ((artifactId.startsWith('react-native-') || artifactId.startsWith('@react-native-community'))
|
||||
&& groupId.equals('jitsi-meet')) {
|
||||
if (artifactId.startsWith('react-native-') && groupId.equals('jitsi-meet')) {
|
||||
groupId = rootProject.ext.moduleGroupId
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ include ':react-native-background-timer'
|
||||
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
include ':react-native-community-async-storage'
|
||||
project(':react-native-community-async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android')
|
||||
include ':react-native-fast-image'
|
||||
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android')
|
||||
include ':react-native-google-signin'
|
||||
@@ -23,5 +25,3 @@ include ':react-native-webrtc'
|
||||
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
|
||||
include ':react-native-webview'
|
||||
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
|
||||
include ':@react-native-community_async-storage'
|
||||
project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android')
|
||||
|
||||
@@ -2402,17 +2402,26 @@ export default {
|
||||
// Let's handle unknown/non-preferred devices
|
||||
const newAvailDevices
|
||||
= APP.store.getState()['features/base/devices'].availableDevices;
|
||||
let newAudioDevices = [];
|
||||
let oldAudioDevices = [];
|
||||
|
||||
if (typeof newDevices.audiooutput === 'undefined') {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.audioOutput, oldDevices.audioOutput));
|
||||
newAudioDevices = newAvailDevices.audioOutput;
|
||||
oldAudioDevices = oldDevices.audioOutput;
|
||||
}
|
||||
|
||||
if (!requestedInput.audio) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.audioInput, oldDevices.audioInput));
|
||||
newAudioDevices = newAudioDevices.concat(newAvailDevices.audioInput);
|
||||
oldAudioDevices = oldAudioDevices.concat(oldDevices.audioInput);
|
||||
}
|
||||
|
||||
// check for audio
|
||||
if (newAudioDevices.length > 0) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
|
||||
}
|
||||
|
||||
// check for video
|
||||
if (!requestedInput.video) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
|
||||
|
||||
@@ -266,6 +266,13 @@ var config = {
|
||||
// Whether or not some features are checked based on token.
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// Enable lock room for all moderators, even when userRolesBasedOnToken is enabled and participants are guests.
|
||||
// lockRoomGuestEnabled: false,
|
||||
|
||||
// When enabled the password used for locking a room is restricted to up to the number of digits specified
|
||||
// roomPasswordNumberOfDigits: 10,
|
||||
// default: roomPasswordNumberOfDigits: false,
|
||||
|
||||
// Message to show the users. Example: 'The service will be down for
|
||||
// maintenance at 01:00 AM GMT,
|
||||
// noticeMessage: '',
|
||||
|
||||
@@ -130,11 +130,9 @@
|
||||
border-radius:0;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
font-size: 10pt;
|
||||
font-size: 15px;
|
||||
line-height: 30px;
|
||||
padding: 5px;
|
||||
max-height:150px;
|
||||
min-height:35px;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
@@ -162,32 +160,21 @@
|
||||
}
|
||||
|
||||
.display-name {
|
||||
float: left;
|
||||
padding-left: 5px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 95%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
float: right;
|
||||
padding-right: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
padding-top: 20px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
margin-top: 3px;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
margin-top: 3px;
|
||||
max-width: 100%;
|
||||
padding-bottom: 3px;
|
||||
position: relative;
|
||||
|
||||
@@ -290,3 +277,49 @@
|
||||
#usermsg::-webkit-scrollbar-track-piece {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
|
||||
.chat-message-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.local {
|
||||
align-items: flex-end;
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.chatmessage {
|
||||
border-radius: 0px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chatmessage-wrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
display: inline-block;
|
||||
margin-top: 3px;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,4 +162,10 @@ body.welcome-page {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-watermark {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
12
doc/api.md
12
doc/api.md
@@ -222,6 +222,11 @@ api.executeCommand('toggleChat');
|
||||
api.executeCommand('toggleShareScreen');
|
||||
```
|
||||
|
||||
* **toggleTileView** - Enter / exit tile view layout mode. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleTileView');
|
||||
```
|
||||
|
||||
* **hangup** - Hangups the call. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('hangup');
|
||||
@@ -296,6 +301,13 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **tileViewChanged** - event notifications about tile view layout mode being entered or exited. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
enabled: boolean, // whether tile view is not displayed or not
|
||||
}
|
||||
```
|
||||
|
||||
* **incomingMessage** - Event notifications about incoming
|
||||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
|
||||
@@ -5,7 +5,7 @@ signed Android build for that, that can be a debug self-signed build too, just
|
||||
retrieve the signing hash. The key hash of an already signed ap can be obtained
|
||||
as follows (on macOS): ```keytool -list -printcert -jarfile the-app.apk```
|
||||
- Place the generated ```google-services.json``` file in ```android/app```
|
||||
for Android and the ```GoogleService-Info.plist``` into ```ios/app/src``` for
|
||||
for Android and the ```GoogleService-Info.plist``` into ```ios/app``` for
|
||||
iOS (you can stop at that step, no need for the driver and the code changes they
|
||||
suggest in the wizard).
|
||||
- You may want to exclude these files in YOUR GIT config (do not exclude them in
|
||||
|
||||
@@ -167,7 +167,13 @@ var interfaceConfig = {
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
RECENT_LIST_ENABLED: true
|
||||
RECENT_LIST_ENABLED: true,
|
||||
|
||||
/**
|
||||
* A UX mode where the last screen share participant is automatically
|
||||
* pinned. Note: this mode is experimental and subject to breakage.
|
||||
*/
|
||||
AUTO_PIN_LATEST_SCREEN_SHARE: true
|
||||
|
||||
/**
|
||||
* How many columns the tile view can expand to. The respected range is
|
||||
@@ -195,12 +201,6 @@ var interfaceConfig = {
|
||||
*/
|
||||
// ANDROID_APP_PACKAGE: 'org.jitsi.meet',
|
||||
|
||||
/**
|
||||
* A UX mode where the last screen share participant is automatically
|
||||
* pinned. Note: this mode is experimental and subject to breakage.
|
||||
*/
|
||||
// AUTO_PIN_LATEST_SCREEN_SHARE: false,
|
||||
|
||||
/**
|
||||
* Override the behavior of some notifications to remain displayed until
|
||||
* explicitly dismissed through a user action. The value is how long, in
|
||||
|
||||
@@ -84,8 +84,8 @@ PODS:
|
||||
- nanopb/decode (0.3.901)
|
||||
- nanopb/encode (0.3.901)
|
||||
- ObjectiveDropboxOfficial (3.9.4)
|
||||
- React (0.59.5):
|
||||
- React/Core (= 0.59.5)
|
||||
- React (0.59.8):
|
||||
- React/Core (= 0.59.8)
|
||||
- react-native-background-timer (2.1.1):
|
||||
- React
|
||||
- react-native-calendar-events (1.6.4):
|
||||
@@ -101,50 +101,50 @@ PODS:
|
||||
- React
|
||||
- react-native-webview (5.8.1):
|
||||
- React
|
||||
- React/Core (0.59.5):
|
||||
- yoga (= 0.59.5.React)
|
||||
- React/CxxBridge (0.59.5):
|
||||
- React/Core (0.59.8):
|
||||
- yoga (= 0.59.8.React)
|
||||
- React/CxxBridge (0.59.8):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React/Core
|
||||
- React/cxxreact
|
||||
- React/jsiexecutor
|
||||
- React/cxxreact (0.59.5):
|
||||
- React/cxxreact (0.59.8):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- glog
|
||||
- React/jsinspector
|
||||
- React/DevSupport (0.59.5):
|
||||
- React/DevSupport (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTWebSocket
|
||||
- React/fishhook (0.59.5)
|
||||
- React/jsi (0.59.5):
|
||||
- React/fishhook (0.59.8)
|
||||
- React/jsi (0.59.8):
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- glog
|
||||
- React/jsiexecutor (0.59.5):
|
||||
- React/jsiexecutor (0.59.8):
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- glog
|
||||
- React/cxxreact
|
||||
- React/jsi
|
||||
- React/jsinspector (0.59.5)
|
||||
- React/RCTActionSheet (0.59.5):
|
||||
- React/jsinspector (0.59.8)
|
||||
- React/RCTActionSheet (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTAnimation (0.59.5):
|
||||
- React/RCTAnimation (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTBlob (0.59.5):
|
||||
- React/RCTBlob (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTImage (0.59.5):
|
||||
- React/RCTImage (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTNetwork
|
||||
- React/RCTLinkingIOS (0.59.5):
|
||||
- React/RCTLinkingIOS (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTNetwork (0.59.5):
|
||||
- React/RCTNetwork (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTText (0.59.5):
|
||||
- React/RCTText (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTWebSocket (0.59.5):
|
||||
- React/RCTWebSocket (0.59.8):
|
||||
- React/Core
|
||||
- React/fishhook
|
||||
- React/RCTBlob
|
||||
@@ -166,7 +166,7 @@ PODS:
|
||||
- SDWebImage/GIF (4.4.6):
|
||||
- FLAnimatedImage (~> 1.0)
|
||||
- SDWebImage/Core
|
||||
- yoga (0.59.5.React)
|
||||
- yoga (0.59.8.React)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Amplitude-iOS (~> 4.0.4)
|
||||
@@ -283,7 +283,7 @@ SPEC CHECKSUMS:
|
||||
GTMSessionFetcher: 32aeca0aa144acea523e1c8e053089dec2cb98ca
|
||||
nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48
|
||||
ObjectiveDropboxOfficial: a5afefc83f6467c42c45f2253f583f2ad1ffc701
|
||||
React: 90adac468c7b72bf1fa6c64bf230650f851a8388
|
||||
React: 76e6aa2b87d05eb6cccb6926d72685c9a07df152
|
||||
react-native-background-timer: 0d34748e53a972507c66963490c775321a88f6f2
|
||||
react-native-calendar-events: ee9573e355711ac679e071be70789542431f4ce3
|
||||
react-native-fast-image: 47487b71169aea34868e7b38bf870b6b3f2157c5
|
||||
@@ -296,7 +296,7 @@ SPEC CHECKSUMS:
|
||||
RNVectorIcons: d819334932bcda3332deb3d2c8ea4d069e0b98f9
|
||||
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
|
||||
SDWebImage: 3f3f0c02f09798048c47a5ed0a13f17b063572d8
|
||||
yoga: 2e571f113e8cbeb0eb752aeebc86c1bfe7a8200c
|
||||
yoga: 92b2102c3d373d1a790db4ab761d2b0ffc634f64
|
||||
|
||||
PODFILE CHECKSUM: b55338cc43312051ed83f8d9c6aadbd8c9402e6a
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ Leaves the currently active conference.
|
||||
|
||||
#### Universal / deep linking
|
||||
|
||||
In order to support Universal / deep linking, `JitsiMeetView` offers 2 class
|
||||
In order to support Universal / deep linking, `JitsiMeet` offers 2 class
|
||||
methods that you app's delegate should call in order for the app to follow those
|
||||
links.
|
||||
|
||||
@@ -104,7 +104,7 @@ is useful when the host application uses other SDKs which also use linking.
|
||||
continueUserActivity:(NSUserActivity *)userActivity
|
||||
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
|
||||
{
|
||||
return [JitsiMeetView application:application
|
||||
return [[JitsiMeet sharedInstance] application:application
|
||||
continueUserActivity:userActivity
|
||||
restorationHandler:restorationHandler];
|
||||
}
|
||||
@@ -117,7 +117,7 @@ And also one of the following:
|
||||
- (BOOL)application:(UIApplication *)app
|
||||
openURL:(NSURL *)url
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
||||
return [JitsiMeetView application:app
|
||||
return [[JitsiMeet sharedInstance] application:app
|
||||
openURL:url
|
||||
options: options];
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.7 KiB |
15
ios/scripts/bitcode.sh
Executable file
15
ios/scripts/bitcode.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script will download a bitcode build of the WebRTC framework, if needed.
|
||||
|
||||
if [[ ! "$CONFIGURATION" = "Debug" ]]; then
|
||||
RN_WEBRTC="$SRCROOT/../../node_modules/react-native-webrtc"
|
||||
|
||||
if otool -arch arm64 -l $RN_WEBRTC/ios/WebRTC.framework/WebRTC | grep -q LLVM; then
|
||||
echo "WebRTC framework has bitcode"
|
||||
else
|
||||
echo "WebRTC framework has NO bitcode"
|
||||
$RN_WEBRTC/tools/downloadBitcode.sh
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -5,7 +5,8 @@ set -e -u
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
PROJECT_REPO=$(realpath ${THIS_DIR}/../..)
|
||||
RELEASE_REPO=$(realpath ${THIS_DIR}/../../../jitsi-meet-ios-sdk-releases)
|
||||
SDK_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${THIS_DIR}/../sdk/src/Info.plist)
|
||||
DEFAULT_SDK_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${THIS_DIR}/../sdk/src/Info.plist)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
|
||||
|
||||
echo "Releasing Jitsi Meet SDK ${SDK_VERSION}"
|
||||
@@ -24,7 +25,7 @@ popd
|
||||
pushd ${PROJECT_REPO}
|
||||
rm -rf ios/sdk/JitsiMeet.framework
|
||||
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release archive
|
||||
git tag -a ios-sdk-${SDK_VERSION}
|
||||
git tag ios-sdk-${SDK_VERSION}
|
||||
popd
|
||||
|
||||
pushd ${RELEASE_REPO}
|
||||
@@ -33,6 +34,10 @@ pushd ${RELEASE_REPO}
|
||||
cp -r ${PROJECT_REPO}/ios/sdk/JitsiMeet.framework Frameworks/
|
||||
cp -r ${PROJECT_REPO}/node_modules/react-native-webrtc/ios/WebRTC.framework Frameworks/
|
||||
|
||||
# Strip bitcode
|
||||
xcrun bitcode_strip -r Frameworks/JitsiMeet.framework/JitsiMeet -o Frameworks/JitsiMeet.framework/JitsiMeet
|
||||
xcrun bitcode_strip -r Frameworks/WebRTC.framework/WebRTC -o Frameworks/WebRTC.framework/WebRTC
|
||||
|
||||
# Add all files to git
|
||||
git add -A .
|
||||
git commit -m "${SDK_VERSION}"
|
||||
|
||||
@@ -270,6 +270,7 @@
|
||||
buildConfigurationList = 0BD906ED1EC0C00300C8C18E /* Build configuration list for PBXNativeTarget "JitsiMeet" */;
|
||||
buildPhases = (
|
||||
26796D8589142D80C8AFDA51 /* [CP] Check Pods Manifest.lock */,
|
||||
DE3D81D6228B50FB00A6C149 /* Bitcode */,
|
||||
0BD906E01EC0C00300C8C18E /* Sources */,
|
||||
0BD906E11EC0C00300C8C18E /* Frameworks */,
|
||||
0BD906E21EC0C00300C8C18E /* Headers */,
|
||||
@@ -450,6 +451,24 @@
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DE3D81D6228B50FB00A6C149 /* Bitcode */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = Bitcode;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "../scripts/bitcode.sh\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import <React/RCTUtils.h>
|
||||
#import <WebRTC/WebRTC.h>
|
||||
|
||||
#import <JitsiMeet/JitsiMeet-Swift.h>
|
||||
|
||||
@@ -307,21 +308,35 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
|
||||
startedConnectingAt:nil];
|
||||
}
|
||||
|
||||
// The following just help with debugging:
|
||||
#ifdef DEBUG
|
||||
|
||||
- (void) providerDidActivateAudioSessionWithSession:(AVAudioSession *)session {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession:]");
|
||||
#endif
|
||||
[[RTCAudioSession sharedInstance] audioSessionDidActivate:session];
|
||||
}
|
||||
|
||||
- (void) providerDidDeactivateAudioSessionWithSession:(AVAudioSession *)session {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession:]");
|
||||
#endif
|
||||
[[RTCAudioSession sharedInstance] audioSessionDidDeactivate:session];
|
||||
}
|
||||
|
||||
- (void) providerTimedOutPerformingActionWithAction:(CXAction *)action {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction:]");
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// The bridge might already be invalidated by the time a CallKit event is processed,
|
||||
// just ignore it and don't emit it.
|
||||
- (void)sendEventWithName:(NSString *)name body:(id)body {
|
||||
if (!self.bridge) {
|
||||
return;
|
||||
}
|
||||
|
||||
[super sendEventWithName:name body:body];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -21,6 +21,7 @@ import Foundation
|
||||
internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
|
||||
private let listeners = NSMutableArray()
|
||||
private var pendingMuteActions = Set<UUID>()
|
||||
|
||||
internal override init() {}
|
||||
|
||||
@@ -36,6 +37,12 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
// MARK: - Add mute action
|
||||
|
||||
func addMuteAction(_ actionUUID: UUID) {
|
||||
pendingMuteActions.insert(actionUUID)
|
||||
}
|
||||
|
||||
// MARK: - CXProviderDelegate
|
||||
|
||||
func providerDidReset(_ provider: CXProvider) {
|
||||
@@ -43,6 +50,7 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
let listener = $0 as! JMCallKitListener
|
||||
listener.providerDidReset?()
|
||||
}
|
||||
pendingMuteActions.removeAll()
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
|
||||
@@ -64,9 +72,20 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
|
||||
listeners.forEach {
|
||||
let listener = $0 as! JMCallKitListener
|
||||
listener.performSetMutedCall?(UUID: action.callUUID, isMuted: action.isMuted)
|
||||
let uuid = pendingMuteActions.remove(action.uuid)
|
||||
|
||||
// Avoid mute actions ping-pong: if the mute action was caused by
|
||||
// the JS side (we requested a transaction) don't call the delegate
|
||||
// method. If it was called by the provder itself (when the user presses
|
||||
// the mute button in the CallKit view) then call the delegate method.
|
||||
//
|
||||
// NOTE: don't try to be clever and remove this. Been there, done that.
|
||||
// Won't work.
|
||||
if (uuid == nil) {
|
||||
listeners.forEach {
|
||||
let listener = $0 as! JMCallKitListener
|
||||
listener.performSetMutedCall?(UUID: action.callUUID, isMuted: action.isMuted)
|
||||
}
|
||||
}
|
||||
|
||||
action.fulfill()
|
||||
|
||||
@@ -160,6 +160,14 @@ import Foundation
|
||||
completion: @escaping (Error?) -> Swift.Void) {
|
||||
guard enabled else { return }
|
||||
|
||||
// XXX keep track of muted actions to avoid "ping-pong"ing. See
|
||||
// JMCallKitEmitter for details on the CXSetMutedCallAction handling.
|
||||
for action in transaction.actions {
|
||||
if (action as? CXSetMutedCallAction) != nil {
|
||||
emitter.addMuteAction(action.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
callController.request(transaction, completion: completion)
|
||||
}
|
||||
|
||||
@@ -187,3 +195,4 @@ import Foundation
|
||||
return update
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,4 +152,12 @@ xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchi
|
||||
|
||||
echo "Will try deploy the .ipa to: ${IPA_DEPLOY_LOCATION}"
|
||||
|
||||
scp -i ${CERT_DIR}/id_rsa -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
ssh-add ${CERT_DIR}/id_rsa
|
||||
|
||||
if [ ! -z ${SCP_PROXY_HOST} ];
|
||||
then
|
||||
scp -o ProxyCommand="ssh -t -A -l %r ${SCP_PROXY_HOST} -o \"StrictHostKeyChecking no\" -o \"BatchMode yes\" -W %h:%p" -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
else
|
||||
scp -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"en": "Английски",
|
||||
"af": "",
|
||||
"az": "",
|
||||
"bg": "Български",
|
||||
"cs": "",
|
||||
"de": "Немски",
|
||||
"el": "",
|
||||
"eo": "Есперанто",
|
||||
"es": "Испански",
|
||||
"fr": "Френски",
|
||||
"hy": "Арменски",
|
||||
"it": "Италиански",
|
||||
"ja": "",
|
||||
"ko": "",
|
||||
"nb": "Норвежки букмол",
|
||||
"oc": "Окситански",
|
||||
"pl": "Полски",
|
||||
"ptBR": "Португалски (Бразилия)",
|
||||
@@ -14,7 +22,6 @@
|
||||
"sl": "Словенски",
|
||||
"sv": "Шведски",
|
||||
"tr": "Турски",
|
||||
"zhCN": "Китайски (Китай)",
|
||||
"nb": "Норвежки букмол",
|
||||
"eo": "Есперанто"
|
||||
"vi": "",
|
||||
"zhCN": "Китайски (Китай)"
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
{
|
||||
"en": "Englisch",
|
||||
"az": "",
|
||||
"bg": "Bulgarisch",
|
||||
"cs": "",
|
||||
"de": "Deutsch",
|
||||
"el": "",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spanisch",
|
||||
"fr": "Französisch",
|
||||
"hy": "Armenisch",
|
||||
"it": "Italienisch",
|
||||
"ja": "",
|
||||
"ko": "",
|
||||
"nb": "Norwegisch (Bokmal)",
|
||||
"oc": "Okzitanisch",
|
||||
"pl": "Polnisch",
|
||||
"ptBR": "Portugiesisch (Brasilien)",
|
||||
@@ -14,7 +21,6 @@
|
||||
"sl": "Slowenisch",
|
||||
"sv": "Schwedisch",
|
||||
"tr": "Türkisch",
|
||||
"zhCN": "Chinesisch (China)",
|
||||
"nb": "Norwegisch (Bokmal)",
|
||||
"eo": "Esperanto"
|
||||
"vi": "",
|
||||
"zhCN": "Chinesisch (China)"
|
||||
}
|
||||
27
lang/languages-hr.json
Normal file
27
lang/languages-hr.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"en": "",
|
||||
"af": "",
|
||||
"az": "",
|
||||
"bg": "",
|
||||
"cs": "",
|
||||
"de": "",
|
||||
"el": "",
|
||||
"eo": "",
|
||||
"es": "",
|
||||
"fr": "",
|
||||
"hy": "",
|
||||
"it": "",
|
||||
"ja": "",
|
||||
"ko": "",
|
||||
"nb": "",
|
||||
"oc": "",
|
||||
"pl": "",
|
||||
"ptBR": "",
|
||||
"ru": "",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "",
|
||||
"tr": "",
|
||||
"vi": "",
|
||||
"zhCN": ""
|
||||
}
|
||||
1096
lang/main-bg.json
1096
lang/main-bg.json
File diff suppressed because it is too large
Load Diff
@@ -35,31 +35,43 @@
|
||||
"raiseHand": "Hand erheben",
|
||||
"pushToTalk": "Drücken um zu sprechen",
|
||||
"toggleScreensharing": "Zwischen Kamera und Bildschirmfreigabe wechseln",
|
||||
"toggleFilmstrip": "Videos anzeigen oder verbergen",
|
||||
"toggleShortcuts": "Hilfe-Menu anzeigen oder verdecken",
|
||||
"toggleFilmstrip": "",
|
||||
"toggleShortcuts": "",
|
||||
"focusLocal": "Lokales Video fokussieren",
|
||||
"focusRemote": "Auf das Video eines anderen Teilnehmers fokussieren",
|
||||
"toggleChat": "Chat öffnen oder schliessen",
|
||||
"mute": "Stummschaltung aktivieren oder deaktivieren",
|
||||
"fullScreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||
"videoMute": "Kamera starten oder stoppen",
|
||||
"showSpeakerStats": "Statistiken für Sprecher anzeigen"
|
||||
"showSpeakerStats": "Statistiken für Sprecher anzeigen",
|
||||
"localRecording": ""
|
||||
},
|
||||
"\u0005keyboardShortcuts": {},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "",
|
||||
"roomname": "Konferenzname eingeben"
|
||||
},
|
||||
"appDescription": "Auf geht's! Beginne eine Videokonferenz mit dem ganzen Team. Oder eigentlich, lade alle ein die du kennst. __app__ ist eine vollständig verschlüsselte, aus 100% Open-Source-Software bestehende Videokonferenzlösung die du den ganzen Tag kostenlos verwenden kannst — ohne Registrierung.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Sprache",
|
||||
"video": "Video"
|
||||
},
|
||||
"calendar": "Kalender",
|
||||
"connectCalendarText": "",
|
||||
"connectCalendarButton": "",
|
||||
"enterRoomTitle": "",
|
||||
"go": "Los",
|
||||
"join": "Beitreten",
|
||||
"privacy": "Privatsphäre",
|
||||
"recentList": "",
|
||||
"recentListDelete": "",
|
||||
"recentListEmpty": "",
|
||||
"roomname": "Konferenzname eingeben",
|
||||
"roomnameHint": "Name oder URL der Konferenz der Sie beitreten möchten. Sie können einen Namen erfinden, er muss nur den anderen Teilnehmern übermittelt werden damit sie der gleichen Konferenz beitreten.",
|
||||
"sendFeedback": "Senden Sie uns Ihr Feedback",
|
||||
"terms": "Bedingungen",
|
||||
"title": "Sichere, flexible und vollständig freie Videokonferenzen"
|
||||
"title": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
@@ -71,8 +83,41 @@
|
||||
"rejoinKeyTitle": "Erneut teilnehmen"
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "",
|
||||
"audioRoute": "",
|
||||
"callQuality": "Qualitätseinstellungen",
|
||||
"chat": "",
|
||||
"cc": "",
|
||||
"document": "Geteiltes Dokument schliessen",
|
||||
"feedback": "Feedback hinterlasen",
|
||||
"fullScreen": "",
|
||||
"hangup": "",
|
||||
"invite": "Teilnehmer einladen",
|
||||
"localRecording": "",
|
||||
"lockRoom": "",
|
||||
"moreActions": "",
|
||||
"moreActionsMenu": "",
|
||||
"mute": "",
|
||||
"pip": "",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "",
|
||||
"recording": "",
|
||||
"Settings": "",
|
||||
"sharedvideo": "",
|
||||
"shareRoom": "",
|
||||
"shareYourScreen": "",
|
||||
"shortcuts": "",
|
||||
"speakerStats": "",
|
||||
"toggleCamera": "",
|
||||
"tileView": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"addPeople": "Teilnehmer zur Konferenz hinzufügen",
|
||||
"audioonly": "Nur-Audio-Modus aktivieren/deaktivieren (spart Bandbreite)",
|
||||
"audioOnlyOn": "Nur-Audio-Modus aktivieren/deaktivieren (spart Bandbreite)",
|
||||
"audioOnlyOff": "",
|
||||
"audioRoute": "",
|
||||
"callQuality": "Qualitätseinstellungen",
|
||||
"enterFullScreen": "Vollbildmodus",
|
||||
"exitFullScreen": "Vollbildmodus verlassen",
|
||||
@@ -86,8 +131,8 @@
|
||||
"etherpad": "Geteiltes Dokument öffnen / schliessen",
|
||||
"documentOpen": "Geteiltes Dokument öffnen",
|
||||
"documentClose": "Geteiltes Dokument schliessen",
|
||||
"shareRoom": "",
|
||||
"sharedvideo": "YouTube-Video teilen",
|
||||
"sharescreen": "Bildschirmfreigabe",
|
||||
"stopSharedVideo": "YouTube Video stoppen",
|
||||
"fullscreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||
"sip": "SIP Nummer anrufen",
|
||||
@@ -96,25 +141,40 @@
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"sharedVideoMutedPopup": "Das freigegebene Video wurde stumm geschaltet um mit den anderen Teilnehmern zu sprechen.",
|
||||
"toggleCamera": "",
|
||||
"micMutedPopup": "Das Mikrofon wurde stumm geschaltet um das freigegebene Video geniessen zu können.",
|
||||
"talkWhileMutedPopup": "Versuchen sie zu sprechen? Ihr Mikrofon ist stummgeschaltet.",
|
||||
"unableToUnmutePopup": "Die Stummschaltung kann nicht aufgehoben werden während das geteilte Video abgespielt wird.",
|
||||
"cameraDisabled": "Keine Kamera verfügbar",
|
||||
"micDisabled": "Kein Mikrofon verfügbar",
|
||||
"filmstrip": "Videos anzeigen / verbergen",
|
||||
"pip": "",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "Hand erheben",
|
||||
"shortcuts": "Tastenkürzel anzeigen",
|
||||
"speakerStats": "Sprecher-Statistiken"
|
||||
"speakerStats": "Sprecher-Statistiken",
|
||||
"tileViewToggle": "",
|
||||
"invite": "Teilnehmer einladen"
|
||||
},
|
||||
"\u0005toolbar": {
|
||||
"accessibilityLabel": {}
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Name eingeben",
|
||||
"popover": "Name"
|
||||
},
|
||||
"error": "",
|
||||
"messagebox": "Text eingeben..."
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "",
|
||||
"disconnect": "Getrennt",
|
||||
"microsoftSignIn": "",
|
||||
"signedIn": "",
|
||||
"title": "Kalender"
|
||||
},
|
||||
"title": "Einstellungen",
|
||||
"update": "Aktualisieren",
|
||||
"name": "Name",
|
||||
@@ -124,11 +184,18 @@
|
||||
"selectMic": "Mikrofon",
|
||||
"selectAudioOutput": "Audioausgabe",
|
||||
"followMe": "Follow-me für alle Teilnehmer",
|
||||
"language": "",
|
||||
"loggedIn": "",
|
||||
"noDevice": "Kein",
|
||||
"cameraAndMic": "Kamera und Mikrofon",
|
||||
"moderator": "MODERATOR",
|
||||
"moderator": "Moderator",
|
||||
"more": "",
|
||||
"password": "PASSWORT SETZEN",
|
||||
"audioVideo": "AUDIO UND VIDEO"
|
||||
"audioVideo": "AUDIO UND VIDEO",
|
||||
"devices": ""
|
||||
},
|
||||
"\u0005settings": {
|
||||
"calendar": {}
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profil",
|
||||
@@ -148,7 +215,9 @@
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "Verbindungsdaten",
|
||||
"connectedTo": "",
|
||||
"bitrate": "Bitrate:",
|
||||
"bridgeCount": "",
|
||||
"packetloss": "Paketverlust:",
|
||||
"resolution": "Auflösung:",
|
||||
"framerate": "Bildwiederholrate:",
|
||||
@@ -187,7 +256,6 @@
|
||||
"focus": "Konferenz-Organisator",
|
||||
"focusFail": "__component__ ist im Moment nicht verfügbar - wiederholen in __ms__ Sekunden",
|
||||
"grantedTo": "Moderatorenrechte an __to__ vergeben.",
|
||||
"grantedToUnknown": "Moderatorenrechte an $t(notify.somebody) vergeben.",
|
||||
"muted": "Der Konferenz wurde stumm beigetreten.",
|
||||
"mutedTitle": "Stummschaltung aktiv!",
|
||||
"raisedHand": "Möchte sprechen.",
|
||||
@@ -195,8 +263,13 @@
|
||||
"suboptimalExperienceDescription": "Tut uns leid, aber die Konferenz wird mit __appName__ kein grossartiges Erlebnis. Wir versuchen immer die Situation zu verbessern, bis dahin empfehlen wir aber die Verwendung einer der <a href=\"static/recommendedBrowsers.html\" target=\"_blank\">vollständig unterstützen Browser</a>."
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Livestream:"
|
||||
},
|
||||
"allow": "Erlauben",
|
||||
"confirm": "",
|
||||
"kickMessage": "Oh! Sie wurden aus der Konferenz ausgeschlossen.",
|
||||
"kickTitle": "",
|
||||
"popupErrorTitle": "Popup blockiert",
|
||||
"popupError": "Ihr Browser blockiert Popups von dieser Website. Bitte aktivieren Sie Popups in den Sicherheitseinstellungen des Browsers und versuchen Sie es erneut.",
|
||||
"passwordErrorTitle": "Passwort-Fehler",
|
||||
@@ -219,6 +292,7 @@
|
||||
"rejoinNow": "Jetzt erneut beitreten",
|
||||
"maxUsersLimitReachedTitle": "Maximale Anzahl Teilnehmer ist erreicht",
|
||||
"maxUsersLimitReached": "Das Limit der maximalen Anzahl Teilnehmer wurde erreicht. Die Konferenz ist voll. Bitte benachrichtigen Sie den Konferenzorganisator oder versuchen Sie es später noch einmal.",
|
||||
"lockRoom": "",
|
||||
"lockTitle": "Sperren fehlgeschlagen",
|
||||
"lockMessage": "Die Konferenz konnte nicht gesperrt werden.",
|
||||
"warning": "Warnung",
|
||||
@@ -266,6 +340,7 @@
|
||||
"reservationError": "Fehler im Reservationssystem",
|
||||
"reservationErrorMsg": "Fehler, Nummer: __code__, Nachricht: __msg__",
|
||||
"password": "Passwort eingeben",
|
||||
"unlockRoom": "",
|
||||
"userPassword": "Benutzerpasswort",
|
||||
"token": "Token",
|
||||
"tokenAuthFailedTitle": "Authentifizierung fehlgeschlagen",
|
||||
@@ -278,7 +353,7 @@
|
||||
"sorryFeedback": "Tut uns leid. Möchten Sie uns mehr mitteilen?",
|
||||
"liveStreaming": "Live-Streaming",
|
||||
"streamKey": "Name/Schlüssel für den Stream",
|
||||
"startLiveStreaming": "Live-Streaming starten",
|
||||
"startLiveStreaming": "Einen Livestream starten",
|
||||
"startRecording": "Aufnahme starten",
|
||||
"stopStreamingWarning": "Sind Sie sicher dass Sie das Live-Streaming stoppen möchten?",
|
||||
"stopRecordingWarning": "Sind Sie sicher dass Sie die Aufnahme stoppen möchten?",
|
||||
@@ -312,6 +387,10 @@
|
||||
"muteParticipantTitle": "Diesen Teilnehmer stummschalten?",
|
||||
"muteParticipantBody": "Sie können die Stummschaltung anderer Teilnehmer nicht aufheben, aber ein Teilnehmer kann seine eigene Stummschaltung jederzeit beenden.",
|
||||
"muteParticipantButton": "Stummschalten",
|
||||
"liveStreamingDisabledTooltip": "",
|
||||
"liveStreamingDisabledForGuestTooltip": "",
|
||||
"recordingDisabledTooltip": "",
|
||||
"recordingDisabledForGuestTooltip": "",
|
||||
"remoteControlTitle": "Fernsteuerung",
|
||||
"remoteControlRequestMessage": "Möchten Sie __user__ erlauben den Computer fernzusteuern?",
|
||||
"remoteControlShareScreenWarning": "Achtung, wenn Sie die Anfrage genehmigen starten Sie die Bildschirmfreigabe!",
|
||||
@@ -322,10 +401,15 @@
|
||||
"remoteControlStopMessage": "Die Fernsteuerung wurde beendet.",
|
||||
"close": "Schliessen",
|
||||
"shareYourScreen": "Bildschirm freigeben",
|
||||
"shareYourScreenDisabled": "",
|
||||
"shareYourScreenDisabledForGuest": "",
|
||||
"yourEntireScreen": "Ganzer Bildschirm",
|
||||
"applicationWindow": "Anwendungsfenster"
|
||||
"applicationWindow": "Anwendungsfenster",
|
||||
"transcribing": ""
|
||||
},
|
||||
"\u0005dialog": {
|
||||
"accessibilityLabel": {}
|
||||
},
|
||||
"\u0005dialog": {},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Diese Konferenz ist passwortgeschützt. Bitte verwenden Sie diese PIN zum Beitreten:",
|
||||
@@ -357,6 +441,10 @@
|
||||
],
|
||||
"and": "und"
|
||||
},
|
||||
"share": {
|
||||
"mainText": "",
|
||||
"dialInfoText": ""
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Fehler",
|
||||
"CONNECTING": "Verbindung wird hergestellt",
|
||||
@@ -370,18 +458,44 @@
|
||||
"ATTACHED": "Angehängt"
|
||||
},
|
||||
"recording": {
|
||||
"beta": "",
|
||||
"busy": "Es werden Resourcen für eine Aufnahme bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
|
||||
"busyTitle": "Alle Aufnahme-Instanzen sind in Gebrauch",
|
||||
"buttonTooltip": "Aufnahme starten / stoppen",
|
||||
"error": "Die Aufzeichnung ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "Die Aufnahme konnte nicht gestartet werden",
|
||||
"live": "",
|
||||
"off": "Aufnahme gestoppt",
|
||||
"on": "Aufnahme",
|
||||
"pending": "Die Aufnahme wartet auf den Beitritt eines Teilnehmers...",
|
||||
"pending": "",
|
||||
"rec": "",
|
||||
"authDropboxText": "",
|
||||
"serviceName": "Aufnahmedienst",
|
||||
"signOut": "",
|
||||
"signIn": "",
|
||||
"loggedIn": "",
|
||||
"availableSpace": "",
|
||||
"startRecordingBody": "Sind Sie sicher dass Sie die Aufnahme stoppen möchten?",
|
||||
"unavailable": "Oh! Der __serviceName__ ist aktuell nicht verfügbar. Wir arbeiten an der Behebung des Problems. Bitte versuchen Sie es später noch einmal.",
|
||||
"unavailableTitle": "Aufnahme nicht verfügbar"
|
||||
},
|
||||
"\u0005recording": {},
|
||||
"transcribing": {
|
||||
"pending": "",
|
||||
"off": "",
|
||||
"error": "Die Aufzeichnung ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"expandedLabel": "",
|
||||
"failedToStart": "",
|
||||
"tr": "",
|
||||
"labelToolTip": "",
|
||||
"ccButtonTooltip": "",
|
||||
"start": "",
|
||||
"stop": ""
|
||||
},
|
||||
"\u0005transcribing": {},
|
||||
"liveStreaming": {
|
||||
"busy": "Es werden Resourcen zum Streamen bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
|
||||
"busyTitle": "Alle Streaming-Instanzen sind in Gebrauch",
|
||||
@@ -392,17 +506,24 @@
|
||||
"enterStreamKey": "Name/Schlüssel für den YouTube Livestream hier eingeben.",
|
||||
"error": "Das Live-Streaming ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"errorAPI": "Beim abrufen der YouTube Livestreams ist ein Fehler aufgetreten. Bitte versuchen Sie sich erneut anzumelden.",
|
||||
"errorLiveStreamNotEnabled": "",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "Live-Streaming konnte nicht gestartet werden",
|
||||
"off": "Live-Streaming gestoppt",
|
||||
"on": "Live-Streaming",
|
||||
"pending": "Live-Stream wird gestartet...",
|
||||
"serviceName": "Live Streaming-Dienst",
|
||||
"signedInAs": "",
|
||||
"signIn": "Mit Google anmelden",
|
||||
"signOut": "",
|
||||
"signInCTA": "Anmelden oder den Name/Schlüssel des YouTube Livestreams eingeben.",
|
||||
"start": "Einen Livestream starten",
|
||||
"streamIdHelp": "Was ist das?",
|
||||
"unavailableTitle": "Live-Streaming nicht verfügbar"
|
||||
},
|
||||
"\u0005liveStreaming": {},
|
||||
"videoSIPGW": {
|
||||
"busy": "Es stehen keine freien Ressourcen zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
||||
"busyTitle": "Keine freien Ressourcen",
|
||||
@@ -428,9 +549,11 @@
|
||||
"noPermission": "Berechtigungen nicht erteilt",
|
||||
"previewUnavailable": "Keine Vorschau verfügbar",
|
||||
"selectADevice": "Ein Gerät wählen",
|
||||
"testAudio": "Audio testen"
|
||||
"testAudio": ""
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "",
|
||||
"audioOnlyExpanded": "",
|
||||
"callQuality": "Konferenzqualität",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Video wird in HD angezeigt",
|
||||
@@ -458,7 +581,8 @@
|
||||
"add": "Einladen",
|
||||
"countryNotSupported": "Wir unterstützen dieses Land noch nicht.",
|
||||
"countryReminder": "Telefonnummer nicht in den USA? Bitte sicherstellen, dass die Telefonnummer mit dem Ländercode beginnt.",
|
||||
"disabled": "",
|
||||
"disabled": "Sie können keine Teilnehmer einladen.",
|
||||
"footerText": "",
|
||||
"invite": "Einladen",
|
||||
"loading": "Suche nach Teilnehmern und Telefonnummern",
|
||||
"loadingNumber": "Telefonnummer wird überprüft",
|
||||
@@ -495,6 +619,7 @@
|
||||
"veryGood": "Sehr gut"
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "",
|
||||
"addPassword": "Passwort hinzufügen",
|
||||
"cancelPassword": "Password abbrechen",
|
||||
"conferenceURL": "Link:",
|
||||
@@ -516,7 +641,7 @@
|
||||
"numbers": "Einwählnummern",
|
||||
"password": "Passwort:",
|
||||
"title": "Teilen",
|
||||
"tooltip": "Zugriffsinformationen über die Konferenz abrufen"
|
||||
"tooltip": ""
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "OK",
|
||||
@@ -532,17 +657,22 @@
|
||||
"startWithVideoMuted": "Ohne Video beitreten"
|
||||
},
|
||||
"calendarSync": {
|
||||
"later": "Später",
|
||||
"next": "Folgend",
|
||||
"addMeetingURL": "",
|
||||
"confirmAddLink": "",
|
||||
"confirmAddLinkTitle": "Kalender",
|
||||
"join": "",
|
||||
"joinTooltip": "",
|
||||
"nextMeeting": "Nächste Konferenz",
|
||||
"now": "Jetzt",
|
||||
"noEvents": "",
|
||||
"ongoingMeeting": "",
|
||||
"permissionButton": "Einstellungen öffnen",
|
||||
"permissionMessage": "Die App benötigt Zugriff auf den Kalender um die Termine und Konferenzen anzuzeigen."
|
||||
"permissionMessage": "Die App benötigt Zugriff auf den Kalender um die Termine und Konferenzen anzuzeigen.",
|
||||
"refresh": "",
|
||||
"today": "Heute"
|
||||
},
|
||||
"\u0005calendarSync": {},
|
||||
"recentList": {
|
||||
"today": "Heute",
|
||||
"yesterday": "Gestern",
|
||||
"earlier": "Früher"
|
||||
"joinPastMeeting": ""
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Ziehen um zu aktualisieren"
|
||||
@@ -555,5 +685,62 @@
|
||||
"appNotInstalled": "Sie benötigen die __app__ App um der Konferenz auf dem Smartphone beizutreten.",
|
||||
"downloadApp": "App herunterladen",
|
||||
"openApp": "In der App fortfahren"
|
||||
}
|
||||
},
|
||||
"presenceStatus": {
|
||||
"invited": "Einladen",
|
||||
"ringing": "",
|
||||
"calling": "",
|
||||
"initializingCall": "",
|
||||
"connected": "Verbunden",
|
||||
"connecting": "Verbindung wird hergestellt",
|
||||
"connecting2": "",
|
||||
"disconnected": "Getrennt",
|
||||
"busy": "",
|
||||
"rejected": "",
|
||||
"ignored": "",
|
||||
"expired": ""
|
||||
},
|
||||
"\u0005presenceStatus": {},
|
||||
"dateUtils": {
|
||||
"today": "Heute",
|
||||
"yesterday": "Gestern",
|
||||
"earlier": "Früher"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "",
|
||||
"audioCallTitle": "",
|
||||
"decline": "OK",
|
||||
"productLabel": "",
|
||||
"videoCallTitle": ""
|
||||
},
|
||||
"localRecording": {
|
||||
"localRecording": "",
|
||||
"dialogTitle": "",
|
||||
"start": "Aufnahme starten",
|
||||
"stop": "Aufnahme stoppen",
|
||||
"moderator": "Moderator",
|
||||
"me": "Ich",
|
||||
"duration": "",
|
||||
"durationNA": "",
|
||||
"encoding": "",
|
||||
"participantStats": "",
|
||||
"participant": "Teilnehmer",
|
||||
"sessionToken": "",
|
||||
"clientState": {
|
||||
"on": "",
|
||||
"off": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"messages": {
|
||||
"engaged": "",
|
||||
"finished": "",
|
||||
"finishedModerator": "",
|
||||
"notModerator": ""
|
||||
},
|
||||
"yes": "Ja",
|
||||
"no": "",
|
||||
"label": "",
|
||||
"labelToolTip": ""
|
||||
},
|
||||
"\u0005localRecording": {}
|
||||
}
|
||||
746
lang/main-hr.json
Normal file
746
lang/main-hr.json
Normal file
@@ -0,0 +1,746 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"add": "",
|
||||
"countryNotSupported": "",
|
||||
"countryReminder": "",
|
||||
"disabled": "",
|
||||
"failedToAdd": "",
|
||||
"footerText": "",
|
||||
"invite": "",
|
||||
"loading": "",
|
||||
"loadingNumber": "",
|
||||
"loadingPeople": "",
|
||||
"noResults": "",
|
||||
"notAvailable": "",
|
||||
"noValidNumbers": "",
|
||||
"searchNumbers": "",
|
||||
"searchPeople": "",
|
||||
"searchPeopleAndNumbers": "",
|
||||
"telephone": "",
|
||||
"title": ""
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "",
|
||||
"headphones": "",
|
||||
"phone": "",
|
||||
"speaker": ""
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "",
|
||||
"featureToggleDisabled": ""
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "",
|
||||
"confirmAddLink": "",
|
||||
"confirmAddLinkTitle": "",
|
||||
"error": {
|
||||
"appConfiguration": "",
|
||||
"generic": "",
|
||||
"notSignedIn": ""
|
||||
},
|
||||
"join": "",
|
||||
"joinTooltip": "",
|
||||
"nextMeeting": "",
|
||||
"noEvents": "",
|
||||
"ongoingMeeting": "",
|
||||
"permissionButton": "",
|
||||
"permissionMessage": "",
|
||||
"refresh": "",
|
||||
"today": ""
|
||||
},
|
||||
"chat": {
|
||||
"error": "",
|
||||
"messagebox": "",
|
||||
"nickname": {
|
||||
"popover": "",
|
||||
"title": ""
|
||||
},
|
||||
"title": ""
|
||||
},
|
||||
"connection": {
|
||||
"ATTACHED": "",
|
||||
"AUTHENTICATING": "",
|
||||
"AUTHFAIL": "",
|
||||
"CONNECTED": "",
|
||||
"CONNECTING": "",
|
||||
"CONNFAIL": "",
|
||||
"DISCONNECTED": "",
|
||||
"DISCONNECTING": "",
|
||||
"ERROR": "",
|
||||
"RECONNECTING": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "",
|
||||
"bandwidth": "",
|
||||
"bitrate": "",
|
||||
"bridgeCount": "",
|
||||
"connectedTo": "",
|
||||
"framerate": "",
|
||||
"header": "",
|
||||
"less": "",
|
||||
"localaddress": "",
|
||||
"localaddress_plural_2": "",
|
||||
"localport": "",
|
||||
"localport_plural_2": "",
|
||||
"more": "",
|
||||
"na": "",
|
||||
"packetloss": "",
|
||||
"quality": {
|
||||
"good": "",
|
||||
"inactive": "",
|
||||
"lost": "",
|
||||
"nonoptimal": "",
|
||||
"poor": ""
|
||||
},
|
||||
"remoteaddress": "",
|
||||
"remoteaddress_plural_2": "",
|
||||
"remoteport": "",
|
||||
"remoteport_plural_2": "",
|
||||
"resolution": "",
|
||||
"status": "",
|
||||
"transport": "",
|
||||
"transport_plural_2": "",
|
||||
"turn": ""
|
||||
},
|
||||
"contactlist_plural": "",
|
||||
"dateUtils": {
|
||||
"earlier": "",
|
||||
"today": "",
|
||||
"yesterday": ""
|
||||
},
|
||||
"deepLinking": {
|
||||
"appNotInstalled": "",
|
||||
"description": "",
|
||||
"downloadApp": "",
|
||||
"launchWebButton": "",
|
||||
"openApp": "",
|
||||
"title": "",
|
||||
"tryAgainButton": ""
|
||||
},
|
||||
"defaultLink": "",
|
||||
"defaultNickname": "",
|
||||
"deviceError": {
|
||||
"cameraError": "",
|
||||
"cameraPermission": "",
|
||||
"microphoneError": "",
|
||||
"microphonePermission": ""
|
||||
},
|
||||
"deviceSelection": {
|
||||
"deviceSettings": "",
|
||||
"noPermission": "",
|
||||
"previewUnavailable": "",
|
||||
"selectADevice": "",
|
||||
"testAudio": ""
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": ""
|
||||
},
|
||||
"allow": "",
|
||||
"alreadySharedVideoMsg": "",
|
||||
"alreadySharedVideoTitle": "",
|
||||
"applicationWindow": "",
|
||||
"Back": "",
|
||||
"cameraConstraintFailedError": "",
|
||||
"cameraNotFoundError": "",
|
||||
"cameraNotSendingData": "",
|
||||
"cameraNotSendingDataTitle": "",
|
||||
"cameraPermissionDeniedError": "",
|
||||
"cameraUnknownError": "",
|
||||
"cameraUnsupportedResolutionError": "",
|
||||
"Cancel": "",
|
||||
"close": "",
|
||||
"conferenceDisconnectMsg": "",
|
||||
"conferenceDisconnectTitle": "",
|
||||
"conferenceReloadMsg": "",
|
||||
"conferenceReloadTitle": "",
|
||||
"confirm": "",
|
||||
"confirmNo": "",
|
||||
"confirmYes": "",
|
||||
"connectError": "",
|
||||
"connectErrorWithMsg": "",
|
||||
"connecting": "",
|
||||
"contactSupport": "",
|
||||
"copy": "",
|
||||
"currentPassword": "",
|
||||
"defaultError": "",
|
||||
"detectext": "",
|
||||
"dismiss": "",
|
||||
"displayNameRequired": "",
|
||||
"done": "",
|
||||
"doNotShowMessageAgain": "",
|
||||
"enterDisplayName": "",
|
||||
"error": "",
|
||||
"externalInstallationMsg": "",
|
||||
"externalInstallationTitle": "",
|
||||
"failedpermissions": "",
|
||||
"feedbackHelp": "",
|
||||
"feedbackQuestion": "",
|
||||
"goToStore": "",
|
||||
"gracefulShutdown": "",
|
||||
"hungUp": "",
|
||||
"IamHost": "",
|
||||
"incorrectPassword": "",
|
||||
"inlineInstallationMsg": "",
|
||||
"inlineInstallExtension": "",
|
||||
"internalError": "",
|
||||
"internalErrorTitle": "",
|
||||
"joinAgain": "",
|
||||
"kickMessage": "",
|
||||
"kickParticipantButton": "",
|
||||
"kickParticipantDialog": "",
|
||||
"kickParticipantTitle": "",
|
||||
"kickTitle": "",
|
||||
"liveStreaming": "",
|
||||
"liveStreamingDisabledForGuestTooltip": "",
|
||||
"liveStreamingDisabledTooltip": "",
|
||||
"lockMessage": "",
|
||||
"lockRoom": "",
|
||||
"lockTitle": "",
|
||||
"logoutQuestion": "",
|
||||
"logoutTitle": "",
|
||||
"maxUsersLimitReached": "",
|
||||
"maxUsersLimitReachedTitle": "",
|
||||
"micConstraintFailedError": "",
|
||||
"micNotFoundError": "",
|
||||
"micNotSendingData": "",
|
||||
"micNotSendingDataTitle": "",
|
||||
"micPermissionDeniedError": "",
|
||||
"micUnknownError": "",
|
||||
"muteParticipantBody": "",
|
||||
"muteParticipantButton": "",
|
||||
"muteParticipantDialog": "",
|
||||
"muteParticipantTitle": "",
|
||||
"Ok": "",
|
||||
"oops": "",
|
||||
"password": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"passwordErrorTitle": "",
|
||||
"passwordLabel": "",
|
||||
"passwordNotSupported": "",
|
||||
"passwordNotSupportedTitle": "",
|
||||
"passwordRequired": "",
|
||||
"permissionDenied": "",
|
||||
"popupError": "",
|
||||
"popupErrorTitle": "",
|
||||
"recording": "",
|
||||
"recordingDisabledForGuestTooltip": "",
|
||||
"recordingDisabledTooltip": "",
|
||||
"recordingToken": "",
|
||||
"rejoinNow": "",
|
||||
"remoteControlAllowedMessage": "",
|
||||
"remoteControlDeniedMessage": "",
|
||||
"remoteControlErrorMessage": "",
|
||||
"remoteControlRequestMessage": "",
|
||||
"remoteControlShareScreenWarning": "",
|
||||
"remoteControlStopMessage": "",
|
||||
"remoteControlTitle": "",
|
||||
"Remove": "",
|
||||
"removePassword": "",
|
||||
"removeSharedVideoMsg": "",
|
||||
"removeSharedVideoTitle": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "",
|
||||
"retry": "",
|
||||
"Save": "",
|
||||
"screenSharingFailedToInstall": "",
|
||||
"screenSharingFailedToInstallTitle": "",
|
||||
"screenSharingFirefoxPermissionDeniedError": "",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"serviceUnavailable": "",
|
||||
"sessTerminated": "",
|
||||
"Share": "",
|
||||
"shareVideoLinkError": "",
|
||||
"shareVideoTitle": "",
|
||||
"shareYourScreen": "",
|
||||
"shareYourScreenDisabled": "",
|
||||
"shareYourScreenDisabledForGuest": "",
|
||||
"SLDFailure": "",
|
||||
"sorryFeedback": "",
|
||||
"SRDFailure": "",
|
||||
"startLiveStreaming": "",
|
||||
"startRecording": "",
|
||||
"startRemoteControlErrorMessage": "",
|
||||
"stopLiveStreaming": "",
|
||||
"stopRecording": "",
|
||||
"stopRecordingWarning": "",
|
||||
"stopStreamingWarning": "",
|
||||
"streamKey": "",
|
||||
"Submit": "",
|
||||
"thankYou": "",
|
||||
"token": "",
|
||||
"tokenAuthFailed": "",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"transcribing": "",
|
||||
"unableToSwitch": "",
|
||||
"unlockRoom": "",
|
||||
"userPassword": "",
|
||||
"WaitForHostMsg": "",
|
||||
"WaitForHostMsgWOk": "",
|
||||
"WaitingForHost": "",
|
||||
"warning": "",
|
||||
"Yes": "",
|
||||
"yourEntireScreen": ""
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": ""
|
||||
},
|
||||
"email": {
|
||||
"and": "",
|
||||
"body": "",
|
||||
"sharedKey": "",
|
||||
"subject": ""
|
||||
},
|
||||
"feedback": {
|
||||
"average": "",
|
||||
"bad": "",
|
||||
"detailsLabel": "",
|
||||
"good": "",
|
||||
"rateExperience": "",
|
||||
"veryBad": "",
|
||||
"veryGood": ""
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "",
|
||||
"audioCallTitle": "",
|
||||
"decline": "",
|
||||
"productLabel": "",
|
||||
"videoCallTitle": ""
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "",
|
||||
"addPassword": "",
|
||||
"cancelPassword": "",
|
||||
"conferenceURL": "",
|
||||
"country": "",
|
||||
"dialANumber": "",
|
||||
"dialInConferenceID": "",
|
||||
"dialInNotSupported": "",
|
||||
"dialInNumber": "",
|
||||
"dialInTollFree": "",
|
||||
"genericError": "",
|
||||
"inviteLiveStream": "",
|
||||
"invitePhone": "",
|
||||
"invitePhoneAlternatives": "",
|
||||
"inviteURL": "",
|
||||
"liveStreamURL": "",
|
||||
"moreNumbers": "",
|
||||
"noNumbers": "",
|
||||
"noPassword": "",
|
||||
"noRoom": "",
|
||||
"numbers": "",
|
||||
"password": "",
|
||||
"title": "",
|
||||
"tooltip": "",
|
||||
"label": ""
|
||||
},
|
||||
"inviteDialog": {
|
||||
"alertOk": "",
|
||||
"alertText": "",
|
||||
"alertTitle": "",
|
||||
"header": "",
|
||||
"searchCallOnlyPlaceholder": "",
|
||||
"searchPeopleOnlyPlaceholder": "",
|
||||
"searchPlaceholder": "",
|
||||
"send": ""
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "",
|
||||
"retry": "",
|
||||
"support": "",
|
||||
"supportMsg": ""
|
||||
},
|
||||
"inviteUrlDefaultMsg": "",
|
||||
"keyboardShortcuts": {
|
||||
"focusLocal": "",
|
||||
"focusRemote": "",
|
||||
"fullScreen": "",
|
||||
"keyboardShortcuts": "",
|
||||
"localRecording": "",
|
||||
"mute": "",
|
||||
"pushToTalk": "",
|
||||
"raiseHand": "",
|
||||
"showSpeakerStats": "",
|
||||
"toggleChat": "",
|
||||
"toggleFilmstrip": "",
|
||||
"toggleScreensharing": "",
|
||||
"toggleShortcuts": "",
|
||||
"videoMute": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "",
|
||||
"changeSignIn": "",
|
||||
"choose": "",
|
||||
"chooseCTA": "",
|
||||
"enterStreamKey": "",
|
||||
"error": "",
|
||||
"errorAPI": "",
|
||||
"errorLiveStreamNotEnabled": "",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "",
|
||||
"getStreamKeyManually": "",
|
||||
"invalidStreamKey": "",
|
||||
"off": "",
|
||||
"on": "",
|
||||
"pending": "",
|
||||
"serviceName": "",
|
||||
"signedInAs": "",
|
||||
"signIn": "",
|
||||
"signInCTA": "",
|
||||
"signOut": "",
|
||||
"start": "",
|
||||
"streamIdHelp": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
"off": "",
|
||||
"on": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"dialogTitle": "",
|
||||
"duration": "",
|
||||
"durationNA": "",
|
||||
"encoding": "",
|
||||
"label": "",
|
||||
"labelToolTip": "",
|
||||
"localRecording": "",
|
||||
"me": "",
|
||||
"messages": {
|
||||
"engaged": "",
|
||||
"finished": "",
|
||||
"finishedModerator": "",
|
||||
"notModerator": ""
|
||||
},
|
||||
"moderator": "",
|
||||
"no": "",
|
||||
"participant": "",
|
||||
"participantStats": "",
|
||||
"sessionToken": "",
|
||||
"start": "",
|
||||
"stop": "",
|
||||
"yes": ""
|
||||
},
|
||||
"me": "",
|
||||
"notify": {
|
||||
"connectedOneMember": "",
|
||||
"connectedThreePlusMembers": "",
|
||||
"connectedTwoMembers": "",
|
||||
"disconnected": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"me": "",
|
||||
"moderator": "",
|
||||
"muted": "",
|
||||
"mutedTitle": "",
|
||||
"raisedHand": "",
|
||||
"somebody": "",
|
||||
"suboptimalExperienceDescription": "",
|
||||
"suboptimalExperienceTitle": ""
|
||||
},
|
||||
"passwordSetRemotely": "",
|
||||
"poweredby": "",
|
||||
"presenceStatus": {
|
||||
"busy": "",
|
||||
"calling": "",
|
||||
"connected": "",
|
||||
"connecting": "",
|
||||
"connecting2": "",
|
||||
"disconnected": "",
|
||||
"expired": "",
|
||||
"ignored": "",
|
||||
"initializingCall": "",
|
||||
"invited": "",
|
||||
"rejected": "",
|
||||
"ringing": ""
|
||||
},
|
||||
"profile": {
|
||||
"setDisplayNameLabel": "",
|
||||
"setEmailInput": "",
|
||||
"setEmailLabel": "",
|
||||
"title": ""
|
||||
},
|
||||
"raisedHand": "",
|
||||
"recentList": {
|
||||
"joinPastMeeting": ""
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "",
|
||||
"availableSpace": "",
|
||||
"beta": "",
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "",
|
||||
"error": "",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "",
|
||||
"live": "",
|
||||
"loggedIn": "",
|
||||
"off": "",
|
||||
"on": "",
|
||||
"pending": "",
|
||||
"rec": "",
|
||||
"serviceDescription": "",
|
||||
"serviceName": "",
|
||||
"signIn": "",
|
||||
"signOut": "",
|
||||
"startRecordingBody": "",
|
||||
"unavailable": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": ""
|
||||
},
|
||||
"settings": {
|
||||
"audioVideo": "",
|
||||
"calendar": {
|
||||
"about": "",
|
||||
"disconnect": "",
|
||||
"microsoftSignIn": "",
|
||||
"signedIn": "",
|
||||
"title": ""
|
||||
},
|
||||
"cameraAndMic": "",
|
||||
"devices": "",
|
||||
"followMe": "",
|
||||
"language": "",
|
||||
"loggedIn": "",
|
||||
"moderator": "",
|
||||
"more": "",
|
||||
"name": "",
|
||||
"noDevice": "",
|
||||
"password": "",
|
||||
"selectAudioOutput": "",
|
||||
"selectCamera": "",
|
||||
"selectMic": "",
|
||||
"startAudioMuted": "",
|
||||
"startVideoMuted": "",
|
||||
"title": "",
|
||||
"update": ""
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "",
|
||||
"alertTitle": "",
|
||||
"alertURLText": "",
|
||||
"conferenceSection": "",
|
||||
"displayName": "",
|
||||
"email": "",
|
||||
"header": "",
|
||||
"profileSection": "",
|
||||
"serverURL": "",
|
||||
"startWithAudioMuted": "",
|
||||
"startWithVideoMuted": ""
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "",
|
||||
"mainText": ""
|
||||
},
|
||||
"speaker": "",
|
||||
"speakerStats": {
|
||||
"hours": "",
|
||||
"minutes": "",
|
||||
"name": "",
|
||||
"seconds": "",
|
||||
"speakerStats": "",
|
||||
"speakerTime": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
"title": ""
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"rejoinKeyTitle": "",
|
||||
"text": "",
|
||||
"title": ""
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "",
|
||||
"audioRoute": "",
|
||||
"callQuality": "",
|
||||
"cc": "",
|
||||
"chat": "",
|
||||
"document": "",
|
||||
"feedback": "",
|
||||
"fullScreen": "",
|
||||
"hangup": "",
|
||||
"invite": "",
|
||||
"kick": "",
|
||||
"localRecording": "",
|
||||
"lockRoom": "",
|
||||
"moreActions": "",
|
||||
"moreActionsMenu": "",
|
||||
"mute": "",
|
||||
"pip": "",
|
||||
"profile": "",
|
||||
"raiseHand": "",
|
||||
"recording": "",
|
||||
"remoteMute": "",
|
||||
"Settings": "",
|
||||
"sharedvideo": "",
|
||||
"shareRoom": "",
|
||||
"shareYourScreen": "",
|
||||
"shortcuts": "",
|
||||
"speakerStats": "",
|
||||
"tileView": "",
|
||||
"toggleCamera": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"addPeople": "",
|
||||
"audioonly": "",
|
||||
"audioOnlyOff": "",
|
||||
"audioOnlyOn": "",
|
||||
"audioRoute": "",
|
||||
"authenticate": "",
|
||||
"callQuality": "",
|
||||
"cameraDisabled": "",
|
||||
"chat": "",
|
||||
"closeChat": "",
|
||||
"documentClose": "",
|
||||
"documentOpen": "",
|
||||
"enterFullScreen": "",
|
||||
"enterTileView": "",
|
||||
"etherpad": "",
|
||||
"exitFullScreen": "",
|
||||
"exitTileView": "",
|
||||
"feedback": "",
|
||||
"filmstrip": "",
|
||||
"fullscreen": "",
|
||||
"hangup": "",
|
||||
"invite": "",
|
||||
"lock": "",
|
||||
"login": "",
|
||||
"logout": "",
|
||||
"lowerYourHand": "",
|
||||
"micDisabled": "",
|
||||
"micMutedPopup": "",
|
||||
"moreActions": "",
|
||||
"mute": "",
|
||||
"openChat": "",
|
||||
"pip": "",
|
||||
"profile": "",
|
||||
"raiseHand": "",
|
||||
"raiseYourHand": "",
|
||||
"Settings": "",
|
||||
"sharedvideo": "",
|
||||
"sharedVideoMutedPopup": "",
|
||||
"shareRoom": "",
|
||||
"shortcuts": "",
|
||||
"sip": "",
|
||||
"speakerStats": "",
|
||||
"startScreenSharing": "",
|
||||
"startSubtitles": "",
|
||||
"stopScreenSharing": "",
|
||||
"stopSubtitles": "",
|
||||
"stopSharedVideo": "",
|
||||
"talkWhileMutedPopup": "",
|
||||
"tileViewToggle": "",
|
||||
"toggleCamera": "",
|
||||
"unableToUnmutePopup": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "",
|
||||
"error": "",
|
||||
"expandedLabel": "",
|
||||
"failedToStart": "",
|
||||
"labelToolTip": "",
|
||||
"off": "",
|
||||
"pending": "",
|
||||
"start": "",
|
||||
"stop": "",
|
||||
"tr": ""
|
||||
},
|
||||
"userMedia": {
|
||||
"androidGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"edgeGrantPermissions": "",
|
||||
"electronGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"nwjsGrantPermissions": "",
|
||||
"operaGrantPermissions": "",
|
||||
"react-nativeGrantPermissions": "",
|
||||
"safariGrantPermissions": ""
|
||||
},
|
||||
"videoSIPGW": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"errorAlreadyInvited": "",
|
||||
"errorInvite": "",
|
||||
"errorInviteFailed": "",
|
||||
"errorInviteFailedTitle": "",
|
||||
"errorInviteTitle": "",
|
||||
"pending": "",
|
||||
"serviceName": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "",
|
||||
"audioOnlyExpanded": "",
|
||||
"callQuality": "",
|
||||
"hd": "",
|
||||
"hdTooltip": "",
|
||||
"highDefinition": "",
|
||||
"labelTooiltipNoVideo": "",
|
||||
"labelTooltipAudioOnly": "",
|
||||
"labelTooltipVideo": "",
|
||||
"ld": "",
|
||||
"ldTooltip": "",
|
||||
"lowDefinition": "",
|
||||
"onlyAudioAvailable": "",
|
||||
"onlyAudioSupported": "",
|
||||
"p2pEnabled": "",
|
||||
"p2pVideoQualityDescription": "",
|
||||
"qualityButtonTip": "",
|
||||
"recHighDefinitionOnly": "",
|
||||
"sd": "",
|
||||
"sdTooltip": "",
|
||||
"standardDefinition": ""
|
||||
},
|
||||
"videothumbnail": {
|
||||
"domute": "",
|
||||
"flip": "",
|
||||
"kick": "",
|
||||
"moderator": "",
|
||||
"mute": "",
|
||||
"muted": "",
|
||||
"remoteControl": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "",
|
||||
"roomname": ""
|
||||
},
|
||||
"appDescription": "",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "",
|
||||
"video": ""
|
||||
},
|
||||
"calendar": "",
|
||||
"connectCalendarButton": "",
|
||||
"connectCalendarText": "",
|
||||
"enterRoomTitle": "",
|
||||
"go": "",
|
||||
"join": "",
|
||||
"privacy": "",
|
||||
"recentList": "",
|
||||
"recentListDelete": "",
|
||||
"recentListEmpty": "",
|
||||
"roomname": "",
|
||||
"roomnameHint": "",
|
||||
"sendFeedback": "",
|
||||
"terms": "",
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
@@ -357,7 +357,9 @@
|
||||
"inviteLiveStream": "To view the live stream of this meeting, click this link: __url__",
|
||||
"invitePhone": "One tap audio Dial In: __number__,,__conferenceID__#",
|
||||
"invitePhoneAlternatives": "Looking for a different dial in number? Please see: __url__",
|
||||
"inviteURL": "You are invited to join a meeting.\n__moreInfo__\nJoin meeting: __url__\n",
|
||||
"inviteURLFirstPartGeneral": "You are invited to join a meeting.",
|
||||
"inviteURLFirstPartPersonal": "__name__ is inviting you to a meeting.",
|
||||
"inviteURLSecondPart": "\n__moreInfo__\nJoin meeting: __url__\n",
|
||||
"inviteURLMoreInfo": "Meeting ID: __conferenceID__#\n",
|
||||
"liveStreamURL": "Live stream:",
|
||||
"moreNumbers": "More numbers",
|
||||
@@ -479,11 +481,11 @@
|
||||
"suboptimalExperienceDescription": "Eer... we are afraid your experience with __appName__ isn't going to be that great here. We are looking for ways to improve this but, until then, please try using one of the <a href='static/recommendedBrowsers.html' target='_blank'>fully supported browsers</a>.",
|
||||
"suboptimalExperienceTitle": "Browser Warning",
|
||||
"newDeviceCameraTitle": "New camera detected",
|
||||
"newDeviceMicTitle": "New microphone detected",
|
||||
"newDeviceCameraTitle": "New audio output detected",
|
||||
"newDeviceAudioTitle": "New audio device detected",
|
||||
"newDeviceAction": "Use"
|
||||
},
|
||||
"passwordSetRemotely": "set by another member",
|
||||
"passwordDigitsOnly": "Up to __number__ digits",
|
||||
"poweredby": "powered by",
|
||||
"presenceStatus": {
|
||||
"busy": "Busy",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { setSubject } from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import { invite } from '../../react/features/invite';
|
||||
import { toggleTileView } from '../../react/features/video-layout';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
|
||||
import { API_ID } from './constants';
|
||||
@@ -97,6 +98,11 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('screen.sharing.toggled'));
|
||||
toggleScreenSharing();
|
||||
},
|
||||
'toggle-tile-view': () => {
|
||||
sendAnalytics(createApiEvent('tile-view.toggled'));
|
||||
|
||||
APP.store.dispatch(toggleTileView());
|
||||
},
|
||||
'video-hangup': () => {
|
||||
sendAnalytics(createApiEvent('video.hangup'));
|
||||
APP.conference.hangup(true);
|
||||
@@ -622,6 +628,21 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that tile view has been
|
||||
* entered or exited.
|
||||
*
|
||||
* @param {string} enabled - True if tile view is currently displayed, false
|
||||
* otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyTileViewChanged(enabled: boolean) {
|
||||
this._sendEvent({
|
||||
name: 'tile-view-changed',
|
||||
enabled
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
|
||||
4
modules/API/external/external_api.js
vendored
4
modules/API/external/external_api.js
vendored
@@ -39,6 +39,7 @@ const commands = {
|
||||
toggleChat: 'toggle-chat',
|
||||
toggleFilmStrip: 'toggle-film-strip',
|
||||
toggleShareScreen: 'toggle-share-screen',
|
||||
toggleTileView: 'toggle-tile-view',
|
||||
toggleVideo: 'toggle-video'
|
||||
};
|
||||
|
||||
@@ -67,7 +68,8 @@ const events = {
|
||||
'video-availability-changed': 'videoAvailabilityChanged',
|
||||
'video-mute-status-changed': 'videoMuteStatusChanged',
|
||||
'screen-sharing-status-changed': 'screenSharingStatusChanged',
|
||||
'subject-change': 'subjectChange'
|
||||
'subject-change': 'subjectChange',
|
||||
'tile-view-changed': 'tileViewChanged'
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -166,22 +166,6 @@ function getCameraVideoPosition( // eslint-disable-line max-params
|
||||
verticalIndent };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the video horizontal and vertical indents.
|
||||
* Centers horizontally and top aligns vertically.
|
||||
*
|
||||
* @return an array with 2 elements, the horizontal indent and the vertical
|
||||
* indent
|
||||
*/
|
||||
function getDesktopVideoPosition(videoWidth, videoHeight, videoSpaceWidth) {
|
||||
const horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
|
||||
|
||||
const verticalIndent = 0;// Top aligned
|
||||
|
||||
return { horizontalIndent,
|
||||
verticalIndent };
|
||||
}
|
||||
|
||||
/**
|
||||
* Container for user video.
|
||||
*/
|
||||
@@ -366,23 +350,23 @@ export class VideoContainer extends LargeContainer {
|
||||
* @returns {{horizontalIndent, verticalIndent}}
|
||||
*/
|
||||
getVideoPosition(width, height, containerWidth, containerHeight) {
|
||||
let containerWidthToUse = containerWidth;
|
||||
|
||||
/* eslint-enable max-params */
|
||||
if (this.stream && this.isScreenSharing()) {
|
||||
let availableContainerWidth = containerWidth;
|
||||
|
||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
availableContainerWidth -= Filmstrip.getFilmstripWidth();
|
||||
containerWidthToUse -= Filmstrip.getFilmstripWidth();
|
||||
}
|
||||
|
||||
return getDesktopVideoPosition(width,
|
||||
return getCameraVideoPosition(width,
|
||||
height,
|
||||
availableContainerWidth,
|
||||
containerWidthToUse,
|
||||
containerHeight);
|
||||
}
|
||||
|
||||
return getCameraVideoPosition(width,
|
||||
height,
|
||||
containerWidth,
|
||||
containerWidthToUse,
|
||||
containerHeight);
|
||||
|
||||
}
|
||||
|
||||
101
package-lock.json
generated
101
package-lock.json
generated
@@ -2454,9 +2454,9 @@
|
||||
"integrity": "sha512-fJmzL27x0BEjhmMXPnDPnUNCZK7bph+NBVCfAz9fzHzAamaiOkdUwuL3PvE4Oj4Kw4knP8ocw5VRDGorAidZ2g=="
|
||||
},
|
||||
"@react-native-community/cli": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-1.9.2.tgz",
|
||||
"integrity": "sha512-wSw3g6HrSUvLZiHiWRcO++JrKdbYNRWycGbGHVCnRLsdDRsj/y152xPlvBa29C8w+1SwiiN8aGsBOO0x9hkrCg==",
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-1.9.4.tgz",
|
||||
"integrity": "sha512-7XjgqCdi23g6V7RV4tsYvqVqOBtNjAsWe5Oj2dR5KxDi3YqUyIyPjDWzyFkIxiO9XTGp9Al4QSmRwtOERvHO8A==",
|
||||
"requires": {
|
||||
"chalk": "^1.1.1",
|
||||
"commander": "^2.19.0",
|
||||
@@ -2840,7 +2840,7 @@
|
||||
"blueimp-md5": "^2.10.0",
|
||||
"json3": "^3.3.2",
|
||||
"lodash": "^4.17.4",
|
||||
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f1"
|
||||
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d"
|
||||
},
|
||||
"dependencies": {
|
||||
"ua-parser-js": {
|
||||
@@ -5485,12 +5485,41 @@
|
||||
}
|
||||
},
|
||||
"errorhandler": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.0.tgz",
|
||||
"integrity": "sha1-6rpkyl1UKjEayUX1gt78M2Fl2fQ=",
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz",
|
||||
"integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.3",
|
||||
"accepts": "~1.3.7",
|
||||
"escape-html": "~1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
|
||||
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.24",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
|
||||
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
|
||||
"requires": {
|
||||
"mime-db": "1.40.0"
|
||||
}
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
@@ -6721,8 +6750,7 @@
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -6740,13 +6768,11 @@
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -6759,18 +6785,15 @@
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -6873,8 +6896,7 @@
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -6884,7 +6906,6 @@
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -6897,20 +6918,17 @@
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -6927,7 +6945,6 @@
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -7000,8 +7017,7 @@
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -7011,7 +7027,6 @@
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -7087,8 +7102,7 @@
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -7118,7 +7132,6 @@
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -7136,7 +7149,6 @@
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -7175,13 +7187,11 @@
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12043,9 +12053,9 @@
|
||||
}
|
||||
},
|
||||
"react-native": {
|
||||
"version": "0.59.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.59.5.tgz",
|
||||
"integrity": "sha512-8Q/9cS6IMsGNiFhJgzmncbUeuacXQMe5EJl0c63fW30DvjEjeTVCvhM08eGzSpsNlOvL2XDRa4YOiCrwI7S1TA==",
|
||||
"version": "0.59.8",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.59.8.tgz",
|
||||
"integrity": "sha512-x1T+/pEXrjgdH9uDzd5doJy5aFlBqW04j7ljDKIGALchhnvdFbtXXrUZ/1PfWHMrIdZxtaDt4tkSttp662GSQA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"@react-native-community/cli": "^1.2.1",
|
||||
@@ -12369,6 +12379,15 @@
|
||||
"exenv": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"react-textarea-autosize": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-7.1.0.tgz",
|
||||
"integrity": "sha512-c2FlR/fP0qbxmlrW96SdrbgP/v0XZMTupqB90zybvmDVDutytUgPl7beU35klwcTeMepUIQEpQUn3P3bdshGPg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"react-transform-hmr": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-transform-hmr/-/react-transform-hmr-1.0.4.tgz",
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"react-emoji-render": "0.4.6",
|
||||
"react-i18next": "7.13.0",
|
||||
"react-linkify": "0.2.2",
|
||||
"react-native": "0.59.5",
|
||||
"react-native": "0.59.8",
|
||||
"react-native-background-timer": "2.1.1",
|
||||
"react-native-calendar-events": "1.6.4",
|
||||
"react-native-callstats": "3.58.2",
|
||||
@@ -79,6 +79,7 @@
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#4064c6f2db4f8b961daaaa8dafc6a896d7cfbc43",
|
||||
"react-native-webview": "5.8.1",
|
||||
"react-redux": "5.0.7",
|
||||
"react-textarea-autosize": "7.1.0",
|
||||
"react-transition-group": "2.4.0",
|
||||
"redux": "4.0.0",
|
||||
"redux-thunk": "2.2.0",
|
||||
|
||||
@@ -17,11 +17,6 @@ declare var APP;
|
||||
*/
|
||||
export function appWillMount(app: Object) {
|
||||
return (dispatch: Dispatch<any>) => {
|
||||
dispatch({
|
||||
type: APP_WILL_MOUNT,
|
||||
app
|
||||
});
|
||||
|
||||
// TODO There was a redux action creator appInit which I did not like
|
||||
// because we already had the redux action creator appWillMount and,
|
||||
// respectively, the redux action APP_WILL_MOUNT. So I set out to remove
|
||||
@@ -30,6 +25,11 @@ export function appWillMount(app: Object) {
|
||||
// API module into its own feature yet so we're bound to work on that in
|
||||
// the future.
|
||||
typeof APP === 'object' && APP.API.init();
|
||||
|
||||
dispatch({
|
||||
type: APP_WILL_MOUNT,
|
||||
app
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,28 @@ import {
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Maps the WebRTC string for device type to the keys used to store configure,
|
||||
* within redux, which devices should be used by default.
|
||||
*/
|
||||
const DEVICE_TYPE_TO_SETTINGS_KEYS = {
|
||||
audioInput: {
|
||||
currentDeviceId: 'micDeviceId',
|
||||
userSelectedDeviceId: 'userSelectedMicDeviceId',
|
||||
userSelectedDeviceLabel: 'userSelectedMicDeviceLabel'
|
||||
},
|
||||
audioOutput: {
|
||||
currentDeviceId: 'audioOutputDeviceId',
|
||||
userSelectedDeviceId: 'userSelectedAudioOutputDeviceId',
|
||||
userSelectedDeviceLabel: 'userSelectedAudioOutputDeviceLabel'
|
||||
},
|
||||
videoInput: {
|
||||
currentDeviceId: 'audioOutputDeviceId',
|
||||
userSelectedDeviceId: 'userSelectedCameraDeviceId',
|
||||
userSelectedDeviceLabel: 'userSelectedCameraDeviceLabel'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a pending device request.
|
||||
*
|
||||
@@ -70,19 +92,19 @@ export function configureInitialDevices() {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const newSettings = {};
|
||||
const devicesKeysToSettingsKeys = {
|
||||
audioInput: 'micDeviceId',
|
||||
audioOutput: 'audioOutputDeviceId',
|
||||
videoInput: 'cameraDeviceId'
|
||||
};
|
||||
|
||||
Object.keys(deviceLabels).forEach(key => {
|
||||
const label = deviceLabels[key];
|
||||
const deviceId = getDeviceIdByLabel(state, label, key);
|
||||
|
||||
if (deviceId) {
|
||||
newSettings[devicesKeysToSettingsKeys[key]] = deviceId;
|
||||
const settingsTranslationMap = DEVICE_TYPE_TO_SETTINGS_KEYS[key];
|
||||
|
||||
newSettings[settingsTranslationMap.currentDeviceId] = deviceId;
|
||||
newSettings[settingsTranslationMap.userSelectedDeviceId] = deviceId;
|
||||
newSettings[settingsTranslationMap.userSelectedDeviceLabel] = label;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -79,7 +79,9 @@ function _conferenceJoined({ dispatch, getState }, next, action) {
|
||||
|
||||
/**
|
||||
* Finds a new device by comparing new and old array of devices and dispatches
|
||||
* notification with the new device.
|
||||
* notification with the new device. For new devices with same groupId only one
|
||||
* notification will be shown, this is so to avoid showing multiple notifications
|
||||
* for audio input and audio output devices.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
@@ -97,7 +99,25 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
|
||||
nDevice => !oldDevices.find(
|
||||
device => device.deviceId === nDevice.deviceId));
|
||||
|
||||
onlyNewDevices.forEach(newDevice => {
|
||||
// we group devices by groupID which normally is the grouping by physical device
|
||||
// plugging in headset we provide normally two device, one input and one output
|
||||
// and we want to show only one notification for this physical audio device
|
||||
const devicesGroupBy = onlyNewDevices.reduce((accumulated, value) => {
|
||||
accumulated[value.groupId] = accumulated[value.groupId] || [];
|
||||
accumulated[value.groupId].push(value);
|
||||
|
||||
return accumulated;
|
||||
}, {});
|
||||
|
||||
Object.values(devicesGroupBy).forEach(devicesArray => {
|
||||
|
||||
if (devicesArray.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// let's get the first device as a reference, we will use it for
|
||||
// label and type
|
||||
const newDevice = devicesArray[0];
|
||||
|
||||
// we want to strip any device details that are not very
|
||||
// user friendly, like usb ids put in brackets at the end
|
||||
@@ -115,12 +135,9 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
|
||||
titleKey = 'notify.newDeviceCameraTitle';
|
||||
break;
|
||||
}
|
||||
case 'audioinput': {
|
||||
titleKey = 'notify.newDeviceMicTitle';
|
||||
break;
|
||||
}
|
||||
case 'audioinput' :
|
||||
case 'audiooutput': {
|
||||
titleKey = 'notify.newDeviceCameraTitle';
|
||||
titleKey = 'notify.newDeviceAudioTitle';
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -129,7 +146,7 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
|
||||
description,
|
||||
titleKey,
|
||||
customActionNameKey: 'notify.newDeviceAction',
|
||||
customActionHandler: _useDevice.bind(undefined, store, newDevice)
|
||||
customActionHandler: _useDevice.bind(undefined, store, devicesArray)
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -139,47 +156,49 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {MediaDeviceInfo} device - The device to save.
|
||||
* @param {Array<MediaDeviceInfo|InputDeviceInfo>} devices - The devices to save.
|
||||
* @returns {boolean} - Returns true in order notifications to be dismissed.
|
||||
* @private
|
||||
*/
|
||||
function _useDevice({ dispatch }, device) {
|
||||
switch (device.kind) {
|
||||
case 'videoinput': {
|
||||
dispatch(updateSettings({
|
||||
userSelectedCameraDeviceId: device.deviceId,
|
||||
userSelectedCameraDeviceLabel: device.label
|
||||
}));
|
||||
function _useDevice({ dispatch }, devices) {
|
||||
devices.forEach(device => {
|
||||
switch (device.kind) {
|
||||
case 'videoinput': {
|
||||
dispatch(updateSettings({
|
||||
userSelectedCameraDeviceId: device.deviceId,
|
||||
userSelectedCameraDeviceLabel: device.label
|
||||
}));
|
||||
|
||||
dispatch(setVideoInputDevice(device.deviceId));
|
||||
break;
|
||||
}
|
||||
case 'audioinput': {
|
||||
dispatch(updateSettings({
|
||||
userSelectedMicDeviceId: device.deviceId,
|
||||
userSelectedMicDeviceLabel: device.label
|
||||
}));
|
||||
dispatch(setVideoInputDevice(device.deviceId));
|
||||
break;
|
||||
}
|
||||
case 'audioinput': {
|
||||
dispatch(updateSettings({
|
||||
userSelectedMicDeviceId: device.deviceId,
|
||||
userSelectedMicDeviceLabel: device.label
|
||||
}));
|
||||
|
||||
dispatch(setAudioInputDevice(device.deviceId));
|
||||
break;
|
||||
}
|
||||
case 'audiooutput': {
|
||||
setAudioOutputDeviceId(
|
||||
device.deviceId,
|
||||
dispatch,
|
||||
true,
|
||||
device.label)
|
||||
.then(() => logger.log('changed audio output device'))
|
||||
.catch(err => {
|
||||
logger.warn(
|
||||
'Failed to change audio output device.',
|
||||
'Default or previously set audio output device will',
|
||||
' be used instead.',
|
||||
err);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(setAudioInputDevice(device.deviceId));
|
||||
break;
|
||||
}
|
||||
case 'audiooutput': {
|
||||
setAudioOutputDeviceId(
|
||||
device.deviceId,
|
||||
dispatch,
|
||||
true,
|
||||
device.label)
|
||||
.then(() => logger.log('changed audio output device'))
|
||||
.catch(err => {
|
||||
logger.warn(
|
||||
'Failed to change audio output device.',
|
||||
'Default or previously set audio output device will',
|
||||
' be used instead.',
|
||||
err);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,12 @@ type Props = BaseProps & {
|
||||
|
||||
t: Function,
|
||||
|
||||
textInputProps: ?Object
|
||||
textInputProps: ?Object,
|
||||
|
||||
/**
|
||||
* Validating of the input.
|
||||
*/
|
||||
validateInput: ?Function
|
||||
}
|
||||
|
||||
type State = {
|
||||
@@ -118,6 +123,12 @@ class InputDialog extends BaseDialog<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChangeText(fieldValue) {
|
||||
|
||||
if (this.props.validateInput
|
||||
&& !this.props.validateInput(fieldValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
fieldValue
|
||||
});
|
||||
|
||||
@@ -260,15 +260,37 @@ function _getAllParticipants(stateful) {
|
||||
: toState(stateful)['features/base/participants'] || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all of the meeting participants are moderators.
|
||||
*
|
||||
* @param {Object|Function} stateful -Object or function that can be resolved
|
||||
* to the Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isEveryoneModerator(stateful: Object | Function) {
|
||||
const participants = _getAllParticipants(stateful);
|
||||
|
||||
for (const participant of participants) {
|
||||
if (participant.role !== PARTICIPANT_ROLE.MODERATOR) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current local participant is a moderator in the
|
||||
* conference.
|
||||
*
|
||||
* @param {Object|Function} stateful - Object or function that can be resolved
|
||||
* to the Redux state.
|
||||
* @param {?boolean} ignoreToken - When true we ignore the token check.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalParticipantModerator(stateful: Object | Function) {
|
||||
export function isLocalParticipantModerator(
|
||||
stateful: Object | Function,
|
||||
ignoreToken: ?boolean = false) {
|
||||
const state = toState(stateful);
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
@@ -278,7 +300,8 @@ export function isLocalParticipantModerator(stateful: Object | Function) {
|
||||
|
||||
return (
|
||||
localParticipant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& (!state['features/base/config'].enableUserRolesBasedOnToken
|
||||
&& (ignoreToken
|
||||
|| !state['features/base/config'].enableUserRolesBasedOnToken
|
||||
|| !state['features/base/jwt'].isGuest));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { getLocalizedDateFormatter } from '../../base/i18n';
|
||||
import { getAvatarURLByParticipantId } from '../../base/participants';
|
||||
|
||||
/**
|
||||
* Formatter string to display the message timestamp.
|
||||
*/
|
||||
const TIMESTAMP_FORMAT = 'H:mm';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code AbstractChatMessage}.
|
||||
*/
|
||||
@@ -19,6 +25,24 @@ export type Props = {
|
||||
*/
|
||||
message: Object,
|
||||
|
||||
/**
|
||||
* Whether or not the avatar image of the participant which sent the message
|
||||
* should be displayed.
|
||||
*/
|
||||
showAvatar: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the name of the participant which sent the message should
|
||||
* be displayed.
|
||||
*/
|
||||
showDisplayName: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the time at which the message was sent should be
|
||||
* displayed.
|
||||
*/
|
||||
showTimestamp: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to receive translated strings.
|
||||
*/
|
||||
@@ -28,7 +52,17 @@ export type Props = {
|
||||
/**
|
||||
* Abstract component to display a chat message.
|
||||
*/
|
||||
export default class AbstractChatMessage<P: Props> extends PureComponent<P> {}
|
||||
export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
|
||||
/**
|
||||
* Returns the timestamp to display for the message.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getFormattedTimestamp() {
|
||||
return getLocalizedDateFormatter(new Date(this.props.message.timestamp))
|
||||
.format(TIMESTAMP_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
|
||||
53
react/features/chat/components/AbstractMessageContainer.js
Normal file
53
react/features/chat/components/AbstractMessageContainer.js
Normal file
@@ -0,0 +1,53 @@
|
||||
// @flow
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The messages array to render.
|
||||
*/
|
||||
messages: Array<Object>
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract component to display a list of chat messages, grouped by sender.
|
||||
*
|
||||
* @extends PureComponent
|
||||
*/
|
||||
export default class AbstractMessageContainer extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
messages: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterates over all the messages and creates nested arrays which hold
|
||||
* consecutive messages sent by the same participant.
|
||||
*
|
||||
* @private
|
||||
* @returns {Array<Array<Object>>}
|
||||
*/
|
||||
_getMessagesGroupedBySender() {
|
||||
const messagesCount = this.props.messages.length;
|
||||
const groups = [];
|
||||
let currentGrouping = [];
|
||||
let currentGroupParticipantId;
|
||||
|
||||
for (let i = 0; i < messagesCount; i++) {
|
||||
const message = this.props.messages[i];
|
||||
|
||||
if (message.id === currentGroupParticipantId) {
|
||||
currentGrouping.push(message);
|
||||
} else {
|
||||
currentGrouping.length && groups.push(currentGrouping);
|
||||
|
||||
currentGrouping = [ message ];
|
||||
currentGroupParticipantId = message.id;
|
||||
}
|
||||
}
|
||||
|
||||
groups.push(currentGrouping);
|
||||
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { getLocalizedDateFormatter, translate } from '../../../base/i18n';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Avatar } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
@@ -13,16 +13,6 @@ import AbstractChatMessage, {
|
||||
} from '../AbstractChatMessage';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Size of the rendered avatar in the message.
|
||||
*/
|
||||
const AVATAR_SIZE = 32;
|
||||
|
||||
/**
|
||||
* Formatter string to display the message timestamp.
|
||||
*/
|
||||
const TIMESTAMP_FORMAT = 'H:mm';
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*/
|
||||
@@ -34,8 +24,6 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
*/
|
||||
render() {
|
||||
const { message } = this.props;
|
||||
const timeStamp = getLocalizedDateFormatter(
|
||||
new Date(message.timestamp)).format(TIMESTAMP_FORMAT);
|
||||
const localMessage = message.messageType === 'local';
|
||||
|
||||
// Style arrays that need to be updated in various scenarios, such as
|
||||
@@ -60,18 +48,12 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
|
||||
return (
|
||||
<View style = { styles.messageWrapper } >
|
||||
{
|
||||
|
||||
// Avatar is only rendered for remote messages.
|
||||
!localMessage && this._renderAvatar()
|
||||
}
|
||||
{ this._renderAvatar() }
|
||||
<View style = { detailsWrapperStyle }>
|
||||
<View style = { textWrapperStyle } >
|
||||
{
|
||||
|
||||
// Display name is only rendered for remote
|
||||
// messages.
|
||||
!localMessage && this._renderDisplayName()
|
||||
this.props.showDisplayName
|
||||
&& this._renderDisplayName()
|
||||
}
|
||||
<Text style = { styles.messageText }>
|
||||
{ message.messageType === 'error'
|
||||
@@ -82,27 +64,26 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
: message.message }
|
||||
</Text>
|
||||
</View>
|
||||
<Text style = { styles.timeText }>
|
||||
{ timeStamp }
|
||||
</Text>
|
||||
{ this.props.showTimestamp && this._renderTimestamp() }
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_getFormattedTimestamp: () => string;
|
||||
|
||||
/**
|
||||
* Renders the avatar of the sender.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderAvatar() {
|
||||
const { _avatarURL } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.avatarWrapper }>
|
||||
<Avatar
|
||||
size = { AVATAR_SIZE }
|
||||
uri = { _avatarURL } />
|
||||
{ this.props.showAvatar && <Avatar
|
||||
size = { styles.avatarWrapper.width }
|
||||
uri = { this.props._avatarURL } />
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -113,11 +94,22 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderDisplayName() {
|
||||
const { message } = this.props;
|
||||
|
||||
return (
|
||||
<Text style = { styles.displayName }>
|
||||
{ message.displayName }
|
||||
{ this.props.message.displayName }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the time at which the message was sent.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderTimestamp() {
|
||||
return (
|
||||
<Text style = { styles.timeText }>
|
||||
{ this._getFormattedTimestamp() }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
86
react/features/chat/components/native/ChatMessageGroup.js
Normal file
86
react/features/chat/components/native/ChatMessageGroup.js
Normal file
@@ -0,0 +1,86 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The messages array to render.
|
||||
*/
|
||||
messages: Array<Object>
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a container to render all the chat messages in a conference.
|
||||
*/
|
||||
export default class ChatMessageGroup extends Component<Props> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
this._renderMessage = this._renderMessage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<FlatList
|
||||
data = { this.props.messages }
|
||||
inverted = { true }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
renderItem = { this._renderMessage }
|
||||
style = { styles.messageContainer } />
|
||||
);
|
||||
}
|
||||
|
||||
_keyExtractor: Object => string
|
||||
|
||||
/**
|
||||
* Key extractor for the flatlist.
|
||||
*
|
||||
* @param {Object} item - The flatlist item that we need the key to be
|
||||
* generated for.
|
||||
* @param {number} index - The index of the element.
|
||||
* @returns {string}
|
||||
*/
|
||||
_keyExtractor(item, index) {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
_renderMessage: Object => React$Element<*>;
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*
|
||||
* @param {Object} message - The chat message to render.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderMessage({ index, item: message }) {
|
||||
return (
|
||||
<ChatMessage
|
||||
message = { message }
|
||||
showAvatar = {
|
||||
this.props.messages[0].messageType !== 'local'
|
||||
&& index === this.props.messages.length - 1
|
||||
}
|
||||
showDisplayName = {
|
||||
this.props.messages[0].messageType === 'remote'
|
||||
&& index === this.props.messages.length - 1
|
||||
}
|
||||
showTimestamp = { index === 0 } />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,18 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
import AbstractMessageContainer, { type Props }
|
||||
from '../AbstractMessageContainer';
|
||||
|
||||
import ChatMessageGroup from './ChatMessageGroup';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The messages array to render.
|
||||
*/
|
||||
messages: Array<Object>
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a container to render all the chat messages in a conference.
|
||||
*/
|
||||
export default class MessageContainer extends Component<Props> {
|
||||
export default class MessageContainer extends AbstractMessageContainer {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
@@ -27,7 +22,7 @@ export default class MessageContainer extends Component<Props> {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
this._renderMessage = this._renderMessage.bind(this);
|
||||
this._renderMessageGroup = this._renderMessageGroup.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,14 +33,16 @@ export default class MessageContainer extends Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<FlatList
|
||||
data = { this.props.messages }
|
||||
data = { this._getMessagesGroupedBySender() }
|
||||
inverted = { true }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
renderItem = { this._renderMessage }
|
||||
renderItem = { this._renderMessageGroup }
|
||||
style = { styles.messageContainer } />
|
||||
);
|
||||
}
|
||||
|
||||
_getMessagesGroupedBySender: () => Array<Array<Object>>;
|
||||
|
||||
_keyExtractor: Object => string
|
||||
|
||||
/**
|
||||
@@ -60,17 +57,15 @@ export default class MessageContainer extends Component<Props> {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
_renderMessage: Object => React$Element<*>;
|
||||
_renderMessageGroup: Object => React$Element<*>;
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*
|
||||
* @param {Object} message - The chat message to render.
|
||||
* @param {Array<Object>} messages - The chat message to render.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderMessage({ item: message }) {
|
||||
return (
|
||||
<ChatMessage message = { message } />
|
||||
);
|
||||
_renderMessageGroup({ item: messages }) {
|
||||
return <ChatMessageGroup messages = { messages } />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ export default {
|
||||
* Wrapper View for the avatar.
|
||||
*/
|
||||
avatarWrapper: {
|
||||
marginRight: 8
|
||||
marginRight: 8,
|
||||
width: 32
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,8 +12,8 @@ import AbstractChat, {
|
||||
type Props
|
||||
} from '../AbstractChat';
|
||||
import ChatInput from './ChatInput';
|
||||
import ChatMessage from './ChatMessage';
|
||||
import DisplayNameForm from './DisplayNameForm';
|
||||
import MessageContainer from './MessageContainer';
|
||||
|
||||
/**
|
||||
* React Component for holding the chat feature in a side panel that slides in
|
||||
@@ -28,10 +28,10 @@ class Chat extends AbstractChat<Props> {
|
||||
_isExited: boolean;
|
||||
|
||||
/**
|
||||
* Reference to the HTML element at the end of the list of displayed chat
|
||||
* messages. Used for scrolling to the end of the chat messages.
|
||||
* Reference to the React Component for displaying chat messages. Used for
|
||||
* scrolling to the end of the chat messages.
|
||||
*/
|
||||
_messagesListEnd: ?HTMLElement;
|
||||
_messageContainerRef: Object;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Chat} instance.
|
||||
@@ -43,32 +43,34 @@ class Chat extends AbstractChat<Props> {
|
||||
super(props);
|
||||
|
||||
this._isExited = true;
|
||||
this._messagesListEnd = null;
|
||||
this._messageContainerRef = React.createRef();
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._renderMessage = this._renderMessage.bind(this);
|
||||
this._renderPanelContent = this._renderPanelContent.bind(this);
|
||||
this._setMessageListEndRef = this._setMessageListEndRef.bind(this);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onChatInputResize = this._onChatInputResize.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount()}.
|
||||
* Implements {@code Component#componentDidMount}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._scrollMessagesToBottom();
|
||||
this._scrollMessageContainerToBottom(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates chat input focus.
|
||||
* Implements {@code Component#componentDidUpdate}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props._messages !== prevProps._messages) {
|
||||
this._scrollMessagesToBottom();
|
||||
|
||||
this._scrollMessageContainerToBottom(true);
|
||||
} else if (this.props._isOpen && !prevProps._isOpen) {
|
||||
this._scrollMessageContainerToBottom(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +90,19 @@ class Chat extends AbstractChat<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_onChatInputResize: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked when {@code ChatInput} changes height. Preserves
|
||||
* displaying the latest message if it is scrolled to.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChatInputResize() {
|
||||
this._messageContainerRef.current.maybeUpdateBottomScroll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a React Element for showing chat messages and a form to send new
|
||||
* chat messages.
|
||||
@@ -96,18 +111,12 @@ class Chat extends AbstractChat<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderChat() {
|
||||
const messages = this.props._messages.map(this._renderMessage);
|
||||
|
||||
messages.push(<div
|
||||
key = 'end-marker'
|
||||
ref = { this._setMessageListEndRef } />);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id = 'chatconversation'>
|
||||
{ messages }
|
||||
</div>
|
||||
<ChatInput />
|
||||
<MessageContainer
|
||||
messages = { this.props._messages }
|
||||
ref = { this._messageContainerRef } />
|
||||
<ChatInput onResize = { this._onChatInputResize } />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -129,23 +138,6 @@ class Chat extends AbstractChat<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_renderMessage: (Object) => void;
|
||||
|
||||
/**
|
||||
* Called by {@code _onSubmitMessage} to create the chat div.
|
||||
*
|
||||
* @param {string} message - The chat message to display.
|
||||
* @param {string} id - The chat message ID to use as a unique key.
|
||||
* @returns {Array<ReactElement>}
|
||||
*/
|
||||
_renderMessage(message: Object, id: string) {
|
||||
return (
|
||||
<ChatMessage
|
||||
key = { id }
|
||||
message = { message } />
|
||||
);
|
||||
}
|
||||
|
||||
_renderPanelContent: (string) => React$Node | null;
|
||||
|
||||
/**
|
||||
@@ -188,31 +180,18 @@ class Chat extends AbstractChat<Props> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically scrolls the displayed chat messages down to the latest.
|
||||
* Scrolls the chat messages so the latest message is visible.
|
||||
*
|
||||
* @param {boolean} withAnimation - Whether or not to show a scrolling
|
||||
* animation.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_scrollMessagesToBottom() {
|
||||
if (this._messagesListEnd) {
|
||||
this._messagesListEnd.scrollIntoView({
|
||||
behavior: this._isExited ? 'auto' : 'smooth'
|
||||
});
|
||||
_scrollMessageContainerToBottom(withAnimation) {
|
||||
if (this._messageContainerRef.current) {
|
||||
this._messageContainerRef.current.scrollToBottom(withAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
_setMessageListEndRef: (?HTMLElement) => void;
|
||||
|
||||
/**
|
||||
* Sets a reference to the HTML element at the bottom of the message list.
|
||||
*
|
||||
* @param {Object} messageListEnd - The HTML element.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setMessageListEndRef(messageListEnd: ?HTMLElement) {
|
||||
this._messagesListEnd = messageListEnd;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import Emoji from 'react-emoji-render';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
@@ -22,9 +23,10 @@ type Props = {
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Optional callback to get a reference to the chat input element.
|
||||
* Optional callback to invoke when the chat textarea has auto-resized to
|
||||
* fit overflowing text.
|
||||
*/
|
||||
getChatInputRef?: Function,
|
||||
onResize: ?Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
@@ -90,7 +92,7 @@ class ChatInput extends Component<Props, State> {
|
||||
* HTML Textareas do not support autofocus. Simulate autofocus by
|
||||
* manually focusing.
|
||||
*/
|
||||
this.focus();
|
||||
this._focus();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,33 +121,27 @@ class ChatInput extends Component<Props, State> {
|
||||
</div>
|
||||
</div>
|
||||
<div className = 'usrmsg-form'>
|
||||
<textarea
|
||||
<TextareaAutosize
|
||||
id = 'usermsg'
|
||||
inputRef = { this._setTextAreaRef }
|
||||
maxRows = { 5 }
|
||||
onChange = { this._onMessageChange }
|
||||
onHeightChange = { this.props.onResize }
|
||||
onKeyDown = { this._onDetectSubmit }
|
||||
placeholder = { this.props.t('chat.messagebox') }
|
||||
ref = { this._setTextAreaRef }
|
||||
value = { this.state.message } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes cursor focus on this component's text area.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
blur() {
|
||||
this._textArea && this._textArea.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* Place cursor focus on this component's text area.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
focus() {
|
||||
_focus() {
|
||||
this._textArea && this._textArea.focus();
|
||||
}
|
||||
|
||||
@@ -203,7 +199,7 @@ class ChatInput extends Component<Props, State> {
|
||||
showSmileysPanel: false
|
||||
});
|
||||
|
||||
this.focus();
|
||||
this._focus();
|
||||
}
|
||||
|
||||
_onToggleSmileysPanel: () => void;
|
||||
@@ -217,7 +213,7 @@ class ChatInput extends Component<Props, State> {
|
||||
_onToggleSmileysPanel() {
|
||||
this.setState({ showSmileysPanel: !this.state.showSmileysPanel });
|
||||
|
||||
this.focus();
|
||||
this._focus();
|
||||
}
|
||||
|
||||
_setTextAreaRef: (?HTMLTextAreaElement) => void;
|
||||
@@ -231,10 +227,6 @@ class ChatInput extends Component<Props, State> {
|
||||
*/
|
||||
_setTextAreaRef(textAreaElement: ?HTMLTextAreaElement) {
|
||||
this._textArea = textAreaElement;
|
||||
|
||||
if (this.props.getChatInputRef) {
|
||||
this.props.getChatInputRef(textAreaElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,24 +23,12 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
*/
|
||||
render() {
|
||||
const { message } = this.props;
|
||||
let messageTypeClassname = '';
|
||||
let messageToDisplay = message.message;
|
||||
|
||||
switch (message.messageType) {
|
||||
case 'local':
|
||||
messageTypeClassname = 'localuser';
|
||||
|
||||
break;
|
||||
case 'error':
|
||||
messageTypeClassname = 'error';
|
||||
messageToDisplay = this.props.t('chat.error', {
|
||||
const messageToDisplay = message.messageType === 'error'
|
||||
? this.props.t('chat.error', {
|
||||
error: message.error,
|
||||
originalText: messageToDisplay
|
||||
});
|
||||
break;
|
||||
default:
|
||||
messageTypeClassname = 'remoteuser';
|
||||
}
|
||||
originalText: message.message
|
||||
})
|
||||
: message.message;
|
||||
|
||||
// replace links and smileys
|
||||
// Strophe already escapes special symbols on sending,
|
||||
@@ -68,46 +56,44 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className = { `chatmessage ${messageTypeClassname}` }>
|
||||
<div className = 'display-name'>
|
||||
{ message.displayName }
|
||||
</div>
|
||||
<div className = { 'timestamp' }>
|
||||
{ ChatMessage.formatTimestamp(message.timestamp) }
|
||||
</div>
|
||||
<div className = 'usermessage'>
|
||||
{ processedMessage }
|
||||
<div className = 'chatmessage-wrapper'>
|
||||
<div className = 'chatmessage'>
|
||||
{ this.props.showDisplayName && this._renderDisplayName() }
|
||||
<div className = 'usermessage'>
|
||||
{ processedMessage }
|
||||
</div>
|
||||
</div>
|
||||
{ this.props.showTimestamp && this._renderTimestamp() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_getFormattedTimestamp: () => string;
|
||||
|
||||
/**
|
||||
* Renders the display name of the sender.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderDisplayName() {
|
||||
return (
|
||||
<div className = 'display-name'>
|
||||
{ this.props.message.displayName }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timestamp formatted for display.
|
||||
* Renders the time at which the message was sent.
|
||||
*
|
||||
* @param {number} timestamp - The timestamp for the chat message.
|
||||
* @private
|
||||
* @returns {string}
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
static formatTimestamp(timestamp) {
|
||||
const now = new Date(timestamp);
|
||||
let hour = now.getHours();
|
||||
let minute = now.getMinutes();
|
||||
let second = now.getSeconds();
|
||||
|
||||
if (hour.toString().length === 1) {
|
||||
hour = `0${hour}`;
|
||||
}
|
||||
|
||||
if (minute.toString().length === 1) {
|
||||
minute = `0${minute}`;
|
||||
}
|
||||
|
||||
if (second.toString().length === 1) {
|
||||
second = `0${second}`;
|
||||
}
|
||||
|
||||
return `${hour}:${minute}:${second}`;
|
||||
_renderTimestamp() {
|
||||
return (
|
||||
<div className = 'timestamp'>
|
||||
{ this._getFormattedTimestamp() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
60
react/features/chat/components/web/ChatMessageGroup.js
Normal file
60
react/features/chat/components/web/ChatMessageGroup.js
Normal file
@@ -0,0 +1,60 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import ChatMessage from './ChatMessage';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Additional CSS classes to apply to the root element.
|
||||
*/
|
||||
className: string,
|
||||
|
||||
/**
|
||||
* The messages to display as a group.
|
||||
*/
|
||||
messages: Array<Object>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays a list of chat messages. Will show only the display name for the
|
||||
* first chat message and the timestamp for the last chat message.
|
||||
*
|
||||
* @extends React.Component
|
||||
*/
|
||||
class ChatMessageGroup extends Component<Props> {
|
||||
static defaultProps = {
|
||||
className: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { className, messages } = this.props;
|
||||
|
||||
const messagesLength = messages.length;
|
||||
|
||||
if (!messagesLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = { `chat-message-group ${className}` }>
|
||||
{
|
||||
messages.map((message, i) => (
|
||||
<ChatMessage
|
||||
key = { i }
|
||||
message = { message }
|
||||
showDisplayName = { i === 0 }
|
||||
showTimestamp = { i === messages.length - 1 } />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatMessageGroup;
|
||||
123
react/features/chat/components/web/MessageContainer.js
Normal file
123
react/features/chat/components/web/MessageContainer.js
Normal file
@@ -0,0 +1,123 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import AbstractMessageContainer, { type Props }
|
||||
from '../AbstractMessageContainer';
|
||||
|
||||
import ChatMessageGroup from './ChatMessageGroup';
|
||||
|
||||
/**
|
||||
* Displays all received chat messages, grouped by sender.
|
||||
*
|
||||
* @extends AbstractMessageContainer
|
||||
*/
|
||||
export default class MessageContainer extends AbstractMessageContainer {
|
||||
/**
|
||||
* Whether or not chat has been scrolled to the bottom of the screen. Used
|
||||
* to determine if chat should be scrolled automatically to the bottom when
|
||||
* the {@code ChatInput} resizes.
|
||||
*/
|
||||
_isScrolledToBottom: boolean;
|
||||
|
||||
/**
|
||||
* Reference to the HTML element at the end of the list of displayed chat
|
||||
* messages. Used for scrolling to the end of the chat messages.
|
||||
*/
|
||||
_messagesListEndRef: Object;
|
||||
|
||||
/**
|
||||
* A React ref to the HTML element containing all {@code ChatMessageGroup}
|
||||
* instances.
|
||||
*/
|
||||
_messageListRef: Object;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code MessageContainer} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code MessageContainer} instance with.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._isScrolledToBottom = true;
|
||||
|
||||
this._messageListRef = React.createRef();
|
||||
this._messagesListEndRef = React.createRef();
|
||||
|
||||
this._onChatScroll = this._onChatScroll.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const groupedMessages = this._getMessagesGroupedBySender();
|
||||
const messages = groupedMessages.map((group, index) => {
|
||||
const messageType = group[0] && group[0].messageType;
|
||||
|
||||
return (
|
||||
<ChatMessageGroup
|
||||
className = { messageType || 'remote' }
|
||||
key = { index }
|
||||
messages = { group } />
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
id = 'chatconversation'
|
||||
onScroll = { this._onChatScroll }
|
||||
ref = { this._messageListRef }>
|
||||
{ messages }
|
||||
<div ref = { this._messagesListEndRef } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to the bottom again if the instance had previously been scrolled
|
||||
* to the bottom. This method is used when a resize has occurred below the
|
||||
* instance and bottom scroll needs to be maintained.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
maybeUpdateBottomScroll() {
|
||||
if (this._isScrolledToBottom) {
|
||||
this.scrollToBottom(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically scrolls the displayed chat messages down to the latest.
|
||||
*
|
||||
* @param {boolean} withAnimation - Whether or not to show a scrolling
|
||||
* animation.
|
||||
* @returns {void}
|
||||
*/
|
||||
scrollToBottom(withAnimation: boolean) {
|
||||
this._messagesListEndRef.current.scrollIntoView({
|
||||
behavior: withAnimation ? 'smooth' : 'auto'
|
||||
});
|
||||
}
|
||||
|
||||
_getMessagesGroupedBySender: () => Array<Array<Object>>;
|
||||
|
||||
_onChatScroll: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to listen to the current scroll location.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChatScroll() {
|
||||
const element = this._messageListRef.current;
|
||||
|
||||
this._isScrolledToBottom
|
||||
= element.scrollHeight - element.scrollTop === element.clientHeight;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { Audio, MEDIA_TYPE } from '../../../base/media';
|
||||
import {
|
||||
PARTICIPANT_ROLE,
|
||||
ParticipantView,
|
||||
isEveryoneModerator,
|
||||
isLocalParticipantModerator,
|
||||
pinParticipant
|
||||
} from '../../../base/participants';
|
||||
@@ -38,6 +39,11 @@ type Props = {
|
||||
*/
|
||||
_audioTrack: Object,
|
||||
|
||||
/**
|
||||
* True if everone in the meeting is moderator.
|
||||
*/
|
||||
_isEveryoneModerator: boolean,
|
||||
|
||||
/**
|
||||
* True if the local participant is a moderator.
|
||||
*/
|
||||
@@ -117,6 +123,7 @@ class Thumbnail extends Component<Props> {
|
||||
render() {
|
||||
const {
|
||||
_audioTrack: audioTrack,
|
||||
_isEveryoneModerator,
|
||||
_isModerator,
|
||||
_largeVideo: largeVideo,
|
||||
_onClick,
|
||||
@@ -172,7 +179,7 @@ class Thumbnail extends Component<Props> {
|
||||
|
||||
{ renderDisplayName && <DisplayNameLabel participantId = { participantId } /> }
|
||||
|
||||
{ participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
{ !_isEveryoneModerator && participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& <View style = { styles.moderatorIndicatorContainer }>
|
||||
<ModeratorIndicator />
|
||||
</View> }
|
||||
@@ -275,6 +282,7 @@ function _mapStateToProps(state, ownProps) {
|
||||
|
||||
return {
|
||||
_audioTrack: audioTrack,
|
||||
_isEveryoneModerator: isEveryoneModerator(state),
|
||||
_isModerator: isLocalParticipantModerator(state),
|
||||
_largeVideo: largeVideo,
|
||||
_styles: ColorSchemeRegistry.get(state, 'Thumbnail'),
|
||||
|
||||
@@ -8,7 +8,10 @@ import { getInviteURL } from '../../../../base/connection';
|
||||
import { Dialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { isLocalParticipantModerator } from '../../../../base/participants';
|
||||
import {
|
||||
isLocalParticipantModerator,
|
||||
getLocalParticipant
|
||||
} from '../../../../base/participants';
|
||||
|
||||
import { _getDefaultPhoneNumber, getDialInfoPageURL } from '../../../functions';
|
||||
import DialInNumber from './DialInNumber';
|
||||
@@ -37,11 +40,21 @@ type Props = {
|
||||
*/
|
||||
_conferenceName: string,
|
||||
|
||||
/**
|
||||
* The number of digits to be used in the password.
|
||||
*/
|
||||
_passwordNumberOfDigits: ?number,
|
||||
|
||||
/**
|
||||
* The current url of the conference to be copied onto the clipboard.
|
||||
*/
|
||||
_inviteURL: string,
|
||||
|
||||
/**
|
||||
* The redux representation of the local participant.
|
||||
*/
|
||||
_localParticipant: Object,
|
||||
|
||||
/**
|
||||
* The current location url of the conference.
|
||||
*/
|
||||
@@ -237,7 +250,8 @@ class InfoDialog extends Component<Props, State> {
|
||||
editEnabled = { this.state.passwordEditEnabled }
|
||||
locked = { this.props._locked }
|
||||
onSubmit = { this._onPasswordSubmit }
|
||||
password = { this.props._password } />
|
||||
password = { this.props._password }
|
||||
passwordNumberOfDigits = { this.props._passwordNumberOfDigits } />
|
||||
</div>
|
||||
<div className = 'info-dialog-action-links'>
|
||||
<div className = 'info-dialog-action-link'>
|
||||
@@ -293,14 +307,18 @@ class InfoDialog extends Component<Props, State> {
|
||||
* @returns {string}
|
||||
*/
|
||||
_getTextToCopy() {
|
||||
const { liveStreamViewURL, t } = this.props;
|
||||
const { _localParticipant, liveStreamViewURL, t } = this.props;
|
||||
const shouldDisplayDialIn = this._shouldDisplayDialIn();
|
||||
const moreInfo
|
||||
= shouldDisplayDialIn
|
||||
? t('info.inviteURLMoreInfo', { conferenceID: this.props.dialIn.conferenceID })
|
||||
: '';
|
||||
|
||||
let invite = t('info.inviteURL', {
|
||||
let invite = _localParticipant && _localParticipant.name
|
||||
? t('info.inviteURLFirstPartPersonal', { name: _localParticipant.name })
|
||||
: t('info.inviteURLFirstPartGeneral');
|
||||
|
||||
invite += t('info.inviteURLSecondPart', {
|
||||
url: this.props._inviteURL,
|
||||
moreInfo
|
||||
});
|
||||
@@ -576,10 +594,12 @@ function _mapStateToProps(state) {
|
||||
} = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_canEditPassword: isLocalParticipantModerator(state),
|
||||
_canEditPassword: isLocalParticipantModerator(state, state['features/base/config'].lockRoomGuestEnabled),
|
||||
_conference: conference,
|
||||
_conferenceName: room,
|
||||
_passwordNumberOfDigits: state['features/base/config'].roomPasswordNumberOfDigits,
|
||||
_inviteURL: getInviteURL(state),
|
||||
_localParticipant: getLocalParticipant(state),
|
||||
_locationURL: state['features/base/connection'].locationURL,
|
||||
_locked: locked,
|
||||
_password: password
|
||||
|
||||
@@ -32,6 +32,11 @@ type Props = {
|
||||
*/
|
||||
password: string,
|
||||
|
||||
/**
|
||||
* The number of digits to be used in the password.
|
||||
*/
|
||||
passwordNumberOfDigits: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
@@ -117,6 +122,14 @@ class PasswordForm extends Component<Props, State> {
|
||||
*/
|
||||
_renderPasswordField() {
|
||||
if (this.props.editEnabled) {
|
||||
let digitPattern, placeHolderText;
|
||||
|
||||
if (this.props.passwordNumberOfDigits) {
|
||||
placeHolderText = this.props.t('passwordDigitsOnly', {
|
||||
number: this.props.passwordNumberOfDigits });
|
||||
digitPattern = '\\d*';
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
className = 'info-password-form'
|
||||
@@ -125,7 +138,10 @@ class PasswordForm extends Component<Props, State> {
|
||||
<input
|
||||
autoFocus = { true }
|
||||
className = 'info-password-input'
|
||||
maxLength = { this.props.passwordNumberOfDigits }
|
||||
onChange = { this._onEnteredPasswordChange }
|
||||
pattern = { digitPattern }
|
||||
placeholder = { placeHolderText }
|
||||
spellCheck = { 'false' }
|
||||
type = 'text'
|
||||
value = { this.state.enteredPassword } />
|
||||
|
||||
@@ -4,11 +4,12 @@ import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { NativeModules, Text, TouchableHighlight, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { hideDialog, BottomSheet } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon } from '../../../base/font-icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { ColorPalette } from '../../../base/styles';
|
||||
import { ColorPalette, type StyleType } from '../../../base/styles';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
@@ -44,6 +45,11 @@ type Device = {
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Style of the bottom sheet feature.
|
||||
*/
|
||||
_bottomSheetStyles: StyleType,
|
||||
|
||||
/**
|
||||
* Used for hiding the dialog when the selection was completed.
|
||||
*/
|
||||
@@ -203,6 +209,7 @@ class AudioRoutePickerDialog extends Component<Props, State> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderDevice(device: Device) {
|
||||
const { _bottomSheetStyles } = this.props;
|
||||
const { iconName, selected, text } = device;
|
||||
const selectedStyle = selected ? styles.selectedText : {};
|
||||
|
||||
@@ -214,8 +221,8 @@ class AudioRoutePickerDialog extends Component<Props, State> {
|
||||
<View style = { styles.deviceRow } >
|
||||
<Icon
|
||||
name = { iconName }
|
||||
style = { [ styles.deviceIcon, selectedStyle ] } />
|
||||
<Text style = { [ styles.deviceText, selectedStyle ] } >
|
||||
style = { [ styles.deviceIcon, _bottomSheetStyles.iconStyle, selectedStyle ] } />
|
||||
<Text style = { [ styles.deviceText, _bottomSheetStyles.labelStyle, selectedStyle ] } >
|
||||
{ text }
|
||||
</Text>
|
||||
</View>
|
||||
@@ -244,10 +251,22 @@ class AudioRoutePickerDialog extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet')
|
||||
};
|
||||
}
|
||||
|
||||
// Only export the dialog if we have support for getting / setting audio devices
|
||||
// in AudioMode.
|
||||
if (AudioMode.getAudioDevices && AudioMode.setAudioDevice) {
|
||||
AudioRoutePickerDialog_ = translate(connect()(AudioRoutePickerDialog));
|
||||
AudioRoutePickerDialog_ = translate(connect(_mapStateToProps)(AudioRoutePickerDialog));
|
||||
}
|
||||
|
||||
export default AudioRoutePickerDialog_;
|
||||
|
||||
@@ -66,7 +66,10 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
onChangeText = { this._onInputChange }
|
||||
placeholder = { t('liveStreaming.enterStreamKey') }
|
||||
placeholderTextColor = { PLACEHOLDER_COLOR }
|
||||
style = { styles.streamKeyInput }
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
styles.streamKeyInput
|
||||
] }
|
||||
value = { this.props.value } />
|
||||
<View style = { styles.formFooter }>
|
||||
{
|
||||
|
||||
@@ -90,9 +90,10 @@ export default createStyleSheet({
|
||||
alignSelf: 'stretch',
|
||||
borderColor: ColorPalette.lightGrey,
|
||||
borderBottomWidth: 1,
|
||||
color: ColorPalette.white,
|
||||
fontSize: 14,
|
||||
height: 40,
|
||||
marginBottom: 5
|
||||
marginBottom: 5,
|
||||
textAlign: 'left'
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,11 @@ export function beginRoomLockRequest(conference: ?Object) {
|
||||
conference = getState()['features/base/conference'].conference;
|
||||
}
|
||||
if (conference) {
|
||||
dispatch(openDialog(RoomLockPrompt, { conference }));
|
||||
const passwordNumberOfDigits = getState()['features/base/config'].roomPasswordNumberOfDigits;
|
||||
|
||||
dispatch(openDialog(RoomLockPrompt, {
|
||||
conference,
|
||||
passwordNumberOfDigits }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,10 +28,15 @@ type Props = {
|
||||
*/
|
||||
conference: Object,
|
||||
|
||||
/**
|
||||
* The number of digits to be used in the password.
|
||||
*/
|
||||
passwordNumberOfDigits: ?number,
|
||||
|
||||
/**
|
||||
* Redux store dispatch function.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -51,6 +56,7 @@ class RoomLockPrompt extends Component<Props> {
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._validateInput = this._validateInput.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,12 +66,23 @@ class RoomLockPrompt extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
let textInputProps = _TEXT_INPUT_PROPS;
|
||||
|
||||
if (this.props.passwordNumberOfDigits) {
|
||||
textInputProps = {
|
||||
...textInputProps,
|
||||
keyboardType: 'number-pad',
|
||||
maxLength: this.props.passwordNumberOfDigits
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<InputDialog
|
||||
contentKey = 'dialog.passwordLabel'
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onSubmit }
|
||||
textInputProps = { _TEXT_INPUT_PROPS } />
|
||||
textInputProps = { textInputProps }
|
||||
validateInput = { this._validateInput } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -100,6 +117,28 @@ class RoomLockPrompt extends Component<Props> {
|
||||
|
||||
return false; // Do not hide.
|
||||
}
|
||||
|
||||
_validateInput: (string) => boolean;
|
||||
|
||||
/**
|
||||
* Verifies input in case only digits are required.
|
||||
*
|
||||
* @param {string|undefined} value - The submitted value.
|
||||
* @private
|
||||
* @returns {boolean} False when the value is not valid and True otherwise.
|
||||
*/
|
||||
_validateInput(value: string) {
|
||||
|
||||
// we want only digits, but both number-pad and numeric add ',' and '.' as symbols
|
||||
if (this.props.passwordNumberOfDigits
|
||||
&& value.length > 0
|
||||
&& !/^\d+$/.test(value)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(RoomLockPrompt);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
SCREEN_SHARE_PARTICIPANTS_UPDATED,
|
||||
SET_TILE_VIEW
|
||||
@@ -39,3 +41,17 @@ export function setTileView(enabled: boolean) {
|
||||
enabled
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (redux) action which signals either to exit tile view if currently
|
||||
* enabled or enter tile view if currently disabled.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleTileView() {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const { tileViewEnabled } = getState()['features/video-layout'];
|
||||
|
||||
dispatch(setTileView(!tileViewEnabled));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { selectParticipant } from '../large-video';
|
||||
import { shouldDisplayTileView } from './functions';
|
||||
import { setParticipantsWithScreenShare } from './actions';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
@@ -35,6 +36,10 @@ StateListenerRegistry.register(
|
||||
_updateAutoPinnedParticipant(store);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof APP === 'object') {
|
||||
APP.API.notifyTileViewChanged(displayTileView);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -119,7 +119,9 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
className = { `welcome ${showAdditionalContent
|
||||
? 'with-content' : 'without-content'}` }
|
||||
id = 'welcome_page'>
|
||||
<Watermarks />
|
||||
<div className = 'welcome-watermark'>
|
||||
<Watermarks />
|
||||
</div>
|
||||
<div className = 'header'>
|
||||
<div className = 'welcome-page-settings'>
|
||||
<SettingsButton
|
||||
|
||||
@@ -26,8 +26,9 @@ module:hook("bosh-session", function(event)
|
||||
local params = formdecode(query);
|
||||
session.auth_token = query and params.token or nil;
|
||||
|
||||
-- The room name from the bosh query
|
||||
-- The room name and optional prefix from the bosh query
|
||||
session.jitsi_bosh_query_room = params.room;
|
||||
session.jitsi_bosh_query_prefix = params.prefix or "";
|
||||
end
|
||||
end);
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ prosody.events.add_handler("pre-jitsi-authentication", function(session)
|
||||
if (session.jitsi_meet_context_user) then
|
||||
local room = get_room(
|
||||
session.jitsi_bosh_query_room,
|
||||
session.jitsi_meet_domain);
|
||||
session.jitsi_bosh_query_prefix);
|
||||
|
||||
if (not room) then
|
||||
return nil;
|
||||
|
||||
Reference in New Issue
Block a user