mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-04 05:42:28 +00:00
Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f8fd1019b | ||
|
|
f14095ecfc | ||
|
|
6947926494 | ||
|
|
784d94b30f | ||
|
|
1d7e0845aa | ||
|
|
f64c13d4b7 | ||
|
|
4e36127dc7 | ||
|
|
8b1aff5512 | ||
|
|
d7103c1c4c | ||
|
|
9cbcbf6e26 | ||
|
|
2c4a3b0f60 | ||
|
|
d82f172db8 | ||
|
|
169c47ac7f | ||
|
|
2af76ebcf9 | ||
|
|
6931b8f2fb | ||
|
|
adec8e6438 | ||
|
|
382c548cf9 | ||
|
|
ff9820a61b | ||
|
|
1ab107b238 | ||
|
|
2861d8d24e | ||
|
|
172342cac3 | ||
|
|
f8941c846a | ||
|
|
8daa13cd99 | ||
|
|
a86ca3f41c | ||
|
|
c029663b77 | ||
|
|
66bf5cf966 | ||
|
|
63c165ee8b | ||
|
|
374e3ccf2c | ||
|
|
09482f053b | ||
|
|
1e69dc93d6 | ||
|
|
008645568c | ||
|
|
13e0e18f37 | ||
|
|
8c0bb377ba | ||
|
|
fc25125667 | ||
|
|
5b7b373e21 | ||
|
|
8e42a7b034 | ||
|
|
4bd94fc94c | ||
|
|
41e1c3a2e2 | ||
|
|
6586be9a8e | ||
|
|
56d8210e35 | ||
|
|
f1ab160c62 | ||
|
|
eac74aa0b7 | ||
|
|
e30d141cec | ||
|
|
1513e1f3b3 | ||
|
|
0539e8f2df | ||
|
|
eb19f94598 | ||
|
|
fd44721bac | ||
|
|
219b93a3c9 | ||
|
|
2a55548b84 | ||
|
|
8f142d5ec4 | ||
|
|
5cf16a20d3 | ||
|
|
be78ab5317 | ||
|
|
907cb013a8 | ||
|
|
8828525511 | ||
|
|
c03e66954d | ||
|
|
157800c494 | ||
|
|
3285d647e6 | ||
|
|
78ff0f7864 | ||
|
|
76c56339c4 | ||
|
|
95927c4804 | ||
|
|
6c5482fb9d | ||
|
|
4f157b71f3 | ||
|
|
5270da4c14 | ||
|
|
fe473bf426 | ||
|
|
abee3331aa | ||
|
|
a5e4fb000f | ||
|
|
27a1be1e1c | ||
|
|
4d942440db | ||
|
|
461540d874 | ||
|
|
3f99e80358 | ||
|
|
2ce3c2d459 | ||
|
|
8363f3cfeb | ||
|
|
f8537dde6b | ||
|
|
f34686afee | ||
|
|
ea1aef0703 | ||
|
|
8b2ce21e1a | ||
|
|
c377219013 | ||
|
|
10c8d380b7 | ||
|
|
7e9a64d7c1 | ||
|
|
a783939f12 | ||
|
|
6883ee0141 | ||
|
|
1eee20dd5a | ||
|
|
7d86e3f8e7 | ||
|
|
60fde5efb8 | ||
|
|
79b7e1641d | ||
|
|
decbcefbd4 | ||
|
|
cb70c084b3 | ||
|
|
0afe72d42b | ||
|
|
84e5e657a0 | ||
|
|
d8ad39ed3f | ||
|
|
16c2bc2d15 | ||
|
|
01e0dfe58a | ||
|
|
38c8a41634 | ||
|
|
b2efcadeb8 | ||
|
|
b73b51f1f4 | ||
|
|
0cd32c8155 | ||
|
|
45adf3e26a | ||
|
|
58d1b69148 | ||
|
|
1f0dc6fcd8 | ||
|
|
95e00405b6 | ||
|
|
968b279b37 | ||
|
|
0456df239f | ||
|
|
2f23f8e400 | ||
|
|
2412239206 | ||
|
|
cc6e04ddf8 | ||
|
|
6f11bbc400 | ||
|
|
7c08116dc2 | ||
|
|
f8717a7135 | ||
|
|
1b85442dba | ||
|
|
ea46cbc479 | ||
|
|
b76ab305e3 | ||
|
|
358ce0799e | ||
|
|
e7223c49ef | ||
|
|
02a31746fb | ||
|
|
40154b1feb | ||
|
|
4c49e3bec0 | ||
|
|
0a086fa3f7 | ||
|
|
b353b8fffb | ||
|
|
a3c00021de | ||
|
|
1e0a3ceb74 | ||
|
|
8492aad7d6 | ||
|
|
7ad9fa8392 | ||
|
|
6916252ce1 | ||
|
|
b4eae56eed | ||
|
|
eb69fb69cb | ||
|
|
2b7cdbc6a8 | ||
|
|
f3a90f048a | ||
|
|
8bf69d30b7 | ||
|
|
45078fe6b2 | ||
|
|
4783b22018 | ||
|
|
d93782af8a | ||
|
|
962df14382 | ||
|
|
01db70fd3d | ||
|
|
6cc8800016 | ||
|
|
e5596c3cd5 | ||
|
|
1b91e0bc2f | ||
|
|
b258e0d397 | ||
|
|
83f47c2df1 | ||
|
|
a39da15c94 | ||
|
|
fd787abf85 | ||
|
|
7822155e5e |
4
Makefile
4
Makefile
@@ -9,6 +9,7 @@ STYLES_BUNDLE = css/all.bundle.css
|
||||
STYLES_DESTINATION = css/all.css
|
||||
STYLES_MAIN = css/main.scss
|
||||
WEBPACK = ./node_modules/.bin/webpack
|
||||
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack-dev-server
|
||||
|
||||
all: compile deploy clean
|
||||
|
||||
@@ -56,6 +57,9 @@ deploy-css:
|
||||
deploy-local:
|
||||
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
|
||||
|
||||
dev: deploy-init deploy-css deploy-lib-jitsi-meet
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
|
||||
source-package:
|
||||
mkdir -p source_package/jitsi-meet/css && \
|
||||
cp -r *.js *.html connection_optimization favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
|
||||
|
||||
17
README.md
17
README.md
@@ -44,7 +44,7 @@ To build the Jitsi Meet application, just type
|
||||
make
|
||||
```
|
||||
|
||||
## Working with the library sources (lib-jitsi-meet)
|
||||
### Working with the library sources (lib-jitsi-meet)
|
||||
|
||||
By default the library is build from its git repository sources. The default dependency path in package.json is :
|
||||
```json
|
||||
@@ -72,12 +72,12 @@ It allows to link `lib-jitsi-meet` dependency to local source in few steps:
|
||||
```bash
|
||||
cd lib-jitsi-meet
|
||||
|
||||
# create global symlink for lib-jitsi-meet package
|
||||
#### create global symlink for lib-jitsi-meet package
|
||||
npm link
|
||||
|
||||
cd ../jitsi-meet
|
||||
|
||||
# create symlink from the local node_modules folder to the global lib-jitsi-meet symlink
|
||||
#### create symlink from the local node_modules folder to the global lib-jitsi-meet symlink
|
||||
npm link lib-jitsi-meet
|
||||
```
|
||||
|
||||
@@ -90,16 +90,17 @@ cd jitsi-meet
|
||||
npm unlink lib-jitsi-meet
|
||||
npm install
|
||||
```
|
||||
## Running with webpack-dev-server for development
|
||||
### Running with webpack-dev-server for development
|
||||
|
||||
Use it at the CLI, type
|
||||
```
|
||||
node_modules/.bin/webpack-dev-server
|
||||
make dev
|
||||
```
|
||||
|
||||
By default the backend deployment used is `beta.meet.jit.si`, you can point the Jitsi-Meet app at a different backend by using a proxy server. To do this set the WEBPACK_DEV_SERVER_PROXY_TARGET variable, type
|
||||
By default the backend deployment used is `beta.meet.jit.si`, you can point the Jitsi-Meet app at a different backend by using a proxy server. To do this set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
|
||||
```
|
||||
WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com node_modules/.bin/webpack-dev-server
|
||||
export WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com
|
||||
make dev
|
||||
```
|
||||
|
||||
The app should be running at https://localhost:8080/
|
||||
@@ -122,7 +123,7 @@ network but decrypted on the machine that hosts the bridge.
|
||||
|
||||
The Jitsi Meet architecture allows you to deploy your own version, including
|
||||
all server components, and in that case your security guarantees will be roughly
|
||||
equivalent to these of a direct one-to-one WebRTC call. This is what's unique to
|
||||
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
|
||||
|
||||
@@ -19,12 +19,14 @@ package org.jitsi.meet;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import org.jitsi.meet.sdk.InviteSearchController;
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
import org.jitsi.meet.sdk.JitsiMeetViewListener;
|
||||
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -84,6 +86,11 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
on("CONFERENCE_WILL_LEAVE", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchNativeInvite(InviteSearchController inviteSearchController) {
|
||||
on("LAUNCH_NATIVE_INVITE", new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadConfigError(Map<String, Object> data) {
|
||||
on("LOAD_CONFIG_ERROR", data);
|
||||
|
||||
@@ -33,6 +33,8 @@ dependencies {
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-webrtc')
|
||||
compile project(':react-native-calendar-events')
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
// Build process helpers
|
||||
@@ -69,6 +71,22 @@ gradle.projectsEvaluated {
|
||||
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")
|
||||
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)
|
||||
//
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Controller object used by native code to query and submit user selections for the user invitation flow.
|
||||
*/
|
||||
public class InviteSearchController {
|
||||
|
||||
/**
|
||||
* The InviteSearchControllerDelegate for this controller, used to pass query
|
||||
* results back to the native code that initiated the query.
|
||||
*/
|
||||
private InviteSearchControllerDelegate searchControllerDelegate;
|
||||
|
||||
/**
|
||||
* Local cache of search query results. Used to re-hydrate the list
|
||||
* of selected items based on their ids passed to submitSelectedItemIds
|
||||
* in order to pass the full item maps back to the JitsiMeetView during submission.
|
||||
*/
|
||||
private Map<String, ReadableMap> items = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Randomly generated UUID, used for identification in the InviteSearchModule
|
||||
*/
|
||||
private String uuid = UUID.randomUUID().toString();
|
||||
|
||||
private WeakReference<InviteSearchModule> parentModuleRef;
|
||||
|
||||
public InviteSearchController(InviteSearchModule module) {
|
||||
parentModuleRef = new WeakReference<>(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a search for entities to invite with the given query.
|
||||
* Results will be returned through the associated InviteSearchControllerDelegate's
|
||||
* onReceiveResults method.
|
||||
*
|
||||
* @param query
|
||||
*/
|
||||
public void performQuery(String query) {
|
||||
JitsiMeetView.onInviteQuery(query, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send invites to selected users based on their item ids
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
public void submitSelectedItemIds(List<String> ids) {
|
||||
WritableArray selectedItems = new WritableNativeArray();
|
||||
for(int i=0; i<ids.size(); i++) {
|
||||
if(items.containsKey(ids.get(i))) {
|
||||
WritableNativeMap map = new WritableNativeMap();
|
||||
map.merge(items.get(ids.get(i)));
|
||||
selectedItems.pushMap(map);
|
||||
} else {
|
||||
// if the id doesn't exist in the map, we can't do anything, so just skip it
|
||||
}
|
||||
}
|
||||
|
||||
JitsiMeetView.submitSelectedItems(selectedItems, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches results received by the search into a local map for use
|
||||
* later when the items are submitted. Submission requires the full
|
||||
* map of information, but only the IDs are returned back to the delegate.
|
||||
* Using this map means we don't have to send the whole map back to the delegate.
|
||||
*
|
||||
* @param results
|
||||
* @param query
|
||||
*/
|
||||
void receivedResultsForQuery(ReadableArray results, String query) {
|
||||
|
||||
List<Map<String, Object>> jvmResults = new ArrayList<>();
|
||||
// cache results for use in submission later
|
||||
// convert to jvm array
|
||||
for(int i=0; i<results.size(); i++) {
|
||||
ReadableMap map = results.getMap(i);
|
||||
if(map.hasKey("id")) {
|
||||
items.put(map.getString("id"), map);
|
||||
} else if(map.hasKey("type") && map.getString("type").equals("phone") && map.hasKey("number")) {
|
||||
items.put(map.getString("number"), map);
|
||||
} else {
|
||||
Log.w("InviteSearchController", "Received result without id and that was not a phone number, so not adding it to suggestions: " + map);
|
||||
}
|
||||
|
||||
jvmResults.add(map.toHashMap());
|
||||
}
|
||||
|
||||
|
||||
searchControllerDelegate.onReceiveResults(this, jvmResults, query);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the InviteSearchControllerDelegate for this controller, used to pass query
|
||||
* results back to the native code that initiated the query.
|
||||
*/
|
||||
public InviteSearchControllerDelegate getSearchControllerDelegate() {
|
||||
return searchControllerDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the InviteSearchControllerDelegate for this controller, used to pass query results
|
||||
* back to the native code that initiated the query.
|
||||
*
|
||||
* @param searchControllerDelegate
|
||||
*/
|
||||
public void setSearchControllerDelegate(InviteSearchControllerDelegate searchControllerDelegate) {
|
||||
this.searchControllerDelegate = searchControllerDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the invitation flow and free memory allocated to the InviteSearchController. After
|
||||
* calling this method, this object is invalid - a new InviteSearchController will be passed
|
||||
* to the caller through launchNativeInvite.
|
||||
*/
|
||||
public void cancelSearch() {
|
||||
InviteSearchModule parentModule = parentModuleRef.get();
|
||||
if(parentModule != null) {
|
||||
parentModule.removeSearchController(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the unique identifier for this InviteSearchController
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public interface InviteSearchControllerDelegate {
|
||||
/**
|
||||
* Called when results are received for a query called through InviteSearchController.query()
|
||||
*
|
||||
* @param searchController
|
||||
* @param results a List of Map<String, Object> objects that represent items returned by the query.
|
||||
* The object at key "type" describes the type of item: "user", "videosipgw" (conference room), or "phone".
|
||||
* "user" types have properties at "id", "name", and "avatar"
|
||||
* "videosipgw" types have properties at "id" and "name"
|
||||
* "phone" types have properties at "number", "title", "and "subtitle"
|
||||
* @param query the query that generated the given results
|
||||
*/
|
||||
void onReceiveResults(InviteSearchController searchController, List<Map<String, Object>> results, String query);
|
||||
|
||||
/**
|
||||
* Called when the call to {@link InviteSearchController#submitSelectedItemIds(List)} completes successfully
|
||||
* and invitations are sent to all given IDs.
|
||||
*
|
||||
* @param searchController the active {@link InviteSearchController} for this invite flow. This object will be
|
||||
* cleaned up after the call to inviteSucceeded completes.
|
||||
*/
|
||||
void inviteSucceeded(InviteSearchController searchController);
|
||||
|
||||
/**
|
||||
* Called when the call to {@link InviteSearchController#submitSelectedItemIds(List)} completes, but the
|
||||
* invitation fails for one or more of the selected items.
|
||||
*
|
||||
* @param searchController the active {@link InviteSearchController} for this invite flow. This object
|
||||
* should be cleaned up by calling {@link InviteSearchController#cancelSearch()} if
|
||||
* the user exits the invite flow. Otherwise, it can stay active if the user
|
||||
* will attempt to invite
|
||||
* @param failedInviteItems a {@code List} of {@code Map<String, Object>} dictionaries that represent the
|
||||
* invitations that failed. The data type of the objects is identical to the results
|
||||
* returned in onReceiveResuls.
|
||||
*/
|
||||
void inviteFailed(InviteSearchController searchController, List<Map<String, Object>> failedInviteItems);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Native module for Invite Search
|
||||
*/
|
||||
class InviteSearchModule extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* Map of InviteSearchController objects passed to connected JitsiMeetView.
|
||||
* A call to launchNativeInvite will create a new InviteSearchController and pass
|
||||
* it back to the caller. On a successful invitation, the controller will be removed automatically.
|
||||
* On a failed invitation, the caller has the option of calling InviteSearchController#cancelSearch()
|
||||
* to remove the controller from this map. The controller should also be removed if the user cancels
|
||||
* the invitation flow.
|
||||
*/
|
||||
private Map<String, InviteSearchController> searchControllers = new HashMap<>();
|
||||
|
||||
public InviteSearchModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the native user invite flow
|
||||
*
|
||||
* @param externalAPIScope a string that represents a connection to a specific JitsiMeetView
|
||||
*/
|
||||
@ReactMethod
|
||||
public void launchNativeInvite(String externalAPIScope) {
|
||||
JitsiMeetView viewToLaunchInvite = JitsiMeetView.findViewByExternalAPIScope(externalAPIScope);
|
||||
|
||||
if(viewToLaunchInvite == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(viewToLaunchInvite.getListener() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
InviteSearchController controller = createSearchController();
|
||||
viewToLaunchInvite.getListener().launchNativeInvite(controller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for results received from the JavaScript invite search call
|
||||
*
|
||||
* @param results the results in a ReadableArray of ReadableMap objects
|
||||
* @param query the query associated with the search
|
||||
* @param inviteSearchControllerScope a string that represents a connection to a specific InviteSearchController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void receivedResults(ReadableArray results, String query, String inviteSearchControllerScope) {
|
||||
InviteSearchController controller = searchControllers.get(inviteSearchControllerScope);
|
||||
|
||||
if(controller == null) {
|
||||
Log.w("InviteSearchModule", "Received results, but unable to find active controller to send results back");
|
||||
return;
|
||||
}
|
||||
|
||||
controller.receivedResultsForQuery(results, query);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for invitation failures
|
||||
*
|
||||
* @param items the items for which the invitation failed
|
||||
* @param inviteSearchControllerScope a string that represents a connection to a specific InviteSearchController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void inviteFailedForItems(ReadableArray items, String inviteSearchControllerScope) {
|
||||
InviteSearchController controller = searchControllers.get(inviteSearchControllerScope);
|
||||
|
||||
if(controller == null) {
|
||||
Log.w("InviteSearchModule", "Invite failed, but unable to find active controller to notify");
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<Map<String, Object>> jvmItems = new ArrayList<>();
|
||||
for(int i=0; i<items.size(); i++) {
|
||||
ReadableMap item = items.getMap(i);
|
||||
jvmItems.add(item.toHashMap());
|
||||
}
|
||||
|
||||
controller.getSearchControllerDelegate().inviteFailed(controller, jvmItems);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void inviteSucceeded(String inviteSearchControllerScope) {
|
||||
InviteSearchController controller = searchControllers.get(inviteSearchControllerScope);
|
||||
|
||||
if(controller == null) {
|
||||
Log.w("InviteSearchModule", "Invite succeeded, but unable to find active controller to notify");
|
||||
return;
|
||||
}
|
||||
|
||||
controller.getSearchControllerDelegate().inviteSucceeded(controller);
|
||||
searchControllers.remove(inviteSearchControllerScope);
|
||||
}
|
||||
|
||||
void removeSearchController(String inviteSearchControllerUuid) {
|
||||
searchControllers.remove(inviteSearchControllerUuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "InviteSearch";
|
||||
}
|
||||
|
||||
private InviteSearchController createSearchController() {
|
||||
InviteSearchController searchController = new InviteSearchController(this);
|
||||
searchControllers.put(searchController.getUuid(), searchController);
|
||||
return searchController;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -33,7 +32,9 @@ import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
@@ -43,6 +44,7 @@ import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
@@ -76,9 +78,11 @@ public class JitsiMeetView extends FrameLayout {
|
||||
new AppInfoModule(reactContext),
|
||||
new AudioModeModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new InviteSearchModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext)
|
||||
new WiFiStatsModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -268,15 +272,43 @@ public class JitsiMeetView extends FrameLayout {
|
||||
sendEvent("onUserLeaveHint", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a query for users to invite to the conference. Results will be
|
||||
* returned through the {@link InviteSearchController.InviteSearchControllerDelegate#onReceiveResults(InviteSearchController, List, String)}
|
||||
* method.
|
||||
*
|
||||
* @param query {@code String} to use for the query
|
||||
*/
|
||||
public static void onInviteQuery(String query, String inviteSearchControllerScope) {
|
||||
WritableNativeMap params = new WritableNativeMap();
|
||||
params.putString("query", query);
|
||||
params.putString("inviteScope", inviteSearchControllerScope);
|
||||
sendEvent("performQueryAction", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends JavaScript event to submit invitations to the given item ids
|
||||
*
|
||||
* @param selectedItems a WritableArray of WritableNativeMaps representing selected items.
|
||||
* Each map representing a selected item should match the data passed
|
||||
* back in the return from a query.
|
||||
*/
|
||||
public static void submitSelectedItems(WritableArray selectedItems, String inviteSearchControllerScope) {
|
||||
WritableNativeMap params = new WritableNativeMap();
|
||||
params.putArray("selectedItems", selectedItems);
|
||||
params.putString("inviteScope", inviteSearchControllerScope);
|
||||
sendEvent("performSubmitInviteAction", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to send an event to JavaScript.
|
||||
*
|
||||
* @param eventName {@code String} containing the event name.
|
||||
* @param params {@code WritableMap} optional ancillary data for the event.
|
||||
* @param data {@code Object} optional ancillary data for the event.
|
||||
*/
|
||||
private static void sendEvent(
|
||||
String eventName,
|
||||
@Nullable WritableMap params) {
|
||||
@Nullable Object data) {
|
||||
if (reactInstanceManager != null) {
|
||||
ReactContext reactContext
|
||||
= reactInstanceManager.getCurrentReactContext();
|
||||
@@ -284,11 +316,16 @@ public class JitsiMeetView extends FrameLayout {
|
||||
reactContext
|
||||
.getJSModule(
|
||||
DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(eventName, params);
|
||||
.emit(eventName, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether user invitation is enabled.
|
||||
*/
|
||||
private boolean addPeopleEnabled;
|
||||
|
||||
/**
|
||||
* The default base {@code URL} used to join a conference when a partial URL
|
||||
* (e.g. a room name only) is specified to {@link #loadURLString(String)} or
|
||||
@@ -296,6 +333,11 @@ public class JitsiMeetView extends FrameLayout {
|
||||
*/
|
||||
private URL defaultURL;
|
||||
|
||||
/**
|
||||
* Whether the ability to add users by phone number is enabled.
|
||||
*/
|
||||
private boolean dialOutEnabled;
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code JitsiMeetView} within the process
|
||||
* for the purposes of {@link ExternalAPI}. The name scope was inspired by
|
||||
@@ -454,6 +496,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||
// welcomePageEnabled
|
||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||
|
||||
props.putBoolean("addPeopleEnabled", addPeopleEnabled);
|
||||
props.putBoolean("dialOutEnabled", dialOutEnabled);
|
||||
|
||||
// XXX The method loadURLObject: is supposed to be imperative i.e.
|
||||
// a second invocation with one and the same URL is expected to join
|
||||
// the respective conference again if the first invocation was followed
|
||||
@@ -535,6 +580,18 @@ public class JitsiMeetView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the ability to add users to the call is enabled.
|
||||
* If this is enabled, an add user button will appear on the {@link JitsiMeetView}.
|
||||
* If enabled, and the user taps the add user button,
|
||||
* {@link JitsiMeetViewListener#launchNativeInvite(Map)} will be called.
|
||||
*
|
||||
* @param addPeopleEnabled {@code true} to enable the add people button; otherwise, {@code false}
|
||||
*/
|
||||
public void setAddPeopleEnabled(boolean addPeopleEnabled) {
|
||||
this.addPeopleEnabled = addPeopleEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default base {@code URL} used to join a conference when a
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
@@ -548,6 +605,18 @@ public class JitsiMeetView extends FrameLayout {
|
||||
this.defaultURL = defaultURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the ability to add phone numbers to the call is enabled.
|
||||
* Must be enabled along with {@link #setAddPeopleEnabled(boolean)} to
|
||||
* be effective.
|
||||
*
|
||||
* @param dialOutEnabled {@code true} to enable the ability to add
|
||||
* phone numbers to the call; otherwise, {@code false}
|
||||
*/
|
||||
public void setDialOutEnabled(boolean dialOutEnabled) {
|
||||
this.dialOutEnabled = dialOutEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific {@link JitsiMeetViewListener} on this
|
||||
* {@code JitsiMeetView}.
|
||||
|
||||
@@ -46,4 +46,8 @@ public abstract class JitsiMeetViewAdapter implements JitsiMeetViewListener {
|
||||
@Override
|
||||
public void onLoadConfigError(Map<String, Object> data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchNativeInvite(InviteSearchController inviteSearchController) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,16 @@ public interface JitsiMeetViewListener {
|
||||
*/
|
||||
void onConferenceWillLeave(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called when the add user button is tapped.
|
||||
*
|
||||
* @param inviteSearchController {@code InviteSearchController} scoped
|
||||
* for this user invite flow. The {@code InviteSearchController} is used
|
||||
* to start user queries and accepts an {@code InviteSearchControllerDelegate}
|
||||
* for receiving user query responses.
|
||||
*/
|
||||
void launchNativeInvite(InviteSearchController inviteSearchController);
|
||||
|
||||
/**
|
||||
* Called when loading the main configuration file from the Jitsi Meet
|
||||
* deployment fails.
|
||||
|
||||
@@ -44,14 +44,26 @@ public class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
PictureInPictureParams.Builder builder
|
||||
= new PictureInPictureParams.Builder()
|
||||
.setAspectRatio(new Rational(1, 1));
|
||||
boolean r
|
||||
= currentActivity.enterPictureInPictureMode(builder.build());
|
||||
Throwable error;
|
||||
|
||||
if (r) {
|
||||
// https://developer.android.com/reference/android/app/Activity.html#enterPictureInPictureMode(android.app.PictureInPictureParams)
|
||||
//
|
||||
// The system may disallow entering picture-in-picture in various
|
||||
// cases, including when the activity is not visible, if the screen
|
||||
// is locked or if the user has an activity pinned.
|
||||
try {
|
||||
error
|
||||
= currentActivity.enterPictureInPictureMode(builder.build())
|
||||
? null
|
||||
: new Exception("Failed to enter Picture-in-Picture");
|
||||
} catch (RuntimeException re) {
|
||||
error = re;
|
||||
}
|
||||
|
||||
if (error == null) {
|
||||
promise.resolve(null);
|
||||
} else {
|
||||
promise.reject(
|
||||
new Exception("Failed to enter Picture-in-Picture"));
|
||||
promise.reject(error);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jitsi.meet.sdk.net;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Constructs IPv6 addresses for IPv4 addresses in the NAT64 environment.
|
||||
*
|
||||
* NAT64 translates IPv4 to IPv6 addresses by adding "well known" prefix and
|
||||
* suffix configured by the administrator. Those are figured out by discovering
|
||||
* both IPv6 and IPv4 addresses of a host and then trying to find a place where
|
||||
* the IPv4 address fits into the format described here:
|
||||
* https://tools.ietf.org/html/rfc6052#section-2.2
|
||||
*/
|
||||
public class NAT64AddrInfo {
|
||||
/**
|
||||
* Coverts bytes array to upper case HEX string.
|
||||
*
|
||||
* @param bytes an array of bytes to be converted
|
||||
* @return ex. "010AFF" for an array of {1, 10, 255}.
|
||||
*/
|
||||
static String bytesToHexString(byte[] bytes) {
|
||||
StringBuilder hexStr = new StringBuilder();
|
||||
|
||||
for (byte b : bytes) {
|
||||
hexStr.append(String.format("%02X", b));
|
||||
}
|
||||
|
||||
return hexStr.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to discover the NAT64 prefix/suffix based on the IPv4 and IPv6
|
||||
* addresses resolved for given {@code host}.
|
||||
*
|
||||
* @param host the host for which the code will try to discover IPv4 and
|
||||
* IPv6 addresses which then will be used to figure out the NAT64 prefix.
|
||||
* @return {@link NAT64AddrInfo} instance if the NAT64 prefix/suffix was
|
||||
* successfully discovered or {@code null} if it failed for any reason.
|
||||
* @throws UnknownHostException thrown by {@link InetAddress#getAllByName}.
|
||||
*/
|
||||
public static NAT64AddrInfo discover(String host)
|
||||
throws UnknownHostException {
|
||||
InetAddress ipv4 = null;
|
||||
InetAddress ipv6 = null;
|
||||
|
||||
for(InetAddress addr : InetAddress.getAllByName(host)) {
|
||||
byte[] bytes = addr.getAddress();
|
||||
|
||||
if (bytes.length == 4) {
|
||||
ipv4 = addr;
|
||||
} else if (bytes.length == 16) {
|
||||
ipv6 = addr;
|
||||
}
|
||||
}
|
||||
|
||||
if (ipv4 != null && ipv6 != null) {
|
||||
return figureOutNAT64AddrInfo(ipv4.getAddress(), ipv6.getAddress());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on IPv4 and IPv6 addresses of the same host, the method will make
|
||||
* an attempt to figure out what are the NAT64 prefix and suffix.
|
||||
*
|
||||
* @param ipv4AddrBytes the IPv4 address of the same host in NAT64 network,
|
||||
* as returned by {@link InetAddress#getAddress()}.
|
||||
* @param ipv6AddrBytes the IPv6 address of the same host in NAT64 network,
|
||||
* as returned by {@link InetAddress#getAddress()}.
|
||||
* @return {@link NAT64AddrInfo} instance which contains the prefix/suffix
|
||||
* of the current NAT64 network or {@code null} if the prefix could not be
|
||||
* found.
|
||||
*/
|
||||
static NAT64AddrInfo figureOutNAT64AddrInfo(
|
||||
byte[] ipv4AddrBytes,
|
||||
byte[] ipv6AddrBytes) {
|
||||
String ipv6Str = bytesToHexString(ipv6AddrBytes);
|
||||
String ipv4Str = bytesToHexString(ipv4AddrBytes);
|
||||
|
||||
// NAT64 address format:
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------|
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |32| prefix |v4(32) | u | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |40| prefix |v4(24) | u |(8)| suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |48| prefix |v4(16) | u | (16) | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |56| prefix |(8)| u | v4(24) | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |64| prefix | u | v4(32) | suffix |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
// |96| prefix | v4(32) |
|
||||
// +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
||||
int prefixLength = 96;
|
||||
int suffixLength = 0;
|
||||
String prefix = null;
|
||||
String suffix = null;
|
||||
|
||||
if (ipv4Str.equalsIgnoreCase(ipv6Str.substring(prefixLength / 4))) {
|
||||
prefix = ipv6Str.substring(0, prefixLength / 4);
|
||||
} else {
|
||||
// Cut out the 'u' octet
|
||||
ipv6Str = ipv6Str.substring(0, 16) + ipv6Str.substring(18);
|
||||
|
||||
for (prefixLength = 64, suffixLength = 6; prefixLength >= 32; ) {
|
||||
if (ipv4Str.equalsIgnoreCase(
|
||||
ipv6Str.substring(
|
||||
prefixLength / 4, prefixLength / 4 + 8))) {
|
||||
prefix = ipv6Str.substring(0, prefixLength / 4);
|
||||
suffix = ipv6Str.substring(ipv6Str.length() - suffixLength);
|
||||
break;
|
||||
}
|
||||
|
||||
prefixLength -= 8;
|
||||
suffixLength += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return prefix != null ? new NAT64AddrInfo(prefix, suffix) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An overload for {@link #hexStringToIPv6String(StringBuilder)}.
|
||||
*
|
||||
* @param hexStr a hex representation of IPv6 address bytes.
|
||||
* @return an IPv6 address string.
|
||||
*/
|
||||
static String hexStringToIPv6String(String hexStr) {
|
||||
return hexStringToIPv6String(new StringBuilder(hexStr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from HEX representation of IPv6 address bytes into IPv6 address
|
||||
* string which includes the ':' signs.
|
||||
*
|
||||
* @param str a hex representation of IPv6 address bytes.
|
||||
* @return eg. FE80:CD00:0000:0CDA:1357:0000:212F:749C
|
||||
*/
|
||||
static String hexStringToIPv6String(StringBuilder str) {
|
||||
for (int i = 32 - 4; i > 0; i -= 4) {
|
||||
str.insert(i, ":");
|
||||
}
|
||||
|
||||
return str.toString().toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an IPv4 address string and returns it's byte array representation.
|
||||
*
|
||||
* @param ipv4Address eg. '192.168.3.23'
|
||||
* @return byte representation of given IPv4 address string.
|
||||
* @throws IllegalArgumentException if the address is not in valid format.
|
||||
*/
|
||||
static byte[] ipv4AddressStringToBytes(String ipv4Address) {
|
||||
InetAddress address;
|
||||
|
||||
try {
|
||||
address = InetAddress.getByName(ipv4Address);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid IP address: " + ipv4Address, e);
|
||||
}
|
||||
|
||||
byte[] bytes = address.getAddress();
|
||||
|
||||
if (bytes.length != 4) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not an IPv4 address: " + ipv4Address);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* The NAT64 prefix added to construct IPv6 from an IPv4 address.
|
||||
*/
|
||||
private final String prefix;
|
||||
|
||||
/**
|
||||
* The NAT64 suffix (if any) used to construct IPv6 from an IPv4 address.
|
||||
*/
|
||||
private final String suffix;
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link NAT64AddrInfo}.
|
||||
*
|
||||
* @param prefix the NAT64 prefix.
|
||||
* @param suffix the NAT64 suffix.
|
||||
*/
|
||||
private NAT64AddrInfo(String prefix, String suffix) {
|
||||
this.prefix = prefix;
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the NAT64 prefix and suffix will create an IPv6 representation
|
||||
* of the given IPv4 address.
|
||||
*
|
||||
* @param ipv4Address eg. '192.34.2.3'
|
||||
* @return IPv6 address string eg. FE80:CD00:0000:0CDA:1357:0000:212F:749C
|
||||
* @throws IllegalArgumentException if given string is not a valid IPv4
|
||||
* address.
|
||||
*/
|
||||
public String getIPv6Address(String ipv4Address) {
|
||||
byte[] ipv4AddressBytes = ipv4AddressStringToBytes(ipv4Address);
|
||||
StringBuilder newIPv6Str = new StringBuilder();
|
||||
|
||||
newIPv6Str.append(prefix);
|
||||
newIPv6Str.append(bytesToHexString(ipv4AddressBytes));
|
||||
|
||||
if (suffix != null) {
|
||||
// Insert the 'u' octet.
|
||||
newIPv6Str.insert(16, "00");
|
||||
newIPv6Str.append(suffix);
|
||||
}
|
||||
|
||||
return hexStringToIPv6String(newIPv6Str);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jitsi.meet.sdk.net;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* This module exposes the functionality of creating an IPv6 representation
|
||||
* of IPv4 addresses in NAT64 environment.
|
||||
*
|
||||
* See[1] and [2] for more info on what NAT64 is.
|
||||
* [1]: https://tools.ietf.org/html/rfc6146
|
||||
* [2]: https://tools.ietf.org/html/rfc6052
|
||||
*/
|
||||
public class NAT64AddrInfoModule extends ReactContextBaseJavaModule {
|
||||
/**
|
||||
* The host for which the module wil try to resolve both IPv4 and IPv6
|
||||
* addresses in order to figure out the NAT64 prefix.
|
||||
*/
|
||||
private final static String HOST = "nat64.jitsi.net";
|
||||
|
||||
/**
|
||||
* How long is the {@link NAT64AddrInfo} instance valid.
|
||||
*/
|
||||
private final static long INFO_LIFETIME = 60 * 1000;
|
||||
|
||||
/**
|
||||
* The name of this module.
|
||||
*/
|
||||
private final static String MODULE_NAME = "NAT64AddrInfo";
|
||||
|
||||
/**
|
||||
* The {@code Log} tag {@code NAT64AddrInfoModule} is to log messages with.
|
||||
*/
|
||||
private final static String TAG = MODULE_NAME;
|
||||
|
||||
/**
|
||||
* The {@link NAT64AddrInfo} instance which holds NAT64 prefix/suffix.
|
||||
*/
|
||||
private NAT64AddrInfo info;
|
||||
|
||||
/**
|
||||
* When {@link #info} was created.
|
||||
*/
|
||||
private long infoTimestamp;
|
||||
|
||||
/**
|
||||
* Creates new {@link NAT64AddrInfoModule}.
|
||||
*
|
||||
* @param reactContext the react context to be used by the new module
|
||||
* instance.
|
||||
*/
|
||||
public NAT64AddrInfoModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to obtain IPv6 address for given IPv4 address in NAT64 environment.
|
||||
*
|
||||
* @param ipv4Address IPv4 address string.
|
||||
* @param promise a {@link Promise} which will be resolved either with IPv6
|
||||
* address for given IPv4 address or with {@code null} if no
|
||||
* {@link NAT64AddrInfo} was resolved for the current network. Will be
|
||||
* rejected if given {@code ipv4Address} is not a valid IPv4 address.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getIPv6Address(String ipv4Address, final Promise promise) {
|
||||
// Reset if cached for too long.
|
||||
if (System.currentTimeMillis() - infoTimestamp > INFO_LIFETIME) {
|
||||
info = null;
|
||||
}
|
||||
|
||||
if (info == null) {
|
||||
String host = HOST;
|
||||
|
||||
try {
|
||||
info = NAT64AddrInfo.discover(host);
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e(TAG, "NAT64AddrInfo.discover: " + host, e);
|
||||
}
|
||||
infoTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
String result;
|
||||
|
||||
try {
|
||||
result = info == null ? null : info.getIPv6Address(ipv4Address);
|
||||
} catch (IllegalArgumentException exc) {
|
||||
Log.e(TAG, "Failed to get IPv6 address for: " + ipv4Address, exc);
|
||||
|
||||
// We don't want to reject. It's not a big deal if there's no IPv6
|
||||
// address resolved.
|
||||
result = null;
|
||||
}
|
||||
promise.resolve(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jitsi.meet.sdk.net;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link NAT64AddrInfo} class.
|
||||
*/
|
||||
public class NAT64AddrInfoTest {
|
||||
/**
|
||||
* Test case for the 96 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test96Prefix() {
|
||||
testPrefixSuffix(
|
||||
"260777000000000400000000", "", "203.0.113.1", "23.17.23.3");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 64 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test64Prefix() {
|
||||
String prefix = "1FF2A227B3AAF3D2";
|
||||
String suffix = "BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "48.46.87.34", "23.87.145.4");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 56 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test56Prefix() {
|
||||
String prefix = "1FF2A227B3AAF3";
|
||||
String suffix = "A2BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "34.72.234.255", "1.235.3.65");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 48 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test48Prefix() {
|
||||
String prefix = "1FF2A227B3AA";
|
||||
String suffix = "72A2BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "97.54.3.23", "77.49.0.33");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 40 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test40Prefix() {
|
||||
String prefix = "1FF2A227B3";
|
||||
String suffix = "D972A2BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "10.23.56.121", "97.65.32.21");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the 32 prefix length.
|
||||
*/
|
||||
@Test
|
||||
public void test32Prefix()
|
||||
throws UnknownHostException {
|
||||
String prefix = "1FF2A227";
|
||||
String suffix = "20D972A2BB87C8";
|
||||
|
||||
testPrefixSuffix(prefix, suffix, "162.63.65.189", "135.222.84.206");
|
||||
}
|
||||
|
||||
private static String buildIPv6Addr(
|
||||
String prefix, String suffix, String ipv4Hex) {
|
||||
String ipv6Str = prefix + ipv4Hex + suffix;
|
||||
|
||||
if (suffix.length() > 0) {
|
||||
ipv6Str = new StringBuilder(ipv6Str).insert(16, "00").toString();
|
||||
}
|
||||
|
||||
return ipv6Str;
|
||||
}
|
||||
|
||||
private void testPrefixSuffix(
|
||||
String prefix, String suffix, String ipv4, String otherIPv4) {
|
||||
byte[] ipv4Bytes = NAT64AddrInfo.ipv4AddressStringToBytes(ipv4);
|
||||
String ipv4String = NAT64AddrInfo.bytesToHexString(ipv4Bytes);
|
||||
String ipv6Str = buildIPv6Addr(prefix, suffix, ipv4String);
|
||||
|
||||
BigInteger ipv6Address = new BigInteger(ipv6Str, 16);
|
||||
|
||||
NAT64AddrInfo nat64AddrInfo
|
||||
= NAT64AddrInfo.figureOutNAT64AddrInfo(
|
||||
ipv4Bytes, ipv6Address.toByteArray());
|
||||
|
||||
assertNotNull("Failed to figure out NAT64 info", nat64AddrInfo);
|
||||
|
||||
String newIPv6 = nat64AddrInfo.getIPv6Address(ipv4);
|
||||
|
||||
assertEquals(
|
||||
NAT64AddrInfo.hexStringToIPv6String(ipv6Address.toString(16)),
|
||||
newIPv6);
|
||||
|
||||
byte[] ipv4Addr2 = NAT64AddrInfo.ipv4AddressStringToBytes(otherIPv4);
|
||||
String ipv4Addr2Hex = NAT64AddrInfo.bytesToHexString(ipv4Addr2);
|
||||
|
||||
newIPv6 = nat64AddrInfo.getIPv6Address(otherIPv4);
|
||||
|
||||
assertEquals(
|
||||
NAT64AddrInfo.hexStringToIPv6String(
|
||||
buildIPv6Addr(prefix, suffix, ipv4Addr2Hex)),
|
||||
newIPv6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidIPv4Format() {
|
||||
testInvalidIPv4Format("256.1.2.3");
|
||||
testInvalidIPv4Format("FE80:CD00:0000:0CDA:1357:0000:212F:749C");
|
||||
}
|
||||
|
||||
private void testInvalidIPv4Format(String ipv4Str) {
|
||||
try {
|
||||
NAT64AddrInfo.ipv4AddressStringToBytes(ipv4Str);
|
||||
fail("Did not throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException exc) {
|
||||
/* OK */
|
||||
}
|
||||
}
|
||||
}
|
||||
193
conference.js
193
conference.js
@@ -21,13 +21,13 @@ import {
|
||||
createSelectParticipantFailedEvent,
|
||||
createStreamSwitchDelayEvent,
|
||||
createTrackMutedEvent,
|
||||
initAnalytics,
|
||||
sendAnalytics
|
||||
} from './react/features/analytics';
|
||||
import {
|
||||
redirectWithStoredParams,
|
||||
reloadWithStoredParams
|
||||
} from './react/features/app';
|
||||
import { updateRecordingState } from './react/features/recording';
|
||||
|
||||
import EventEmitter from 'events';
|
||||
|
||||
@@ -43,11 +43,11 @@ import {
|
||||
lockStateChanged,
|
||||
onStartMutedPolicyChanged,
|
||||
p2pStatusChanged,
|
||||
sendLocalParticipant
|
||||
sendLocalParticipant,
|
||||
setDesktopSharingEnabled
|
||||
} from './react/features/base/conference';
|
||||
import { updateDeviceList } from './react/features/base/devices';
|
||||
import {
|
||||
isAnalyticsEnabled,
|
||||
isFatalJitsiConnectionError,
|
||||
JitsiConferenceErrors,
|
||||
JitsiConferenceEvents,
|
||||
@@ -93,7 +93,6 @@ import {
|
||||
getLocationContextRoot,
|
||||
getJitsiMeetGlobalNS
|
||||
} from './react/features/base/util';
|
||||
import { statsEmitter } from './react/features/connection-indicator';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker';
|
||||
import { appendSuffix } from './react/features/display-name';
|
||||
import {
|
||||
@@ -104,10 +103,8 @@ import {
|
||||
mediaPermissionPromptVisibilityChanged,
|
||||
suspendDetected
|
||||
} from './react/features/overlay';
|
||||
import {
|
||||
isButtonEnabled,
|
||||
showDesktopSharingButton
|
||||
} from './react/features/toolbox';
|
||||
import { setSharedVideoStatus } from './react/features/shared-video';
|
||||
import { isButtonEnabled } from './react/features/toolbox';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
@@ -234,13 +231,14 @@ function maybeRedirectToWelcomePage(options) {
|
||||
}));
|
||||
}
|
||||
|
||||
// if Welcome page is enabled redirect to welcome page after 3 sec.
|
||||
// if Welcome page is enabled redirect to welcome page after 3 sec, if
|
||||
// there is a thank you message to be shown, 0.5s otherwise.
|
||||
if (config.enableWelcomePage) {
|
||||
setTimeout(
|
||||
() => {
|
||||
APP.store.dispatch(redirectWithStoredParams('/'));
|
||||
},
|
||||
3000);
|
||||
options.showThankYou ? 3000 : 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,16 +503,6 @@ export default {
|
||||
*/
|
||||
desktopSharingDisabledTooltip: null,
|
||||
|
||||
/*
|
||||
* Whether the local "raisedHand" flag is on.
|
||||
*/
|
||||
isHandRaised: false,
|
||||
|
||||
/*
|
||||
* Whether the local participant is the dominant speaker in the conference.
|
||||
*/
|
||||
isDominantSpeaker: false,
|
||||
|
||||
/**
|
||||
* The local audio track (if any).
|
||||
* FIXME tracks from redux store should be the single source of truth
|
||||
@@ -695,53 +683,20 @@ export default {
|
||||
/**
|
||||
* Open new connection and join to the conference.
|
||||
* @param {object} options
|
||||
* @param {string} roomName name of the conference
|
||||
* @param {string} roomName - The name of the conference.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
init(options) {
|
||||
this.roomName = options.roomName;
|
||||
|
||||
// attaches global error handler, if there is already one, respect it
|
||||
if (JitsiMeetJS.getGlobalOnErrorHandler) {
|
||||
const oldOnErrorHandler = window.onerror;
|
||||
|
||||
// eslint-disable-next-line max-params
|
||||
window.onerror = (message, source, lineno, colno, error) => {
|
||||
JitsiMeetJS.getGlobalOnErrorHandler(
|
||||
message, source, lineno, colno, error);
|
||||
|
||||
if (oldOnErrorHandler) {
|
||||
oldOnErrorHandler(message, source, lineno, colno, error);
|
||||
}
|
||||
};
|
||||
|
||||
const oldOnUnhandledRejection = window.onunhandledrejection;
|
||||
|
||||
window.onunhandledrejection = function(event) {
|
||||
JitsiMeetJS.getGlobalOnErrorHandler(
|
||||
null, null, null, null, event.reason);
|
||||
|
||||
if (oldOnUnhandledRejection) {
|
||||
oldOnUnhandledRejection(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
JitsiMeetJS.init({
|
||||
enableAnalyticsLogging: isAnalyticsEnabled(APP.store),
|
||||
...config
|
||||
}).then(() => {
|
||||
initAnalytics(APP.store);
|
||||
|
||||
return this.createInitialLocalTracksAndConnect(
|
||||
options.roomName, {
|
||||
startAudioOnly: config.startAudioOnly,
|
||||
startScreenSharing: config.startScreenSharing,
|
||||
startWithAudioMuted: config.startWithAudioMuted,
|
||||
startWithVideoMuted: config.startWithVideoMuted
|
||||
});
|
||||
})
|
||||
this.createInitialLocalTracksAndConnect(
|
||||
options.roomName, {
|
||||
startAudioOnly: config.startAudioOnly,
|
||||
startScreenSharing: config.startScreenSharing,
|
||||
startWithAudioMuted: config.startWithAudioMuted,
|
||||
startWithVideoMuted: config.startWithVideoMuted
|
||||
})
|
||||
.then(([ tracks, con ]) => {
|
||||
tracks.forEach(track => {
|
||||
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|
||||
@@ -773,7 +728,8 @@ export default {
|
||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||
this.isDesktopSharingEnabled);
|
||||
|
||||
APP.store.dispatch(showDesktopSharingButton());
|
||||
APP.store.dispatch(
|
||||
setDesktopSharingEnabled(this.isDesktopSharingEnabled));
|
||||
|
||||
this._createRoom(tracks);
|
||||
APP.remoteControl.init();
|
||||
@@ -1364,7 +1320,6 @@ export default {
|
||||
this.isSharingScreen = newStream && newStream.videoType === 'desktop';
|
||||
|
||||
if (wasSharingScreen !== this.isSharingScreen) {
|
||||
APP.UI.updateDesktopSharingButtons();
|
||||
APP.API.notifyScreenSharingStatusChanged(this.isSharingScreen);
|
||||
}
|
||||
},
|
||||
@@ -1387,37 +1342,6 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers a tooltip to display when a feature was attempted to be used
|
||||
* while in audio only mode.
|
||||
*
|
||||
* @param {string} featureName - The name of the feature that attempted to
|
||||
* toggle.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_displayAudioOnlyTooltip(featureName) {
|
||||
let buttonName = null;
|
||||
let tooltipElementId = null;
|
||||
|
||||
switch (featureName) {
|
||||
case 'screenShare':
|
||||
buttonName = 'desktop';
|
||||
tooltipElementId = 'screenshareWhileAudioOnly';
|
||||
break;
|
||||
case 'videoMute':
|
||||
buttonName = 'camera';
|
||||
tooltipElementId = 'unmuteWhileAudioOnly';
|
||||
break;
|
||||
}
|
||||
|
||||
if (tooltipElementId) {
|
||||
APP.UI.showToolbar(6000);
|
||||
APP.UI.showCustomToolbarPopup(
|
||||
buttonName, tooltipElementId, true, 5000);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether or not the conference is currently in audio only mode.
|
||||
*
|
||||
@@ -1528,8 +1452,6 @@ export default {
|
||||
}
|
||||
|
||||
if (this.isAudioOnly()) {
|
||||
this._displayAudioOnlyTooltip('screenShare');
|
||||
|
||||
return Promise.reject('No screensharing in audio only mode');
|
||||
}
|
||||
|
||||
@@ -1871,9 +1793,6 @@ export default {
|
||||
|
||||
room.on(JitsiConferenceEvents.TALK_WHILE_MUTED, () => {
|
||||
APP.UI.showToolbar(6000);
|
||||
|
||||
APP.UI.showCustomToolbarPopup(
|
||||
'microphone', 'talkWhileMutedPopup', true, 5000);
|
||||
});
|
||||
|
||||
room.on(
|
||||
@@ -1896,21 +1815,14 @@ export default {
|
||||
});
|
||||
room.on(JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id => {
|
||||
APP.store.dispatch(dominantSpeakerChanged(id));
|
||||
|
||||
if (this.isLocalId(id)) {
|
||||
this.isDominantSpeaker = true;
|
||||
this.setRaisedHand(false);
|
||||
} else {
|
||||
this.isDominantSpeaker = false;
|
||||
const participant = room.getParticipantById(id);
|
||||
|
||||
if (participant) {
|
||||
APP.UI.setRaisedHandStatus(participant, false);
|
||||
}
|
||||
}
|
||||
APP.UI.markDominantSpeaker(id);
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.LIVE_STREAM_URL_CHANGED,
|
||||
(from, liveStreamViewURL) =>
|
||||
APP.store.dispatch(updateRecordingState({
|
||||
liveStreamViewURL
|
||||
})));
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
APP.UI.markVideoInterrupted(true);
|
||||
@@ -1963,10 +1875,6 @@ export default {
|
||||
reportError(e);
|
||||
}
|
||||
});
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY,
|
||||
() => this._displayAudioOnlyTooltip('videoMute'));
|
||||
}
|
||||
|
||||
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
@@ -2022,7 +1930,10 @@ export default {
|
||||
(participant, name, oldValue, newValue) => {
|
||||
switch (name) {
|
||||
case 'raisedHand':
|
||||
APP.UI.setRaisedHandStatus(participant, newValue);
|
||||
APP.store.dispatch(participantUpdated({
|
||||
id: participant.getId(),
|
||||
raisedHand: newValue === 'true'
|
||||
}));
|
||||
break;
|
||||
case 'remoteControlSessionStatus':
|
||||
APP.UI.setRemoteControlActiveStatus(
|
||||
@@ -2081,26 +1992,13 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.DTMF_SUPPORT_CHANGED,
|
||||
isDTMFSupported => {
|
||||
APP.UI.updateDTMFSupport(isDTMFSupported);
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(UIEvents.AUDIO_MUTED, muted => {
|
||||
this.muteAudio(muted);
|
||||
});
|
||||
APP.UI.addListener(UIEvents.VIDEO_MUTED, muted => {
|
||||
if (this.isAudioOnly() && !muted) {
|
||||
this._displayAudioOnlyTooltip('videoMute');
|
||||
} else {
|
||||
this.muteVideo(muted);
|
||||
}
|
||||
this.muteVideo(muted);
|
||||
});
|
||||
|
||||
statsEmitter.startListeningForStats(room);
|
||||
|
||||
room.addCommandListener(this.commands.defaults.ETHERPAD,
|
||||
({ value }) => {
|
||||
APP.UI.initEtherpad(value);
|
||||
@@ -2198,13 +2096,6 @@ export default {
|
||||
room.toggleRecording(options);
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.SUBJECT_CHANGED, topic => {
|
||||
room.setSubject(topic);
|
||||
});
|
||||
room.on(JitsiConferenceEvents.SUBJECT_CHANGED, subject => {
|
||||
APP.UI.setSubject(subject);
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
|
||||
AuthHandler.authenticate(room);
|
||||
});
|
||||
@@ -2361,6 +2252,8 @@ export default {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
APP.store.dispatch(setSharedVideoStatus(state));
|
||||
});
|
||||
room.addCommandListener(
|
||||
this.commands.defaults.SHARED_VIDEO,
|
||||
@@ -2623,30 +2516,6 @@ export default {
|
||||
APP.API.notifyVideoAvailabilityChanged(available);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the local "raised hand" status.
|
||||
*/
|
||||
maybeToggleRaisedHand() {
|
||||
this.setRaisedHand(!this.isHandRaised);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the local "raised hand" status to a particular value.
|
||||
*/
|
||||
setRaisedHand(raisedHand) {
|
||||
if (raisedHand !== this.isHandRaised) {
|
||||
APP.UI.onLocalRaiseHandChanged(raisedHand);
|
||||
|
||||
this.isHandRaised = raisedHand;
|
||||
|
||||
// Advertise the updated status
|
||||
room.setLocalParticipantProperty('raisedHand', raisedHand);
|
||||
|
||||
// Update the view
|
||||
APP.UI.setLocalRaisedHandStatus(raisedHand);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disconnect from the conference and optionally request user feedback.
|
||||
* @param {boolean} [requestFeedback=false] if user feedback should be
|
||||
|
||||
21
config.js
21
config.js
@@ -57,6 +57,9 @@ var config = {
|
||||
// P2P test mode disables automatic switching to P2P when there are 2
|
||||
// participants in the conference.
|
||||
p2pTestMode: false
|
||||
|
||||
// Enables the test specific features consumed by jitsi-meet-torture
|
||||
// testMode: false
|
||||
},
|
||||
|
||||
// Disables ICE/UDP by filtering out local and remote UDP candidates in
|
||||
@@ -178,6 +181,23 @@ var config = {
|
||||
// Disables or enables RTX (RFC 4588) (defaults to false).
|
||||
// disableRtx: false,
|
||||
|
||||
// Disables or enables TCC (the default is in Jicofo and set to true)
|
||||
// (draft-holmer-rmcat-transport-wide-cc-extensions-01). This setting
|
||||
// affects congestion control, it practically enables send-side bandwidth
|
||||
// estimations.
|
||||
// enableTcc: true,
|
||||
|
||||
// Disables or enables REMB (the default is in Jicofo and set to false)
|
||||
// (draft-alvestrand-rmcat-remb-03). This setting affects congestion
|
||||
// control, it practically enables recv-side bandwidth estimations. When
|
||||
// both TCC and REMB are enabled, TCC takes precedence. When both are
|
||||
// disabled, then bandwidth estimations are disabled.
|
||||
// enableRemb: false,
|
||||
|
||||
// Defines the minimum number of participants to start a call (the default
|
||||
// is set in Jicofo and set to 2).
|
||||
// minParticipants: 2,
|
||||
|
||||
// Use XEP-0215 to fetch STUN and TURN servers.
|
||||
// useStunTurn: true,
|
||||
|
||||
@@ -322,7 +342,6 @@ var config = {
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
alwaysVisibleToolbar
|
||||
autoEnableDesktopSharing
|
||||
autoRecord
|
||||
autoRecordToken
|
||||
debug
|
||||
|
||||
@@ -2,42 +2,6 @@
|
||||
* Project animations
|
||||
**/
|
||||
|
||||
/**
|
||||
* Slide in animation for extended toolbar.
|
||||
*/
|
||||
@include keyframes(slideInX) {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(0%); }
|
||||
}
|
||||
|
||||
@include keyframes(slideOutX) {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-100%); }
|
||||
}
|
||||
|
||||
@include keyframes(slideInExtX) {
|
||||
0% { transform: translateX(-500%); }
|
||||
100% { transform: translateX(0%); }
|
||||
}
|
||||
|
||||
@include keyframes(slideOutExtX) {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-500%); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide in / out animation for main toolbar.
|
||||
*/
|
||||
|
||||
@include keyframes(slideInY) {
|
||||
100% { transform: translateY(0%); }
|
||||
}
|
||||
|
||||
@include keyframes(slideOutY) {
|
||||
0% { transform: translateY(0%); }
|
||||
100% { transform: translateY(-100%); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide in animation for extended toolbar (inner) panel.
|
||||
*/
|
||||
@@ -53,17 +17,3 @@
|
||||
from { left: 0; }
|
||||
to { left: -$sidebarWidth; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide in animation for extended toolbar container
|
||||
**/
|
||||
|
||||
@include keyframes(slideOutExtContainer) {
|
||||
from { width: $sidebarWidth; }
|
||||
to { width: 0; }
|
||||
}
|
||||
|
||||
@include keyframes(slideInExtContainer) {
|
||||
from { width: 0; }
|
||||
to { width: $sidebarWidth; }
|
||||
}
|
||||
@@ -3,11 +3,17 @@
|
||||
* none is applied. Other browsers already support selecting within inputs while
|
||||
* user-select is none. As such, disallow user-select except on inputs.
|
||||
*/
|
||||
*:not(input) {
|
||||
* {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
@@ -22,6 +28,16 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AtlasKitThemeProvider sets a background color on an app-wrapping div, thereby
|
||||
* preventing transparency in filmstrip-only mode. The selector chosen to
|
||||
* override this behavior is specific to where the AtlasKitThemeProvider might
|
||||
* be placed within the app hierarchy.
|
||||
*/
|
||||
.filmstrip-only #react > .ckAJgx {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,36 @@
|
||||
.localuser {
|
||||
color: #087dba;
|
||||
}
|
||||
.use-new-toolbox {
|
||||
.chatmessage {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.localuser {
|
||||
color: #4C9AFF;
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chatmessage,
|
||||
#smileysarea,
|
||||
#smileysContainer,
|
||||
#usermsg {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
}
|
||||
|
||||
.smileyContainer:hover {
|
||||
background-color: $newToolbarButtonToggleColor;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
color: red;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#contacts_container {
|
||||
cursor: default;
|
||||
|
||||
#contacts {
|
||||
font-size: 12px;
|
||||
bottom: 0px;
|
||||
margin: 0;
|
||||
margin-top: 12px;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
#contacts {
|
||||
.contact-list-item {
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
color: $baseLight;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 36px;
|
||||
list-style-type: none;
|
||||
padding: 0 10%;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background: $toolbarSelectBackground;
|
||||
}
|
||||
|
||||
.contact-list-item-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
margin: 0 8px 0 4px;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.avatar {
|
||||
padding: 0px;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
font-size: 22pt;
|
||||
border-radius: 20px;
|
||||
max-height: 30px;
|
||||
max-width: 30px;
|
||||
}
|
||||
@@ -5,6 +5,20 @@
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.use-new-toolbox {
|
||||
.filmstrip.reduce-height {
|
||||
bottom: $newToolbarSizeWithPadding;
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
transition: bottom .3s;
|
||||
}
|
||||
|
||||
.filmstrip__videos.hidden {
|
||||
bottom: calc(-196px - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
@@ -66,37 +80,6 @@
|
||||
&#filmstripLocalVideo {
|
||||
align-self: flex-end;
|
||||
display: block;
|
||||
|
||||
/**
|
||||
* The invite button style.
|
||||
*/
|
||||
.filmstrip__invite {
|
||||
padding-bottom: 5px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/**
|
||||
* The invite button group style.
|
||||
* TOFIX: use AtlasKit.ButtonGroup if it starts supporting different
|
||||
* flex grow options for the buttons.
|
||||
*/
|
||||
.invite-button-group {
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
& button {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
& > * {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
|
||||
@@ -180,3 +180,18 @@
|
||||
.icon-gsm-bars:before {
|
||||
content: "\e926";
|
||||
}
|
||||
.icon-open_in_new:before {
|
||||
content: "\e89e";
|
||||
}
|
||||
.icon-AUD:before {
|
||||
content: "\e900";
|
||||
}
|
||||
.icon-HD:before {
|
||||
content: "\e927";
|
||||
}
|
||||
.icon-LD:before {
|
||||
content: "\e928";
|
||||
}
|
||||
.icon-SD:before {
|
||||
content: "\e929";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
/**
|
||||
* Toolbar side panel main container element.
|
||||
*/
|
||||
.use-new-toolbox #sideToolbarContainer {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
|
||||
/**
|
||||
* Make the sidebar flush with the top of the toolbar. Take the size of
|
||||
* the toolbar and subtract from 100%.
|
||||
*/
|
||||
height: calc(100% - #{$newToolbarSizeWithPadding});
|
||||
left: 0;
|
||||
|
||||
.side-toolbar-close {
|
||||
background: gray;
|
||||
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 100%;
|
||||
color: white;
|
||||
cursor:pointer;
|
||||
height: 10px;
|
||||
line-height: 10px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
text-align: center;
|
||||
top: 5px;
|
||||
width: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#chatconversation {
|
||||
top: 15px;
|
||||
}
|
||||
}
|
||||
#sideToolbarContainer {
|
||||
background-color: $sideToolbarContainerBg;
|
||||
height: 100%;
|
||||
@@ -44,7 +75,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner container, for example contact list, settings or profile.
|
||||
* Inner container, for example settings or profile.
|
||||
*/
|
||||
.sideToolbarContainer__inner {
|
||||
display: none;
|
||||
|
||||
@@ -19,361 +19,275 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.toolbar-container {
|
||||
display: block;
|
||||
left:0;
|
||||
min-height: 100px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right:0;
|
||||
text-align: center;
|
||||
top:0;
|
||||
z-index: $toolbarZ;
|
||||
.use-new-toolbox {
|
||||
.cxGWJB{
|
||||
bottom: calc(#{$newToolbarSizeWithPadding});
|
||||
}
|
||||
.gXSEsl:nth-child(n+2) {
|
||||
transform: translateX(0) translateY(100%) translateY(16px);
|
||||
-ms-transform: translateX(0) translateY(100%) translateY(16px);
|
||||
-webkit-transform: translateX(0) translateY(100%) translateY(16px);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common toolbar styles.
|
||||
* TODO: when the old filmstrip has been removed, remove the "new-" prefix.
|
||||
*/
|
||||
.toolbar {
|
||||
height: 100%;
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
.new-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
bottom: calc((#{$newToolbarSize} * 2) * -1);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px 8px;
|
||||
position: absolute;
|
||||
transition: bottom .3s ease-in;
|
||||
width: 100%;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
/**
|
||||
* Ensure nested elements that don't have a button class, maybe because they
|
||||
* are wrapped in a React Element, still display in a line.
|
||||
*/
|
||||
> div {
|
||||
display: inline-block;
|
||||
&.visible {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always on top overrides.
|
||||
*/
|
||||
&.always-on-top {
|
||||
/**
|
||||
* Toolbar button styles for always on top.
|
||||
*/
|
||||
.button {
|
||||
font-size: $alwaysOnTopToolbarFontSize;
|
||||
height: $alwaysOnTopToolbarSize;
|
||||
line-height: $alwaysOnTopToolbarSize;
|
||||
width: $alwaysOnTopToolbarSize;
|
||||
&_hangup, &_hangup:hover {
|
||||
font-size: $alwaysOnTopToolbarFontSize;
|
||||
}
|
||||
}
|
||||
&.no-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar button styles.
|
||||
*/
|
||||
.button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
z-index: $zindex1;
|
||||
display: inline-block;
|
||||
font-size: $toolbarFontSize;
|
||||
height: $defaultToolbarSize;
|
||||
line-height: $defaultToolbarSize;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
top:0px;
|
||||
vertical-align: middle;
|
||||
width: $defaultToolbarSize;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&_hangup, &_hangup:hover {
|
||||
color: $hangupColor;
|
||||
font-size: $hangupFontSize;
|
||||
}
|
||||
|
||||
&:not(.toggled) {
|
||||
&:hover, &:active {
|
||||
// sum opacity with background layer should give us 0.8
|
||||
background: $toolbarSelectBackground;
|
||||
}
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background: $toolbarToggleBackground;
|
||||
|
||||
&.icon-camera {
|
||||
@extend .icon-camera-disabled;
|
||||
}
|
||||
|
||||
&.icon-full-screen {
|
||||
@extend .icon-exit-full-screen;
|
||||
}
|
||||
|
||||
&.icon-microphone {
|
||||
@extend .icon-mic-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
|
||||
&:hover, &:active, &.selected {
|
||||
background: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary toolbar styles.
|
||||
*/
|
||||
&_primary {
|
||||
background-color: $toolbarBackground;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 30px;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: $defaultToolbarSize;
|
||||
border-radius: 3px;
|
||||
opacity: 0;
|
||||
|
||||
&.always-on-top {
|
||||
height: $alwaysOnTopToolbarSize;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
@include transform(translateX(-50%));
|
||||
|
||||
> a:first-child.button,
|
||||
> div:first-child .button {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
|
||||
> a:last-child.button,
|
||||
> div:last-child .button {
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&_primary a.button:last-child::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Secondary toolbar styles.
|
||||
*/
|
||||
&_secondary {
|
||||
background-color: $secondaryToolbarBg;
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
.button-group-center,
|
||||
.button-group-left,
|
||||
.button-group-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.button-group-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-group-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite font-awesome styling to match jitsi-icon styling.
|
||||
*/
|
||||
.fa {
|
||||
height: 24px;
|
||||
margin-left: 2px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
i {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: inherit;
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
left: 0;
|
||||
padding-top: 24px;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
transform: translateX(-100%);
|
||||
width: $defaultToolbarSize;
|
||||
-webkit-transform: translateX(-100%);
|
||||
line-height: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: $secToolbarFontSize;
|
||||
height: $secToolbarLineHeight;
|
||||
line-height: $secToolbarLineHeight;
|
||||
}
|
||||
i:hover {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
> * {
|
||||
pointer-events: auto
|
||||
}
|
||||
i.toggled {
|
||||
background: $newToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
.button.toggled:not(.icon-raised-hand):not(.button-active) {
|
||||
background: $secondaryToolbarBg;
|
||||
i.toggled:hover {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.disabled {
|
||||
cursor: initial
|
||||
}
|
||||
|
||||
i.disabled:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
color: $hangupColor;
|
||||
}
|
||||
|
||||
.overflow-menu {
|
||||
font-size: 1.2em;
|
||||
list-style-type: none;
|
||||
/**
|
||||
* Undo atlaskit padding by reducing margins.
|
||||
*/
|
||||
margin: -15px -24px;
|
||||
padding: 4px 0;
|
||||
|
||||
.overflow-menu-item {
|
||||
align-items: center;
|
||||
color: #B8C7E0;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 22px;
|
||||
padding: 5px 12px;
|
||||
|
||||
&:hover {
|
||||
background: #313D52;
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
|
||||
&:hover, &:active, &.selected {
|
||||
background: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.overflow-menu-item-icon {
|
||||
margin-right: 10px;
|
||||
|
||||
i {
|
||||
display: inline;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-text {
|
||||
max-width: 150px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Styles the toolbar in filmstrip-only mode.
|
||||
*/
|
||||
&_filmstrip-only {
|
||||
background-color: $toolbarBackground;
|
||||
border-radius: 3px;
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
width: $defaultFilmStripOnlyToolbarSize;
|
||||
line-height: $newToolbarSize;
|
||||
margin: 0 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 37px;
|
||||
line-height: 37px !important;
|
||||
width: 37px;
|
||||
}
|
||||
.toolbar-button-with-badge {
|
||||
position: relative;
|
||||
|
||||
.button-popover-message {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.toolbar-button-wrapper:first-child .button {
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
.toolbar-button-wrapper:last-child .button {
|
||||
border-bottom-right-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
.badge-round {
|
||||
bottom: -5px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
min-width: 20px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar specific round badge.
|
||||
*/
|
||||
.badge-round {
|
||||
bottom: 9px;
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
.toolbox-button-wth-dialog {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.toolbox-icon {
|
||||
height: $newToolbarSize;
|
||||
font-size: 24px;
|
||||
width: $newToolbarSize;
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip-only {
|
||||
.toolbox,
|
||||
.toolbox-toolbars {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
.always-on-top-toolbox,
|
||||
.filmstrip-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.toggled {
|
||||
background: $newToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
i.toggled:hover:not(.disabled) {
|
||||
background-color: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
color: $hangupColor;
|
||||
}
|
||||
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.always-on-top-toolbox {
|
||||
flex-direction: row;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
transform: translateX(-50%);
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i {
|
||||
font-size: $alwaysOnTopToolbarFontSize;
|
||||
height: $alwaysOnTopToolbarSize;
|
||||
line-height: $alwaysOnTopToolbarSize;
|
||||
width: $alwaysOnTopToolbarSize;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
.toolbox-button:first-child i {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
.toolbox-button:last-child i {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.subject {
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
box-shadow: 0 0 2px #000000, 0 0 10px #000000;
|
||||
margin-left: 40%;
|
||||
margin-right: 40%;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: auto;
|
||||
z-index: $zindex3;
|
||||
|
||||
&.subject_slide-in {
|
||||
top: 80px;
|
||||
@include transition(top .3s ease-in);
|
||||
.filmstrip-toolbox {
|
||||
i {
|
||||
font-size: 1.9em;
|
||||
height: 37px;
|
||||
line-height: 37px;
|
||||
width: 37px;
|
||||
}
|
||||
|
||||
&.subject_slide-out {
|
||||
top: 0;
|
||||
@include transition(top .3s ease-out);
|
||||
.toolbox-button:first-child i {
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.toolbox-button:last-child i {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
#toolbar_button_profile {
|
||||
height: $toolbarAvatarSize + 2*$toolbarAvatarPadding;
|
||||
}
|
||||
|
||||
a.button>#avatar {
|
||||
border-radius: 50%;
|
||||
padding-bottom: $toolbarAvatarPadding;
|
||||
padding-top: $toolbarAvatarPadding;
|
||||
width: $toolbarAvatarSize;
|
||||
}
|
||||
|
||||
#feedbackButton {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* START of slide in animation for extended toolbar.
|
||||
*/
|
||||
@include keyframes(slideInX) {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(0%); }
|
||||
}
|
||||
|
||||
.slideInX {
|
||||
@include animation('slideInX .5s forwards');
|
||||
}
|
||||
|
||||
@include keyframes(slideOutX) {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-100%); }
|
||||
}
|
||||
|
||||
.slideOutX {
|
||||
@include animation('slideOutX .5s forwards');
|
||||
}
|
||||
|
||||
@include keyframes(slideInExtX) {
|
||||
0% { transform: translateX(-500%); }
|
||||
100% { transform: translateX(0%); }
|
||||
}
|
||||
|
||||
.slideInExtX {
|
||||
@include animation('slideInExtX .5s forwards');
|
||||
}
|
||||
|
||||
@include keyframes(slideOutExtX) {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-500%); }
|
||||
}
|
||||
|
||||
.slideOutExtX {
|
||||
@include animation('slideOutExtX .5s forwards');
|
||||
}
|
||||
|
||||
/**
|
||||
* END of slide out animation for extended toolbar.
|
||||
*/
|
||||
|
||||
/**
|
||||
* START of slide in / out animation for main toolbar.
|
||||
*/
|
||||
@include keyframes(slideInY) {
|
||||
100% { transform: translateY(0%); }
|
||||
}
|
||||
|
||||
.slideInY {
|
||||
@include animation('slideInY .5s forwards');
|
||||
}
|
||||
|
||||
@include keyframes(slideOutY) {
|
||||
0% { transform: translateY(0%); }
|
||||
100% { transform: translateY(-100%); }
|
||||
}
|
||||
|
||||
.slideOutY {
|
||||
@include animation('slideOutY .5s forwards');
|
||||
}
|
||||
/**
|
||||
* END of slide in / out animation for main toolbar.
|
||||
*/
|
||||
|
||||
/**
|
||||
* START of slide in animation for extended toolbar panel.
|
||||
*/
|
||||
|
||||
@@ -35,21 +35,14 @@ $defaultDarkColor: #2b3d5c;
|
||||
$alwaysOnTopToolbarFontSize: 1em;
|
||||
$alwaysOnTopToolbarSize: 30px;
|
||||
$defaultToolbarSize: 50px;
|
||||
$defaultFilmStripOnlyToolbarSize: 37px;
|
||||
$secToolbarFontSize: 1.9em;
|
||||
$secToolbarLineHeight: 45px;
|
||||
$toolbarAvatarPadding: 10px;
|
||||
$toolbarAvatarSize: 40px;
|
||||
$toolbarFontSize: 1.9em;
|
||||
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
|
||||
$newToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
|
||||
$newToolbarButtonToggleColor: rgba(14, 20, 35, 1);
|
||||
$newToolbarFontSize: 1.9em;
|
||||
$newToolbarSize: 32px;
|
||||
$newToolbarSizeWithPadding: calc(32px + 32px);
|
||||
$toolbarTitleFontSize: 19px;
|
||||
|
||||
/**
|
||||
* Main controls
|
||||
* TODO: looks like we don't use it
|
||||
*/
|
||||
$inputSemiBackground: rgba(132, 132, 132, .8);
|
||||
$inputLightBackground: #EBEBEB;
|
||||
|
||||
/**
|
||||
* Video layout
|
||||
*/
|
||||
@@ -74,10 +67,6 @@ $feedbackInputBg: #fff;
|
||||
$feedbackTextColor: #000;
|
||||
$feedbackInputTextColor: #333;
|
||||
$feedbackInputPlaceholderColor: #777;
|
||||
$rateStarLabelColor: #333;
|
||||
$rateStarDefault: #ccc;
|
||||
$rateStarActivity: #165ecc;
|
||||
$rateStarSize: 34px;
|
||||
|
||||
/**
|
||||
* Modals
|
||||
|
||||
@@ -19,6 +19,20 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&.use-new-toolbox {
|
||||
/**
|
||||
* Adjust the height of the filmstrip as the toolbar is displayed.
|
||||
*/
|
||||
.filmstrip {
|
||||
top: 0;
|
||||
transition: height .3s ease-in;
|
||||
|
||||
&.reduce-height {
|
||||
height: calc(100% - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
align-items: flex-end;
|
||||
box-sizing: border-box;
|
||||
@@ -32,14 +46,7 @@
|
||||
* any parent is also fixed.
|
||||
*/
|
||||
position: fixed;
|
||||
|
||||
/**
|
||||
* z-index adjusting is needed because the video state indicator has to
|
||||
* display over the filmstrip when no videos are displayed but still be
|
||||
* clickable but its inline dialogs must display over the video state
|
||||
* indicator when videos are displayed.
|
||||
*/
|
||||
z-index: #{$tooltipsZ + 1};
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
/**
|
||||
* Hide videos by making them slight to the right.
|
||||
@@ -59,8 +66,9 @@
|
||||
&#remoteVideos {
|
||||
/**
|
||||
* Remove horizontal filmstrip padding used to prevent videos
|
||||
* from overlapping the left-side toolbar. An id selector is
|
||||
* used to match id specificity with filmstrip styles.
|
||||
* from overlapping getting near the left-side of the screen.
|
||||
* An id selector is used to match id specificity with filmstrip
|
||||
* styles.
|
||||
*/
|
||||
padding-left: 0;
|
||||
transition: right 2s;
|
||||
@@ -68,8 +76,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styles the local Video and invite button to better fit the
|
||||
* vertical filmstrip layout.
|
||||
* Re-styles the local Video to better fit vertical filmstrip layout.
|
||||
*/
|
||||
#filmstripLocalVideo {
|
||||
align-self: initial;
|
||||
@@ -78,12 +85,6 @@
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
|
||||
.filmstrip__invite {
|
||||
padding-bottom: 0px;
|
||||
padding-top: 5px;
|
||||
z-index: $dropdownZ;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,20 +169,6 @@
|
||||
.filmstrip__videos-filmstripOnly {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* In filmstrip only mode, the toolbar is normally displayed in the
|
||||
* vertical center of the filmstrip strip. In vertical filmstrip mode,
|
||||
* that alignment makes the toolbar appear floating and detached from
|
||||
* the filmstrip. So, instead anchor the toolbar next to the local
|
||||
* video.
|
||||
*/
|
||||
.toolbar_filmstrip-only {
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
body.welcome-page {
|
||||
background: inherit;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
font-family: $welcomePageFontFamily;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
|
||||
.header {
|
||||
@@ -19,6 +23,7 @@
|
||||
justify-content: space-around;
|
||||
margin-top: 120px;
|
||||
margin-bottom: 20px;
|
||||
max-width: calc(100% - 40px);
|
||||
min-height: 286px;
|
||||
width: 645px;
|
||||
}
|
||||
@@ -50,6 +55,7 @@
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
max-width: calc(100% - 40px);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
|
||||
75
css/deep-linking/_desktop.scss
Normal file
75
css/deep-linking/_desktop.scss
Normal file
@@ -0,0 +1,75 @@
|
||||
.deep-linking-desktop {
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 55px;
|
||||
background-color: #f1f2f5;
|
||||
padding-top: 15px;
|
||||
padding-left: 50px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex: 0 0 55px;
|
||||
.logo {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-flow: row;
|
||||
.leftColumn {
|
||||
left: 0px;
|
||||
width: 50%;
|
||||
min-height: 156px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
.leftColumnContent{
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
.image {
|
||||
background-image: url('../images/deep-linking-image.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.rightColumn {
|
||||
top: 0px;
|
||||
width: 50%;
|
||||
min-height: 156px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
.rightColumnContent {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding: 20px 20px 20px 60px;
|
||||
.title {
|
||||
color: #1c2946;
|
||||
}
|
||||
.description {
|
||||
color: #606a80;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.buttons {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
css/deep-linking/_main.scss
Normal file
3
css/deep-linking/_main.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@import 'desktop';
|
||||
@import 'mobile';
|
||||
@import 'no-mobile-app';
|
||||
@@ -1,10 +1,23 @@
|
||||
.unsupported-mobile-browser {
|
||||
.deep-linking-mobile {
|
||||
background-color: #fff;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
background-color: #f1f2f5;
|
||||
text-align: center;
|
||||
.logo {
|
||||
margin-top: 15px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none
|
||||
}
|
||||
@@ -20,10 +33,19 @@
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-weight: bolder;
|
||||
padding: 10px 10px 0px 10px;
|
||||
}
|
||||
|
||||
&__text,
|
||||
.unsupported-dial-in {
|
||||
.deep-linking-dial-in {
|
||||
font-size: 1.2em;
|
||||
line-height: em(29px, 21px);
|
||||
margin-bottom: 0.65em;
|
||||
@@ -39,21 +61,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__logo {
|
||||
height: 108px;
|
||||
width: 77px;
|
||||
&__href {
|
||||
height: 2.2857142857142856em;
|
||||
line-height: 2.2857142857142856em;
|
||||
margin: 18px auto 20px;
|
||||
max-width: 300px;
|
||||
width: auto;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
&__button {
|
||||
border: 0;
|
||||
height: 2.2857142857142856em;
|
||||
line-height: 2.2857142857142856em;
|
||||
margin: 18px auto 20px;
|
||||
margin: 18px auto 10px;
|
||||
padding: 0px 10px 0px 10px;
|
||||
max-width: 300px;
|
||||
width: auto;
|
||||
@include border-radius(3px);
|
||||
background-color: $unsupportedBrowserButtonBgColor;
|
||||
color: #505F79;
|
||||
font-weight: bold;
|
||||
|
||||
&:active {
|
||||
background-color: $unsupportedBrowserButtonBgColor;
|
||||
@@ -69,7 +97,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.unsupported-dial-in {
|
||||
.deep-linking-dial-in {
|
||||
display: none;
|
||||
|
||||
&.has-numbers {
|
||||
@@ -48,7 +48,6 @@
|
||||
@import 'popup_menu';
|
||||
@import 'recording';
|
||||
@import 'login_menu';
|
||||
@import 'contact_list';
|
||||
@import 'chat';
|
||||
@import 'ringing/ringing';
|
||||
@import 'welcome_page';
|
||||
@@ -72,5 +71,6 @@
|
||||
@import 'unsupported-browser/main';
|
||||
@import 'modals/invite/add-people';
|
||||
@import 'vertical_filmstrip_overrides';
|
||||
@import 'deep-linking/main';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
@@ -47,10 +47,6 @@
|
||||
|
||||
.feedback-dialog {
|
||||
.details {
|
||||
margin-top: 20px;
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
|
||||
textarea {
|
||||
min-height: 100px;
|
||||
}
|
||||
@@ -77,16 +73,15 @@
|
||||
text-align: center;
|
||||
|
||||
.star-label {
|
||||
color: $rateStarLabelColor;
|
||||
font-size: 14px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.star-btn {
|
||||
color: $rateStarDefault;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: $rateStarSize;
|
||||
font-size: 34px;
|
||||
outline: none;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
@@ -95,7 +90,7 @@
|
||||
&.active,
|
||||
&:hover,
|
||||
&.starHover {
|
||||
color: $rateStarActivity;
|
||||
color: #36B37E;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,15 @@
|
||||
}
|
||||
|
||||
.add-telephone-icon {
|
||||
display: flex;
|
||||
height: 28px;
|
||||
transform: scaleX(-1);
|
||||
width: 28px;
|
||||
|
||||
i {
|
||||
line-height: 28px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.use-new-toolbox {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-dialog {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
@@ -38,7 +42,7 @@
|
||||
.info-dialog-copy-element {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
@@ -55,12 +59,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.info-dialog-conference-url {
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: text;
|
||||
white-space: nowrap;
|
||||
.info-dialog-conference-url,
|
||||
.info-dialog-live-stream-url {
|
||||
width: max-content;
|
||||
width: -moz-max-content;
|
||||
width: -webkit-max-content;
|
||||
word-break: break-all;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.info-dialog-dial-in {
|
||||
@@ -77,19 +82,29 @@
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.info-dialog-url-text,
|
||||
.info-dialog-url-text:hover {
|
||||
color: inherit;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.info-dialog-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.info-password,
|
||||
.info-dialog-password,
|
||||
.info-password,
|
||||
.info-password-form {
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info-password-field {
|
||||
margin-left: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -110,10 +125,6 @@
|
||||
.info-password-local {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.conference-id {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.dial-in-page {
|
||||
@@ -125,10 +136,6 @@
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
* {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.dial-in-numbers-list {
|
||||
font-size: 24px;
|
||||
margin-top: 20px;
|
||||
@@ -139,3 +146,12 @@
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
.info-dialog,
|
||||
.dial-in-page {
|
||||
* {
|
||||
user-select: text;
|
||||
-moz-user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog-form {
|
||||
.video-quality-dialog-title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.video-state-indicator {
|
||||
background: $videoStateIndicatorBackground;
|
||||
cursor: default;
|
||||
@@ -162,11 +168,11 @@
|
||||
}
|
||||
|
||||
.centeredVideoLabel.moveToCorner {
|
||||
z-index: $tooltipsZ;
|
||||
z-index: $zindex3;
|
||||
}
|
||||
|
||||
#videoResolutionLabel {
|
||||
z-index: #{$tooltipsZ + 1};
|
||||
z-index: $zindex3 + 1;
|
||||
}
|
||||
|
||||
.centeredVideoLabel {
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
@import 'no-mobile-app';
|
||||
@import 'unsupported-desktop-browser';
|
||||
@import 'unsupported-mobile-browser';
|
||||
|
||||
@@ -97,11 +97,6 @@ api.executeCommand('toggleFilmStrip')
|
||||
api.executeCommand('toggleChat')
|
||||
```
|
||||
|
||||
* **toggleContactList** - Hides / shows the contact list. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleContactList')
|
||||
```
|
||||
|
||||
* **toggleShareScreen** - Starts / stops screen sharing. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleShareScreen')
|
||||
|
||||
@@ -20,6 +20,14 @@ wget -qO - https://download.jitsi.org/jitsi-key.gpg.key | apt-key add -
|
||||
apt-get update
|
||||
```
|
||||
|
||||
(If you get an error:
|
||||
E: The method driver /usr/lib/apt/methods/https could not be found.
|
||||
run:
|
||||
```sh
|
||||
apt-get install apt-transport-https
|
||||
```
|
||||
)
|
||||
|
||||
### Install Jitsi Meet
|
||||
|
||||
Note : Something to consider before installation is how you're planning to serve Jitsi Meet. The installer will check if Nginx or Apache is present (with this order) and configure a virtualhost within the web server it finds to serve Jitsi Meet. If none of the above is found it then configures itself to be served via jetty. So if for example you are planning on deploying Jitsi Meet with a web server, you have to make sure to install the server **before** installing jitsi-meet.
|
||||
|
||||
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
@@ -23,7 +23,9 @@
|
||||
<glyph unicode="" glyph-name="event_note" d="M598 426v-84h-300v84h300zM810 214v468h-596v-468h596zM810 896c46 0 86-40 86-86v-596c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h42v86h86v-86h340v86h86v-86h42zM726 598v-86h-428v86h428z" />
|
||||
<glyph unicode="" glyph-name="phone-talk" d="M640 512c0 70-58 128-128 128v86c118 0 214-96 214-214h-86zM810 512c0 166-132 298-298 298v86c212 0 384-172 384-384h-86zM854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24z" />
|
||||
<glyph unicode="" glyph-name="public" d="M764 282c56 60 90 142 90 230 0 142-88 266-214 316v-18c0-46-40-84-86-84h-84v-86c0-24-20-42-44-42h-84v-86h256c24 0 42-18 42-42v-128h42c38 0 70-26 82-60zM470 174v82c-46 0-86 40-86 86v42l-204 204c-6-24-10-50-10-76 0-174 132-318 300-338zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
||||
<glyph unicode="" glyph-name="open_in_new" d="M598 896h298v-298h-86v152l-418-418-60 60 418 418h-152v86zM810 214v298h86v-298c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h298v-86h-298v-596h596z" />
|
||||
<glyph unicode="" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
|
||||
<glyph unicode="" glyph-name="AUD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM308.25 387.3h57.225l-87.675 252.525h-62.125l-87.675-252.525h53.025l19.425 60.2h88.725l19.075-60.2zM461.9 639.825h-52.85v-165.375c0-56 41.125-93.625 105.7-93.625 64.75 0 105.875 37.625 105.875 93.625v165.375h-52.85v-159.95c0-31.85-19.075-52.15-53.025-52.15-33.775 0-52.85 20.3-52.85 52.15v159.95zM682.225 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM735.075 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15zM243.5 587.325l-31.675-99.050h66.15l-31.325 99.050h-3.15z" />
|
||||
<glyph unicode="" glyph-name="avatar" d="M512 204c106 0 200 56 256 138-2 84-172 132-256 132-86 0-254-48-256-132 56-82 150-138 256-138zM512 810c-70 0-128-58-128-128s58-128 128-128 128 58 128 128-58 128-128 128zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
||||
<glyph unicode="" glyph-name="download" d="M726 470h-128v170h-172v-170h-128l214-214zM826 596c110-8 198-100 198-212 0-118-96-214-214-214h-554c-142 0-256 114-256 256 0 132 100 240 228 254 54 102 160 174 284 174 156 0 284-110 314-258z" />
|
||||
<glyph unicode="" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />
|
||||
@@ -62,4 +64,7 @@
|
||||
<glyph unicode="" glyph-name="visibility-off" d="M506 640h6c70 0 128-58 128-128v-8zM322 606c-14-28-24-60-24-94 0-118 96-214 214-214 34 0 66 10 94 24l-66 66c-8-2-18-4-28-4-70 0-128 58-128 128 0 10 2 20 4 28zM86 842l54 54 756-756-54-54c-47.968 47.365-96.266 94.401-144 142-58-24-120-36-186-36-214 0-396 132-470 320 34 84 90 156 160 212-39.017 38.983-77.307 78.693-116 118zM512 726c-28 0-54-6-78-16l-92 92c52 20 110 30 170 30 214 0 394-132 468-320-32-80-82-148-146-202l-124 124c10 24 16 50 16 78 0 118-96 214-214 214z" />
|
||||
<glyph unicode="" glyph-name="dialpad" d="M512 982c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 726c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM768 726c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM768 470c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 470c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM768 810c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86zM256 470c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM256 726c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM256 982c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 214c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86z" />
|
||||
<glyph unicode="" glyph-name="gsm-bars-black" d="M896 1024c70.692 0 128-57.308 128-128v-768c0-70.692-57.308-128-128-128s-128 57.308-128 128v768c0 70.692 57.308 128 128 128zM512 768c70.692 0 128-57.308 128-128v-512c0-70.692-57.308-128-128-128s-128 57.308-128 128v512c0 70.692 57.308 128 128 128zM128 384v0c70.692 0 128-57.308 128-128v-128c0-70.692-57.308-128-128-128s-128 57.308-128 128v128c0 70.692 57.308 128 128 128v0z" />
|
||||
<glyph unicode="" glyph-name="HD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM481.359 384v255.823h-54.273v-103.18h-116.813v103.18h-54.273v-255.823h54.273v106.903h116.813v-106.903h54.273zM544.258 640v-256h102.077c77.636 0 121.665 46.626 121.665 129.773 0 80.133-44.569 126.227-121.665 126.227h-102.077zM598.531 594.26v-164.521h39.177c47.983 0 74.94 29.075 74.94 83.147 0 51.767-27.855 81.374-74.94 81.374h-39.177z" />
|
||||
<glyph unicode="" glyph-name="LD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM472.4 433.325h-112.35v206.5h-52.85v-252.525h165.2v46.025zM520.35 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM573.2 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15z" />
|
||||
<glyph unicode="" glyph-name="SD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM281.6 451.175c1.925-47.075 40.95-76.65 101.15-76.65 63.35 0 102.375 31.15 102.375 82.075 0 39.2-21.875 61.075-72.625 71.75l-30.45 6.475c-29.575 6.3-41.65 15.225-41.65 30.8 0 19.25 17.5 31.5 43.925 31.5 25.55 0 44.1-13.3 46.55-33.25h49.7c-1.575 44.975-40.95 76.125-96.6 76.125-58.275 0-96.6-31.325-96.6-78.925 0-38.5 22.575-62.475 68.6-72.1l32.9-7c30.975-6.65 43.575-15.925 43.575-32.025 0-19.075-19.425-32.375-46.9-32.375-29.75 0-50.4 13.125-52.85 33.6h-51.1zM535 633.7v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM587.85 588.55v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
1193
fonts/selection.json
1193
fonts/selection.json
File diff suppressed because it is too large
Load Diff
BIN
images/logo-deep-linking.png
Normal file
BIN
images/logo-deep-linking.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -23,9 +23,11 @@ var interfaceConfig = {
|
||||
SHOW_BRAND_WATERMARK: false,
|
||||
BRAND_WATERMARK_LINK: '',
|
||||
SHOW_POWERED_BY: false,
|
||||
SHOW_DEEP_LINKING_IMAGE: false,
|
||||
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
|
||||
DISPLAY_WELCOME_PAGE_CONTENT: true,
|
||||
APP_NAME: 'Jitsi Meet',
|
||||
NATIVE_APP_NAME: 'Jitsi Meet',
|
||||
LANG_DETECTION: false, // Allow i18n to detect the system language
|
||||
INVITATION_POWERED_BY: true,
|
||||
|
||||
@@ -44,22 +46,18 @@ var interfaceConfig = {
|
||||
'microphone', 'camera', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup',
|
||||
|
||||
// extended toolbar
|
||||
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip' ],
|
||||
'profile', 'info', 'chat', 'recording', 'etherpad',
|
||||
'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip',
|
||||
'invite', 'feedback', 'stats', 'shortcuts'
|
||||
],
|
||||
|
||||
/**
|
||||
* Main Toolbar Buttons
|
||||
* All of them should be in TOOLBAR_BUTTONS
|
||||
*/
|
||||
MAIN_TOOLBAR_BUTTONS: [ 'microphone', 'camera', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup' ],
|
||||
SETTINGS_SECTIONS: [ 'language', 'devices', 'moderator' ],
|
||||
INVITE_OPTIONS: [ 'invite', 'dialout', 'addtocall' ],
|
||||
|
||||
// Determines how the video would fit the screen. 'both' would fit the whole
|
||||
// screen, 'height' would fit the original video height to the height of the
|
||||
// screen, 'width' would fit the original video width to the width of the
|
||||
// screen respecting ratio.
|
||||
VIDEO_LAYOUT_FIT: 'both',
|
||||
SHOW_CONTACTLIST_AVATARS: true,
|
||||
|
||||
/**
|
||||
* Whether to only show the filmstrip (and hide the toolbar).
|
||||
@@ -165,7 +163,7 @@ var interfaceConfig = {
|
||||
/**
|
||||
* Specify mobile app scheme for opening the app from the mobile browser.
|
||||
*/
|
||||
// MOBILE_APP_SCHEME: 'org.jitsi.meet'
|
||||
// APP_SCHEME: 'org.jitsi.meet'
|
||||
};
|
||||
|
||||
/* eslint-enable no-unused-vars, no-var, max-len */
|
||||
|
||||
@@ -3,7 +3,7 @@ PODS:
|
||||
- React/Core (= 0.51.0)
|
||||
- react-native-background-timer (2.0.0):
|
||||
- React
|
||||
- react-native-calendar-events (1.4.3):
|
||||
- react-native-calendar-events (1.5.0):
|
||||
- React
|
||||
- react-native-fetch-blob (0.10.6):
|
||||
- React/Core
|
||||
@@ -11,7 +11,8 @@ PODS:
|
||||
- React
|
||||
- react-native-locale-detector (1.0.0):
|
||||
- React
|
||||
- react-native-webrtc (1.58.2)
|
||||
- react-native-webrtc (1.58.2):
|
||||
- React
|
||||
- React/BatchedBridge (0.51.0):
|
||||
- React/Core
|
||||
- React/cxxreact_legacy
|
||||
@@ -43,10 +44,10 @@ PODS:
|
||||
- React/Core
|
||||
- React/fishhook
|
||||
- React/RCTBlob
|
||||
- RNSound (0.10.4):
|
||||
- RNSound (0.10.9):
|
||||
- React/Core
|
||||
- RNSound/Core (= 0.10.4)
|
||||
- RNSound/Core (0.10.4):
|
||||
- RNSound/Core (= 0.10.9)
|
||||
- RNSound/Core (0.10.9):
|
||||
- React/Core
|
||||
- RNVectorIcons (4.4.2):
|
||||
- React
|
||||
@@ -102,8 +103,8 @@ SPEC CHECKSUMS:
|
||||
react-native-fetch-blob: 63394b1d7b0781547b3e4463b3195790177b1222
|
||||
react-native-keep-awake: 0de4bd66de0c23178107dce0c2fcc3354b2a8e94
|
||||
react-native-locale-detector: d1b2c6fe5abb56e3a1efb6c2d6f308c05c4251f1
|
||||
react-native-webrtc: bc044ca9530fc802e7533f247aa08fe1b6bf8dc5
|
||||
RNSound: d0818fe2435254fe30540fae48a429c5ffb72e09
|
||||
react-native-webrtc: 31b6d3f1e3e2ce373aa43fd682b04367250f807d
|
||||
RNSound: b360b3862d3118ed1c74bb9825696b5957686ac4
|
||||
RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
|
||||
yoga: 17521bbb0dd54a47c0b3ac43253e78cdac7488e0
|
||||
|
||||
|
||||
@@ -255,6 +255,7 @@
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
|
||||
CODE_SIGN_ENTITLEMENTS = app.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -286,6 +287,7 @@
|
||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
|
||||
CODE_SIGN_ENTITLEMENTS = app.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
|
||||
@@ -55,8 +55,8 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>See your scheduled conferences in the app.</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>See your scheduled conferences in the app.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Participate in conferences with video.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
||||
@@ -21,19 +21,73 @@ class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var videoButton: UIButton?
|
||||
|
||||
private var jitsiMeetCoordinator: JitsiMeetPresentationCoordinator?
|
||||
fileprivate var pipViewCoordinator: PiPViewCoordinator?
|
||||
fileprivate var jitsiMeetView: JitsiMeetView?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize,
|
||||
with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
let rect = CGRect(origin: CGPoint.zero, size: size)
|
||||
pipViewCoordinator?.resetBounds(bounds: rect)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func openJitsiMeet(sender: Any?) {
|
||||
let jitsiMeetCoordinator = JitsiMeetPresentationCoordinator()
|
||||
self.jitsiMeetCoordinator = jitsiMeetCoordinator
|
||||
jitsiMeetCoordinator.jitsiMeetView().welcomePageEnabled = true
|
||||
jitsiMeetCoordinator.jitsiMeetView().load(nil)
|
||||
jitsiMeetCoordinator.show()
|
||||
cleanUp()
|
||||
|
||||
// create and configure jitsimeet view
|
||||
let jitsiMeetView = JitsiMeetView()
|
||||
jitsiMeetView.welcomePageEnabled = true
|
||||
jitsiMeetView.pictureInPictureEnabled = true
|
||||
jitsiMeetView.load(nil)
|
||||
jitsiMeetView.delegate = self
|
||||
self.jitsiMeetView = jitsiMeetView
|
||||
|
||||
// Enable jitsimeet view to be a view that can be displayed
|
||||
// on top of all the things, and let the coordinator to manage
|
||||
// the view state and interactions
|
||||
pipViewCoordinator = PiPViewCoordinator(withView: jitsiMeetView)
|
||||
pipViewCoordinator?.configureAsStickyView(withParentView: view)
|
||||
|
||||
// animate in
|
||||
jitsiMeetView.alpha = 0
|
||||
pipViewCoordinator?.show()
|
||||
}
|
||||
|
||||
fileprivate func cleanUp() {
|
||||
jitsiMeetView?.removeFromSuperview()
|
||||
jitsiMeetView = nil
|
||||
pipViewCoordinator = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: JitsiMeetViewDelegate {
|
||||
|
||||
func conferenceFailed(_ data: [AnyHashable : Any]!) {
|
||||
hideJitsiMeetViewAndCleanUp()
|
||||
}
|
||||
|
||||
func conferenceLeft(_ data: [AnyHashable : Any]!) {
|
||||
hideJitsiMeetViewAndCleanUp()
|
||||
}
|
||||
|
||||
func enterPicture(inPicture data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.pipViewCoordinator?.enterPictureInPicture()
|
||||
}
|
||||
}
|
||||
|
||||
private func hideJitsiMeetViewAndCleanUp() {
|
||||
DispatchQueue.main.async {
|
||||
self.pipViewCoordinator?.hide() { _ in
|
||||
self.cleanUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -27,12 +27,14 @@
|
||||
0BCA496C1EC4BBF900B793EE /* jitsi.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0BCA496B1EC4BBF900B793EE /* jitsi.ttf */; };
|
||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */; };
|
||||
75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
|
||||
75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
|
||||
412BF89D206AA66F0053B9E5 /* InviteSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 412BF89C206AA66F0053B9E5 /* InviteSearch.m */; };
|
||||
412BF89F206ABAE40053B9E5 /* InviteSearch.h in Headers */ = {isa = PBXBuildFile; fileRef = 412BF89E206AA82F0053B9E5 /* InviteSearch.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
|
||||
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
|
||||
C6A3425F204EF76800E062DD /* PiPWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425C204EF76800E062DD /* PiPWindow.swift */; };
|
||||
C6A34260204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */; };
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
|
||||
C6A3426D204F1C3300E062DD /* JitsiMeetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */; };
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
|
||||
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -60,14 +62,16 @@
|
||||
0BD906E51EC0C00300C8C18E /* JitsiMeet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JitsiMeet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeet.h; sourceTree = "<group>"; };
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
|
||||
75635B0920751D6D00F29C9F /* left.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = left.wav; path = ../../sounds/left.wav; sourceTree = "<group>"; };
|
||||
412BF89C206AA66F0053B9E5 /* InviteSearch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InviteSearch.m; sourceTree = "<group>"; };
|
||||
412BF89E206AA82F0053B9E5 /* InviteSearch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InviteSearch.h; sourceTree = "<group>"; };
|
||||
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
|
||||
C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
|
||||
C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
|
||||
C6A3425C204EF76800E062DD /* PiPWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PiPWindow.swift; sourceTree = "<group>"; };
|
||||
C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JitsiMeetPresentationCoordinator.swift; sourceTree = "<group>"; };
|
||||
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
|
||||
C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetViewController.swift; sourceTree = "<group>"; };
|
||||
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -89,6 +93,8 @@
|
||||
0BCA49681EC4BBE500B793EE /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
75635B0820751D6D00F29C9F /* joined.wav */,
|
||||
75635B0920751D6D00F29C9F /* left.wav */,
|
||||
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
|
||||
C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
|
||||
C6245F5C2053091D0040BE68 /* image-resize@3x.png */,
|
||||
@@ -123,6 +129,8 @@
|
||||
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
|
||||
0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
|
||||
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
|
||||
412BF89E206AA82F0053B9E5 /* InviteSearch.h */,
|
||||
412BF89C206AA66F0053B9E5 /* InviteSearch.m */,
|
||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
||||
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
|
||||
@@ -165,9 +173,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C6A3425E204EF76800E062DD /* DragGestureController.swift */,
|
||||
C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */,
|
||||
C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */,
|
||||
C6A3425C204EF76800E062DD /* PiPWindow.swift */,
|
||||
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */,
|
||||
);
|
||||
path = "picture-in-picture";
|
||||
sourceTree = "<group>";
|
||||
@@ -180,6 +186,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
|
||||
412BF89F206ABAE40053B9E5 /* InviteSearch.h in Headers */,
|
||||
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
|
||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
||||
@@ -252,6 +259,8 @@
|
||||
0BCA496C1EC4BBF900B793EE /* jitsi.ttf in Resources */,
|
||||
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */,
|
||||
0BC4B8691F8C03A700CE8B21 /* CallKitIcon.png in Resources */,
|
||||
75635B0B20751D6D00F29C9F /* left.wav in Resources */,
|
||||
75635B0A20751D6D00F29C9F /* joined.wav in Resources */,
|
||||
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -337,16 +346,15 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
|
||||
C6A34260204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift in Sources */,
|
||||
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
|
||||
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
|
||||
C6A3426D204F1C3300E062DD /* JitsiMeetViewController.swift in Sources */,
|
||||
C6A3425F204EF76800E062DD /* PiPWindow.swift in Sources */,
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
|
||||
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
|
||||
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
|
||||
412BF89D206AA66F0053B9E5 /* InviteSearch.m in Sources */,
|
||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
|
||||
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
|
||||
@@ -405,7 +413,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -458,7 +466,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
||||
@@ -29,8 +29,13 @@ RCT_EXPORT_MODULE();
|
||||
- (NSDictionary *)constantsToExport {
|
||||
NSDictionary<NSString *, id> *infoDictionary
|
||||
= [[NSBundle mainBundle] infoDictionary];
|
||||
|
||||
// calendarEnabled
|
||||
BOOL calendarEnabled
|
||||
= infoDictionary[@"NSCalendarsUsageDescription"] != nil;
|
||||
|
||||
// name
|
||||
NSString *name = infoDictionary[@"CFBundleDisplayName"];
|
||||
NSString *version = infoDictionary[@"CFBundleShortVersionString"];
|
||||
|
||||
if (name == nil) {
|
||||
name = infoDictionary[@"CFBundleName"];
|
||||
@@ -38,6 +43,13 @@ RCT_EXPORT_MODULE();
|
||||
name = @"";
|
||||
}
|
||||
}
|
||||
|
||||
// sdkBundlePath
|
||||
NSString *sdkBundlePath = [[NSBundle bundleForClass:self.class] bundlePath];
|
||||
|
||||
// version
|
||||
NSString *version = infoDictionary[@"CFBundleShortVersionString"];
|
||||
|
||||
if (version == nil) {
|
||||
version = infoDictionary[@"CFBundleVersion"];
|
||||
if (version == nil) {
|
||||
@@ -46,7 +58,9 @@ RCT_EXPORT_MODULE();
|
||||
}
|
||||
|
||||
return @{
|
||||
@"calendarEnabled": [NSNumber numberWithBool:calendarEnabled],
|
||||
@"name": name,
|
||||
@"sdkBundlePath": sdkBundlePath,
|
||||
@"version": version
|
||||
};
|
||||
};
|
||||
|
||||
49
ios/sdk/src/InviteSearch.h
Normal file
49
ios/sdk/src/InviteSearch.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@class InviteSearchController;
|
||||
|
||||
@protocol InviteSearchControllerDelegate
|
||||
|
||||
/**
|
||||
* Called when an InviteSearchController has results for a query that was previously provided.
|
||||
*/
|
||||
- (void)inviteSearchController:(InviteSearchController * _Nonnull)controller
|
||||
didReceiveResults:(NSArray<NSDictionary*> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query;
|
||||
|
||||
/**
|
||||
* Called when all invitations were sent successfully.
|
||||
*/
|
||||
- (void)inviteDidSucceedForSearchController:(InviteSearchController * _Nonnull)searchController;
|
||||
|
||||
/**
|
||||
* Called when one or more invitations fails to send successfully.
|
||||
*/
|
||||
- (void)inviteDidFailForItems:(NSArray<NSDictionary *> * _Nonnull)items
|
||||
fromSearchController:(InviteSearchController * _Nonnull)searchController;
|
||||
|
||||
@end
|
||||
|
||||
@interface InviteSearchController: NSObject
|
||||
|
||||
@property (nonatomic, nullable, weak) id<InviteSearchControllerDelegate> delegate;
|
||||
|
||||
- (void)performQuery:(NSString * _Nonnull)query;
|
||||
- (void)cancelSearch;
|
||||
- (void)submitSelectedItemIds:(NSArray<NSString *> * _Nonnull)ids;
|
||||
|
||||
@end
|
||||
215
ios/sdk/src/InviteSearch.m
Normal file
215
ios/sdk/src/InviteSearch.m
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import "JitsiMeetView+Private.h"
|
||||
|
||||
#import "InviteSearch.h"
|
||||
|
||||
// The events emitted/supported by InviteSearch:
|
||||
static NSString * const InviteSearchPerformQueryAction = @"performQueryAction";
|
||||
static NSString * const InviteSearchPerformSubmitInviteAction = @"performSubmitInviteAction";
|
||||
|
||||
|
||||
@interface InviteSearch : RCTEventEmitter
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface InviteSearchController ()
|
||||
|
||||
@property (nonatomic, readonly) NSString* _Nonnull identifier;
|
||||
@property (nonatomic, strong) NSMutableDictionary* _Nonnull items;
|
||||
@property (nonatomic, nullable, weak) InviteSearch* module;
|
||||
|
||||
- (instancetype)initWithSearchModule:(InviteSearch *)module;
|
||||
|
||||
- (void)didReceiveResults:(NSArray<NSDictionary*> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query;
|
||||
|
||||
- (void)inviteDidSucceed;
|
||||
|
||||
- (void)inviteDidFailForItems:(NSArray<NSDictionary *> *)items;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation InviteSearch
|
||||
|
||||
static NSMutableDictionary* searchControllers;
|
||||
|
||||
RCT_EXTERN void RCTRegisterModule(Class);
|
||||
|
||||
+ (void)load {
|
||||
RCTRegisterModule(self);
|
||||
|
||||
searchControllers = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
+ (NSString *)moduleName {
|
||||
return @"InviteSearch";
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[
|
||||
InviteSearchPerformQueryAction,
|
||||
InviteSearchPerformSubmitInviteAction
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the corresponding JitsiMeetView's delegate to request that the native
|
||||
* invite search be presented.
|
||||
*
|
||||
* @param scope
|
||||
*/
|
||||
RCT_EXPORT_METHOD(launchNativeInvite:(NSString *)scope) {
|
||||
// The JavaScript App needs to provide uniquely identifying information to
|
||||
// the native module so that the latter may match the former to the native
|
||||
// JitsiMeetView which hosts it.
|
||||
JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:scope];
|
||||
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
id<JitsiMeetViewDelegate> delegate = view.delegate;
|
||||
|
||||
if (!delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(launchNativeInviteForSearchController:)]) {
|
||||
InviteSearchController* searchController = [searchControllers objectForKey:scope];
|
||||
if (!searchController) {
|
||||
searchController = [self makeInviteSearchController];
|
||||
}
|
||||
|
||||
[delegate launchNativeInviteForSearchController:searchController];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(inviteSucceeded:(NSString *)inviteScope) {
|
||||
InviteSearchController* searchController = [searchControllers objectForKey:inviteScope];
|
||||
|
||||
[searchController inviteDidSucceed];
|
||||
|
||||
[searchControllers removeObjectForKey:inviteScope];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(inviteFailedForItems:(NSArray<NSDictionary *> *)items inviteScope:(NSString *)inviteScope) {
|
||||
InviteSearchController* searchController = [searchControllers objectForKey:inviteScope];
|
||||
|
||||
[searchController inviteDidFailForItems:items];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(receivedResults:(NSArray *)results forQuery:(NSString *)query inviteScope:(NSString *)inviteScope) {
|
||||
|
||||
InviteSearchController* searchController = [searchControllers objectForKey:inviteScope];
|
||||
|
||||
[searchController didReceiveResults:results forQuery:query];
|
||||
}
|
||||
|
||||
- (InviteSearchController *)makeInviteSearchController {
|
||||
InviteSearchController* searchController = [[InviteSearchController alloc] initWithSearchModule:self];
|
||||
|
||||
[searchControllers setObject:searchController forKey:searchController.identifier];
|
||||
|
||||
return searchController;
|
||||
}
|
||||
|
||||
- (void)performQuery:(NSString * _Nonnull)query inviteScope:(NSString * _Nonnull)inviteScope {
|
||||
[self sendEventWithName:InviteSearchPerformQueryAction body:@{ @"query": query, @"inviteScope": inviteScope }];
|
||||
}
|
||||
|
||||
- (void)cancelSearchForInviteScope:(NSString * _Nonnull)inviteScope {
|
||||
[searchControllers removeObjectForKey:inviteScope];
|
||||
}
|
||||
|
||||
- (void)submitSelectedItems:(NSArray<NSDictionary *> * _Nonnull)items inviteScope:(NSString * _Nonnull)inviteScope {
|
||||
[self sendEventWithName:InviteSearchPerformSubmitInviteAction body:@{ @"selectedItems": items, @"inviteScope": inviteScope }];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation InviteSearchController
|
||||
|
||||
- (instancetype)initWithSearchModule:(InviteSearch *)module {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_identifier = [[NSUUID UUID] UUIDString];
|
||||
|
||||
self.items = [[NSMutableDictionary alloc] init];
|
||||
self.module = module;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)performQuery:(NSString *)query {
|
||||
[self.module performQuery:query inviteScope:self.identifier];
|
||||
}
|
||||
|
||||
- (void)cancelSearch {
|
||||
[self.module cancelSearchForInviteScope:self.identifier];
|
||||
}
|
||||
|
||||
- (void)submitSelectedItemIds:(NSArray<NSString *> * _Nonnull)ids {
|
||||
NSMutableArray* items = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSString* itemId in ids) {
|
||||
id item = [self.items objectForKey:itemId];
|
||||
|
||||
if (item) {
|
||||
[items addObject:item];
|
||||
}
|
||||
}
|
||||
|
||||
[self.module submitSelectedItems:items inviteScope:self.identifier];
|
||||
}
|
||||
|
||||
- (void)didReceiveResults:(NSArray<NSDictionary *> *)results forQuery:(NSString *)query {
|
||||
for (NSDictionary* item in results) {
|
||||
NSString* itemId = item[@"id"];
|
||||
NSString* itemType = item[@"type"];
|
||||
if (itemId) {
|
||||
[self.items setObject:item forKey:itemId];
|
||||
} else if (itemType != nil && [itemType isEqualToString: @"phone"]) {
|
||||
NSString* number = item[@"number"];
|
||||
if (number) {
|
||||
[self.items setObject:item forKey:number];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[self.delegate inviteSearchController:self didReceiveResults:results forQuery:query];
|
||||
}
|
||||
|
||||
- (void)inviteDidSucceed {
|
||||
[self.delegate inviteDidSucceedForSearchController:self];
|
||||
}
|
||||
|
||||
- (void)inviteDidFailForItems:(NSArray<NSDictionary *> *)items {
|
||||
if (!items) {
|
||||
items = @[];
|
||||
}
|
||||
[self.delegate inviteDidFailForItems:items fromSearchController:self];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -16,3 +16,4 @@
|
||||
|
||||
#import <JitsiMeet/JitsiMeetView.h>
|
||||
#import <JitsiMeet/JitsiMeetViewDelegate.h>
|
||||
#import <JitsiMeet/InviteSearch.h>
|
||||
|
||||
@@ -21,10 +21,14 @@
|
||||
|
||||
@interface JitsiMeetView : UIView
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
@property (nonatomic) BOOL addPeopleEnabled;
|
||||
|
||||
@property (copy, nonatomic, nullable) NSURL *defaultURL;
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
|
||||
@property (nonatomic) BOOL dialOutEnabled;
|
||||
|
||||
@property (nonatomic) BOOL pictureInPictureEnabled;
|
||||
|
||||
@property (nonatomic) BOOL welcomePageEnabled;
|
||||
|
||||
@@ -268,6 +268,8 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||
props[@"defaultURL"] = [self.defaultURL absoluteString];
|
||||
}
|
||||
|
||||
props[@"addPeopleEnabled"] = @(self.addPeopleEnabled);
|
||||
props[@"dialOutEnabled"] = @(self.dialOutEnabled);
|
||||
props[@"externalAPIScope"] = externalAPIScope;
|
||||
props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
|
||||
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@class InviteSearchController;
|
||||
|
||||
@protocol JitsiMeetViewDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
@@ -55,6 +57,15 @@
|
||||
*/
|
||||
- (void)conferenceWillLeave:(NSDictionary *)data;
|
||||
|
||||
|
||||
/**
|
||||
* Called when the invite button in the conference is tapped.
|
||||
*
|
||||
* The search controller provided can be used to query user search within the
|
||||
* conference.
|
||||
*/
|
||||
- (void)launchNativeInviteForSearchController:(InviteSearchController *)searchController;
|
||||
|
||||
/**
|
||||
* Called when entering Picture-in-Picture is requested by the user. The app
|
||||
* should now activate its Picture-in-Picture implementation (and resize the
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Coordinates the presentation of JitsiMeetViewController inside of
|
||||
/// an external window that can be resized and dragged with custom PiP mode
|
||||
open class JitsiMeetPresentationCoordinator: NSObject {
|
||||
|
||||
fileprivate let meetViewController: JitsiMeetViewController
|
||||
fileprivate let meetWindow: PiPWindow
|
||||
|
||||
public init(meetViewController: JitsiMeetViewController? = nil,
|
||||
meetWindow: PiPWindow? = nil) {
|
||||
self.meetViewController = meetViewController ?? JitsiMeetViewController()
|
||||
self.meetWindow = meetWindow ?? PiPWindow(frame: UIScreen.main.bounds)
|
||||
|
||||
super.init()
|
||||
|
||||
configureMeetWindow()
|
||||
configureMeetViewController()
|
||||
}
|
||||
|
||||
public func jitsiMeetView() -> JitsiMeetView {
|
||||
return meetViewController.jitsiMeetView
|
||||
}
|
||||
|
||||
open func show(completion: CompletionAction? = nil) {
|
||||
meetWindow.show(completion: completion)
|
||||
}
|
||||
|
||||
open func hide(completion: CompletionAction? = nil) {
|
||||
meetWindow.hide(completion: completion)
|
||||
}
|
||||
|
||||
open func cleanUp() {
|
||||
// TODO: more clean up work on this
|
||||
|
||||
meetWindow.isHidden = true
|
||||
meetWindow.stopDragGesture()
|
||||
}
|
||||
|
||||
deinit {
|
||||
cleanUp()
|
||||
}
|
||||
|
||||
// MARK: - helpers
|
||||
|
||||
private func configureMeetViewController() {
|
||||
meetViewController.jitsiMeetView.pictureInPictureEnabled = true
|
||||
meetViewController.delegate = self
|
||||
}
|
||||
|
||||
private func configureMeetWindow() {
|
||||
meetWindow.backgroundColor = .clear
|
||||
meetWindow.windowLevel = UIWindowLevelStatusBar + 100
|
||||
meetWindow.rootViewController = self.meetViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension JitsiMeetPresentationCoordinator: JitsiMeetViewControllerDelegate {
|
||||
|
||||
open func performPresentationUpdate(to: JitsiMeetPresentationUpdate) {
|
||||
switch to {
|
||||
case .enterPictureInPicture:
|
||||
meetWindow.enterPictureInPicture()
|
||||
case .traitChange:
|
||||
// resize to full screen if rotation happens
|
||||
if meetWindow.isInPiP {
|
||||
meetWindow.exitPictureInPicture()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func conferenceStarted() {
|
||||
if meetWindow.isHidden {
|
||||
meetWindow.show()
|
||||
}
|
||||
}
|
||||
|
||||
open func conferenceEnded(didFail: Bool) {
|
||||
cleanUp()
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
public enum JitsiMeetPresentationUpdate {
|
||||
|
||||
/// The conference wants to enter Picture-in-Picture
|
||||
case enterPictureInPicture
|
||||
|
||||
/// A system traitCollectionChange (usually screen rotation)
|
||||
case traitChange
|
||||
}
|
||||
|
||||
public protocol JitsiMeetViewControllerDelegate: class {
|
||||
|
||||
/// Notifies a change of the conference presentation style.
|
||||
///
|
||||
/// - Parameter to: The presentation state that will be changed to
|
||||
func performPresentationUpdate(to: JitsiMeetPresentationUpdate)
|
||||
|
||||
/// The conference started
|
||||
func conferenceStarted()
|
||||
|
||||
/// The conference ended
|
||||
///
|
||||
/// - Parameter didFail: The reason of ending the conference
|
||||
func conferenceEnded(didFail: Bool)
|
||||
}
|
||||
|
||||
/// Wrapper ViewController of a JitsiMeetView
|
||||
///
|
||||
/// Is suggested to override this class and implement some customization
|
||||
/// on how to handle the JitsiMeetView delegate events
|
||||
open class JitsiMeetViewController: UIViewController {
|
||||
|
||||
open weak var delegate: JitsiMeetViewControllerDelegate?
|
||||
|
||||
private(set) var jitsiMeetView: JitsiMeetView = JitsiMeetView()
|
||||
|
||||
override open func loadView() {
|
||||
super.loadView()
|
||||
self.view = jitsiMeetView
|
||||
}
|
||||
|
||||
open override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
jitsiMeetView.delegate = self
|
||||
}
|
||||
|
||||
open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
delegate?.performPresentationUpdate(to: .traitChange)
|
||||
}
|
||||
}
|
||||
|
||||
extension JitsiMeetViewController: JitsiMeetViewDelegate {
|
||||
|
||||
open func conferenceWillJoin(_ data: [AnyHashable : Any]!) {
|
||||
// do something
|
||||
}
|
||||
|
||||
open func conferenceJoined(_ data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.conferenceStarted()
|
||||
}
|
||||
}
|
||||
|
||||
open func conferenceWillLeave(_ data: [AnyHashable : Any]!) {
|
||||
// do something
|
||||
}
|
||||
|
||||
open func conferenceLeft(_ data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.conferenceEnded(didFail: false)
|
||||
}
|
||||
}
|
||||
|
||||
open func conferenceFailed(_ data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.conferenceEnded(didFail: true)
|
||||
}
|
||||
}
|
||||
|
||||
open func loadConfigError(_ data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.conferenceEnded(didFail: true)
|
||||
}
|
||||
}
|
||||
|
||||
open func enterPicture(inPicture data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.performPresentationUpdate(to: .enterPictureInPicture)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// Alias defining a completion closure that returns a Bool
|
||||
public typealias CompletionAction = (Bool) -> Void
|
||||
public typealias AnimationCompletion = (Bool) -> Void
|
||||
|
||||
/// A window that allows its root view controller to be presented
|
||||
/// in full screen or in a custom Picture in Picture mode
|
||||
open class PiPWindow: UIWindow {
|
||||
/// Coordinates the view state of a specified view to allow
|
||||
/// to be presented in full screen or in a custom Picture in Picture mode.
|
||||
/// This object will also provide the drag and tap interactions of the view
|
||||
/// when is presented in Picure in Picture mode.
|
||||
public class PiPViewCoordinator {
|
||||
|
||||
/// Limits the boundries of root view position on screen when minimized
|
||||
/// Limits the boundries of view position on screen when minimized
|
||||
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
|
||||
left: 5,
|
||||
bottom: 5,
|
||||
@@ -31,53 +32,75 @@ open class PiPWindow: UIWindow {
|
||||
}
|
||||
}
|
||||
|
||||
/// The size ratio for root view controller view when in PiP mode
|
||||
public var pipSizeRatio: CGFloat = 0.333
|
||||
/// The size ratio of the view when in PiP mode
|
||||
public var pipSizeRatio: CGFloat = {
|
||||
let deviceIdiom = UIScreen.main.traitCollection.userInterfaceIdiom
|
||||
switch deviceIdiom {
|
||||
case .pad:
|
||||
return 0.25
|
||||
case .phone:
|
||||
return 0.33
|
||||
default:
|
||||
return 0.25
|
||||
}
|
||||
}()
|
||||
|
||||
/// The PiP state of this contents of the window
|
||||
private(set) var isInPiP: Bool = false
|
||||
private(set) var isInPiP: Bool = false // true if view is in PiP mode
|
||||
|
||||
private let dragController: DragGestureController = DragGestureController()
|
||||
private(set) var view: UIView
|
||||
private var currentBounds: CGRect = CGRect.zero
|
||||
|
||||
/// Used when in PiP mode to enable/disable exit PiP UI
|
||||
private var tapGestureRecognizer: UITapGestureRecognizer?
|
||||
private var exitPiPButton: UIButton?
|
||||
|
||||
/// Help out to bubble up the gesture detection outside of the rootVC frame
|
||||
open override func point(inside point: CGPoint,
|
||||
with event: UIEvent?) -> Bool {
|
||||
guard let vc = rootViewController else {
|
||||
return super.point(inside: point, with: event)
|
||||
}
|
||||
return vc.view.frame.contains(point)
|
||||
private let dragController: DragGestureController = DragGestureController()
|
||||
|
||||
public init(withView view: UIView) {
|
||||
self.view = view
|
||||
}
|
||||
|
||||
/// animate in the window
|
||||
open func show(completion: CompletionAction? = nil) {
|
||||
if self.isHidden || self.alpha < 1 {
|
||||
self.isHidden = false
|
||||
self.alpha = 0
|
||||
/// Configure the view to be always on top of all the contents
|
||||
/// of the provided parent view.
|
||||
/// If a parentView is not provided it will try to use the main window
|
||||
public func configureAsStickyView(withParentView parentView: UIView? = nil) {
|
||||
guard
|
||||
let parentView = parentView ?? UIApplication.shared.keyWindow
|
||||
else { return }
|
||||
|
||||
parentView.addSubview(view)
|
||||
currentBounds = parentView.bounds
|
||||
view.frame = currentBounds
|
||||
view.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
/// Show view with fade in animation
|
||||
public func show(completion: AnimationCompletion? = nil) {
|
||||
if view.isHidden || view.alpha < 1 {
|
||||
view.isHidden = false
|
||||
view.alpha = 0
|
||||
|
||||
animateTransition(animations: {
|
||||
self.alpha = 1
|
||||
animateTransition(animations: { [weak self] in
|
||||
self?.view.alpha = 1
|
||||
}, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
/// animate out the window
|
||||
open func hide(completion: CompletionAction? = nil) {
|
||||
if !self.isHidden || self.alpha > 0 {
|
||||
animateTransition(animations: {
|
||||
self.alpha = 1
|
||||
/// Hide view with fade out animation
|
||||
public func hide(completion: AnimationCompletion? = nil) {
|
||||
if view.isHidden || view.alpha > 0 {
|
||||
animateTransition(animations: { [weak self] in
|
||||
self?.view.alpha = 0
|
||||
self?.view.isHidden = true
|
||||
}, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize the root view to PiP mode
|
||||
open func enterPictureInPicture() {
|
||||
guard let view = rootViewController?.view else { return }
|
||||
/// Resize view to and change state to custom PictureInPicture mode
|
||||
/// This will resize view, add a gesture to enable user to "drag" view
|
||||
/// around screen, and add a button of top of the view to be able to exit mode
|
||||
public func enterPictureInPicture() {
|
||||
isInPiP = true
|
||||
animateRootViewChange()
|
||||
animateViewChange()
|
||||
dragController.startDragListener(inView: view)
|
||||
dragController.insets = dragBoundInsets
|
||||
|
||||
@@ -89,10 +112,11 @@ open class PiPWindow: UIWindow {
|
||||
view.addGestureRecognizer(tapGestureRecognizer)
|
||||
}
|
||||
|
||||
/// Resize the root view to full screen
|
||||
open func exitPictureInPicture() {
|
||||
/// Exit Picture in picture mode, this will resize view, remove
|
||||
/// exit pip button, and disable the drag gesture
|
||||
@objc public func exitPictureInPicture() {
|
||||
isInPiP = false
|
||||
animateRootViewChange()
|
||||
animateViewChange()
|
||||
dragController.stopDragListener()
|
||||
|
||||
// hide PiP UI
|
||||
@@ -105,6 +129,13 @@ open class PiPWindow: UIWindow {
|
||||
tapGestureRecognizer = nil
|
||||
}
|
||||
|
||||
/// Reset view to provide bounds, use this method on rotation or
|
||||
/// screen size changes
|
||||
public func resetBounds(bounds: CGRect) {
|
||||
currentBounds = bounds
|
||||
exitPictureInPicture()
|
||||
}
|
||||
|
||||
/// Stop the dragging gesture of the root view
|
||||
public func stopDragGesture() {
|
||||
dragController.stopDragListener()
|
||||
@@ -122,41 +153,14 @@ open class PiPWindow: UIWindow {
|
||||
button.backgroundColor = .gray
|
||||
button.layer.cornerRadius = size.width / 2
|
||||
button.frame = CGRect(origin: CGPoint.zero, size: size)
|
||||
if let view = rootViewController?.view {
|
||||
button.center = view.convert(view.center, from:view.superview)
|
||||
}
|
||||
button.center = view.convert(view.center, from: view.superview)
|
||||
button.addTarget(target, action: action, for: .touchUpInside)
|
||||
return button
|
||||
}
|
||||
|
||||
// MARK: - Manage presentation switching
|
||||
|
||||
private func animateRootViewChange() {
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.rootViewController?.view.frame = self.changeRootViewRect()
|
||||
self.rootViewController?.view.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private func changeRootViewRect() -> CGRect {
|
||||
guard isInPiP else {
|
||||
return self.bounds
|
||||
}
|
||||
|
||||
// resize to suggested ratio and position to the bottom right
|
||||
let adjustedBounds = UIEdgeInsetsInsetRect(self.bounds, dragBoundInsets)
|
||||
let size = CGSize(width: bounds.size.width * pipSizeRatio,
|
||||
height: bounds.size.height * pipSizeRatio)
|
||||
let x: CGFloat = adjustedBounds.maxX - size.width
|
||||
let y: CGFloat = adjustedBounds.maxY - size.height
|
||||
return CGRect(x: x, y: y, width: size.width, height: size.height)
|
||||
}
|
||||
|
||||
// MARK: - Exit PiP
|
||||
// MARK: - Interactions
|
||||
|
||||
@objc private func toggleExitPiP() {
|
||||
guard let view = rootViewController?.view else { return }
|
||||
|
||||
if exitPiPButton == nil {
|
||||
// show button
|
||||
let exitSelector = #selector(exitPictureInPicture)
|
||||
@@ -172,18 +176,40 @@ open class PiPWindow: UIWindow {
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func exitPiP() {
|
||||
exitPictureInPicture()
|
||||
// MARK: - Size calculation
|
||||
|
||||
private func animateViewChange() {
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.view.frame = self.changeViewRect()
|
||||
self.view.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Animation transition
|
||||
private func changeViewRect() -> CGRect {
|
||||
let bounds = currentBounds
|
||||
|
||||
guard isInPiP else {
|
||||
return bounds
|
||||
}
|
||||
|
||||
// resize to suggested ratio and position to the bottom right
|
||||
let adjustedBounds = UIEdgeInsetsInsetRect(bounds, dragBoundInsets)
|
||||
let size = CGSize(width: bounds.size.width * pipSizeRatio,
|
||||
height: bounds.size.height * pipSizeRatio)
|
||||
let x: CGFloat = adjustedBounds.maxX - size.width
|
||||
let y: CGFloat = adjustedBounds.maxY - size.height
|
||||
return CGRect(x: x, y: y, width: size.width, height: size.height)
|
||||
}
|
||||
|
||||
// MARK: - Animation helpers
|
||||
|
||||
private func animateTransition(animations: @escaping () -> Void,
|
||||
completion: CompletionAction?) {
|
||||
completion: AnimationCompletion?) {
|
||||
UIView.animate(withDuration: 0.1,
|
||||
delay: 0,
|
||||
options: .beginFromCurrentState,
|
||||
animations: animations,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
}
|
||||
20
lang/languages-ko.json
Normal file
20
lang/languages-ko.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"en": "",
|
||||
"bg": "",
|
||||
"de": "",
|
||||
"es": "",
|
||||
"fr": "",
|
||||
"hy": "",
|
||||
"it": "",
|
||||
"oc": "",
|
||||
"pl": "",
|
||||
"ptBR": "",
|
||||
"ru": "",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "",
|
||||
"tr": "",
|
||||
"zhCN": "",
|
||||
"nb": "",
|
||||
"eo": ""
|
||||
}
|
||||
512
lang/main-ko.json
Normal file
512
lang/main-ko.json
Normal file
@@ -0,0 +1,512 @@
|
||||
{
|
||||
"contactlist": "",
|
||||
"contactlist_plural_undefined": "",
|
||||
"passwordSetRemotely": "",
|
||||
"poweredby": "",
|
||||
"inviteUrlDefaultMsg": "",
|
||||
"me": "",
|
||||
"speaker": "",
|
||||
"raisedHand": "",
|
||||
"defaultNickname": "",
|
||||
"defaultLink": "",
|
||||
"audioDevices": {
|
||||
"bluetooth": "",
|
||||
"headphones": "",
|
||||
"phone": "",
|
||||
"speaker": ""
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "",
|
||||
"featureToggleDisabled": ""
|
||||
},
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"androidGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "",
|
||||
"operaGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"safariGrantPermissions": "",
|
||||
"nwjsGrantPermissions": "",
|
||||
"edgeGrantPermissions": ""
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "",
|
||||
"raiseHand": "",
|
||||
"pushToTalk": "",
|
||||
"toggleScreensharing": "",
|
||||
"toggleFilmstrip": "",
|
||||
"toggleShortcuts": "",
|
||||
"focusLocal": "",
|
||||
"focusRemote": "",
|
||||
"toggleChat": "",
|
||||
"mute": "",
|
||||
"fullScreen": "",
|
||||
"videoMute": "",
|
||||
"showSpeakerStats": ""
|
||||
},
|
||||
"welcomepage": {
|
||||
"appDescription": "",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "",
|
||||
"video": ""
|
||||
},
|
||||
"calendar": "",
|
||||
"go": "",
|
||||
"join": "",
|
||||
"privacy": "",
|
||||
"roomname": "",
|
||||
"roomnameHint": "",
|
||||
"sendFeedback": "",
|
||||
"terms": "",
|
||||
"title": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
"title": ""
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "",
|
||||
"text": "",
|
||||
"rejoinKeyTitle": ""
|
||||
},
|
||||
"toolbar": {
|
||||
"addPeople": "",
|
||||
"audioonly": "",
|
||||
"mute": "",
|
||||
"videomute": "",
|
||||
"authenticate": "",
|
||||
"lock": "",
|
||||
"chat": "",
|
||||
"etherpad": "",
|
||||
"sharedvideo": "",
|
||||
"sharescreen": "",
|
||||
"fullscreen": "",
|
||||
"sip": "",
|
||||
"Settings": "",
|
||||
"hangup": "",
|
||||
"login": "",
|
||||
"logout": "",
|
||||
"dialpad": "",
|
||||
"sharedVideoMutedPopup": "",
|
||||
"micMutedPopup": "",
|
||||
"talkWhileMutedPopup": "",
|
||||
"unableToUnmutePopup": "",
|
||||
"cameraDisabled": "",
|
||||
"micDisabled": "",
|
||||
"filmstrip": "",
|
||||
"profile": "",
|
||||
"raiseHand": ""
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appNotInstalled": "",
|
||||
"downloadApp": "",
|
||||
"openApp": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "",
|
||||
"filmstrip": "",
|
||||
"contactlist": ""
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "",
|
||||
"popover": ""
|
||||
},
|
||||
"messagebox": ""
|
||||
},
|
||||
"settings": {
|
||||
"title": "",
|
||||
"update": "",
|
||||
"name": "",
|
||||
"startAudioMuted": "",
|
||||
"startVideoMuted": "",
|
||||
"selectCamera": "",
|
||||
"selectMic": "",
|
||||
"selectAudioOutput": "",
|
||||
"followMe": "",
|
||||
"noDevice": "",
|
||||
"cameraAndMic": "",
|
||||
"moderator": "",
|
||||
"password": "",
|
||||
"audioVideo": ""
|
||||
},
|
||||
"profile": {
|
||||
"title": "",
|
||||
"setDisplayNameLabel": "",
|
||||
"setEmailLabel": "",
|
||||
"setEmailInput": ""
|
||||
},
|
||||
"videothumbnail": {
|
||||
"moderator": "",
|
||||
"videomute": "",
|
||||
"mute": "",
|
||||
"kick": "",
|
||||
"muted": "",
|
||||
"domute": "",
|
||||
"flip": "",
|
||||
"remoteControl": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "",
|
||||
"bitrate": "",
|
||||
"packetloss": "",
|
||||
"resolution": "",
|
||||
"framerate": "",
|
||||
"less": "",
|
||||
"more": "",
|
||||
"address": "",
|
||||
"remoteport": "",
|
||||
"remoteport_plural_undefined": "",
|
||||
"localport": "",
|
||||
"localport_plural_undefined": "",
|
||||
"localaddress": "",
|
||||
"localaddress_plural_undefined": "",
|
||||
"remoteaddress": "",
|
||||
"remoteaddress_plural_undefined": "",
|
||||
"transport": "",
|
||||
"transport_plural_undefined": "",
|
||||
"bandwidth": "",
|
||||
"na": "",
|
||||
"turn": "",
|
||||
"quality": {
|
||||
"good": "",
|
||||
"inactive": "",
|
||||
"lost": "",
|
||||
"nonoptimal": "",
|
||||
"poor": ""
|
||||
},
|
||||
"status": ""
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "",
|
||||
"moderator": "",
|
||||
"connectedOneMember": "",
|
||||
"connectedTwoMembers": "",
|
||||
"connectedThreePlusMembers": "",
|
||||
"somebody": "",
|
||||
"me": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"grantedToUnknown": "",
|
||||
"muted": "",
|
||||
"mutedTitle": "",
|
||||
"raisedHand": "",
|
||||
"suboptimalExperienceTitle": "",
|
||||
"suboptimalExperienceDescription": ""
|
||||
},
|
||||
"dialog": {
|
||||
"allow": "",
|
||||
"kickMessage": "",
|
||||
"popupErrorTitle": "",
|
||||
"popupError": "",
|
||||
"passwordErrorTitle": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"connectError": "",
|
||||
"connectErrorWithMsg": "",
|
||||
"incorrectPassword": "",
|
||||
"connecting": "",
|
||||
"copy": "",
|
||||
"contactSupport": "",
|
||||
"error": "",
|
||||
"detectext": "",
|
||||
"failedpermissions": "",
|
||||
"conferenceReloadTitle": "",
|
||||
"conferenceReloadMsg": "",
|
||||
"conferenceDisconnectTitle": "",
|
||||
"conferenceDisconnectMsg": "",
|
||||
"dismiss": "",
|
||||
"rejoinNow": "",
|
||||
"maxUsersLimitReachedTitle": "",
|
||||
"maxUsersLimitReached": "",
|
||||
"lockTitle": "",
|
||||
"lockMessage": "",
|
||||
"warning": "",
|
||||
"passwordNotSupportedTitle": "",
|
||||
"passwordNotSupported": "",
|
||||
"internalErrorTitle": "",
|
||||
"internalError": "",
|
||||
"unableToSwitch": "",
|
||||
"SLDFailure": "",
|
||||
"SRDFailure": "",
|
||||
"oops": "",
|
||||
"currentPassword": "",
|
||||
"passwordLabel": "",
|
||||
"defaultError": "",
|
||||
"passwordRequired": "",
|
||||
"Ok": "",
|
||||
"done": "",
|
||||
"Remove": "",
|
||||
"removePassword": "",
|
||||
"shareVideoTitle": "",
|
||||
"shareVideoLinkError": "",
|
||||
"removeSharedVideoTitle": "",
|
||||
"removeSharedVideoMsg": "",
|
||||
"alreadySharedVideoMsg": "",
|
||||
"alreadySharedVideoTitle": "",
|
||||
"WaitingForHost": "",
|
||||
"WaitForHostMsg": "",
|
||||
"IamHost": "",
|
||||
"Cancel": "",
|
||||
"Submit": "",
|
||||
"retry": "",
|
||||
"logoutTitle": "",
|
||||
"logoutQuestion": "",
|
||||
"sessTerminated": "",
|
||||
"hungUp": "",
|
||||
"joinAgain": "",
|
||||
"Share": "",
|
||||
"Save": "",
|
||||
"recording": "",
|
||||
"recordingToken": "",
|
||||
"Back": "",
|
||||
"serviceUnavailable": "",
|
||||
"gracefulShutdown": "",
|
||||
"Yes": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "",
|
||||
"password": "",
|
||||
"userPassword": "",
|
||||
"token": "",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"tokenAuthFailed": "",
|
||||
"displayNameRequired": "",
|
||||
"enterDisplayName": "",
|
||||
"feedbackHelp": "",
|
||||
"feedbackQuestion": "",
|
||||
"thankYou": "",
|
||||
"sorryFeedback": "",
|
||||
"liveStreaming": "",
|
||||
"streamKey": "",
|
||||
"startLiveStreaming": "",
|
||||
"stopStreamingWarning": "",
|
||||
"stopRecordingWarning": "",
|
||||
"stopLiveStreaming": "",
|
||||
"stopRecording": "",
|
||||
"doNotShowMessageAgain": "",
|
||||
"permissionDenied": "",
|
||||
"screenSharingFailedToInstall": "",
|
||||
"screenSharingFailedToInstallTitle": "",
|
||||
"screenSharingFirefoxPermissionDeniedError": "",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"cameraUnsupportedResolutionError": "",
|
||||
"cameraUnknownError": "",
|
||||
"cameraPermissionDeniedError": "",
|
||||
"cameraNotFoundError": "",
|
||||
"cameraConstraintFailedError": "",
|
||||
"micUnknownError": "",
|
||||
"micPermissionDeniedError": "",
|
||||
"micNotFoundError": "",
|
||||
"micConstraintFailedError": "",
|
||||
"micNotSendingDataTitle": "",
|
||||
"micNotSendingData": "",
|
||||
"cameraNotSendingDataTitle": "",
|
||||
"cameraNotSendingData": "",
|
||||
"goToStore": "",
|
||||
"externalInstallationTitle": "",
|
||||
"externalInstallationMsg": "",
|
||||
"inlineInstallationMsg": "",
|
||||
"inlineInstallExtension": "",
|
||||
"muteParticipantTitle": "",
|
||||
"muteParticipantBody": "",
|
||||
"muteParticipantButton": "",
|
||||
"remoteControlTitle": "",
|
||||
"remoteControlRequestMessage": "",
|
||||
"remoteControlShareScreenWarning": "",
|
||||
"remoteControlDeniedMessage": "",
|
||||
"remoteControlAllowedMessage": "",
|
||||
"remoteControlErrorMessage": "",
|
||||
"startRemoteControlErrorMessage": "",
|
||||
"remoteControlStopMessage": "",
|
||||
"close": "",
|
||||
"shareYourScreen": "",
|
||||
"yourEntireScreen": "",
|
||||
"applicationWindow": ""
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": "",
|
||||
"subject": "",
|
||||
"body": "",
|
||||
"and": ""
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "",
|
||||
"CONNECTING": "",
|
||||
"RECONNECTING": "",
|
||||
"CONNFAIL": "",
|
||||
"AUTHENTICATING": "",
|
||||
"AUTHFAIL": "",
|
||||
"CONNECTED": "",
|
||||
"DISCONNECTED": "",
|
||||
"DISCONNECTING": "",
|
||||
"ATTACHED": ""
|
||||
},
|
||||
"recording": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "",
|
||||
"error": "",
|
||||
"failedToStart": "",
|
||||
"off": "",
|
||||
"on": "",
|
||||
"pending": "",
|
||||
"serviceName": "",
|
||||
"unavailable": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "",
|
||||
"changeSignIn": "",
|
||||
"choose": "",
|
||||
"chooseCTA": "",
|
||||
"enterStreamKey": "",
|
||||
"error": "",
|
||||
"errorAPI": "",
|
||||
"failedToStart": "",
|
||||
"off": "",
|
||||
"on": "",
|
||||
"pending": "",
|
||||
"serviceName": "",
|
||||
"signIn": "",
|
||||
"signInCTA": "",
|
||||
"start": "",
|
||||
"streamIdHelp": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"videoSIPGW": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"errorInvite": "",
|
||||
"errorInviteTitle": "",
|
||||
"errorAlreadyInvited": "",
|
||||
"errorInviteFailedTitle": "",
|
||||
"errorInviteFailed": "",
|
||||
"pending": "",
|
||||
"serviceName": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"speakerStats": {
|
||||
"hours": "",
|
||||
"minutes": "",
|
||||
"name": "",
|
||||
"seconds": "",
|
||||
"speakerStats": "",
|
||||
"speakerTime": ""
|
||||
},
|
||||
"deviceSelection": {
|
||||
"deviceSettings": "",
|
||||
"noPermission": "",
|
||||
"previewUnavailable": "",
|
||||
"selectADevice": "",
|
||||
"testAudio": ""
|
||||
},
|
||||
"videoStatus": {
|
||||
"callQuality": "",
|
||||
"hd": "",
|
||||
"highDefinition": "",
|
||||
"labelTooltipVideo": "",
|
||||
"labelTooltipAudioOnly": "",
|
||||
"ld": "",
|
||||
"lowDefinition": "",
|
||||
"onlyAudioAvailable": "",
|
||||
"onlyAudioSupported": "",
|
||||
"p2pEnabled": "",
|
||||
"p2pVideoQualityDescription": "",
|
||||
"recHighDefinitionOnly": "",
|
||||
"sd": "",
|
||||
"standardDefinition": "",
|
||||
"qualityButtonTip": ""
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": ""
|
||||
},
|
||||
"addPeople": {
|
||||
"add": "",
|
||||
"countryNotSupported": "",
|
||||
"countryReminder": "",
|
||||
"disabled": "",
|
||||
"invite": "",
|
||||
"loading": "",
|
||||
"loadingNumber": "",
|
||||
"loadingPeople": "",
|
||||
"noResults": "",
|
||||
"noValidNumbers": "",
|
||||
"searchNumbers": "",
|
||||
"searchPeople": "",
|
||||
"searchPeopleAndNumbers": "",
|
||||
"telephone": "",
|
||||
"title": "",
|
||||
"failedToAdd": ""
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "",
|
||||
"retry": "",
|
||||
"support": "",
|
||||
"supportMsg": ""
|
||||
},
|
||||
"deviceError": {
|
||||
"cameraError": "",
|
||||
"microphoneError": "",
|
||||
"cameraPermission": "",
|
||||
"microphonePermission": ""
|
||||
},
|
||||
"feedback": {
|
||||
"average": "",
|
||||
"bad": "",
|
||||
"good": "",
|
||||
"rateExperience": "",
|
||||
"veryBad": "",
|
||||
"veryGood": ""
|
||||
},
|
||||
"info": {
|
||||
"addPassword": "",
|
||||
"cancelPassword": "",
|
||||
"conferenceURL": "",
|
||||
"country": "",
|
||||
"dialANumber": "",
|
||||
"dialInNumber": "",
|
||||
"dialInConferenceID": "",
|
||||
"dialInNotSupported": "",
|
||||
"genericError": "",
|
||||
"invitePhone": "",
|
||||
"invitePhoneAlternatives": "",
|
||||
"inviteURL": "",
|
||||
"moreNumbers": "",
|
||||
"noNumbers": "",
|
||||
"noPassword": "",
|
||||
"noRoom": "",
|
||||
"numbers": "",
|
||||
"password": "",
|
||||
"title": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "",
|
||||
"alertTitle": "",
|
||||
"alertURLText": "",
|
||||
"conferenceSection": "",
|
||||
"displayName": "",
|
||||
"email": "",
|
||||
"header": "",
|
||||
"profileSection": "",
|
||||
"serverURL": "",
|
||||
"startWithAudioMuted": "",
|
||||
"startWithVideoMuted": ""
|
||||
},
|
||||
"calendarSync": {
|
||||
"later": "",
|
||||
"next": "",
|
||||
"nextMeeting": "",
|
||||
"now": ""
|
||||
},
|
||||
"recentList": {
|
||||
"today": "",
|
||||
"yesterday": "",
|
||||
"earlier": ""
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"contactlist": "__count__ Member",
|
||||
"contactlist_plural": "__count__ Members",
|
||||
"passwordSetRemotely": "set by another member",
|
||||
"poweredby": "powered by",
|
||||
@@ -23,6 +22,7 @@
|
||||
"react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"androidGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"electronGrantPermissions": "Please grant permissions to use your camera and microphone",
|
||||
"firefoxGrantPermissions": "Select <b><i>Share Selected Device</i></b> when your browser asks for permissions.",
|
||||
"operaGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"iexplorerGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
|
||||
@@ -73,21 +73,28 @@
|
||||
"toolbar": {
|
||||
"addPeople": "Add people to your call",
|
||||
"audioonly": "Enable / Disable audio only mode (saves bandwidth)",
|
||||
"callQuality": "Manage call quality",
|
||||
"enterFullScreen": "View full screen",
|
||||
"exitFullScreen": "Exit full screen",
|
||||
"feedback": "Leave feedback",
|
||||
"moreActions": "More actions",
|
||||
"mute": "Mute / Unmute",
|
||||
"videomute": "Start / Stop camera",
|
||||
"authenticate": "Authenticate",
|
||||
"lock": "Lock / Unlock room",
|
||||
"chat": "Open / Close chat",
|
||||
"etherpad": "Open / Close shared document",
|
||||
"documentOpen": "Open shared document",
|
||||
"documentClose": "Close shared document",
|
||||
"sharedvideo": "Share a YouTube video",
|
||||
"sharescreen": "Start / Stop screen sharing",
|
||||
"sharescreen": "Screen share",
|
||||
"stopSharedVideo": "Stop YouTube video",
|
||||
"fullscreen": "View / Exit full screen",
|
||||
"sip": "Call SIP number",
|
||||
"Settings": "Settings",
|
||||
"hangup": "Leave",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"dialpad": "Open / Close dialpad",
|
||||
"sharedVideoMutedPopup": "Your shared video has been muted so that you can talk to the other members.",
|
||||
"micMutedPopup": "Your microphone has been muted so that you would fully enjoy your shared video.",
|
||||
"talkWhileMutedPopup": "Trying to speak? You are muted.",
|
||||
@@ -96,17 +103,9 @@
|
||||
"micDisabled": "Microphone is not available",
|
||||
"filmstrip": "Show / Hide videos",
|
||||
"profile": "Edit your profile",
|
||||
"raiseHand": "Raise / Lower your hand"
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appNotInstalled": "Join this meeting with __app__ on your phone.",
|
||||
"downloadApp": "Download the app",
|
||||
"openApp": "Continue to __app__"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Open / close chat",
|
||||
"filmstrip": "Show / hide videos",
|
||||
"contactlist": "View and invite members"
|
||||
"raiseHand": "Raise / Lower your hand",
|
||||
"shortcuts": "View shortcuts",
|
||||
"speakerStats": "Speaker stats"
|
||||
},
|
||||
"chat":{
|
||||
"nickname": {
|
||||
@@ -285,6 +284,7 @@
|
||||
"liveStreaming": "Live Streaming",
|
||||
"streamKey": "Live stream key",
|
||||
"startLiveStreaming": "Go live now",
|
||||
"startRecording": "Start recording",
|
||||
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
|
||||
"stopRecordingWarning": "Are you sure you would like to stop the recording?",
|
||||
"stopLiveStreaming": "Stop live streaming",
|
||||
@@ -445,10 +445,13 @@
|
||||
"videoStatus": {
|
||||
"callQuality": "Call Quality",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Viewing high definition video",
|
||||
"highDefinition": "High definition",
|
||||
"labelTooltipVideo": "Current video quality",
|
||||
"labelTooltipAudioOnly": "Audio-only mode enabled",
|
||||
"labelTooiltipNoVideo": "No video",
|
||||
"labelTooltipVideo": "Current video quality",
|
||||
"ld": "LD",
|
||||
"ldTooltip": "Viewing low definition video",
|
||||
"lowDefinition": "Low definition",
|
||||
"onlyAudioAvailable": "Only audio is available",
|
||||
"onlyAudioSupported": "We only support audio in this browser.",
|
||||
@@ -456,6 +459,7 @@
|
||||
"p2pVideoQualityDescription": "In peer to peer mode, received call quality can only be toggled between high and audio only. Other settings will not be honored until peer to peer is exited.",
|
||||
"recHighDefinitionOnly": "Will prefer high definition.",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Viewing standard definition video",
|
||||
"standardDefinition": "Standard definition",
|
||||
"qualityButtonTip": "Change received video quality"
|
||||
},
|
||||
@@ -463,7 +467,7 @@
|
||||
"statusMessage": "is now __status__"
|
||||
},
|
||||
"addPeople": {
|
||||
"add": "Add",
|
||||
"add": "Invite",
|
||||
"countryNotSupported": "We do not support this destination yet.",
|
||||
"countryReminder": "Calling outside the US? Please make sure you start with the country code!",
|
||||
"disabled": "You can't invite people.",
|
||||
@@ -473,11 +477,12 @@
|
||||
"loadingPeople": "Searching for people to invite",
|
||||
"noResults": "No matching search results",
|
||||
"noValidNumbers": "Please enter a phone number",
|
||||
"searchNumbers": "Enter a phone number to invite",
|
||||
"searchPeople": "Enter a name to invite",
|
||||
"searchPeopleAndNumbers": "Enter a name or phone number to invite",
|
||||
"notAvailable": "You can't invite people.",
|
||||
"searchNumbers": "Add phone numbers",
|
||||
"searchPeople": "Search for people",
|
||||
"searchPeopleAndNumbers": "Search for people or add their phone numbers",
|
||||
"telephone": "Telephone: __number__",
|
||||
"title": "Invite people to your meeting",
|
||||
"title": "Invite people to this meeting",
|
||||
"failedToAdd": "Failed to add members"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
@@ -496,30 +501,33 @@
|
||||
"average": "Average",
|
||||
"bad": "Bad",
|
||||
"good": "Good",
|
||||
"rateExperience": "Please rate your meeting experience.",
|
||||
"detailsLabel": "Tell us more about it.",
|
||||
"rateExperience": "Rate your meeting experience",
|
||||
"veryBad": "Very Bad",
|
||||
"veryGood": "Very Good"
|
||||
},
|
||||
"info": {
|
||||
"addPassword": "Add password",
|
||||
"cancelPassword": "Cancel password",
|
||||
"conferenceURL": "Link: __url__",
|
||||
"conferenceURL": "Link:",
|
||||
"country": "Country",
|
||||
"dialANumber": "To join your meeting, dial one of these numbers and then enter this PIN: __conferenceID__#",
|
||||
"dialInNumber": "Dial-in: __phoneNumber__",
|
||||
"dialInConferenceID": "PIN: __conferenceID__#",
|
||||
"dialInNumber": "Dial-in:",
|
||||
"dialInConferenceID": "PIN:",
|
||||
"dialInNotSupported": "Sorry, dialing in is currently not suppported.",
|
||||
"genericError": "Whoops, something went wrong.",
|
||||
"inviteLiveStream": "To view the live stream of this meeting, click this link: __url__",
|
||||
"invitePhone": "To join by phone, dial __number__ and enter this PIN: __conferenceID__#",
|
||||
"invitePhoneAlternatives": "To view more phone numbers, click this link: __url__",
|
||||
"inviteURL": "To join the video meeting, click this link: __url__",
|
||||
"liveStreamURL": "Live stream:",
|
||||
"moreNumbers": "More numbers",
|
||||
"noNumbers": "No dial-in numbers.",
|
||||
"noPassword": "None",
|
||||
"noRoom": "No room was specified to dial-in into.",
|
||||
"numbers": "Dial-in Numbers",
|
||||
"password": "Password:",
|
||||
"title": "Call info",
|
||||
"title": "Share",
|
||||
"tooltip": "Get access info about the meeting"
|
||||
},
|
||||
"settingsView": {
|
||||
@@ -539,11 +547,25 @@
|
||||
"later": "Later",
|
||||
"next": "Upcoming",
|
||||
"nextMeeting": "next meeting",
|
||||
"now": "Now"
|
||||
"now": "Now",
|
||||
"permissionButton": "Open settings",
|
||||
"permissionMessage": "Calendar permission is required to list your meetings in the app."
|
||||
},
|
||||
"recentList": {
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"earlier": "Earlier"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Pull to refresh"
|
||||
},
|
||||
"deepLinking": {
|
||||
"title": "Launching your meeting in __app__...",
|
||||
"description": "Nothing happened? We tried launching your meeting in the __app__ desktop app. Try again or launch it in the __app__ web app.",
|
||||
"tryAgainButton": "Try again in desktop",
|
||||
"launchWebButton": "Launch in web",
|
||||
"appNotInstalled": "You need the __app__ mobile app to join this meeting on your phone.",
|
||||
"downloadApp": "Download the app",
|
||||
"openApp": "Continue to the app"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +81,6 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('chat.toggled'));
|
||||
APP.UI.toggleChat();
|
||||
},
|
||||
'toggle-contact-list': () => {
|
||||
sendAnalytics(createApiEvent('contact.list.toggled'));
|
||||
APP.UI.toggleContactList();
|
||||
},
|
||||
'toggle-share-screen': () => {
|
||||
sendAnalytics(createApiEvent('screen.sharing.toggled'));
|
||||
toggleScreenSharing();
|
||||
|
||||
2
modules/API/external/external_api.js
vendored
2
modules/API/external/external_api.js
vendored
@@ -24,7 +24,6 @@ const commands = {
|
||||
submitFeedback: 'submit-feedback',
|
||||
toggleAudio: 'toggle-audio',
|
||||
toggleChat: 'toggle-chat',
|
||||
toggleContactList: 'toggle-contact-list',
|
||||
toggleFilmStrip: 'toggle-film-strip',
|
||||
toggleShareScreen: 'toggle-share-screen',
|
||||
toggleVideo: 'toggle-video'
|
||||
@@ -551,7 +550,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* toggleVideo - mutes / unmutes video. no arguments
|
||||
* toggleFilmStrip - hides / shows the filmstrip. no arguments
|
||||
* toggleChat - hides / shows chat. no arguments.
|
||||
* toggleContactList - hides / shows contact list. no arguments.
|
||||
* toggleShareScreen - starts / stops screen sharing. no arguments.
|
||||
*
|
||||
* @param {Object} commandList - The object with commands to be executed.
|
||||
|
||||
128
modules/UI/UI.js
128
modules/UI/UI.js
@@ -30,19 +30,13 @@ import {
|
||||
} from '../../react/features/base/participants';
|
||||
import { destroyLocalTracks } from '../../react/features/base/tracks';
|
||||
import { openDisplayNamePrompt } from '../../react/features/display-name';
|
||||
import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
|
||||
import {
|
||||
setNotificationsEnabled,
|
||||
showWarningNotification
|
||||
} from '../../react/features/notifications';
|
||||
import {
|
||||
checkAutoEnableDesktopSharing,
|
||||
clearButtonPopup,
|
||||
dockToolbox,
|
||||
setButtonPopupTimeout,
|
||||
setToolbarButton,
|
||||
showDialPadButton,
|
||||
showEtherpadButton,
|
||||
showSharedVideoButton,
|
||||
showToolbox
|
||||
} from '../../react/features/toolbox';
|
||||
|
||||
@@ -100,9 +94,6 @@ const UIListeners = new Map([
|
||||
], [
|
||||
UIEvents.SHARED_VIDEO_CLICKED,
|
||||
() => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
|
||||
], [
|
||||
UIEvents.TOGGLE_FULLSCREEN,
|
||||
() => UI.toggleFullScreen()
|
||||
], [
|
||||
UIEvents.TOGGLE_CHAT,
|
||||
() => UI.toggleChat()
|
||||
@@ -120,9 +111,6 @@ const UIListeners = new Map([
|
||||
UI.toggleSidePanel('settings_container');
|
||||
}
|
||||
}
|
||||
], [
|
||||
UIEvents.TOGGLE_CONTACT_LIST,
|
||||
() => UI.toggleContactList()
|
||||
], [
|
||||
UIEvents.TOGGLE_PROFILE,
|
||||
() => UI.toggleSidePanel('profile_container')
|
||||
@@ -135,14 +123,6 @@ const UIListeners = new Map([
|
||||
]
|
||||
]);
|
||||
|
||||
/**
|
||||
* Toggles the application in and out of full screen mode
|
||||
* (a.k.a. presentation mode in Chrome).
|
||||
*/
|
||||
UI.toggleFullScreen = function() {
|
||||
UIUtil.isFullScreen() ? UIUtil.exitFullScreen() : UIUtil.enterFullScreen();
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates if we're currently in full screen mode.
|
||||
*
|
||||
@@ -255,12 +235,20 @@ UI.showLocalConnectionInterrupted = function(isInterrupted) {
|
||||
|
||||
/**
|
||||
* Sets the "raised hand" status for a participant.
|
||||
*
|
||||
* @param {string} id - The id of the participant whose raised hand UI should
|
||||
* be updated.
|
||||
* @param {string} name - The name of the participant with the raised hand
|
||||
* update.
|
||||
* @param {boolean} raisedHandStatus - Whether the participant's hand is raised
|
||||
* or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.setRaisedHandStatus = (participant, raisedHandStatus) => {
|
||||
VideoLayout.setRaisedHandStatus(participant.getId(), raisedHandStatus);
|
||||
UI.setRaisedHandStatus = (id, name, raisedHandStatus) => {
|
||||
VideoLayout.setRaisedHandStatus(id, raisedHandStatus);
|
||||
if (raisedHandStatus) {
|
||||
messageHandler.participantNotification(
|
||||
participant.getDisplayName(),
|
||||
name,
|
||||
'notify.somebody',
|
||||
'connected',
|
||||
'notify.raisedHand');
|
||||
@@ -280,7 +268,7 @@ UI.setLocalRaisedHandStatus
|
||||
* Initialize conference UI.
|
||||
*/
|
||||
UI.initConference = function() {
|
||||
const { dispatch, getState } = APP.store;
|
||||
const { getState } = APP.store;
|
||||
const { email, id, name } = getLocalParticipant(getState);
|
||||
|
||||
// Update default button states before showing the toolbar
|
||||
@@ -300,8 +288,6 @@ UI.initConference = function() {
|
||||
UI.setUserEmail(id, email);
|
||||
}
|
||||
|
||||
dispatch(checkAutoEnableDesktopSharing());
|
||||
|
||||
// FollowMe attempts to copy certain aspects of the moderator's UI into the
|
||||
// other participants' UI. Consequently, it needs (1) read and write access
|
||||
// to the UI (depending on the moderator role of the local participant) and
|
||||
@@ -354,25 +340,28 @@ UI.start = function() {
|
||||
VideoLayout.resizeVideoArea(true, true);
|
||||
|
||||
sharedVideoManager = new SharedVideoManager(eventEmitter);
|
||||
// eslint-disable-next-line no-negated-condition
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
$('body').addClass('filmstrip-only');
|
||||
Filmstrip.setFilmstripOnly();
|
||||
APP.store.dispatch(setNotificationsEnabled(false));
|
||||
} else {
|
||||
// Initialise the recording module.
|
||||
if (config.enableRecording) {
|
||||
Recording.init(eventEmitter, config.recordingType);
|
||||
}
|
||||
config.enableRecording
|
||||
&& Recording.init(eventEmitter, config.recordingType);
|
||||
|
||||
// Initialize side panels
|
||||
SidePanels.init(eventEmitter);
|
||||
} else {
|
||||
$('body').addClass('filmstrip-only');
|
||||
UI.showToolbar();
|
||||
Filmstrip.setFilmstripOnly();
|
||||
APP.store.dispatch(setNotificationsEnabled(false));
|
||||
|
||||
// TODO: remove this class once the old toolbar has been removed. This
|
||||
// class is set so that any CSS changes needed to adjust elements
|
||||
// outside of the new toolbar can be scoped to just the app with the new
|
||||
// toolbar enabled.
|
||||
$('body').addClass('use-new-toolbox');
|
||||
}
|
||||
|
||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
$('body').addClass('vertical-filmstrip');
|
||||
}
|
||||
interfaceConfig.VERTICAL_FILMSTRIP
|
||||
&& $('body').addClass('vertical-filmstrip');
|
||||
|
||||
document.title = interfaceConfig.APP_NAME;
|
||||
};
|
||||
@@ -404,12 +393,7 @@ UI.bindEvents = () => {
|
||||
// Resize and reposition videos in full screen mode.
|
||||
$(document).on(
|
||||
'webkitfullscreenchange mozfullscreenchange fullscreenchange',
|
||||
() => {
|
||||
eventEmitter.emit(
|
||||
UIEvents.FULLSCREEN_TOGGLED,
|
||||
UIUtil.isFullScreen());
|
||||
onResize();
|
||||
});
|
||||
onResize);
|
||||
|
||||
$(window).resize(onResize);
|
||||
};
|
||||
@@ -456,12 +440,6 @@ UI.addRemoteStream = track => VideoLayout.onRemoteStreamAdded(track);
|
||||
*/
|
||||
UI.removeRemoteStream = track => VideoLayout.onRemoteStreamRemoved(track);
|
||||
|
||||
/**
|
||||
* Update chat subject.
|
||||
* @param {string} subject new chat subject
|
||||
*/
|
||||
UI.setSubject = subject => Chat.setSubject(subject);
|
||||
|
||||
/**
|
||||
* Setup and show Etherpad.
|
||||
* @param {string} name etherpad id
|
||||
@@ -474,7 +452,7 @@ UI.initEtherpad = name => {
|
||||
etherpadManager
|
||||
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
|
||||
|
||||
APP.store.dispatch(showEtherpadButton());
|
||||
APP.store.dispatch(setEtherpadHasInitialzied());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -542,10 +520,6 @@ UI.onPeerVideoTypeChanged
|
||||
UI.updateLocalRole = isModerator => {
|
||||
VideoLayout.showModeratorIndicator();
|
||||
|
||||
APP.store.dispatch(showSharedVideoButton());
|
||||
|
||||
Recording.showRecordingButton(isModerator);
|
||||
|
||||
if (isModerator) {
|
||||
if (!interfaceConfig.DISABLE_FOCUS_INDICATOR) {
|
||||
messageHandler.participantNotification(
|
||||
@@ -639,11 +613,6 @@ UI.isChatVisible = () => Chat.isVisible();
|
||||
*/
|
||||
UI.toggleChat = () => UI.toggleSidePanel('chat_container');
|
||||
|
||||
/**
|
||||
* Toggles contact list panel.
|
||||
*/
|
||||
UI.toggleContactList = () => UI.toggleSidePanel('contacts_container');
|
||||
|
||||
/**
|
||||
* Toggles the given side panel.
|
||||
*
|
||||
@@ -659,27 +628,6 @@ UI.inputDisplayNameHandler = function(newDisplayName) {
|
||||
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newDisplayName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show custom popup/tooltip for a specified button.
|
||||
*
|
||||
* @param {string} buttonName - The name of the button as specified in the
|
||||
* button configurations for the toolbar.
|
||||
* @param {string} popupSelectorID - The id of the popup to show as specified in
|
||||
* the button configurations for the toolbar.
|
||||
* @param {boolean} show - True or false/show or hide the popup
|
||||
* @param {number} timeout - The time to show the popup
|
||||
* @returns {void}
|
||||
*/
|
||||
// eslint-disable-next-line max-params
|
||||
UI.showCustomToolbarPopup = function(buttonName, popupID, show, timeout) {
|
||||
const action
|
||||
= show
|
||||
? setButtonPopupTimeout(buttonName, popupID, timeout)
|
||||
: clearButtonPopup(buttonName);
|
||||
|
||||
APP.store.dispatch(action);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the type of the remote video.
|
||||
* @param jid the jid for the remote video
|
||||
@@ -902,17 +850,6 @@ UI.promptDisplayName = () => {
|
||||
*/
|
||||
UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl);
|
||||
|
||||
/**
|
||||
* Update state of desktop sharing buttons.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.updateDesktopSharingButtons
|
||||
= () =>
|
||||
APP.store.dispatch(setToolbarButton('desktop', {
|
||||
toggled: APP.conference.isSharingScreen
|
||||
}));
|
||||
|
||||
/**
|
||||
* Hide connection quality statistics from UI.
|
||||
*/
|
||||
@@ -944,9 +881,6 @@ UI.addMessage = function(from, displayName, message, stamp) {
|
||||
Chat.updateChatConversation(from, displayName, message, stamp);
|
||||
};
|
||||
|
||||
UI.updateDTMFSupport
|
||||
= isDTMFSupported => APP.store.dispatch(showDialPadButton(isDTMFSupported));
|
||||
|
||||
UI.updateRecordingState = function(state) {
|
||||
Recording.updateRecordingState(state);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/* global $, interfaceConfig */
|
||||
/* global $, APP, interfaceConfig */
|
||||
|
||||
import { setDocumentEditingState } from '../../../react/features/etherpad';
|
||||
import { getToolboxHeight } from '../../../react/features/toolbox';
|
||||
|
||||
import VideoLayout from '../videolayout/VideoLayout';
|
||||
import LargeContainer from '../videolayout/LargeContainer';
|
||||
@@ -126,7 +129,7 @@ class Etherpad extends LargeContainer {
|
||||
let height, width;
|
||||
|
||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
height = containerHeight;
|
||||
height = containerHeight - getToolboxHeight();
|
||||
width = containerWidth - Filmstrip.getFilmstripWidth();
|
||||
} else {
|
||||
height = containerHeight - Filmstrip.getFilmstripHeight();
|
||||
@@ -152,6 +155,9 @@ class Etherpad extends LargeContainer {
|
||||
document.body.style.background = '#eeeeee';
|
||||
$iframe.css({ visibility: 'visible' });
|
||||
$container.css({ zIndex: 2 });
|
||||
|
||||
APP.store.dispatch(setDocumentEditingState(true));
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -170,6 +176,9 @@ class Etherpad extends LargeContainer {
|
||||
$iframe.fadeOut(300, () => {
|
||||
$iframe.css({ visibility: 'hidden' });
|
||||
$container.css({ zIndex: 0 });
|
||||
|
||||
APP.store.dispatch(setDocumentEditingState(false));
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -242,5 +251,7 @@ export default class EtherpadManager {
|
||||
|
||||
this.eventEmitter
|
||||
.emit(UIEvents.TOGGLED_SHARED_DOCUMENT, !isVisible);
|
||||
|
||||
APP.store.dispatch(setDocumentEditingState(!isVisible));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global APP, $, config, interfaceConfig */
|
||||
/* global APP, config, interfaceConfig */
|
||||
/*
|
||||
* Copyright @ 2015 Atlassian Pty Ltd
|
||||
*
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
StartLiveStreamDialog,
|
||||
StopLiveStreamDialog,
|
||||
hideRecordingLabel,
|
||||
setRecordingType,
|
||||
updateRecordingState
|
||||
} from '../../../react/features/recording';
|
||||
|
||||
@@ -108,7 +109,10 @@ function _requestLiveStreamId() {
|
||||
return new Promise((resolve, reject) =>
|
||||
APP.store.dispatch(openDialog(StartLiveStreamDialog, {
|
||||
onCancel: reject,
|
||||
onSubmit: resolve
|
||||
onSubmit: (streamId, broadcastId) => resolve({
|
||||
broadcastId,
|
||||
streamId
|
||||
})
|
||||
})));
|
||||
}
|
||||
|
||||
@@ -191,8 +195,7 @@ function isStartingStatus(status) {
|
||||
|
||||
/**
|
||||
* Manages the recording user interface and user experience.
|
||||
* @type {{init, initRecordingButton, showRecordingButton, updateRecordingState,
|
||||
* updateRecordingUI, checkAutoRecord}}
|
||||
* @type {{init, updateRecordingState, updateRecordingUI, checkAutoRecord}}
|
||||
*/
|
||||
const Recording = {
|
||||
/**
|
||||
@@ -202,6 +205,8 @@ const Recording = {
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.recordingType = recordingType;
|
||||
|
||||
APP.store.dispatch(setRecordingType(recordingType));
|
||||
|
||||
this.updateRecordingState(APP.conference.getRecordingState());
|
||||
|
||||
if (recordingType === 'jibri') {
|
||||
@@ -212,12 +217,8 @@ const Recording = {
|
||||
Object.assign(this, RECORDING_TRANSLATION_KEYS);
|
||||
}
|
||||
|
||||
// XXX Due to the React-ification of Toolbox, the HTMLElement with id
|
||||
// toolbar_button_record may not exist yet.
|
||||
$(document).on(
|
||||
'click',
|
||||
'#toolbar_button_record',
|
||||
ev => this._onToolbarButtonClick(ev));
|
||||
this.eventEmitter.on(UIEvents.TOGGLE_RECORDING,
|
||||
() => this._onToolbarButtonClick());
|
||||
|
||||
// If I am a recorder then I publish my recorder custom role to notify
|
||||
// everyone.
|
||||
@@ -236,28 +237,6 @@ const Recording = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialise the recording button.
|
||||
*/
|
||||
initRecordingButton() {
|
||||
const selector = $('#toolbar_button_record');
|
||||
|
||||
selector.addClass(this.baseClass);
|
||||
selector.attr('data-i18n', `[content]${this.recordingButtonTooltip}`);
|
||||
APP.translation.translateElement(selector);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows or hides the 'recording' button.
|
||||
* @param show {true} to show the recording button, {false} to hide it
|
||||
*/
|
||||
showRecordingButton(show) {
|
||||
const shouldShow = show && _isRecordingButtonEnabled();
|
||||
const id = 'toolbar_button_record';
|
||||
|
||||
UIUtil.setVisible(id, shouldShow);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the recording state UI.
|
||||
* @param recordingState gives us the current recording state
|
||||
@@ -281,12 +260,12 @@ const Recording = {
|
||||
* @param recordingState gives us the current recording state
|
||||
*/
|
||||
updateRecordingUI(recordingState) {
|
||||
|
||||
const oldState = this.currentState;
|
||||
|
||||
this.currentState = recordingState;
|
||||
|
||||
let labelDisplayConfiguration;
|
||||
let isRecording = false;
|
||||
|
||||
switch (recordingState) {
|
||||
case JitsiRecordingStatus.ON:
|
||||
@@ -297,7 +276,7 @@ const Recording = {
|
||||
showSpinner: recordingState === JitsiRecordingStatus.RETRYING
|
||||
};
|
||||
|
||||
this._setToolbarButtonToggled(true);
|
||||
isRecording = true;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -322,8 +301,6 @@ const Recording = {
|
||||
: this.recordingOffKey
|
||||
};
|
||||
|
||||
this._setToolbarButtonToggled(false);
|
||||
|
||||
setTimeout(() => {
|
||||
APP.store.dispatch(hideRecordingLabel());
|
||||
}, 5000);
|
||||
@@ -337,8 +314,6 @@ const Recording = {
|
||||
key: this.recordingPendingKey
|
||||
};
|
||||
|
||||
this._setToolbarButtonToggled(false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -348,8 +323,6 @@ const Recording = {
|
||||
key: this.recordingErrorKey
|
||||
};
|
||||
|
||||
this._setToolbarButtonToggled(false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -362,6 +335,7 @@ const Recording = {
|
||||
}
|
||||
|
||||
APP.store.dispatch(updateRecordingState({
|
||||
isRecording,
|
||||
labelDisplayConfiguration,
|
||||
recordingState
|
||||
}));
|
||||
@@ -416,10 +390,13 @@ const Recording = {
|
||||
case JitsiRecordingStatus.OFF: {
|
||||
if (this.recordingType === 'jibri') {
|
||||
_requestLiveStreamId()
|
||||
.then(streamId => {
|
||||
.then(({ broadcastId, streamId }) => {
|
||||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED,
|
||||
{ streamId });
|
||||
{
|
||||
broadcastId,
|
||||
streamId
|
||||
});
|
||||
|
||||
// The confirm button on the start recording dialog was
|
||||
// clicked
|
||||
@@ -479,16 +456,6 @@ const Recording = {
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the toggled state of the recording toolbar button.
|
||||
*
|
||||
* @param {boolean} isToggled indicates if the button should be toggled
|
||||
* or not
|
||||
*/
|
||||
_setToolbarButtonToggled(isToggled) {
|
||||
$('#toolbar_button_record').toggleClass('toggled', isToggled);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,11 @@ import {
|
||||
participantJoined,
|
||||
participantLeft
|
||||
} from '../../../react/features/base/participants';
|
||||
import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
|
||||
import {
|
||||
dockToolbox,
|
||||
getToolboxHeight,
|
||||
showToolbox
|
||||
} from '../../../react/features/toolbox';
|
||||
|
||||
import SharedVideoThumb from './SharedVideoThumb';
|
||||
|
||||
@@ -282,7 +286,7 @@ export default class SharedVideoManager {
|
||||
|
||||
thumb.setDisplayName('YouTube');
|
||||
VideoLayout.addRemoteVideoContainer(self.url, thumb);
|
||||
VideoLayout.resizeThumbnails(false, true);
|
||||
VideoLayout.resizeThumbnails(true);
|
||||
|
||||
const iframe = player.getIframe();
|
||||
|
||||
@@ -360,7 +364,6 @@ export default class SharedVideoManager {
|
||||
|
||||
player.setVolume(attributes.volume);
|
||||
logger.info(`Player change of volume:${attributes.volume}`);
|
||||
this.showSharedVideoMutedPopup(false);
|
||||
}
|
||||
|
||||
if (isPlayerPaused) {
|
||||
@@ -560,8 +563,6 @@ export default class SharedVideoManager {
|
||||
this.smartAudioMute();
|
||||
}
|
||||
}
|
||||
|
||||
this.showSharedVideoMutedPopup(mute);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -576,7 +577,6 @@ export default class SharedVideoManager {
|
||||
sendAnalytics(createEvent('audio.unmuted'));
|
||||
logger.log('Shared video: audio unmuted');
|
||||
this.emitter.emit(UIEvents.AUDIO_MUTED, false, false);
|
||||
this.showMicMutedPopup(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,38 +590,8 @@ export default class SharedVideoManager {
|
||||
sendAnalytics(createEvent('audio.muted'));
|
||||
logger.log('Shared video: audio muted');
|
||||
this.emitter.emit(UIEvents.AUDIO_MUTED, true, false);
|
||||
this.showMicMutedPopup(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a popup under the microphone toolbar icon that notifies the user
|
||||
* of automatic mute after a shared video has started.
|
||||
* @param show boolean, show or hide the notification
|
||||
*/
|
||||
showMicMutedPopup(show) {
|
||||
if (show) {
|
||||
this.showSharedVideoMutedPopup(false);
|
||||
}
|
||||
|
||||
APP.UI.showCustomToolbarPopup(
|
||||
'microphone', 'micMutedPopup', show, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a popup under the shared video toolbar icon that notifies the user
|
||||
* of automatic mute of the shared video after the user has unmuted their
|
||||
* mic.
|
||||
* @param show boolean, show or hide the notification
|
||||
*/
|
||||
showSharedVideoMutedPopup(show) {
|
||||
if (show) {
|
||||
this.showMicMutedPopup(false);
|
||||
}
|
||||
|
||||
APP.UI.showCustomToolbarPopup(
|
||||
'sharedvideo', 'sharedVideoMutedPopup', show, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -695,7 +665,7 @@ class SharedVideoContainer extends LargeContainer {
|
||||
let height, width;
|
||||
|
||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
height = containerHeight;
|
||||
height = containerHeight - getToolboxHeight();
|
||||
width = containerWidth - Filmstrip.getFilmstripWidth();
|
||||
} else {
|
||||
height = containerHeight - Filmstrip.getFilmstripHeight();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* global $ */
|
||||
/* global $, APP */
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import { setVisiblePanel } from '../../../react/features/side-panel';
|
||||
|
||||
/**
|
||||
* Handles open and close of the extended toolbar side panel
|
||||
@@ -57,6 +58,7 @@ const SideContainerToggler = {
|
||||
|
||||
if (isSelectorVisible) {
|
||||
this.hide();
|
||||
APP.store.dispatch(setVisiblePanel(null));
|
||||
} else {
|
||||
if (this.isVisible()) {
|
||||
$('#sideToolbarContainer').children()
|
||||
@@ -74,6 +76,7 @@ const SideContainerToggler = {
|
||||
}
|
||||
|
||||
this.showInnerContainer(elementSelector);
|
||||
APP.store.dispatch(setVisiblePanel(elementId));
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Chat from './chat/Chat';
|
||||
import SettingsMenu from './settings/SettingsMenu';
|
||||
import Profile from './profile/Profile';
|
||||
import ContactListView from './contactlist/ContactListView';
|
||||
import { isButtonEnabled } from '../../../react/features/toolbox';
|
||||
|
||||
const SidePanels = {
|
||||
@@ -20,11 +19,6 @@ const SidePanels = {
|
||||
if (isButtonEnabled('profile')) {
|
||||
Profile.init(eventEmitter);
|
||||
}
|
||||
|
||||
// Initialize contact list view
|
||||
if (isButtonEnabled('contacts')) {
|
||||
ContactListView.init();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* global APP, $ */
|
||||
|
||||
import { processReplacements, linkify } from './Replacement';
|
||||
import CommandsProcessor from './Commands';
|
||||
import { processReplacements } from './Replacement';
|
||||
import VideoLayout from '../../videolayout/VideoLayout';
|
||||
|
||||
import UIUtil from '../../util/UIUtil';
|
||||
@@ -9,7 +8,11 @@ import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import { smileys } from './smileys';
|
||||
|
||||
import { dockToolbox, setSubject } from '../../../../react/features/toolbox';
|
||||
import { addMessage, markAllRead } from '../../../../react/features/chat';
|
||||
import {
|
||||
dockToolbox,
|
||||
getToolboxHeight
|
||||
} from '../../../../react/features/toolbox';
|
||||
|
||||
let unreadMessages = 0;
|
||||
const sidePanelsContainerId = 'sideToolbarContainer';
|
||||
@@ -163,6 +166,8 @@ function addSmileys() {
|
||||
* Resizes the chat conversation.
|
||||
*/
|
||||
function resizeChatConversation() {
|
||||
// FIXME: this function can all be done with CSS. If Chat is ever rewritten,
|
||||
// do not copy over this logic.
|
||||
const msgareaHeight = $('#usermsg').outerHeight();
|
||||
const chatspace = $(`#${CHAT_CONTAINER_ID}`);
|
||||
const width = chatspace.width();
|
||||
@@ -173,7 +178,12 @@ function resizeChatConversation() {
|
||||
$('#smileys').css('bottom', (msgareaHeight - 26) / 2);
|
||||
$('#smileysContainer').css('bottom', msgareaHeight);
|
||||
chat.width(width - 10);
|
||||
chat.height(window.innerHeight - 15 - msgareaHeight);
|
||||
|
||||
const maybeAMagicNumberForPaddingAndMargin = 100;
|
||||
const offset = maybeAMagicNumberForPaddingAndMargin
|
||||
+ msgareaHeight + getToolboxHeight();
|
||||
|
||||
chat.height(window.innerHeight - offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,15 +233,10 @@ const Chat = {
|
||||
|
||||
usermsg.val('').trigger('autosize.resize');
|
||||
this.focus();// eslint-disable-line no-invalid-this
|
||||
const command = new CommandsProcessor(value, eventEmitter);
|
||||
|
||||
if (command.isCommand()) {
|
||||
command.processCommand();
|
||||
} else {
|
||||
const message = UIUtil.escapeHtml(value);
|
||||
const message = UIUtil.escapeHtml(value);
|
||||
|
||||
eventEmitter.emit(UIEvents.MESSAGE_CREATED, message);
|
||||
}
|
||||
eventEmitter.emit(UIEvents.MESSAGE_CREATED, message);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -249,6 +254,7 @@ const Chat = {
|
||||
}
|
||||
|
||||
unreadMessages = 0;
|
||||
APP.store.dispatch(markAllRead());
|
||||
updateVisualNotification();
|
||||
|
||||
// Undock the toolbar when the chat is shown and if we're in a
|
||||
@@ -274,9 +280,10 @@ const Chat = {
|
||||
*/
|
||||
// eslint-disable-next-line max-params
|
||||
updateChatConversation(id, displayName, message, stamp) {
|
||||
const isFromLocalParticipant = APP.conference.isLocalId(id);
|
||||
let divClassName = '';
|
||||
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
if (isFromLocalParticipant) {
|
||||
divClassName = 'localuser';
|
||||
} else {
|
||||
divClassName = 'remoteuser';
|
||||
@@ -294,6 +301,7 @@ const Chat = {
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\n/g, '<br/>');
|
||||
const escDisplayName = UIUtil.escapeHtml(displayName);
|
||||
const timestamp = getCurrentTime(stamp);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
message = processReplacements(escMessage);
|
||||
@@ -302,13 +310,18 @@ const Chat = {
|
||||
= `${'<div class="chatmessage">'
|
||||
+ '<img src="images/chatArrow.svg" class="chatArrow">'
|
||||
+ '<div class="username '}${divClassName}">${escDisplayName
|
||||
}</div><div class="timestamp">${getCurrentTime(stamp)
|
||||
}</div><div class="timestamp">${timestamp
|
||||
}</div><div class="usermessage">${message}</div>`
|
||||
+ '</div>';
|
||||
|
||||
$('#chatconversation').append(messageContainer);
|
||||
$('#chatconversation').animate(
|
||||
{ scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
|
||||
|
||||
const markAsRead = Chat.isVisible() || isFromLocalParticipant;
|
||||
|
||||
APP.store.dispatch(addMessage(
|
||||
escDisplayName, message, timestamp, markAsRead));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -331,21 +344,6 @@ const Chat = {
|
||||
{ scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the subject to the UI
|
||||
* @param subject the subject
|
||||
*/
|
||||
setSubject(subject) {
|
||||
if (subject) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
subject = subject.trim();
|
||||
}
|
||||
|
||||
const html = linkify(UIUtil.escapeHtml(subject));
|
||||
|
||||
APP.store.dispatch(setSubject(html));
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the chat conversation mode.
|
||||
* Conversation mode is the normal chat mode, non conversation mode is
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import UIUtil from '../../util/UIUtil';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
/**
|
||||
* List with supported commands. The keys are the names of the commands and
|
||||
* the value is the function that processes the message.
|
||||
* @type {{String: function}}
|
||||
*/
|
||||
const commands = {
|
||||
'topic': processTopic
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the command from the message.
|
||||
* @param message the received message
|
||||
* @returns {string} the command
|
||||
*/
|
||||
function getCommand(message) {
|
||||
if (message) {
|
||||
for (const command in commands) {
|
||||
if (message.indexOf(`/${command}`) === 0) {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the data for topic command.
|
||||
* @param commandArguments the arguments of the topic command.
|
||||
*/
|
||||
function processTopic(commandArguments, emitter) {
|
||||
const topic = UIUtil.escapeHtml(commandArguments);
|
||||
|
||||
emitter.emit(UIEvents.SUBJECT_CHANGED, topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new CommandProccessor instance from a message that
|
||||
* handles commands received via chat messages.
|
||||
* @param message the message
|
||||
* @constructor
|
||||
*/
|
||||
function CommandsProcessor(message, emitter) {
|
||||
const command = getCommand(message);
|
||||
|
||||
this.emitter = emitter;
|
||||
|
||||
/**
|
||||
* Returns the name of the command.
|
||||
* @returns {String} the command
|
||||
*/
|
||||
this.getCommand = function() {
|
||||
return command;
|
||||
};
|
||||
|
||||
|
||||
const messageArgument = message.substr(command.length + 2);
|
||||
|
||||
/**
|
||||
* Returns the arguments of the command.
|
||||
* @returns {string}
|
||||
*/
|
||||
this.getArgument = function() {
|
||||
return messageArgument;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this instance is valid command or not.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
CommandsProcessor.prototype.isCommand = function() {
|
||||
if (this.getCommand()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes the command.
|
||||
*/
|
||||
CommandsProcessor.prototype.processCommand = function() {
|
||||
if (!this.isCommand()) {
|
||||
return;
|
||||
}
|
||||
|
||||
commands[this.getCommand()](this.getArgument(), this.emitter);
|
||||
};
|
||||
|
||||
export default CommandsProcessor;
|
||||
@@ -1,56 +0,0 @@
|
||||
/* global $, APP */
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { i18next } from '../../../../react/features/base/i18n';
|
||||
import { ContactListPanel } from '../../../../react/features/contact-list';
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
import UIUtil from '../../util/UIUtil';
|
||||
|
||||
/**
|
||||
* Contact list.
|
||||
*
|
||||
* FIXME: One day this view should no longer be called "contact list" because
|
||||
* the term "contact" is not used elsewhere. Normally people in the conference
|
||||
* are internally refered to as "participants" or externally as "members".
|
||||
*/
|
||||
const ContactListView = {
|
||||
/**
|
||||
* Creates and appends the contact list to the side panel.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
init() {
|
||||
const contactListPanelContainer = document.createElement('div');
|
||||
|
||||
contactListPanelContainer.id = 'contacts_container';
|
||||
contactListPanelContainer.className = 'sideToolbarContainer__inner';
|
||||
|
||||
$('#sideToolbarContainer').append(contactListPanelContainer);
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store = { APP.store }>
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<ContactListPanel />
|
||||
</I18nextProvider>
|
||||
</Provider>,
|
||||
contactListPanelContainer
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates if the contact list is currently visible.
|
||||
*
|
||||
* @return {boolean) true if the contact list is currently visible.
|
||||
*/
|
||||
isVisible() {
|
||||
return UIUtil.isVisible(document.getElementById('contactlist'));
|
||||
}
|
||||
};
|
||||
|
||||
export default ContactListView;
|
||||
@@ -227,43 +227,10 @@ const UIUtil = {
|
||||
* mode, {false} otherwise
|
||||
*/
|
||||
isFullScreen() {
|
||||
return document.fullscreenElement
|
||||
return Boolean(document.fullscreenElement
|
||||
|| document.mozFullScreenElement
|
||||
|| document.webkitFullscreenElement
|
||||
|| document.msFullscreenElement;
|
||||
},
|
||||
|
||||
/**
|
||||
* Exits full screen mode.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
|
||||
*/
|
||||
exitFullScreen() {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Enter full screen mode.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
|
||||
*/
|
||||
enterFullScreen() {
|
||||
if (document.documentElement.requestFullscreen) {
|
||||
document.documentElement.requestFullscreen();
|
||||
} else if (document.documentElement.msRequestFullscreen) {
|
||||
document.documentElement.msRequestFullscreen();
|
||||
} else if (document.documentElement.mozRequestFullScreen) {
|
||||
document.documentElement.mozRequestFullScreen();
|
||||
} else if (document.documentElement.webkitRequestFullscreen) {
|
||||
document.documentElement
|
||||
.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
||||
}
|
||||
|| document.msFullscreenElement);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -438,87 +438,56 @@ const Filmstrip = {
|
||||
* Resizes thumbnails
|
||||
* @param local
|
||||
* @param remote
|
||||
* @param animate
|
||||
* @param forceUpdate
|
||||
* @returns {Promise}
|
||||
*/
|
||||
// eslint-disable-next-line max-params
|
||||
resizeThumbnails(local, remote, animate = false, forceUpdate = false) {
|
||||
return new Promise(resolve => {
|
||||
const thumbs = this.getThumbs(!forceUpdate);
|
||||
const promises = [];
|
||||
resizeThumbnails(local, remote, forceUpdate = false) {
|
||||
const thumbs = this.getThumbs(!forceUpdate);
|
||||
|
||||
if (thumbs.localThumb) {
|
||||
// eslint-disable-next-line no-shadow
|
||||
promises.push(new Promise(resolve => {
|
||||
thumbs.localThumb.animate({
|
||||
height: local.thumbHeight,
|
||||
'min-height': local.thumbHeight,
|
||||
'min-width': local.thumbWidth,
|
||||
width: local.thumbWidth
|
||||
}, this._getAnimateOptions(animate, resolve));
|
||||
}));
|
||||
}
|
||||
if (thumbs.remoteThumbs) {
|
||||
// eslint-disable-next-line no-shadow
|
||||
promises.push(new Promise(resolve => {
|
||||
thumbs.remoteThumbs.animate({
|
||||
height: remote.thumbHeight,
|
||||
'min-height': remote.thumbHeight,
|
||||
'min-width': remote.thumbWidth,
|
||||
width: remote.thumbWidth
|
||||
}, this._getAnimateOptions(animate, resolve));
|
||||
}));
|
||||
}
|
||||
if (thumbs.localThumb) {
|
||||
// eslint-disable-next-line no-shadow
|
||||
promises.push(new Promise(resolve => {
|
||||
// Let CSS take care of height in vertical filmstrip mode.
|
||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
$('#filmstripLocalVideo').animate({
|
||||
// adds 4 px because of small video 2px border
|
||||
width: local.thumbWidth + 4
|
||||
}, this._getAnimateOptions(animate, resolve));
|
||||
} else {
|
||||
this.filmstrip.animate({
|
||||
// adds 4 px because of small video 2px border
|
||||
height: remote.thumbHeight + 4
|
||||
}, this._getAnimateOptions(animate, resolve));
|
||||
}
|
||||
}));
|
||||
thumbs.localThumb.css({
|
||||
display: 'inline-block',
|
||||
height: `${local.thumbHeight}px`,
|
||||
'min-height': `${local.thumbHeight}px`,
|
||||
'min-width': `${local.thumbWidth}px`,
|
||||
width: `${local.thumbWidth}px`
|
||||
});
|
||||
}
|
||||
|
||||
promises.push(new Promise(() => {
|
||||
const { localThumb } = this.getThumbs();
|
||||
const height = localThumb ? localThumb.height() : 0;
|
||||
const fontSize = UIUtil.getIndicatorFontSize(height);
|
||||
if (thumbs.remoteThumbs) {
|
||||
thumbs.remoteThumbs.css({
|
||||
display: 'inline-block',
|
||||
height: `${remote.thumbHeight}px`,
|
||||
'min-height': `${remote.thumbHeight}px`,
|
||||
'min-width': `${remote.thumbWidth}px`,
|
||||
width: `${remote.thumbWidth}px`
|
||||
});
|
||||
}
|
||||
|
||||
this.filmstrip.find('.indicator').animate({
|
||||
fontSize
|
||||
}, this._getAnimateOptions(animate, resolve));
|
||||
}));
|
||||
// Let CSS take care of height in vertical filmstrip mode.
|
||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
$('#filmstripLocalVideo').css({
|
||||
// adds 4 px because of small video 2px border
|
||||
width: `${local.thumbWidth + 4}px`
|
||||
});
|
||||
} else {
|
||||
this.filmstrip.css({
|
||||
// adds 4 px because of small video 2px border
|
||||
height: `${remote.thumbHeight + 4}px`
|
||||
});
|
||||
}
|
||||
|
||||
if (!animate) {
|
||||
resolve();
|
||||
}
|
||||
const { localThumb } = this.getThumbs();
|
||||
const height = localThumb ? localThumb.height() : 0;
|
||||
const fontSize = UIUtil.getIndicatorFontSize(height);
|
||||
|
||||
Promise.all(promises).then(resolve);
|
||||
this.filmstrip.find('.indicator').css({
|
||||
'font-size': `${fontSize}px`
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper method. Returns options for jQuery animation
|
||||
* @param animate {Boolean} - animation flag
|
||||
* @param cb {Function} - complete callback
|
||||
* @returns {Object} - animation options object
|
||||
* @private
|
||||
*/
|
||||
_getAnimateOptions(animate, cb = $.noop) {
|
||||
return {
|
||||
queue: false,
|
||||
duration: animate ? 500 : 0,
|
||||
complete: cb
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns thumbnails of the filmstrip
|
||||
* @param onlyVisible
|
||||
|
||||
@@ -95,7 +95,7 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
|
||||
|
||||
this.updateRemoteVideoMenu();
|
||||
|
||||
this.VideoLayout.resizeThumbnails(false, true);
|
||||
this.VideoLayout.resizeThumbnails(true);
|
||||
|
||||
this.addAudioLevelIndicator();
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ const VideoLayout = {
|
||||
init(emitter) {
|
||||
eventEmitter = emitter;
|
||||
|
||||
// Unregister listeners in case of reinitialization
|
||||
this.unregisterListeners();
|
||||
|
||||
localVideoThumbnail = new LocalVideo(VideoLayout, emitter);
|
||||
|
||||
// sets default video type of local video
|
||||
@@ -77,7 +74,7 @@ const VideoLayout = {
|
||||
|
||||
// if we do not resize the thumbs here, if there is no video device
|
||||
// the local video thumb maybe one pixel
|
||||
this.resizeThumbnails(false, true);
|
||||
this.resizeThumbnails(true);
|
||||
|
||||
this.handleVideoThumbClicked = this.handleVideoThumbClicked.bind(this);
|
||||
|
||||
@@ -104,20 +101,6 @@ const VideoLayout = {
|
||||
registerListeners() {
|
||||
eventEmitter.addListener(UIEvents.LOCAL_FLIPX_CHANGED,
|
||||
onLocalFlipXChanged);
|
||||
eventEmitter.addListener(UIEvents.CONTACT_CLICKED,
|
||||
this.handleVideoThumbClicked);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregistering listeners for UI events in Video layout component.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
unregisterListeners() {
|
||||
if (this._onContactClicked) {
|
||||
eventEmitter.removeListener(UIEvents.CONTACT_CLICKED,
|
||||
this.handleVideoThumbClicked);
|
||||
}
|
||||
},
|
||||
|
||||
initLargeVideo() {
|
||||
@@ -485,7 +468,7 @@ const VideoLayout = {
|
||||
remoteVideo.setVideoType(VIDEO_CONTAINER_TYPE);
|
||||
}
|
||||
|
||||
VideoLayout.resizeThumbnails(false, true);
|
||||
VideoLayout.resizeThumbnails(true);
|
||||
|
||||
// Initialize the view
|
||||
remoteVideo.updateView();
|
||||
@@ -497,7 +480,7 @@ const VideoLayout = {
|
||||
logger.info(`${resourceJid} video is now active`, videoElement);
|
||||
|
||||
VideoLayout.resizeThumbnails(
|
||||
false, false, () => {
|
||||
false, () => {
|
||||
if (videoElement) {
|
||||
$(videoElement).show();
|
||||
}
|
||||
@@ -592,19 +575,16 @@ const VideoLayout = {
|
||||
* Resizes thumbnails.
|
||||
*/
|
||||
resizeThumbnails(
|
||||
animate = false,
|
||||
forceUpdate = false,
|
||||
onComplete = null) {
|
||||
const { localVideo, remoteVideo }
|
||||
= Filmstrip.calculateThumbnailSize();
|
||||
|
||||
Filmstrip.resizeThumbnails(localVideo, remoteVideo,
|
||||
animate, forceUpdate)
|
||||
.then(() => {
|
||||
if (onComplete && typeof onComplete === 'function') {
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
Filmstrip.resizeThumbnails(localVideo, remoteVideo, forceUpdate);
|
||||
|
||||
if (onComplete && typeof onComplete === 'function') {
|
||||
onComplete();
|
||||
}
|
||||
|
||||
return { localVideo,
|
||||
remoteVideo };
|
||||
@@ -896,7 +876,7 @@ const VideoLayout = {
|
||||
}
|
||||
|
||||
// Resize the thumbnails first.
|
||||
this.resizeThumbnails(false, forceUpdate);
|
||||
this.resizeThumbnails(forceUpdate);
|
||||
|
||||
// Resize the video area element.
|
||||
$('#videospace').animate({
|
||||
@@ -1112,7 +1092,7 @@ const VideoLayout = {
|
||||
* Currently used by tests (torture).
|
||||
*/
|
||||
getLargeVideoID() {
|
||||
return largeVideo.id;
|
||||
return largeVideo && largeVideo.id;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,9 +15,9 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Map of shortcuts. When a shortcut is registered it enters the mapping.
|
||||
* @type {{}}
|
||||
* @type {Map}
|
||||
*/
|
||||
const _shortcuts = {};
|
||||
const _shortcuts = new Map();
|
||||
|
||||
/**
|
||||
* Map of registered keyboard keys and translation keys describing the
|
||||
@@ -49,8 +49,8 @@ const KeyboardShortcut = {
|
||||
if (!($(':focus').is('input[type=text]')
|
||||
|| $(':focus').is('input[type=password]')
|
||||
|| $(':focus').is('textarea'))) {
|
||||
if (_shortcuts.hasOwnProperty(key)) {
|
||||
_shortcuts[key].function(e);
|
||||
if (_shortcuts.has(key)) {
|
||||
_shortcuts.get(key).function(e);
|
||||
} else if (!isNaN(num) && num >= 0 && num <= 9) {
|
||||
APP.UI.clickOnVideo(num);
|
||||
}
|
||||
@@ -90,6 +90,17 @@ const KeyboardShortcut = {
|
||||
enabled = value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the {@KeyboardShortcutsDialog} dialog.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
openDialog() {
|
||||
APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
|
||||
shortcutDescriptions: _shortcutsHelp
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a new shortcut.
|
||||
*
|
||||
@@ -106,11 +117,11 @@ const KeyboardShortcut = {
|
||||
shortcutAttr,
|
||||
exec,
|
||||
helpDescription) {
|
||||
_shortcuts[shortcutChar] = {
|
||||
_shortcuts.set(shortcutChar, {
|
||||
character: shortcutChar,
|
||||
shortcutAttr,
|
||||
function: exec
|
||||
};
|
||||
function: exec,
|
||||
shortcutAttr
|
||||
});
|
||||
|
||||
if (helpDescription) {
|
||||
this._addShortcutToHelp(shortcutChar, helpDescription);
|
||||
@@ -124,7 +135,7 @@ const KeyboardShortcut = {
|
||||
* no longer be usable
|
||||
*/
|
||||
unregisterShortcut(shortcutChar) {
|
||||
_shortcuts.remove(shortcutChar);
|
||||
_shortcuts.delete(shortcutChar);
|
||||
_shortcutsHelp.delete(shortcutChar);
|
||||
},
|
||||
|
||||
@@ -133,7 +144,12 @@ const KeyboardShortcut = {
|
||||
* @returns {string} e.key or something close if not supported
|
||||
*/
|
||||
_getKeyboardKey(e) {
|
||||
if (typeof e.key === 'string') {
|
||||
// If e.key is a string, then it is assumed it already plainly states
|
||||
// the key pressed. This may not be true in all cases, such as with Edge
|
||||
// and "?", when the browser cannot properly map a key press event to a
|
||||
// keyboard key. To be safe, when a key is "Unidentified" it must be
|
||||
// further analyzed by jitsi to a key using e.which.
|
||||
if (typeof e.key === 'string' && e.key !== 'Unidentified') {
|
||||
return e.key;
|
||||
}
|
||||
if (e.type === 'keypress'
|
||||
@@ -177,9 +193,7 @@ const KeyboardShortcut = {
|
||||
_initGlobalShortcuts() {
|
||||
this.registerShortcut('?', null, () => {
|
||||
sendAnalytics(createShortcutEvent('help'));
|
||||
APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
|
||||
shortcutDescriptions: _shortcutsHelp
|
||||
}));
|
||||
this.openDialog();
|
||||
}, 'keyboardShortcuts.toggleShortcuts');
|
||||
|
||||
// register SPACE shortcut in two steps to insure visibility of help
|
||||
|
||||
@@ -22,7 +22,6 @@ const LEGACY_INCOMING_METHODS = [
|
||||
'email',
|
||||
'toggle-audio',
|
||||
'toggle-chat',
|
||||
'toggle-contact-list',
|
||||
'toggle-film-strip',
|
||||
'toggle-share-screen',
|
||||
'toggle-video',
|
||||
|
||||
50
modules/util/JitsiMeetInMemoryLogStorage.js
Normal file
50
modules/util/JitsiMeetInMemoryLogStorage.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Implements in memory logs storage, used for testing/debugging.
|
||||
*/
|
||||
export default class JitsiMeetInMemoryLogStorage {
|
||||
|
||||
/**
|
||||
* Creates new <tt>JitsiMeetInMemoryLogStorage</tt>
|
||||
*/
|
||||
constructor() {
|
||||
/**
|
||||
* Array of the log entries to keep.
|
||||
* @type {array}
|
||||
*/
|
||||
this.logs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} <tt>true</tt> when this storage is ready or
|
||||
* <tt>false</tt> otherwise.
|
||||
*/
|
||||
isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the <tt>LogCollector</tt> to store a series of log lines into
|
||||
* batch.
|
||||
* @param {string|object[]} logEntries an array containing strings
|
||||
* representing log lines or aggregated lines objects.
|
||||
*/
|
||||
storeLogs(logEntries) {
|
||||
for (let i = 0, len = logEntries.length; i < len; i++) {
|
||||
const logEntry = logEntries[i];
|
||||
|
||||
if (typeof logEntry === 'object') {
|
||||
this.logs.push(logEntry.text);
|
||||
} else {
|
||||
// Regular message
|
||||
this.logs.push(logEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {array} the collected log entries.
|
||||
*/
|
||||
getLogs() {
|
||||
return this.logs;
|
||||
}
|
||||
}
|
||||
494
package-lock.json
generated
494
package-lock.json
generated
@@ -91,7 +91,7 @@
|
||||
"@atlaskit/layer": "2.7.2",
|
||||
"@atlaskit/spinner": "4.0.0",
|
||||
"@atlaskit/theme": "2.4.0",
|
||||
"@atlaskit/tooltip": "6.0.0",
|
||||
"@atlaskit/tooltip": "6.2.2",
|
||||
"babel-runtime": "6.26.0",
|
||||
"classnames": "2.2.5",
|
||||
"keycode": "2.1.9",
|
||||
@@ -109,6 +109,19 @@
|
||||
"styled-components": "1.4.6"
|
||||
}
|
||||
},
|
||||
"@atlaskit/tooltip": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/tooltip/-/tooltip-6.2.2.tgz",
|
||||
"integrity": "sha1-zHE3McH4BNnYAXYRGzk6c3DXUdo=",
|
||||
"requires": {
|
||||
"@atlaskit/layer": "2.7.2",
|
||||
"@atlaskit/theme": "2.4.0",
|
||||
"@atlaskit/util-shared-styles": "2.10.6",
|
||||
"babel-runtime": "6.26.0",
|
||||
"prop-types": "15.6.0",
|
||||
"styled-components": "1.4.6"
|
||||
}
|
||||
},
|
||||
"styled-components": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-1.4.6.tgz",
|
||||
@@ -409,49 +422,91 @@
|
||||
}
|
||||
},
|
||||
"@atlaskit/inline-dialog": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/inline-dialog/-/inline-dialog-5.0.2.tgz",
|
||||
"integrity": "sha1-xwPi9seo0M+nrcPXgJK0bE7ShkQ=",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/inline-dialog/-/inline-dialog-5.3.0.tgz",
|
||||
"integrity": "sha512-4bEeC5rZwtb4YO9BxW1UCJYCp/dyCVXqcygRW1BDnYVbveAI8wdym6qEi4BRvIwXCT4qgNhsVsqcxSrn0X6CKQ==",
|
||||
"requires": {
|
||||
"@atlaskit/layer": "2.7.2",
|
||||
"@atlaskit/layer": "2.9.1",
|
||||
"@atlaskit/theme": "2.4.0",
|
||||
"babel-runtime": "6.26.0",
|
||||
"prop-types": "15.6.0",
|
||||
"styled-components": "1.4.6"
|
||||
}
|
||||
},
|
||||
"@atlaskit/inline-message": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/inline-message/-/inline-message-3.0.1.tgz",
|
||||
"integrity": "sha1-VIF4Yg+CHx/nFBI+E4dQh9Upn78=",
|
||||
"requires": {
|
||||
"@atlaskit/button": "3.6.0",
|
||||
"@atlaskit/icon": "7.1.0",
|
||||
"@atlaskit/inline-dialog": "5.0.2",
|
||||
"@atlaskit/theme": "2.4.0",
|
||||
"babel-runtime": "6.26.0",
|
||||
"prop-types": "15.6.0",
|
||||
"styled-components": "1.4.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlaskit/button": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/button/-/button-3.6.0.tgz",
|
||||
"integrity": "sha1-tx5uySqBkHWAfqqbItyV59NSgqE=",
|
||||
"@atlaskit/layer": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/layer/-/layer-2.9.1.tgz",
|
||||
"integrity": "sha512-nyIVGeS2OhuGR5gIMTYUfRmCG8z/9KMgUzTpbpsB70sH6+d4KSFhfkz+KhKNIa8gvKI6zBc+3UBYSlUW1t1qmQ==",
|
||||
"requires": {
|
||||
"@atlaskit/util-shared-styles": "2.10.6",
|
||||
"styled-components": "1.4.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@atlaskit/inline-message": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/inline-message/-/inline-message-4.0.0.tgz",
|
||||
"integrity": "sha512-JhZf3Oux7kp+7RMFIGuaVblGiwWPd8EfCQ2kJXSvxYFTRo798gPFBKz4LU/NKQU3Fl2Rht6o5qmvjbdbzoxcZg==",
|
||||
"requires": {
|
||||
"@atlaskit/button": "7.0.0",
|
||||
"@atlaskit/icon": "11.0.0",
|
||||
"@atlaskit/inline-dialog": "6.0.0",
|
||||
"@atlaskit/theme": "3.0.0",
|
||||
"styled-components": "1.4.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlaskit/analytics-next": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/analytics-next/-/analytics-next-2.0.0.tgz",
|
||||
"integrity": "sha512-HHRifSrETd/hR7vj3Vqscz4374G1hhEnSehFkPvP+2BIL64TwsqlDLjwL/y1j+/5tFpSYRjfIAVBM/RcCuuzzg==",
|
||||
"requires": {
|
||||
"prop-types": "15.6.0"
|
||||
}
|
||||
},
|
||||
"@atlaskit/button": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/button/-/button-7.0.0.tgz",
|
||||
"integrity": "sha512-FXozSxBcmigSt3+ff+ca8kfEox+mP6dJsXG4i/fkYfsmZr8WXYnyPWndWvOJYs7jvqqP9FMIGSJJQeOJHI60Ow==",
|
||||
"requires": {
|
||||
"@atlaskit/analytics-next": "2.0.0",
|
||||
"@atlaskit/theme": "3.0.0",
|
||||
"babel-runtime": "6.26.0",
|
||||
"classnames": "2.2.5",
|
||||
"prop-types": "15.6.0",
|
||||
"styled-components": "1.4.6"
|
||||
}
|
||||
},
|
||||
"@atlaskit/icon": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/icon/-/icon-7.1.0.tgz",
|
||||
"integrity": "sha1-czQVCEhzUmPeShO8mPTUTU8r6N8=",
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/icon/-/icon-11.0.0.tgz",
|
||||
"integrity": "sha512-KxGxDwbuWeGZ5ivP1JFlPOhHipQQzgm0lAUGzziaYi6yOaNmfwmNjpj2xBpOhlkkymvhnFkzV5JSwVUH+0rW1w==",
|
||||
"requires": {
|
||||
"@atlaskit/theme": "3.0.0",
|
||||
"babel-runtime": "6.26.0",
|
||||
"styled-components": "1.4.6",
|
||||
"uuid": "3.1.0"
|
||||
}
|
||||
},
|
||||
"@atlaskit/inline-dialog": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/inline-dialog/-/inline-dialog-6.0.0.tgz",
|
||||
"integrity": "sha512-ITGozbuJg6UFzZHQjrWOsx17jKMFR4TE6YdTYA2UTKmKJalHvH414APuNyZdIz8XCFFnQo8NuGdkAjiBYCKG0w==",
|
||||
"requires": {
|
||||
"@atlaskit/layer": "3.1.0",
|
||||
"@atlaskit/theme": "3.0.0",
|
||||
"styled-components": "1.4.6"
|
||||
}
|
||||
},
|
||||
"@atlaskit/layer": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/layer/-/layer-3.1.0.tgz",
|
||||
"integrity": "sha512-QdOiu/c0u+YZKuC5z1hYZj/TW0axO5pL2kWBU2MLyMnThfHA+8UCMvIKtpI5/DoMevR5bEuLTX6ivos15l2ulA==",
|
||||
"requires": {
|
||||
"react-scrolllock": "2.0.2",
|
||||
"styled-components": "1.4.6"
|
||||
}
|
||||
},
|
||||
"@atlaskit/theme": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-3.0.0.tgz",
|
||||
"integrity": "sha512-j9n0x+WjpwLelPR6Mf64meuPSWsHBK7hEU6q3dLK5YerqXaocu9Z5Dcnv6E8F+zfzGvPn/9ISgO2L3ts5Gv/3g==",
|
||||
"requires": {
|
||||
"prop-types": "15.6.0",
|
||||
"styled-components": "1.4.6"
|
||||
}
|
||||
@@ -838,16 +893,50 @@
|
||||
}
|
||||
},
|
||||
"@atlaskit/tooltip": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/tooltip/-/tooltip-6.0.0.tgz",
|
||||
"integrity": "sha1-rn3xMCmvO1iZqcuKNPPIT8K7mpc=",
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/tooltip/-/tooltip-9.1.1.tgz",
|
||||
"integrity": "sha512-LGMg+OP9qRIMzr8DJtMUBAkHbCN9Vwvh30QWGu/pXktBaxSM5JbxGnUughnoE1JFYyeycJGmyZ0ibtQDnmiB7w==",
|
||||
"requires": {
|
||||
"@atlaskit/layer": "2.7.2",
|
||||
"@atlaskit/theme": "2.4.0",
|
||||
"@atlaskit/util-shared-styles": "2.10.6",
|
||||
"babel-runtime": "6.26.0",
|
||||
"prop-types": "15.6.0",
|
||||
"@atlaskit/layer-manager": "3.0.1",
|
||||
"@atlaskit/theme": "3.0.0",
|
||||
"react-deprecate": "0.1.0",
|
||||
"react-transition-group": "2.3.0",
|
||||
"styled-components": "1.4.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlaskit/layer-manager": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/layer-manager/-/layer-manager-3.0.1.tgz",
|
||||
"integrity": "sha512-UPACU1LI+mseZ+GM5HDAHVAONemCQlVMVDlGAW77jYyG4rrCwQUzjZNS8OULvCfHZyULDrvT/w9FqG6eKPTo+A==",
|
||||
"requires": {
|
||||
"focusin": "2.0.0",
|
||||
"prop-types": "15.6.0",
|
||||
"react-transition-group": "2.3.0",
|
||||
"styled-components": "1.4.6",
|
||||
"tabbable": "1.1.2"
|
||||
}
|
||||
},
|
||||
"@atlaskit/theme": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-3.0.0.tgz",
|
||||
"integrity": "sha512-j9n0x+WjpwLelPR6Mf64meuPSWsHBK7hEU6q3dLK5YerqXaocu9Z5Dcnv6E8F+zfzGvPn/9ISgO2L3ts5Gv/3g==",
|
||||
"requires": {
|
||||
"prop-types": "15.6.0",
|
||||
"styled-components": "1.4.6"
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.3.0.tgz",
|
||||
"integrity": "sha512-OU3/swEL8y233u5ajTn3FIcQQ/b3XWjLXB6e2LnM1OK5JATtsyfJvPTZ8c/dawHNqjUltcdHRSpgMtPe7v07pw==",
|
||||
"requires": {
|
||||
"chain-function": "1.0.0",
|
||||
"dom-helpers": "3.3.1",
|
||||
"loose-envify": "1.3.1",
|
||||
"prop-types": "15.6.0",
|
||||
"warning": "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@atlaskit/util-common": {
|
||||
@@ -4695,6 +4784,11 @@
|
||||
"strip-eof": "1.0.0"
|
||||
}
|
||||
},
|
||||
"exenv": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
|
||||
"integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
|
||||
},
|
||||
"exit": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
||||
@@ -6236,6 +6330,21 @@
|
||||
"globule": "1.2.0"
|
||||
}
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
|
||||
"integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
|
||||
"dev": true
|
||||
},
|
||||
"generate-object-property": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
|
||||
"integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-property": "1.0.2"
|
||||
}
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
|
||||
@@ -6785,11 +6894,6 @@
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.2.tgz",
|
||||
"integrity": "sha512-pH3vDzpczdsKHdZ9xxR3O46unSjisgVx0IImay7Zz2EdhRVbCkj+nthx9OuuWEhakx9FAO+fNVGrF0rZ2oMOvw=="
|
||||
},
|
||||
"immutable": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
|
||||
"integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
|
||||
},
|
||||
"import-local": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-0.1.1.tgz",
|
||||
@@ -7066,6 +7170,25 @@
|
||||
"is-extglob": "1.0.0"
|
||||
}
|
||||
},
|
||||
"is-my-ip-valid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz",
|
||||
"integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"is-my-json-valid": {
|
||||
"version": "2.17.2",
|
||||
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz",
|
||||
"integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"generate-function": "2.0.0",
|
||||
"generate-object-property": "1.2.0",
|
||||
"is-my-ip-valid": "1.0.0",
|
||||
"jsonpointer": "4.0.1",
|
||||
"xtend": "4.0.1"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
|
||||
@@ -7127,6 +7250,12 @@
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
|
||||
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
|
||||
},
|
||||
"is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
|
||||
"dev": true
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
|
||||
@@ -7386,6 +7515,12 @@
|
||||
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
|
||||
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
|
||||
},
|
||||
"jsonpointer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
|
||||
"integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
|
||||
"dev": true
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||
@@ -7473,7 +7608,7 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#0503ec4d3f175b154b1c6fd7037520ce0768fa58",
|
||||
"version": "github:jitsi/lib-jitsi-meet#307777207ec219df344b8fed2fd2071d2238f9f8",
|
||||
"requires": {
|
||||
"async": "0.9.0",
|
||||
"current-executing-script": "0.1.3",
|
||||
@@ -7639,6 +7774,12 @@
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.mergewith": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
|
||||
"integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.pad": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz",
|
||||
@@ -8315,7 +8456,8 @@
|
||||
"nan": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.9.2.tgz",
|
||||
"integrity": "sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw=="
|
||||
"integrity": "sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw==",
|
||||
"optional": true
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "1.4.0",
|
||||
@@ -8488,9 +8630,9 @@
|
||||
}
|
||||
},
|
||||
"node-sass": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-3.13.1.tgz",
|
||||
"integrity": "sha1-ckD7v/I5YwS0IjUn7TAgWJwAT8I=",
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.8.3.tgz",
|
||||
"integrity": "sha512-tfFWhUsCk/Y19zarDcPo5xpj+IW3qCfOjVdHtYeG6S1CKbQOh1zqylnQK6cV3z9k80yxAnFX9Y+a9+XysDhhfg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async-foreach": "0.1.3",
|
||||
@@ -8502,15 +8644,45 @@
|
||||
"in-publish": "2.0.0",
|
||||
"lodash.assign": "4.2.0",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"lodash.mergewith": "4.6.1",
|
||||
"meow": "3.7.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"nan": "2.9.2",
|
||||
"nan": "2.10.0",
|
||||
"node-gyp": "3.6.2",
|
||||
"npmlog": "4.1.2",
|
||||
"request": "2.83.0",
|
||||
"sass-graph": "2.2.4"
|
||||
"request": "2.79.0",
|
||||
"sass-graph": "2.2.4",
|
||||
"stdout-stream": "1.4.0",
|
||||
"true-case-path": "1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
|
||||
"integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
|
||||
"dev": true
|
||||
},
|
||||
"aws-sign2": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
|
||||
"integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
|
||||
"dev": true
|
||||
},
|
||||
"boom": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
|
||||
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hoek": "2.16.3"
|
||||
}
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
|
||||
"integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
|
||||
"dev": true
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
|
||||
@@ -8521,6 +8693,26 @@
|
||||
"which": "1.3.0"
|
||||
}
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
|
||||
"integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"boom": "2.10.1"
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
|
||||
"integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "0.4.0",
|
||||
"combined-stream": "1.0.6",
|
||||
"mime-types": "2.1.18"
|
||||
}
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||
@@ -8537,6 +8729,47 @@
|
||||
"wide-align": "1.1.2"
|
||||
}
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
|
||||
"integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "1.1.3",
|
||||
"commander": "2.14.1",
|
||||
"is-my-json-valid": "2.17.2",
|
||||
"pinkie-promise": "2.0.1"
|
||||
}
|
||||
},
|
||||
"hawk": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
|
||||
"integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"boom": "2.10.1",
|
||||
"cryptiles": "2.0.5",
|
||||
"hoek": "2.16.3",
|
||||
"sntp": "1.0.9"
|
||||
}
|
||||
},
|
||||
"hoek": {
|
||||
"version": "2.16.3",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
|
||||
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
|
||||
"dev": true
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
|
||||
"integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"assert-plus": "0.2.0",
|
||||
"jsprim": "1.4.1",
|
||||
"sshpk": "1.13.1"
|
||||
}
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
@@ -8546,6 +8779,12 @@
|
||||
"number-is-nan": "1.0.1"
|
||||
}
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
||||
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
|
||||
"dev": true
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||
@@ -8558,6 +8797,49 @@
|
||||
"set-blocking": "2.0.0"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.3.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz",
|
||||
"integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=",
|
||||
"dev": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.79.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
|
||||
"integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"aws-sign2": "0.6.0",
|
||||
"aws4": "1.6.0",
|
||||
"caseless": "0.11.0",
|
||||
"combined-stream": "1.0.6",
|
||||
"extend": "3.0.1",
|
||||
"forever-agent": "0.6.1",
|
||||
"form-data": "2.1.4",
|
||||
"har-validator": "2.0.6",
|
||||
"hawk": "3.1.3",
|
||||
"http-signature": "1.1.1",
|
||||
"is-typedarray": "1.0.0",
|
||||
"isstream": "0.1.2",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"mime-types": "2.1.18",
|
||||
"oauth-sign": "0.8.2",
|
||||
"qs": "6.3.2",
|
||||
"stringstream": "0.0.5",
|
||||
"tough-cookie": "2.3.3",
|
||||
"tunnel-agent": "0.4.3",
|
||||
"uuid": "3.1.0"
|
||||
}
|
||||
},
|
||||
"sntp": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
|
||||
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hoek": "2.16.3"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
@@ -8568,6 +8850,12 @@
|
||||
"is-fullwidth-code-point": "1.0.0",
|
||||
"strip-ansi": "3.0.1"
|
||||
}
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
|
||||
"integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -8644,14 +8932,6 @@
|
||||
"gauge": "1.2.7"
|
||||
}
|
||||
},
|
||||
"nuclear-js": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/nuclear-js/-/nuclear-js-1.4.0.tgz",
|
||||
"integrity": "sha1-bJwAGwZz8K6dj4sYjE2gTtaTp74=",
|
||||
"requires": {
|
||||
"immutable": "3.8.2"
|
||||
}
|
||||
},
|
||||
"num2fraction": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
|
||||
@@ -10010,6 +10290,11 @@
|
||||
"resolved": "https://registry.npmjs.org/react-deep-force-update/-/react-deep-force-update-1.1.1.tgz",
|
||||
"integrity": "sha1-vNMUeAJ7ZLMznxCJIatSC0MT3Cw="
|
||||
},
|
||||
"react-deprecate": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-deprecate/-/react-deprecate-0.1.0.tgz",
|
||||
"integrity": "sha512-9ooyaovhANHgfuOxXRgrEiEfWjEhvygeSxrRTGxNlXErnXnyHBGjxCxrKYsT/Gsc62lS9rFOBeK0c2wwdyUnvQ=="
|
||||
},
|
||||
"react-devtools-core": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-2.5.2.tgz",
|
||||
@@ -10156,9 +10441,7 @@
|
||||
"integrity": "sha512-vLNJIedXQZN4p3ChFsAgVHacnJqQMnLl+wBsnZuliRkmsjEHo8kQOA9fnLih/OoiDi1O3eHQvXC5L8f+RYiKgw=="
|
||||
},
|
||||
"react-native-calendar-events": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-calendar-events/-/react-native-calendar-events-1.4.3.tgz",
|
||||
"integrity": "sha1-KYBOi0TWlG5pq1ogkC2USe0xXEc="
|
||||
"version": "github:jitsi/react-native-calendar-events#dabfabc4bacc1424a8b93ebdda6f3820dee198cb"
|
||||
},
|
||||
"react-native-callstats": {
|
||||
"version": "3.27.0",
|
||||
@@ -10171,9 +10454,7 @@
|
||||
}
|
||||
},
|
||||
"react-native-fetch-blob": {
|
||||
"version": "0.10.8",
|
||||
"resolved": "https://registry.npmjs.org/react-native-fetch-blob/-/react-native-fetch-blob-0.10.8.tgz",
|
||||
"integrity": "sha1-T8JWq64MtfEOfEHyjBGz/zMNcqk=",
|
||||
"version": "github:flatfox-ag/react-native-fetch-blob#01f38a4537baecd3ea0cb93c27e84553f3fc5231",
|
||||
"requires": {
|
||||
"base-64": "0.1.0",
|
||||
"glob": "7.0.6"
|
||||
@@ -10221,9 +10502,9 @@
|
||||
"integrity": "sha1-QeDsKqfdjxLzo+6Dr51jxLZw+KE="
|
||||
},
|
||||
"react-native-sound": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.10.4.tgz",
|
||||
"integrity": "sha512-V9v4CjKgv8ekQRLOJSoKA7pxJ03F4Ih3T/RfMIlMWLktz7v/O4sdJPjRBLOzZRqAnr9FWTLbSk1ZCjioXh3mjQ=="
|
||||
"version": "0.10.9",
|
||||
"resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.10.9.tgz",
|
||||
"integrity": "sha1-awCw9K/QF83gn7udFx3xtdW4Uag="
|
||||
},
|
||||
"react-native-vector-icons": {
|
||||
"version": "4.4.2",
|
||||
@@ -10258,7 +10539,7 @@
|
||||
}
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "github:jitsi/react-native-webrtc#626818af40384356617f70366133317b6a475171",
|
||||
"version": "github:jitsi/react-native-webrtc#52fe4646401408e0569e972cabf08f3c21b7a107",
|
||||
"requires": {
|
||||
"base64-js": "1.2.3",
|
||||
"event-target-shim": "1.1.1",
|
||||
@@ -10294,6 +10575,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-scrolllock": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-scrolllock/-/react-scrolllock-2.0.2.tgz",
|
||||
"integrity": "sha512-Kz0cTwHnGdTEvvNmKWKhFYrwpap8jGLbdTSjzZE1X37OJW72TVDWsL4yQ9oKS/uAsHUlbrg7AZt8Z1vgcRHmFQ==",
|
||||
"requires": {
|
||||
"exenv": "1.2.2"
|
||||
}
|
||||
},
|
||||
"react-syntax-highlighter": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-3.0.2.tgz",
|
||||
@@ -11467,6 +11756,47 @@
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
},
|
||||
"stdout-stream": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz",
|
||||
"integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"readable-stream": "2.3.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "2.0.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
"string_decoder": "1.1.1",
|
||||
"util-deprecate": "1.0.2"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"stream-browserify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||
@@ -11947,6 +12277,30 @@
|
||||
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
|
||||
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM="
|
||||
},
|
||||
"true-case-path": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz",
|
||||
"integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "6.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
||||
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inflight": "1.0.6",
|
||||
"inherits": "2.0.3",
|
||||
"minimatch": "3.0.4",
|
||||
"once": "1.4.0",
|
||||
"path-is-absolute": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tsscmp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz",
|
||||
|
||||
19
package.json
19
package.json
@@ -23,8 +23,8 @@
|
||||
"@atlaskit/field-text-area": "1.2.0",
|
||||
"@atlaskit/flag": "6.1.0",
|
||||
"@atlaskit/icon": "10.0.0",
|
||||
"@atlaskit/inline-dialog": "5.0.2",
|
||||
"@atlaskit/inline-message": "3.0.1",
|
||||
"@atlaskit/inline-dialog": "5.3.0",
|
||||
"@atlaskit/inline-message": "4.0.0",
|
||||
"@atlaskit/layer-manager": "2.8.0",
|
||||
"@atlaskit/lozenge": "3.4.2",
|
||||
"@atlaskit/modal-dialog": "3.4.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
"@atlaskit/spinner": "4.0.0",
|
||||
"@atlaskit/tabs": "4.0.1",
|
||||
"@atlaskit/theme": "2.4.0",
|
||||
"@atlaskit/tooltip": "6.0.0",
|
||||
"@atlaskit/tooltip": "9.1.1",
|
||||
"autosize": "1.18.13",
|
||||
"es6-iterator": "2.0.3",
|
||||
"es6-symbol": "3.1.1",
|
||||
@@ -46,10 +46,9 @@
|
||||
"jquery-i18next": "1.2.0",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0503ec4d3f175b154b1c6fd7037520ce0768fa58",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#307777207ec219df344b8fed2fd2071d2238f9f8",
|
||||
"lodash": "4.17.4",
|
||||
"moment": "2.19.4",
|
||||
"nuclear-js": "1.4.0",
|
||||
"postis": "2.2.0",
|
||||
"prop-types": "15.6.0",
|
||||
"react": "16.2.0",
|
||||
@@ -57,17 +56,17 @@
|
||||
"react-i18next": "4.8.0",
|
||||
"react-native": "0.51.0",
|
||||
"react-native-background-timer": "2.0.0",
|
||||
"react-native-calendar-events": "1.4.3",
|
||||
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#dabfabc4bacc1424a8b93ebdda6f3820dee198cb",
|
||||
"react-native-callstats": "3.27.0",
|
||||
"react-native-fetch-blob": "0.10.8",
|
||||
"react-native-fetch-blob": "github:flatfox-ag/react-native-fetch-blob#exception_fixes",
|
||||
"react-native-img-cache": "1.5.2",
|
||||
"react-native-immersive": "1.1.0",
|
||||
"react-native-keep-awake": "2.0.6",
|
||||
"react-native-locale-detector": "github:jitsi/react-native-locale-detector#cc76092fc4335488a28a9529c8b50afae2c3ecdc",
|
||||
"react-native-prompt": "1.0.0",
|
||||
"react-native-sound": "0.10.4",
|
||||
"react-native-sound": "0.10.9",
|
||||
"react-native-vector-icons": "4.4.2",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#626818af40384356617f70366133317b6a475171",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#52fe4646401408e0569e972cabf08f3c21b7a107",
|
||||
"react-redux": "5.0.6",
|
||||
"redux": "3.7.2",
|
||||
"redux-thunk": "2.2.0",
|
||||
@@ -99,7 +98,7 @@
|
||||
"file-loader": "1.1.5",
|
||||
"flow-bin": "0.57.3",
|
||||
"imports-loader": "0.7.1",
|
||||
"node-sass": "3.13.1",
|
||||
"node-sass": "4.8.3",
|
||||
"precommit-hook": "3.0.0",
|
||||
"string-replace-loader": "1.3.0",
|
||||
"style-loader": "0.19.0",
|
||||
|
||||
@@ -2,54 +2,10 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import StatelessToolbar from '../toolbox/components/StatelessToolbar';
|
||||
import StatelessToolbarButton
|
||||
from '../toolbox/components/StatelessToolbarButton';
|
||||
import ToolboxAlwaysOnTop from './ToolboxAlwaysOnTop';
|
||||
|
||||
const { api } = window.alwaysOnTop;
|
||||
|
||||
/**
|
||||
* Map with toolbar button descriptors.
|
||||
*/
|
||||
const TOOLBAR_BUTTONS = {
|
||||
/**
|
||||
* The descriptor of the camera toolbar button.
|
||||
*/
|
||||
camera: {
|
||||
classNames: [ 'button', 'icon-camera' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_camera',
|
||||
onClick() {
|
||||
api.executeCommand('toggleVideo');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The descriptor of the toolbar button which hangs up the call/conference.
|
||||
*/
|
||||
hangup: {
|
||||
classNames: [ 'button', 'icon-hangup', 'button_hangup' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_hangup',
|
||||
onClick() {
|
||||
api.executeCommand('hangup');
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The descriptor of the microphone toolbar button.
|
||||
*/
|
||||
microphone: {
|
||||
classNames: [ 'button', 'icon-microphone' ],
|
||||
enabled: true,
|
||||
id: 'toolbar_button_mute',
|
||||
onClick() {
|
||||
api.executeCommand('toggleAudio');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The timeout in ms for hidding the toolbar.
|
||||
*/
|
||||
@@ -385,62 +341,16 @@ export default class AlwaysOnTop extends Component<*, State> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const className
|
||||
= `toolbar_primary always-on-top ${
|
||||
this.state.visible ? 'fadeIn' : 'fadeOut'}`;
|
||||
|
||||
return (
|
||||
<div id = 'alwaysOnTop'>
|
||||
<StatelessToolbar
|
||||
className = { className }
|
||||
<ToolboxAlwaysOnTop
|
||||
audioAvailable = { this.state.audioAvailable }
|
||||
audioMuted = { this.state.audioMuted }
|
||||
className = { this.state.visible ? 'fadeIn' : 'fadeOut' }
|
||||
onMouseOut = { this._onMouseOut }
|
||||
onMouseOver = { this._onMouseOver }>
|
||||
{
|
||||
Object.entries(TOOLBAR_BUTTONS).map(
|
||||
([ key, button ]) => {
|
||||
// XXX The following silences a couple of flow
|
||||
// errors:
|
||||
if (button === null
|
||||
|| typeof button !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { onClick } = button;
|
||||
let enabled = false;
|
||||
let toggled = false;
|
||||
|
||||
switch (key) {
|
||||
case 'microphone':
|
||||
enabled = this.state.audioAvailable;
|
||||
toggled = enabled
|
||||
? this.state.audioMuted : true;
|
||||
break;
|
||||
case 'camera':
|
||||
enabled = this.state.videoAvailable;
|
||||
toggled = enabled
|
||||
? this.state.videoMuted : true;
|
||||
break;
|
||||
default: // hangup button
|
||||
toggled = false;
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
const updatedButton = {
|
||||
...button,
|
||||
enabled,
|
||||
toggled
|
||||
};
|
||||
|
||||
return (
|
||||
<StatelessToolbarButton
|
||||
button = { updatedButton }
|
||||
key = { key }
|
||||
onClick = { onClick } />
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</StatelessToolbar>
|
||||
onMouseOver = { this._onMouseOver }
|
||||
videoAvailable = { this.state.videoAvailable }
|
||||
videoMuted = { this.state.videoMuted } />
|
||||
{
|
||||
this._renderVideoNotAvailableScreen()
|
||||
}
|
||||
|
||||
159
react/features/always-on-top/ToolboxAlwaysOnTop.js
Normal file
159
react/features/always-on-top/ToolboxAlwaysOnTop.js
Normal file
@@ -0,0 +1,159 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
// FIXME: AlwaysOnTop imports the button directly in order to avoid bringing in
|
||||
// other components that use lib-jitsi-meet, which always on top does not
|
||||
// import.
|
||||
import ToolbarButton from '../toolbox/components/ToolbarButton';
|
||||
|
||||
const { api } = window.alwaysOnTop;
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ToolboxAlwaysOnTop}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether or not microphone access is available.
|
||||
*/
|
||||
audioAvailable: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the user is currently audio muted.
|
||||
*/
|
||||
audioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Additional CSS class names to add to the root of the toolbar.
|
||||
*/
|
||||
className: string,
|
||||
|
||||
/**
|
||||
* Callback invoked when no longer moused over the toolbar.
|
||||
*/
|
||||
onMouseOut: Function,
|
||||
|
||||
/**
|
||||
* Callback invoked when the mouse has moved over the toolbar.
|
||||
*/
|
||||
onMouseOver: Function,
|
||||
|
||||
/**
|
||||
* Whether or not camera access is available.
|
||||
*/
|
||||
videoAvailable: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the user is currently video muted.
|
||||
*/
|
||||
videoMuted: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the toolbar in the Always On Top window.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class ToolboxAlwaysOnTop extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code ToolboxAlwaysOnTop} instance.
|
||||
*
|
||||
* @param {Props} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onToolbarHangup = this._onToolbarHangup.bind(this);
|
||||
this._onToolbarToggleAudio = this._onToolbarToggleAudio.bind(this);
|
||||
this._onToolbarToggleVideo = this._onToolbarToggleVideo.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
audioAvailable,
|
||||
audioMuted,
|
||||
className = '',
|
||||
onMouseOut,
|
||||
onMouseOver,
|
||||
videoAvailable,
|
||||
videoMuted
|
||||
} = this.props;
|
||||
|
||||
const videoMuteIcon = `${videoMuted || !videoAvailable
|
||||
? 'icon-camera-disabled toggled' : 'icon-camera'} ${
|
||||
videoAvailable ? '' : 'disabled'}`;
|
||||
const audioMuteIcon = `${audioMuted || !audioAvailable
|
||||
? 'icon-mic-disabled toggled' : 'icon-microphone'} ${
|
||||
audioAvailable ? '' : 'disabled'}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { `always-on-top-toolbox ${className}` }
|
||||
onMouseOut = { onMouseOut }
|
||||
onMouseOver = { onMouseOver }>
|
||||
<ToolbarButton
|
||||
accessibilityLabel = 'Video mute'
|
||||
iconName = { videoMuteIcon }
|
||||
onClick = { this._onToolbarToggleVideo } />
|
||||
<ToolbarButton
|
||||
accessibilityLabel = 'Hangup'
|
||||
iconName = 'icon-hangup'
|
||||
onClick = { this._onToolbarHangup } />
|
||||
<ToolbarButton
|
||||
accessibilityLabel = 'Audio mute'
|
||||
iconName = { audioMuteIcon }
|
||||
onClick = { this._onToolbarToggleAudio } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_onToolbarHangup: () => void;
|
||||
|
||||
/**
|
||||
* Ends the conference call and closes the always on top window.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarHangup() {
|
||||
api.executeCommand('hangup');
|
||||
window.close();
|
||||
}
|
||||
|
||||
_onToolbarToggleAudio: () => void;
|
||||
|
||||
/**
|
||||
* Toggles audio mute if audio is avaiable.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarToggleAudio() {
|
||||
if (this.props.audioAvailable) {
|
||||
api.executeCommand('toggleAudio');
|
||||
}
|
||||
}
|
||||
|
||||
_onToolbarToggleVideo: () => void;
|
||||
|
||||
/**
|
||||
* Toggles video mute if video is avaiable.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarToggleVideo() {
|
||||
if (this.props.videoAvailable) {
|
||||
api.executeCommand('toggleVideo');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,25 @@ export function createAudioOnlyChangedEvent(enabled) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event for an action on the deep linking page.
|
||||
*
|
||||
* @param {string} action - The action that the event represents.
|
||||
* @param {string} actionSubject - The subject that was acted upon.
|
||||
* @param {boolean} attributes - Additional attributes to attach to the event.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createDeepLinkingPageEvent(
|
||||
action, actionSubject, attributes = {}) {
|
||||
return {
|
||||
action,
|
||||
actionSubject,
|
||||
source: 'deepLinkingPage',
|
||||
attributes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates that a device was changed.
|
||||
*
|
||||
@@ -129,16 +148,21 @@ export function createFeedbackOpenEvent() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates that the invite dialog was closed. This is
|
||||
* not a TYPE_UI event, since it is not necessarily the result of a user
|
||||
* interaction.
|
||||
* Creates an event for an action regarding the AddPeopleDialog (invites).
|
||||
*
|
||||
* @param {string} action - The action that the event represents.
|
||||
* @param {string} actionSubject - The subject that was acted upon.
|
||||
* @param {boolean} attributes - Additional attributes to attach to the event.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createInviteDialogClosedEvent() {
|
||||
export function createInviteDialogEvent(
|
||||
action, actionSubject, attributes = {}) {
|
||||
return {
|
||||
action: 'invite.dialog.closed'
|
||||
action,
|
||||
actionSubject,
|
||||
attributes,
|
||||
source: 'inviteDialog'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -124,10 +124,8 @@ function _appNavigateToMandatoryLocation(
|
||||
});
|
||||
}
|
||||
|
||||
const profile = getState()['features/base/profile'];
|
||||
|
||||
return promise.then(() =>
|
||||
dispatch(setConfig(_mergeConfigWithProfile(config, profile))));
|
||||
dispatch(setConfig(config)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,23 +288,3 @@ function _loadConfig({ contextRoot, host, protocol, room }) {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the downloaded config with the current profile values. The profile
|
||||
* values are named the same way as the config values in the config.js so
|
||||
* a clean merge is possible.
|
||||
*
|
||||
* @param {Object|undefined} config - The downloaded config.
|
||||
* @param {Object} profile - The persisted profile.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mergeConfigWithProfile(config, profile) {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
...config,
|
||||
...profile
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ export class App extends AbstractApp {
|
||||
static propTypes = {
|
||||
...AbstractApp.propTypes,
|
||||
|
||||
addPeopleEnabled: PropTypes.bool,
|
||||
|
||||
dialOutEnabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled. If {@code true}, a toolbar
|
||||
* button is rendered in the {@link Conference} view to afford entering
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* @flow */
|
||||
// @flow
|
||||
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
export * from './getRouteToRender';
|
||||
@@ -11,3 +12,13 @@ export * from './getRouteToRender';
|
||||
export function getName() {
|
||||
return NativeModules.AppInfo.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the Jitsi Meet SDK bundle on iOS. On Android it will be
|
||||
* undefined.
|
||||
*
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
export function getSdkBundlePath() {
|
||||
return NativeModules.AppInfo.sdkBundlePath;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
/* @flow */
|
||||
|
||||
import { Platform } from '../base/react';
|
||||
import { toState } from '../base/redux';
|
||||
import { getDeepLinkingPage } from '../deep-linking';
|
||||
import {
|
||||
NoMobileApp,
|
||||
PluginRequiredBrowser,
|
||||
UnsupportedDesktopBrowser,
|
||||
UnsupportedMobileBrowser
|
||||
UnsupportedDesktopBrowser
|
||||
} from '../unsupported-browser';
|
||||
|
||||
import {
|
||||
@@ -24,43 +22,18 @@ declare var loggingConfig: Object;
|
||||
*
|
||||
* @private
|
||||
* @param {Object} state - Object containing current redux state.
|
||||
* @returns {ReactElement|void}
|
||||
* @returns {Promise<ReactElement>|void}
|
||||
* @type {Function[]}
|
||||
*/
|
||||
const _INTERCEPT_COMPONENT_RULES = [
|
||||
|
||||
/**
|
||||
* This rule describes case when user opens application using mobile
|
||||
* browser. In order to promote the app, we choose to suggest the mobile
|
||||
* app even if the browser supports the app (e.g. Google Chrome with
|
||||
* WebRTC support on Android).
|
||||
*
|
||||
* @param {Object} state - The redux state of the app.
|
||||
* @returns {UnsupportedMobileBrowser|void} If the rule is satisfied then
|
||||
* we should intercept existing component by UnsupportedMobileBrowser.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
state => {
|
||||
const OS = Platform.OS;
|
||||
|
||||
if (OS === 'android' || OS === 'ios') {
|
||||
const mobileAppPromo
|
||||
= typeof interfaceConfig === 'object'
|
||||
&& interfaceConfig.MOBILE_APP_PROMO;
|
||||
|
||||
return (
|
||||
typeof mobileAppPromo === 'undefined' || Boolean(mobileAppPromo)
|
||||
? UnsupportedMobileBrowser
|
||||
: NoMobileApp);
|
||||
}
|
||||
},
|
||||
getDeepLinkingPage,
|
||||
state => {
|
||||
const { webRTCReady } = state['features/base/lib-jitsi-meet'];
|
||||
|
||||
switch (typeof webRTCReady) {
|
||||
case 'boolean':
|
||||
if (webRTCReady === false) {
|
||||
return UnsupportedDesktopBrowser;
|
||||
return Promise.resolve(UnsupportedDesktopBrowser);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -69,8 +42,10 @@ const _INTERCEPT_COMPONENT_RULES = [
|
||||
break;
|
||||
|
||||
default:
|
||||
return PluginRequiredBrowser;
|
||||
return Promise.resolve(PluginRequiredBrowser);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
];
|
||||
|
||||
@@ -80,16 +55,19 @@ const _INTERCEPT_COMPONENT_RULES = [
|
||||
*
|
||||
* @param {(Object|Function)} stateOrGetState - The redux state or
|
||||
* {@link getState} function.
|
||||
* @returns {Route}
|
||||
* @returns {Promise<Route>}
|
||||
*/
|
||||
export function _getRouteToRender(stateOrGetState: Object | Function) {
|
||||
export function _getRouteToRender(stateOrGetState: Object | Function): Object {
|
||||
const route = _super_getRouteToRender(stateOrGetState);
|
||||
|
||||
// Intercepts route components if any of component interceptor rules is
|
||||
// satisfied.
|
||||
route.component = _interceptComponent(stateOrGetState, route.component);
|
||||
return _interceptComponent(stateOrGetState, route.component).then(
|
||||
(component: React$Element<*>) => {
|
||||
route.component = component;
|
||||
|
||||
return route;
|
||||
return route;
|
||||
}, () => Promise.resolve(route));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,23 +77,24 @@ export function _getRouteToRender(stateOrGetState: Object | Function) {
|
||||
* {@link getState} function.
|
||||
* @param {ReactElement} component - Current route component to render.
|
||||
* @private
|
||||
* @returns {ReactElement} If any of the pre-defined rules is satisfied, returns
|
||||
* intercepted component.
|
||||
* @returns {Promise<ReactElement>} If any of the pre-defined rules is
|
||||
* satisfied, returns intercepted component.
|
||||
*/
|
||||
function _interceptComponent(
|
||||
stateOrGetState: Object | Function,
|
||||
component: React$Element<*>) {
|
||||
let result;
|
||||
const state = toState(stateOrGetState);
|
||||
|
||||
for (const rule of _INTERCEPT_COMPONENT_RULES) {
|
||||
result = rule(state);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const promises = [];
|
||||
|
||||
return result || component;
|
||||
_INTERCEPT_COMPONENT_RULES.forEach(rule => {
|
||||
promises.push(rule(state));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(
|
||||
results =>
|
||||
results.find(result => typeof result !== 'undefined') || component,
|
||||
() => Promise.resolve(component));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,7 +79,18 @@ function _navigate({ getState }) {
|
||||
const { app } = state['features/app'];
|
||||
const routeToRender = _getRouteToRender(state);
|
||||
|
||||
return app._navigate(routeToRender);
|
||||
// XXX Web changed _getRouteToRender to return Promsie instead of Route.
|
||||
// Unfortunately, the commit left mobile to return Route.
|
||||
let routeToRenderPromise;
|
||||
|
||||
if (routeToRender && typeof routeToRender.then === 'function') {
|
||||
routeToRenderPromise = routeToRender;
|
||||
}
|
||||
if (!routeToRenderPromise) {
|
||||
routeToRenderPromise = Promise.resolve(routeToRender);
|
||||
}
|
||||
|
||||
routeToRenderPromise.then(app._navigate.bind(app));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,6 +62,17 @@ export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
|
||||
*/
|
||||
export const DATA_CHANNEL_OPENED = Symbol('DATA_CHANNEL_OPENED');
|
||||
|
||||
/**
|
||||
* The type of action which signals that the user has been kicked out from
|
||||
* the conference.
|
||||
*
|
||||
* {
|
||||
* type: KICKED_OUT,
|
||||
* conference: JitsiConference
|
||||
* }
|
||||
*/
|
||||
export const KICKED_OUT = Symbol('KICKED_OUT');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the lock state of a specific
|
||||
* {@code JitsiConference} changed.
|
||||
@@ -96,6 +107,18 @@ export const P2P_STATUS_CHANGED = Symbol('P2P_STATUS_CHANGED');
|
||||
*/
|
||||
export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the desktop sharing enabled flag for
|
||||
* the current conference.
|
||||
*
|
||||
* {
|
||||
* type: SET_DESKTOP_SHARING_ENABLED,
|
||||
* desktopSharingEnabled: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_DESKTOP_SHARING_ENABLED
|
||||
= Symbol('SET_DESKTOP_SHARING_ENABLED');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which updates the current known status of the
|
||||
* Follow Me feature.
|
||||
|
||||
@@ -28,9 +28,11 @@ import {
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
DATA_CHANNEL_OPENED,
|
||||
KICKED_OUT,
|
||||
LOCK_STATE_CHANGED,
|
||||
P2P_STATUS_CHANGED,
|
||||
SET_AUDIO_ONLY,
|
||||
SET_DESKTOP_SHARING_ENABLED,
|
||||
SET_FOLLOW_ME,
|
||||
SET_LASTN,
|
||||
SET_PASSWORD,
|
||||
@@ -77,6 +79,10 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
JitsiConferenceEvents.CONFERENCE_LEFT,
|
||||
(...args) => dispatch(conferenceLeft(conference, ...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.KICKED,
|
||||
() => dispatch(kickedOut(conference)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.LOCK_STATE_CHANGED,
|
||||
(...args) => dispatch(lockStateChanged(conference, ...args)));
|
||||
@@ -357,6 +363,23 @@ export function dataChannelOpened() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that we've been kicked out of the conference.
|
||||
*
|
||||
* @param {JitsiConference} conference - The {@link JitsiConference} instance
|
||||
* for which the event is being signaled.
|
||||
* @returns {{
|
||||
* type: KICKED_OUT,
|
||||
* conference: JitsiConference
|
||||
* }}
|
||||
*/
|
||||
export function kickedOut(conference: Object) {
|
||||
return {
|
||||
type: KICKED_OUT,
|
||||
conference
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the lock state of a specific JitsiConference changed.
|
||||
*
|
||||
@@ -433,6 +456,22 @@ export function setAudioOnly(audioOnly: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag for indicating if desktop sharing is enabled.
|
||||
*
|
||||
* @param {boolean} desktopSharingEnabled - True if desktop sharing is enabled.
|
||||
* @returns {{
|
||||
* type: SET_DESKTOP_SHARING_ENABLED,
|
||||
* desktopSharingEnabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setDesktopSharingEnabled(desktopSharingEnabled: boolean) {
|
||||
return {
|
||||
type: SET_DESKTOP_SHARING_ENABLED,
|
||||
desktopSharingEnabled
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the Follow Me feature.
|
||||
*
|
||||
|
||||
@@ -22,14 +22,11 @@ import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
|
||||
import {
|
||||
conferenceLeft,
|
||||
createConference,
|
||||
setAudioOnly,
|
||||
setLastN,
|
||||
toggleAudioOnly
|
||||
} from './actions';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
DATA_CHANNEL_OPENED,
|
||||
SET_AUDIO_ONLY,
|
||||
SET_LASTN,
|
||||
@@ -58,10 +55,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case CONNECTION_ESTABLISHED:
|
||||
return _connectionEstablished(store, next, action);
|
||||
|
||||
case CONFERENCE_FAILED:
|
||||
case CONFERENCE_LEFT:
|
||||
return _conferenceFailedOrLeft(store, next, action);
|
||||
|
||||
case CONFERENCE_JOINED:
|
||||
return _conferenceJoined(store, next, action);
|
||||
|
||||
@@ -116,43 +109,6 @@ function _connectionEstablished({ dispatch }, next, action) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does extra sync up on properties that may need to be updated after the
|
||||
* conference failed or was left.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified action is being
|
||||
* dispatched.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The redux action {@link CONFERENCE_FAILED} or
|
||||
* {@link CONFERENCE_LEFT} which is being dispatched in the specified store.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _conferenceFailedOrLeft({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const state = getState();
|
||||
const { audioOnly } = state['features/base/conference'];
|
||||
const { startAudioOnly } = state['features/base/profile'];
|
||||
|
||||
// FIXME: Consider implementing a standalone audio-only feature that handles
|
||||
// all these state changes.
|
||||
if (audioOnly) {
|
||||
if (!startAudioOnly) {
|
||||
sendAnalytics(createAudioOnlyChangedEvent(false));
|
||||
logger.log('Audio only disabled');
|
||||
dispatch(setAudioOnly(false));
|
||||
}
|
||||
} else if (startAudioOnly) {
|
||||
sendAnalytics(createAudioOnlyChangedEvent(true));
|
||||
logger.log('Audio only enabled');
|
||||
dispatch(setAudioOnly(true));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does extra sync up on properties that may need to be updated after the
|
||||
* conference was joined.
|
||||
@@ -262,25 +218,34 @@ function _pinParticipant({ getState }, next, action) {
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _setAudioOnly({ dispatch, getState }, next, action) {
|
||||
const { audioOnly: oldValue } = getState()['features/base/conference'];
|
||||
const result = next(action);
|
||||
const { audioOnly: newValue } = getState()['features/base/conference'];
|
||||
|
||||
const { audioOnly } = getState()['features/base/conference'];
|
||||
// Send analytics. We could've done it in the action creator setAudioOnly.
|
||||
// I don't know why it has to happen as early as possible but the analytics
|
||||
// were originally sent before the SET_AUDIO_ONLY action was even dispatched
|
||||
// in the redux store so I'm now sending the analytics as early as possible.
|
||||
if (oldValue !== newValue) {
|
||||
sendAnalytics(createAudioOnlyChangedEvent(newValue));
|
||||
logger.log(`Audio-only ${newValue ? 'enabled' : 'disabled'}`);
|
||||
}
|
||||
|
||||
// Set lastN to 0 in case audio-only is desired; leave it as undefined,
|
||||
// otherwise, and the default lastN value will be chosen automatically.
|
||||
dispatch(setLastN(audioOnly ? 0 : undefined));
|
||||
dispatch(setLastN(newValue ? 0 : undefined));
|
||||
|
||||
// Mute/unmute the local video.
|
||||
dispatch(
|
||||
setVideoMuted(
|
||||
audioOnly,
|
||||
newValue,
|
||||
VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY,
|
||||
/* ensureTrack */ true));
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
// TODO This should be a temporary solution that lasts only until
|
||||
// video tracks and all ui is moved into react/redux on the web.
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly);
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_AUDIO_ONLY, newValue);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
LOCK_STATE_CHANGED,
|
||||
P2P_STATUS_CHANGED,
|
||||
SET_AUDIO_ONLY,
|
||||
SET_DESKTOP_SHARING_ENABLED,
|
||||
SET_FOLLOW_ME,
|
||||
SET_PASSWORD,
|
||||
SET_RECEIVE_VIDEO_QUALITY,
|
||||
@@ -57,6 +58,9 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
|
||||
case SET_AUDIO_ONLY:
|
||||
return _setAudioOnly(state, action);
|
||||
|
||||
case SET_DESKTOP_SHARING_ENABLED:
|
||||
return _setDesktopSharingEnabled(state, action);
|
||||
|
||||
case SET_FOLLOW_ME:
|
||||
return {
|
||||
...state,
|
||||
@@ -329,6 +333,21 @@ function _setAudioOnly(state, action) {
|
||||
return set(state, 'audioOnly', action.audioOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action SET_DESKTOP_SHARING_ENABLED of the feature
|
||||
* base/conference.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/conference.
|
||||
* @param {Action} action - The Redux action SET_DESKTOP_SHARING_ENABLED to
|
||||
* reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setDesktopSharingEnabled(state, action) {
|
||||
return set(state, 'desktopSharingEnabled', action.desktopSharingEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
|
||||
*
|
||||
|
||||
@@ -20,7 +20,6 @@ const WHITELISTED_KEYS = [
|
||||
'_peerConnStatusRtcMuteTimeout',
|
||||
'abTesting',
|
||||
'alwaysVisibleToolbar',
|
||||
'autoEnableDesktopSharing',
|
||||
'autoRecord',
|
||||
'autoRecordToken',
|
||||
'avgRtpStatsN',
|
||||
@@ -57,20 +56,24 @@ const WHITELISTED_KEYS = [
|
||||
'enableLipSync',
|
||||
'enableLocalVideoFlip',
|
||||
'enableRecording',
|
||||
'enableRemb',
|
||||
'enableStatsID',
|
||||
'enableTalkWhileMuted',
|
||||
'enableTcc',
|
||||
'enableUserRolesBasedOnToken',
|
||||
'etherpad_base',
|
||||
'failICE',
|
||||
'firefox_fake_device',
|
||||
'forceJVB121Ratio',
|
||||
'gatherStats',
|
||||
'googleApiApplicationClientID',
|
||||
'hiddenDomain',
|
||||
'hosts',
|
||||
'iAmRecorder',
|
||||
'iAmSipGateway',
|
||||
'iceTransportPolicy',
|
||||
'ignoreStartMuted',
|
||||
'minParticipants',
|
||||
'nick',
|
||||
'openBridgeChannel',
|
||||
'p2p',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* @flow */
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
@@ -30,34 +30,38 @@ export function connect() {
|
||||
|
||||
// XXX Lib-jitsi-meet does not accept uppercase letters.
|
||||
const room = state['features/base/conference'].room.toLowerCase();
|
||||
const { initPromise } = state['features/base/lib-jitsi-meet'];
|
||||
|
||||
// XXX For web based version we use conference initialization logic
|
||||
// from the old app (at the moment of writing).
|
||||
return APP.conference.init({ roomName: room })
|
||||
.catch(error => {
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
logger.error(error);
|
||||
return initPromise.then(() => APP.conference.init({
|
||||
roomName: room
|
||||
})).catch(error => {
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
logger.error(error);
|
||||
|
||||
// TODO The following are in fact Errors raised by
|
||||
// JitsiMeetJS.init() which should be taken care of in
|
||||
// features/base/lib-jitsi-meet but we are not there yet on the
|
||||
// Web at the time of this writing.
|
||||
switch (error.name) {
|
||||
case WEBRTC_NOT_READY:
|
||||
case WEBRTC_NOT_SUPPORTED:
|
||||
dispatch(libInitError(error));
|
||||
}
|
||||
});
|
||||
// TODO The following are in fact Errors raised by
|
||||
// JitsiMeetJS.init() which should be taken care of in
|
||||
// features/base/lib-jitsi-meet but we are not there yet on the
|
||||
// Web at the time of this writing.
|
||||
switch (error.name) {
|
||||
case WEBRTC_NOT_READY:
|
||||
case WEBRTC_NOT_SUPPORTED:
|
||||
dispatch(libInitError(error));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection.
|
||||
*
|
||||
* @param {boolean} [requestFeedback] - Whether or not to attempt showing a
|
||||
* request for call feedback.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function disconnect() {
|
||||
export function disconnect(requestFeedback: boolean = false) {
|
||||
// XXX For web based version we use conference hanging up logic from the old
|
||||
// app.
|
||||
return () => APP.conference.hangup();
|
||||
return () => APP.conference.hangup(requestFeedback);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,24 @@ export function getURLWithoutParams(url: URL): URL {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URL string without hash and query/search params from a specific
|
||||
* {@code URL}.
|
||||
*
|
||||
* @param {URL} url - The {@code URL} which may have hash and query/search
|
||||
* params.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getURLWithoutParamsNormalized(url: URL): string {
|
||||
const urlWithoutParams = getURLWithoutParams(url).href;
|
||||
|
||||
if (urlWithoutParams) {
|
||||
return urlWithoutParams.toLowerCase();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a specific id to jid if it's not jid yet.
|
||||
*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user