mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-26 19:37:47 +00:00
Compare commits
43 Commits
3057
...
emcho-patc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
917ef449ec | ||
|
|
3fdf944763 | ||
|
|
56100d0d5c | ||
|
|
486e8e35d9 | ||
|
|
554974a36d | ||
|
|
cd943319d6 | ||
|
|
837f496e8f | ||
|
|
c43f7c8979 | ||
|
|
c9c9f7eac0 | ||
|
|
5ccc397e47 | ||
|
|
00cd82d976 | ||
|
|
61deb74444 | ||
|
|
b30008e3a5 | ||
|
|
62b6737a3f | ||
|
|
cd77a9176c | ||
|
|
2a61968566 | ||
|
|
be4813e10d | ||
|
|
ae890dc093 | ||
|
|
9407f562f6 | ||
|
|
011a46ce2d | ||
|
|
8e0bd36ece | ||
|
|
6f8743af3a | ||
|
|
58d220d645 | ||
|
|
d3c5756f7a | ||
|
|
5ff1ce5a60 | ||
|
|
843f08f38e | ||
|
|
418575136f | ||
|
|
8c97ce2ee9 | ||
|
|
b2245729cc | ||
|
|
cc2b5a261b | ||
|
|
13c4ec884b | ||
|
|
7162080d00 | ||
|
|
b71adbdf70 | ||
|
|
2ae2f04f0a | ||
|
|
4424c456a9 | ||
|
|
cfa1e2f90d | ||
|
|
d290d28248 | ||
|
|
0474031a78 | ||
|
|
a57a5ca49d | ||
|
|
d8c1f107da | ||
|
|
9d27c36d80 | ||
|
|
057b300074 | ||
|
|
e164a23cf0 |
18
.flowconfig
18
.flowconfig
@@ -38,7 +38,23 @@ node_modules/react-native/flow-github/
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
module.system=haste
|
||||
module.system.haste.use_name_reducers=true
|
||||
# get basename
|
||||
module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
|
||||
# strip .js or .js.flow suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
|
||||
# strip .ios suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
|
||||
module.system.haste.paths.blacklist=.*/__tests__/.*
|
||||
module.system.haste.paths.blacklist=.*/__mocks__/.*
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
@@ -67,4 +83,4 @@ module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
|
||||
[version]
|
||||
^0.67.0
|
||||
^0.78.0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
osx_image: xcode9.4
|
||||
osx_image: xcode10
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
|
||||
@@ -130,7 +130,7 @@ equivalent to these of a direct one-to-one WebRTC call. This is what's unique to
|
||||
Jitsi Meet in terms of security.
|
||||
|
||||
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
|
||||
at [Atlassian](https://atlassian.com).
|
||||
at [8x8](https://8x8.com).
|
||||
|
||||
## Mobile app
|
||||
Jitsi Meet is also available as a React Native app for Android and iOS.
|
||||
|
||||
@@ -126,9 +126,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// The e2e rtt are not useful in GA, and there are too many of them.
|
||||
// We just filter them out for now.
|
||||
if (event.action === 'e2e_rtt') {
|
||||
const ignoredEvents
|
||||
= [ 'e2e_rtt', 'rtp.stats', 'rtt.by.region', 'available.device',
|
||||
'stream.switch.delay', 'ice.state.changed', 'ice.duration' ];
|
||||
|
||||
// Temporary removing some of the events that are too noisy.
|
||||
if (ignoredEvents.indexOf(event.action) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'org.jitsi.meet'
|
||||
@@ -44,9 +45,10 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:22.2.0'
|
||||
compile 'com.google.android.gms:play-services-auth:15.0.0'
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation 'com.google.android.gms:play-services-auth:15.0.0'
|
||||
|
||||
implementation project(':sdk')
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
classpath 'com.google.gms:google-services:3.2.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
@@ -153,10 +153,11 @@ allprojects {
|
||||
}
|
||||
|
||||
ext {
|
||||
buildToolsVersion = "26.0.2"
|
||||
compileSdkVersion = 26
|
||||
buildToolsVersion = "27.0.3"
|
||||
compileSdkVersion = 27
|
||||
minSdkVersion = 21
|
||||
targetSdkVersion = 26
|
||||
supportLibVersion = "27.1.1"
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
@@ -179,3 +180,8 @@ subprojects { subproject ->
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.4'
|
||||
distributionUrl = distributionUrl.replace("bin", "all")
|
||||
}
|
||||
|
||||
@@ -17,5 +17,4 @@
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
android.useDeprecatedNdk=true
|
||||
version=1
|
||||
version=1
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
|
||||
110
android/gradlew
vendored
110
android/gradlew
vendored
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +6,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
@@ -90,7 +89,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -114,6 +113,7 @@ fi
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
14
android/gradlew.bat
vendored
14
android/gradlew.bat
vendored
@@ -8,14 +8,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +46,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
@@ -60,11 +59,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -19,136 +19,144 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
compile 'com.android.support:appcompat-v7:27.0.2'
|
||||
compile 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
compile 'com.facebook.react:react-native:+'
|
||||
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
|
||||
compile project(':react-native-background-timer')
|
||||
compile project(':react-native-fast-image')
|
||||
compile(project(":react-native-google-signin")) {
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
api 'com.facebook.react:react-native:+'
|
||||
|
||||
implementation project(':react-native-background-timer')
|
||||
implementation project(':react-native-calendar-events')
|
||||
implementation(project(':react-native-fast-image')) {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
implementation(project(":react-native-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
compile project(':react-native-immersive')
|
||||
compile project(':react-native-keep-awake')
|
||||
compile project(':react-native-linear-gradient')
|
||||
compile project(':react-native-locale-detector')
|
||||
compile project(':react-native-sound')
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-webrtc')
|
||||
compile project(':react-native-calendar-events')
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-linear-gradient')
|
||||
implementation project(':react-native-locale-detector')
|
||||
implementation project(':react-native-sound')
|
||||
implementation project(':react-native-vector-icons')
|
||||
implementation project(':react-native-webrtc')
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
// Build process helpers
|
||||
//
|
||||
|
||||
void runBefore(String dependentTaskName, Task task) {
|
||||
Task dependentTask = tasks.findByPath(dependentTaskName);
|
||||
if (dependentTask != null) {
|
||||
dependentTask.dependsOn task
|
||||
// Here we bundle all assets, resources and React files. We cannot use the
|
||||
// react.gradle file provided by react-native because it's designed to be used
|
||||
// in an application (it taps into applicationVariants, but the SDK is a library
|
||||
// so we need libraryVariants instead).
|
||||
android.libraryVariants.all { def variant ->
|
||||
// Create variant and target names
|
||||
def targetName = variant.name.capitalize()
|
||||
def targetPath = variant.dirName
|
||||
|
||||
// React js bundle directories
|
||||
def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
|
||||
def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
|
||||
|
||||
def jsBundleFile = file("$jsBundleDir/index.android.bundle")
|
||||
|
||||
def currentBundleTask = tasks.create(
|
||||
name: "bundle${targetName}JsAndAssets",
|
||||
type: Exec) {
|
||||
group = "react"
|
||||
description = "bundle JS and assets for ${targetName}."
|
||||
|
||||
// Create dirs if they are not there (e.g. the "clean" task just ran)
|
||||
doFirst {
|
||||
jsBundleDir.deleteDir()
|
||||
jsBundleDir.mkdirs()
|
||||
resourcesDir.deleteDir()
|
||||
resourcesDir.mkdirs()
|
||||
}
|
||||
|
||||
// Set up inputs and outputs so gradle can cache the result
|
||||
def reactRoot = file("${projectDir}/../../")
|
||||
inputs.files fileTree(dir: reactRoot, excludes: ["android/**", "ios/**"])
|
||||
outputs.dir jsBundleDir
|
||||
outputs.dir resourcesDir
|
||||
|
||||
// Set up the call to the react-native cli
|
||||
workingDir reactRoot
|
||||
|
||||
// Set up dev mode
|
||||
def devEnabled = !targetName.toLowerCase().contains("release")
|
||||
|
||||
// Run the bundler
|
||||
commandLine(
|
||||
"node",
|
||||
"node_modules/react-native/local-cli/cli.js",
|
||||
"bundle",
|
||||
"--platform", "android",
|
||||
"--dev", "${devEnabled}",
|
||||
"--reset-cache",
|
||||
"--entry-file", "index.android.js",
|
||||
"--bundle-output", jsBundleFile,
|
||||
"--assets-dest", resourcesDir)
|
||||
|
||||
// Disable bundling on dev builds
|
||||
enabled !devEnabled
|
||||
}
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
android.buildTypes.all { buildType ->
|
||||
def buildNameCapitalized = "${buildType.name.capitalize()}"
|
||||
def bundlePath = "${buildDir}/intermediates/bundles/${buildType.name}"
|
||||
currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
|
||||
currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
|
||||
|
||||
// Bundle fonts in react-native-vector-icons.
|
||||
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
|
||||
variant.mergeResources.dependsOn(currentBundleTask)
|
||||
|
||||
def assetsDir = variant.mergeAssets.outputDir
|
||||
|
||||
variant.mergeAssets.doLast {
|
||||
// Bundle fonts
|
||||
//
|
||||
|
||||
def currentFontTask = tasks.create(
|
||||
name: "copy${buildNameCapitalized}Fonts",
|
||||
type: Copy) {
|
||||
copy {
|
||||
from("${projectDir}/../../fonts/jitsi.ttf")
|
||||
from("${projectDir}/../../node_modules/react-native-vector-icons/Fonts/")
|
||||
into("${bundlePath}/assets/fonts")
|
||||
into("${assetsDir}/fonts")
|
||||
}
|
||||
|
||||
currentFontTask.dependsOn("merge${buildNameCapitalized}Resources")
|
||||
currentFontTask.dependsOn("merge${buildNameCapitalized}Assets")
|
||||
|
||||
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentFontTask)
|
||||
runBefore("processX86${buildNameCapitalized}Resources", currentFontTask)
|
||||
runBefore("processUniversal${buildNameCapitalized}Resources", currentFontTask)
|
||||
runBefore("process${buildNameCapitalized}Resources", currentFontTask)
|
||||
|
||||
def currentSoundsTask = tasks.create(
|
||||
name: "copy${buildNameCapitalized}Sounds",
|
||||
type: Copy) {
|
||||
from("${projectDir}/../../sounds/joined.wav")
|
||||
from("${projectDir}/../../sounds/left.wav")
|
||||
from("${projectDir}/../../sounds/outgoingRinging.wav")
|
||||
from("${projectDir}/../../sounds/outgoingStart.wav")
|
||||
from("${projectDir}/../../sounds/recordingOn.mp3")
|
||||
from("${projectDir}/../../sounds/recordingOff.mp3")
|
||||
from("${projectDir}/../../sounds/rejected.wav")
|
||||
into("${bundlePath}/assets/sounds")
|
||||
}
|
||||
|
||||
currentSoundsTask.dependsOn("merge${buildNameCapitalized}Resources")
|
||||
currentSoundsTask.dependsOn("merge${buildNameCapitalized}Assets")
|
||||
|
||||
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
runBefore("processX86${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
runBefore("processUniversal${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
runBefore("process${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
|
||||
// Bundle JavaScript and React resources.
|
||||
// (adapted from react-native/react.gradle)
|
||||
// Bundle sounds
|
||||
//
|
||||
|
||||
// React JS bundle directories
|
||||
def jsBundleDir = file("${bundlePath}/assets")
|
||||
def resourcesDir = file("${bundlePath}/res/merged")
|
||||
def jsBundleFile = file("${jsBundleDir}/index.android.bundle")
|
||||
|
||||
// Bundle task name for variant.
|
||||
def bundleJsAndAssetsTaskName = "bundle${buildNameCapitalized}JsAndAssets"
|
||||
|
||||
def currentBundleTask = tasks.create(
|
||||
name: bundleJsAndAssetsTaskName,
|
||||
type: Exec) {
|
||||
// Set up inputs and outputs so gradle can cache the result.
|
||||
def reactRoot = file("${projectDir}/../../")
|
||||
inputs.files fileTree(dir: reactRoot, excludes: ['android/**', 'ios/**'])
|
||||
outputs.dir jsBundleDir
|
||||
outputs.dir resourcesDir
|
||||
|
||||
// Set up the call to the react-native cli.
|
||||
workingDir reactRoot
|
||||
|
||||
// Create JS bundle
|
||||
def devEnabled = !buildNameCapitalized.toLowerCase().contains('release')
|
||||
commandLine(
|
||||
'node',
|
||||
'node_modules/react-native/local-cli/cli.js',
|
||||
'bundle',
|
||||
'--assets-dest', resourcesDir,
|
||||
'--bundle-output', jsBundleFile,
|
||||
'--dev', "${devEnabled}",
|
||||
'--entry-file', 'index.android.js',
|
||||
'--platform', 'android',
|
||||
'--reset-cache')
|
||||
|
||||
// Disable bundling on dev builds
|
||||
enabled !devEnabled
|
||||
copy {
|
||||
from("${projectDir}/../../sounds/joined.wav")
|
||||
from("${projectDir}/../../sounds/left.wav")
|
||||
from("${projectDir}/../../sounds/outgoingRinging.wav")
|
||||
from("${projectDir}/../../sounds/outgoingStart.wav")
|
||||
from("${projectDir}/../../sounds/recordingOn.mp3")
|
||||
from("${projectDir}/../../sounds/recordingOff.mp3")
|
||||
from("${projectDir}/../../sounds/rejected.wav")
|
||||
into("${assetsDir}/sounds")
|
||||
}
|
||||
|
||||
// Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
|
||||
currentBundleTask.dependsOn("merge${buildNameCapitalized}Resources")
|
||||
currentBundleTask.dependsOn("merge${buildNameCapitalized}Assets")
|
||||
// Copy React assets
|
||||
//
|
||||
if (currentBundleTask.enabled) {
|
||||
copy {
|
||||
from(jsBundleDir)
|
||||
into(assetsDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentBundleTask)
|
||||
runBefore("processX86${buildNameCapitalized}Resources", currentBundleTask)
|
||||
runBefore("processUniversal${buildNameCapitalized}Resources", currentBundleTask)
|
||||
runBefore("process${buildNameCapitalized}Resources", currentBundleTask)
|
||||
variant.mergeResources.doLast {
|
||||
// Copy React resources
|
||||
//
|
||||
if (currentBundleTask.enabled) {
|
||||
copy {
|
||||
from(resourcesDir)
|
||||
into(variant.mergeResources.outputDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
aarArchive(MavenPublication) {
|
||||
|
||||
10
config.js
10
config.js
@@ -18,9 +18,6 @@ var config = {
|
||||
// XMPP domain.
|
||||
domain: 'jitsi-meet.example.com',
|
||||
|
||||
// XMPP MUC domain. FIXME: use XEP-0030 to discover it.
|
||||
muc: 'conference.jitsi-meet.example.com'
|
||||
|
||||
// When using authentication, domain for guest users.
|
||||
// anonymousdomain: 'guest.example.com',
|
||||
|
||||
@@ -35,6 +32,9 @@ var config = {
|
||||
|
||||
// Focus component domain. Defaults to focus.<domain>.
|
||||
// focus: 'focus.jitsi-meet.example.com',
|
||||
|
||||
// XMPP MUC domain. FIXME: use XEP-0030 to discover it.
|
||||
muc: 'conference.jitsi-meet.example.com'
|
||||
},
|
||||
|
||||
// BOSH URL. FIXME: use XEP-0156 to discover it.
|
||||
@@ -241,10 +241,6 @@ var config = {
|
||||
// Disable hiding of remote thumbnails when in a 1-on-1 conference call.
|
||||
// disable1On1Mode: false,
|
||||
|
||||
// The minimum value a video's height (or width, whichever is smaller) needs
|
||||
// to be in order to be considered high-definition.
|
||||
minHDHeight: 540,
|
||||
|
||||
// Default language for the user interface.
|
||||
// defaultLanguage: 'en',
|
||||
|
||||
|
||||
117
css/_meetings_list.scss
Normal file
117
css/_meetings_list.scss
Normal file
@@ -0,0 +1,117 @@
|
||||
.meetings-list {
|
||||
font-size: 14px;
|
||||
color: #253858;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.meetings-list-empty {
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #0074E0;
|
||||
border-radius: 4px;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: rgba(255,255,255,0.50);
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
margin-top: 5px;
|
||||
min-height: 92px;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
text-align: left;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 140px;
|
||||
flex-grow: 0;
|
||||
padding-left: 30px;
|
||||
padding-top: 25px;
|
||||
|
||||
.date {
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.right-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding-left: 30px;
|
||||
padding-top: 25px;
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 0;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
&.with-click-handler {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.with-click-handler:hover {
|
||||
background-color: #75A7E7;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.join-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover .join-button {
|
||||
display: block
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ $watermarkHeight: 74px;
|
||||
/**
|
||||
* Welcome page variables.
|
||||
*/
|
||||
$welcomePageDescriptionColor: #E6EDFA;
|
||||
$welcomePageDescriptionColor: #fff;
|
||||
$welcomePageFontFamily: inherit;
|
||||
$welcomePageHeaderBackground: #1D69D4;
|
||||
$welcomePageHeaderBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
|
||||
$welcomePageTitleColor: #fff;
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
* wrapper needed before we're able to move all top toolbar indicators
|
||||
* creation to react.
|
||||
*/
|
||||
.ckAJgx {
|
||||
.sc-ifAKCX {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ body.welcome-page {
|
||||
}
|
||||
|
||||
.welcome {
|
||||
background-color: $welcomePageHeaderBackground;
|
||||
background-image: $welcomePageHeaderBackground;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: $welcomePageFontFamily;
|
||||
@@ -24,8 +24,8 @@ body.welcome-page {
|
||||
.header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: $watermarkHeight + 80;
|
||||
margin-bottom: 36px;
|
||||
margin-top: $watermarkHeight + 35;
|
||||
margin-bottom: 35px;
|
||||
max-width: calc(100% - 40px);
|
||||
width: 650px;
|
||||
z-index: $zindex2;
|
||||
@@ -35,7 +35,6 @@ body.welcome-page {
|
||||
color: $welcomePageTitleColor;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.18;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@@ -49,41 +48,118 @@ body.welcome-page {
|
||||
}
|
||||
|
||||
#enter_room {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: calc(100% - 40px);
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
width: 650px;
|
||||
width: 680px;
|
||||
z-index: $zindex2;
|
||||
background-color: #fff;
|
||||
padding: 25px 30px;
|
||||
|
||||
.enter-room-input {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
.enter-room-input-container {
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
padding-bottom: 5px;
|
||||
text-align: left;
|
||||
color: #253858;
|
||||
height: fit-content;
|
||||
border-width: 0px 0px 2px 0px;
|
||||
border-style: solid;
|
||||
border-image: linear-gradient(to right, #dee1e6, #fff) 1;
|
||||
|
||||
.enter-room-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.enter-room-input {
|
||||
border: none;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: #253858;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: 650px;
|
||||
min-height: 354px;
|
||||
width: 710px;
|
||||
background: #75A7E7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tab-content{
|
||||
margin: 5px 0px;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
|
||||
> * {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
font-size: 18px;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-direction: row;
|
||||
min-height: 54px;
|
||||
width: 100%;
|
||||
|
||||
.tab {
|
||||
text-align: center;
|
||||
background: rgba(9,30,66,0.37);
|
||||
height: 55px;
|
||||
line-height: 54px;
|
||||
flex-grow: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected, &:hover {
|
||||
background: rgba(9,30,66,0.71);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-page-button {
|
||||
font-size: 16px;
|
||||
width: 51px;
|
||||
height: 35px;
|
||||
font-size: 14px;
|
||||
background: #0074E0;
|
||||
border-radius: 4px;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 35px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.welcome-page-settings {
|
||||
color: $welcomePageDescriptionColor;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 32px;
|
||||
right: 32px;
|
||||
z-index: $zindex2;
|
||||
|
||||
* {
|
||||
cursor: pointer;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
@import 'modals/invite/add-people';
|
||||
@import 'deep-linking/main';
|
||||
@import 'transcription-subtitles';
|
||||
@import '_meetings_list.scss';
|
||||
@import 'navigate_section_list';
|
||||
@import 'third-party-branding/google';
|
||||
@import 'third-party-branding/microsoft';
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
# Setting up Google Authentication
|
||||
|
||||
- Create a Firebase project here: https://firebase.google.com/. You'll need a
|
||||
signed Android build for that, that can be a debug auto-signed build too, just
|
||||
retrieve the signing hash.
|
||||
signed Android build for that, that can be a debug self-signed build too, just
|
||||
retrieve the signing hash. The key hash of an already signed ap can be obtained
|
||||
as follows (on macOS): ```keytool -list -printcert -jarfile the-app.apk```
|
||||
- Place the generated ```google-services.json``` file in ```android/app```
|
||||
for Android and the ```GoogleService-Info.plist``` into ```ios/app/src``` for
|
||||
iOS (you can stop at that step, no need for the driver and the code changes they
|
||||
suggest in the wizard).
|
||||
- You may want to exclude these files in YOUR GIT config (do not exclude them in
|
||||
the ```.gitignore``` of the application itself!).
|
||||
- Your WEB and iOS client IDs are auto generated during the Firebase project
|
||||
creation. Find them in the Google Developer console:
|
||||
https://console.developers.google.com/
|
||||
- Make sure your config reflects these IDs so then the Redux state of the
|
||||
feature ```features/base/config``` contains variables
|
||||
```googleApiApplicationClientID``` and ```googleApiIOSClientID``` with the
|
||||
respective values.
|
||||
- Add your iOS client ID as an application URL schema into
|
||||
```ios/app/src/Info.plist``` (replacing placeholder).
|
||||
- Enable YouTube API access on the developer console (see above) for live
|
||||
- Your web client ID is auto generated during the Firebase project
|
||||
creation. Find them in the Google Developer console
|
||||
(https://console.developers.google.com/)
|
||||
- Make sure your config reflects this ID by setting
|
||||
```googleApiApplicationClientID``` in config.js.
|
||||
- Add your iOS client ID (the REVERSED_CLIENT_ID in the plist file) as an
|
||||
application URL schema into ```ios/app/src/Info.plist```
|
||||
(replacing placeholder).
|
||||
- Enable YouTube API access on the developer console (see above) to enable live
|
||||
streaming.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 612 B |
Binary file not shown.
|
Before Width: | Height: | Size: 889 B |
@@ -1,13 +1,13 @@
|
||||
PODS:
|
||||
- boost-for-react-native (1.63.0)
|
||||
- DoubleConversion (1.1.5)
|
||||
- DoubleConversion (1.1.6)
|
||||
- FLAnimatedImage (1.0.12)
|
||||
- Folly (2016.09.26.00):
|
||||
- Folly (2016.10.31.00):
|
||||
- boost-for-react-native
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- glog (0.3.4)
|
||||
- GoogleSignIn (4.2.0):
|
||||
- glog (0.3.5)
|
||||
- GoogleSignIn (4.3.0):
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (~> 2.1)"
|
||||
- GTMOAuth2 (~> 1.0)
|
||||
@@ -27,9 +27,9 @@ PODS:
|
||||
- GTMSessionFetcher/Core (1.2.0)
|
||||
- GTMSessionFetcher/Full (1.2.0):
|
||||
- GTMSessionFetcher/Core (= 1.2.0)
|
||||
- ObjectiveDropboxOfficial (3.9.1)
|
||||
- React (0.55.4):
|
||||
- React/Core (= 0.55.4)
|
||||
- ObjectiveDropboxOfficial (3.9.2)
|
||||
- React (0.57.1):
|
||||
- React/Core (= 0.57.1)
|
||||
- react-native-background-timer (2.0.0):
|
||||
- React
|
||||
- react-native-calendar-events (1.6.2):
|
||||
@@ -43,48 +43,48 @@ PODS:
|
||||
- React
|
||||
- react-native-locale-detector (1.0.0):
|
||||
- React
|
||||
- react-native-webrtc (1.63.0):
|
||||
- react-native-webrtc (1.67.1):
|
||||
- React
|
||||
- React/Core (0.55.4):
|
||||
- yoga (= 0.55.4.React)
|
||||
- React/CxxBridge (0.55.4):
|
||||
- Folly (= 2016.09.26.00)
|
||||
- React/Core (0.57.1):
|
||||
- yoga (= 0.57.1.React)
|
||||
- React/CxxBridge (0.57.1):
|
||||
- Folly (= 2016.10.31.00)
|
||||
- React/Core
|
||||
- React/cxxreact
|
||||
- React/cxxreact (0.55.4):
|
||||
- React/cxxreact (0.57.1):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- Folly (= 2016.09.26.00)
|
||||
- Folly (= 2016.10.31.00)
|
||||
- React/jschelpers
|
||||
- React/jsinspector
|
||||
- React/DevSupport (0.55.4):
|
||||
- React/DevSupport (0.57.1):
|
||||
- React/Core
|
||||
- React/RCTWebSocket
|
||||
- React/fishhook (0.55.4)
|
||||
- React/jschelpers (0.55.4):
|
||||
- Folly (= 2016.09.26.00)
|
||||
- React/fishhook (0.57.1)
|
||||
- React/jschelpers (0.57.1):
|
||||
- Folly (= 2016.10.31.00)
|
||||
- React/PrivateDatabase
|
||||
- React/jsinspector (0.55.4)
|
||||
- React/PrivateDatabase (0.55.4)
|
||||
- React/RCTActionSheet (0.55.4):
|
||||
- React/jsinspector (0.57.1)
|
||||
- React/PrivateDatabase (0.57.1)
|
||||
- React/RCTActionSheet (0.57.1):
|
||||
- React/Core
|
||||
- React/RCTAnimation (0.55.4):
|
||||
- React/RCTAnimation (0.57.1):
|
||||
- React/Core
|
||||
- React/RCTBlob (0.55.4):
|
||||
- React/RCTBlob (0.57.1):
|
||||
- React/Core
|
||||
- React/RCTImage (0.55.4):
|
||||
- React/RCTImage (0.57.1):
|
||||
- React/Core
|
||||
- React/RCTNetwork
|
||||
- React/RCTLinkingIOS (0.55.4):
|
||||
- React/RCTLinkingIOS (0.57.1):
|
||||
- React/Core
|
||||
- React/RCTNetwork (0.55.4):
|
||||
- React/RCTNetwork (0.57.1):
|
||||
- React/Core
|
||||
- React/RCTText (0.55.4):
|
||||
- React/RCTText (0.57.1):
|
||||
- React/Core
|
||||
- React/RCTWebSocket (0.55.4):
|
||||
- React/RCTWebSocket (0.57.1):
|
||||
- React/Core
|
||||
- React/fishhook
|
||||
- React/RCTBlob
|
||||
- RNGoogleSignin (1.0.0-rc3):
|
||||
- RNGoogleSignin (1.0.0-rc6):
|
||||
- GoogleSignIn
|
||||
- React
|
||||
- RNSound (0.10.9):
|
||||
@@ -92,13 +92,13 @@ PODS:
|
||||
- RNSound/Core (= 0.10.9)
|
||||
- RNSound/Core (0.10.9):
|
||||
- React/Core
|
||||
- RNVectorIcons (4.4.2):
|
||||
- RNVectorIcons (6.0.2):
|
||||
- React
|
||||
- SDWebImage/Core (4.4.2)
|
||||
- SDWebImage/GIF (4.4.2):
|
||||
- FLAnimatedImage (~> 1.0)
|
||||
- SDWebImage/Core
|
||||
- yoga (0.55.4.React)
|
||||
- yoga (0.57.1.React)
|
||||
|
||||
DEPENDENCIES:
|
||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
@@ -169,16 +169,16 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
DoubleConversion: e22e0762848812a87afd67ffda3998d9ef29170c
|
||||
DoubleConversion: bb338842f62ab1d708ceb63ec3d999f0f3d98ecd
|
||||
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
|
||||
Folly: 211775e49d8da0ca658aebc8eab89d642935755c
|
||||
glog: 1de0bb937dccdc981596d3b5825ebfb765017ded
|
||||
GoogleSignIn: 591e46382014e591269f862ba6e7bc0fbd793532
|
||||
Folly: c89ac2d5c6ab169cd7397ef27485c44f35f742c7
|
||||
glog: e8acf0ebbf99759d3ff18c86c292a5898282dcde
|
||||
GoogleSignIn: 11183592dc63e105475c7305a325045ff95e02b7
|
||||
GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f
|
||||
GTMOAuth2: c77fe325e4acd453837e72d91e3b5f13116857b2
|
||||
GTMSessionFetcher: 0c4baf0a73acd0041bf9f71ea018deedab5ea84e
|
||||
ObjectiveDropboxOfficial: 274ce69d66286c94416daf1da5237c55e105e8c0
|
||||
React: aa2040dbb6f317b95314968021bd2888816e03d5
|
||||
ObjectiveDropboxOfficial: aa792e0556ceb7b72955fa29a2709072f6e35fd9
|
||||
React: 1fe0eb13d90b625d94c3b117c274dcfd2e760e11
|
||||
react-native-background-timer: 63dcbf37dbcf294b5c6c071afcdc661fa06a7594
|
||||
react-native-calendar-events: fe6fbc8ed337a7423c98f2c9012b25f20444de09
|
||||
react-native-fast-image: cba3d9bf9c2cf8ddb643d887a686c53a5dd90a2c
|
||||
@@ -187,9 +187,9 @@ SPEC CHECKSUMS:
|
||||
react-native-webrtc: 31b6d3f1e3e2ce373aa43fd682b04367250f807d
|
||||
RNGoogleSignin: 44debd8c359a662c0e2d585952e88b985bf78008
|
||||
RNSound: b360b3862d3118ed1c74bb9825696b5957686ac4
|
||||
RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
|
||||
RNVectorIcons: 8c52e1e8da1153613fdef44748e865c25556cb9c
|
||||
SDWebImage: 624d6e296c69b244bcede364c72ae0430ac14681
|
||||
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
|
||||
yoga: b1ce48b6cf950b98deae82838f5173ea7cf89e85
|
||||
|
||||
PODFILE CHECKSUM: cf8276ba4b0933b24c6082a25a5f4eabe0ba4ea6
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0920;
|
||||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
TargetAttributes = {
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
@@ -327,12 +327,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -383,12 +385,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0920"
|
||||
LastUpgradeVersion = "1000"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "NO"
|
||||
@@ -40,7 +40,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
@@ -60,7 +59,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
# This script is executed from Xcode to start the React packager for Debug
|
||||
# targets.
|
||||
|
||||
export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}"
|
||||
echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${SRCROOT}/../../node_modules/react-native/scripts/.packager.env"
|
||||
|
||||
if [[ "$CONFIGURATION" = "Debug" ]]; then
|
||||
if nc -w 5 -z localhost 8081 ; then
|
||||
if ! curl -s "http://localhost:8081/status" | grep -q "packager-status:running" ; then
|
||||
echo "Port 8081 already in use, packager is either not running or not running correctly"
|
||||
if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then
|
||||
if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then
|
||||
echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly"
|
||||
exit 2
|
||||
fi
|
||||
else
|
||||
open -g "$SRCROOT/../../node_modules/react-native/scripts/launchPackager.command" || echo "Can't start packager automatically"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
0B49424520AD8DBD00BD2DE0 /* outgoingStart.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */; };
|
||||
0B49424620AD8DBD00BD2DE0 /* outgoingRinging.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */; };
|
||||
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B7C2CFC200F51D60060D076 /* LaunchOptions.m */; };
|
||||
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
|
||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */; };
|
||||
@@ -114,7 +113,6 @@
|
||||
files = (
|
||||
0BB9AD791F5EC6D7001C08DB /* Intents.framework in Frameworks */,
|
||||
0BB9AD771F5EC6CE001C08DB /* CallKit.framework in Frameworks */,
|
||||
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */,
|
||||
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -285,6 +283,7 @@
|
||||
0BD906E31EC0C00300C8C18E /* Resources */,
|
||||
0BCA49651EC4B77500B793EE /* Package React bundle */,
|
||||
C7BC10B338C94EEB98048E64 /* [CP] Copy Pods Resources */,
|
||||
0B64F7BE2175DFEA005009CD /* Remove unneeded fonts */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -301,7 +300,7 @@
|
||||
0BD906DC1EC0C00300C8C18E /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0920;
|
||||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = Jitsi;
|
||||
TargetAttributes = {
|
||||
0BD906E41EC0C00300C8C18E = {
|
||||
@@ -350,6 +349,24 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
0B64F7BE2175DFEA005009CD /* Remove unneeded fonts */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Remove unneeded fonts";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# We need to manually do this because react-native-vecotr-icons lists fonts as resources in the Pod spec file\n# so they are automatically added.\n\nshopt -s extglob\n\nrm -f ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/!(jitsi).ttf\n";
|
||||
};
|
||||
0BCA49651EC4B77500B793EE /* Package React bundle */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -391,10 +408,14 @@
|
||||
"${SRCROOT}/../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh",
|
||||
"${PODS_ROOT}/GTMOAuth2/Source/Touch/GTMOAuth2ViewTouch.xib",
|
||||
"${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
|
||||
@@ -407,10 +428,14 @@
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMOAuth2ViewTouch.nib",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
|
||||
@@ -470,6 +495,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -477,6 +503,7 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -529,6 +556,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -536,6 +564,7 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -561,6 +590,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0920"
|
||||
LastUpgradeVersion = "1000"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,7 +26,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
@@ -37,7 +36,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
<string>1.9.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>JitsiMeetFonts</key>
|
||||
<array>
|
||||
<string>FontAwesome.ttf</string>
|
||||
<string>jitsi.ttf</string>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>dbapi-2</string>
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
#import <Intents/Intents.h>
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
@@ -53,35 +52,6 @@ RCTFatalHandler _RCTFatal = ^(NSError *error) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to dynamically load custom fonts. The `UIAppFonts` key in the
|
||||
* plist file doesn't work for frameworks, so fonts have to be manually loaded.
|
||||
*/
|
||||
void loadCustomFonts(Class clazz) {
|
||||
NSBundle *bundle = [NSBundle bundleForClass:clazz];
|
||||
NSArray *fonts = [bundle objectForInfoDictionaryKey:@"JitsiMeetFonts"];
|
||||
|
||||
for (NSString *item in fonts) {
|
||||
NSString *fontName = [item stringByDeletingPathExtension];
|
||||
NSString *fontExt = [item pathExtension];
|
||||
NSString *fontPath = [bundle pathForResource:fontName ofType:fontExt];
|
||||
NSData *inData = [NSData dataWithContentsOfFile:fontPath];
|
||||
CFErrorRef error;
|
||||
CGDataProviderRef provider
|
||||
= CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
|
||||
CGFontRef font = CGFontCreateWithDataProvider(provider);
|
||||
|
||||
if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
|
||||
CFStringRef errorDescription = CFErrorCopyDescription(error);
|
||||
|
||||
NSLog(@"Failed to load font: %@", errorDescription);
|
||||
CFRelease(errorDescription);
|
||||
}
|
||||
CFRelease(font);
|
||||
CFRelease(provider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to register a fatal error handler for React. Our handler
|
||||
* won't kill the process, it will swallow JS errors and print stack traces
|
||||
@@ -410,9 +380,6 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||
= [[RCTBridgeWrapper alloc] initWithLaunchOptions:_launchOptions];
|
||||
views = [NSMapTable strongToWeakObjectsMapTable];
|
||||
|
||||
// Dynamically load custom bundled fonts.
|
||||
loadCustomFonts(self.class);
|
||||
|
||||
// Register a fatal error handler for React.
|
||||
registerFatalErrorHandler();
|
||||
});
|
||||
|
||||
@@ -59,16 +59,18 @@
|
||||
"calendar": "Calendar",
|
||||
"connectCalendarText": "Connect your calendar to view all your meetings in __app__. Plus, add __app__ meetings to your calendar and start them with one click.",
|
||||
"connectCalendarButton": "Connect your calendar",
|
||||
"enterRoomTitle": "Start a new meeting",
|
||||
"go": "GO",
|
||||
"join": "JOIN",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Recent",
|
||||
"recentListDelete": "Delete",
|
||||
"recentListEmpty": "Your recent list is currently empty. Chat with your team and you will find all your recent meetings here.",
|
||||
"roomname": "Enter room name",
|
||||
"roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",
|
||||
"sendFeedback": "Send feedback",
|
||||
"terms": "Terms",
|
||||
"title": "More secure, more flexible, and completely free video conferencing."
|
||||
"title": "Secure, fully featured, and completely free video conferencing"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
|
||||
@@ -112,7 +112,7 @@ function initCommands() {
|
||||
const { name } = request;
|
||||
|
||||
switch (name) {
|
||||
case 'invite': // eslint-disable-line no-case-declarations
|
||||
case 'invite': {
|
||||
const { invitees } = request;
|
||||
|
||||
if (!Array.isArray(invitees) || invitees.length === 0) {
|
||||
@@ -143,6 +143,7 @@ function initCommands() {
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'is-audio-muted':
|
||||
callback(APP.conference.isLocalAudioMuted());
|
||||
break;
|
||||
|
||||
64
modules/API/external/external_api.js
vendored
64
modules/API/external/external_api.js
vendored
@@ -114,10 +114,10 @@ function parseArguments(args) {
|
||||
|
||||
switch (typeof firstArg) {
|
||||
case 'string': // old arguments format
|
||||
case undefined: // eslint-disable-line no-case-declarations
|
||||
// not sure which format but we are trying to parse the old
|
||||
// format because if the new format is used everything will be undefined
|
||||
// anyway.
|
||||
case undefined: {
|
||||
// Not sure which format but we are trying to parse the old
|
||||
// format because if the new format is used everything will be undefined
|
||||
// anyway.
|
||||
const [
|
||||
roomName,
|
||||
width,
|
||||
@@ -141,6 +141,7 @@ function parseArguments(args) {
|
||||
jwt,
|
||||
onload
|
||||
};
|
||||
}
|
||||
case 'object': // new arguments format
|
||||
return args[0];
|
||||
default:
|
||||
@@ -461,55 +462,57 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* the event and value - the listener.
|
||||
* Currently we support the following
|
||||
* events:
|
||||
* incomingMessage - receives event notifications about incoming
|
||||
* {@code incomingMessage} - receives event notifications about incoming
|
||||
* messages. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* 'from': from,//JID of the user that sent the message
|
||||
* 'nick': nick,//the nickname of the user that sent the message
|
||||
* 'message': txt//the text of the message
|
||||
* }}
|
||||
* outgoingMessage - receives event notifications about outgoing
|
||||
* {@code outgoingMessage} - receives event notifications about outgoing
|
||||
* messages. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* 'message': txt//the text of the message
|
||||
* }}
|
||||
* displayNameChanged - receives event notifications about display name
|
||||
* change. The listener will receive object with the following structure:
|
||||
* {@code displayNameChanged} - receives event notifications about display
|
||||
* name change. The listener will receive object with the following
|
||||
* structure:
|
||||
* {{
|
||||
* jid: jid,//the JID of the participant that changed his display name
|
||||
* displayname: displayName //the new display name
|
||||
* }}
|
||||
* participantJoined - receives event notifications about new participant.
|
||||
* {@code participantJoined} - receives event notifications about new
|
||||
* participant.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid //the jid of the participant
|
||||
* }}
|
||||
* participantLeft - receives event notifications about the participant that
|
||||
* left the room.
|
||||
* {@code participantLeft} - receives event notifications about the
|
||||
* participant that left the room.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid //the jid of the participant
|
||||
* }}
|
||||
* video-conference-joined - receives event notifications about the local
|
||||
* user has successfully joined the video conference.
|
||||
* {@code video-conference-joined} - receives event notifications about the
|
||||
* local user has successfully joined the video conference.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* roomName: room //the room name of the conference
|
||||
* }}
|
||||
* video-conference-left - receives event notifications about the local user
|
||||
* has left the video conference.
|
||||
* {@code video-conference-left} - receives event notifications about the
|
||||
* local user has left the video conference.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* roomName: room //the room name of the conference
|
||||
* }}
|
||||
* screenSharingStatusChanged - receives event notifications about
|
||||
* {@code screenSharingStatusChanged} - receives event notifications about
|
||||
* turning on/off the local user screen sharing.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* on: on //whether screen sharing is on
|
||||
* }}
|
||||
* readyToClose - all hangup operations are completed and Jitsi Meet is
|
||||
* ready to be disposed.
|
||||
* {@code readyToClose} - all hangup operations are completed and Jitsi Meet
|
||||
* is ready to be disposed.
|
||||
* @returns {void}
|
||||
*
|
||||
* @deprecated
|
||||
@@ -536,11 +539,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Executes command. The available commands are:
|
||||
* displayName - sets the display name of the local participant to the value
|
||||
* passed in the arguments array.
|
||||
* toggleAudio - mutes / unmutes audio with no arguments.
|
||||
* toggleVideo - mutes / unmutes video with no arguments.
|
||||
* toggleFilmStrip - hides / shows the filmstrip with no arguments.
|
||||
* {@code displayName} - Sets the display name of the local participant to
|
||||
* the value passed in the arguments array.
|
||||
* {@code toggleAudio} - Mutes / unmutes audio with no arguments.
|
||||
* {@code toggleVideo} - Mutes / unmutes video with no arguments.
|
||||
* {@code toggleFilmStrip} - Hides / shows the filmstrip with no arguments.
|
||||
*
|
||||
* If the command doesn't require any arguments the parameter should be set
|
||||
* to empty array or it may be omitted.
|
||||
*
|
||||
@@ -561,13 +565,13 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Executes commands. The available commands are:
|
||||
* displayName - sets the display name of the local participant to the value
|
||||
* passed in the arguments array.
|
||||
* toggleAudio - mutes / unmutes audio. no arguments
|
||||
* toggleVideo - mutes / unmutes video. no arguments
|
||||
* toggleFilmStrip - hides / shows the filmstrip. no arguments
|
||||
* toggleChat - hides / shows chat. no arguments.
|
||||
* toggleShareScreen - starts / stops screen sharing. no arguments.
|
||||
* {@code displayName} - Sets the display name of the local participant to
|
||||
* the value passed in the arguments array.
|
||||
* {@code toggleAudio} - Mutes / unmutes audio. No arguments.
|
||||
* {@code toggleVideo} - Mutes / unmutes video. No arguments.
|
||||
* {@code toggleFilmStrip} - Hides / shows the filmstrip. No arguments.
|
||||
* {@code toggleChat} - Hides / shows chat. No arguments.
|
||||
* {@code toggleShareScreen} - Starts / stops screen sharing. No arguments.
|
||||
*
|
||||
* @param {Object} commandList - The object with commands to be executed.
|
||||
* The keys of the object are the commands that will be executed and the
|
||||
|
||||
@@ -87,6 +87,7 @@ class Etherpad extends LargeContainer {
|
||||
this.container.appendChild(iframe);
|
||||
|
||||
iframe.onload = function() {
|
||||
// eslint-disable-next-line no-self-assign
|
||||
document.domain = document.domain;
|
||||
bubbleIframeMouseMove(iframe);
|
||||
|
||||
|
||||
4799
package-lock.json
generated
4799
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
@@ -47,61 +47,69 @@
|
||||
"jquery-contextmenu": "2.4.5",
|
||||
"jquery-i18next": "1.2.0",
|
||||
"js-md5": "0.6.1",
|
||||
"js-utils": "github:jitsi/js-utils#446497893023aa8dec403e0e4e35a22cae6bc87d",
|
||||
"jsc-android": "224109.1.0",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#ba0dc564cba5b49aa55cdce0f8aae19e9e475d8e",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#34f728456eb5e8e9817bdaf999e4702bac2ee6ce",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.4",
|
||||
"moment": "2.19.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"postis": "2.2.0",
|
||||
"prop-types": "15.6.0",
|
||||
"react": "16.3.1",
|
||||
"react-dom": "16.3.1",
|
||||
"react-i18next": "4.8.0",
|
||||
"react-native": "0.55.4",
|
||||
"react": "16.5.0",
|
||||
"react-dom": "16.5.0",
|
||||
"react-i18next": "7.13.0",
|
||||
"react-native": "0.57.1",
|
||||
"react-native-background-timer": "2.0.0",
|
||||
"react-native-calendar-events": "github:wmcmahan/react-native-calendar-events#cb2731db6684a49b4343e09de7f9c2fcc68bcd9b",
|
||||
"react-native-callstats": "3.53.4",
|
||||
"react-native-fast-image": "github:jitsi/react-native-fast-image#1f8c93a5584869848d75cc9b946beb9688efe285",
|
||||
"react-native-google-signin": "1.0.0-rc3",
|
||||
"react-native-google-signin": "1.0.0-rc6",
|
||||
"react-native-immersive": "1.1.0",
|
||||
"react-native-keep-awake": "2.0.6",
|
||||
"react-native-linear-gradient": "2.4.0",
|
||||
"react-native-locale-detector": "github:jitsi/react-native-locale-detector#845281e9fd4af756f6d0f64afe5cce08c63e5ee9",
|
||||
"react-native-prompt": "1.0.0",
|
||||
"react-native-sound": "0.10.9",
|
||||
"react-native-vector-icons": "4.4.2",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#be3de15bb988cfabbb62cd4b3b06f4c920ee5ba0",
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-vector-icons": "6.0.2",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#6322a9b5a38ce590cfaea4041072ea87c8dbf558",
|
||||
"react-redux": "5.0.7",
|
||||
"react-transition-group": "2.4.0",
|
||||
"redux": "4.0.0",
|
||||
"redux-thunk": "2.2.0",
|
||||
"styled-components": "1.4.6",
|
||||
"styled-components": "3.4.9",
|
||||
"uuid": "3.1.0",
|
||||
"xmldom": "0.1.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "6.26.0",
|
||||
"babel-eslint": "8.0.3",
|
||||
"babel-loader": "7.1.2",
|
||||
"babel-preset-env": "1.6.1",
|
||||
"babel-preset-react": "6.24.1",
|
||||
"babel-preset-stage-1": "6.24.1",
|
||||
"@babel/core": "7.1.2",
|
||||
"@babel/preset-env": "7.1.0",
|
||||
"@babel/preset-flow": "7.0.0",
|
||||
"@babel/preset-react": "7.0.0",
|
||||
"@babel/plugin-proposal-class-properties": "7.1.0",
|
||||
"@babel/plugin-proposal-export-default-from": "7.0.0",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.0.0",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.0.0",
|
||||
"@babel/runtime": "7.1.2",
|
||||
"babel-eslint": "10.0.1",
|
||||
"babel-loader": "8.0.4",
|
||||
"clean-css": "3.4.25",
|
||||
"css-loader": "0.28.7",
|
||||
"eslint": "4.12.1",
|
||||
"eslint": "5.6.1",
|
||||
"eslint-config-jitsi": "github:jitsi/eslint-config-jitsi#7474f6668515eb5852f1273dc5a50b940a550d3f",
|
||||
"eslint-plugin-flowtype": "2.39.1",
|
||||
"eslint-plugin-import": "2.8.0",
|
||||
"eslint-plugin-jsdoc": "3.2.0",
|
||||
"eslint-plugin-react": "7.5.1",
|
||||
"eslint-plugin-react-native": "3.2.0",
|
||||
"eslint-plugin-flowtype": "2.50.3",
|
||||
"eslint-plugin-import": "2.14.0",
|
||||
"eslint-plugin-jsdoc": "3.8.0",
|
||||
"eslint-plugin-react": "7.11.1",
|
||||
"eslint-plugin-react-native": "3.3.0",
|
||||
"expose-loader": "0.7.4",
|
||||
"file-loader": "1.1.5",
|
||||
"flow-bin": "0.67.1",
|
||||
"flow-bin": "0.78.0",
|
||||
"imports-loader": "0.7.1",
|
||||
"metro-react-native-babel-preset": "0.47.0",
|
||||
"node-sass": "4.8.3",
|
||||
"precommit-hook": "3.0.0",
|
||||
"string-replace-loader": "1.3.0",
|
||||
|
||||
@@ -4,5 +4,8 @@ module.exports = {
|
||||
'eslint-config-jitsi/jsdoc',
|
||||
'eslint-config-jitsi/react',
|
||||
'.eslintrc-react-native.js'
|
||||
]
|
||||
],
|
||||
'rules': {
|
||||
'react/no-deprecated': 0
|
||||
}
|
||||
};
|
||||
|
||||
@@ -413,7 +413,7 @@ export function createRemoteVideoMenuButtonEvent(buttonName, attributes) {
|
||||
|
||||
/**
|
||||
* Creates an event indicating that an action related to screen sharing
|
||||
* occurred (e.g. it was started or stopped).
|
||||
* occurred (e.g. It was started or stopped).
|
||||
*
|
||||
* @param {string} action - The action which occurred.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
@@ -466,7 +466,7 @@ export function createSharedVideoEvent(action, attributes = {}) {
|
||||
* Creates an event associated with a shortcut being pressed, released or
|
||||
* triggered. By convention, where appropriate an attribute named 'enable'
|
||||
* should be used to indicate the action which resulted by the shortcut being
|
||||
* pressed (e.g. whether screen sharing was enabled or disabled).
|
||||
* pressed (e.g. Whether screen sharing was enabled or disabled).
|
||||
*
|
||||
* @param {string} shortcut - The identifier of the shortcut which produced
|
||||
* an action.
|
||||
@@ -512,7 +512,7 @@ export function createStartAudioOnlyEvent(audioOnly) {
|
||||
*
|
||||
* @param {string} source - The source of the configuration, 'local' or
|
||||
* 'remote' depending on whether it comes from the static configuration (i.e.
|
||||
* config.js) or comes dynamically from Jicofo.
|
||||
* {@code config.js}) or comes dynamically from Jicofo.
|
||||
* @param {boolean} audioMute - Whether the configuration requests that audio
|
||||
* is muted.
|
||||
* @param {boolean} videoMute - Whether the configuration requests that video
|
||||
@@ -573,7 +573,7 @@ export function createSyncTrackStateEvent(mediaType, muted) {
|
||||
* Creates an event associated with a toolbar button being clicked/pressed. By
|
||||
* convention, where appropriate an attribute named 'enable' should be used to
|
||||
* indicate the action which resulted by the shortcut being pressed (e.g.
|
||||
* whether screen sharing was enabled or disabled).
|
||||
* Whether screen sharing was enabled or disabled).
|
||||
*
|
||||
* @param {string} buttonName - The identifier of the toolbar button which was
|
||||
* clicked/pressed.
|
||||
@@ -595,9 +595,9 @@ export function createToolbarEvent(buttonName, attributes = {}) {
|
||||
* Creates an event which indicates that a local track was muted.
|
||||
*
|
||||
* @param {string} mediaType - The track's media type ('audio' or 'video').
|
||||
* @param {string} reason - The reason the track was muted (e.g. it was
|
||||
* @param {string} reason - The reason the track was muted (e.g. It was
|
||||
* triggered by the "initial mute" option, or a previously muted track was
|
||||
* replaced (e.g. when a new device was used)).
|
||||
* replaced (e.g. When a new device was used)).
|
||||
* @param {boolean} muted - Whether the track was muted or unmuted.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
|
||||
@@ -128,10 +128,10 @@ export class AbstractApp extends BaseApp<Props, *> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates this {@code AbstractApp} to (i.e. opens) a specific URL.
|
||||
* Navigates this {@code AbstractApp} to (i.e. Opens) a specific URL.
|
||||
*
|
||||
* @param {Object|string} url - The URL to navigate this {@code AbstractApp}
|
||||
* to (i.e. the URL to open).
|
||||
* to (i.e. The URL to open).
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
/**
|
||||
@@ -13,27 +14,24 @@ const AUDIO_LEVEL_DOTS = 5;
|
||||
*/
|
||||
const CENTER_DOT_INDEX = Math.floor(AUDIO_LEVEL_DOTS / 2);
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AudioLevelIndicator}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current audio level to display. The value should be a number between
|
||||
* 0 and 1.
|
||||
*/
|
||||
audioLevel: number
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a ReactElement responsible for drawing audio levels.
|
||||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
class AudioLevelIndicator extends Component {
|
||||
/**
|
||||
* {@code AudioLevelIndicator}'s property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The current audio level to display. The value should be a number
|
||||
* between 0 and 1.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
audioLevel: PropTypes.number
|
||||
};
|
||||
|
||||
class AudioLevelIndicator extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
||||
@@ -23,7 +23,7 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
* password + guest access configuration. Refer to {@link LoginDialog} for more
|
||||
* info.
|
||||
*
|
||||
* @param {string} id - The XMPP user's ID (e.g. user@domain.com).
|
||||
* @param {string} id - The XMPP user's ID (e.g. {@code user@domain.com}).
|
||||
* @param {string} password - The XMPP user's password.
|
||||
* @param {JitsiConference} conference - The conference for which the local
|
||||
* participant's role will be upgraded.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, TextInput, View } from 'react-native';
|
||||
import { connect as reduxConnect } from 'react-redux';
|
||||
@@ -11,6 +12,65 @@ import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
|
||||
import { authenticateAndUpgradeRole, cancelLogin } from '../actions';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} props of {@link LoginDialog}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* {@link JitsiConference} that needs authentication - will hold a valid
|
||||
* value in XMPP login + guest access mode.
|
||||
*/
|
||||
_conference: Object,
|
||||
|
||||
/**
|
||||
* The server hosts specified in the global config.
|
||||
*/
|
||||
_configHosts: Object,
|
||||
|
||||
/**
|
||||
* Indicates if the dialog should display "connecting" status message.
|
||||
*/
|
||||
_connecting: boolean,
|
||||
|
||||
/**
|
||||
* The error which occurred during login/authentication.
|
||||
*/
|
||||
_error: Object,
|
||||
|
||||
/**
|
||||
* The progress in the floating range between 0 and 1 of the authenticating
|
||||
* and upgrading the role of the local participant/user.
|
||||
*/
|
||||
_progress: number,
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: Dispatch<*>,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} state of {@link LoginDialog}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The user entered password for the conference.
|
||||
*/
|
||||
password: string,
|
||||
|
||||
/**
|
||||
* The user entered local participant name.
|
||||
*/
|
||||
username: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Dialog asks user for username and password.
|
||||
*
|
||||
@@ -38,58 +98,14 @@ import styles from './styles';
|
||||
* See {@link https://github.com/jitsi/jicofo#secure-domain} for a description
|
||||
* of the configuration parameters.
|
||||
*/
|
||||
class LoginDialog extends Component {
|
||||
/**
|
||||
* LoginDialog component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* {@link JitsiConference} that needs authentication - will hold a valid
|
||||
* value in XMPP login + guest access mode.
|
||||
*/
|
||||
_conference: PropTypes.object,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
_configHosts: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Indicates if the dialog should display "connecting" status message.
|
||||
*/
|
||||
_connecting: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The error which occurred during login/authentication.
|
||||
*/
|
||||
_error: PropTypes.object,
|
||||
|
||||
/**
|
||||
* The progress in the floating range between 0 and 1 of the
|
||||
* authenticating and upgrading the role of the local participant/user.
|
||||
*/
|
||||
_progress: PropTypes.number,
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: PropTypes.func
|
||||
};
|
||||
|
||||
class LoginDialog extends Component<Props, State> {
|
||||
/**
|
||||
* Initializes a new LoginDialog instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -119,7 +135,7 @@ class LoginDialog extends Component {
|
||||
} = this.props;
|
||||
|
||||
let messageKey;
|
||||
let messageOptions;
|
||||
const messageOptions = {};
|
||||
|
||||
if (progress && progress < 1) {
|
||||
messageKey = 'connection.FETCH_SESSION_ID';
|
||||
@@ -142,7 +158,6 @@ class LoginDialog extends Component {
|
||||
}
|
||||
} else if (name) {
|
||||
messageKey = 'dialog.connectErrorWithMsg';
|
||||
messageOptions || (messageOptions = {});
|
||||
messageOptions.msg = `${name} ${error.message}`;
|
||||
}
|
||||
}
|
||||
@@ -170,7 +185,7 @@ class LoginDialog extends Component {
|
||||
<Text style = { styles.dialogText }>
|
||||
{
|
||||
messageKey
|
||||
? t(messageKey, messageOptions || {})
|
||||
? t(messageKey, messageOptions)
|
||||
: connecting
|
||||
? t('connection.CONNECTING')
|
||||
: ''
|
||||
@@ -181,6 +196,8 @@ class LoginDialog extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
_onUsernameChange: (string) => void;
|
||||
|
||||
/**
|
||||
* Called when user edits the username.
|
||||
*
|
||||
@@ -194,6 +211,8 @@ class LoginDialog extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
_onPasswordChange: (string) => void;
|
||||
|
||||
/**
|
||||
* Called when user edits the password.
|
||||
*
|
||||
@@ -207,6 +226,8 @@ class LoginDialog extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
_onCancel: () => void;
|
||||
|
||||
/**
|
||||
* Notifies this LoginDialog that it has been dismissed by cancel.
|
||||
*
|
||||
@@ -217,6 +238,8 @@ class LoginDialog extends Component {
|
||||
this.props.dispatch(cancelLogin());
|
||||
}
|
||||
|
||||
_onLogin: () => void;
|
||||
|
||||
/**
|
||||
* Notifies this LoginDialog that the login button (OK) has been pressed by
|
||||
* the user.
|
||||
|
||||
@@ -198,8 +198,8 @@ export function getNearestReceiverVideoQualityLevel(availableHeight: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an error thrown by the backend (i.e. lib-jitsi-meet) while
|
||||
* manipulating a conference participant (e.g. pin or select participant).
|
||||
* Handle an error thrown by the backend (i.e. {@code lib-jitsi-meet}) while
|
||||
* manipulating a conference participant (e.g. Pin or select participant).
|
||||
*
|
||||
* @param {Error} err - The Error which was thrown by the backend while
|
||||
* manipulating a conference participant and which is to be handled.
|
||||
|
||||
@@ -28,8 +28,13 @@ import { VIDEO_QUALITY_LEVELS } from './constants';
|
||||
import { isRoomValid } from './functions';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
conference: undefined,
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
locked: undefined,
|
||||
maxReceiverVideoQuality: VIDEO_QUALITY_LEVELS.HIGH,
|
||||
password: undefined,
|
||||
passwordRequired: undefined,
|
||||
preferredReceiverVideoQuality: VIDEO_QUALITY_LEVELS.HIGH
|
||||
};
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ export type ConnectionFailedError = {
|
||||
/**
|
||||
* Opens new connection.
|
||||
*
|
||||
* @param {string} [id] - The XMPP user's ID (e.g. user@server.com).
|
||||
* @param {string} [id] - The XMPP user's ID (e.g. {@code user@server.com}).
|
||||
* @param {string} [password] - The XMPP user's password.
|
||||
* @returns {Function}
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@ import { toState } from '../redux';
|
||||
|
||||
/**
|
||||
* Retrieves a simplified version of the conference/location URL stripped of URL
|
||||
* params (i.e. query/search and hash) which should be used for sending invites.
|
||||
* params (i.e. Query/search and hash) which should be used for sending invites.
|
||||
*
|
||||
* @param {Function|Object} stateOrGetState - The redux state or redux's
|
||||
* {@code getState} function.
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import PropTypes from 'prop-types';
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link DialogContainer}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The component to render.
|
||||
*/
|
||||
_component: Function,
|
||||
|
||||
/**
|
||||
* The props to pass to the component that will be rendered.
|
||||
*/
|
||||
_componentProps: Object,
|
||||
|
||||
/**
|
||||
* True if the UI is in a compact state where we don't show dialogs.
|
||||
*/
|
||||
_reducedUI: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a DialogContainer responsible for showing all dialogs. We will
|
||||
* need a separate container so we can handle multiple dialogs by showing them
|
||||
* simultaneously or queuing them.
|
||||
*/
|
||||
export class DialogContainer extends Component {
|
||||
/**
|
||||
* DialogContainer component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The component to render.
|
||||
*/
|
||||
_component: PropTypes.func,
|
||||
|
||||
/**
|
||||
* The props to pass to the component that will be rendered.
|
||||
*/
|
||||
_componentProps: PropTypes.object,
|
||||
|
||||
/**
|
||||
* True if the UI is in a compact state where we don't show dialogs.
|
||||
*/
|
||||
_reducedUI: PropTypes.bool
|
||||
};
|
||||
|
||||
export class DialogContainer extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { Container, Text } from '../../react';
|
||||
|
||||
import { dialog as styles } from './styles';
|
||||
|
||||
@@ -31,9 +32,9 @@ export default class DialogContent extends Component<Props> {
|
||||
: children;
|
||||
|
||||
return (
|
||||
<View style = { styles.dialogContainer }>
|
||||
<Container style = { styles.dialogContainer }>
|
||||
{ childrenComponent }
|
||||
</View>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
5
react/features/base/dialog/components/styles.web.js
Normal file
5
react/features/base/dialog/components/styles.web.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Placeholder styles for web to be able to use cross platform components
|
||||
* unmodified such as {@code DialogContent}.
|
||||
*/
|
||||
export const dialog = {};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
// FIXME The import of react-native-vector-icons makes the file native-specific
|
||||
// but the file's name and/or location (within the directory structure) don't
|
||||
// reflect that, it suggests the file is platform-independent.
|
||||
@@ -10,3 +12,8 @@ import icoMoonConfig from './jitsi.json';
|
||||
* Creates the Jitsi icon set from the ico moon project config file.
|
||||
*/
|
||||
export const Icon = createIconSetFromIcoMoon(icoMoonConfig);
|
||||
|
||||
// Dynamically load font on iOS
|
||||
if (Platform.OS === 'ios') {
|
||||
Icon.loadFont('jitsi.ttf');
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ declare var interfaceConfig: Object;
|
||||
* @public
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
export const LANGUAGES = Object.keys(LANGUAGES_RESOURCES);
|
||||
export const LANGUAGES: Array<string> = Object.keys(LANGUAGES_RESOURCES);
|
||||
|
||||
/**
|
||||
* The default language.
|
||||
|
||||
@@ -32,7 +32,7 @@ const SOCK_STREAM = 1; /* stream socket */
|
||||
* The RTCPeerConnection provided by react-native-webrtc fires onaddstream
|
||||
* before it remembers remotedescription (and thus makes it available to API
|
||||
* clients). Because that appears to be a problem for lib-jitsi-meet which has
|
||||
* been successfully running on Chrome, Firefox, etc. for a very long
|
||||
* been successfully running on Chrome, Firefox and others for a very long
|
||||
* time, attempt to meet its expectations (by extending RTCPPeerConnection).
|
||||
*
|
||||
* @class
|
||||
|
||||
@@ -39,9 +39,10 @@ function _getCommonPrototype(a, b) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an absolute minimum of the common logic of Document.querySelector
|
||||
* and Element.querySelector. Implements the most simple of selectors necessary
|
||||
* to satisfy the call sites at the time of this writing i.e. select by tagName.
|
||||
* Implements an absolute minimum of the common logic of
|
||||
* {@code Document.querySelector} and {@code Element.querySelector}. Implements
|
||||
* the most simple of selectors necessary to satisfy the call sites at the time
|
||||
* of this writing (i.e. Select by tagName).
|
||||
*
|
||||
* @param {Node} node - The Node which is the root of the tree to query.
|
||||
* @param {string} selectors - The group of CSS selectors to match on.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { trackVideoStarted } from '../../tracks';
|
||||
@@ -6,51 +7,71 @@ import { trackVideoStarted } from '../../tracks';
|
||||
import { shouldRenderVideoTrack } from '../functions';
|
||||
import { Video } from './_';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AbstractVideoTrack}.
|
||||
*/
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Dispatch<*>,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the {@link Video} of {@code AbstractVideoTrack}
|
||||
* is clicked/pressed.
|
||||
*/
|
||||
onPress?: Function,
|
||||
|
||||
/**
|
||||
* The Redux representation of the participant's video track.
|
||||
*/
|
||||
videoTrack?: Object,
|
||||
|
||||
/**
|
||||
* Whether or not video should be rendered after knowing video playback has
|
||||
* started.
|
||||
*/
|
||||
waitForVideoStarted?: boolean,
|
||||
|
||||
/**
|
||||
* The z-order of the Video of AbstractVideoTrack in the stacking space of
|
||||
* all Videos. For more details, refer to the zOrder property of the Video
|
||||
* class for React Native.
|
||||
*/
|
||||
zOrder?: number,
|
||||
|
||||
/**
|
||||
* Indicates whether zooming (pinch to zoom and/or drag) is enabled.
|
||||
*/
|
||||
zoomEnabled?: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link AbstractVideoTrack}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The Redux representation of the participant's video track.
|
||||
*/
|
||||
videoTrack: Object | null
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} that renders video element for a
|
||||
* specific video track.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
export default class AbstractVideoTrack extends Component {
|
||||
/**
|
||||
* AbstractVideoTrack component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the {@link Video} of
|
||||
* {@code AbstractVideoTrack} is clicked/pressed.
|
||||
*/
|
||||
onPress: PropTypes.func,
|
||||
|
||||
videoTrack: PropTypes.object,
|
||||
|
||||
waitForVideoStarted: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The z-order of the Video of AbstractVideoTrack in the stacking space
|
||||
* of all Videos. For more details, refer to the zOrder property of the
|
||||
* Video class for React Native.
|
||||
*/
|
||||
zOrder: PropTypes.number,
|
||||
|
||||
/**
|
||||
* Indicates whether zooming (pinch to zoom and/or drag) is enabled.
|
||||
*/
|
||||
zoomEnabled: PropTypes.bool
|
||||
};
|
||||
|
||||
export default class AbstractVideoTrack<P: Props> extends Component<P, State> {
|
||||
/**
|
||||
* Initializes a new AbstractVideoTrack instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -69,7 +90,7 @@ export default class AbstractVideoTrack extends Component {
|
||||
* receive.
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
componentWillReceiveProps(nextProps: P) {
|
||||
const oldValue = this.state.videoTrack;
|
||||
const newValue = _falsy2null(nextProps.videoTrack);
|
||||
|
||||
@@ -88,7 +109,7 @@ export default class AbstractVideoTrack extends Component {
|
||||
const { videoTrack } = this.state;
|
||||
let render;
|
||||
|
||||
if (this.props.waitForVideoStarted) {
|
||||
if (this.props.waitForVideoStarted && videoTrack) {
|
||||
// That's the complex case: we have to wait for onPlaying before we
|
||||
// render videoTrack. The complexity comes from the fact that
|
||||
// onPlaying will come after we render videoTrack.
|
||||
@@ -110,14 +131,15 @@ export default class AbstractVideoTrack extends Component {
|
||||
render = shouldRenderVideoTrack(videoTrack, false);
|
||||
}
|
||||
|
||||
const stream
|
||||
= render ? videoTrack.jitsiTrack.getOriginalStream() : null;
|
||||
const stream = render && videoTrack
|
||||
? videoTrack.jitsiTrack.getOriginalStream() : null;
|
||||
|
||||
// Actual zoom is currently only enabled if the stream is a desktop
|
||||
// stream.
|
||||
const zoomEnabled
|
||||
= this.props.zoomEnabled
|
||||
&& stream
|
||||
&& videoTrack
|
||||
&& videoTrack.videoType === 'desktop';
|
||||
|
||||
return (
|
||||
@@ -131,6 +153,8 @@ export default class AbstractVideoTrack extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
_onVideoPlaying: () => void;
|
||||
|
||||
/**
|
||||
* Handler for case when video starts to play.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { RTCView } from 'react-native-webrtc';
|
||||
|
||||
@@ -9,60 +8,58 @@ import { Pressable } from '../../../react';
|
||||
import styles from './styles';
|
||||
import VideoTransform from './VideoTransform';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Video}.
|
||||
*/
|
||||
type Props = {
|
||||
mirror: boolean,
|
||||
|
||||
onPlaying: Function,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the {@code Video} is clicked/pressed.
|
||||
*/
|
||||
onPress: Function,
|
||||
|
||||
stream: Object,
|
||||
|
||||
/**
|
||||
* Similarly to the CSS property z-index, specifies the z-order of this
|
||||
* Video in the stacking space of all Videos. When Videos overlap,
|
||||
* zOrder determines which one covers the other. A Video with a larger
|
||||
* zOrder generally covers a Video with a lower one.
|
||||
*
|
||||
* Non-overlapping Videos may safely share a z-order (because one does
|
||||
* not have to cover the other).
|
||||
*
|
||||
* The support for zOrder is platform-dependent and/or
|
||||
* implementation-specific. Thus, specifying a value for zOrder is to be
|
||||
* thought of as giving a hint rather than as imposing a requirement.
|
||||
* For example, video renderers such as Video are commonly implemented
|
||||
* using OpenGL and OpenGL views may have different numbers of layers in
|
||||
* their stacking space. Android has three: a layer bellow the window
|
||||
* (aka default), a layer bellow the window again but above the previous
|
||||
* layer (aka media overlay), and above the window. Consequently, it is
|
||||
* advisable to limit the number of utilized layers in the stacking
|
||||
* space to the minimum sufficient for the desired display. For example,
|
||||
* a video call application usually needs a maximum of two zOrder
|
||||
* values: 0 for the remote video(s) which appear in the background, and
|
||||
* 1 for the local video(s) which appear above the remote video(s).
|
||||
*/
|
||||
zOrder: number,
|
||||
|
||||
/**
|
||||
* Indicates whether zooming (pinch to zoom and/or drag) is enabled.
|
||||
*/
|
||||
zoomEnabled: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* The React Native {@link Component} which is similar to Web's
|
||||
* {@code HTMLVideoElement} and wraps around react-native-webrtc's
|
||||
* {@link RTCView}.
|
||||
*/
|
||||
export default class Video extends Component<*> {
|
||||
/**
|
||||
* {@code Video} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
mirror: PropTypes.bool,
|
||||
|
||||
onPlaying: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the {@code Video} is clicked/pressed.
|
||||
*/
|
||||
onPress: PropTypes.func,
|
||||
|
||||
stream: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Similarly to the CSS property z-index, specifies the z-order of this
|
||||
* Video in the stacking space of all Videos. When Videos overlap,
|
||||
* zOrder determines which one covers the other. A Video with a larger
|
||||
* zOrder generally covers a Video with a lower one.
|
||||
*
|
||||
* Non-overlapping Videos may safely share a z-order (because one does
|
||||
* not have to cover the other).
|
||||
*
|
||||
* The support for zOrder is platform-dependent and/or
|
||||
* implementation-specific. Thus, specifying a value for zOrder is to be
|
||||
* thought of as giving a hint rather than as imposing a requirement.
|
||||
* For example, video renderers such as Video are commonly implemented
|
||||
* using OpenGL and OpenGL views may have different numbers of layers in
|
||||
* their stacking space. Android has three: a layer bellow the window
|
||||
* (aka default), a layer bellow the window again but above the previous
|
||||
* layer (aka media overlay), and above the window. Consequently, it is
|
||||
* advisable to limit the number of utilized layers in the stacking
|
||||
* space to the minimum sufficient for the desired display. For example,
|
||||
* a video call application usually needs a maximum of two zOrder
|
||||
* values: 0 for the remote video(s) which appear in the background, and
|
||||
* 1 for the local video(s) which appear above the remote video(s).
|
||||
*/
|
||||
zOrder: PropTypes.number,
|
||||
|
||||
/**
|
||||
* Indicates whether zooming (pinch to zoom and/or drag) is enabled.
|
||||
*/
|
||||
zoomEnabled: PropTypes.bool
|
||||
};
|
||||
|
||||
export default class Video extends Component<Props> {
|
||||
/**
|
||||
* React Component method that executes once component is mounted.
|
||||
*
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import AbstractVideoTrack from '../AbstractVideoTrack';
|
||||
import type { Props } from '../AbstractVideoTrack';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
@@ -10,14 +13,7 @@ import styles from './styles';
|
||||
*
|
||||
* @extends AbstractVideoTrack
|
||||
*/
|
||||
class VideoTrack extends AbstractVideoTrack {
|
||||
/**
|
||||
* VideoTrack component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = AbstractVideoTrack.propTypes
|
||||
|
||||
class VideoTrack extends AbstractVideoTrack<Props> {
|
||||
/**
|
||||
* Renders the video element for the associated video track.
|
||||
*
|
||||
|
||||
@@ -41,11 +41,9 @@ export default class Audio extends AbstractAudio {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const loop = this.props.loop ? 'true' : null;
|
||||
|
||||
return (
|
||||
<audio
|
||||
loop = { loop }
|
||||
loop = { Boolean(this.props.loop) }
|
||||
onCanPlayThrough = { this._onCanPlayThrough }
|
||||
preload = 'auto'
|
||||
|
||||
|
||||
@@ -1,12 +1,42 @@
|
||||
import PropTypes from 'prop-types';
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Video}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* CSS classes to add to the video element.
|
||||
*/
|
||||
className: string,
|
||||
|
||||
/**
|
||||
* The value of the id attribute of the video. Used by the torture tests to
|
||||
* locate video elements.
|
||||
*/
|
||||
id: string,
|
||||
|
||||
/**
|
||||
* Optional callback to invoke once the video starts playing.
|
||||
*/
|
||||
onVideoPlaying: Function,
|
||||
|
||||
/**
|
||||
* The JitsiLocalTrack to display.
|
||||
*/
|
||||
videoTrack: ?Object
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that renders a video element for a passed in video track.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class Video extends Component {
|
||||
class Video extends Component<Props> {
|
||||
_videoElement: ?Object;
|
||||
|
||||
/**
|
||||
* Default values for {@code Video} component's properties.
|
||||
*
|
||||
@@ -18,41 +48,13 @@ class Video extends Component {
|
||||
id: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* {@code Video} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* CSS classes to add to the video element.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The value of the id attribute of the video. Used by the torture tests
|
||||
* to locate video elements.
|
||||
*/
|
||||
id: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Optional callback to invoke once the video starts playing.
|
||||
*/
|
||||
onVideoPlaying: PropTypes.func,
|
||||
|
||||
/**
|
||||
* The JitsiLocalTrack to display.
|
||||
*/
|
||||
videoTrack: PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Video} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
/**
|
||||
@@ -78,8 +80,10 @@ class Video extends Component {
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._videoElement.volume = 0;
|
||||
this._videoElement.onplaying = this._onVideoPlaying;
|
||||
if (this._videoElement) {
|
||||
this._videoElement.volume = 0;
|
||||
this._videoElement.onplaying = this._onVideoPlaying;
|
||||
}
|
||||
|
||||
this._attachTrack(this.props.videoTrack);
|
||||
}
|
||||
@@ -98,13 +102,13 @@ class Video extends Component {
|
||||
/**
|
||||
* Updates the video display only if a new track is added. This component's
|
||||
* updating is blackboxed from React to prevent re-rendering of video
|
||||
* element, as the lib uses track.attach(videoElement) instead.
|
||||
* element, as the lib uses {@code track.attach(videoElement)} instead.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {boolean} - False is always returned to blackbox this component.
|
||||
* @returns {boolean} - False is always returned to blackbox this component
|
||||
* from React.
|
||||
*/
|
||||
shouldComponentUpdate(nextProps) {
|
||||
shouldComponentUpdate(nextProps: Props) {
|
||||
const currentJitsiTrack = this.props.videoTrack
|
||||
&& this.props.videoTrack.jitsiTrack;
|
||||
const nextJitsiTrack = nextProps.videoTrack
|
||||
@@ -167,6 +171,8 @@ class Video extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
_onVideoPlaying: () => void;
|
||||
|
||||
/**
|
||||
* Invokes the onvideoplaying callback if defined.
|
||||
*
|
||||
@@ -179,6 +185,8 @@ class Video extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
_setVideoElement: () => void;
|
||||
|
||||
/**
|
||||
* Sets an instance variable for the component's video element so it can be
|
||||
* referenced later for attaching and detaching a JitsiLocalTrack.
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
import PropTypes from 'prop-types';
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import AbstractVideoTrack from '../AbstractVideoTrack';
|
||||
import type { Props as AbstractVideoTrackProps } from '../AbstractVideoTrack';
|
||||
|
||||
import Video from './Video';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link VideoTrack}.
|
||||
*/
|
||||
type Props = {
|
||||
...AbstractVideoTrackProps,
|
||||
|
||||
/**
|
||||
* CSS classes to add to the video element.
|
||||
*/
|
||||
className: string,
|
||||
|
||||
/**
|
||||
* The value of the id attribute of the video. Used by the torture tests
|
||||
* to locate video elements.
|
||||
*/
|
||||
id: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that renders a video element for a passed in video track and
|
||||
* notifies the store when the video has started playing.
|
||||
*
|
||||
* @extends AbstractVideoTrack
|
||||
*/
|
||||
class VideoTrack extends AbstractVideoTrack {
|
||||
class VideoTrack extends AbstractVideoTrack<Props> {
|
||||
/**
|
||||
* Default values for {@code VideoTrack} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
...AbstractVideoTrack.defaultProps,
|
||||
|
||||
className: '',
|
||||
|
||||
id: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* {@code VideoTrack} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
...AbstractVideoTrack.propTypes,
|
||||
|
||||
/**
|
||||
* CSS classes to add to the video element.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The value of the id attribute of the video. Used by the torture tests
|
||||
* to locate video elements.
|
||||
*/
|
||||
id: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the video element.
|
||||
*
|
||||
@@ -62,6 +60,8 @@ class VideoTrack extends AbstractVideoTrack {
|
||||
videoTrack = { this.props.videoTrack } />
|
||||
);
|
||||
}
|
||||
|
||||
_onVideoPlaying: () => void;
|
||||
}
|
||||
|
||||
export default connect()(VideoTrack);
|
||||
|
||||
@@ -58,7 +58,7 @@ export function isVideoMutedByUser(stateful: Function | Object) {
|
||||
* otherwise, false.
|
||||
*/
|
||||
export function shouldRenderVideoTrack(
|
||||
videoTrack: { muted: boolean, videoStarted: boolean },
|
||||
videoTrack: ?{ muted: boolean, videoStarted: boolean },
|
||||
waitForVideoStarted: boolean) {
|
||||
return (
|
||||
videoTrack
|
||||
|
||||
@@ -146,11 +146,11 @@ export default class Avatar extends Component<Props, State> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this {@code Component} that it will be unmounted and destroyed
|
||||
* and, most importantly, that it should no longer call
|
||||
* {@link #setState(Object)}. {@code Avatar} needs it because it downloads
|
||||
* images via {@link ImageCache} which will asynchronously notify about
|
||||
* success.
|
||||
* Notifies this {@code Component} that it will be unmounted and destroyed,
|
||||
* and most importantly, that it should no longer call
|
||||
* {@link #setState(Object)}. The {@code Avatar} needs it because it
|
||||
* downloads images via {@link ImageCache} which will asynchronously notify
|
||||
* about success.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import md5 from 'js-md5';
|
||||
import { getAvatarURL as _getAvatarURL } from 'js-utils/avatar';
|
||||
|
||||
import { toState } from '../redux';
|
||||
|
||||
@@ -42,40 +42,18 @@ export function getAvatarURL({ avatarID, avatarURL, email, id }: {
|
||||
return avatarURL;
|
||||
}
|
||||
|
||||
let key = email || avatarID;
|
||||
let urlPrefix;
|
||||
let urlSuffix;
|
||||
// The deployment is allowed to choose the avatar service which is to
|
||||
// generate the random avatars.
|
||||
const avatarService
|
||||
= typeof interfaceConfig === 'object'
|
||||
&& interfaceConfig.RANDOM_AVATAR_URL_PREFIX
|
||||
? {
|
||||
urlPrefix: interfaceConfig.RANDOM_AVATAR_URL_PREFIX,
|
||||
urlSuffix: interfaceConfig.RANDOM_AVATAR_URL_SUFFIX }
|
||||
: undefined;
|
||||
|
||||
// If the ID looks like an e-mail address, we'll use Gravatar because it
|
||||
// supports e-mail addresses.
|
||||
if (key && key.indexOf('@') > 0) {
|
||||
urlPrefix = 'https://www.gravatar.com/avatar/';
|
||||
urlSuffix = '?d=wavatar&size=200';
|
||||
} else {
|
||||
// Otherwise, we do not have much a choice but a random avatar (fetched
|
||||
// from a configured avatar service).
|
||||
if (!key) {
|
||||
key = id;
|
||||
if (!key) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// The deployment is allowed to choose the avatar service which is to
|
||||
// generate the random avatars.
|
||||
urlPrefix
|
||||
= typeof interfaceConfig === 'object'
|
||||
&& interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
|
||||
if (urlPrefix) {
|
||||
urlSuffix = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
|
||||
} else {
|
||||
// Otherwise, use a default (meeples, of course).
|
||||
urlPrefix = 'https://abotars.jitsi.net/meeple/';
|
||||
urlSuffix = '';
|
||||
}
|
||||
}
|
||||
|
||||
return urlPrefix + md5.hex(key.trim().toLowerCase()) + urlSuffix;
|
||||
// eslint-disable-next-line object-property-newline
|
||||
return _getAvatarURL({ avatarID, email, id }, avatarService);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,8 +127,9 @@ export function getParticipantCount(stateful: Object | Function) {
|
||||
|
||||
/**
|
||||
* Returns participant's display name.
|
||||
* FIXME: remove the hardcoded strings once interfaceConfig is stored in redux
|
||||
* and merge with a similarly named method in conference.js.
|
||||
*
|
||||
* FIXME: Remove the hardcoded strings once interfaceConfig is stored in redux
|
||||
* and merge with a similarly named method in {@code conference.js}.
|
||||
*
|
||||
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
|
||||
* {@code getState} function to be used to retrieve the state.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
import InlineDialog from '@atlaskit/inline-dialog';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
/**
|
||||
@@ -28,13 +29,68 @@ function _mapPositionToPaddingClass(position = 'left') {
|
||||
return DIALOG_TO_PADDING_POSITION[position.split(' ')[0]];
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Popover}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* A child React Element to use as the trigger for showing the dialog.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Additional CSS classnames to apply to the root of the {@code Popover}
|
||||
* component.
|
||||
*/
|
||||
className: string,
|
||||
|
||||
/**
|
||||
* The ReactElement to display within the dialog.
|
||||
*/
|
||||
content: Object,
|
||||
|
||||
/**
|
||||
* Whether displaying of the popover should be prevented.
|
||||
*/
|
||||
disablePopover: boolean,
|
||||
|
||||
/**
|
||||
* An id attribute to apply to the root of the {@code Popover}
|
||||
* component.
|
||||
*/
|
||||
id: string,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the popover has opened.
|
||||
*/
|
||||
onPopoverOpen: Function,
|
||||
|
||||
/**
|
||||
* From which side of the dialog trigger the dialog should display. The
|
||||
* value will be passed to {@code InlineDialog}.
|
||||
*/
|
||||
position: string
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link Popover}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* Whether or not the {@code InlineDialog} should be displayed.
|
||||
*/
|
||||
showDialog: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@code Component} for showing an {@code InlineDialog} on
|
||||
* mouseenter of the trigger and contents, and hiding the dialog on mouseleave.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class Popover extends Component {
|
||||
class Popover extends Component<Props, State> {
|
||||
/**
|
||||
* Default values for {@code Popover} component's properties.
|
||||
*
|
||||
@@ -45,66 +101,16 @@ class Popover extends Component {
|
||||
id: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* {@code Popover} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* A child React Element to use as the trigger for showing the dialog.
|
||||
*/
|
||||
children: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Additional CSS classnames to apply to the root of the {@code Popover}
|
||||
* component.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The ReactElement to display within the dialog.
|
||||
*/
|
||||
content: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Whether displaying of the popover should be prevented.
|
||||
*/
|
||||
disablePopover: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* An id attribute to apply to the root of the {@code Popover}
|
||||
* component.
|
||||
*/
|
||||
id: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the popover has opened.
|
||||
*/
|
||||
onPopoverOpen: PropTypes.func,
|
||||
|
||||
/**
|
||||
* From which side of the dialog trigger the dialog should display. The
|
||||
* value will be passed to {@code InlineDialog}.
|
||||
*/
|
||||
position: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Popover} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
/**
|
||||
* Whether or not the {@code InlineDialog} should be displayed.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
showDialog: false
|
||||
};
|
||||
|
||||
@@ -136,6 +142,8 @@ class Popover extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
_onHideDialog: () => void;
|
||||
|
||||
/**
|
||||
* Stops displaying the {@code InlineDialog}.
|
||||
*
|
||||
@@ -146,6 +154,8 @@ class Popover extends Component {
|
||||
this.setState({ showDialog: false });
|
||||
}
|
||||
|
||||
_onShowDialog: () => void;
|
||||
|
||||
/**
|
||||
* Displays the {@code InlineDialog} and calls any registered onPopoverOpen
|
||||
* callbacks.
|
||||
|
||||
@@ -17,6 +17,11 @@ export type Item = {
|
||||
*/
|
||||
elementAfter?: ?ComponentType<any>,
|
||||
|
||||
/**
|
||||
* Unique ID of the item.
|
||||
*/
|
||||
id: Object | string,
|
||||
|
||||
/**
|
||||
* Item title
|
||||
*/
|
||||
|
||||
@@ -58,7 +58,6 @@ export type Props = {
|
||||
visible?: ?boolean
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Abstract (base) class for container of React {@link Component} children with
|
||||
* a style.
|
||||
@@ -95,6 +94,7 @@ export default class AbstractContainer<P: Props> extends Component<P> {
|
||||
...filteredProps
|
||||
} = props || this.props;
|
||||
|
||||
// $FlowFixMe
|
||||
return React.createElement(type, filteredProps, children);
|
||||
}
|
||||
}
|
||||
|
||||
22
react/features/base/react/components/AbstractPage.js
Normal file
22
react/features/base/react/components/AbstractPage.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
/**
|
||||
* Abstract component that defines a refreshable page to be rendered by
|
||||
* {@code PagedList}.
|
||||
*/
|
||||
export default class AbstractPage<P> extends Component<P> {
|
||||
/**
|
||||
* Method to be overriden by the implementing classes to refresh the data
|
||||
* content of the component.
|
||||
*
|
||||
* Note: It is a static method as the {@code Component} may not be
|
||||
* initialized yet when the UI invokes refresh (e.g. Tab change).
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
static refresh() {
|
||||
// No implementation in abstract class.
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,13 @@ type Props = {
|
||||
/**
|
||||
* An array of sections
|
||||
*/
|
||||
sections: Array<Section>
|
||||
sections: Array<Section>,
|
||||
|
||||
/**
|
||||
* Optional array of on-slide actions this list should support. For details
|
||||
* see https://github.com/dancormier/react-native-swipeout.
|
||||
*/
|
||||
slideActions?: Array<Object>
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -85,8 +91,8 @@ class NavigateSectionList extends Component<Props> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's Component.render.
|
||||
* Note: we don't use the refreshing value yet, because refreshing of these
|
||||
* Implements React's {@code Component.render}.
|
||||
* Note: We don't use the refreshing value yet, because refreshing of these
|
||||
* lists is super quick, no need to complicate the code - yet.
|
||||
*
|
||||
* @inheritdoc
|
||||
@@ -205,7 +211,8 @@ class NavigateSectionList extends Component<Props> {
|
||||
key = { key }
|
||||
onPress = { url ? this._onPress(url) : undefined }
|
||||
secondaryAction = {
|
||||
url ? undefined : this._onSecondaryAction(id) } />
|
||||
url ? undefined : this._onSecondaryAction(id) }
|
||||
slideActions = { this.props.slideActions } />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './_';
|
||||
export { default as AbstractPage } from './AbstractPage';
|
||||
export { default as NavigateSectionList } from './NavigateSectionList';
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AbstractPagedList}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The zero-based index of the page that should be rendered (selected) by
|
||||
* default.
|
||||
*/
|
||||
defaultPage: number,
|
||||
|
||||
/**
|
||||
* Indicates if the list is disabled or not.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Callback to execute on page change.
|
||||
*/
|
||||
onSelectPage: ?Function,
|
||||
|
||||
/**
|
||||
* The pages of the PagedList component to be rendered.
|
||||
*
|
||||
* Note: An element's {@code component} may be {@code undefined} and then it
|
||||
* won't need to be rendered.
|
||||
*/
|
||||
pages: Array<{
|
||||
component: ?Object,
|
||||
icon: string | number,
|
||||
title: string
|
||||
}>
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link AbstractPagedList}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The currently selected page.
|
||||
*/
|
||||
pageIndex: number
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class containing the platform independent logic of the paged lists.
|
||||
*/
|
||||
export default class AbstractPagedList extends Component<Props, State> {
|
||||
/**
|
||||
* Initializes a new {@code AbstractPagedList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pageIndex: this._validatePageIndex(props.defaultPage)
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._maybeRefreshSelectedPage
|
||||
= this._maybeRefreshSelectedPage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._maybeRefreshSelectedPage(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
const pages = this.props.pages.filter(({ component }) => component);
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
styles.pagedListContainer,
|
||||
disabled ? styles.pagedListContainerDisabled : null
|
||||
] }>
|
||||
{
|
||||
pages.length > 1
|
||||
? this._renderPagedList(disabled)
|
||||
: pages.length === 1
|
||||
? React.createElement(
|
||||
|
||||
// $FlowExpectedError
|
||||
/* type */ pages[0].component,
|
||||
/* props */ {
|
||||
disabled,
|
||||
style: styles.pagedList
|
||||
})
|
||||
: null
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_maybeRefreshSelectedPage: ?boolean => void;
|
||||
|
||||
/**
|
||||
* Components that this PagedList displays may have a refresh function to
|
||||
* refresh its content when displayed (or based on custom logic). This
|
||||
* function invokes this logic if it's present.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
_maybeRefreshSelectedPage(isInteractive: boolean = true) {
|
||||
const selectedPage = this.props.pages[this.state.pageIndex];
|
||||
let component;
|
||||
|
||||
if (selectedPage && (component = selectedPage.component)) {
|
||||
const { refresh } = component;
|
||||
|
||||
typeof refresh === 'function'
|
||||
&& refresh.call(component, this.props.dispatch, isInteractive);
|
||||
}
|
||||
}
|
||||
|
||||
_renderPagedList: boolean => React$Node;
|
||||
|
||||
_selectPage: number => void;
|
||||
|
||||
/**
|
||||
* Sets the selected page.
|
||||
*
|
||||
* @param {number} pageIndex - The index of the selected page.
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_selectPage(pageIndex: number) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
pageIndex = this._validatePageIndex(pageIndex);
|
||||
|
||||
const { onSelectPage } = this.props;
|
||||
|
||||
typeof onSelectPage === 'function' && onSelectPage(pageIndex);
|
||||
|
||||
this.setState({ pageIndex }, this._maybeRefreshSelectedPage);
|
||||
}
|
||||
|
||||
_validatePageIndex: number => number;
|
||||
|
||||
/**
|
||||
* Validates the requested page index and returns a safe value.
|
||||
*
|
||||
* @private
|
||||
* @param {number} pageIndex - The requested page index.
|
||||
* @returns {number}
|
||||
*/
|
||||
_validatePageIndex(pageIndex) {
|
||||
// pageIndex may point to a non-existing page if some of the pages are
|
||||
// disabled (their component property is undefined).
|
||||
const maxPageIndex
|
||||
= this.props.pages.filter(({ component }) => component).length - 1;
|
||||
|
||||
return Math.max(0, Math.min(maxPageIndex, pageIndex));
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ export default class Container<P: Props> extends AbstractContainer<P> {
|
||||
accessibilityLabel,
|
||||
accessible,
|
||||
onPress: onClick,
|
||||
underlayColor
|
||||
...touchFeedback && { underlayColor }
|
||||
},
|
||||
element);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
import PropTypes from 'prop-types';
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
|
||||
import Text from './Text';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Link}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The children to be displayed within this Link.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Notifies that this Link failed to open the URL associated with it.
|
||||
*/
|
||||
onLinkingOpenURLRejected: Function,
|
||||
|
||||
/**
|
||||
* The CSS style to be applied to this Link for the purposes of display.
|
||||
*/
|
||||
style: Object,
|
||||
|
||||
/**
|
||||
* The URL to be opened when this Link is clicked/pressed.
|
||||
*/
|
||||
url: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a (hyper)link to a URL in the fashion of the HTML anchor element
|
||||
* and its href attribute.
|
||||
*/
|
||||
export default class Link extends Component {
|
||||
/**
|
||||
* {@code Link} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The children to be displayed within this Link.
|
||||
*/
|
||||
children: PropTypes.node,
|
||||
|
||||
/**
|
||||
* Notifies that this Link failed to open the URL associated with it.
|
||||
*/
|
||||
onLinkingOpenURLRejected: PropTypes.func,
|
||||
|
||||
/**
|
||||
* The CSS style to be applied to this Link for the purposes of display.
|
||||
*/
|
||||
style: PropTypes.object,
|
||||
|
||||
/**
|
||||
* The URL to be opened when this Link is clicked/pressed.
|
||||
*/
|
||||
url: PropTypes.string
|
||||
};
|
||||
|
||||
export default class Link extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new Link instance.
|
||||
*
|
||||
* @param {Object} props - Component properties.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
@@ -77,6 +77,8 @@ export default class Link extends Component {
|
||||
onRejected && onRejected(reason);
|
||||
}
|
||||
|
||||
_onPress: () => void;
|
||||
|
||||
/**
|
||||
* Handles press on this Link. Opens the URL associated with this Link.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import Swipeout from 'react-native-swipeout';
|
||||
|
||||
import { ColorPalette } from '../../../styles';
|
||||
|
||||
import Container from './Container';
|
||||
import Text from './Text';
|
||||
@@ -22,7 +25,13 @@ type Props = {
|
||||
/**
|
||||
* Function to be invoked when secondary action was performed on an Item.
|
||||
*/
|
||||
secondaryAction: ?Function
|
||||
secondaryAction: ?Function,
|
||||
|
||||
/**
|
||||
* Optional array of on-slide actions this list should support. For details
|
||||
* see https://github.com/dancormier/react-native-swipeout.
|
||||
*/
|
||||
slideActions?: Array<Object>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,37 +138,59 @@ export default class NavigateSectionListItem extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { colorBase, lines, title } = this.props.item;
|
||||
const { slideActions } = this.props;
|
||||
const { id, colorBase, lines, title } = this.props.item;
|
||||
const avatarStyles = {
|
||||
...styles.avatar,
|
||||
...this._getAvatarColor(colorBase)
|
||||
};
|
||||
let right;
|
||||
|
||||
// NOTE: The {@code Swipeout} component has an onPress prop encapsulated
|
||||
// in the {@code right} array, but we need to bind it to the ID of the
|
||||
// item too.
|
||||
|
||||
if (slideActions) {
|
||||
right = [];
|
||||
for (const slideAction of slideActions) {
|
||||
right.push({
|
||||
backgroundColor: slideAction.backgroundColor,
|
||||
onPress: slideAction.onPress.bind(undefined, id),
|
||||
text: slideAction.text
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container
|
||||
onClick = { this.props.onPress }
|
||||
style = { styles.listItem }
|
||||
underlayColor = { UNDERLAY_COLOR }>
|
||||
<Container style = { styles.avatarContainer }>
|
||||
<Container style = { avatarStyles }>
|
||||
<Text style = { styles.avatarContent }>
|
||||
{title.substr(0, 1).toUpperCase()}
|
||||
</Text>
|
||||
<Swipeout
|
||||
backgroundColor = { ColorPalette.transparent }
|
||||
right = { right }>
|
||||
<Container
|
||||
onClick = { this.props.onPress }
|
||||
style = { styles.listItem }
|
||||
underlayColor = { UNDERLAY_COLOR }>
|
||||
<Container style = { styles.avatarContainer }>
|
||||
<Container style = { avatarStyles }>
|
||||
<Text style = { styles.avatarContent }>
|
||||
{title.substr(0, 1).toUpperCase()}
|
||||
</Text>
|
||||
</Container>
|
||||
</Container>
|
||||
<Container style = { styles.listItemDetails }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { [
|
||||
styles.listItemText,
|
||||
styles.listItemTitle
|
||||
] }>
|
||||
{title}
|
||||
</Text>
|
||||
{this._renderItemLines(lines)}
|
||||
</Container>
|
||||
{ this.props.secondaryAction
|
||||
&& this._renderSecondaryAction() }
|
||||
</Container>
|
||||
<Container style = { styles.listItemDetails }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = {{
|
||||
...styles.listItemText,
|
||||
...styles.listItemTitle
|
||||
}}>
|
||||
{title}
|
||||
</Text>
|
||||
{this._renderItemLines(lines)}
|
||||
</Container>
|
||||
{ this.props.secondaryAction && this._renderSecondaryAction() }
|
||||
</Container>
|
||||
</Swipeout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TouchableOpacity, View, ViewPagerAndroid } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Icon } from '../../../font-icons';
|
||||
|
||||
import AbstractPagedList from './AbstractPagedList';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* An Android specific component to render a paged list.
|
||||
*
|
||||
* @extends PagedList
|
||||
*/
|
||||
class PagedList extends AbstractPagedList {
|
||||
/**
|
||||
* A reference to the viewpager.
|
||||
*/
|
||||
_viewPager: ViewPagerAndroid;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code PagedList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onIconPress = this._onIconPress.bind(this);
|
||||
this._getIndicatorStyle = this._getIndicatorStyle.bind(this);
|
||||
this._onPageSelected = this._onPageSelected.bind(this);
|
||||
this._setViewPager = this._setViewPager.bind(this);
|
||||
}
|
||||
|
||||
_onIconPress: number => Function;
|
||||
|
||||
/**
|
||||
* Constructs a function to be used as a callback for the icons in the tab
|
||||
* bar.
|
||||
*
|
||||
* @param {number} pageIndex - The index of the page to activate via the
|
||||
* callback.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onIconPress(pageIndex) {
|
||||
return () => {
|
||||
this._viewPager.setPage(pageIndex);
|
||||
this._selectPage(pageIndex);
|
||||
};
|
||||
}
|
||||
|
||||
_getIndicatorStyle: number => Object;
|
||||
|
||||
/**
|
||||
* Constructs the style of an indicator.
|
||||
*
|
||||
* @param {number} indicatorIndex - The index of the indicator.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getIndicatorStyle(indicatorIndex) {
|
||||
if (this.state.pageIndex === indicatorIndex) {
|
||||
return styles.pageIndicatorActive;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_onPageSelected: Object => void;
|
||||
|
||||
/**
|
||||
* Updates the index of the currently selected page, based on the native
|
||||
* event received from the {@link ViewPagerAndroid} component.
|
||||
*
|
||||
* @param {Object} event - The native event of the callback.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onPageSelected({ nativeEvent: { position } }) {
|
||||
if (this.state.pageIndex !== position) {
|
||||
this._selectPage(position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single page of the page list.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page to render.
|
||||
* @param {number} index - The index of the rendered page.
|
||||
* @param {boolean} disabled - Renders the page disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPage(page, index, disabled) {
|
||||
return page.component
|
||||
? <View key = { index }>
|
||||
{
|
||||
React.createElement(
|
||||
page.component,
|
||||
{
|
||||
disabled
|
||||
})
|
||||
}
|
||||
</View>
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a page indicator (icon) for the page.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page the indicator is rendered for.
|
||||
* @param {number} index - The index of the page the indicator is rendered
|
||||
* for.
|
||||
* @param {boolean} disabled - Renders the indicator disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPageIndicator(page, index, disabled) {
|
||||
return page.component
|
||||
? <TouchableOpacity
|
||||
disabled = { disabled }
|
||||
key = { index }
|
||||
onPress = { this._onIconPress(index) }
|
||||
style = { styles.pageIndicator } >
|
||||
<View style = { styles.pageIndicator }>
|
||||
<Icon
|
||||
name = { page.icon }
|
||||
style = { [
|
||||
styles.pageIndicatorIcon,
|
||||
this._getIndicatorStyle(index)
|
||||
] } />
|
||||
<Text
|
||||
style = { [
|
||||
styles.pageIndicatorText,
|
||||
this._getIndicatorStyle(index)
|
||||
] }>
|
||||
{ page.title }
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the paged list if multiple pages are to be rendered. This is the
|
||||
* platform dependent part of the component.
|
||||
*
|
||||
* @param {boolean} disabled - True if the rendered lists should be
|
||||
* disabled.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderPagedList(disabled) {
|
||||
const { defaultPage, pages } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.pagedListContainer }>
|
||||
<ViewPagerAndroid
|
||||
initialPage = { defaultPage }
|
||||
onPageSelected = { this._onPageSelected }
|
||||
peekEnabled = { true }
|
||||
ref = { this._setViewPager }
|
||||
style = { styles.pagedList }>
|
||||
{
|
||||
pages.map((page, index) => this._renderPage(
|
||||
page, index, disabled
|
||||
))
|
||||
}
|
||||
</ViewPagerAndroid>
|
||||
<View style = { styles.pageIndicatorContainer }>
|
||||
{
|
||||
pages.map((page, index) => this._renderPageIndicator(
|
||||
page, index, disabled
|
||||
))
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_setViewPager: Object => void;
|
||||
|
||||
/**
|
||||
* Sets the {@link ViewPagerAndroid} instance.
|
||||
*
|
||||
* @param {ViewPagerAndroid} viewPager - The {@code ViewPagerAndroid}
|
||||
* instance.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setViewPager(viewPager) {
|
||||
this._viewPager = viewPager;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(PagedList);
|
||||
@@ -1,100 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { TabBarIOS } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import AbstractPagedList from './AbstractPagedList';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* An iOS specific component to render a paged list.
|
||||
*
|
||||
* @extends PagedList
|
||||
*/
|
||||
class PagedList extends AbstractPagedList {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code PagedList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onTabSelected = this._onTabSelected.bind(this);
|
||||
}
|
||||
|
||||
_onTabSelected: number => Function;
|
||||
|
||||
/**
|
||||
* Constructs a callback to update the selected tab when the bottom bar icon
|
||||
* is pressed.
|
||||
*
|
||||
* @param {number} tabIndex - The selected tab.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onTabSelected(tabIndex) {
|
||||
return () => super._selectPage(tabIndex);
|
||||
}
|
||||
|
||||
_renderPage: (Object, number, boolean) => React$Node
|
||||
|
||||
/**
|
||||
* Renders a single page of the page list.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page to render.
|
||||
* @param {number} index - The index of the rendered page.
|
||||
* @param {boolean} disabled - Renders the page disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPage(page, index, disabled) {
|
||||
const { pageIndex } = this.state;
|
||||
|
||||
return page.component
|
||||
? <TabBarIOS.Item
|
||||
icon = { page.icon }
|
||||
key = { index }
|
||||
onPress = { this._onTabSelected(index) }
|
||||
selected = { pageIndex === index }
|
||||
title = { page.title }>
|
||||
{
|
||||
React.createElement(
|
||||
page.component,
|
||||
{
|
||||
disabled
|
||||
})
|
||||
}
|
||||
</TabBarIOS.Item>
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the paged list if multiple pages are to be rendered. This is the
|
||||
* platform dependent part of the component.
|
||||
*
|
||||
* @param {boolean} disabled - True if the rendered lists should be
|
||||
* disabled.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderPagedList(disabled) {
|
||||
const { pages } = this.props;
|
||||
|
||||
return (
|
||||
<TabBarIOS
|
||||
itemPositioning = 'fill'
|
||||
style = { styles.pagedList }>
|
||||
{
|
||||
pages.map((page, index) => this._renderPage(
|
||||
page, index, disabled
|
||||
))
|
||||
}
|
||||
</TabBarIOS>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(PagedList);
|
||||
286
react/features/base/react/components/native/PagedList.js
Normal file
286
react/features/base/react/components/native/PagedList.js
Normal file
@@ -0,0 +1,286 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Icon } from '../../../font-icons';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link PagedList}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The zero-based index of the page that should be rendered (selected) by
|
||||
* default.
|
||||
*/
|
||||
defaultPage: number,
|
||||
|
||||
/**
|
||||
* Indicates if the list is disabled or not.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Callback to execute on page change.
|
||||
*/
|
||||
onSelectPage: ?Function,
|
||||
|
||||
/**
|
||||
* The pages of the PagedList component to be rendered.
|
||||
*
|
||||
* NOTE 1: An element's {@code component} may be {@code undefined} and then
|
||||
* it won't need to be rendered.
|
||||
*
|
||||
* NOTE 2: There must be at least one page available and enabled.
|
||||
*/
|
||||
pages: Array<{
|
||||
component: ?Object,
|
||||
icon: string | number,
|
||||
title: string
|
||||
}>
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link PagedList}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The currently selected page.
|
||||
*/
|
||||
pageIndex: number
|
||||
};
|
||||
|
||||
/**
|
||||
* A component that renders a paged list.
|
||||
*
|
||||
* @extends PagedList
|
||||
*/
|
||||
class PagedList extends Component<Props, State> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code PagedList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pageIndex: this._validatePageIndex(props.defaultPage)
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._maybeRefreshSelectedPage
|
||||
= this._maybeRefreshSelectedPage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
const pages = this.props.pages.filter(({ component }) => component);
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
styles.pagedListContainer,
|
||||
disabled ? styles.pagedListContainerDisabled : null
|
||||
] }>
|
||||
{
|
||||
pages.length > 1
|
||||
? this._renderPagedList(disabled)
|
||||
: React.createElement(
|
||||
|
||||
// $FlowExpectedError
|
||||
/* type */ pages[0].component,
|
||||
/* props */ {
|
||||
disabled,
|
||||
style: styles.pagedList
|
||||
})
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the style of an indicator.
|
||||
*
|
||||
* @param {number} indicatorIndex - The index of the indicator.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getIndicatorStyle(indicatorIndex) {
|
||||
if (this.state.pageIndex === indicatorIndex) {
|
||||
return styles.pageIndicatorActive;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_maybeRefreshSelectedPage: ?boolean => void;
|
||||
|
||||
/**
|
||||
* Components that this PagedList displays may have a refresh function to
|
||||
* refresh its content when displayed (or based on custom logic). This
|
||||
* function invokes this logic if it's present.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
_maybeRefreshSelectedPage(isInteractive: boolean = true) {
|
||||
const selectedPage = this.props.pages[this.state.pageIndex];
|
||||
let component;
|
||||
|
||||
if (selectedPage && (component = selectedPage.component)) {
|
||||
const { refresh } = component;
|
||||
|
||||
refresh.call(component, this.props.dispatch, isInteractive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected page.
|
||||
*
|
||||
* @param {number} pageIndex - The index of the selected page.
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSelectPage(pageIndex: number) {
|
||||
return () => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
pageIndex = this._validatePageIndex(pageIndex);
|
||||
|
||||
const { onSelectPage } = this.props;
|
||||
|
||||
onSelectPage && onSelectPage(pageIndex);
|
||||
|
||||
this.setState({ pageIndex }, this._maybeRefreshSelectedPage);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single page of the page list.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page to render.
|
||||
* @param {boolean} disabled - Renders the page disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPage(page, disabled) {
|
||||
if (!page.component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.pageContainer }>
|
||||
{
|
||||
React.createElement(
|
||||
page.component,
|
||||
{
|
||||
disabled
|
||||
})
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the paged list if multiple pages are to be rendered.
|
||||
*
|
||||
* @param {boolean} disabled - True if the rendered lists should be
|
||||
* disabled.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderPagedList(disabled) {
|
||||
const { pages } = this.props;
|
||||
const { pageIndex } = this.state;
|
||||
|
||||
return (
|
||||
<View style = { styles.pagedListContainer }>
|
||||
{
|
||||
this._renderPage(pages[pageIndex], disabled)
|
||||
}
|
||||
<View style = { styles.pageIndicatorContainer }>
|
||||
{
|
||||
pages.map((page, index) => this._renderPageIndicator(
|
||||
page, index, disabled
|
||||
))
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a page indicator (icon) for the page.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page the indicator is rendered for.
|
||||
* @param {number} index - The index of the page the indicator is rendered
|
||||
* for.
|
||||
* @param {boolean} disabled - Renders the indicator disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPageIndicator(page, index, disabled) {
|
||||
if (!page.component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
disabled = { disabled }
|
||||
key = { index }
|
||||
onPress = { this._onSelectPage(index) }
|
||||
style = { styles.pageIndicator } >
|
||||
<View style = { styles.pageIndicator }>
|
||||
<Icon
|
||||
name = { page.icon }
|
||||
style = { [
|
||||
styles.pageIndicatorIcon,
|
||||
this._getIndicatorStyle(index)
|
||||
] } />
|
||||
<Text
|
||||
style = { [
|
||||
styles.pageIndicatorText,
|
||||
this._getIndicatorStyle(index)
|
||||
] }>
|
||||
{ page.title }
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the requested page index and returns a safe value.
|
||||
*
|
||||
* @private
|
||||
* @param {number} pageIndex - The requested page index.
|
||||
* @returns {number}
|
||||
*/
|
||||
_validatePageIndex(pageIndex) {
|
||||
// pageIndex may point to a non-existing page if some of the pages are
|
||||
// disabled (their component property is undefined).
|
||||
const maxPageIndex
|
||||
= this.props.pages.filter(({ component }) => component).length - 1;
|
||||
|
||||
return Math.max(0, Math.min(maxPageIndex, pageIndex));
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(PagedList);
|
||||
@@ -73,6 +73,13 @@ const HEADER_STYLES = {
|
||||
*/
|
||||
const PAGED_LIST_STYLES = {
|
||||
|
||||
/**
|
||||
* Outermost container of a page in {@code PagedList}.
|
||||
*/
|
||||
pageContainer: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Style of the page indicator (Android).
|
||||
*/
|
||||
@@ -214,7 +221,7 @@ const SECTION_LIST_STYLES = {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
paddingVertical: 5
|
||||
padding: 5
|
||||
},
|
||||
|
||||
listItemDetails: {
|
||||
@@ -239,7 +246,8 @@ const SECTION_LIST_STYLES = {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
padding: 5
|
||||
paddingVertical: 5,
|
||||
paddingHorizontal: 10
|
||||
},
|
||||
|
||||
listSectionText: {
|
||||
|
||||
@@ -9,13 +9,6 @@ import type { Props } from '../AbstractContainer';
|
||||
* @extends AbstractContainer
|
||||
*/
|
||||
export default class Container<P: Props> extends AbstractContainer<P> {
|
||||
/**
|
||||
* {@code Container} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = AbstractContainer.propTypes;
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
/* @flow */
|
||||
|
||||
import Button from '@atlaskit/button';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link InlineDialogFailure}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Allows to retry the call that previously didn't succeed.
|
||||
*/
|
||||
onRetry: Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Inline dialog that represents a failure and allows a retry.
|
||||
*/
|
||||
class InlineDialogFailure extends Component<*> {
|
||||
/**
|
||||
* {@code InlineDialogFailure}'s property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Allows to retry the call that previously didn't succeed.
|
||||
*/
|
||||
onRetry: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: PropTypes.func
|
||||
};
|
||||
|
||||
class InlineDialogFailure extends Component<Props> {
|
||||
/**
|
||||
* Renders the content of this component.
|
||||
*
|
||||
|
||||
201
react/features/base/react/components/web/MeetingsList.js
Normal file
201
react/features/base/react/components/web/MeetingsList.js
Normal file
@@ -0,0 +1,201 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import {
|
||||
getLocalizedDateFormatter,
|
||||
getLocalizedDurationFormatter
|
||||
} from '../../../i18n';
|
||||
|
||||
import Container from './Container';
|
||||
import Text from './Text';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Indicates if the list is disabled or not.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* Indicates if the URL should be hidden or not.
|
||||
*/
|
||||
hideURL: boolean,
|
||||
|
||||
/**
|
||||
* Function to be invoked when an item is pressed. The item's URL is passed.
|
||||
*/
|
||||
onPress: Function,
|
||||
|
||||
/**
|
||||
* Rendered when the list is empty. Should be a rendered element.
|
||||
*/
|
||||
listEmptyComponent: Object,
|
||||
|
||||
/**
|
||||
* An array of meetings.
|
||||
*/
|
||||
meetings: Array<Object>,
|
||||
|
||||
/**
|
||||
* Defines what happens when an item in the section list is clicked
|
||||
*/
|
||||
onItemClick: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a date string for a given date.
|
||||
*
|
||||
* @param {Object} date - The date.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
function _toDateString(date) {
|
||||
return getLocalizedDateFormatter(date).format('MMM Do, YYYY');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a time (interval) string for a given times.
|
||||
*
|
||||
* @param {Array<Date>} times - Array of times.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
function _toTimeString(times) {
|
||||
if (times && times.length > 0) {
|
||||
return (
|
||||
times
|
||||
.map(time => getLocalizedDateFormatter(time).format('LT'))
|
||||
.join(' - '));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React/Web {@link Component} for displaying a list with
|
||||
* meetings.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class MeetingsList extends Component<Props> {
|
||||
/**
|
||||
* Constructor of the MeetingsList component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onPress = this._onPress.bind(this);
|
||||
this._renderItem = this._renderItem.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content of this component.
|
||||
*
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
render() {
|
||||
const { listEmptyComponent, meetings } = this.props;
|
||||
|
||||
/**
|
||||
* If there are no recent meetings we don't want to display anything
|
||||
*/
|
||||
if (meetings) {
|
||||
return (
|
||||
<Container
|
||||
className = 'meetings-list'>
|
||||
{
|
||||
meetings.length === 0
|
||||
? listEmptyComponent
|
||||
: meetings.map(this._renderItem)
|
||||
}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_onPress: string => Function;
|
||||
|
||||
/**
|
||||
* Returns a function that is used in the onPress callback of the items.
|
||||
*
|
||||
* @param {string} url - The URL of the item to navigate to.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onPress(url) {
|
||||
const { disabled, onPress } = this.props;
|
||||
|
||||
if (!disabled && url && typeof onPress === 'function') {
|
||||
return () => onPress(url);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_renderItem: (Object, number) => React$Node;
|
||||
|
||||
/**
|
||||
* Renders an item for the list.
|
||||
*
|
||||
* @param {Object} meeting - Information about the meeting.
|
||||
* @param {number} index - The index of the item.
|
||||
* @returns {Node}
|
||||
*/
|
||||
_renderItem(meeting, index) {
|
||||
const {
|
||||
date,
|
||||
duration,
|
||||
elementAfter,
|
||||
time,
|
||||
title,
|
||||
url
|
||||
} = meeting;
|
||||
const { hideURL = false } = this.props;
|
||||
const onPress = this._onPress(url);
|
||||
const rootClassName
|
||||
= `item ${
|
||||
onPress ? 'with-click-handler' : 'without-click-handler'}`;
|
||||
|
||||
return (
|
||||
<Container
|
||||
className = { rootClassName }
|
||||
key = { index }
|
||||
onClick = { onPress }>
|
||||
<Container className = 'left-column'>
|
||||
<Text className = 'date'>
|
||||
{ _toDateString(date) }
|
||||
</Text>
|
||||
<Text>
|
||||
{ _toTimeString(time) }
|
||||
</Text>
|
||||
</Container>
|
||||
<Container className = 'right-column'>
|
||||
<Text className = 'title'>
|
||||
{ title }
|
||||
</Text>
|
||||
{
|
||||
hideURL || !url ? null : (
|
||||
<Text>
|
||||
{ url }
|
||||
</Text>)
|
||||
}
|
||||
{
|
||||
typeof duration === 'number' ? (
|
||||
<Text>
|
||||
{ getLocalizedDurationFormatter(duration) }
|
||||
</Text>) : null
|
||||
}
|
||||
</Container>
|
||||
<Container className = 'actions'>
|
||||
{ elementAfter || null }
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import { MultiSelectStateless } from '@atlaskit/multi-select';
|
||||
import AKInlineDialog from '@atlaskit/inline-dialog';
|
||||
import _debounce from 'lodash/debounce';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import InlineDialogFailure from './InlineDialogFailure';
|
||||
@@ -9,118 +10,127 @@ import InlineDialogFailure from './InlineDialogFailure';
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* A MultiSelect that is also auto-completing.
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link MultiSelectAutocomplete}.
|
||||
*/
|
||||
class MultiSelectAutocomplete extends Component {
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* {@code MultiSelectAutocomplete} component's property types.
|
||||
*
|
||||
* @static
|
||||
* The default value of the selected item.
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The default value of the selected item.
|
||||
*/
|
||||
defaultValue: PropTypes.array,
|
||||
defaultValue: Array<Object>,
|
||||
|
||||
/**
|
||||
* Optional footer to show as a last element in the results.
|
||||
* Should be of type {content: <some content>}
|
||||
*/
|
||||
footer: PropTypes.object,
|
||||
/**
|
||||
* Optional footer to show as a last element in the results.
|
||||
* Should be of type {content: <some content>}
|
||||
*/
|
||||
footer: Object,
|
||||
|
||||
/**
|
||||
* Indicates if the component is disabled.
|
||||
*/
|
||||
isDisabled: PropTypes.bool,
|
||||
/**
|
||||
* Indicates if the component is disabled.
|
||||
*/
|
||||
isDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Text to display while a query is executing.
|
||||
*/
|
||||
loadingMessage: PropTypes.string,
|
||||
/**
|
||||
* Text to display while a query is executing.
|
||||
*/
|
||||
loadingMessage: string,
|
||||
|
||||
/**
|
||||
* The text to show when no matches are found.
|
||||
*/
|
||||
noMatchesFound: PropTypes.string,
|
||||
/**
|
||||
* The text to show when no matches are found.
|
||||
*/
|
||||
noMatchesFound: string,
|
||||
|
||||
/**
|
||||
* The function called immediately before a selection has been actually
|
||||
* selected. Provides an opportunity to do any formatting.
|
||||
*/
|
||||
onItemSelected: PropTypes.func,
|
||||
/**
|
||||
* The function called immediately before a selection has been actually
|
||||
* selected. Provides an opportunity to do any formatting.
|
||||
*/
|
||||
onItemSelected: Function,
|
||||
|
||||
/**
|
||||
* The function called when the selection changes.
|
||||
*/
|
||||
onSelectionChange: PropTypes.func,
|
||||
/**
|
||||
* The function called when the selection changes.
|
||||
*/
|
||||
onSelectionChange: Function,
|
||||
|
||||
/**
|
||||
* The placeholder text of the input component.
|
||||
*/
|
||||
placeholder: PropTypes.string,
|
||||
/**
|
||||
* The placeholder text of the input component.
|
||||
*/
|
||||
placeholder: string,
|
||||
|
||||
/**
|
||||
* The service providing the search.
|
||||
*/
|
||||
resourceClient: PropTypes.shape({
|
||||
makeQuery: PropTypes.func,
|
||||
parseResults: PropTypes.func
|
||||
}).isRequired,
|
||||
/**
|
||||
* The service providing the search.
|
||||
*/
|
||||
resourceClient: { makeQuery: Function, parseResults: Function },
|
||||
|
||||
/**
|
||||
* Indicates if the component should fit the container.
|
||||
*/
|
||||
shouldFitContainer: PropTypes.bool,
|
||||
/**
|
||||
* Indicates if the component should fit the container.
|
||||
*/
|
||||
shouldFitContainer: boolean,
|
||||
|
||||
/**
|
||||
* Indicates if we should focus.
|
||||
*/
|
||||
shouldFocus: PropTypes.bool
|
||||
};
|
||||
/**
|
||||
* Indicates if we should focus.
|
||||
*/
|
||||
shouldFocus: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of
|
||||
* {@link MultiSelectAutocomplete}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* Indicates if the dropdown is open.
|
||||
*/
|
||||
isOpen: boolean,
|
||||
|
||||
/**
|
||||
* The text that filters the query result of the search.
|
||||
*/
|
||||
filterValue: string,
|
||||
|
||||
/**
|
||||
* Indicates if the component is currently loading results.
|
||||
*/
|
||||
loading: boolean,
|
||||
|
||||
/**
|
||||
* Indicates if there was an error.
|
||||
*/
|
||||
error: boolean,
|
||||
|
||||
/**
|
||||
* The list of result items.
|
||||
*/
|
||||
items: Array<Object>,
|
||||
|
||||
/**
|
||||
* The list of selected items.
|
||||
*/
|
||||
selectedItems: Array<Object>
|
||||
};
|
||||
|
||||
/**
|
||||
* A MultiSelect that is also auto-completing.
|
||||
*/
|
||||
class MultiSelectAutocomplete extends Component<Props, State> {
|
||||
/**
|
||||
* Initializes a new {@code MultiSelectAutocomplete} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const defaultValue = this.props.defaultValue || [];
|
||||
|
||||
this.state = {
|
||||
/**
|
||||
* Indicates if the dropdown is open.
|
||||
*/
|
||||
isOpen: false,
|
||||
|
||||
/**
|
||||
* The text that filters the query result of the search.
|
||||
*/
|
||||
filterValue: '',
|
||||
|
||||
/**
|
||||
* Indicates if the component is currently loading results.
|
||||
*/
|
||||
loading: false,
|
||||
|
||||
|
||||
/**
|
||||
* Indicates if there was an error.
|
||||
*/
|
||||
error: false,
|
||||
|
||||
/**
|
||||
* The list of result items.
|
||||
*/
|
||||
items: [],
|
||||
|
||||
/**
|
||||
* The list of selected items.
|
||||
*/
|
||||
selectedItems: [ ...defaultValue ]
|
||||
};
|
||||
|
||||
@@ -137,7 +147,7 @@ class MultiSelectAutocomplete extends Component {
|
||||
* having been selected.
|
||||
* @returns {void}
|
||||
*/
|
||||
setSelectedItems(selectedItems = []) {
|
||||
setSelectedItems(selectedItems: Array<Object> = []) {
|
||||
this.setState({ selectedItems });
|
||||
}
|
||||
|
||||
@@ -177,6 +187,8 @@ class MultiSelectAutocomplete extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
_onFilterChange: (string) => void;
|
||||
|
||||
/**
|
||||
* Sets the state and sends a query on filter change.
|
||||
*
|
||||
@@ -198,6 +210,8 @@ class MultiSelectAutocomplete extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
_onRetry: () => void;
|
||||
|
||||
/**
|
||||
* Retries the query on retry.
|
||||
*
|
||||
@@ -208,6 +222,8 @@ class MultiSelectAutocomplete extends Component {
|
||||
this._sendQuery(this.state.filterValue);
|
||||
}
|
||||
|
||||
_onSelectionChange: (Object) => void;
|
||||
|
||||
/**
|
||||
* Updates the selected items when a selection event occurs.
|
||||
*
|
||||
@@ -258,6 +274,8 @@ class MultiSelectAutocomplete extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
_sendQuery: (string) => void;
|
||||
|
||||
/**
|
||||
* Sends a query to the resourceClient.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* @flow */
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
@@ -17,32 +16,71 @@ const _RIGHT_WATERMARK_STYLE = {
|
||||
backgroundImage: 'url(images/rightwatermark.png)'
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Watermarks}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether or not the current user is logged in through a JWT.
|
||||
*/
|
||||
_isGuest: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link Watermarks}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The url to open when clicking the brand watermark.
|
||||
*/
|
||||
brandWatermarkLink: string,
|
||||
|
||||
/**
|
||||
* The url to open when clicking the Jitsi watermark.
|
||||
*/
|
||||
jitsiWatermarkLink: string,
|
||||
|
||||
/**
|
||||
* Whether or not the brand watermark should be displayed.
|
||||
*/
|
||||
showBrandWatermark: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the Jitsi watermark should be displayed.
|
||||
*/
|
||||
showJitsiWatermark: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the Jitsi watermark should be displayed for users not
|
||||
* logged in through a JWT.
|
||||
*/
|
||||
showJitsiWatermarkForGuests: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the show the "powered by Jitsi.org" link.
|
||||
*/
|
||||
showPoweredBy: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* A Web Component which renders watermarks such as Jits, brand, powered by,
|
||||
* etc.
|
||||
*/
|
||||
class Watermarks extends Component<*, *> {
|
||||
static propTypes = {
|
||||
_isGuest: PropTypes.bool,
|
||||
t: PropTypes.func
|
||||
};
|
||||
|
||||
state = {
|
||||
brandWatermarkLink: String,
|
||||
jitsiWatermarkLink: String,
|
||||
showBrandWatermark: Boolean,
|
||||
showJitsiWatermark: Boolean,
|
||||
showJitsiWatermarkForGuests: Boolean,
|
||||
showPoweredBy: Boolean
|
||||
};
|
||||
|
||||
class Watermarks extends Component<Props, State> {
|
||||
/**
|
||||
* Initializes a new Watermarks instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Object) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
let showBrandWatermark;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default as Container } from './Container';
|
||||
export { default as LoadingIndicator } from './LoadingIndicator';
|
||||
export { default as MeetingsList } from './MeetingsList';
|
||||
export { default as MultiSelectAutocomplete } from './MultiSelectAutocomplete';
|
||||
export { default as NavigateSectionListEmptyComponent } from
|
||||
'./NavigateSectionListEmptyComponent';
|
||||
|
||||
@@ -113,7 +113,7 @@ function _set(
|
||||
|
||||
/**
|
||||
* Returns redux state from the specified {@code stateful} which is presumed to
|
||||
* be related to the redux state (e.g. the redux store, the redux
|
||||
* be related to the redux state (e.g. The redux store, the redux
|
||||
* {@code getState} function).
|
||||
*
|
||||
* @param {Function|Object} stateful - The entity such as the redux store or the
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
// @flow
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from '../constants';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AspectRatioAware}.
|
||||
*/
|
||||
type Props = {
|
||||
aspectRatio: ASPECT_RATIO_NARROW | ASPECT_RATIO_WIDE
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether a specific React {@code Component} decorated into an
|
||||
* {@link AspectRatioAware} has {@link ASPECT_RATIO_NARROW} as the value of its
|
||||
@@ -34,20 +40,7 @@ export function makeAspectRatioAware(
|
||||
/**
|
||||
* Renders {@code WrappedComponent} with the React prop {@code aspectRatio}.
|
||||
*/
|
||||
class AspectRatioAware extends Component<*> {
|
||||
/**
|
||||
* Properties of the aspect ratio aware wrapper.
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Either {@link ASPECT_RATIO_NARROW} or {@link ASPECT_RATIO_WIDE}.
|
||||
*/
|
||||
aspectRatio: PropTypes.oneOf([
|
||||
ASPECT_RATIO_NARROW,
|
||||
ASPECT_RATIO_WIDE
|
||||
])
|
||||
}
|
||||
|
||||
class AspectRatioAware extends Component<Props> {
|
||||
/**
|
||||
* Implement's React render method to wrap the nested component.
|
||||
*
|
||||
|
||||
@@ -57,7 +57,7 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
* Retrieves the legacy profile values regardless of it's being in pre or
|
||||
* post-flattening format.
|
||||
*
|
||||
* FIXME: Let's remove this after a predefined time (e.g. by July 2018) to avoid
|
||||
* FIXME: Let's remove this after a predefined time (e.g. By July 2018) to avoid
|
||||
* garbage in the source.
|
||||
*
|
||||
* @private
|
||||
|
||||
@@ -73,7 +73,7 @@ export default class Storage {
|
||||
* Returns the value associated with a specific key in this {@code Storage}
|
||||
* in an async manner. The method is required for the cases where we need
|
||||
* the stored data but we're not sure yet whether this {@code Storage} is
|
||||
* already initialized (e.g. on app start).
|
||||
* already initialized (e.g. On app start).
|
||||
*
|
||||
* @param {string} key - The name of the key to retrieve the value of.
|
||||
* @returns {Promise}
|
||||
|
||||
@@ -26,6 +26,7 @@ export const ColorPalette = {
|
||||
lightGrey: '#AAAAAA',
|
||||
lighterGrey: '#EEEEEE',
|
||||
red: '#D00000',
|
||||
transparent: 'rgba(0, 0, 0, 0)',
|
||||
white: 'white',
|
||||
|
||||
/**
|
||||
|
||||
@@ -418,7 +418,7 @@ function _addTracks(tracks) {
|
||||
* @returns {Promise} - A {@code Promise} resolved once all
|
||||
* {@code gumProcess.cancel()} {@code Promise}s are settled because all we care
|
||||
* about here is to be sure that the {@code getUserMedia} callbacks have
|
||||
* completed (i.e. returned from the native side).
|
||||
* completed (i.e. Returned from the native side).
|
||||
*/
|
||||
function _cancelGUMProcesses(getState) {
|
||||
const logError
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import Button from '@atlaskit/button';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Tooltip from '@atlaskit/tooltip';
|
||||
@@ -65,12 +64,11 @@ class AddMeetingUrlButton extends Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<Tooltip content = { this.props.t('calendarSync.addMeetingURL') }>
|
||||
<Button
|
||||
appearance = 'primary'
|
||||
onClick = { this._onClick }
|
||||
type = 'button'>
|
||||
<div
|
||||
className = 'button add-button'
|
||||
onClick = { this._onClick }>
|
||||
<i className = { 'icon-add' } />
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -92,4 +90,3 @@ class AddMeetingUrlButton extends Component<Props> {
|
||||
}
|
||||
|
||||
export default translate(connect()(AddMeetingUrlButton));
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openSettings } from '../../mobile/permissions';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { AbstractPage } from '../../base/react';
|
||||
|
||||
import { refreshCalendar } from '../actions';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
import styles from './styles';
|
||||
|
||||
import BaseCalendarList from './BaseCalendarList';
|
||||
import CalendarListContent from './CalendarListContent';
|
||||
|
||||
/**
|
||||
* The tyoe of the React {@code Component} props of {@link CalendarList}.
|
||||
@@ -36,7 +38,7 @@ type Props = {
|
||||
/**
|
||||
* Component to display a list of events from the (mobile) user's calendar.
|
||||
*/
|
||||
class CalendarList extends Component<Props> {
|
||||
class CalendarList extends AbstractPage<Props> {
|
||||
/**
|
||||
* Initializes a new {@code CalendarList} instance.
|
||||
*
|
||||
@@ -50,6 +52,21 @@ class CalendarList extends Component<Props> {
|
||||
= this._getRenderListEmptyComponent.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public API method for {@code Component}s rendered in
|
||||
* {@link AbstractPagedList}. When invoked, refreshes the calendar entries
|
||||
* in the app.
|
||||
*
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @public
|
||||
* @returns {void}
|
||||
*/
|
||||
static refresh(dispatch, isInteractive) {
|
||||
dispatch(refreshCalendar(false, isInteractive));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render}.
|
||||
*
|
||||
@@ -59,10 +76,10 @@ class CalendarList extends Component<Props> {
|
||||
const { disabled } = this.props;
|
||||
|
||||
return (
|
||||
BaseCalendarList
|
||||
? <BaseCalendarList
|
||||
CalendarListContent
|
||||
? <CalendarListContent
|
||||
disabled = { disabled }
|
||||
renderListEmptyComponent
|
||||
listEmptyComponent
|
||||
= { this._getRenderListEmptyComponent() } />
|
||||
: null
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import Button from '@atlaskit/button';
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { AbstractPage } from '../../base/react';
|
||||
import { openSettingsDialog, SETTINGS_TABS } from '../../settings';
|
||||
import {
|
||||
createCalendarClickedEvent,
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { refreshCalendar } from '../actions';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
import BaseCalendarList from './BaseCalendarList';
|
||||
import CalendarListContent from './CalendarListContent';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
@@ -53,7 +53,7 @@ type Props = {
|
||||
/**
|
||||
* Component to display a list of events from the user's calendar.
|
||||
*/
|
||||
class CalendarList extends Component<Props> {
|
||||
class CalendarList extends AbstractPage<Props> {
|
||||
/**
|
||||
* Initializes a new {@code CalendarList} instance.
|
||||
*
|
||||
@@ -78,10 +78,10 @@ class CalendarList extends Component<Props> {
|
||||
const { disabled } = this.props;
|
||||
|
||||
return (
|
||||
BaseCalendarList
|
||||
? <BaseCalendarList
|
||||
CalendarListContent
|
||||
? <CalendarListContent
|
||||
disabled = { disabled }
|
||||
renderListEmptyComponent
|
||||
listEmptyComponent
|
||||
= { this._getRenderListEmptyComponent() } />
|
||||
: null
|
||||
);
|
||||
@@ -101,21 +101,18 @@ class CalendarList extends Component<Props> {
|
||||
|
||||
if (_hasIntegrationSelected && _hasLoadedEvents) {
|
||||
return (
|
||||
<div className = 'navigate-section-list-empty'>
|
||||
<div className = 'meetings-list-empty'>
|
||||
<div>{ t('calendarSync.noEvents') }</div>
|
||||
<Button
|
||||
appearance = 'primary'
|
||||
className = 'calendar-button'
|
||||
id = 'connect_calendar_button'
|
||||
onClick = { this._onRefreshEvents }
|
||||
type = 'button'>
|
||||
<div
|
||||
className = 'button'
|
||||
onClick = { this._onRefreshEvents }>
|
||||
{ t('calendarSync.refresh') }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (_hasIntegrationSelected && !_hasLoadedEvents) {
|
||||
return (
|
||||
<div className = 'navigate-section-list-empty'>
|
||||
<div className = 'meetings-list-empty'>
|
||||
<Spinner
|
||||
invertColor = { true }
|
||||
isCompleting = { false }
|
||||
@@ -125,20 +122,17 @@ class CalendarList extends Component<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = 'navigate-section-list-empty'>
|
||||
<p className = 'header-text-description'>
|
||||
<div className = 'meetings-list-empty'>
|
||||
<p className = 'description'>
|
||||
{ t('welcomepage.connectCalendarText', {
|
||||
app: interfaceConfig.APP_NAME
|
||||
}) }
|
||||
</p>
|
||||
<Button
|
||||
appearance = 'primary'
|
||||
className = 'calendar-button'
|
||||
id = 'connect_calendar_button'
|
||||
onClick = { this._onOpenSettings }
|
||||
type = 'button'>
|
||||
<div
|
||||
className = 'button'
|
||||
onClick = { this._onOpenSettings }>
|
||||
{ t('welcomepage.connectCalendarButton') }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,15 +13,12 @@ import { getLocalizedDateFormatter, translate } from '../../base/i18n';
|
||||
import { NavigateSectionList } from '../../base/react';
|
||||
|
||||
import { refreshCalendar, openUpdateCalendarEventDialog } from '../actions';
|
||||
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
import AddMeetingUrlButton from './AddMeetingUrlButton';
|
||||
import JoinButton from './JoinButton';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link BaseCalendarList}.
|
||||
* {@link CalendarListContent}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
@@ -43,7 +40,7 @@ type Props = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
renderListEmptyComponent: Function,
|
||||
listEmptyComponent: React$Node,
|
||||
|
||||
/**
|
||||
* The translate function.
|
||||
@@ -54,7 +51,7 @@ type Props = {
|
||||
/**
|
||||
* Component to display a list of events from a connected calendar.
|
||||
*/
|
||||
class BaseCalendarList extends Component<Props> {
|
||||
class CalendarListContent extends Component<Props> {
|
||||
/**
|
||||
* Default values for the component's props.
|
||||
*/
|
||||
@@ -63,26 +60,7 @@ class BaseCalendarList extends Component<Props> {
|
||||
};
|
||||
|
||||
/**
|
||||
* Public API method for {@code Component}s rendered in
|
||||
* {@link AbstractPagedList}. When invoked, refreshes the calendar entries
|
||||
* in the app.
|
||||
*
|
||||
* Note: It is a static method as the {@code Component} may not be
|
||||
* initialized yet when the UI invokes refresh (e.g. {@link TabBarIOS} tab
|
||||
* change).
|
||||
*
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @public
|
||||
* @returns {void}
|
||||
*/
|
||||
static refresh(dispatch, isInteractive) {
|
||||
dispatch(refreshCalendar(false, isInteractive));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code BaseCalendarList} instance.
|
||||
* Initializes a new {@code CalendarListContent} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
@@ -90,7 +68,6 @@ class BaseCalendarList extends Component<Props> {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onJoinPress = this._onJoinPress.bind(this);
|
||||
this._onPress = this._onPress.bind(this);
|
||||
this._onRefresh = this._onRefresh.bind(this);
|
||||
this._onSecondaryAction = this._onSecondaryAction.bind(this);
|
||||
@@ -117,7 +94,7 @@ class BaseCalendarList extends Component<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled, renderListEmptyComponent } = this.props;
|
||||
const { disabled, listEmptyComponent } = this.props;
|
||||
|
||||
return (
|
||||
<NavigateSectionList
|
||||
@@ -126,27 +103,11 @@ class BaseCalendarList extends Component<Props> {
|
||||
onRefresh = { this._onRefresh }
|
||||
onSecondaryAction = { this._onSecondaryAction }
|
||||
renderListEmptyComponent
|
||||
= { renderListEmptyComponent }
|
||||
= { listEmptyComponent }
|
||||
sections = { this._toDisplayableList() } />
|
||||
);
|
||||
}
|
||||
|
||||
_onJoinPress: (Object, string) => Function;
|
||||
|
||||
/**
|
||||
* Handles the list's navigate action.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} event - The click event.
|
||||
* @param {string} url - The url string to navigate to.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onJoinPress(event, url) {
|
||||
event.stopPropagation();
|
||||
|
||||
this._onPress(url, 'calendar.meeting.join');
|
||||
}
|
||||
|
||||
_onPress: (string, string) => Function;
|
||||
|
||||
/**
|
||||
@@ -154,7 +115,7 @@ class BaseCalendarList extends Component<Props> {
|
||||
*
|
||||
* @private
|
||||
* @param {string} url - The url string to navigate to.
|
||||
* @param {string} analyticsEventName - Тhe name of the analytics event.
|
||||
* @param {string} analyticsEventName - Тhe name of the analytics event
|
||||
* associated with this action.
|
||||
* @returns {void}
|
||||
*/
|
||||
@@ -217,13 +178,6 @@ class BaseCalendarList extends Component<Props> {
|
||||
*/
|
||||
_toDisplayableItem(event) {
|
||||
return {
|
||||
elementAfter: event.url
|
||||
? <JoinButton
|
||||
onPress = { this._onJoinPress }
|
||||
url = { event.url } />
|
||||
: (<AddMeetingUrlButton
|
||||
calendarId = { event.calendarId }
|
||||
eventId = { event.id } />),
|
||||
id: event.id,
|
||||
key: `${event.id}-${event.startDate}`,
|
||||
lines: [
|
||||
@@ -318,5 +272,5 @@ function _mapStateToProps(state: Object) {
|
||||
}
|
||||
|
||||
export default isCalendarEnabled()
|
||||
? translate(connect(_mapStateToProps)(BaseCalendarList))
|
||||
? translate(connect(_mapStateToProps)(CalendarListContent))
|
||||
: undefined;
|
||||
@@ -0,0 +1,177 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { appNavigate } from '../../app';
|
||||
import {
|
||||
createCalendarClickedEvent,
|
||||
createCalendarSelectedEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { MeetingsList } from '../../base/react';
|
||||
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
import AddMeetingUrlButton from './AddMeetingUrlButton';
|
||||
import JoinButton from './JoinButton';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link CalendarListContent}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The calendar event list.
|
||||
*/
|
||||
_eventList: Array<Object>,
|
||||
|
||||
/**
|
||||
* Indicates if the list is disabled or not.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
listEmptyComponent: React$Node,
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to display a list of events from a connected calendar.
|
||||
*/
|
||||
class CalendarListContent extends Component<Props> {
|
||||
/**
|
||||
* Default values for the component's props.
|
||||
*/
|
||||
static defaultProps = {
|
||||
_eventList: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code CalendarListContent} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onJoinPress = this._onJoinPress.bind(this);
|
||||
this._onPress = this._onPress.bind(this);
|
||||
this._toDisplayableItem = this._toDisplayableItem.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount()}. Invoked
|
||||
* immediately after this component is mounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
sendAnalytics(createCalendarSelectedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled, listEmptyComponent } = this.props;
|
||||
const { _eventList = [] } = this.props;
|
||||
const meetings = _eventList.map(this._toDisplayableItem);
|
||||
|
||||
return (
|
||||
<MeetingsList
|
||||
disabled = { disabled }
|
||||
listEmptyComponent = { listEmptyComponent }
|
||||
meetings = { meetings }
|
||||
onPress = { this._onPress } />
|
||||
);
|
||||
}
|
||||
|
||||
_onJoinPress: (Object, string) => Function;
|
||||
|
||||
/**
|
||||
* Handles the list's navigate action.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} event - The click event.
|
||||
* @param {string} url - The url string to navigate to.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onJoinPress(event, url) {
|
||||
event.stopPropagation();
|
||||
|
||||
this._onPress(url, 'calendar.meeting.join');
|
||||
}
|
||||
|
||||
_onPress: (string, string) => Function;
|
||||
|
||||
/**
|
||||
* Handles the list's navigate action.
|
||||
*
|
||||
* @private
|
||||
* @param {string} url - The url string to navigate to.
|
||||
* @param {string} analyticsEventName - Тhe name of the analytics event
|
||||
* associated with this action.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onPress(url, analyticsEventName = 'calendar.meeting.tile') {
|
||||
sendAnalytics(createCalendarClickedEvent(analyticsEventName));
|
||||
|
||||
this.props.dispatch(appNavigate(url));
|
||||
}
|
||||
|
||||
_toDisplayableItem: Object => Object;
|
||||
|
||||
/**
|
||||
* Creates a displayable object from an event.
|
||||
*
|
||||
* @param {Object} event - The calendar event.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_toDisplayableItem(event) {
|
||||
return {
|
||||
elementAfter: event.url
|
||||
? <JoinButton
|
||||
onPress = { this._onJoinPress }
|
||||
url = { event.url } />
|
||||
: (<AddMeetingUrlButton
|
||||
calendarId = { event.calendarId }
|
||||
eventId = { event.id } />),
|
||||
date: event.startDate,
|
||||
time: [ event.startDate, event.endDate ],
|
||||
description: event.url,
|
||||
title: event.title,
|
||||
url: event.url
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps redux state to component props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {{
|
||||
* _eventList: Array<Object>
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
return {
|
||||
_eventList: state['features/calendar-sync'].events
|
||||
};
|
||||
}
|
||||
|
||||
export default isCalendarEnabled()
|
||||
? connect(_mapStateToProps)(CalendarListContent)
|
||||
: undefined;
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import Button from '@atlaskit/button';
|
||||
import React, { Component } from 'react';
|
||||
import Tooltip from '@atlaskit/tooltip';
|
||||
|
||||
@@ -58,13 +57,11 @@ class JoinButton extends Component<Props> {
|
||||
return (
|
||||
<Tooltip
|
||||
content = { t('calendarSync.joinTooltip') }>
|
||||
<Button
|
||||
appearance = 'primary'
|
||||
className = 'join-button'
|
||||
onClick = { this._onClick }
|
||||
type = 'button'>
|
||||
<div
|
||||
className = 'button join-button'
|
||||
onClick = { this._onClick }>
|
||||
{ t('calendarSync.join') }
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -84,4 +81,3 @@ class JoinButton extends Component<Props> {
|
||||
}
|
||||
|
||||
export default translate(JoinButton);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ function _isDisplayableCalendarEntry(entry) {
|
||||
/**
|
||||
* Updates the calendar entries in redux when new list is received. The feature
|
||||
* calendar-sync doesn't display all calendar events, it displays unique
|
||||
* title, URL, and start time tuples i.e. it doesn't display subsequent
|
||||
* title, URL, and start time tuples, and it doesn't display subsequent
|
||||
* occurrences of recurring events, and the repetitions of events coming from
|
||||
* multiple calendars.
|
||||
*
|
||||
|
||||
@@ -62,6 +62,7 @@ isCalendarEnabled()
|
||||
// knownDomains. At this point, it should have already been
|
||||
// translated into the new state format (namely, base/known-domains)
|
||||
// and the app no longer needs it.
|
||||
// $FlowFixMe
|
||||
if (typeof state.knownDomains !== 'undefined') {
|
||||
return set(state, 'knownDomains', undefined);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
import { regexes } from './smileys';
|
||||
|
||||
/* eslint-disable no-useless-escape, max-len */
|
||||
const replacePatterns = {
|
||||
|
||||
// URLs starting with http://, https://, or ftp://
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>':
|
||||
/(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim,
|
||||
|
||||
// URLs starting with "www." (without // before it, or it'd re-link the ones done above).
|
||||
'$1<a href="https://$2" target="_blank" rel="noopener noreferrer">$2</a>':
|
||||
/(^|[^\/])(www\.[\S]+(\b|$))/gim,
|
||||
|
||||
// Change email addresses to mailto: links.
|
||||
'<a href="mailto:$1">$1</a>':
|
||||
/(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim
|
||||
};
|
||||
/* eslint-enable no-useless-escape, max-len */
|
||||
|
||||
/**
|
||||
* Processes links and smileys in "body".
|
||||
*
|
||||
@@ -8,39 +25,38 @@ import { regexes } from './smileys';
|
||||
*/
|
||||
export function processReplacements(body) {
|
||||
// make links clickable + add smileys
|
||||
return smilify(linkify(body));
|
||||
}
|
||||
|
||||
// non of the patterns we search contains a space, that's why we tokenize it
|
||||
// and after processing each token we join it again with the results
|
||||
// making sure we do only one replacement for a token
|
||||
const tokens = body.split(' ');
|
||||
const resultText = [];
|
||||
|
||||
/**
|
||||
* Finds and replaces all links in the links in "body" with an href tag.
|
||||
*
|
||||
* @param {string} inputText - The message body.
|
||||
* @returns {string} The text replaced with HTML tags for links.
|
||||
*/
|
||||
function linkify(inputText) {
|
||||
let replacedText;
|
||||
for (const token of tokens) {
|
||||
let replacedText;
|
||||
const tokenLength = token.length;
|
||||
|
||||
/* eslint-disable no-useless-escape, max-len */
|
||||
for (const newString in replacePatterns) { // eslint-disable-line guard-for-in, max-len
|
||||
const replacePattern = replacePatterns[newString];
|
||||
|
||||
// URLs starting with http://, https://, or ftp://
|
||||
const replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
|
||||
replacedText = token.replace(replacePattern, newString);
|
||||
|
||||
replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');
|
||||
// replacement was done, stop doing any other replacements
|
||||
if (replacedText.length > tokenLength) {
|
||||
break;
|
||||
}
|
||||
replacedText = null;
|
||||
}
|
||||
|
||||
// URLs starting with "www." (without // before it, or it'd re-link the ones done above).
|
||||
const replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
|
||||
// no replacement was done, then just check for smiley
|
||||
if (!replacedText) {
|
||||
replacedText = smilify(token);
|
||||
}
|
||||
|
||||
replacedText = replacedText.replace(replacePattern2, '$1<a href="https://$2" target="_blank" rel="noopener noreferrer">$2</a>');
|
||||
resultText.push(replacedText);
|
||||
}
|
||||
|
||||
// Change email addresses to mailto: links.
|
||||
const replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
|
||||
|
||||
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
|
||||
|
||||
/* eslint-enable no-useless-escape */
|
||||
|
||||
return replacedText;
|
||||
return resultText.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user