mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-24 22:47:48 +00:00
Compare commits
70 Commits
8716
...
tests-wait
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11a6e94541 | ||
|
|
e1e262fb68 | ||
|
|
b0a96b32d2 | ||
|
|
dac9b5e244 | ||
|
|
d15cfd845a | ||
|
|
91e4ac1665 | ||
|
|
fda42e5230 | ||
|
|
142d4441c1 | ||
|
|
5814c4dda7 | ||
|
|
8c1dc03363 | ||
|
|
ff6fc198f1 | ||
|
|
f1a0012fc1 | ||
|
|
85522aea25 | ||
|
|
000c370c64 | ||
|
|
a762d585b8 | ||
|
|
ded8f22363 | ||
|
|
c3e1c9d568 | ||
|
|
8901132af9 | ||
|
|
71f92f6e17 | ||
|
|
76166df81a | ||
|
|
eb2ba39289 | ||
|
|
048b089acd | ||
|
|
b774f18f80 | ||
|
|
dbe4e6a784 | ||
|
|
d2e52d2c2a | ||
|
|
b5ad984dab | ||
|
|
81ce664ad7 | ||
|
|
181ef92e1f | ||
|
|
79dbc2d1ee | ||
|
|
f56ce78b9d | ||
|
|
8269b88796 | ||
|
|
252ef4604a | ||
|
|
fc816aa149 | ||
|
|
6de18fe82d | ||
|
|
5b7e3bb2d7 | ||
|
|
bc08b38791 | ||
|
|
6613f630d7 | ||
|
|
719b6d68c8 | ||
|
|
6a62c5120f | ||
|
|
64270f3015 | ||
|
|
cb621f8e32 | ||
|
|
3c80cfddd7 | ||
|
|
557f6defb8 | ||
|
|
52fa36f930 | ||
|
|
b050e5f5e8 | ||
|
|
bf8d83953b | ||
|
|
f16bf466eb | ||
|
|
29ea811527 | ||
|
|
435d034fdb | ||
|
|
419baa7ab7 | ||
|
|
9eb7b7bb01 | ||
|
|
19ee989cda | ||
|
|
ab1dcc5375 | ||
|
|
3047b4c8c4 | ||
|
|
2afce3d151 | ||
|
|
1cea9b1786 | ||
|
|
2b7299ae05 | ||
|
|
4b50f13e96 | ||
|
|
c639acebcf | ||
|
|
1a34ed9a2d | ||
|
|
0939e207eb | ||
|
|
8c3ea05ae6 | ||
|
|
daf8a929b1 | ||
|
|
2f3df2c66f | ||
|
|
d8d1f8331e | ||
|
|
0e69336f94 | ||
|
|
ede8ae6cb9 | ||
|
|
7e57156d2a | ||
|
|
6742435487 | ||
|
|
99f34aaef4 |
@@ -28,6 +28,14 @@ android {
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DANDROID_STL=c++_shared"
|
||||
cppFlags "-std=c++17"
|
||||
cFlags "-DANDROID_PLATFORM=android-26"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -46,8 +54,8 @@ android {
|
||||
applicationIdSuffix ".debug"
|
||||
}
|
||||
release {
|
||||
// Uncomment the following line for singing a test release build.
|
||||
//signingConfig signingConfigs.debug
|
||||
// Uncomment the following line for signing a test release build.
|
||||
// signingConfig signingConfigs.debug
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
|
||||
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
|
||||
@@ -102,8 +110,6 @@ dependencies {
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
// Dropbox integration
|
||||
//
|
||||
|
||||
def dropboxAppKey
|
||||
if (project.file('dropbox.key').exists()) {
|
||||
dropboxAppKey = project.file('dropbox.key').text.trim() - 'db-'
|
||||
@@ -174,7 +180,6 @@ gradle.projectsEvaluated {
|
||||
|
||||
packageTask.dependsOn(currentRunPackagerTask)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (googleServicesEnabled) {
|
||||
|
||||
@@ -6,6 +6,5 @@
|
||||
-keep public class * extends java.lang.Exception
|
||||
|
||||
# R8 missing classes - suppress warnings
|
||||
-dontwarn com.facebook.fresco.ui.common.LoggingListener
|
||||
-dontwarn com.facebook.memory.config.MemorySpikeConfig
|
||||
-dontwarn kotlinx.parcelize.Parcelize
|
||||
|
||||
5
android/app/proguard-rules.pro
vendored
5
android/app/proguard-rules.pro
vendored
@@ -96,8 +96,3 @@
|
||||
|
||||
# Rule to avoid build errors related to SVGs.
|
||||
-keep public class com.horcrux.svg.** {*;}
|
||||
|
||||
# https://github.com/facebook/fresco/issues/2638
|
||||
-keep public class com.facebook.imageutils.** {
|
||||
public *;
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import org.gradle.util.VersionNumber
|
||||
buildscript {
|
||||
ext {
|
||||
kotlinVersion = "2.0.21"
|
||||
gradlePluginVersion = "8.4.2"
|
||||
buildToolsVersion = "34.0.0"
|
||||
compileSdkVersion = 34
|
||||
gradlePluginVersion = "8.6.0"
|
||||
buildToolsVersion = "35.0.0"
|
||||
compileSdkVersion = 35
|
||||
minSdkVersion = 26
|
||||
targetSdkVersion = 34
|
||||
targetSdkVersion = 35
|
||||
supportLibVersion = "28.0.0"
|
||||
ndkVersion = "27.1.12297006"
|
||||
|
||||
@@ -75,26 +75,6 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
// Due to a dependency conflict between React Native and the Fresco library used by GiphySDK,
|
||||
// GIFs appear as static images instead of animating
|
||||
// https://github.com/Giphy/giphy-react-native-sdk/commit/7fe466ed6fddfaec95f9cbc959d33bd75ad8f900
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy {
|
||||
forcedModules = [
|
||||
'com.facebook.fresco:fresco:3.2.0',
|
||||
'com.facebook.fresco:animated-gif:3.2.0',
|
||||
'com.facebook.fresco:animated-base:3.2.0',
|
||||
'com.facebook.fresco:animated-drawable:3.2.0',
|
||||
'com.facebook.fresco:animated-webp:3.2.0',
|
||||
'com.facebook.fresco:webpsupport:3.2.0',
|
||||
'com.facebook.fresco:imagepipeline-okhttp3:3.2.0',
|
||||
'com.facebook.fresco:middleware:3.2.0',
|
||||
'com.facebook.fresco:nativeimagetranscoder:3.2.0'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Third-party react-native modules which Jitsi Meet SDK for Android depends
|
||||
// on and which are not available in third-party Maven repositories need to
|
||||
// be deployed in a Maven repository of ours.
|
||||
|
||||
113
android/scripts/check_elf_alignment.sh
Executable file
113
android/scripts/check_elf_alignment.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
progname="${0##*/}"
|
||||
progname="${progname%.sh}"
|
||||
|
||||
# usage: check_elf_alignment.sh [path to *.so files|path to *.apk]
|
||||
|
||||
cleanup_trap() {
|
||||
if [ -n "${tmp}" -a -d "${tmp}" ]; then
|
||||
rm -rf ${tmp}
|
||||
fi
|
||||
exit $1
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Host side script to check the ELF alignment of shared libraries."
|
||||
echo "Shared libraries are reported ALIGNED when their ELF regions are"
|
||||
echo "16 KB or 64 KB aligned. Otherwise they are reported as UNALIGNED."
|
||||
echo
|
||||
echo "Usage: ${progname} [input-path|input-APK|input-APEX]"
|
||||
}
|
||||
|
||||
if [ ${#} -ne 1 ]; then
|
||||
usage
|
||||
exit
|
||||
fi
|
||||
|
||||
case ${1} in
|
||||
--help | -h | -\?)
|
||||
usage
|
||||
exit
|
||||
;;
|
||||
|
||||
*)
|
||||
dir="${1}"
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! [ -f "${dir}" -o -d "${dir}" ]; then
|
||||
echo "Invalid file: ${dir}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${dir}" == *.apk ]]; then
|
||||
trap 'cleanup_trap' EXIT
|
||||
|
||||
echo
|
||||
echo "Recursively analyzing $dir"
|
||||
echo
|
||||
|
||||
if { zipalign --help 2>&1 | grep -q "\-P <pagesize_kb>"; }; then
|
||||
echo "=== APK zip-alignment ==="
|
||||
zipalign -v -c -P 16 4 "${dir}" | egrep 'lib/arm64-v8a|lib/x86_64|Verification'
|
||||
echo "========================="
|
||||
else
|
||||
echo "NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher."
|
||||
echo " You can install the latest build-tools by running the below command"
|
||||
echo " and updating your \$PATH:"
|
||||
echo
|
||||
echo " sdkmanager \"build-tools;35.0.0-rc3\""
|
||||
fi
|
||||
|
||||
dir_filename=$(basename "${dir}")
|
||||
tmp=$(mktemp -d -t "${dir_filename%.apk}_out_XXXXX")
|
||||
unzip "${dir}" lib/* -d "${tmp}" >/dev/null 2>&1
|
||||
dir="${tmp}"
|
||||
fi
|
||||
|
||||
if [[ "${dir}" == *.apex ]]; then
|
||||
trap 'cleanup_trap' EXIT
|
||||
|
||||
echo
|
||||
echo "Recursively analyzing $dir"
|
||||
echo
|
||||
|
||||
dir_filename=$(basename "${dir}")
|
||||
tmp=$(mktemp -d -t "${dir_filename%.apex}_out_XXXXX")
|
||||
deapexer extract "${dir}" "${tmp}" || { echo "Failed to deapex." && exit 1; }
|
||||
dir="${tmp}"
|
||||
fi
|
||||
|
||||
RED="\e[31m"
|
||||
GREEN="\e[32m"
|
||||
ENDCOLOR="\e[0m"
|
||||
|
||||
unaligned_libs=()
|
||||
|
||||
echo
|
||||
echo "=== ELF alignment ==="
|
||||
|
||||
matches="$(find "${dir}" -type f)"
|
||||
IFS=$'\n'
|
||||
for match in $matches; do
|
||||
# We could recursively call this script or rewrite it to though.
|
||||
[[ "${match}" == *".apk" ]] && echo "WARNING: doesn't recursively inspect .apk file: ${match}"
|
||||
[[ "${match}" == *".apex" ]] && echo "WARNING: doesn't recursively inspect .apex file: ${match}"
|
||||
|
||||
[[ $(file "${match}") == *"ELF"* ]] || continue
|
||||
|
||||
res="$(objdump -p "${match}" | grep LOAD | awk '{ print $NF }' | head -1)"
|
||||
if [[ $res =~ 2\*\*(1[4-9]|[2-9][0-9]|[1-9][0-9]{2,}) ]]; then
|
||||
echo -e "${match}: ${GREEN}ALIGNED${ENDCOLOR} ($res)"
|
||||
else
|
||||
echo -e "${match}: ${RED}UNALIGNED${ENDCOLOR} ($res)"
|
||||
unaligned_libs+=("${match}")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#unaligned_libs[@]} -gt 0 ]; then
|
||||
echo -e "${RED}Found ${#unaligned_libs[@]} unaligned libs (only arm64-v8a/x86_64 libs need to be aligned).${ENDCOLOR}"
|
||||
elif [ -n "${dir_filename}" ]; then
|
||||
echo -e "ELF Verification Successful"
|
||||
fi
|
||||
echo "====================="
|
||||
@@ -44,12 +44,12 @@ dependencies {
|
||||
api "com.facebook.react:react-android:$rootProject.ext.rnVersion"
|
||||
api "com.facebook.react:hermes-android:$rootProject.ext.rnVersion"
|
||||
|
||||
implementation 'com.facebook.fresco:animated-gif:2.5.0'
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
|
||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'androidx.startup:startup-runtime:1.1.0'
|
||||
implementation 'com.google.j2objc:j2objc-annotations:3.0.0'
|
||||
|
||||
// Only add these packages if we are NOT doing a LIBRE_BUILD
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
|
||||
@@ -49,6 +49,10 @@ public class JitsiInitializer implements Initializer<Boolean> {
|
||||
// Register activity lifecycle handler for the orientation locker module.
|
||||
((Application) context).registerActivityLifecycleCallbacks(OrientationActivityLifecycle.getInstance());
|
||||
|
||||
// Initialize ReactInstanceManager during application startup
|
||||
// This ensures it's ready before any Activity onCreate is called
|
||||
ReactInstanceManagerHolder.initReactInstanceManager((Application) context);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,10 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// ReactInstanceManager is now initialized by JitsiInitializer during application startup
|
||||
// Just call onHostResume since the manager is already ready
|
||||
JitsiMeetActivityDelegate.onHostResume(this);
|
||||
|
||||
setContentView(R.layout.activity_jitsi_meet);
|
||||
this.jitsiView = findViewById(R.id.jitsiView);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
@@ -196,8 +197,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
}
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager((Activity) context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -207,9 +208,9 @@ class ReactInstanceManagerHolder {
|
||||
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||
* only {@code ReactInstanceManager}.
|
||||
*
|
||||
* @param activity {@code Activity} current running Activity.
|
||||
* @param app {@code Application}
|
||||
*/
|
||||
static void initReactInstanceManager(Activity activity) {
|
||||
static void initReactInstanceManager(Application app) {
|
||||
if (reactInstanceManager != null) {
|
||||
return;
|
||||
}
|
||||
@@ -231,14 +232,14 @@ class ReactInstanceManagerHolder {
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(activity.getApplication())
|
||||
.setCurrentActivity(activity)
|
||||
.setApplication(app)
|
||||
.setCurrentActivity(null)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.setJavaScriptExecutorFactory(new HermesExecutorFactory())
|
||||
.addPackages(getReactNativePackages())
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ import {
|
||||
commonUserJoinedHandling,
|
||||
commonUserLeftHandling,
|
||||
getConferenceOptions,
|
||||
sendLocalParticipant
|
||||
sendLocalParticipant,
|
||||
updateTrackMuteState
|
||||
} from './react/features/base/conference/functions';
|
||||
import { getReplaceParticipant, getSsrcRewritingFeatureFlag } from './react/features/base/config/functions';
|
||||
import { connect } from './react/features/base/connection/actions.web';
|
||||
@@ -1663,8 +1664,12 @@ export default {
|
||||
room.on(
|
||||
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
|
||||
({ audio, video }) => {
|
||||
APP.store.dispatch(
|
||||
onStartMutedPolicyChanged(audio, video));
|
||||
APP.store.dispatch(onStartMutedPolicyChanged(audio, video));
|
||||
|
||||
const state = APP.store.getState();
|
||||
|
||||
updateTrackMuteState(state, APP.store.dispatch, true);
|
||||
updateTrackMuteState(state, APP.store.dispatch, false);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -761,7 +761,7 @@ var config = {
|
||||
// hideDominantSpeakerBadge: false,
|
||||
|
||||
// Default language for the user interface. Cannot be overwritten.
|
||||
// DEPRECATED! Use the `lang` iframe option directly instead.
|
||||
// For iframe integrations, use the `lang` option directly instead.
|
||||
// defaultLanguage: 'en',
|
||||
|
||||
// Disables profile and the edit of all fields from the profile settings (display name and email)
|
||||
@@ -791,7 +791,6 @@ var config = {
|
||||
// Configs for prejoin page.
|
||||
// prejoinConfig: {
|
||||
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
|
||||
// // This replaces `prejoinPageEnabled`. Defaults to true.
|
||||
// enabled: true,
|
||||
// // Hides the participant name editing field in the prejoin screen.
|
||||
// // If requireDisplayName is also set as true, a name should still be provided through
|
||||
@@ -1363,7 +1362,9 @@ var config = {
|
||||
// disableGrantModerator: true,
|
||||
// // If set to 'all' the 'Private chat' button will be disabled for all participants.
|
||||
// // If set to 'allow-moderator-chat' the 'Private chat' button will be available for chats with moderators.
|
||||
// disablePrivateChat: 'all' | 'allow-moderator-chat',
|
||||
// // If set to 'disable-visitor-chat' the 'Private chat' button will be disabled for visitor-main participant
|
||||
// // conversations.
|
||||
// disablePrivateChat: 'all' | 'allow-moderator-chat' | 'disable-visitor-chat',
|
||||
// },
|
||||
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ $welcomePageHeaderContainerMargin: $welcomePageHeaderContainerMarginTop auto 0;
|
||||
$welcomePageHeaderTextTitleMarginBottom: 0;
|
||||
$welcomePageHeaderTextTitleFontSize: 2.625rem;
|
||||
$welcomePageHeaderTextTitleFontWeight: normal;
|
||||
$welcomePageHeaderTextTitleLineHeight: 50px;
|
||||
$welcomePageHeaderTextTitleLineHeight: 3.125rem;
|
||||
$welcomePageHeaderTextTitleOpacity: 1;
|
||||
|
||||
$welcomePageEnterRoomDisplay: flex;
|
||||
|
||||
16
giphy-analytics-stub.js
Normal file
16
giphy-analytics-stub.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// Stub replacement for @giphy/js-analytics to prevent beforeunload handlers
|
||||
// This completely disables all Giphy analytics functionality
|
||||
|
||||
export const pingback = () => {
|
||||
// Completely disabled - do nothing
|
||||
|
||||
};
|
||||
|
||||
export const mergeAttributes = (attributes, newAttributes) => {
|
||||
// Return merged attributes without any analytics calls
|
||||
return { ...attributes,
|
||||
...newAttributes };
|
||||
};
|
||||
|
||||
// Ensure no beforeunload handlers are ever registered
|
||||
export default pingback;
|
||||
@@ -64,11 +64,11 @@ PODS:
|
||||
- GoogleUtilities/UserDefaults (~> 7.7)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- fmt (11.0.2)
|
||||
- Giphy (2.2.13):
|
||||
- Giphy (2.2.16):
|
||||
- libwebp
|
||||
- giphy-react-native-sdk (3.3.1):
|
||||
- giphy-react-native-sdk (4.1.0):
|
||||
- DoubleConversion
|
||||
- Giphy (= 2.2.13)
|
||||
- Giphy (= 2.2.16)
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly (= 2024.11.18.00)
|
||||
@@ -1382,9 +1382,9 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-netinfo (11.1.0):
|
||||
- React-Core
|
||||
- react-native-orientation-locker (1.6.0):
|
||||
- react-native-orientation-locker (1.5.0):
|
||||
- React-Core
|
||||
- react-native-pager-view (6.4.1):
|
||||
- react-native-pager-view (6.8.1):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -1407,7 +1407,7 @@ PODS:
|
||||
- Yoga
|
||||
- react-native-performance (5.1.2):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (5.4.0):
|
||||
- react-native-safe-area-context (5.5.2):
|
||||
- React-Core
|
||||
- react-native-slider (4.5.6):
|
||||
- DoubleConversion
|
||||
@@ -1794,7 +1794,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNDefaultPreference (1.4.4):
|
||||
- React-Core
|
||||
- RNDeviceInfo (10.9.0):
|
||||
- RNDeviceInfo (12.1.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.24.0):
|
||||
- DoubleConversion
|
||||
@@ -2201,8 +2201,8 @@ SPEC CHECKSUMS:
|
||||
FirebaseCrashlytics: feb07e4e9187be3c23c6a846cce4824e5ce2dd0b
|
||||
FirebaseInstallations: 40bd9054049b2eae9a2c38ef1c3dd213df3605cd
|
||||
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
|
||||
Giphy: a11dd02b51ac2ec37b881de1717ebf2cc8e9df62
|
||||
giphy-react-native-sdk: 678a115ea5a47a43d39d1b61703e0d08b1e48917
|
||||
Giphy: 55914215541027873875757f350530e6d8986fba
|
||||
giphy-react-native-sdk: 733177b2537b527cfa55979c396cc1f2046eb457
|
||||
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
|
||||
GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e
|
||||
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
|
||||
@@ -2249,10 +2249,10 @@ SPEC CHECKSUMS:
|
||||
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
|
||||
react-native-keep-awake: 03b74eebe4f2bb5e8478fc8f420651a92463b6f8
|
||||
react-native-netinfo: 5364263f903da576bdef9c84a76fe243ab06812c
|
||||
react-native-orientation-locker: ee8bb2177365ca74f51dc1e11218fe544634d523
|
||||
react-native-pager-view: 68e8a65a607a6f91a1e25865002192c3c4f53fcf
|
||||
react-native-orientation-locker: dbd3f6ddbe9e62389cb0807dc2af63f6c36dec36
|
||||
react-native-pager-view: 11662c698c8f11d39e05891316d2a144fa00adc4
|
||||
react-native-performance: 125a96c145e29918b55b45ce25cbba54f1e24dcd
|
||||
react-native-safe-area-context: 9d72abf6d8473da73033b597090a80b709c0b2f1
|
||||
react-native-safe-area-context: 0f7bf11598f9a61b7ceac8dc3f59ef98697e99e1
|
||||
react-native-slider: 1205801a8d29b28cacc14eef08cb120015cdafcb
|
||||
react-native-video: eb861d67a71dfef1bbf6086a811af5f338b13781
|
||||
react-native-webrtc: 2261a482150195092246fe70b3aff976f2e11ec5
|
||||
@@ -2290,7 +2290,7 @@ SPEC CHECKSUMS:
|
||||
RNCAsyncStorage: aa75595c1aefa18f868452091fa0c411a516ce11
|
||||
RNCClipboard: 7c3e3b5f71d84ef61690ad377b6c50cf27864ff5
|
||||
RNDefaultPreference: ee13d69e6693d193cd223d10e15e5b3c012d31ba
|
||||
RNDeviceInfo: 8af23685571b7867d8dc15fb89e7fb5fa8607e1e
|
||||
RNDeviceInfo: 723e97dd98af9b7913477e7a40252c15517c258c
|
||||
RNGestureHandler: 9f3109e11ed88fe5bed280bf7762b25e4c52f396
|
||||
RNGoogleSignin: 30e1aee80140dc0706cd78a4951c411376c88329
|
||||
RNScreens: 9ef996b6041d0960a4794a845f7d0808b171b4ef
|
||||
|
||||
@@ -30,6 +30,14 @@
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>85F4.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"disabled": "Tērzēšanas ziņojumu sūtīšana ir atspējota.",
|
||||
"enter": "Ienākt istabā",
|
||||
"error": "Kļūda: Jūsu ziņa netika nosūtīta. Cēlonis: {{error}}",
|
||||
"fieldPlaceHolder": "Rakstiet ziņu šeit",
|
||||
@@ -124,7 +125,8 @@
|
||||
"title": "Ierakstiet vārdu, lai izmantotu tērzēšanā",
|
||||
"titleWithCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā un slēptos subtitros",
|
||||
"titleWithPolls": "Ierakstiet segvārdu, lai izmantotu tērzēšanā un aptaujās",
|
||||
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās un slēptos subtitros"
|
||||
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās un slēptos subtitros",
|
||||
"titleWithPollsAndCCAndFileSharing": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās, slēptos subtitros un failos"
|
||||
},
|
||||
"noMessagesMessage": "Sapulcē pagaidām nav nevienas ziņas. Uzsāciet saraksti!",
|
||||
"privateNotice": "Privāta ziņa adresātam {{recipient}}",
|
||||
@@ -134,12 +136,14 @@
|
||||
"tabs": {
|
||||
"chat": "Tērzēšana",
|
||||
"closedCaptions": "Slēptie subtitri",
|
||||
"fileSharing": "Faili",
|
||||
"polls": "Aptaujas"
|
||||
},
|
||||
"title": "Tērzēšana",
|
||||
"titleWithCC": "Tērzēšana un slēptie subtitri",
|
||||
"titleWithCC": "Tērzēšana un Slēptie subtitri",
|
||||
"titleWithFeatures": "Tērzēšana un",
|
||||
"titleWithFileSharing": "Faili",
|
||||
"titleWithPolls": "Tērzēšana un Aptaujas",
|
||||
"titleWithPollsAndCC": "Tērzēšana, Aptaujas un Slēptie subtitri",
|
||||
"you": "jūs"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
@@ -296,6 +300,12 @@
|
||||
"alreadySharedVideoTitle": "Atļauts tikai viens kopīgots videoklips",
|
||||
"applicationWindow": "Lietotnes logs",
|
||||
"authenticationRequired": "Nepieciešama autentifikācija",
|
||||
"cameraCaptureDialog": {
|
||||
"description": "Uzņemt un nosūtīt attēlu, izmantojot mobilā tālruņa kameru",
|
||||
"ok": "Atvērt kameru",
|
||||
"reject": "Ne tagad",
|
||||
"title": "Uzņemt attēlu"
|
||||
},
|
||||
"cameraConstraintFailedError": "Kamera neatbilst noteiktajām prasībām.",
|
||||
"cameraNotFoundError": "Kamera nav atrasta.",
|
||||
"cameraNotSendingData": "Nevar piekļūt jūsu kamerai. Lūdzu, pārbaudiet, vai šo ierīci neizmanto cita programma, iestatījumu izvēlnē atlasiet citu ierīci vai mēģiniet atkārtoti ielādēt programmu.",
|
||||
@@ -371,22 +381,34 @@
|
||||
"micTimeoutError": "Nevarēja palaist audio avotu. Iestājās noildze!",
|
||||
"micUnknownError": "Nevar izmantot mikrofonu nezināma iemesla dēļ.",
|
||||
"moderationAudioLabel": "Atļaut dalībniekiem ieslēgt savu mikrofonu",
|
||||
"moderationDesktopLabel": "Atļaut lietotājiem, kas nav moderatori, kopīgot savu ekrānu",
|
||||
"moderationVideoLabel": "Atļaut dalībniekiem ieslēgt savu kameru",
|
||||
"muteEveryoneDialog": "Dalībnieki paši var ieslēgt savu mikrofonu.",
|
||||
"muteEveryoneDialogModerationOn": "Dalībnieki var nosūtīt pieprasījumu ieslēgt savu mikrofonu.",
|
||||
"muteEveryoneElseDialog": "Kad skaņa būs izslēgta, jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieki to varēs izdarīt paši.",
|
||||
"muteEveryoneElseTitle": "Vai izslēgt skaņu visiem, izņemot {{whom}}?",
|
||||
"muteEveryoneElsesDesktopDialog": "Kad kopīgošana būs apturēta, jūs vairs nevarēsiet to ieslēgt atpakaļ, bet viņi to varēs izdarīt jebkurā laikā.",
|
||||
"muteEveryoneElsesDesktopTitle": "Apturēt ekrāna kopīgošanu visiem, izņemot {{kam}}?",
|
||||
"muteEveryoneElsesVideoDialog": "Kad video būs izslēgts, jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieki to varēs izdarīt paši.",
|
||||
"muteEveryoneElsesVideoTitle": "Vai izslēgt video visiem, izņemot {{whom}}?",
|
||||
"muteEveryoneSelf": "jūs",
|
||||
"muteEveryoneStartMuted": "No šī brīža visi jauni dalībnieki pieslēdzas ar izslēgt skaņu",
|
||||
"muteEveryoneTitle": "Vai izslēgt skaņu visiem?",
|
||||
"muteEveryonesDesktopDialog": "Dalībnieki var kopīgot savu ekrānu jebkurā laikā.",
|
||||
"muteEveryonesDesktopDialogModerationOn": "Dalībnieki jebkurā laikā var nosūtīt pieprasījumu kopīgot savu ekrānu.",
|
||||
"muteEveryonesDesktopTitle": "Vai pārtraukt ekrāna kopīgošanu visiem?",
|
||||
"muteEveryonesVideoDialog": "Dalībnieki var ieslēgt savu video.",
|
||||
"muteEveryonesVideoDialogModerationOn": "Dalībnieki var nosūtīt pieprasījumu ieslēgt viņu video.",
|
||||
"muteEveryonesVideoDialogOk": "Atspējot",
|
||||
"muteEveryonesVideoTitle": "Vai apturēt ikviena video?",
|
||||
"muteParticipantBody": "Jūs nevariet viņiem ieslēgt skaņu, bet viņi paši to var izdarīt jebkurā laikā.",
|
||||
"muteParticipantButton": "Izslēgt skaņu",
|
||||
"muteParticipantsDesktopBody": "Jūs nevarēsiet sākt viņu ekrāna kopīgošanu, bet viņi to varēs izdarīt jebkurā laikā.",
|
||||
"muteParticipantsDesktopBodyModerationOn": "Jūs nevarēsiet sākt viņu ekrāna kopīgošanu, un arī viņi to nevarēs izdarīt.",
|
||||
"muteParticipantsDesktopButton": "Pārtraukt ekrāna kopīgošanu",
|
||||
"muteParticipantsDesktopDialog": "Vai tiešām vēlaties izslēgt šī dalībnieka ekrāna kopīgošanu? Jūs vairs nevarēsiet ieslēgt to atpakaļ, bet viņš to varēs izdarīt jebkurā laikā.",
|
||||
"muteParticipantsDesktopDialogModerationOn": "Vai tiešām vēlaties izslēgt šī dalībnieka ekrāna kopīgošanu? Jūs vairs nevarēsiet ieslēgt to atpakaļ, un arī viņš to nevarēs izdarīt.",
|
||||
"muteParticipantsDesktopTitle": "Atspējot ekrāna kopīgošanu šim dalībniekam?",
|
||||
"muteParticipantsVideoBody": "Jūs nevarēsiet kameru ieslēgt atpakaļ, taču viņi paši to varēs izdarīt jebkurā laikā.",
|
||||
"muteParticipantsVideoBodyModerationOn": "Ne Jūs, ne dalībnieki nevarēsiet ieslēgt kameru atpakaļ.",
|
||||
"muteParticipantsVideoButton": "Pārtraukt video",
|
||||
@@ -539,6 +561,19 @@
|
||||
"veryBad": "Ļoti Slikta",
|
||||
"veryGood": "Ļoti Laba"
|
||||
},
|
||||
"fileSharing": {
|
||||
"downloadFailedDescription": "Lūdzu, mēģiniet vēlreiz.",
|
||||
"downloadFailedTitle": "Lejuplādes kļūda",
|
||||
"downloadFile": "Lejuplādēt",
|
||||
"dragAndDrop": "Velciet un palaidiet failus šeit, vai jebkurā ekrāna vietā",
|
||||
"fileAlreadyUploaded": "Fails jau ir augšupielādēts šajā sanāksmē.",
|
||||
"fileTooLargeDescription": "Lūdzu, pārliecinieties, vai faila lielums nepārsniedz {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "Izvēlētais fails ir pārāk liels",
|
||||
"removeFile": "Noņemt",
|
||||
"uploadFailedDescription": "Lūdzu, mēģiniet vēlreiz.",
|
||||
"uploadFailedTitle": "Augšuplādes kļūda",
|
||||
"uploadFile": "Kopīgot failu"
|
||||
},
|
||||
"filmstrip": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Video sīktēli"
|
||||
@@ -750,8 +785,9 @@
|
||||
"me": "es",
|
||||
"notify": {
|
||||
"OldElectronAPPTitle": "Drošības ievainojamība!",
|
||||
"allowAll": "Atļaut visu",
|
||||
"allowAudio": "Atļaut audio",
|
||||
"allowBoth": "Abus",
|
||||
"allowDesktop": "Atļaut ekrāna kopīgošanu",
|
||||
"allowVideo": "Atļaut video",
|
||||
"allowedUnmute": "Varat ieslēgt mikrofona skaņu, ieslēgt kameru vai kopīgot ekrānu.",
|
||||
"audioUnmuteBlockedDescription": "Mikrofona ieslēgšanas darbība ir īslaicīgi bloķēta sistēmas ierobežojumu dēļ.",
|
||||
@@ -765,6 +801,7 @@
|
||||
"dataChannelClosedDescription": "Savienojuma kanāls nedarbojas, tāpēc video kvalitāte var būt ierobežota līdz zemākajam iestatījumam.",
|
||||
"dataChannelClosedDescriptionWithAudio": "Savienojuma kanāls nedarbojas, tāpēc var rasties audio un video traucējumi.",
|
||||
"dataChannelClosedWithAudio": "Audio un video kvalitāte var būt traucēta",
|
||||
"desktopMutedRemotelyTitle": "",
|
||||
"disabledIframe": "Iegulšana ir paredzēta tikai demonstrācijas nolūkiem, tāpēc šis zvans tiks atvienots pēc {{timeout}} minūtēm.",
|
||||
"disabledIframeSecondaryNative": "Domēna {{domain}} iegulšana ir paredzēta tikai demonstrācijas nolūkiem, tāpēc šis zvans tiks pārtraukts pēc {{timeout}} minūtēm.",
|
||||
"disabledIframeSecondaryWeb": "Domēna {{domain}} iegulšana ir paredzēta tikai demonstrācijas nolūkiem, tāpēc šis zvans tiks pārtraukts pēc {{timeout}} minūtēm. Lūdzu, produkcijas videi izmantojiet <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a>!",
|
||||
@@ -845,6 +882,7 @@
|
||||
"suggestRecordingDescription": "Vai vēlaties sākt ierakstīšanu?",
|
||||
"suggestRecordingTitle": "Ierakstīt sanāksmi",
|
||||
"unmute": "Ieslēgt mikrofonu",
|
||||
"unmuteScreen": "",
|
||||
"unmuteVideo": "Ieslēgt video",
|
||||
"videoMutedRemotelyDescription": "Jūs vienmēr varat to atkal ieslēgt.",
|
||||
"videoMutedRemotelyTitle": "{{participantDisplayName}} izslēdza jūsu video",
|
||||
@@ -864,11 +902,14 @@
|
||||
"admit": "Apstiprināt",
|
||||
"admitAll": "Apstiprināt visus",
|
||||
"allow": "Atļaut dalībniekiem:",
|
||||
"allowDesktop": "Atļaut ekrāna kopīgošanu",
|
||||
"allowVideo": "Atļaut video",
|
||||
"askDesktop": "Lūgt kopīgot ekrānu",
|
||||
"askUnmute": "Lūgt ieslēgt skaņu",
|
||||
"audioModeration": "Ieslēgt savu skaņu",
|
||||
"blockEveryoneMicCamera": "Bloķēt visiem mikrofonu un kameru",
|
||||
"breakoutRooms": "Grupu istabas",
|
||||
"desktopModeration": "Sākt ekrāna kopīgošanu",
|
||||
"goLive": "Sākt",
|
||||
"invite": "Uzaicināt",
|
||||
"lowerAllHands": "Nolaist visas paceltās rokas",
|
||||
@@ -880,6 +921,8 @@
|
||||
"muteAll": "Apklusināt visus",
|
||||
"muteEveryoneElse": "Apklusināt pārējos",
|
||||
"reject": "Noraidīt",
|
||||
"stopDesktop": "Pārtraukt ekrāna kopīgošanu",
|
||||
"stopEveryonesDesktop": "Pārtraukt visiem ekrāna kopīgošanu",
|
||||
"stopEveryonesVideo": "Izslēgt visiem video",
|
||||
"stopVideo": "Izslēgt video",
|
||||
"unblockEveryoneMicCamera": "Atbloķēt visiem mikrofonu un kameru",
|
||||
@@ -889,9 +932,11 @@
|
||||
"headings": {
|
||||
"lobby": "Vestibils ({{count}})",
|
||||
"participantsList": "Sapulces dalībnieki ({{count}})",
|
||||
"viewerRequests": "Apmeklētāju pieprasījumi {{count}}",
|
||||
"visitorInQueue": " (gaida {{count}})",
|
||||
"visitorRequests": " (pieprasījumi {{count}})",
|
||||
"visitors": "Apmeklētāji ({{count}})",
|
||||
"visitors": "Apmeklētāji {{count}}",
|
||||
"visitorsList": "Apmeklētāji ({{count}})",
|
||||
"waitingLobby": "Gaida vestibilā ({{count}})"
|
||||
},
|
||||
"search": "Meklēt dalībniekus",
|
||||
@@ -1484,6 +1529,8 @@
|
||||
"connectionInfo": "Informācija par savienojumu",
|
||||
"demote": "Pārveidot par apmeklētāju",
|
||||
"domute": "Izlsēgt skaņu",
|
||||
"domuteDesktop": "Pārtraukt ekrāna kopīgošanu",
|
||||
"domuteDesktopOfOthers": "Pārtraukt ekrāna kopīgošanu visiem pārējiem",
|
||||
"domuteOthers": "Izslēgt skaņu visiem pārējiem",
|
||||
"domuteVideo": "Izslēgt kameru",
|
||||
"domuteVideoOfOthers": "Izslēgt video visiem pārējiem",
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
"disabled": "Sending chat messages is disabled.",
|
||||
"enter": "Enter room",
|
||||
"error": "Error: your message was not sent. Reason: {{error}}",
|
||||
"everyone": "Everyone",
|
||||
"fieldPlaceHolder": "Aa",
|
||||
"lobbyChatMessageTo": "Lobby chat message to {{recipient}}",
|
||||
"message": "Message",
|
||||
@@ -300,6 +301,12 @@
|
||||
"alreadySharedVideoTitle": "Only one shared video is allowed at a time",
|
||||
"applicationWindow": "Application window",
|
||||
"authenticationRequired": "Authentication required",
|
||||
"cameraCaptureDialog": {
|
||||
"description": "Take and send a picture using your mobile camera",
|
||||
"ok": "Open camera",
|
||||
"reject": "Not now",
|
||||
"title": "Take a picture"
|
||||
},
|
||||
"cameraConstraintFailedError": "Your camera does not satisfy some of the required constraints.",
|
||||
"cameraNotFoundError": "Camera was not found.",
|
||||
"cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to reload the application.",
|
||||
@@ -374,7 +381,8 @@
|
||||
"micPermissionDeniedError": "You have not granted permission to use your microphone. You can still join the conference but others won't hear you. Use the camera button in the address bar to fix this.",
|
||||
"micTimeoutError": "Could not start audio source. Timeout occurred!",
|
||||
"micUnknownError": "Cannot use microphone for an unknown reason.",
|
||||
"moderationAudioLabel": "Allow attendees to unmute themselves",
|
||||
"moderationAudioLabel": "Allow non-moderators to unmute themselves",
|
||||
"moderationDesktopLabel": "Allow non-moderators to share their screen",
|
||||
"moderationVideoLabel": "Allow non-moderators to start their video",
|
||||
"muteEveryoneDialog": "The participants can unmute themselves at any time.",
|
||||
"muteEveryoneDialogModerationOn": "The participants can send a request to speak at any time.",
|
||||
@@ -387,6 +395,9 @@
|
||||
"muteEveryoneSelf": "yourself",
|
||||
"muteEveryoneStartMuted": "Everyone starts muted from now on",
|
||||
"muteEveryoneTitle": "Mute everyone?",
|
||||
"muteEveryonesDesktopDialog": "The participants can share their screen at any time.",
|
||||
"muteEveryonesDesktopDialogModerationOn": "The participants can send a request to share their screen at any time.",
|
||||
"muteEveryonesDesktopTitle": "Stop everyone's screen share?",
|
||||
"muteEveryonesVideoDialog": "The participants can turn on their video at any time.",
|
||||
"muteEveryonesVideoDialogModerationOn": "The participants can send a request to turn on their video at any time.",
|
||||
"muteEveryonesVideoDialogOk": "Disable",
|
||||
@@ -559,6 +570,8 @@
|
||||
"fileAlreadyUploaded": "File has already been uploaded to this meeting.",
|
||||
"fileTooLargeDescription": "Please make sure the file does not exceed {{ maxFileSize }}.",
|
||||
"fileTooLargeTitle": "The selected file is too large",
|
||||
"fileUploadProgress": "File upload progress",
|
||||
"fileUploadedSuccessfully": "File uploaded successfully",
|
||||
"removeFile": "Remove",
|
||||
"uploadFailedDescription": "Please try again.",
|
||||
"uploadFailedTitle": "Upload failed",
|
||||
|
||||
@@ -30,6 +30,7 @@ import { overwriteConfig } from '../../react/features/base/config/actions';
|
||||
import { getWhitelistedJSON } from '../../react/features/base/config/functions.any';
|
||||
import { toggleDialog } from '../../react/features/base/dialog/actions';
|
||||
import { isSupportedBrowser } from '../../react/features/base/environment/environment';
|
||||
import { isMobileBrowser } from '../../react/features/base/environment/utils';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt/functions';
|
||||
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../../react/features/base/media/constants';
|
||||
@@ -113,7 +114,10 @@ import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../react/features/rec
|
||||
import { getActiveSession, supportsLocalRecording } from '../../react/features/recording/functions';
|
||||
import { startAudioScreenShareFlow, startScreenShareFlow } from '../../react/features/screen-share/actions';
|
||||
import { isScreenAudioSupported } from '../../react/features/screen-share/functions';
|
||||
import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-capture/actions';
|
||||
import {
|
||||
openCameraCaptureDialog,
|
||||
toggleScreenshotCaptureSummary
|
||||
} from '../../react/features/screenshot-capture/actions';
|
||||
import { isScreenshotCaptureEnabled } from '../../react/features/screenshot-capture/functions';
|
||||
import SettingsDialog from '../../react/features/settings/components/web/SettingsDialog';
|
||||
import { SETTINGS_TABS } from '../../react/features/settings/constants';
|
||||
@@ -940,6 +944,20 @@ function initCommands() {
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'capture-camera-picture' : {
|
||||
const { cameraFacingMode, descriptionText, titleText } = request;
|
||||
|
||||
if (!isMobileBrowser()) {
|
||||
logger.error('This feature is only supported on mobile');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(openCameraCaptureDialog(callback, { cameraFacingMode,
|
||||
descriptionText,
|
||||
titleText }));
|
||||
break;
|
||||
}
|
||||
case 'deployment-info':
|
||||
callback(APP.store.getState()['features/base/config'].deploymentInfo);
|
||||
break;
|
||||
|
||||
21
modules/API/external/external_api.js
vendored
21
modules/API/external/external_api.js
vendored
@@ -820,6 +820,27 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures a picture through OS camera.
|
||||
*
|
||||
* @param {string} cameraFacingMode - The OS camera facing mode (environment/user).
|
||||
* @param {string} descriptionText - The OS camera facing mode (environment/user).
|
||||
* @param {string} titleText - The OS camera facing mode (environment/user).
|
||||
* @returns {Promise<string>} - Resolves with a base64 encoded image data of the screenshot.
|
||||
*/
|
||||
captureCameraPicture(
|
||||
cameraFacingMode,
|
||||
descriptionText,
|
||||
titleText
|
||||
) {
|
||||
return this._transport.sendRequest({
|
||||
name: 'capture-camera-picture',
|
||||
cameraFacingMode,
|
||||
descriptionText,
|
||||
titleText
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listeners and removes the Jitsi Meet frame.
|
||||
*
|
||||
|
||||
574
package-lock.json
generated
574
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -23,7 +23,7 @@
|
||||
"@emotion/styled": "11.10.6",
|
||||
"@giphy/js-fetch-api": "4.9.3",
|
||||
"@giphy/react-components": "6.9.4",
|
||||
"@giphy/react-native-sdk": "3.3.1",
|
||||
"@giphy/react-native-sdk": "4.1.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
@@ -59,6 +59,7 @@
|
||||
"dayjs": "1.11.13",
|
||||
"dropbox": "10.7.0",
|
||||
"focus-visible": "5.1.0",
|
||||
"glob": "11.0.3",
|
||||
"grapheme-splitter": "1.0.4",
|
||||
"i18n-iso-countries": "6.8.0",
|
||||
"i18next": "17.0.6",
|
||||
@@ -70,7 +71,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2030.0.0+b225c920/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2051.0.0+ccc06e83/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"null-loader": "4.0.1",
|
||||
"optional-require": "1.0.3",
|
||||
@@ -87,16 +88,16 @@
|
||||
"react-native-background-timer": "https://github.com/jitsi/react-native-background-timer.git#d180dfaa4486ae3ee17d01242db92cb3195f4718",
|
||||
"react-native-calendar-events": "https://github.com/jitsi/react-native-calendar-events.git#47f068dedfed7c0f72042e093f688eb11624eb7b",
|
||||
"react-native-default-preference": "https://github.com/jitsi/react-native-default-preference.git#c9bf63bdc058e3fa2aa0b87b1ee1af240f44ed02",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-device-info": "12.1.0",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.24.0",
|
||||
"react-native-get-random-values": "1.11.0",
|
||||
"react-native-immersive-mode": "https://github.com/jitsi/react-native-immersive-mode.git#38cc9001db24618bc0c61800f81e889bcfb6ff2c",
|
||||
"react-native-orientation-locker": "1.6.0",
|
||||
"react-native-pager-view": "6.4.1",
|
||||
"react-native-orientation-locker": "https://github.com/jitsi/react-native-orientation-locker.git#fe095651d819cf134624f786b61fc8667862178a",
|
||||
"react-native-pager-view": "6.8.1",
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.1.2",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-safe-area-context": "5.5.2",
|
||||
"react-native-screens": "4.11.1",
|
||||
"react-native-sound": "https://github.com/jitsi/react-native-sound.git#ea13c97b5c2a4ff5e0d9bacbd9ff5e4457fe2c3c",
|
||||
"react-native-splash-view": "0.0.18",
|
||||
@@ -111,7 +112,7 @@
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-textarea-autosize": "8.3.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.26",
|
||||
"react-virtualized-auto-sizer": "1.0.26",
|
||||
"react-window": "1.8.6",
|
||||
"react-youtube": "10.1.0",
|
||||
"redux": "4.0.4",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
diff --git a/node_modules/@giphy/js-analytics/dist/send-pingback.js b/node_modules/@giphy/js-analytics/dist/send-pingback.js
|
||||
index 989f0ff..52471cb 100644
|
||||
--- a/node_modules/@giphy/js-analytics/dist/send-pingback.js
|
||||
+++ b/node_modules/@giphy/js-analytics/dist/send-pingback.js
|
||||
@@ -10,6 +10,9 @@ var global_1 = __importDefault(require("./global"));
|
||||
var environment = (global_1.default === null || global_1.default === void 0 ? void 0 : global_1.default.GIPHY_PINGBACK_URL) || 'https://pingback.giphy.com';
|
||||
var pingBackUrl = "".concat(environment, "/v2/pingback?apikey=l0HlIwPWyBBUDAUgM");
|
||||
var sendPingback = function (events) {
|
||||
+ // Disabled.
|
||||
+ return Promise.resolve();
|
||||
+
|
||||
var headers = (0, js_util_1.getGiphySDKRequestHeaders)();
|
||||
/* istanbul ignore next */
|
||||
headers === null || headers === void 0 ? void 0 : headers.set('Content-Type', 'application/json');
|
||||
@@ -70,6 +70,7 @@ cd ios && pod install && cd ..
|
||||
## Android
|
||||
|
||||
- In your build.gradle have at least `minSdkVersion = 26`
|
||||
- In your build.gradle have `gradlePluginVersion = "8.4.2"` or higher
|
||||
- In `android/app/src/debug/AndroidManifest.xml` and `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
|
||||
@@ -7,7 +7,6 @@ buildscript {
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$rootProject.ext.gradlePluginVersion"
|
||||
}
|
||||
namespace 'org.jitsi.meet.reactnativesdk'
|
||||
}
|
||||
|
||||
def isNewArchitectureEnabled() {
|
||||
@@ -29,6 +28,7 @@ def getExtOrIntegerDefault(name) {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'org.jitsi.meet.sdk'
|
||||
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
|
||||
|
||||
defaultConfig {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.jitsi.meet.sdk">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -35,3 +35,12 @@ export function fixDeviceID(amplitude: Types.BrowserClient) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amplitude shared deviceId.
|
||||
*
|
||||
* @returns {string} - The amplitude deviceId.
|
||||
*/
|
||||
export function getDeviceID() {
|
||||
return jitsiLocalStorage.getItem(DEVICE_ID_KEY);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { getServerURL } from '../base/settings/functions.web';
|
||||
import { getJitsiMeetGlobalNS } from '../base/util/helpers';
|
||||
|
||||
export * from './functions.any';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Retrieves the default URL for the app. This can either come from a prop to
|
||||
@@ -31,3 +33,27 @@ export function getDefaultURL(stateful: IStateful) {
|
||||
export function getName() {
|
||||
return interfaceConfig.APP_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a handler function after the window load event has been received.
|
||||
* If the app has already loaded, the handler is executed immediately.
|
||||
* Otherwise, the handler is registered as a 'load' event listener.
|
||||
*
|
||||
* @param {Function} handler - The callback function to execute.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function executeAfterLoad(handler: () => void) {
|
||||
const safeHandler = () => {
|
||||
try {
|
||||
handler();
|
||||
} catch (error) {
|
||||
logger.error('Error executing handler after load:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (getJitsiMeetGlobalNS()?.hasLoaded) {
|
||||
safeHandler();
|
||||
} else {
|
||||
window.addEventListener('load', safeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import '../calendar-sync/middleware';
|
||||
import '../chat/middleware';
|
||||
import '../conference/middleware';
|
||||
import '../connection-indicator/middleware';
|
||||
import '../deep-linking/middleware';
|
||||
import '../device-selection/middleware';
|
||||
import '../display-name/middleware';
|
||||
import '../dynamic-branding/middleware';
|
||||
|
||||
@@ -2,6 +2,7 @@ import '../base/app/middleware';
|
||||
import '../base/connection/middleware';
|
||||
import '../base/devices/middleware';
|
||||
import '../base/media/middleware';
|
||||
import '../deep-linking/middleware.web';
|
||||
import '../dynamic-branding/middleware';
|
||||
import '../e2ee/middleware';
|
||||
import '../external-api/middleware';
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { pixelsToRem } from '../../../ui/functions.any';
|
||||
import { isIcon } from '../../functions';
|
||||
import { IAvatarProps } from '../../types';
|
||||
import { PRESENCE_AVAILABLE_COLOR, PRESENCE_AWAY_COLOR, PRESENCE_BUSY_COLOR, PRESENCE_IDLE_COLOR } from '../styles';
|
||||
@@ -50,9 +50,8 @@ const useStyles = makeStyles()(theme => {
|
||||
avatar: {
|
||||
backgroundColor: '#AAA',
|
||||
borderRadius: '50%',
|
||||
fontWeight: '600',
|
||||
color: theme.palette?.text01 || '#fff',
|
||||
...withPixelLineHeight(theme.typography?.heading1 ?? {}),
|
||||
...(theme.typography?.heading1 ?? {}),
|
||||
fontSize: 'inherit',
|
||||
objectFit: 'cover',
|
||||
textAlign: 'center',
|
||||
@@ -137,7 +136,7 @@ const StatelessAvatar = ({
|
||||
const _getAvatarStyle = (backgroundColor?: string) => {
|
||||
return {
|
||||
background: backgroundColor || undefined,
|
||||
fontSize: size ? size * 0.4 : '180%',
|
||||
fontSize: size ? pixelsToRem(size * 0.4) : '180%',
|
||||
height: size || '100%',
|
||||
width: size || '100%'
|
||||
};
|
||||
|
||||
@@ -4,13 +4,12 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Icon from '../icons/components/Icon';
|
||||
import { IconCheck, IconCopy } from '../icons/svg';
|
||||
import { withPixelLineHeight } from '../styles/functions.web';
|
||||
import { copyText } from '../util/copyText.web';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
copyButton: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortBold),
|
||||
...theme.typography.bodyShortBold,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
|
||||
@@ -83,7 +83,8 @@ import {
|
||||
getConferenceState,
|
||||
getCurrentConference,
|
||||
getVisitorOptions,
|
||||
sendLocalParticipant
|
||||
sendLocalParticipant,
|
||||
updateTrackMuteState
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { IConferenceMetadata, IJitsiConference } from './reducer';
|
||||
@@ -186,6 +187,15 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
|
||||
(disableVideoMuteChange: boolean) => {
|
||||
dispatch(setVideoUnmutePermissions(disableVideoMuteChange));
|
||||
});
|
||||
conference.on(
|
||||
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
|
||||
({ audio, video }: { audio: boolean; video: boolean; }) => {
|
||||
dispatch(onStartMutedPolicyChanged(audio, video));
|
||||
|
||||
updateTrackMuteState(state, dispatch, true);
|
||||
updateTrackMuteState(state, dispatch, false);
|
||||
}
|
||||
);
|
||||
|
||||
// Dispatches into features/base/tracks follow:
|
||||
|
||||
@@ -1013,6 +1023,8 @@ export function setStartMutedPolicy(
|
||||
audio: startAudioMuted,
|
||||
video: startVideoMuted
|
||||
});
|
||||
|
||||
dispatch(onStartMutedPolicyChanged(startAudioMuted, startVideoMuted));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -40,3 +40,8 @@ export const CONFERENCE_LEAVE_REASONS = {
|
||||
SWITCH_ROOM: 'switch_room',
|
||||
UNRECOVERABLE_ERROR: 'unrecoverable_error'
|
||||
};
|
||||
|
||||
/**
|
||||
* The ID of the notification that is shown when the user is muted by focus.
|
||||
*/
|
||||
export const START_MUTED_NOTIFICATION_ID = 'start-muted';
|
||||
|
||||
@@ -3,9 +3,13 @@ import { upperFirst, words } from 'lodash-es';
|
||||
|
||||
import { getName } from '../../app/functions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { determineTranscriptionLanguage } from '../../transcribing/functions';
|
||||
import { IStateful } from '../app/types';
|
||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setVideoMuted } from '../media/actions';
|
||||
import { VIDEO_MUTISM_AUTHORITY } from '../media/constants';
|
||||
import {
|
||||
participantJoined,
|
||||
participantLeft
|
||||
@@ -22,7 +26,8 @@ import { setObfuscatedRoom } from './actions';
|
||||
import {
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
JITSI_CONFERENCE_URL_KEY
|
||||
JITSI_CONFERENCE_URL_KEY,
|
||||
START_MUTED_NOTIFICATION_ID
|
||||
} from './constants';
|
||||
import logger from './logger';
|
||||
import { IJitsiConference } from './reducer';
|
||||
@@ -574,3 +579,42 @@ function safeStartCase(s = '') {
|
||||
(result, word, index) => result + (index ? ' ' : '') + upperFirst(word)
|
||||
, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the mute state of the track based on the start muted policy.
|
||||
*
|
||||
* @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
|
||||
* @param {Function} dispatch - Redux dispatch function.
|
||||
* @param {boolean} isAudio - Whether the track is audio or video.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function updateTrackMuteState(stateful: IStateful, dispatch: IStore['dispatch'], isAudio: boolean) {
|
||||
const state = toState(stateful);
|
||||
const mutedPolicyKey = isAudio ? 'startAudioMutedPolicy' : 'startVideoMutedPolicy';
|
||||
const mutedPolicyValue = state['features/base/conference'][mutedPolicyKey];
|
||||
|
||||
// Currently, the policy only supports force muting others, not unmuting them.
|
||||
if (!mutedPolicyValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
let muteStateUpdated = false;
|
||||
const { muted } = isAudio ? state['features/base/media'].audio : state['features/base/media'].video;
|
||||
|
||||
if (isAudio && !Boolean(muted)) {
|
||||
dispatch(setAudioMuted(mutedPolicyValue, true));
|
||||
muteStateUpdated = true;
|
||||
} else if (!isAudio && !Boolean(muted)) {
|
||||
// TODO: Add a new authority for video mutism for the moderator case.
|
||||
dispatch(setVideoMuted(mutedPolicyValue, VIDEO_MUTISM_AUTHORITY.USER, true));
|
||||
muteStateUpdated = true;
|
||||
}
|
||||
|
||||
if (muteStateUpdated) {
|
||||
dispatch(showNotification({
|
||||
titleKey: 'notify.mutedTitle',
|
||||
descriptionKey: 'notify.muted',
|
||||
uid: START_MUTED_NOTIFICATION_ID // use the same id, to make sure we show one notification
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ import {
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { IConferenceMetadata } from './reducer';
|
||||
import './subscriber';
|
||||
|
||||
/**
|
||||
* Handler for before unload event.
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { setAudioMuted, setVideoMuted } from '../media/actions';
|
||||
import { VIDEO_MUTISM_AUTHORITY } from '../media/constants';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
|
||||
let hasShownNotification = false;
|
||||
|
||||
/**
|
||||
* Handles changes in the start muted policy for audio and video tracks in the meta data set for the conference.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/conference'].startAudioMutedPolicy,
|
||||
/* listener */ (startAudioMutedPolicy, store) => {
|
||||
_updateTrackMuteState(store, true);
|
||||
});
|
||||
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/conference'].startVideoMutedPolicy,
|
||||
/* listener */(startVideoMutedPolicy, store) => {
|
||||
_updateTrackMuteState(store, false);
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the mute state of the track based on the start muted policy.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @param {boolean} isAudio - Whether the track is audio or video.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _updateTrackMuteState(store: IStore, isAudio: boolean) {
|
||||
const { dispatch, getState } = store;
|
||||
const mutedPolicyKey = isAudio ? 'startAudioMutedPolicy' : 'startVideoMutedPolicy';
|
||||
const mutedPolicyValue = getState()['features/base/conference'][mutedPolicyKey];
|
||||
|
||||
// Currently, the policy only supports force muting others, not unmuting them.
|
||||
if (!mutedPolicyValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
let muteStateUpdated = false;
|
||||
const { muted } = isAudio ? getState()['features/base/media'].audio : getState()['features/base/media'].video;
|
||||
|
||||
if (isAudio && !Boolean(muted)) {
|
||||
dispatch(setAudioMuted(mutedPolicyValue, true));
|
||||
muteStateUpdated = true;
|
||||
} else if (!isAudio && !Boolean(muted)) {
|
||||
// TODO: Add a new authority for video mutism for the moderator case.
|
||||
dispatch(setVideoMuted(mutedPolicyValue, VIDEO_MUTISM_AUTHORITY.USER, true));
|
||||
muteStateUpdated = true;
|
||||
}
|
||||
|
||||
if (!hasShownNotification && muteStateUpdated) {
|
||||
hasShownNotification = true;
|
||||
dispatch(showNotification({
|
||||
titleKey: 'notify.mutedTitle',
|
||||
descriptionKey: 'notify.muted'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
}
|
||||
}
|
||||
@@ -521,7 +521,6 @@ export interface IConfig {
|
||||
preCallTestEnabled?: boolean;
|
||||
preCallTestICEUrl?: string;
|
||||
};
|
||||
prejoinPageEnabled?: boolean;
|
||||
raisedHands?: {
|
||||
disableLowerHandByModerator?: boolean;
|
||||
disableLowerHandNotification?: boolean;
|
||||
@@ -553,7 +552,7 @@ export interface IConfig {
|
||||
disableDemote?: boolean;
|
||||
disableGrantModerator?: boolean;
|
||||
disableKick?: boolean;
|
||||
disablePrivateChat?: 'all' | 'allow-moderator-chat';
|
||||
disablePrivateChat?: 'all' | 'allow-moderator-chat' | 'disable-visitor-chat';
|
||||
disabled?: boolean;
|
||||
};
|
||||
replaceParticipant?: string;
|
||||
|
||||
@@ -202,7 +202,6 @@ export default [
|
||||
'prejoinConfig.enabled',
|
||||
'prejoinConfig.hideDisplayName',
|
||||
'prejoinConfig.hideExtraJoinButtons',
|
||||
'prejoinPageEnabled',
|
||||
'raisedHands',
|
||||
'recordingService',
|
||||
'requireDisplayName',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { isEmpty, mergeWith, pick } from 'lodash-es';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getLocalParticipant } from '../participants/functions';
|
||||
import { isEmbedded } from '../util/embedUtils';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
|
||||
import { IConfig } from './configType';
|
||||
@@ -335,7 +336,7 @@ export function setConfigFromURLParams(
|
||||
|
||||
overrideConfigJSON(config, interfaceConfig, json);
|
||||
|
||||
// Print warning about depricated URL params
|
||||
// Print warning about deprecated URL params
|
||||
if ('interfaceConfig.SUPPORT_URL' in params) {
|
||||
logger.warn('Using SUPPORT_URL interfaceConfig URL overwrite is deprecated.'
|
||||
+ ' Please use supportUrl from advanced branding!');
|
||||
@@ -371,6 +372,13 @@ export function setConfigFromURLParams(
|
||||
logger.warn('Using liveStreaming config URL overwrite and/or LIVE_STREAMING_HELP_LINK interfaceConfig URL'
|
||||
+ ' overwrite is deprecated. Please use liveStreaming from advanced branding!');
|
||||
}
|
||||
|
||||
// When not in an iframe, start without media if the pre-join page is not enabled.
|
||||
if (!isEmbedded()
|
||||
&& 'config.prejoinConfig.enabled' in params && config.prejoinConfig?.enabled === false) {
|
||||
logger.warn('Using prejoinConfig.enabled config URL overwrite implies starting without media.');
|
||||
config.disableInitialGUM = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable max-params */
|
||||
|
||||
@@ -405,13 +405,6 @@ function _translateLegacyConfig(oldValue: IConfig) {
|
||||
newValue.welcomePage.disabled = !oldValue.enableWelcomePage;
|
||||
}
|
||||
|
||||
newValue.prejoinConfig = oldValue.prejoinConfig || {};
|
||||
if (oldValue.hasOwnProperty('prejoinPageEnabled')
|
||||
&& !newValue.prejoinConfig.hasOwnProperty('enabled')
|
||||
) {
|
||||
newValue.prejoinConfig.enabled = oldValue.prejoinPageEnabled;
|
||||
}
|
||||
|
||||
newValue.disabledSounds = newValue.disabledSounds || [];
|
||||
|
||||
if (oldValue.disableJoinLeaveSounds) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useCallback } from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { COLORS } from '../../constants';
|
||||
|
||||
interface IProps {
|
||||
@@ -55,7 +54,7 @@ interface IProps {
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
label: {
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
alignItems: 'center',
|
||||
background: theme.palette.ui04,
|
||||
borderRadius: '4px',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export const CAMERA_FACING_MODE = {
|
||||
export const CAMERA_FACING_MODE: Record<string, string> = {
|
||||
ENVIRONMENT: 'environment',
|
||||
USER: 'user'
|
||||
};
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
import { getGravatarURL } from '@jitsi/js-utils/avatar';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { isVisitorChatParticipant } from '../../chat/functions';
|
||||
import { isStageFilmstripAvailable } from '../../filmstrip/functions';
|
||||
import { isAddPeopleEnabled, isDialOutEnabled } from '../../invite/functions';
|
||||
import { toggleShareDialog } from '../../share-room/actions';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { IVisitorChatParticipant } from '../../visitors/types';
|
||||
import { IStateful } from '../app/types';
|
||||
import { GRAVATAR_BASE_URL } from '../avatar/constants';
|
||||
import { isCORSAvatarURL } from '../avatar/functions';
|
||||
@@ -827,18 +829,28 @@ export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean, dispat
|
||||
/**
|
||||
* Checks if private chat is enabled for the given participant.
|
||||
*
|
||||
* @param {IParticipant|undefined} participant - The participant to check.
|
||||
* @param {IParticipant|IVisitorChatParticipant|undefined} participant - The participant to check.
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {boolean} - True if private chat is enabled, false otherwise.
|
||||
*/
|
||||
export function isPrivateChatEnabled(participant: IParticipant | undefined, state: IReduxState) {
|
||||
export function isPrivateChatEnabled(participant: IParticipant | IVisitorChatParticipant | undefined, state: IReduxState) {
|
||||
const { remoteVideoMenu = {} } = state['features/base/config'];
|
||||
const { disablePrivateChat } = remoteVideoMenu;
|
||||
|
||||
if (participant?.local || state['features/visitors'].iAmVisitor || disablePrivateChat === 'all') {
|
||||
if ((!isVisitorChatParticipant(participant) && participant?.local) || disablePrivateChat === 'all') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (disablePrivateChat === 'disable-visitor-chat') {
|
||||
// Block if the participant we're trying to message is a visitor
|
||||
// OR if the local user is a visitor
|
||||
if (isVisitorChatParticipant(participant) || iAmVisitor(state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // should allow private chat for other participants
|
||||
}
|
||||
|
||||
if (disablePrivateChat === 'allow-moderator-chat') {
|
||||
return isLocalParticipantModerator(state) || isParticipantModerator(participant);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { IconArrowDown } from '../../../icons/svg';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
|
||||
interface IProps {
|
||||
|
||||
@@ -82,7 +81,7 @@ interface IProps {
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
actionButton: {
|
||||
...withPixelLineHeight(theme.typography.bodyLongBold),
|
||||
...theme.typography.bodyLongBold,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxSizing: 'border-box',
|
||||
color: theme.palette.text01,
|
||||
@@ -115,7 +114,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
'&.text': {
|
||||
width: 'auto',
|
||||
fontSize: '13px',
|
||||
fontSize: '0.875rem',
|
||||
margin: '0',
|
||||
padding: '0'
|
||||
},
|
||||
@@ -135,7 +134,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
|
||||
[theme.breakpoints.down(400)]: {
|
||||
fontSize: 16,
|
||||
fontSize: '1rem',
|
||||
marginBottom: 8,
|
||||
padding: '11px 16px'
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { IconArrowDown, IconCloseCircle, IconWifi1Bar, IconWifi2Bars, IconWifi3Bars } from '../../../icons/svg';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { PREJOIN_DEFAULT_CONTENT_WIDTH } from '../../../ui/components/variables';
|
||||
import Spinner from '../../../ui/components/web/Spinner';
|
||||
import { runPreCallTest } from '../../actions.web';
|
||||
@@ -16,7 +15,7 @@ const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
connectionStatus: {
|
||||
color: '#fff',
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import Toolbox from '../../../../toolbox/components/web/Toolbox';
|
||||
import { isButtonEnabled } from '../../../../toolbox/functions.web';
|
||||
import { getConferenceName } from '../../../conference/functions';
|
||||
import { PREMEETING_BUTTONS, THIRD_PARTY_PREJOIN_BUTTONS } from '../../../config/constants';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import Tooltip from '../../../tooltip/components/Tooltip';
|
||||
import { isPreCallTestEnabled } from '../../functions';
|
||||
|
||||
@@ -152,7 +151,7 @@ const useStyles = makeStyles()(theme => {
|
||||
width: '100%'
|
||||
},
|
||||
title: {
|
||||
...withPixelLineHeight(theme.typography.heading4),
|
||||
...theme.typography.heading4,
|
||||
color: `${theme.palette.text01}!important`,
|
||||
marginBottom: theme.spacing(3),
|
||||
textAlign: 'center',
|
||||
@@ -168,7 +167,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
roomName: {
|
||||
...withPixelLineHeight(theme.typography.heading5),
|
||||
...theme.typography.heading5,
|
||||
color: theme.palette.text01,
|
||||
display: 'inline-block',
|
||||
overflow: 'hidden',
|
||||
|
||||
@@ -2,8 +2,6 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
warning: {
|
||||
@@ -11,7 +9,7 @@ const useStyles = makeStyles()(theme => {
|
||||
color: theme.palette.text03,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
marginBottom: theme.spacing(3),
|
||||
marginTop: theme.spacing(2),
|
||||
paddingLeft: theme.spacing(3),
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import Checkbox from '../../../ui/components/web/Checkbox';
|
||||
import getUnsafeRoomText from '../../../util/getUnsafeRoomText.web';
|
||||
import { setUnsafeRoomConsent } from '../../actions.web';
|
||||
@@ -14,7 +13,7 @@ const useStyles = makeStyles()(theme => {
|
||||
warning: {
|
||||
backgroundColor: theme.palette.warning01,
|
||||
color: theme.palette.text04,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
padding: theme.spacing(3),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
marginBottom: theme.spacing(3)
|
||||
|
||||
@@ -6,6 +6,7 @@ import { translate } from '../../../i18n/functions';
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import Tooltip from '../../../tooltip/components/Tooltip';
|
||||
import { TOOLTIP_POSITION } from '../../../ui/constants.any';
|
||||
import { pixelsToRem } from '../../../ui/functions.any';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link BaseIndicator}.
|
||||
@@ -40,7 +41,7 @@ interface IProps extends WithTranslation {
|
||||
/**
|
||||
* The font size for the icon.
|
||||
*/
|
||||
iconSize: string | number;
|
||||
iconSize: number;
|
||||
|
||||
/**
|
||||
* The ID attribute to set on the root element of the component.
|
||||
@@ -88,10 +89,10 @@ const BaseIndicator = ({
|
||||
tooltipPosition = 'top'
|
||||
}: IProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const style: { fontSize?: string | number; } = {};
|
||||
const style: { fontSize?: string; } = {};
|
||||
|
||||
if (iconSize) {
|
||||
style.fontSize = iconSize;
|
||||
style.fontSize = pixelsToRem(iconSize);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import Button from '../../../ui/components/web/Button';
|
||||
import { getSupportUrl } from '../../functions';
|
||||
|
||||
@@ -15,7 +14,7 @@ const useStyles = makeStyles()(theme => {
|
||||
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
padding: `${theme.spacing(3)} 10`,
|
||||
'& .retry-button': {
|
||||
margin: '16px auto 0 auto'
|
||||
|
||||
@@ -22,17 +22,3 @@ export function getFixedPlatformStyle(style?: StyleType | StyleType[]) {
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the line height of a CSS Object group in pixels.
|
||||
* By default lineHeight is unitless in CSS, but not in RN.
|
||||
*
|
||||
* @param {Object} base - The base object containing the `lineHeight` property.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function withPixelLineHeight(base: any) {
|
||||
return {
|
||||
...base,
|
||||
lineHeight: `${base.lineHeight}px`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { isMobileBrowser } from '../../environment/utils';
|
||||
import Popover from '../../popover/components/Popover.web';
|
||||
import { withPixelLineHeight } from '../../styles/functions.web';
|
||||
import { TOOLTIP_POSITION } from '../../ui/constants.any';
|
||||
import { hideTooltip, showTooltip } from '../actions';
|
||||
|
||||
@@ -26,7 +25,7 @@ const useStyles = makeStyles()(theme => {
|
||||
backgroundColor: theme.palette.uiBackground,
|
||||
borderRadius: '3px',
|
||||
padding: theme.spacing(2),
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text01,
|
||||
position: 'relative',
|
||||
|
||||
|
||||
@@ -118,8 +118,8 @@ export const colorMap = {
|
||||
|
||||
|
||||
export const font = {
|
||||
weightRegular: '400',
|
||||
weightSemiBold: '600'
|
||||
weightRegular: 400,
|
||||
weightSemiBold: 600
|
||||
};
|
||||
|
||||
export const shape = {
|
||||
@@ -136,64 +136,64 @@ export const typography = {
|
||||
labelBold: 'labelBold01',
|
||||
|
||||
bodyShortRegularSmall: {
|
||||
fontSize: 10,
|
||||
lineHeight: 16,
|
||||
fontSize: '0.625rem',
|
||||
lineHeight: '1rem',
|
||||
fontWeight: font.weightRegular,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyShortRegular: {
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.25rem',
|
||||
fontWeight: font.weightRegular,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyShortBold: {
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.25rem',
|
||||
fontWeight: font.weightSemiBold,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyShortRegularLarge: {
|
||||
fontSize: 16,
|
||||
lineHeight: 22,
|
||||
fontSize: '1rem',
|
||||
lineHeight: '1.375rem',
|
||||
fontWeight: font.weightRegular,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyShortBoldLarge: {
|
||||
fontSize: 16,
|
||||
lineHeight: 22,
|
||||
fontSize: '1rem',
|
||||
lineHeight: '1.375rem',
|
||||
fontWeight: font.weightSemiBold,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyLongRegular: {
|
||||
fontSize: 14,
|
||||
lineHeight: 24,
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.5rem',
|
||||
fontWeight: font.weightRegular,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyLongRegularLarge: {
|
||||
fontSize: 16,
|
||||
lineHeight: 26,
|
||||
fontSize: '1rem',
|
||||
lineHeight: '1.625rem',
|
||||
fontWeight: font.weightRegular,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyLongBold: {
|
||||
fontSize: 14,
|
||||
lineHeight: 24,
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.5rem',
|
||||
fontWeight: font.weightSemiBold,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyLongBoldLarge: {
|
||||
fontSize: 16,
|
||||
lineHeight: 26,
|
||||
fontSize: '1rem',
|
||||
lineHeight: '1.625rem',
|
||||
fontWeight: font.weightSemiBold,
|
||||
letterSpacing: 0
|
||||
},
|
||||
@@ -203,29 +203,29 @@ export const typography = {
|
||||
heading2: 'heading02',
|
||||
|
||||
heading3: {
|
||||
fontSize: 32,
|
||||
lineHeight: 40,
|
||||
fontSize: '2rem',
|
||||
lineHeight: '2.5rem',
|
||||
fontWeight: font.weightSemiBold,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
heading4: {
|
||||
fontSize: 28,
|
||||
lineHeight: 36,
|
||||
fontSize: '1.75rem',
|
||||
lineHeight: '2.25rem',
|
||||
fontWeight: font.weightSemiBold,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
heading5: {
|
||||
fontSize: 20,
|
||||
lineHeight: 28,
|
||||
fontSize: '1.25rem',
|
||||
lineHeight: '1.75rem',
|
||||
fontWeight: font.weightSemiBold,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
heading6: {
|
||||
fontSize: 16,
|
||||
lineHeight: 26,
|
||||
fontSize: '1rem',
|
||||
lineHeight: '1.625rem',
|
||||
fontWeight: font.weightSemiBold,
|
||||
letterSpacing: 0
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { keyframes } from 'tss-react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { isElementInTheViewport } from '../../functions.web';
|
||||
|
||||
import { DialogTransitionContext } from './DialogTransition';
|
||||
@@ -16,7 +15,7 @@ const useStyles = makeStyles()(theme => {
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.bodyLongRegular),
|
||||
...theme.typography.bodyLongRegular,
|
||||
top: 0,
|
||||
left: 0,
|
||||
display: 'flex',
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { BUTTON_TYPES } from '../../constants.web';
|
||||
import { IButtonProps } from '../types';
|
||||
|
||||
@@ -57,7 +56,7 @@ const useStyles = makeStyles()(theme => {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: 0,
|
||||
...withPixelLineHeight(theme.typography.bodyShortBold),
|
||||
...theme.typography.bodyShortBold,
|
||||
transition: 'background .2s',
|
||||
cursor: 'pointer',
|
||||
|
||||
@@ -151,7 +150,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
small: {
|
||||
padding: '8px 16px',
|
||||
...withPixelLineHeight(theme.typography.labelBold),
|
||||
...theme.typography.labelBold,
|
||||
|
||||
'&.iconButton': {
|
||||
padding: theme.spacing(1)
|
||||
@@ -162,7 +161,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
large: {
|
||||
padding: '13px 16px',
|
||||
...withPixelLineHeight(theme.typography.bodyShortBoldLarge),
|
||||
...theme.typography.bodyShortBoldLarge,
|
||||
|
||||
'&.iconButton': {
|
||||
padding: '12px'
|
||||
|
||||
@@ -4,7 +4,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { isMobileBrowser } from '../../../environment/utils';
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { IconCheck } from '../../../icons/svg';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
|
||||
interface ICheckboxProps {
|
||||
|
||||
@@ -42,13 +41,13 @@ interface ICheckboxProps {
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
formControl: {
|
||||
...withPixelLineHeight(theme.typography.bodyLongRegular),
|
||||
...theme.typography.bodyLongRegular,
|
||||
color: theme.palette.text01,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
|
||||
'&.is-mobile': {
|
||||
...withPixelLineHeight(theme.typography.bodyLongRegularLarge)
|
||||
...theme.typography.bodyLongRegularLarge
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,7 +8,6 @@ import Drawer from '../../../../toolbox/components/web/Drawer';
|
||||
import JitsiPortal from '../../../../toolbox/components/web/JitsiPortal';
|
||||
import { showOverflowDrawer } from '../../../../toolbox/functions.web';
|
||||
import participantsPaneTheme from '../../../components/themes/participantsPaneTheme.json';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { spacing } from '../../Tokens';
|
||||
|
||||
|
||||
@@ -139,7 +138,7 @@ const useStyles = makeStyles()(theme => {
|
||||
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
marginTop: '48px',
|
||||
position: 'absolute',
|
||||
right: `${participantsPaneTheme.panePadding}px`,
|
||||
@@ -159,7 +158,7 @@ const useStyles = makeStyles()(theme => {
|
||||
paddingTop: '16px',
|
||||
|
||||
'& > div': {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge),
|
||||
...theme.typography.bodyShortRegularLarge,
|
||||
|
||||
'& svg': {
|
||||
fill: theme.palette.icon01
|
||||
|
||||
@@ -4,7 +4,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { showOverflowDrawer } from '../../../../toolbox/functions.web';
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { TEXT_OVERFLOW_TYPES } from '../../constants.any';
|
||||
|
||||
import TextWithOverflow from './TextWithOverflow';
|
||||
@@ -14,7 +13,7 @@ export interface IProps {
|
||||
/**
|
||||
* Label used for accessibility.
|
||||
*/
|
||||
accessibilityLabel: string;
|
||||
accessibilityLabel?: string;
|
||||
|
||||
/**
|
||||
* The context menu item background color.
|
||||
@@ -174,12 +173,12 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
text: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01
|
||||
},
|
||||
|
||||
drawerText: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
...theme.typography.bodyShortRegularLarge
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -232,7 +231,7 @@ const ContextMenuItem = ({
|
||||
<div
|
||||
aria-controls = { controls }
|
||||
aria-disabled = { disabled }
|
||||
aria-label = { accessibilityLabel }
|
||||
aria-label = { accessibilityLabel || undefined }
|
||||
aria-selected = { role === 'tab' ? selected : undefined }
|
||||
className = { cx(styles.contextMenuItem,
|
||||
_overflowDrawer && styles.contextMenuItemDrawer,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { hideDialog } from '../../../dialog/actions';
|
||||
import { IconCloseLarge } from '../../../icons/svg';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { operatesWithEnterKey } from '../../functions.web';
|
||||
|
||||
import BaseDialog, { IProps as IBaseDialogProps } from './BaseDialog';
|
||||
@@ -26,7 +25,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
title: {
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.heading5),
|
||||
...theme.typography.heading5,
|
||||
margin: 0,
|
||||
padding: 0
|
||||
},
|
||||
|
||||
@@ -6,7 +6,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { hideDialog } from '../../../dialog/actions';
|
||||
import { IconArrowBack, IconCloseLarge } from '../../../icons/svg';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
|
||||
import BaseDialog, { IProps as IBaseProps } from './BaseDialog';
|
||||
import Button from './Button';
|
||||
@@ -70,7 +69,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
title: {
|
||||
...withPixelLineHeight(theme.typography.heading5),
|
||||
...theme.typography.heading5,
|
||||
color: `${theme.palette.text01} !important`,
|
||||
margin: 0,
|
||||
padding: 0
|
||||
|
||||
@@ -5,7 +5,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { isMobileBrowser } from '../../../environment/utils';
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { IconCloseCircle } from '../../../icons/svg';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { IInputProps } from '../types';
|
||||
|
||||
import { HiddenDescription } from './HiddenDescription';
|
||||
@@ -51,11 +50,11 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
label: {
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
marginBottom: theme.spacing(2),
|
||||
|
||||
'&.is-mobile': {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
...theme.typography.bodyShortRegularLarge
|
||||
}
|
||||
},
|
||||
|
||||
@@ -68,7 +67,7 @@ const useStyles = makeStyles()(theme => {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
background: theme.palette.ui03,
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
padding: '10px 16px',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: 0,
|
||||
@@ -92,7 +91,7 @@ const useStyles = makeStyles()(theme => {
|
||||
'&.is-mobile': {
|
||||
height: '48px',
|
||||
padding: '13px 16px',
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
...theme.typography.bodyShortRegularLarge
|
||||
},
|
||||
|
||||
'&.icon-input': {
|
||||
@@ -139,11 +138,11 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
bottomLabel: {
|
||||
marginTop: theme.spacing(2),
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text02,
|
||||
|
||||
'&.is-mobile': {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular)
|
||||
...theme.typography.bodyShortRegular
|
||||
},
|
||||
|
||||
'&.error': {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { ACTION_TRIGGER } from '../../../../participants-pane/constants';
|
||||
import participantsPaneTheme from '../../../components/themes/participantsPaneTheme.json';
|
||||
import { isMobileBrowser } from '../../../environment/utils';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
|
||||
interface IProps {
|
||||
|
||||
@@ -86,7 +85,7 @@ const useStyles = makeStyles()(theme => {
|
||||
alignItems: 'center',
|
||||
color: theme.palette.text01,
|
||||
display: 'flex',
|
||||
...withPixelLineHeight(theme.typography.bodyShortBold),
|
||||
...theme.typography.bodyShortBold,
|
||||
margin: `0 -${participantsPaneTheme.panePadding}px`,
|
||||
padding: `${theme.spacing(2)} ${participantsPaneTheme.panePadding}px`,
|
||||
position: 'relative',
|
||||
@@ -110,7 +109,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
[`@media(max-width: ${participantsPaneTheme.MD_BREAKPOINT})`]: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortBoldLarge),
|
||||
...theme.typography.bodyShortBoldLarge,
|
||||
padding: `${theme.spacing(3)} ${participantsPaneTheme.panePadding}px`
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IconCloseLarge } from '../../../icons/svg';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { MultiSelectItem } from '../types';
|
||||
|
||||
import ClickableIcon from './ClickableIcon';
|
||||
@@ -42,7 +41,7 @@ const useStyles = makeStyles()(theme => {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
border: `1px solid ${theme.palette.ui04}`,
|
||||
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
zIndex: 2,
|
||||
maxHeight: `${MULTI_SELECT_HEIGHT}px`,
|
||||
overflowY: 'auto',
|
||||
|
||||
@@ -4,7 +4,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { isMobileBrowser } from '../../../environment/utils';
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { IconArrowDown } from '../../../icons/svg';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
|
||||
interface ISelectProps {
|
||||
|
||||
@@ -18,6 +17,11 @@ interface ISelectProps {
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* Class name for additional styles for container.
|
||||
*/
|
||||
containerClassName?: string;
|
||||
|
||||
/**
|
||||
* Whether or not the select is disabled.
|
||||
*/
|
||||
@@ -67,11 +71,11 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
label: {
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
marginBottom: theme.spacing(2),
|
||||
|
||||
'&.is-mobile': {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
...theme.typography.bodyShortRegularLarge
|
||||
}
|
||||
},
|
||||
|
||||
@@ -83,7 +87,7 @@ const useStyles = makeStyles()(theme => {
|
||||
backgroundColor: theme.palette.ui03,
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
width: '100%',
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01,
|
||||
padding: '10px 16px',
|
||||
paddingRight: '42px',
|
||||
@@ -103,7 +107,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'&.is-mobile': {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge),
|
||||
...theme.typography.bodyShortRegularLarge,
|
||||
padding: '12px 16px',
|
||||
paddingRight: '46px'
|
||||
},
|
||||
@@ -127,11 +131,11 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
bottomLabel: {
|
||||
marginTop: theme.spacing(2),
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text02,
|
||||
|
||||
'&.is-mobile': {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular)
|
||||
...theme.typography.bodyShortRegular
|
||||
},
|
||||
|
||||
'&.error': {
|
||||
@@ -143,6 +147,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
const Select = ({
|
||||
bottomLabel,
|
||||
containerClassName,
|
||||
className,
|
||||
disabled,
|
||||
error,
|
||||
@@ -155,7 +160,7 @@ const Select = ({
|
||||
const isMobile = isMobileBrowser();
|
||||
|
||||
return (
|
||||
<div className = { classes.container }>
|
||||
<div className = { cx(classes.container, containerClassName) }>
|
||||
{label && <label
|
||||
className = { cx(classes.label, isMobile && 'is-mobile') }
|
||||
htmlFor = { id } >
|
||||
|
||||
@@ -3,7 +3,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { isMobileBrowser } from '../../../environment/utils';
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
|
||||
interface ITabProps {
|
||||
accessibilityLabel: string;
|
||||
@@ -18,6 +17,7 @@ interface ITabProps {
|
||||
icon?: Function;
|
||||
id: string;
|
||||
label?: string;
|
||||
title?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
tab: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortBold),
|
||||
...theme.typography.bodyShortBold,
|
||||
color: theme.palette.text02,
|
||||
flex: 1,
|
||||
padding: '14px',
|
||||
@@ -65,12 +65,12 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
'&.is-mobile': {
|
||||
...withPixelLineHeight(theme.typography.bodyShortBoldLarge)
|
||||
...theme.typography.bodyShortBoldLarge
|
||||
}
|
||||
},
|
||||
|
||||
badge: {
|
||||
...withPixelLineHeight(theme.typography.labelBold),
|
||||
...theme.typography.labelBold,
|
||||
color: theme.palette.text04,
|
||||
padding: `0 ${theme.spacing(1)}`,
|
||||
borderRadius: '100%',
|
||||
@@ -127,26 +127,34 @@ const Tabs = ({
|
||||
aria-label = { accessibilityLabel }
|
||||
className = { cx(classes.container, className) }
|
||||
role = 'tablist'>
|
||||
{tabs.map((tab, index) => (
|
||||
<button
|
||||
aria-controls = { tab.controlsId }
|
||||
aria-label = { tab.accessibilityLabel }
|
||||
aria-selected = { selected === tab.id }
|
||||
className = { cx(classes.tab, selected === tab.id && 'selected', isMobile && 'is-mobile') }
|
||||
disabled = { tab.disabled }
|
||||
id = { tab.id }
|
||||
key = { tab.id }
|
||||
onClick = { onClick(tab.id) }
|
||||
onKeyDown = { onKeyDown(index) }
|
||||
role = 'tab'
|
||||
tabIndex = { selected === tab.id ? undefined : -1 }>
|
||||
{tab.icon && <Icon
|
||||
className = { classes.icon }
|
||||
src = { tab.icon } />}
|
||||
{tab.label}
|
||||
{tab.countBadge && <span className = { classes.badge }>{tab.countBadge}</span>}
|
||||
</button>
|
||||
))}
|
||||
{
|
||||
tabs.map((tab, index) => (
|
||||
<button
|
||||
aria-controls = { tab.controlsId }
|
||||
aria-label = { tab.accessibilityLabel }
|
||||
aria-selected = { selected === tab.id }
|
||||
className = { cx(classes.tab, selected === tab.id && 'selected', isMobile && 'is-mobile') }
|
||||
disabled = { tab.disabled }
|
||||
id = { tab.id }
|
||||
key = { tab.id }
|
||||
onClick = { onClick(tab.id) }
|
||||
onKeyDown = { onKeyDown(index) }
|
||||
role = 'tab'
|
||||
tabIndex = { selected === tab.id ? undefined : -1 }
|
||||
title = { tab.title }>
|
||||
{
|
||||
tab.icon && <Icon
|
||||
className = { classes.icon }
|
||||
src = { tab.icon } />
|
||||
}
|
||||
{ tab.label }
|
||||
{
|
||||
tab.countBadge && <span className = { classes.badge }>
|
||||
{ tab.countBadge }
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
marginTop: theme.spacing(3),
|
||||
|
||||
'& label': {
|
||||
fontSize: '14px'
|
||||
fontSize: '0.875rem'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -42,10 +42,10 @@ export const commonStyles = (theme: Theme) => {
|
||||
color: theme.palette.text01,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
fontSize: 14,
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 400,
|
||||
height: 40,
|
||||
lineHeight: '24px',
|
||||
lineHeight: '1.5rem',
|
||||
padding: '8px 16px',
|
||||
boxSizing: 'border-box' as const,
|
||||
'& > div': {
|
||||
@@ -85,7 +85,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
|
||||
'& i': {
|
||||
display: 'inline',
|
||||
fontSize: 24
|
||||
fontSize: '1.5rem'
|
||||
},
|
||||
|
||||
'@media (hover: hover) and (pointer: fine)': {
|
||||
@@ -120,8 +120,8 @@ export const commonStyles = (theme: Theme) => {
|
||||
},
|
||||
|
||||
'.prejoin-dialog-label': {
|
||||
fontSize: '15px',
|
||||
lineHeight: '24px'
|
||||
fontSize: '1rem',
|
||||
lineHeight: '1.5rem'
|
||||
},
|
||||
|
||||
'.prejoin-dialog-label-num': {
|
||||
@@ -156,8 +156,8 @@ export const commonStyles = (theme: Theme) => {
|
||||
|
||||
'.prejoin-dialog-title': {
|
||||
display: 'inline-block',
|
||||
fontSize: '24px',
|
||||
lineHeight: '32px'
|
||||
fontSize: '1.5rem',
|
||||
lineHeight: '2rem'
|
||||
},
|
||||
|
||||
'.prejoin-dialog-icon': {
|
||||
@@ -196,7 +196,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
'.prejoin-dialog-delimiter-txt': {
|
||||
background: theme.palette.uiBackground,
|
||||
color: theme.palette.text01,
|
||||
fontSize: '11px',
|
||||
fontSize: '0.75rem',
|
||||
textTransform: 'uppercase' as const,
|
||||
padding: `0 ${theme.spacing(2)}`
|
||||
}
|
||||
@@ -212,7 +212,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
display: 'flex',
|
||||
borderRadius: 3,
|
||||
flexDirection: 'column' as const,
|
||||
fontSize: 24,
|
||||
fontSize: '1.5rem',
|
||||
height: 48,
|
||||
justifyContent: 'center',
|
||||
width: 48,
|
||||
@@ -249,7 +249,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
color: theme.palette.text01,
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
lineHeight: '48px',
|
||||
lineHeight: '3rem',
|
||||
textAlign: 'center' as const
|
||||
},
|
||||
|
||||
|
||||
25
react/features/base/ui/functions.any.ts
Normal file
25
react/features/base/ui/functions.any.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// Base font size in pixels (standard is 16px = 1rem)
|
||||
const BASE_FONT_SIZE = 16;
|
||||
|
||||
/**
|
||||
* Converts rem to pixels.
|
||||
*
|
||||
* @param {string} remValue - The value in rem units (e.g. '0.875rem').
|
||||
* @returns {number}
|
||||
*/
|
||||
export function remToPixels(remValue: string): number {
|
||||
const numericValue = parseFloat(remValue.replace('rem', ''));
|
||||
|
||||
return Math.round(numericValue * BASE_FONT_SIZE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts pixels to rem.
|
||||
*
|
||||
* @param {number} pixels - The value in pixels.
|
||||
* @returns {string}
|
||||
* */
|
||||
export function pixelsToRem(pixels: number): string {
|
||||
return `${(pixels / BASE_FONT_SIZE).toFixed(3)}rem`;
|
||||
}
|
||||
@@ -1,7 +1,36 @@
|
||||
import { DefaultTheme } from 'react-native-paper';
|
||||
|
||||
import { remToPixels } from './functions.any';
|
||||
import { createColorTokens } from './utils';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
/**
|
||||
* Converts all rem to pixels in an object.
|
||||
*
|
||||
* @param {Object} obj - The object to convert rem values in.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function convertRemValues(obj: any): any {
|
||||
const converted: { [key: string]: any; } = {};
|
||||
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
Object.entries(obj).forEach(([ key, value ]) => {
|
||||
if (typeof value === 'string' && value.includes('rem')) {
|
||||
converted[key] = remToPixels(value);
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
converted[key] = convertRemValues(value);
|
||||
} else {
|
||||
converted[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a React Native Paper theme based on local UI tokens.
|
||||
*
|
||||
@@ -16,7 +45,7 @@ export function createNativeTheme({ font, colorMap, shape, spacing, typography }
|
||||
spacing,
|
||||
typography: {
|
||||
font,
|
||||
...typography
|
||||
...convertRemValues(typography)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ITypography, IPalette as Palette1 } from '../ui/types';
|
||||
|
||||
import { createColorTokens, createTypographyTokens } from './utils';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface Palette extends Palette1 {}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
interface ITypographyType {
|
||||
fontSize: number;
|
||||
fontWeight: number; // TODO: revisit this.
|
||||
fontSize: string;
|
||||
fontWeight: 'normal' | 'bold' | 'bolder' | 'lighter' | number;
|
||||
letterSpacing: number;
|
||||
lineHeight: number;
|
||||
lineHeight: string;
|
||||
}
|
||||
|
||||
export interface IPalette {
|
||||
|
||||
@@ -130,7 +130,7 @@ export default createStyleSheet({
|
||||
*/
|
||||
notificationIcon: {
|
||||
color: 'white',
|
||||
fontSize: 25
|
||||
fontSize: '1.5rem'
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -148,7 +148,7 @@ export default createStyleSheet({
|
||||
*/
|
||||
notificationText: {
|
||||
color: 'white',
|
||||
fontSize: 13
|
||||
fontSize: '0.875rem'
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -163,3 +163,12 @@ export const SET_USER_CHAT_WIDTH = 'SET_USER_CHAT_WIDTH';
|
||||
* }
|
||||
*/
|
||||
export const SET_CHAT_IS_RESIZING = 'SET_CHAT_IS_RESIZING';
|
||||
|
||||
/**
|
||||
* The type of action sets the timestamp of the last private chat recipients list changed.
|
||||
*
|
||||
* {
|
||||
* type: NOTIFY_PRIVATE_RECIPIENTS_CHANGED
|
||||
* }
|
||||
*/
|
||||
export const NOTIFY_PRIVATE_RECIPIENTS_CHANGED = 'NOTIFY_PRIVATE_RECIPIENTS_CHANGED';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { getLocalParticipant } from '../base/participants/functions';
|
||||
import { getLocalParticipant, getParticipantById } from '../base/participants/functions';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { LOBBY_CHAT_INITIALIZED } from '../lobby/constants';
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
CLEAR_MESSAGES,
|
||||
CLOSE_CHAT,
|
||||
EDIT_MESSAGE,
|
||||
NOTIFY_PRIVATE_RECIPIENTS_CHANGED,
|
||||
OPEN_CHAT,
|
||||
REMOVE_LOBBY_CHAT_PARTICIPANT,
|
||||
SEND_MESSAGE,
|
||||
@@ -170,6 +171,25 @@ export function setPrivateMessageRecipient(participant?: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the sending of a private message to the supplied participantId.
|
||||
*
|
||||
* @param {string} participantId - The participant id to set the recipient to.
|
||||
* @returns {{
|
||||
* participant: IParticipant,
|
||||
* type: SET_PRIVATE_MESSAGE_RECIPIENT
|
||||
* }}
|
||||
*/
|
||||
export function setPrivateMessageRecipientById(participantId: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const participant = getParticipantById(getState(), participantId);
|
||||
|
||||
if (participant) {
|
||||
dispatch(setPrivateMessageRecipient(participant));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the currently focused tab.
|
||||
*
|
||||
@@ -252,6 +272,22 @@ export function setLobbyChatActiveState(value: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the private chat recipients list changed.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function notifyPrivateRecipientsChanged() {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
return dispatch({
|
||||
type: NOTIFY_PRIVATE_RECIPIENTS_CHANGED,
|
||||
payload: timestamp
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes lobby type messages.
|
||||
*
|
||||
|
||||
@@ -4,6 +4,7 @@ import { WithTranslation } from 'react-i18next';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { getParticipantById } from '../../base/participants/functions';
|
||||
import { IParticipant } from '../../base/participants/types';
|
||||
import { IVisitorChatParticipant } from '../../visitors/types';
|
||||
import { sendMessage, setPrivateMessageRecipient } from '../actions';
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
@@ -23,6 +24,16 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
_participant?: IParticipant;
|
||||
|
||||
/**
|
||||
* The display name of the visitor (if applicable).
|
||||
*/
|
||||
displayName?: string;
|
||||
|
||||
/**
|
||||
* Whether the message is from a visitor.
|
||||
*/
|
||||
isFromVisitor?: boolean;
|
||||
|
||||
/**
|
||||
* The message that is about to be sent.
|
||||
*/
|
||||
@@ -67,9 +78,21 @@ export class AbstractChatPrivacyDialog extends PureComponent<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSendPrivateMessage() {
|
||||
const { message, _onSendMessage, _onSetMessageRecipient, _participant } = this.props;
|
||||
const { message, _onSendMessage, _onSetMessageRecipient, _participant, isFromVisitor, displayName, participantID } = this.props;
|
||||
|
||||
if (isFromVisitor) {
|
||||
// For visitors, create a participant object since they don't exist in the main participant list
|
||||
const visitorParticipant = {
|
||||
id: participantID,
|
||||
name: displayName,
|
||||
isVisitor: true
|
||||
};
|
||||
|
||||
_onSetMessageRecipient(visitorParticipant);
|
||||
} else {
|
||||
_onSetMessageRecipient(_participant);
|
||||
}
|
||||
|
||||
_onSetMessageRecipient(_participant);
|
||||
_onSendMessage(message);
|
||||
|
||||
return true;
|
||||
@@ -88,7 +111,7 @@ export function _mapDispatchToProps(dispatch: IStore['dispatch']) {
|
||||
dispatch(sendMessage(message, true));
|
||||
},
|
||||
|
||||
_onSetMessageRecipient: (participant: IParticipant) => {
|
||||
_onSetMessageRecipient: (participant: IParticipant | IVisitorChatParticipant) => {
|
||||
dispatch(setPrivateMessageRecipient(participant));
|
||||
}
|
||||
};
|
||||
@@ -103,6 +126,6 @@ export function _mapDispatchToProps(dispatch: IStore['dispatch']) {
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
return {
|
||||
_participant: getParticipantById(state, ownProps.participantID)
|
||||
_participant: ownProps.isFromVisitor ? undefined : getParticipantById(state, ownProps.participantID)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { getParticipantDisplayName, isLocalParticipantModerator } from '../../base/participants/functions';
|
||||
import { getVisitorDisplayName } from '../../visitors/functions';
|
||||
import { setLobbyChatActiveState, setPrivateMessageRecipient } from '../actions.any';
|
||||
import { isVisitorChatParticipant } from '../functions';
|
||||
|
||||
|
||||
export interface IProps extends WithTranslation {
|
||||
@@ -13,6 +15,11 @@ export interface IProps extends WithTranslation {
|
||||
*/
|
||||
_isLobbyChatActive: boolean;
|
||||
|
||||
/**
|
||||
* Whether the private message recipient is a visitor.
|
||||
*/
|
||||
_isVisitor?: boolean;
|
||||
|
||||
/**
|
||||
* The name of the lobby message recipient, if any.
|
||||
*/
|
||||
@@ -72,10 +79,18 @@ export function _mapDispatchToProps(dispatch: IStore['dispatch']) {
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { privateMessageRecipient, lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
|
||||
let _privateMessageRecipient;
|
||||
const _isVisitor = isVisitorChatParticipant(privateMessageRecipient);
|
||||
|
||||
if (privateMessageRecipient) {
|
||||
_privateMessageRecipient = _isVisitor
|
||||
? getVisitorDisplayName(state, privateMessageRecipient.name)
|
||||
: getParticipantDisplayName(state, privateMessageRecipient.id);
|
||||
}
|
||||
|
||||
return {
|
||||
_privateMessageRecipient:
|
||||
privateMessageRecipient ? getParticipantDisplayName(state, privateMessageRecipient.id) : undefined,
|
||||
_privateMessageRecipient,
|
||||
_isVisitor,
|
||||
_isLobbyChatActive: isLobbyChatActive,
|
||||
_lobbyMessageRecipient:
|
||||
isLobbyChatActive && lobbyMessageRecipient ? lobbyMessageRecipient.name : undefined,
|
||||
|
||||
@@ -122,15 +122,17 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
* @returns {React.ReactElement<*> | null}
|
||||
*/
|
||||
_renderDisplayName() {
|
||||
const { message, showDisplayName } = this.props;
|
||||
const { message, showDisplayName, t } = this.props;
|
||||
|
||||
if (!showDisplayName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { displayName, isFromVisitor } = message;
|
||||
|
||||
return (
|
||||
<Text style = { styles.senderDisplayName }>
|
||||
{ message.displayName }
|
||||
{ `${displayName}${isFromVisitor ? ` ${t('visitors.chatIndicator')}` : ''}` }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { setLobbyChatActiveState, setPrivateMessageRecipient } from '../../actions.any';
|
||||
import AbstractMessageRecipient, {
|
||||
IProps as AbstractProps
|
||||
IProps as AbstractProps,
|
||||
_mapStateToProps as _mapStateToPropsAbstract
|
||||
} from '../AbstractMessageRecipient';
|
||||
|
||||
import styles from './styles';
|
||||
@@ -36,11 +37,6 @@ interface IProps extends AbstractProps {
|
||||
id: string;
|
||||
name: string;
|
||||
} | ILocalParticipant;
|
||||
|
||||
/**
|
||||
* The participant object set for private messaging.
|
||||
*/
|
||||
privateMessageRecipient: { name: string; };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +92,8 @@ class MessageRecipient extends AbstractMessageRecipient<IProps> {
|
||||
const {
|
||||
isLobbyChatActive,
|
||||
lobbyMessageRecipient,
|
||||
privateMessageRecipient,
|
||||
_privateMessageRecipient,
|
||||
_isVisitor,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
@@ -120,7 +117,7 @@ class MessageRecipient extends AbstractMessageRecipient<IProps> {
|
||||
);
|
||||
}
|
||||
|
||||
if (!privateMessageRecipient) {
|
||||
if (!_privateMessageRecipient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -130,7 +127,7 @@ class MessageRecipient extends AbstractMessageRecipient<IProps> {
|
||||
style = { styles.messageRecipientContainer as ViewStyle }>
|
||||
<Text style = { styles.messageRecipientText }>
|
||||
{ t('chat.messageTo', {
|
||||
recipient: privateMessageRecipient.name
|
||||
recipient: `${_privateMessageRecipient}${_isVisitor ? ` ${t('visitors.chatIndicator')}` : ''}`
|
||||
}) }
|
||||
</Text>
|
||||
<TouchableHighlight
|
||||
@@ -157,6 +154,7 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
|
||||
|
||||
return {
|
||||
..._mapStateToPropsAbstract(state, _ownProps),
|
||||
isLobbyChatActive,
|
||||
lobbyMessageRecipient
|
||||
};
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
import { throttle } from 'lodash-es';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconInfo, IconMessage, IconShareDoc, IconSubtitles } from '../../../base/icons/svg';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import { getLocalParticipant, getRemoteParticipants } from '../../../base/participants/functions';
|
||||
import Select from '../../../base/ui/components/web/Select';
|
||||
import Tabs from '../../../base/ui/components/web/Tabs';
|
||||
import { arePollsDisabled } from '../../../conference/functions.any';
|
||||
import FileSharing from '../../../file-sharing/components/web/FileSharing';
|
||||
import { isFileSharingEnabled } from '../../../file-sharing/functions.any';
|
||||
import PollsPane from '../../../polls/components/web/PollsPane';
|
||||
import { isCCTabEnabled } from '../../../subtitles/functions.any';
|
||||
import { sendMessage, setChatIsResizing, setFocusedTab, setUserChatWidth, toggleChat } from '../../actions.web';
|
||||
import { CHAT_SIZE, ChatTabs, SMALL_WIDTH_THRESHOLD } from '../../constants';
|
||||
import {
|
||||
sendMessage,
|
||||
setChatIsResizing,
|
||||
setFocusedTab,
|
||||
setPrivateMessageRecipient,
|
||||
setPrivateMessageRecipientById,
|
||||
setUserChatWidth,
|
||||
toggleChat
|
||||
} from '../../actions.web';
|
||||
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
|
||||
import { getChatMaxSize } from '../../functions';
|
||||
import { IChatProps as AbstractProps } from '../../types';
|
||||
|
||||
@@ -216,6 +225,10 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme,
|
||||
height: '100px',
|
||||
width: '3px',
|
||||
borderRadius: '1px'
|
||||
},
|
||||
|
||||
privateMessageRecipientsList: {
|
||||
padding: '0 16px 5px'
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -245,6 +258,34 @@ const Chat = ({
|
||||
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
|
||||
const [ dragChatWidth, setDragChatWidth ] = useState<number | null>(null);
|
||||
const maxChatWidth = useSelector(getChatMaxSize);
|
||||
const notifyTimestamp = useSelector((state: IReduxState) =>
|
||||
state['features/chat'].notifyPrivateRecipientsChangedTimestamp
|
||||
);
|
||||
const {
|
||||
defaultRemoteDisplayName = 'Fellow Jitster'
|
||||
} = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const privateMessageRecipient = useSelector((state: IReduxState) => state['features/chat'].privateMessageRecipient);
|
||||
const participants = useSelector(getRemoteParticipants);
|
||||
|
||||
const options = useMemo(() => {
|
||||
const o = Array.from(participants?.values() || [])
|
||||
.filter(p => !p.fakeParticipant)
|
||||
.map(p => {
|
||||
return {
|
||||
value: p.id,
|
||||
label: p.name ?? defaultRemoteDisplayName
|
||||
};
|
||||
});
|
||||
|
||||
o.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
o.unshift({
|
||||
label: t('chat.everyone'),
|
||||
value: OPTION_GROUPCHAT
|
||||
});
|
||||
|
||||
return o;
|
||||
}, [ participants, defaultRemoteDisplayName, t, notifyTimestamp ]);
|
||||
|
||||
/**
|
||||
* Handles mouse down on the drag handle.
|
||||
@@ -376,6 +417,17 @@ const Chat = ({
|
||||
dispatch(setFocusedTab(id as ChatTabs));
|
||||
}, [ dispatch ]);
|
||||
|
||||
|
||||
const onSelectedRecipientChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const selected = e.target.value;
|
||||
|
||||
if (selected === OPTION_GROUPCHAT) {
|
||||
dispatch(setPrivateMessageRecipient());
|
||||
} else {
|
||||
dispatch(setPrivateMessageRecipientById(selected));
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Returns a React Element for showing chat messages and a form to send new
|
||||
* chat messages.
|
||||
@@ -403,6 +455,12 @@ const Chat = ({
|
||||
<MessageContainer
|
||||
messages = { _messages } />
|
||||
<MessageRecipient />
|
||||
<Select
|
||||
containerClassName = { cx(classes.privateMessageRecipientsList) }
|
||||
id = 'select-chat-recipient'
|
||||
onChange = { onSelectedRecipientChange }
|
||||
options = { options }
|
||||
value = { privateMessageRecipient?.id || OPTION_GROUPCHAT } />
|
||||
<ChatInput
|
||||
onSend = { onSendMessage } />
|
||||
</div>
|
||||
@@ -454,7 +512,8 @@ const Chat = ({
|
||||
_focusedTab !== ChatTabs.CHAT && _nbUnreadMessages > 0 ? _nbUnreadMessages : undefined,
|
||||
id: ChatTabs.CHAT,
|
||||
controlsId: `${ChatTabs.CHAT}-panel`,
|
||||
icon: IconMessage
|
||||
icon: IconMessage,
|
||||
title: t('chat.tabs.chat')
|
||||
}
|
||||
];
|
||||
|
||||
@@ -464,7 +523,8 @@ const Chat = ({
|
||||
countBadge: _focusedTab !== ChatTabs.POLLS && _nbUnreadPolls > 0 ? _nbUnreadPolls : undefined,
|
||||
id: ChatTabs.POLLS,
|
||||
controlsId: `${ChatTabs.POLLS}-panel`,
|
||||
icon: IconInfo
|
||||
icon: IconInfo,
|
||||
title: t('chat.tabs.polls')
|
||||
});
|
||||
}
|
||||
|
||||
@@ -474,7 +534,8 @@ const Chat = ({
|
||||
countBadge: undefined,
|
||||
id: ChatTabs.CLOSED_CAPTIONS,
|
||||
controlsId: `${ChatTabs.CLOSED_CAPTIONS}-panel`,
|
||||
icon: IconSubtitles
|
||||
icon: IconSubtitles,
|
||||
title: t('chat.tabs.closedCaptions')
|
||||
});
|
||||
}
|
||||
|
||||
@@ -484,7 +545,8 @@ const Chat = ({
|
||||
countBadge: undefined,
|
||||
id: ChatTabs.FILE_SHARING,
|
||||
controlsId: `${ChatTabs.FILE_SHARING}-panel`,
|
||||
icon: IconShareDoc
|
||||
icon: IconShareDoc,
|
||||
title: t('chat.tabs.fileSharing')
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { translate } from '../../../base/i18n/functions';
|
||||
import { getParticipantById, getParticipantDisplayName, isPrivateChatEnabled } from '../../../base/participants/functions';
|
||||
import Popover from '../../../base/popover/components/Popover.web';
|
||||
import Message from '../../../base/react/components/web/Message';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import { MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import { getFormattedTimestamp, getMessageText, getPrivateNoticeMessage } from '../../functions';
|
||||
import { IChatMessageProps } from '../../types';
|
||||
|
||||
@@ -16,9 +16,10 @@ import MessageMenu from './MessageMenu';
|
||||
import ReactButton from './ReactButton';
|
||||
|
||||
interface IProps extends IChatMessageProps {
|
||||
shouldDisplayChatMessageMenu: boolean;
|
||||
className?: string;
|
||||
enablePrivateChat?: boolean;
|
||||
shouldDisplayMenuOnRight?: boolean;
|
||||
state?: IReduxState;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()((theme: Theme) => {
|
||||
@@ -128,7 +129,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
minHeight: '32px'
|
||||
},
|
||||
displayName: {
|
||||
...withPixelLineHeight(theme.typography.labelBold),
|
||||
...theme.typography.labelBold,
|
||||
color: theme.palette.text02,
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
@@ -137,18 +138,18 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
maxWidth: '130px'
|
||||
},
|
||||
userMessage: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01,
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word'
|
||||
},
|
||||
privateMessageNotice: {
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text02,
|
||||
marginTop: theme.spacing(1)
|
||||
},
|
||||
timestamp: {
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text03,
|
||||
marginTop: theme.spacing(1),
|
||||
marginLeft: theme.spacing(1),
|
||||
@@ -190,11 +191,12 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
});
|
||||
|
||||
const ChatMessage = ({
|
||||
className = '',
|
||||
message,
|
||||
state,
|
||||
showDisplayName,
|
||||
type,
|
||||
shouldDisplayChatMessageMenu,
|
||||
shouldDisplayMenuOnRight,
|
||||
enablePrivateChat,
|
||||
knocking,
|
||||
t
|
||||
}: IProps) => {
|
||||
@@ -224,11 +226,13 @@ const ChatMessage = ({
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
function _renderDisplayName() {
|
||||
const { displayName, isFromVisitor = false } = message;
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-hidden = { true }
|
||||
className = { cx('display-name', classes.displayName) }>
|
||||
{message.displayName}
|
||||
{`${displayName}${isFromVisitor ? ` ${t('visitors.chatIndicator')}` : ''}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -329,26 +333,28 @@ const ChatMessage = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { cx(classes.chatMessageWrapper, type) }
|
||||
className = { cx(classes.chatMessageWrapper, className) }
|
||||
id = { message.messageId }
|
||||
onMouseEnter = { handleMouseEnter }
|
||||
onMouseLeave = { handleMouseLeave }
|
||||
tabIndex = { -1 }>
|
||||
<div className = { classes.sideBySideContainer }>
|
||||
{!shouldDisplayChatMessageMenu && (
|
||||
{!shouldDisplayMenuOnRight && (
|
||||
<div className = { classes.optionsButtonContainer }>
|
||||
{isHovered && <MessageMenu
|
||||
displayName = { message.displayName }
|
||||
enablePrivateChat = { Boolean(enablePrivateChat) }
|
||||
isFromVisitor = { message.isFromVisitor }
|
||||
isLobbyMessage = { message.lobbyChat }
|
||||
message = { message.message }
|
||||
participantId = { message.participantId }
|
||||
shouldDisplayChatMessageMenu = { shouldDisplayChatMessageMenu } />}
|
||||
participantId = { message.participantId } />}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className = { cx(
|
||||
'chatmessage',
|
||||
classes.chatMessage,
|
||||
type,
|
||||
className,
|
||||
message.privateMessage && 'privatemessage',
|
||||
message.lobbyChat && !knocking && 'lobbymessage'
|
||||
) }>
|
||||
@@ -379,7 +385,7 @@ const ChatMessage = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{shouldDisplayChatMessageMenu && (
|
||||
{shouldDisplayMenuOnRight && (
|
||||
<div className = { classes.sideBySideContainer }>
|
||||
{!message.privateMessage && !message.lobbyChat && <div>
|
||||
<div className = { classes.optionsButtonContainer }>
|
||||
@@ -391,10 +397,12 @@ const ChatMessage = ({
|
||||
<div>
|
||||
<div className = { classes.optionsButtonContainer }>
|
||||
{isHovered && <MessageMenu
|
||||
displayName = { message.displayName }
|
||||
enablePrivateChat = { Boolean(enablePrivateChat) }
|
||||
isFromVisitor = { message.isFromVisitor }
|
||||
isLobbyMessage = { message.lobbyChat }
|
||||
message = { message.message }
|
||||
participantId = { message.participantId }
|
||||
shouldDisplayChatMessageMenu = { shouldDisplayChatMessageMenu } />}
|
||||
participantId = { message.participantId } />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -414,10 +422,23 @@ function _mapStateToProps(state: IReduxState, { message }: IProps) {
|
||||
const { knocking } = state['features/lobby'];
|
||||
|
||||
const participant = getParticipantById(state, message.participantId);
|
||||
const enablePrivateChat = isPrivateChatEnabled(participant, state);
|
||||
|
||||
// For visitor private messages, participant will be undefined but we should still allow private chat
|
||||
// Create a visitor participant object for visitor messages to pass to isPrivateChatEnabled
|
||||
const participantForCheck = message.isFromVisitor
|
||||
? { id: message.participantId, name: message.displayName, isVisitor: true as const }
|
||||
: participant;
|
||||
|
||||
const enablePrivateChat = (!message.isFromVisitor || message.privateMessage)
|
||||
&& isPrivateChatEnabled(participantForCheck, state);
|
||||
|
||||
// Only the local messages appear on the right side of the chat therefore only for them the menu has to be on the
|
||||
// left side.
|
||||
const shouldDisplayMenuOnRight = message.messageType !== MESSAGE_TYPE_LOCAL;
|
||||
|
||||
return {
|
||||
shouldDisplayChatMessageMenu: enablePrivateChat,
|
||||
shouldDisplayMenuOnRight,
|
||||
enablePrivateChat,
|
||||
knocking,
|
||||
state
|
||||
};
|
||||
|
||||
@@ -71,12 +71,11 @@ const ChatMessageGroup = ({ className = '', messages }: IProps) => {
|
||||
<div className = { `${classes.messageGroup} chat-message-group ${className}` }>
|
||||
{messages.map((message, i) => (
|
||||
<ChatMessage
|
||||
className = { className }
|
||||
key = { i }
|
||||
message = { message }
|
||||
shouldDisplayChatMessageMenu = { false }
|
||||
showDisplayName = { i === 0 }
|
||||
showTimestamp = { i === messages.length - 1 }
|
||||
type = { className } />
|
||||
showTimestamp = { i === messages.length - 1 } />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconSubtitles } from '../../../base/icons/svg';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import { groupMessagesBySender } from '../../../base/util/messageGrouping';
|
||||
import { setRequestingSubtitles } from '../../../subtitles/actions.any';
|
||||
@@ -67,7 +66,7 @@ const useStyles = makeStyles()(theme => {
|
||||
}
|
||||
},
|
||||
emptyState: {
|
||||
...withPixelLineHeight(theme.typography.bodyLongBold),
|
||||
...theme.typography.bodyLongBold,
|
||||
color: theme.palette.text02
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,10 +15,12 @@ import { handleLobbyChatInitialized, openChat } from '../../actions.web';
|
||||
|
||||
export interface IProps {
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
enablePrivateChat: boolean;
|
||||
isFromVisitor?: boolean;
|
||||
isLobbyMessage: boolean;
|
||||
message: string;
|
||||
participantId: string;
|
||||
shouldDisplayChatMessageMenu: boolean;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
@@ -46,7 +48,7 @@ const useStyles = makeStyles()(theme => {
|
||||
color: 'white',
|
||||
padding: '4px 8px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
fontSize: '0.75rem',
|
||||
zIndex: 1000,
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
@@ -58,7 +60,7 @@ const useStyles = makeStyles()(theme => {
|
||||
};
|
||||
});
|
||||
|
||||
const MessageMenu = ({ message, participantId, isLobbyMessage, shouldDisplayChatMessageMenu }: IProps) => {
|
||||
const MessageMenu = ({ message, participantId, isFromVisitor, isLobbyMessage, enablePrivateChat, displayName }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const { classes, cx } = useStyles();
|
||||
const { t } = useTranslation();
|
||||
@@ -82,10 +84,23 @@ const MessageMenu = ({ message, participantId, isLobbyMessage, shouldDisplayChat
|
||||
if (isLobbyMessage) {
|
||||
dispatch(handleLobbyChatInitialized(participantId));
|
||||
} else {
|
||||
dispatch(openChat(participant));
|
||||
// For visitor messages, participant will be undefined but we can still open chat
|
||||
// using the participantId which contains the visitor's original JID
|
||||
if (isFromVisitor) {
|
||||
// Handle visitor participant that doesn't exist in main participant list
|
||||
const visitorParticipant = {
|
||||
id: participantId,
|
||||
name: displayName,
|
||||
isVisitor: true
|
||||
};
|
||||
|
||||
dispatch(openChat(visitorParticipant));
|
||||
} else {
|
||||
dispatch(openChat(participant));
|
||||
}
|
||||
}
|
||||
handleClose();
|
||||
}, [ dispatch, isLobbyMessage, participant, participantId ]);
|
||||
}, [ dispatch, isLobbyMessage, participant, participantId, displayName ]);
|
||||
|
||||
const handleCopyClick = useCallback(() => {
|
||||
copyText(message)
|
||||
@@ -115,7 +130,7 @@ const MessageMenu = ({ message, participantId, isLobbyMessage, shouldDisplayChat
|
||||
|
||||
const popoverContent = (
|
||||
<div className = { classes.menuPanel }>
|
||||
{shouldDisplayChatMessageMenu && (
|
||||
{enablePrivateChat && (
|
||||
<div
|
||||
className = { classes.menuItem }
|
||||
onClick = { handlePrivateClick }>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IconCloseLarge } from '../../../base/icons/svg';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.any';
|
||||
import {
|
||||
@@ -24,7 +23,7 @@ const useStyles = makeStyles()(theme => {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.palette.support05,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01
|
||||
},
|
||||
|
||||
@@ -48,6 +47,7 @@ const useStyles = makeStyles()(theme => {
|
||||
const MessageRecipient = ({
|
||||
_privateMessageRecipient,
|
||||
_isLobbyChatActive,
|
||||
_isVisitor,
|
||||
_lobbyMessageRecipient,
|
||||
_onRemovePrivateMessageRecipient,
|
||||
_onHideLobbyChatRecipient,
|
||||
@@ -80,9 +80,9 @@ const MessageRecipient = ({
|
||||
id = 'chat-recipient'
|
||||
role = 'alert'>
|
||||
<span className = { classes.text }>
|
||||
{t(_isLobbyChatActive ? 'chat.lobbyChatMessageTo' : 'chat.messageTo', {
|
||||
recipient: _isLobbyChatActive ? _lobbyMessageRecipient : _privateMessageRecipient
|
||||
})}
|
||||
{ _isLobbyChatActive
|
||||
? t('chat.lobbyChatMessageTo', { recipient: _lobbyMessageRecipient })
|
||||
: t('chat.messageTo', { recipient: `${_privateMessageRecipient}${_isVisitor ? ` ${t('visitors.chatIndicator')}` : ''}` }) }
|
||||
</span>
|
||||
<Button
|
||||
accessibilityLabel = { t('dialog.close') }
|
||||
|
||||
@@ -5,7 +5,6 @@ import { makeStyles } from 'tss-react/mui';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconArrowDown } from '../../../base/icons/svg';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.web';
|
||||
|
||||
export interface INewMessagesButtonProps extends WithTranslation {
|
||||
@@ -53,7 +52,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
textContainer: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text04,
|
||||
paddingLeft: '8px'
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { getParticipantDisplayName } from '../../../base/participants/functions';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import { ISubtitle } from '../../../subtitles/types';
|
||||
|
||||
/**
|
||||
@@ -39,7 +38,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
messageHeader: {
|
||||
...withPixelLineHeight(theme.typography.labelBold),
|
||||
...theme.typography.labelBold,
|
||||
color: theme.palette.text02,
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
@@ -49,14 +48,14 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
messageText: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
...theme.typography.bodyShortRegular,
|
||||
color: theme.palette.text01,
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word'
|
||||
},
|
||||
|
||||
timestamp: {
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text03,
|
||||
marginTop: theme.spacing(1)
|
||||
},
|
||||
|
||||
@@ -55,3 +55,5 @@ export const TIMESTAMP_FORMAT = 'H:mm';
|
||||
* The namespace for system messages.
|
||||
*/
|
||||
export const MESSAGE_TYPE_SYSTEM = 'system_chat_message';
|
||||
|
||||
export const OPTION_GROUPCHAT = 'groupchat';
|
||||
|
||||
@@ -9,10 +9,12 @@ import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
|
||||
import i18next from '../base/i18n/i18next';
|
||||
import { MEET_FEATURES } from '../base/jwt/constants';
|
||||
import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { getParticipantById } from '../base/participants/functions';
|
||||
import { getParticipantById, isPrivateChatEnabled } from '../base/participants/functions';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { escapeRegexp } from '../base/util/helpers';
|
||||
import { getParticipantsPaneWidth } from '../participants-pane/functions';
|
||||
import { VIDEO_SPACE_MIN_SIZE } from '../video-layout/constants';
|
||||
import { IVisitorChatParticipant } from '../visitors/types';
|
||||
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
|
||||
import { IMessage } from './types';
|
||||
@@ -178,9 +180,24 @@ export function getCanReplyToMessage(state: IReduxState, message: IMessage) {
|
||||
const { knocking } = state['features/lobby'];
|
||||
const participant = getParticipantById(state, message.participantId);
|
||||
|
||||
return Boolean(participant)
|
||||
// Check if basic reply conditions are met
|
||||
const basicCanReply = (Boolean(participant) || message.isFromVisitor)
|
||||
&& (message.privateMessage || (message.lobbyChat && !knocking))
|
||||
&& message.messageType !== MESSAGE_TYPE_LOCAL;
|
||||
|
||||
if (!basicCanReply) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check private chat configuration for visitor messages
|
||||
if (message.isFromVisitor) {
|
||||
const visitorParticipant = { id: message.participantId, name: message.displayName, isVisitor: true as const };
|
||||
|
||||
return isPrivateChatEnabled(visitorParticipant, state);
|
||||
}
|
||||
|
||||
// For non-visitor messages, use the regular participant
|
||||
return isPrivateChatEnabled(participant, state);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,8 +207,19 @@ export function getCanReplyToMessage(state: IReduxState, message: IMessage) {
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getPrivateNoticeMessage(message: IMessage) {
|
||||
let recipient;
|
||||
|
||||
if (message.messageType === MESSAGE_TYPE_LOCAL) {
|
||||
// For messages sent by local user, show the recipient name
|
||||
// For visitor messages, use the visitor's display name with indicator
|
||||
recipient = message.sentToVisitor ? `${message.recipient} ${i18next.t('visitors.chatIndicator')}` : message.recipient;
|
||||
} else {
|
||||
// For messages received from others, show "you"
|
||||
recipient = i18next.t('chat.you');
|
||||
}
|
||||
|
||||
return i18next.t('chat.privateNotice', {
|
||||
recipient: message.messageType === MESSAGE_TYPE_LOCAL ? message.recipient : i18next.t('chat.you')
|
||||
recipient
|
||||
});
|
||||
}
|
||||
|
||||
@@ -225,3 +253,15 @@ export function getChatMaxSize(state: IReduxState) {
|
||||
|
||||
return Math.max(clientWidth - getParticipantsPaneWidth(state) - VIDEO_SPACE_MIN_SIZE, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a participant is a visitor chat participant.
|
||||
*
|
||||
* @param {IParticipant | IVisitorChatParticipant | undefined} participant - The participant to check.
|
||||
* @returns {boolean} - True if the participant is a visitor chat participant.
|
||||
*/
|
||||
export function isVisitorChatParticipant(
|
||||
participant?: IParticipant | IVisitorChatParticipant
|
||||
): participant is IVisitorChatParticipant {
|
||||
return Boolean(participant && 'isVisitor' in participant && participant.isVisitor === true);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ import {
|
||||
JitsiConferenceErrors,
|
||||
JitsiConferenceEvents
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, PARTICIPANT_UPDATED } from '../base/participants/actionTypes';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
getParticipantDisplayName
|
||||
} from '../base/participants/functions';
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
|
||||
@@ -33,8 +35,7 @@ import { pushReactions } from '../reactions/actions.any';
|
||||
import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
|
||||
import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any';
|
||||
import { showToolbox } from '../toolbox/actions';
|
||||
|
||||
import './subscriber';
|
||||
import { getVisitorDisplayName } from '../visitors/functions';
|
||||
|
||||
import {
|
||||
ADD_MESSAGE,
|
||||
@@ -44,7 +45,14 @@ import {
|
||||
SEND_REACTION,
|
||||
SET_FOCUSED_TAB
|
||||
} from './actionTypes';
|
||||
import { addMessage, addMessageReaction, clearMessages, closeChat, setPrivateMessageRecipient } from './actions.any';
|
||||
import {
|
||||
addMessage,
|
||||
addMessageReaction,
|
||||
clearMessages,
|
||||
closeChat,
|
||||
notifyPrivateRecipientsChanged,
|
||||
setPrivateMessageRecipient
|
||||
} from './actions.any';
|
||||
import { ChatPrivacyDialog } from './components';
|
||||
import {
|
||||
ChatTabs,
|
||||
@@ -55,8 +63,9 @@ import {
|
||||
MESSAGE_TYPE_REMOTE,
|
||||
MESSAGE_TYPE_SYSTEM
|
||||
} from './constants';
|
||||
import { getUnreadCount, isSendGroupChatDisabled } from './functions';
|
||||
import { getUnreadCount, isSendGroupChatDisabled, isVisitorChatParticipant } from './functions';
|
||||
import { INCOMING_MSG_SOUND_FILE } from './sounds';
|
||||
import './subscriber';
|
||||
|
||||
/**
|
||||
* Timeout for when to show the privacy notice after a private message was received.
|
||||
@@ -186,6 +195,9 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
if (participant) {
|
||||
action.participant = participant;
|
||||
} else if (isVisitorChatParticipant(privateMessageRecipient)) {
|
||||
// Handle visitor participants that don't exist in the main participant list
|
||||
action.participant = privateMessageRecipient;
|
||||
}
|
||||
}
|
||||
} else if (focusedTab === ChatTabs.POLLS) {
|
||||
@@ -195,6 +207,19 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_JOINED:
|
||||
case PARTICIPANT_LEFT:
|
||||
case PARTICIPANT_UPDATED: {
|
||||
if (_shouldNotifyPrivateRecipientsChanged(store, action)) {
|
||||
const result = next(action);
|
||||
|
||||
dispatch(notifyPrivateRecipientsChanged());
|
||||
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SEND_MESSAGE: {
|
||||
const state = store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
@@ -204,14 +229,17 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
// recipient. This logic tries to mitigate this risk.
|
||||
const shouldSendPrivateMessageTo = _shouldSendPrivateMessageTo(state, action);
|
||||
|
||||
const participantExists = shouldSendPrivateMessageTo
|
||||
&& getParticipantById(state, shouldSendPrivateMessageTo);
|
||||
if (shouldSendPrivateMessageTo) {
|
||||
const participantExists = getParticipantById(state, shouldSendPrivateMessageTo.id);
|
||||
|
||||
if (shouldSendPrivateMessageTo && participantExists) {
|
||||
dispatch(openDialog(ChatPrivacyDialog, {
|
||||
message: action.message,
|
||||
participantID: shouldSendPrivateMessageTo
|
||||
}));
|
||||
if (participantExists || shouldSendPrivateMessageTo.isFromVisitor) {
|
||||
dispatch(openDialog(ChatPrivacyDialog, {
|
||||
message: action.message,
|
||||
participantID: shouldSendPrivateMessageTo.id,
|
||||
isFromVisitor: shouldSendPrivateMessageTo.isFromVisitor,
|
||||
displayName: shouldSendPrivateMessageTo.name
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// Sending the message if privacy notice doesn't need to be shown.
|
||||
|
||||
@@ -227,10 +255,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
type: LOBBY_CHAT_MESSAGE,
|
||||
message: action.message
|
||||
}, lobbyMessageRecipient.id);
|
||||
_persistSentPrivateMessage(store, lobbyMessageRecipient.id, action.message, true);
|
||||
_persistSentPrivateMessage(store, lobbyMessageRecipient, action.message, true);
|
||||
} else if (privateMessageRecipient) {
|
||||
conference.sendPrivateTextMessage(privateMessageRecipient.id, action.message);
|
||||
_persistSentPrivateMessage(store, privateMessageRecipient.id, action.message);
|
||||
conference.sendPrivateTextMessage(privateMessageRecipient.id, action.message, 'body', isVisitorChatParticipant(privateMessageRecipient));
|
||||
_persistSentPrivateMessage(store, privateMessageRecipient, action.message);
|
||||
} else {
|
||||
conference.sendTextMessage(action.message);
|
||||
}
|
||||
@@ -297,6 +325,27 @@ StateListenerRegistry.register(
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks whether a notification for private chat recipients is needed.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @param {{ participant: IParticipant, type: string }} action - The action.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function _shouldNotifyPrivateRecipientsChanged(
|
||||
store: IStore, action: { participant: IParticipant; type: string; }
|
||||
) {
|
||||
const { type, participant } = action;
|
||||
|
||||
if ([ PARTICIPANT_LEFT, PARTICIPANT_JOINED ].includes(type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { id, name } = participant;
|
||||
|
||||
return name !== getParticipantDisplayName(store, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers listener for {@link JitsiConferenceEvents.MESSAGE_RECEIVED} that
|
||||
* will perform various chat related activities.
|
||||
@@ -317,7 +366,7 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
|
||||
JitsiConferenceEvents.MESSAGE_RECEIVED,
|
||||
/* eslint-disable max-params */
|
||||
(participantId: string, message: string, timestamp: number,
|
||||
displayName: string, isGuest: boolean, messageId: string) => {
|
||||
displayName: string, isFromVisitor: boolean, messageId: string) => {
|
||||
/* eslint-enable max-params */
|
||||
_onConferenceMessageReceived(store, {
|
||||
// in case of messages coming from visitors we can have unknown id
|
||||
@@ -325,7 +374,7 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
|
||||
message,
|
||||
timestamp,
|
||||
displayName,
|
||||
isGuest,
|
||||
isFromVisitor,
|
||||
messageId,
|
||||
privateMessage: false });
|
||||
|
||||
@@ -350,13 +399,15 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.PRIVATE_MESSAGE_RECEIVED,
|
||||
(participantId: string, message: string, timestamp: number, messageId: string) => {
|
||||
(participantId: string, message: string, timestamp: number, messageId: string, displayName?: string, isFromVisitor?: boolean) => {
|
||||
_onConferenceMessageReceived(store, {
|
||||
participantId,
|
||||
message,
|
||||
timestamp,
|
||||
displayName,
|
||||
messageId,
|
||||
privateMessage: true
|
||||
privateMessage: true,
|
||||
isFromVisitor
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -375,8 +426,8 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConferenceMessageReceived(store: IStore,
|
||||
{ displayName, isGuest, message, messageId, participantId, privateMessage, timestamp }: {
|
||||
displayName?: string; isGuest?: boolean; message: string; messageId?: string;
|
||||
{ displayName, isFromVisitor, message, messageId, participantId, privateMessage, timestamp }: {
|
||||
displayName?: string; isFromVisitor?: boolean; message: string; messageId?: string;
|
||||
participantId: string; privateMessage: boolean; timestamp: number; }
|
||||
) {
|
||||
|
||||
@@ -390,7 +441,7 @@ function _onConferenceMessageReceived(store: IStore,
|
||||
}
|
||||
_handleReceivedMessage(store, {
|
||||
displayName,
|
||||
isGuest,
|
||||
isFromVisitor,
|
||||
participantId,
|
||||
message,
|
||||
privateMessage,
|
||||
@@ -505,8 +556,8 @@ function getLobbyChatDisplayName(state: IReduxState, participantId: string) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
{ displayName, isGuest, lobbyChat, message, messageId, participantId, privateMessage, timestamp }: {
|
||||
displayName?: string; isGuest?: boolean; lobbyChat: boolean; message: string;
|
||||
{ displayName, isFromVisitor, lobbyChat, message, messageId, participantId, privateMessage, timestamp }: {
|
||||
displayName?: string; isFromVisitor?: boolean; lobbyChat: boolean; message: string;
|
||||
messageId?: string; participantId: string; privateMessage: boolean; timestamp: number; },
|
||||
shouldPlaySound = true,
|
||||
isReaction = false
|
||||
@@ -525,9 +576,17 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
const participant = getParticipantById(state, participantId) || { local: undefined };
|
||||
|
||||
const localParticipant = getLocalParticipant(getState);
|
||||
let displayNameToShow = lobbyChat
|
||||
? getLobbyChatDisplayName(state, participantId)
|
||||
: displayName || getParticipantDisplayName(state, participantId);
|
||||
let _displayName, displayNameToShow;
|
||||
|
||||
if (lobbyChat) {
|
||||
displayNameToShow = _displayName = getLobbyChatDisplayName(state, participantId);
|
||||
} else if (isFromVisitor) {
|
||||
_displayName = getVisitorDisplayName(state, displayName);
|
||||
displayNameToShow = `${_displayName} ${i18next.t('visitors.chatIndicator')}`;
|
||||
} else {
|
||||
displayNameToShow = _displayName = getParticipantDisplayName(state, participantId);
|
||||
}
|
||||
|
||||
const hasRead = participant.local || isChatOpen;
|
||||
const timestampToDate = timestamp ? new Date(timestamp) : new Date();
|
||||
const millisecondsTimestamp = timestampToDate.getTime();
|
||||
@@ -536,12 +595,8 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
const shouldShowNotification = userSelectedNotifications?.['notify.chatMessages']
|
||||
&& !hasRead && !isReaction && (!timestamp || lobbyChat);
|
||||
|
||||
if (isGuest) {
|
||||
displayNameToShow = `${displayNameToShow} ${i18next.t('visitors.chatIndicator')}`;
|
||||
}
|
||||
|
||||
dispatch(addMessage({
|
||||
displayName: displayNameToShow,
|
||||
displayName: _displayName,
|
||||
hasRead,
|
||||
participantId,
|
||||
messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
|
||||
@@ -551,7 +606,8 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
recipient: getParticipantDisplayName(state, localParticipant?.id ?? ''),
|
||||
timestamp: millisecondsTimestamp,
|
||||
messageId,
|
||||
isReaction
|
||||
isReaction,
|
||||
isFromVisitor
|
||||
}));
|
||||
|
||||
if (shouldShowNotification) {
|
||||
@@ -574,6 +630,15 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for recipient objects used in private messaging.
|
||||
*/
|
||||
interface IRecipient {
|
||||
id: string;
|
||||
isVisitor?: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the sent private messages as if they were received over the muc.
|
||||
*
|
||||
@@ -582,12 +647,12 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
* But those messages should be in the store as well, otherwise they don't appear in the chat window.
|
||||
*
|
||||
* @param {Store} store - The Redux store.
|
||||
* @param {string} recipientID - The ID of the recipient the private message was sent to.
|
||||
* @param {IRecipient} recipient - The recipient the private message was sent to.
|
||||
* @param {string} message - The sent message.
|
||||
* @param {boolean} isLobbyPrivateMessage - Is a lobby message.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _persistSentPrivateMessage({ dispatch, getState }: IStore, recipientID: string,
|
||||
function _persistSentPrivateMessage({ dispatch, getState }: IStore, recipient: IRecipient,
|
||||
message: string, isLobbyPrivateMessage = false) {
|
||||
const state = getState();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
@@ -598,6 +663,13 @@ function _persistSentPrivateMessage({ dispatch, getState }: IStore, recipientID:
|
||||
const displayName = getParticipantDisplayName(state, localParticipant.id);
|
||||
const { lobbyMessageRecipient } = state['features/chat'];
|
||||
|
||||
const recipientName
|
||||
= recipient.isVisitor
|
||||
? getVisitorDisplayName(state, recipient.name)
|
||||
: (isLobbyPrivateMessage
|
||||
? lobbyMessageRecipient?.name
|
||||
: getParticipantDisplayName(getState, recipient?.id));
|
||||
|
||||
dispatch(addMessage({
|
||||
displayName,
|
||||
hasRead: true,
|
||||
@@ -606,20 +678,19 @@ function _persistSentPrivateMessage({ dispatch, getState }: IStore, recipientID:
|
||||
message,
|
||||
privateMessage: !isLobbyPrivateMessage,
|
||||
lobbyChat: isLobbyPrivateMessage,
|
||||
recipient: isLobbyPrivateMessage
|
||||
? lobbyMessageRecipient?.name
|
||||
: getParticipantDisplayName(getState, recipientID),
|
||||
recipient: recipientName,
|
||||
sentToVisitor: recipient.isVisitor,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the participant who we may have wanted to send the message
|
||||
* Returns the participant info for who we may have wanted to send the message
|
||||
* that we're about to send.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} action - The action being dispatched now.
|
||||
* @returns {string?}
|
||||
* @returns {IRecipient?} - The recipient info or undefined if no notice should be shown.
|
||||
*/
|
||||
function _shouldSendPrivateMessageTo(state: IReduxState, action: AnyAction) {
|
||||
if (action.ignorePrivacy) {
|
||||
@@ -651,7 +722,11 @@ function _shouldSendPrivateMessageTo(state: IReduxState, action: AnyAction) {
|
||||
|
||||
if (lastMessage.privateMessage) {
|
||||
// We show the notice if the last received message was private.
|
||||
return lastMessage.participantId;
|
||||
return {
|
||||
id: lastMessage.participantId,
|
||||
isFromVisitor: Boolean(lastMessage.isFromVisitor),
|
||||
name: lastMessage.displayName
|
||||
};
|
||||
}
|
||||
|
||||
// But messages may come rapidly, we want to protect our users from mis-sending a message
|
||||
@@ -666,7 +741,11 @@ function _shouldSendPrivateMessageTo(state: IReduxState, action: AnyAction) {
|
||||
? recentPrivateMessages[0] : recentPrivateMessages[recentPrivateMessages.length - 1];
|
||||
|
||||
if (recentPrivateMessage) {
|
||||
return recentPrivateMessage.participantId;
|
||||
return {
|
||||
id: recentPrivateMessage.participantId,
|
||||
isFromVisitor: Boolean(recentPrivateMessage.isFromVisitor),
|
||||
name: recentPrivateMessage.displayName
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { UPDATE_CONFERENCE_METADATA } from '../base/conference/actionTypes';
|
||||
import { ILocalParticipant, IParticipant } from '../base/participants/types';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
import { IVisitorChatParticipant } from '../visitors/types';
|
||||
|
||||
import {
|
||||
ADD_MESSAGE,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
CLEAR_MESSAGES,
|
||||
CLOSE_CHAT,
|
||||
EDIT_MESSAGE,
|
||||
NOTIFY_PRIVATE_RECIPIENTS_CHANGED,
|
||||
OPEN_CHAT,
|
||||
REMOVE_LOBBY_CHAT_PARTICIPANT,
|
||||
SET_CHAT_IS_RESIZING,
|
||||
@@ -25,6 +27,7 @@ const DEFAULT_STATE = {
|
||||
groupChatWithPermissions: false,
|
||||
isOpen: false,
|
||||
messages: [],
|
||||
notifyPrivateRecipientsChangedTimestamp: undefined,
|
||||
reactions: {},
|
||||
nbUnreadMessages: 0,
|
||||
privateMessageRecipient: undefined,
|
||||
@@ -51,7 +54,8 @@ export interface IChatState {
|
||||
} | ILocalParticipant;
|
||||
messages: IMessage[];
|
||||
nbUnreadMessages: number;
|
||||
privateMessageRecipient?: IParticipant;
|
||||
notifyPrivateRecipientsChangedTimestamp?: number;
|
||||
privateMessageRecipient?: IParticipant | IVisitorChatParticipant;
|
||||
width: {
|
||||
current: number;
|
||||
userSet: number | null;
|
||||
@@ -64,6 +68,7 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
|
||||
const newMessage: IMessage = {
|
||||
displayName: action.displayName,
|
||||
error: action.error,
|
||||
isFromVisitor: Boolean(action.isFromVisitor),
|
||||
participantId: action.participantId,
|
||||
isReaction: action.isReaction,
|
||||
messageId: action.messageId,
|
||||
@@ -73,6 +78,7 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
|
||||
privateMessage: action.privateMessage,
|
||||
lobbyChat: action.lobbyChat,
|
||||
recipient: action.recipient,
|
||||
sentToVisitor: Boolean(action.sentToVisitor),
|
||||
timestamp: action.timestamp
|
||||
};
|
||||
|
||||
@@ -259,6 +265,11 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
|
||||
isResizing: action.resizing
|
||||
};
|
||||
}
|
||||
case NOTIFY_PRIVATE_RECIPIENTS_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
notifyPrivateRecipientsChangedTimestamp: action.payload
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { IStore } from '../app/types';
|
||||
export interface IMessage {
|
||||
displayName: string;
|
||||
error?: Object;
|
||||
isFromVisitor?: boolean;
|
||||
isReaction: boolean;
|
||||
lobbyChat: boolean;
|
||||
message: string;
|
||||
@@ -14,6 +15,7 @@ export interface IMessage {
|
||||
privateMessage: boolean;
|
||||
reactions: Map<string, Set<string>>;
|
||||
recipient: string;
|
||||
sentToVisitor?: boolean;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
@@ -60,11 +62,6 @@ export interface IChatMessageProps extends WithTranslation {
|
||||
*/
|
||||
message: IMessage;
|
||||
|
||||
/**
|
||||
* Whether the chat message menu is visible or not.
|
||||
*/
|
||||
shouldDisplayChatMessageMenu?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the avatar image of the participant which sent the message
|
||||
* should be displayed.
|
||||
|
||||
@@ -579,7 +579,7 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { startCarMode } = state['features/base/settings'];
|
||||
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
|
||||
const brandingStyles = backgroundColor ? {
|
||||
backgroundColor
|
||||
background: backgroundColor
|
||||
} : undefined;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import { IDisplayProps } from '../ConferenceTimer';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
timer: {
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text01,
|
||||
padding: '6px 8px',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
|
||||
@@ -11,7 +11,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
|
||||
text: {
|
||||
fontSize: '20px'
|
||||
fontSize: '1.25rem'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,13 +4,12 @@ import { useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { getConferenceName } from '../../../base/conference/functions';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import Tooltip from '../../../base/tooltip/components/Tooltip';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
...withPixelLineHeight(theme.typography.bodyLongRegular),
|
||||
...theme.typography.bodyLongRegular,
|
||||
color: theme.palette.text01,
|
||||
padding: '2px 16px',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
|
||||
@@ -8,7 +8,6 @@ import { MIN_ASSUMED_BANDWIDTH_BPS } from '../../../../../modules/API/constants'
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { setAssumedBandwidthBps as saveAssumedBandwidthBps } from '../../../base/conference/actions';
|
||||
import { IconInfoCircle } from '../../../base/icons/svg';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
|
||||
@@ -20,7 +19,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
info: {
|
||||
background: theme.palette.ui01,
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text02,
|
||||
marginTop: theme.spacing(2)
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
getVirtualScreenshareParticipantTrack
|
||||
} from '../../../base/tracks/functions';
|
||||
import { ITrack } from '../../../base/tracks/types';
|
||||
import { pixelsToRem } from '../../../base/ui/functions.any';
|
||||
import {
|
||||
isTrackStreamingStatusInactive,
|
||||
isTrackStreamingStatusInterrupted
|
||||
@@ -355,7 +356,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
|
||||
|
||||
return (
|
||||
<div
|
||||
style = {{ fontSize: iconSize }}>
|
||||
style = {{ fontSize: pixelsToRem(iconSize) }}>
|
||||
<span className = 'sr-only'>{ t('videothumbnail.connectionInfo') }</span>
|
||||
<ConnectionIndicatorIcon
|
||||
classes = { classes }
|
||||
|
||||
@@ -222,7 +222,7 @@ const useStyles = makeStyles()(theme => {
|
||||
},
|
||||
connectionStatsTable: {
|
||||
'&, & > table': {
|
||||
fontSize: '12px',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 400,
|
||||
|
||||
'& td': {
|
||||
|
||||
@@ -12,7 +12,6 @@ import { getLegalUrls } from '../../base/config/functions.any';
|
||||
import { isSupportedBrowser } from '../../base/environment/environment';
|
||||
import { translate, translateToHTML } from '../../base/i18n/functions';
|
||||
import Platform from '../../base/react/Platform.web';
|
||||
import { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||
import Button from '../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../base/ui/constants.any';
|
||||
import {
|
||||
@@ -46,15 +45,15 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
},
|
||||
launchingMeetingLabel: {
|
||||
marginBottom: 16,
|
||||
...withPixelLineHeight(theme.typography.heading4)
|
||||
...theme.typography.heading4
|
||||
},
|
||||
roomName: {
|
||||
marginBottom: 32,
|
||||
...withPixelLineHeight(theme.typography.heading5)
|
||||
...theme.typography.heading5
|
||||
},
|
||||
descriptionLabel: {
|
||||
marginBottom: 32,
|
||||
...withPixelLineHeight(theme.typography.bodyLongRegular)
|
||||
...theme.typography.bodyLongRegular
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: 'flex',
|
||||
@@ -71,7 +70,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
},
|
||||
label: {
|
||||
marginTop: 40,
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
...theme.typography.labelRegular,
|
||||
color: theme.palette.text02,
|
||||
'& a': {
|
||||
color: theme.palette.link01
|
||||
|
||||
@@ -12,7 +12,6 @@ import { IDeeplinkingConfig, IDeeplinkingMobileConfig } from '../../base/config/
|
||||
import { isSupportedMobileBrowser } from '../../base/environment/environment';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import Platform from '../../base/react/Platform.web';
|
||||
import { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||
import Button from '../../base/ui/components/web/Button';
|
||||
import DialInSummary from '../../invite/components/dial-in-summary/web/DialInSummary';
|
||||
import { openWebApp } from '../actions';
|
||||
@@ -51,10 +50,10 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
marginTop: 24,
|
||||
textAlign: 'center',
|
||||
marginBottom: 32,
|
||||
...withPixelLineHeight(theme.typography.heading5)
|
||||
...theme.typography.heading5
|
||||
},
|
||||
roomNameLabel: {
|
||||
...withPixelLineHeight(theme.typography.bodyLongRegularLarge)
|
||||
...theme.typography.bodyLongRegularLarge
|
||||
},
|
||||
joinMeetWrapper: {
|
||||
marginTop: 24,
|
||||
@@ -63,7 +62,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
labelDescription: {
|
||||
textAlign: 'center',
|
||||
marginTop: 16,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
...theme.typography.bodyShortRegularLarge
|
||||
},
|
||||
linkWrapper: {
|
||||
display: 'flex',
|
||||
@@ -74,7 +73,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
},
|
||||
linkLabel: {
|
||||
color: theme.palette.link01,
|
||||
...withPixelLineHeight(theme.typography.bodyLongBoldLarge)
|
||||
...theme.typography.bodyLongBoldLarge
|
||||
},
|
||||
supportedBrowserContent: {
|
||||
marginTop: 16,
|
||||
@@ -84,7 +83,7 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
labelOr: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
...theme.typography.bodyShortRegularLarge
|
||||
},
|
||||
separator: {
|
||||
marginTop: '32px',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user