Compare commits

..

1 Commits

Author SHA1 Message Date
hristoterezov
9fa1b210f5 feat(loadScript): Use the function from js-utils 2018-03-22 13:15:14 -05:00
155 changed files with 1398 additions and 6738 deletions

View File

@@ -43,8 +43,7 @@ import {
lockStateChanged,
onStartMutedPolicyChanged,
p2pStatusChanged,
sendLocalParticipant,
setDesktopSharingEnabled
sendLocalParticipant
} from './react/features/base/conference';
import { updateDeviceList } from './react/features/base/devices';
import {
@@ -105,7 +104,6 @@ import {
mediaPermissionPromptVisibilityChanged,
suspendDetected
} from './react/features/overlay';
import { setSharedVideoStatus } from './react/features/shared-video';
import {
isButtonEnabled,
showDesktopSharingButton
@@ -236,14 +234,13 @@ function maybeRedirectToWelcomePage(options) {
}));
}
// 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 Welcome page is enabled redirect to welcome page after 3 sec.
if (config.enableWelcomePage) {
setTimeout(
() => {
APP.store.dispatch(redirectWithStoredParams('/'));
},
options.showThankYou ? 3000 : 500);
3000);
}
}
@@ -508,6 +505,16 @@ 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
@@ -766,8 +773,6 @@ export default {
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
this.isDesktopSharingEnabled);
APP.store.dispatch(
setDesktopSharingEnabled(this.isDesktopSharingEnabled));
APP.store.dispatch(showDesktopSharingButton());
this._createRoom(tracks);
@@ -1891,6 +1896,19 @@ 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);
});
if (!interfaceConfig.filmStripOnly) {
@@ -2004,10 +2022,7 @@ export default {
(participant, name, oldValue, newValue) => {
switch (name) {
case 'raisedHand':
APP.store.dispatch(participantUpdated({
id: participant.getId(),
raisedHand: newValue === 'true'
}));
APP.UI.setRaisedHandStatus(participant, newValue);
break;
case 'remoteControlSessionStatus':
APP.UI.setRemoteControlActiveStatus(
@@ -2346,8 +2361,6 @@ export default {
}
});
}
APP.store.dispatch(setSharedVideoStatus(state));
});
room.addCommandListener(
this.commands.defaults.SHARED_VIDEO,
@@ -2610,6 +2623,30 @@ 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

View File

@@ -178,23 +178,6 @@ 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,

View File

@@ -62,36 +62,6 @@
.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;

View File

@@ -5,20 +5,6 @@
justify-content: flex-start;
}
.use-new-toolbox {
.filmstrip.reduce-height {
bottom: $newToolbarSize;
}
.filmstrip {
transition: bottom .3s;
}
.filmstrip__videos.hidden {
bottom: calc(-196px - #{$newToolbarSize});
}
}
.filmstrip {
position: absolute;
bottom: 0;

View File

@@ -180,6 +180,3 @@
.icon-gsm-bars:before {
content: "\e926";
}
.icon-open_in_new:before {
content: "\e89e";
}

View File

@@ -1,37 +1,6 @@
/**
* 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, plus its padding, and subtract from 100%.
*/
height: calc(100% - #{$newToolbarSize} - 10px);
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%;

View File

@@ -19,17 +19,6 @@
vertical-align: middle;
}
.use-new-toolbox {
.cxGWJB{
bottom: calc(#{$newToolbarSize} + 15px);
}
.gXSEsl:nth-child(n+2) {
transform: translateX(0) translateY(100%) translateY(calc(#{$newToolbarSize} + 25px));
-ms-transform: translateX(0) translateY(100%) translateY(calc(#{$newToolbarSize} + 25px));
-webkit-transform: translateX(0) translateY(100%) translateY(calc(#{$newToolbarSize} + 25px));
}
}
.toolbar-container {
display: block;
left:0;
@@ -272,217 +261,6 @@
}
}
/**
* TODO: when the old filmstrip has been removed, remove the "new-" prefix.
*/
.new-toolbox {
background-color: $newToolbarBackgroundColor;
bottom: calc((#{$newToolbarSize} * 2) * -1);
box-sizing: border-box;
display: flex;
justify-content: space-between;
padding: 5px 20px;
position: absolute;
transition: bottom .3s ease-in;
width: 100%;
z-index: $toolbarZ;
&.visible {
bottom: 0;
}
&.no-buttons {
display: none;
}
.button-group-center,
.button-group-left,
.button-group-right {
display: flex;
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 {
font-size: 1.22em;
}
i {
border-radius: 5px;
cursor: pointer;
display: block;
height: 100%;
line-height: inherit;
width: 100%;
}
i:hover {
background-color: $newToolbarButtonHoverColor;
}
i.toggled {
background: $newToolbarButtonToggleColor;
}
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: 0;
.overflow-menu-item {
align-items: center;
cursor: pointer;
display: flex;
padding: 5px 10px;
&:hover {
background: rgba(0255, 255, 255, 0.2);
}
&.unclickable {
cursor: default;
}
&.unclickable:hover {
background: inherit;
}
}
.overflow-menu-item-icon {
margin-right: 10px;
i {
display: inline;
}
i:hover {
background-color: initial;
}
img {
max-width: 18px;
max-height: 18px;
}
}
.profile-text {
max-width: 150px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
.toolbox-button {
color: $toolbarButtonColor;
cursor: pointer;
display: inline-block;
font-size: $newToolbarFontSize;
line-height: $newToolbarSize;
margin: 0 10px;
text-align: center;
}
.toolbar-button-with-badge {
position: relative;
.badge-round {
bottom: 9px;
position: absolute;
right: 9px;
}
}
.toolbox-button-wth-dialog {
display: inline-block;
}
.toolbox-icon {
height: $newToolbarSize;
width: $newToolbarSize;
}
}
.filmstrip-toolbox {
background-color: $newToolbarBackgroundColor;
box-sizing: border-box;
display: flex;
flex-direction: column;
z-index: $toolbarZ;
i {
cursor: pointer;
display: block;
font-size: $newToolbarFontSize;
height: 37px;
line-height: 37px;
width: 37px;
}
i:hover {
background-color: $newToolbarButtonHoverColor;
}
i.toggled {
background: $newToolbarButtonToggleColor;
}
i.toggled:hover {
background-color: $newToolbarButtonHoverColor;
}
.icon-hangup {
color: $hangupColor;
}
.toolbox-button {
color: $toolbarButtonColor;
cursor: pointer;
text-align: center;
}
border-radius: 3px;
.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;
}
}
.filmstrip-only {
.toolbox,
.toolbox-toolbars {

View File

@@ -36,11 +36,6 @@ $alwaysOnTopToolbarFontSize: 1em;
$alwaysOnTopToolbarSize: 30px;
$defaultToolbarSize: 50px;
$defaultFilmStripOnlyToolbarSize: 37px;
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
$newToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
$newToolbarButtonToggleColor: rgba(14, 20, 35, 1);
$newToolbarFontSize: 1.9em;
$newToolbarSize: 50px;
$secToolbarFontSize: 1.9em;
$secToolbarLineHeight: 45px;
$toolbarAvatarPadding: 10px;

View File

@@ -19,20 +19,6 @@
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% - #{$newToolbarSize});
}
}
}
.filmstrip {
align-items: flex-end;
box-sizing: border-box;
@@ -46,7 +32,14 @@
* any parent is also fixed.
*/
position: fixed;
z-index: $filmstripVideosZ;
/**
* 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};
/**
* Hide videos by making them slight to the right.

View File

@@ -77,6 +77,7 @@
text-align: center;
.star-label {
color: $rateStarLabelColor;
font-size: 14px;
height: 16px;
}

View File

@@ -135,12 +135,6 @@
}
}
.modal-dialog-form {
.video-quality-dialog-title {
display: none;
}
}
.video-state-indicator {
background: $videoStateIndicatorBackground;
cursor: default;
@@ -168,11 +162,11 @@
}
.centeredVideoLabel.moveToCorner {
z-index: $zindex3;
z-index: $tooltipsZ;
}
#videoResolutionLabel {
z-index: $zindex3 + 1;
z-index: #{$tooltipsZ + 1};
}
.centeredVideoLabel {

Binary file not shown.

View File

@@ -23,7 +23,6 @@
<glyph unicode="&#xe616;" 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="&#xe61d;" 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="&#xe80b;" 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="&#xe89e;" 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="&#xe8b3;" 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="&#xe901;" 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="&#xe902;" 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" />

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -44,10 +44,7 @@ var interfaceConfig = {
'microphone', 'camera', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup',
// extended toolbar
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad',
'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip',
'invite', 'feedback', 'stats', 'shortcuts'
],
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip' ],
/**
* Main Toolbar Buttons
@@ -153,19 +150,7 @@ var interfaceConfig = {
*
* @type {boolean}
*/
VIDEO_QUALITY_LABEL_DISABLED: false,
/**
* This is a temporary feature flag used to gate access to the toolbox so it
* can be developed through smaller changesets and set to false if bad bugs
* are found. This feature flag will be removed at some point, as well as
* the old toolbox. This new toolbox will be horizontal and the previous
* feature of supporting menu button ordering through interfaceConfig will
* be removed. Support for configuring which buttons display will remain.
*
* @type {boolean}
*/
_USE_NEW_TOOLBOX: true
VIDEO_QUALITY_LABEL_DISABLED: false
/**
* Specify custom URL for downloading android mobile app.

View File

@@ -255,7 +255,6 @@
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;
@@ -287,7 +286,6 @@
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;

View File

@@ -32,8 +32,8 @@ class ViewController: UIViewController {
@IBAction func openJitsiMeet(sender: Any?) {
let jitsiMeetCoordinator = JitsiMeetPresentationCoordinator()
self.jitsiMeetCoordinator = jitsiMeetCoordinator
jitsiMeetCoordinator.jitsiMeetView.welcomePageEnabled = true
jitsiMeetCoordinator.jitsiMeetView.load(nil)
jitsiMeetCoordinator.jitsiMeetView().welcomePageEnabled = true
jitsiMeetCoordinator.jitsiMeetView().load(nil)
jitsiMeetCoordinator.show()
}
}

View File

@@ -405,7 +405,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -458,7 +458,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";

View File

@@ -20,20 +20,8 @@ import Foundation
/// an external window that can be resized and dragged with custom PiP mode
open class JitsiMeetPresentationCoordinator: NSObject {
public let meetViewController: JitsiMeetViewController
public let meetWindow: PiPWindow
public var isInPiP: Bool {
get {
return meetWindow.isInPiP
}
}
public var jitsiMeetView: JitsiMeetView {
get {
return meetViewController.jitsiMeetView
}
}
fileprivate let meetViewController: JitsiMeetViewController
fileprivate let meetWindow: PiPWindow
public init(meetViewController: JitsiMeetViewController? = nil,
meetWindow: PiPWindow? = nil) {
@@ -46,12 +34,14 @@ open class JitsiMeetPresentationCoordinator: NSObject {
configureMeetViewController()
}
/// Show window with jitsi meet and perform a completion closure
public func jitsiMeetView() -> JitsiMeetView {
return meetViewController.jitsiMeetView
}
open func show(completion: CompletionAction? = nil) {
meetWindow.show(completion: completion)
}
/// Hide window with jitsi meet and perform a completion closure
open func hide(completion: CompletionAction? = nil) {
meetWindow.hide(completion: completion)
}
@@ -87,7 +77,7 @@ extension JitsiMeetPresentationCoordinator: JitsiMeetViewControllerDelegate {
switch to {
case .enterPictureInPicture:
meetWindow.enterPictureInPicture()
case .sizeChange:
case .traitChange:
// resize to full screen if rotation happens
if meetWindow.isInPiP {
meetWindow.exitPictureInPicture()

View File

@@ -19,8 +19,8 @@ public enum JitsiMeetPresentationUpdate {
/// The conference wants to enter Picture-in-Picture
case enterPictureInPicture
/// A screen size change (usually screen rotation)
case sizeChange
/// A system traitCollectionChange (usually screen rotation)
case traitChange
}
public protocol JitsiMeetViewControllerDelegate: class {
@@ -59,10 +59,9 @@ open class JitsiMeetViewController: UIViewController {
jitsiMeetView.delegate = self
}
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
delegate?.performPresentationUpdate(to: .sizeChange)
open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
delegate?.performPresentationUpdate(to: .traitChange)
}
}

View File

@@ -32,17 +32,7 @@ open class PiPWindow: UIWindow {
}
/// The size ratio for root view controller 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
}
}()
public var pipSizeRatio: CGFloat = 0.333
/// The PiP state of this contents of the window
private(set) var isInPiP: Bool = false

View File

@@ -1,20 +0,0 @@
{
"en": "",
"bg": "",
"de": "",
"es": "",
"fr": "",
"hy": "",
"it": "",
"oc": "",
"pl": "",
"ptBR": "",
"ru": "",
"sk": "",
"sl": "",
"sv": "",
"tr": "",
"zhCN": "",
"nb": "",
"eo": ""
}

View File

@@ -1,512 +0,0 @@
{
"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": ""
}
}

View File

@@ -23,7 +23,6 @@
"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.",
@@ -74,22 +73,14 @@
"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": "Screen share",
"stopSharedVideo": "Stop YouTube video",
"sharescreen": "Start / Stop screen sharing",
"fullscreen": "View / Exit full screen",
"sip": "Call SIP number",
"Settings": "Settings",
@@ -105,9 +96,7 @@
"micDisabled": "Microphone is not available",
"filmstrip": "Show / Hide videos",
"profile": "Edit your profile",
"raiseHand": "Raise / Lower your hand",
"shortcuts": "View shortcuts",
"speakerStats": "Speaker stats"
"raiseHand": "Raise / Lower your hand"
},
"unsupportedBrowser": {
"appNotInstalled": "Join this meeting with __app__ on your phone.",
@@ -296,7 +285,6 @@
"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",
@@ -485,7 +473,6 @@
"loadingPeople": "Searching for people to invite",
"noResults": "No matching search results",
"noValidNumbers": "Please enter a phone number",
"notAvailable": "You can't invite people.",
"searchNumbers": "Enter a phone number to invite",
"searchPeople": "Enter a name to invite",
"searchPeopleAndNumbers": "Enter a name or phone number to invite",
@@ -552,16 +539,11 @@
"later": "Later",
"next": "Upcoming",
"nextMeeting": "next meeting",
"now": "Now",
"permissionButton": "Open settings",
"permissionMessage": "Calendar permission is required to list your meetings in the app."
"now": "Now"
},
"recentList": {
"today": "Today",
"yesterday": "Yesterday",
"earlier": "Earlier"
},
"sectionList": {
"pullToRefresh": "Pull to refresh"
}
}

View File

@@ -30,7 +30,6 @@ 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
@@ -101,6 +100,9 @@ const UIListeners = new Map([
], [
UIEvents.SHARED_VIDEO_CLICKED,
() => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
], [
UIEvents.TOGGLE_FULLSCREEN,
() => UI.toggleFullScreen()
], [
UIEvents.TOGGLE_CHAT,
() => UI.toggleChat()
@@ -133,6 +135,14 @@ 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.
*
@@ -245,20 +255,12 @@ 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 = (id, name, raisedHandStatus) => {
VideoLayout.setRaisedHandStatus(id, raisedHandStatus);
UI.setRaisedHandStatus = (participant, raisedHandStatus) => {
VideoLayout.setRaisedHandStatus(participant.getId(), raisedHandStatus);
if (raisedHandStatus) {
messageHandler.participantNotification(
name,
participant.getDisplayName(),
'notify.somebody',
'connected',
'notify.raisedHand');
@@ -372,14 +374,6 @@ UI.start = function() {
$('body').addClass('vertical-filmstrip');
}
// 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.
if (interfaceConfig._USE_NEW_TOOLBOX && !interfaceConfig.filmStripOnly) {
$('body').addClass('use-new-toolbox');
}
document.title = interfaceConfig.APP_NAME;
};
@@ -410,7 +404,12 @@ UI.bindEvents = () => {
// Resize and reposition videos in full screen mode.
$(document).on(
'webkitfullscreenchange mozfullscreenchange fullscreenchange',
onResize);
() => {
eventEmitter.emit(
UIEvents.FULLSCREEN_TOGGLED,
UIUtil.isFullScreen());
onResize();
});
$(window).resize(onResize);
};
@@ -475,7 +474,6 @@ UI.initEtherpad = name => {
etherpadManager
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
APP.store.dispatch(setEtherpadHasInitialzied());
APP.store.dispatch(showEtherpadButton());
};

View File

@@ -1,7 +1,4 @@
/* global $, APP, interfaceConfig */
import { setDocumentEditingState } from '../../../react/features/etherpad';
import { getToolboxHeight } from '../../../react/features/toolbox';
/* global $, interfaceConfig */
import VideoLayout from '../videolayout/VideoLayout';
import LargeContainer from '../videolayout/LargeContainer';
@@ -129,8 +126,7 @@ class Etherpad extends LargeContainer {
let height, width;
if (interfaceConfig.VERTICAL_FILMSTRIP) {
height = interfaceConfig._USE_NEW_TOOLBOX
? containerHeight - getToolboxHeight() : containerHeight;
height = containerHeight;
width = containerWidth - Filmstrip.getFilmstripWidth();
} else {
height = containerHeight - Filmstrip.getFilmstripHeight();
@@ -246,7 +242,5 @@ export default class EtherpadManager {
this.eventEmitter
.emit(UIEvents.TOGGLED_SHARED_DOCUMENT, !isVisible);
APP.store.dispatch(setDocumentEditingState(!isVisible));
}
}

View File

@@ -35,7 +35,6 @@ import {
StartLiveStreamDialog,
StopLiveStreamDialog,
hideRecordingLabel,
setRecordingType,
updateRecordingState
} from '../../../react/features/recording';
@@ -203,8 +202,6 @@ const Recording = {
this.eventEmitter = eventEmitter;
this.recordingType = recordingType;
APP.store.dispatch(setRecordingType(recordingType));
this.updateRecordingState(APP.conference.getRecordingState());
if (recordingType === 'jibri') {
@@ -222,9 +219,6 @@ const Recording = {
'#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.
if (config.iAmRecorder) {
@@ -293,7 +287,6 @@ const Recording = {
this.currentState = recordingState;
let labelDisplayConfiguration;
let isRecording = false;
switch (recordingState) {
case JitsiRecordingStatus.ON:
@@ -305,7 +298,6 @@ const Recording = {
};
this._setToolbarButtonToggled(true);
isRecording = true;
break;
}
@@ -370,7 +362,6 @@ const Recording = {
}
APP.store.dispatch(updateRecordingState({
isRecording,
labelDisplayConfiguration,
recordingState
}));

View File

@@ -18,11 +18,7 @@ import {
participantJoined,
participantLeft
} from '../../../react/features/base/participants';
import {
dockToolbox,
getToolboxHeight,
showToolbox
} from '../../../react/features/toolbox';
import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
import SharedVideoThumb from './SharedVideoThumb';
@@ -699,8 +695,7 @@ class SharedVideoContainer extends LargeContainer {
let height, width;
if (interfaceConfig.VERTICAL_FILMSTRIP) {
height = interfaceConfig._USE_NEW_TOOLBOX
? containerHeight - getToolboxHeight() : containerHeight;
height = containerHeight;
width = containerWidth - Filmstrip.getFilmstripWidth();
} else {
height = containerHeight - Filmstrip.getFilmstripHeight();

View File

@@ -1,6 +1,5 @@
/* global $, APP */
/* global $ */
import UIEvents from '../../../service/UI/UIEvents';
import { setVisiblePanel } from '../../../react/features/side-panel';
/**
* Handles open and close of the extended toolbar side panel
@@ -58,7 +57,6 @@ const SideContainerToggler = {
if (isSelectorVisible) {
this.hide();
APP.store.dispatch(setVisiblePanel(null));
} else {
if (this.isVisible()) {
$('#sideToolbarContainer').children()
@@ -76,7 +74,6 @@ const SideContainerToggler = {
}
this.showInnerContainer(elementSelector);
APP.store.dispatch(setVisiblePanel(elementId));
}
},

View File

@@ -1,4 +1,4 @@
/* global APP, $, interfaceConfig */
/* global APP, $ */
import { processReplacements, linkify } from './Replacement';
import CommandsProcessor from './Commands';
@@ -9,12 +9,7 @@ import UIEvents from '../../../../service/UI/UIEvents';
import { smileys } from './smileys';
import { addMessage, markAllRead } from '../../../../react/features/chat';
import {
dockToolbox,
getToolboxHeight,
setSubject
} from '../../../../react/features/toolbox';
import { dockToolbox, setSubject } from '../../../../react/features/toolbox';
let unreadMessages = 0;
const sidePanelsContainerId = 'sideToolbarContainer';
@@ -168,8 +163,6 @@ 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();
@@ -180,16 +173,7 @@ function resizeChatConversation() {
$('#smileys').css('bottom', (msgareaHeight - 26) / 2);
$('#smileysContainer').css('bottom', msgareaHeight);
chat.width(width - 10);
if (interfaceConfig._USE_NEW_TOOLBOX) {
const maybeAMagicNumberForPaddingAndMargin = 100;
const offset = maybeAMagicNumberForPaddingAndMargin
+ msgareaHeight + getToolboxHeight();
chat.height(window.innerHeight - offset);
} else {
chat.height(window.innerHeight - 15 - msgareaHeight);
}
chat.height(window.innerHeight - 15 - msgareaHeight);
}
/**
@@ -265,7 +249,6 @@ const Chat = {
}
unreadMessages = 0;
APP.store.dispatch(markAllRead());
updateVisualNotification();
// Undock the toolbar when the chat is shown and if we're in a
@@ -291,10 +274,9 @@ const Chat = {
*/
// eslint-disable-next-line max-params
updateChatConversation(id, displayName, message, stamp) {
const isFromLocalParticipant = APP.conference.isLocalId(id);
let divClassName = '';
if (isFromLocalParticipant) {
if (APP.conference.isLocalId(id)) {
divClassName = 'localuser';
} else {
divClassName = 'remoteuser';
@@ -312,7 +294,6 @@ const Chat = {
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br/>');
const escDisplayName = UIUtil.escapeHtml(displayName);
const timestamp = getCurrentTime(stamp);
// eslint-disable-next-line no-param-reassign
message = processReplacements(escMessage);
@@ -321,18 +302,13 @@ const Chat = {
= `${'<div class="chatmessage">'
+ '<img src="images/chatArrow.svg" class="chatArrow">'
+ '<div class="username '}${divClassName}">${escDisplayName
}</div><div class="timestamp">${timestamp
}</div><div class="timestamp">${getCurrentTime(stamp)
}</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));
},
/**

View File

@@ -227,10 +227,43 @@ const UIUtil = {
* mode, {false} otherwise
*/
isFullScreen() {
return Boolean(document.fullscreenElement
return document.fullscreenElement
|| document.mozFullScreenElement
|| document.webkitFullscreenElement
|| document.msFullscreenElement);
|| 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);
}
},
/**

View File

@@ -90,17 +90,6 @@ const KeyboardShortcut = {
enabled = value;
},
/**
* Opens the {@KeyboardShortcutsDialog} dialog.
*
* @returns {void}
*/
openDialog() {
APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
shortcutDescriptions: _shortcutsHelp
}));
},
/**
* Registers a new shortcut.
*
@@ -188,7 +177,9 @@ const KeyboardShortcut = {
_initGlobalShortcuts() {
this.registerShortcut('?', null, () => {
sendAnalytics(createShortcutEvent('help'));
this.openDialog();
APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
shortcutDescriptions: _shortcutsHelp
}));
}, 'keyboardShortcuts.toggleShortcuts');
// register SPACE shortcut in two steps to insure visibility of help

15
package-lock.json generated
View File

@@ -6785,6 +6785,11 @@
"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",
@@ -7468,7 +7473,7 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#6282e7a82e768dd6a5970de89602757a8f882ffb",
"version": "github:jitsi/lib-jitsi-meet#0503ec4d3f175b154b1c6fd7037520ce0768fa58",
"requires": {
"async": "0.9.0",
"current-executing-script": "0.1.3",
@@ -8639,6 +8644,14 @@
"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",

View File

@@ -46,9 +46,10 @@
"jquery-i18next": "1.2.0",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#6282e7a82e768dd6a5970de89602757a8f882ffb",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0503ec4d3f175b154b1c6fd7037520ce0768fa58",
"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",

View File

@@ -129,21 +129,16 @@ export function createFeedbackOpenEvent() {
}
/**
* Creates an event for an action regarding the AddPeopleDialog (invites).
* 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.
*
* @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 createInviteDialogEvent(
action, actionSubject, attributes = {}) {
export function createInviteDialogClosedEvent() {
return {
action,
actionSubject,
attributes,
source: 'inviteDialog'
action: 'invite.dialog.closed'
};
}

View File

@@ -96,18 +96,6 @@ 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.

View File

@@ -31,7 +31,6 @@ import {
LOCK_STATE_CHANGED,
P2P_STATUS_CHANGED,
SET_AUDIO_ONLY,
SET_DESKTOP_SHARING_ENABLED,
SET_FOLLOW_ME,
SET_LASTN,
SET_PASSWORD,
@@ -434,22 +433,6 @@ 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.
*

View File

@@ -14,7 +14,6 @@ import {
LOCK_STATE_CHANGED,
P2P_STATUS_CHANGED,
SET_AUDIO_ONLY,
SET_DESKTOP_SHARING_ENABLED,
SET_FOLLOW_ME,
SET_PASSWORD,
SET_RECEIVE_VIDEO_QUALITY,
@@ -58,9 +57,6 @@ 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,
@@ -333,21 +329,6 @@ 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.
*

View File

@@ -65,7 +65,6 @@ const WHITELISTED_KEYS = [
'firefox_fake_device',
'forceJVB121Ratio',
'gatherStats',
'googleApiApplicationClientID',
'hiddenDomain',
'hosts',
'iAmRecorder',

View File

@@ -1,4 +1,4 @@
// @flow
/* @flow */
import type { Dispatch } from 'redux';
@@ -54,12 +54,10 @@ export function connect() {
/**
* Closes connection.
*
* @param {boolean} [requestFeedback] - Whether or not to attempt showing a
* request for call feedback.
* @returns {Function}
*/
export function disconnect(requestFeedback: boolean = false) {
export function disconnect() {
// XXX For web based version we use conference hanging up logic from the old
// app.
return () => APP.conference.hangup(requestFeedback);
return () => APP.conference.hangup();
}

View File

@@ -39,12 +39,6 @@ type Props = {
*/
disableBlanketClickDismiss: boolean,
/**
* If true, the cancel button will not display but cancel actions, like
* clicking the blanket, will cancel.
*/
hideCancelButton: boolean,
/**
* Whether the dialog is modal. This means clicking on the blanket will
* leave the dialog open. No cancel button.
@@ -269,9 +263,7 @@ class StatelessDialog extends Component<Props> {
* not modal.
*/
_renderCancelButton(options = {}) {
if (options.cancelDisabled
|| options.isModal
|| options.hideCancelButton) {
if (options.cancelDisabled || options.isModal) {
return null;
}

File diff suppressed because it is too large Load Diff

View File

@@ -195,18 +195,6 @@ function _visitNode(node, callback) {
};
}
// Element.remove
//
// Required by:
// - lib-jitsi-meet ChatRoom#onPresence parsing
if (typeof elementPrototype.remove === 'undefined') {
elementPrototype.remove = function() {
if (this.parentNode !== null) {
this.parentNode.removeChild(this);
}
};
}
// Element.innerHTML
//
// Required by:
@@ -243,31 +231,6 @@ function _visitNode(node, callback) {
}
});
}
// Element.children
//
// Required by:
// - lib-jitsi-meet ChatRoom#onPresence parsing
if (!elementPrototype.hasOwnProperty('children')) {
Object.defineProperty(elementPrototype, 'children', {
get() {
const nodes = this.childNodes;
const children = [];
let i = 0;
let node = nodes[i];
while (node) {
if (node.nodeType === 1) {
children.push(node);
}
i += 1;
node = nodes[i];
}
return children;
}
});
}
}
// FIXME There is a weird infinite loop related to console.log and

View File

@@ -1,4 +1,4 @@
// @flow
/* @flow */
import UIEvents from '../../../../service/UI/UIEvents';
@@ -10,12 +10,8 @@ import {
import { MiddlewareRegistry } from '../redux';
import { playSound, registerSound, unregisterSound } from '../sounds';
import { localParticipantIdChanged } from './actions';
import {
localParticipantIdChanged,
participantUpdated
} from './actions';
import {
DOMINANT_SPEAKER_CHANGED,
KICK_PARTICIPANT,
MUTE_REMOTE_PARTICIPANT,
PARTICIPANT_DISPLAY_NAME_CHANGED,
@@ -31,7 +27,6 @@ import {
import {
getAvatarURLByParticipantId,
getLocalParticipant,
getParticipantById,
getParticipantCount
} from './functions';
import {
@@ -52,7 +47,7 @@ MiddlewareRegistry.register(store => next => action => {
const { conference } = store.getState()['features/base/conference'];
if (action.type === PARTICIPANT_JOINED
|| action.type === PARTICIPANT_LEFT) {
|| action.type === PARTICIPANT_LEFT) {
_maybePlaySounds(store, action);
}
@@ -71,27 +66,6 @@ MiddlewareRegistry.register(store => next => action => {
store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
break;
case DOMINANT_SPEAKER_CHANGED: {
// Ensure the raised hand state is cleared for the dominant speaker.
const participant = getLocalParticipant(store.getState());
if (participant) {
const local = participant.id === action.participant.id;
store.dispatch(participantUpdated({
id: action.participant.id,
local,
raisedHand: false
}));
}
if (typeof APP === 'object') {
APP.UI.markDominantSpeaker(action.participant.id);
}
break;
}
case KICK_PARTICIPANT:
conference.kickParticipant(action.id);
break;
@@ -116,37 +90,10 @@ MiddlewareRegistry.register(store => next => action => {
case PARTICIPANT_JOINED:
case PARTICIPANT_UPDATED: {
const { participant } = action;
const { id, local, raisedHand } = participant;
if (typeof APP !== 'undefined') {
const participant = action.participant;
const { id, local } = participant;
// Send an external update of the local participant's raised hand state
// if a new raised hand state is defined in the action.
if (typeof raisedHand !== 'undefined') {
if (local) {
conference.setLocalParticipantProperty(
'raisedHand',
raisedHand);
}
if (typeof APP === 'object') {
if (local) {
APP.UI.onLocalRaiseHandChanged(raisedHand);
APP.UI.setLocalRaisedHandStatus(raisedHand);
} else {
const remoteParticipant
= getParticipantById(store.getState(), id);
remoteParticipant
&& APP.UI.setRaisedHandStatus(
remoteParticipant.id,
remoteParticipant.name,
raisedHand);
}
}
}
// Notify external listeners of potential avatarURL changes.
if (typeof APP === 'object') {
const preUpdateAvatarURL
= getAvatarURLByParticipantId(store.getState(), id);

View File

@@ -10,10 +10,6 @@ import {
import styles, { UNDERLAY_COLOR } from './styles';
import { translate } from '../../../i18n';
import { Icon } from '../../../font-icons';
type Props = {
/**
@@ -21,11 +17,6 @@ type Props = {
*/
disabled: boolean,
/**
* The translate function.
*/
t: Function,
/**
* Function to be invoked when an item is pressed. The item's URL is passed.
*/
@@ -36,11 +27,6 @@ type Props = {
*/
onRefresh: Function,
/**
* Function to override the rendered default empty list component.
*/
renderListEmptyComponent: Function,
/**
* Sections to be rendered in the following format:
*
@@ -67,7 +53,7 @@ type Props = {
* property and navigates to (probably) meetings, such as the recent list
* or the meeting list components.
*/
class NavigateSectionList extends Component<Props> {
export default class NavigateSectionList extends Component<Props> {
/**
* Constructor of the NavigateSectionList component.
*
@@ -83,8 +69,6 @@ class NavigateSectionList extends Component<Props> {
this._renderItem = this._renderItem.bind(this);
this._renderItemLine = this._renderItemLine.bind(this);
this._renderItemLines = this._renderItemLines.bind(this);
this._renderListEmptyComponent
= this._renderListEmptyComponent.bind(this);
this._renderSection = this._renderSection.bind(this);
}
@@ -96,16 +80,12 @@ class NavigateSectionList extends Component<Props> {
* @inheritdoc
*/
render() {
const { renderListEmptyComponent, sections } = this.props;
const { sections } = this.props;
return (
<SafeAreaView
style = { styles.container } >
<SectionList
ListEmptyComponent = {
renderListEmptyComponent
|| this._renderListEmptyComponent
}
keyExtractor = { this._getItemKey }
onRefresh = { this._onRefresh }
refreshing = { false }
@@ -294,34 +274,6 @@ class NavigateSectionList extends Component<Props> {
return null;
}
_renderListEmptyComponent: () => Object
/**
* Renders a component to display when the list is empty.
*
* @private
* @param {Object} section - The section being rendered.
* @returns {React$Node}
*/
_renderListEmptyComponent() {
const { t, onRefresh } = this.props;
if (typeof onRefresh === 'function') {
return (
<View style = { styles.pullToRefresh }>
<Text style = { styles.pullToRefreshText }>
{ t('sectionList.pullToRefresh') }
</Text>
<Icon
name = 'menu-down'
style = { styles.pullToRefreshIcon } />
</View>
);
}
return null;
}
_renderSection: Object => Object
/**
@@ -341,5 +293,3 @@ class NavigateSectionList extends Component<Props> {
);
}
}
export default translate(NavigateSectionList);

View File

@@ -180,25 +180,6 @@ const SECTION_LIST_STYLES = {
fontWeight: 'normal'
},
pullToRefresh: {
alignItems: 'center',
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
padding: 20
},
pullToRefreshIcon: {
backgroundColor: 'transparent',
color: OVERLAY_FONT_COLOR,
fontSize: 20
},
pullToRefreshText: {
backgroundColor: 'transparent',
color: OVERLAY_FONT_COLOR
},
touchableView: {
flexDirection: 'row'
}

View File

@@ -6,8 +6,6 @@ import React, { Component } from 'react';
import InlineDialogFailure from './InlineDialogFailure';
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* A MultiSelect that is also auto-completing.
*/
@@ -292,9 +290,7 @@ class MultiSelectAutocomplete extends Component {
error: false
});
})
.catch(error => {
logger.error('MultiSelectAutocomplete error in query', error);
.catch(() => {
this.setState({
error: true,
loading: false,

View File

@@ -7,16 +7,8 @@ import type { Dispatch } from 'redux';
/**
* Size threshold for determining if we are in reduced UI mode or not.
*
* FIXME The logic to base {@code reducedUI} on a hardcoded width or height is
* very brittle because it's completely disconnected from the UI which wants to
* be rendered and, naturally, it broke on iPad where even the secondary Toolbar
* didn't fit in the height. We do need to measure the actual UI at runtime and
* determine whether and how to render it. I'm bumping from 240 to 300 because I
* don't have the time now to refactor {@code ReducedUIDetector} or rip it out
* completely.
*/
const REDUCED_UI_THRESHOLD = 300;
const REDUCED_UI_THRESHOLD = 240;
/**
* Sets the aspect ratio of the app's user interface based on specific width and

View File

@@ -1,13 +1,3 @@
/**
* The type of redux action dispatched to disable screensharing or to start the
* flow for enabling screenshare.
*
* {
* type: TOGGLE_SCREENSHARING
* }
*/
export const TOGGLE_SCREENSHARING = Symbol('TOGGLE_SCREENSHARING');
/**
* The type of redux action dispatched when a track has been (locally or
* remotely) added to the conference.

View File

@@ -12,7 +12,6 @@ import {
import { getLocalParticipant } from '../participants';
import {
TOGGLE_SCREENSHARING,
TRACK_ADDED,
TRACK_CREATE_CANCELED,
TRACK_CREATE_ERROR,
@@ -173,20 +172,6 @@ export function destroyLocalTracks() {
};
}
/**
* Signals that the local participant is ending screensharing or beginning the
* screensharing flow.
*
* @returns {{
* type: TOGGLE_SCREENSHARING,
* }}
*/
export function toggleScreensharing() {
return {
type: TOGGLE_SCREENSHARING
};
}
/**
* Replaces one track with another for one renegotiation instead of invoking
* two renegotiations with a separate removeTrack and addTrack. Disposes the

View File

@@ -1,4 +1,4 @@
// @flow
/* @flow */
import {
CAMERA_FACING_MODE,
@@ -10,15 +10,9 @@ import {
toggleCameraFacingMode
} from '../media';
import { MiddlewareRegistry } from '../redux';
import UIEvents from '../../../../service/UI/UIEvents';
import { createLocalTracksA } from './actions';
import {
TOGGLE_SCREENSHARING,
TRACK_ADDED,
TRACK_REMOVED,
TRACK_UPDATED
} from './actionTypes';
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
import { getLocalTrack, setTrackMuted } from './functions';
declare var APP: Object;
@@ -87,12 +81,6 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case TOGGLE_SCREENSHARING:
if (typeof APP === 'object') {
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
}
break;
case TRACK_ADDED:
// TODO Remove this middleware case once all UI interested in new tracks
// being added are converted to react and listening for store changes.

View File

@@ -1,28 +0,0 @@
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* Wrapper around fetch GET requests to handle json-ifying the response
* and logging errors.
*
* @param {string} url - The URL to perform a GET against.
* @returns {Promise<Object>} The response body, in JSON format, will be
* through the Promise.
*/
export function doGetJSON(url) {
return fetch(url)
.then(response => {
const jsonify = response.json();
if (response.ok) {
return jsonify;
}
return jsonify
.then(result => Promise.reject(result));
})
.catch(error => {
logger.error('Error performing get:', url, error);
return Promise.reject(error);
});
}

View File

@@ -1,5 +1,4 @@
export * from './helpers';
export * from './httpUtils';
export * from './loadScript';
export * from './randomUtil';
export * from './uri';

View File

@@ -1,17 +1,17 @@
// @flow
declare var JitsiMeetJS: Object;
import { loadScript as loadScriptF } from 'js-utils';
/**
* Loads a script from a specific URL. The script will be interpreted upon load.
*
* @param {string} url - The url to be loaded.
* @returns {Promise} Resolved with no arguments when the script is loaded and
* rejected with the error from JitsiMeetJS.ScriptUtil.loadScript method.
* rejected with the error from loadScriptF method.
*/
export function loadScript(url: string): Promise<void> {
return new Promise((resolve, reject) =>
JitsiMeetJS.util.ScriptUtil.loadScript(
loadScriptF(
url,
/* async */ true,
/* prepend */ false,

View File

@@ -1,12 +1,5 @@
// @flow
/**
* Action to signal that calendar access has already been requested
* since the app started, so no new request should be done unless the
* user explicitly tries to refresh the calendar view.
*/
export const CALENDAR_ACCESS_REQUESTED = Symbol('CALENDAR_ACCESS_REQUESTED');
/**
* Action to update the current calendar entry list in the store.
*/

View File

@@ -1,28 +1,10 @@
// @flow
import {
CALENDAR_ACCESS_REQUESTED,
NEW_CALENDAR_ENTRY_LIST,
NEW_KNOWN_DOMAIN,
REFRESH_CALENDAR_ENTRY_LIST
} from './actionTypes';
/**
* Sends an action to signal that a calendar access has been requested. For
* more info see the {@link CALENDAR_ACCESS_REQUESTED}.
*
* @param {string | undefined} status - The result of the last calendar
* access request.
* @returns {{
* type: CALENDAR_ACCESS_REQUESTED
* }}
*/
export function updateCalendarAccessStatus(status: ?string) {
return {
status,
type: CALENDAR_ACCESS_REQUESTED
};
}
/**
* Sends an action to add a new known domain if not present yet.
*
@@ -42,16 +24,12 @@ export function maybeAddNewKnownDomain(domainName: string) {
/**
* Sends an action to refresh the entry list (fetches new data).
*
* @param {boolean|undefined} forcePermission - Whether to force to re-ask
* for the permission or not.
* @returns {{
* type: REFRESH_CALENDAR_ENTRY_LIST,
* forcePermission: boolean
* type: REFRESH_CALENDAR_ENTRY_LIST
* }}
*/
export function refreshCalendarEntryList(forcePermission: boolean = false) {
export function refreshCalendarEntryList() {
return {
forcePermission,
type: REFRESH_CALENDAR_ENTRY_LIST
};
}

View File

@@ -1,16 +1,12 @@
// @flow
import React, { Component } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { connect } from 'react-redux';
import styles from './styles';
import { refreshCalendarEntryList } from '../actions';
import { appNavigate } from '../../app';
import { getLocalizedDateFormatter, translate } from '../../base/i18n';
import { NavigateSectionList } from '../../base/react';
import { openSettings } from '../../mobile/permissions';
type Props = {
@@ -32,11 +28,6 @@ type Props = {
*/
displayed: boolean,
/**
* The current state of the calendar access permission.
*/
_calendarAccessStatus: string,
/**
* The calendar event list.
*/
@@ -52,6 +43,8 @@ type Props = {
* Component to display a list of events from the (mobile) user's calendar.
*/
class MeetingList extends Component<Props> {
_initialLoaded: boolean
/**
* Default values for the component's props.
*/
@@ -67,14 +60,6 @@ class MeetingList extends Component<Props> {
constructor(props) {
super(props);
const { dispatch, displayed } = props;
if (displayed) {
dispatch(refreshCalendarEntryList());
}
this._getRenderListEmptyComponent
= this._getRenderListEmptyComponent.bind(this);
this._onPress = this._onPress.bind(this);
this._onRefresh = this._onRefresh.bind(this);
this._toDisplayableItem = this._toDisplayableItem.bind(this);
@@ -88,11 +73,16 @@ class MeetingList extends Component<Props> {
* @inheritdoc
*/
componentWillReceiveProps(newProps) {
const { displayed } = this.props;
if (newProps.displayed && !displayed) {
// This is a conditional logic to refresh the calendar entries (thus
// to request access to calendar) on component first receives a
// displayed=true prop - to avoid requesting calendar access on
// app start.
if (!this._initialLoaded
&& newProps.displayed
&& !this.props.displayed) {
const { dispatch } = this.props;
this._initialLoaded = true;
dispatch(refreshCalendarEntryList());
}
}
@@ -110,45 +100,10 @@ class MeetingList extends Component<Props> {
disabled = { disabled }
onPress = { this._onPress }
onRefresh = { this._onRefresh }
renderListEmptyComponent = {
this._getRenderListEmptyComponent
}
sections = { this._toDisplayableList() } />
);
}
_getRenderListEmptyComponent: () => Object
/**
* Returns a list empty component if a custom one has to be rendered instead
* of the default one in the {@link NavigateSectionList}.
*
* @private
* @returns {Component}
*/
_getRenderListEmptyComponent() {
const { _calendarAccessStatus, t } = this.props;
if (_calendarAccessStatus === 'denied') {
return (
<View style = { styles.noPermissionMessageView }>
<Text style = { styles.noPermissionMessageText }>
{ t('calendarSync.permissionMessage') }
</Text>
<TouchableOpacity
onPress = { openSettings }
style = { styles.noPermissionMessageButton } >
<Text style = { styles.noPermissionMessageButtonText }>
{ t('calendarSync.permissionButton') }
</Text>
</TouchableOpacity>
</View>
);
}
return null;
}
_onPress: string => Function
/**
@@ -175,7 +130,7 @@ class MeetingList extends Component<Props> {
_onRefresh() {
const { dispatch } = this.props;
dispatch(refreshCalendarEntryList(true));
dispatch(refreshCalendarEntryList());
}
_toDisplayableItem: Object => Object
@@ -264,12 +219,12 @@ class MeetingList extends Component<Props> {
* @returns {string}
*/
_toDateString(event) {
const startDateTime
= getLocalizedDateFormatter(event.startDate).format('lll');
const endTime
= getLocalizedDateFormatter(event.endDate).format('LT');
/* eslint-disable max-len */
const startDateTime = getLocalizedDateFormatter(event.startDate).format('lll');
const endTime = getLocalizedDateFormatter(event.endDate).format('LT');
return `${startDateTime} - ${endTime}`;
/* eslint-enable max-len */
}
}
@@ -282,11 +237,8 @@ class MeetingList extends Component<Props> {
* }}
*/
export function _mapStateToProps(state: Object) {
const calendarSyncState = state['features/calendar-sync'];
return {
_calendarAccessStatus: calendarSyncState.calendarAccessStatus,
_eventList: calendarSyncState.events
_eventList: state['features/calendar-sync'].events
};
}

View File

@@ -1,4 +1,4 @@
import { ColorPalette, createStyleSheet } from '../../base/styles';
import { createStyleSheet } from '../../base/styles';
const NOTIFICATION_SIZE = 55;
@@ -8,46 +8,6 @@ const NOTIFICATION_SIZE = 55;
*/
export default createStyleSheet({
/**
* Button style of the open settings button.
*/
noPermissionMessageButton: {
backgroundColor: ColorPalette.blue,
borderColor: ColorPalette.blue,
borderRadius: 4,
borderWidth: 1,
height: 30,
justifyContent: 'center',
margin: 15,
paddingHorizontal: 20
},
/**
* Text style of the open settings button.
*/
noPermissionMessageButtonText: {
color: ColorPalette.white
},
/**
* Text style of the no permission message.
*/
noPermissionMessageText: {
backgroundColor: 'transparent',
color: 'rgba(255, 255, 255, 0.6)'
},
/**
* Top level view of the no permission message.
*/
noPermissionMessageView: {
alignItems: 'center',
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
padding: 20
},
/**
* The top level container of the notification.
*/

View File

@@ -1,4 +1,3 @@
export * from './actions';
export * from './components';
import './middleware';

View File

@@ -2,18 +2,13 @@
import Logger from 'jitsi-meet-logger';
import RNCalendarEvents from 'react-native-calendar-events';
import { APP_WILL_MOUNT } from '../app';
import { SET_ROOM } from '../base/conference';
import { MiddlewareRegistry } from '../base/redux';
import { APP_LINK_SCHEME, parseURIString } from '../base/util';
import { APP_STATE_CHANGED } from '../mobile/background';
import { APP_WILL_MOUNT } from '../app';
import {
maybeAddNewKnownDomain,
updateCalendarAccessStatus,
updateCalendarEntryList
} from './actions';
import { maybeAddNewKnownDomain, updateCalendarEntryList } from './actions';
import { REFRESH_CALENDAR_ENTRY_LIST } from './actionTypes';
const FETCH_END_DAYS = 10;
@@ -25,15 +20,12 @@ MiddlewareRegistry.register(store => next => action => {
const result = next(action);
switch (action.type) {
case APP_STATE_CHANGED:
_maybeClearAccessStatus(store, action);
break;
case APP_WILL_MOUNT:
_ensureDefaultServer(store);
_fetchCalendarEntries(store, false, false);
_fetchCalendarEntries(store, false);
break;
case REFRESH_CALENDAR_ENTRY_LIST:
_fetchCalendarEntries(store, true, action.forcePermission);
_fetchCalendarEntries(store, true);
break;
case SET_ROOM:
_parseAndAddDomain(store);
@@ -42,53 +34,34 @@ MiddlewareRegistry.register(store => next => action => {
return result;
});
/**
* Clears the calendar access status when the app comes back from
* the background. This is needed as some users may never quit the
* app, but puts it into the background and we need to try to request
* for a permission as often as possible, but not annoyingly often.
*
* @private
* @param {Object} store - The redux store.
* @param {Object} action - The Redux action.
* @returns {void}
*/
function _maybeClearAccessStatus(store, action) {
const { appState } = action;
if (appState === 'background') {
const { dispatch } = store;
dispatch(updateCalendarAccessStatus(undefined));
}
}
/**
* Ensures calendar access if possible and resolves the promise if it's granted.
*
* @private
* @param {boolean} promptForPermission - Flag to tell the app if it should
* prompt for a calendar permission if it wasn't granted yet.
* @param {Function} dispatch - The Redux dispatch function.
* @returns {Promise}
*/
function _ensureCalendarAccess(promptForPermission, dispatch) {
function _ensureCalendarAccess(promptForPermission) {
return new Promise((resolve, reject) => {
RNCalendarEvents.authorizationStatus()
.then(status => {
if (status === 'authorized') {
resolve(true);
resolve();
} else if (promptForPermission) {
RNCalendarEvents.authorizeEventStore()
.then(result => {
dispatch(updateCalendarAccessStatus(result));
resolve(result === 'authorized');
if (result === 'authorized') {
resolve();
} else {
reject(result);
}
})
.catch(error => {
reject(error);
});
} else {
resolve(false);
reject(status);
}
})
.catch(error => {
@@ -118,49 +91,64 @@ function _ensureDefaultServer(store) {
*
* @private
* @param {Object} store - The redux store.
* @param {boolean} maybePromptForPermission - Flag to tell the app if it should
* @param {boolean} promptForPermission - Flag to tell the app if it should
* prompt for a calendar permission if it wasn't granted yet.
* @param {boolean|undefined} forcePermission - Whether to force to re-ask
* for the permission or not.
* @returns {void}
*/
function _fetchCalendarEntries(
store,
maybePromptForPermission,
forcePermission
) {
const { dispatch } = store;
const state = store.getState()['features/calendar-sync'];
const { calendarAccessStatus } = state;
const promptForPermission
= (maybePromptForPermission && !calendarAccessStatus)
|| forcePermission;
function _fetchCalendarEntries(store, promptForPermission) {
_ensureCalendarAccess(promptForPermission)
.then(() => {
const startDate = new Date();
const endDate = new Date();
_ensureCalendarAccess(promptForPermission, dispatch)
.then(accessGranted => {
if (accessGranted) {
const startDate = new Date();
const endDate = new Date();
startDate.setDate(startDate.getDate() + FETCH_START_DAYS);
endDate.setDate(endDate.getDate() + FETCH_END_DAYS);
startDate.setDate(startDate.getDate() + FETCH_START_DAYS);
endDate.setDate(endDate.getDate() + FETCH_END_DAYS);
RNCalendarEvents.fetchAllEvents(
startDate.getTime(),
endDate.getTime(),
[]
)
.then(events => {
const { knownDomains } = store.getState()['features/calendar-sync'];
const eventList = [];
RNCalendarEvents.fetchAllEvents(
startDate.getTime(),
endDate.getTime(),
[]
)
.then(events => {
const { knownDomains } = state;
if (events && events.length) {
for (const event of events) {
const jitsiURL = _getURLFromEvent(event, knownDomains);
const now = Date.now();
_updateCalendarEntries(events, knownDomains, dispatch);
})
.catch(error => {
logger.error('Error fetching calendar.', error);
});
} else {
logger.warn('Calendar access not granted.');
}
if (jitsiURL) {
const eventStartDate = Date.parse(event.startDate);
const eventEndDate = Date.parse(event.endDate);
if (isNaN(eventStartDate) || isNaN(eventEndDate)) {
logger.warn(
'Skipping calendar event due to invalid dates',
event.title,
event.startDate,
event.endDate
);
} else if (eventEndDate > now) {
eventList.push({
endDate: eventEndDate,
id: event.id,
startDate: eventStartDate,
title: event.title,
url: jitsiURL
});
}
}
}
}
store.dispatch(updateCalendarEntryList(eventList.sort((a, b) =>
a.startDate - b.startDate
).slice(0, MAX_LIST_LENGTH)));
})
.catch(error => {
logger.error('Error fetching calendar.', error);
});
})
.catch(reason => {
logger.error('Error accessing calendar.', reason);
@@ -221,70 +209,3 @@ function _parseAndAddDomain(store) {
store.dispatch(maybeAddNewKnownDomain(locationURL.host));
}
/**
* Updates the calendar entries in Redux when new list is received.
*
* @private
* @param {Object} event - An event returned from the native calendar.
* @param {Array<string>} knownDomains - The known domain list.
* @returns {CalendarEntry}
*/
function _parseCalendarEntry(event, knownDomains) {
if (event) {
const jitsiURL = _getURLFromEvent(event, knownDomains);
if (jitsiURL) {
const eventStartDate = Date.parse(event.startDate);
const eventEndDate = Date.parse(event.endDate);
if (isNaN(eventStartDate) || isNaN(eventEndDate)) {
logger.warn(
'Skipping invalid calendar event',
event.title,
event.startDate,
event.endDate
);
} else {
return {
endDate: eventEndDate,
id: event.id,
startDate: eventStartDate,
title: event.title,
url: jitsiURL
};
}
}
}
return null;
}
/**
* Updates the calendar entries in Redux when new list is received.
*
* @private
* @param {Array<CalendarEntry>} events - The new event list.
* @param {Array<string>} knownDomains - The known domain list.
* @param {Function} dispatch - The Redux dispatch function.
* @returns {void}
*/
function _updateCalendarEntries(events, knownDomains, dispatch) {
if (events && events.length) {
const eventList = [];
for (const event of events) {
const calendarEntry
= _parseCalendarEntry(event, knownDomains);
const now = Date.now();
if (calendarEntry && calendarEntry.endDate > now) {
eventList.push(calendarEntry);
}
}
dispatch(updateCalendarEntryList(eventList.sort((a, b) =>
a.startDate - b.startDate
).slice(0, MAX_LIST_LENGTH)));
}
}

View File

@@ -3,19 +3,13 @@
import { ReducerRegistry } from '../base/redux';
import { PersistenceRegistry } from '../base/storage';
import {
CALENDAR_ACCESS_REQUESTED,
NEW_CALENDAR_ENTRY_LIST,
NEW_KNOWN_DOMAIN
} from './actionTypes';
import { NEW_CALENDAR_ENTRY_LIST, NEW_KNOWN_DOMAIN } from './actionTypes';
/**
* ZB: this is an object, as further data is to come here, like:
* - known domain list
*/
const DEFAULT_STATE = {
/**
* Note: If features/calendar-sync ever gets persisted, do not persist the
* calendarAccessStatus value as it's needed to remain a runtime value to
* see if we need to re-request the calendar permission from the user.
*/
calendarAccessStatus: undefined,
events: [],
knownDomains: []
};
@@ -32,12 +26,6 @@ ReducerRegistry.register(
STORE_NAME,
(state = DEFAULT_STATE, action) => {
switch (action.type) {
case CALENDAR_ACCESS_REQUESTED:
return {
...state,
calendarAccessStatus: action.status
};
case NEW_CALENDAR_ENTRY_LIST:
return {
...state,

View File

@@ -1,23 +0,0 @@
/**
* The type of the action which signals to add a new chat message.
*
* {
* type: ADD_MESSAGE,
* hasRead: boolean,
* message: string,
* timestamp: string,
* userName: string
* }
*/
export const ADD_MESSAGE = Symbol('ADD_MESSAGE');
/**
* The type of the action which updates which is the most recent message that
* has been seen by the local participant.
*
* {
* type: SET_LAST_READ_MESSAGE,
* message: Object
* }
*/
export const SET_LAST_READ_MESSAGE = Symbol('SET_LAST_READ_MESSAGE');

View File

@@ -1,63 +0,0 @@
import { ADD_MESSAGE, SET_LAST_READ_MESSAGE } from './actionTypes';
/* eslint-disable max-params */
/**
* Adds a chat message to the collection of messages.
*
* @param {string} userName - The username to display of the participant that
* authored the message.
* @param {string} message - The received message to display.
* @param {string} timestamp - A timestamp to display for when the message was
* received.
* @param {boolean} hasRead - Whether or not to immediately mark the message as
* read.
* @returns {{
* type: ADD_MESSAGE,
* hasRead: boolean,
* message: string,
* timestamp: string,
* userName: string
* }}
*/
export function addMessage(userName, message, timestamp, hasRead) {
return {
type: ADD_MESSAGE,
hasRead,
message,
timestamp,
userName
};
}
/* eslint-enable max-params */
/**
* Sets the last read message cursor to the latest message.
*
* @returns {Function}
*/
export function markAllRead() {
return (dispatch, getState) => {
const { messages } = getState()['features/chat'];
dispatch(setLastReadMessage(messages[messages.length - 1]));
};
}
/**
* Updates the last read message cursor to be set at the passed in message. The
* assumption is that messages will be ordered chronologically.
*
* @param {Object} message - The message from the redux state.
* @returns {{
* type: SET_LAST_READ_MESSAGE,
* message: Object
* }}
*/
export function setLastReadMessage(message) {
return {
type: SET_LAST_READ_MESSAGE,
message
};
}

View File

@@ -1,59 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getUnreadCount } from '../functions';
/**
* FIXME: Move this UI logic to a generic component that can be used for
* {@code ParticipantCounter} as well.
*/
/**
* Implements a React {@link Component} which displays a count of the number of
* unread chat messages.
*
* @extends Component
*/
class ChatCounter extends Component {
static propTypes = {
/**
* The number of unread chat messages in the conference.
*/
_count: PropTypes.number
};
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<span className = 'badge-round'>
<span>
{ this.props._count || null }
</span>
</span>
);
}
}
/**
* Maps (parts of) the Redux state to the associated {@code ChatCounter}'s
* props.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _count: number
* }}
*/
function _mapStateToProps(state) {
return {
_count: getUnreadCount(state)
};
}
export default connect(_mapStateToProps)(ChatCounter);

View File

@@ -1 +0,0 @@
export ChatCounter from './ChatCounter';

View File

@@ -1,20 +0,0 @@
// @flow
/**
* Selector for calculating the number of unread chat messages.
*
* @param {Object} state - The redux state.
* @returns {number} The number of unread messages.
*/
export function getUnreadCount(state: Object) {
const { lastReadMessage, messages } = state['features/chat'];
const messagesCount = messages.length;
if (!messagesCount) {
return 0;
}
const lastReadIndex = messages.lastIndexOf(lastReadMessage);
return messagesCount - (lastReadIndex + 1);
}

View File

@@ -1,7 +1,3 @@
export * from './actions';
export * from './actionTypes';
export * from './components';
export * from './constants';
import './middleware';
import './reducer';

View File

@@ -1,44 +0,0 @@
// @flow
import { ReducerRegistry } from '../base/redux';
import {
ADD_MESSAGE,
SET_LAST_READ_MESSAGE
} from './actionTypes';
const DEFAULT_STATE = {
open: false,
messages: [],
lastReadMessage: null
};
ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case ADD_MESSAGE: {
const newMessage = {
message: action.message,
timestamp: action.timestamp,
userName: action.userName
};
return {
...state,
lastReadMessage:
action.hasRead ? newMessage : state.lastReadMessage,
messages: [
...state.messages,
newMessage
]
};
}
case SET_LAST_READ_MESSAGE:
return {
...state,
lastReadMessage: action.message
};
}
return state;
});

View File

@@ -11,14 +11,7 @@ import { CalleeInfoContainer } from '../../base/jwt';
import { Filmstrip } from '../../filmstrip';
import { LargeVideo } from '../../large-video';
import { NotificationsContainer } from '../../notifications';
import { SidePanel } from '../../side-panel';
import {
Toolbox,
ToolboxV2,
fullScreenChanged,
setToolboxAlwaysVisible,
showToolbox
} from '../../toolbox';
import { showToolbox, Toolbox } from '../../toolbox';
import { HideNotificationBarStyle } from '../../unsupported-browser';
import { maybeShowSuboptimalExperienceNotification } from '../functions';
@@ -26,29 +19,11 @@ import { maybeShowSuboptimalExperienceNotification } from '../functions';
declare var APP: Object;
declare var interfaceConfig: Object;
/**
* DOM events for when full screen mode has changed. Different browsers need
* different vendor prefixes.
*
* @private
* @type {Array<string>}
*/
const FULL_SCREEN_EVENTS = [
'webkitfullscreenchange',
'mozfullscreenchange',
'fullscreenchange'
];
/**
* The type of the React {@code Component} props of {@link Conference}.
*/
type Props = {
/**
* Whether the toolbar should stay visible or be able to autohide.
*/
_alwaysVisibleToolbar: boolean,
/**
* Whether the local participant is recording the conference.
*/
@@ -62,7 +37,6 @@ type Props = {
* The conference page of the Web application.
*/
class Conference extends Component<Props> {
_onFullScreenChange: Function;
_onShowToolbar: Function;
_originalOnShowToolbar: Function;
@@ -85,9 +59,6 @@ class Conference extends Component<Props> {
leading: true,
trailing: false
});
// Bind event handler so it is only bound once for every instance.
this._onFullScreenChange = this._onFullScreenChange.bind(this);
}
/**
@@ -103,16 +74,10 @@ class Conference extends Component<Props> {
APP.UI.registerListeners();
APP.UI.bindEvents();
FULL_SCREEN_EVENTS.forEach(name =>
document.addEventListener(name, this._onFullScreenChange));
const { _alwaysVisibleToolbar, dispatch, t } = this.props;
const { dispatch, t } = this.props;
dispatch(connect());
maybeShowSuboptimalExperienceNotification(dispatch, t);
dispatch(setToolboxAlwaysVisible(
_alwaysVisibleToolbar || interfaceConfig.filmStripOnly));
}
/**
@@ -125,9 +90,6 @@ class Conference extends Component<Props> {
APP.UI.unregisterListeners();
APP.UI.unbindEvents();
FULL_SCREEN_EVENTS.forEach(name =>
document.removeEventListener(name, this._onFullScreenChange));
APP.conference.isJoined() && this.props.dispatch(disconnect());
}
@@ -138,26 +100,12 @@ class Conference extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const {
_USE_NEW_TOOLBOX,
VIDEO_QUALITY_LABEL_DISABLED,
filmStripOnly
} = interfaceConfig;
const { filmStripOnly, VIDEO_QUALITY_LABEL_DISABLED } = interfaceConfig;
const hideVideoQualityLabel
= filmStripOnly
|| VIDEO_QUALITY_LABEL_DISABLED
|| this.props._iAmRecorder;
let ToolboxToUse;
if (filmStripOnly) {
ToolboxToUse = null;
} else if (interfaceConfig._USE_NEW_TOOLBOX) {
ToolboxToUse = ToolboxV2;
} else {
ToolboxToUse = Toolbox;
}
return (
<div
id = 'videoconference_page'
@@ -168,10 +116,7 @@ class Conference extends Component<Props> {
<Filmstrip filmstripOnly = { filmStripOnly } />
</div>
{ ToolboxToUse && <ToolboxToUse /> }
{ _USE_NEW_TOOLBOX && !filmStripOnly
&& <SidePanel /> }
{ filmStripOnly ? null : <Toolbox /> }
<DialogContainer />
<NotificationsContainer />
@@ -190,17 +135,6 @@ class Conference extends Component<Props> {
);
}
/**
* Updates the Redux state when full screen mode has been enabled or
* disabled.
*
* @private
* @returns {void}
*/
_onFullScreenChange() {
this.props.dispatch(fullScreenChanged(APP.UI.isFullScreen()));
}
/**
* Displays the toolbar.
*
@@ -219,30 +153,17 @@ class Conference extends Component<Props> {
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _alwaysVisibleToolbar: boolean,
* _iAmRecorder: boolean
* }}
*/
function _mapStateToProps(state) {
const {
alwaysVisibleToolbar,
iAmRecorder
} = state['features/base/config'];
return {
/**
* Whether the toolbar should stay visible or be able to autohide.
*
* @private
*/
_alwaysVisibleToolbar: alwaysVisibleToolbar,
/**
* Whether the local participant is recording the conference.
*
* @private
*/
_iAmRecorder: iAmRecorder
_iAmRecorder: state['features/base/config'].iAmRecorder
};
}

View File

@@ -1,29 +0,0 @@
/**
* The type of the action which signals document editing has been enabled.
*
* {
* type: ETHERPAD_INITIALIZED
* }
*/
export const ETHERPAD_INITIALIZED = Symbol('ETHERPAD_INITIALIZED');
/**
* The type of the action which signals document editing has stopped or started.
*
* {
* type: SET_DOCUMENT_EDITING_STATUS
* }
*/
export const SET_DOCUMENT_EDITING_STATUS
= Symbol('SET_DOCUMENT_EDITING_STATUS');
/**
* The type of the action which signals to start or stop editing a shared
* document.
*
* {
* type: TOGGLE_DOCUMENT_EDITING
* }
*/
export const TOGGLE_DOCUMENT_EDITING = Symbol('TOGGLE_DOCUMENT_EDITING');

View File

@@ -1,50 +0,0 @@
// @flow
import {
ETHERPAD_INITIALIZED,
SET_DOCUMENT_EDITING_STATUS,
TOGGLE_DOCUMENT_EDITING
} from './actionTypes';
/**
* Dispatches an action to set whether document editing has started or stopped.
*
* @param {boolean} editing - Whether or not a document is currently being
* edited.
* @returns {{
* type: SET_DOCUMENT_EDITING_STATUS,
* editing: boolean
* }}
*/
export function setDocumentEditingState(editing: boolean) {
return {
type: SET_DOCUMENT_EDITING_STATUS,
editing
};
}
/**
* Dispatches an action to set Etherpad as having been initialized.
*
* @returns {{
* type: ETHERPAD_INITIALIZED
* }}
*/
export function setEtherpadHasInitialzied() {
return {
type: ETHERPAD_INITIALIZED
};
}
/**
* Dispatches an action to show or hide Etherpad.
*
* @returns {{
* type: TOGGLE_DOCUMENT_EDITING
* }}
*/
export function toggleDocument() {
return {
type: TOGGLE_DOCUMENT_EDITING
};
}

View File

@@ -1,5 +0,0 @@
export * from './actions';
export * from './actionTypes';
import './middleware';
import './reducer';

View File

@@ -1,30 +0,0 @@
// @flow
import { MiddlewareRegistry } from '../base/redux';
import UIEvents from '../../../service/UI/UIEvents';
import { TOGGLE_DOCUMENT_EDITING } from './actionTypes';
declare var APP: Object;
/**
* Middleware that captures actions related to collaborative document editing
* and notifies components not hooked into redux.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
// eslint-disable-next-line no-unused-vars
MiddlewareRegistry.register(store => next => action => {
if (typeof APP === 'undefined') {
return next(action);
}
switch (action.type) {
case TOGGLE_DOCUMENT_EDITING:
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
break;
}
return next(action);
});

View File

@@ -1,30 +0,0 @@
// @flow
import { ReducerRegistry } from '../base/redux';
import {
ETHERPAD_INITIALIZED,
SET_DOCUMENT_EDITING_STATUS
} from './actionTypes';
/**
* Reduces the Redux actions of the feature features/etherpad.
*/
ReducerRegistry.register('features/etherpad', (state = {}, action) => {
switch (action.type) {
case ETHERPAD_INITIALIZED:
return {
...state,
initialized: true
};
case SET_DOCUMENT_EDITING_STATUS:
return {
...state,
editing: action.editing
};
default:
return state;
}
});

View File

@@ -7,13 +7,11 @@ import { connect } from 'react-redux';
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
import { InviteButton } from '../../invite';
import { Toolbox, ToolboxFilmstrip, dockToolbox } from '../../toolbox';
import { Toolbox } from '../../toolbox';
import { setFilmstripHovered } from '../actions';
import { shouldRemoteVideosBeVisible } from '../functions';
declare var interfaceConfig: Object;
/**
* Implements a React {@link Component} which represents the filmstrip on
* Web/React.
@@ -64,12 +62,6 @@ class Filmstrip extends Component<*> {
*/
_remoteVideosVisible: PropTypes.bool,
/**
* Whether or not the toolbox is visible. The height of the vertical
* filmstrip needs to adjust to accommodate the horizontal toolbox.
*/
_toolboxVisible: PropTypes.bool,
/**
* Updates the redux store with filmstrip hover changes.
*/
@@ -119,7 +111,6 @@ class Filmstrip extends Component<*> {
_isAddToCallAvailable,
_isDialOutAvailable,
_remoteVideosVisible,
_toolboxVisible,
filmstripOnly
} = this.props;
@@ -131,17 +122,13 @@ class Filmstrip extends Component<*> {
* will get updated without replacing the DOM. If the known DOM gets
* modified, then the views will get blown away.
*/
const reduceHeight
= _toolboxVisible && interfaceConfig.TOOLBAR_BUTTONS.length;
const filmstripClassNames = `filmstrip ${_remoteVideosVisible
? '' : 'hide-videos'} ${reduceHeight ? 'reduce-height' : ''}`;
const ToolboxToUse = interfaceConfig._USE_NEW_TOOLBOX
? ToolboxFilmstrip : Toolbox;
const filmstripClassNames = `filmstrip ${_remoteVideosVisible ? ''
: 'hide-videos'}`;
return (
<div className = { filmstripClassNames }>
{ filmstripOnly ? <ToolboxToUse /> : null }
{ filmstripOnly ? <Toolbox /> : null }
<div
className = 'filmstrip__videos'
id = 'remoteVideos'>
@@ -185,9 +172,6 @@ class Filmstrip extends Component<*> {
*/
_notifyOfHoveredStateUpdate() {
if (this.props._hovered !== this._isHovered) {
if (interfaceConfig._USE_NEW_TOOLBOX) {
this.props.dispatch(dockToolbox(this._isHovered));
}
this.props.dispatch(setFilmstripHovered(this._isHovered));
}
}
@@ -227,8 +211,7 @@ class Filmstrip extends Component<*> {
* _hovered: boolean,
* _isAddToCallAvailable: boolean,
* _isDialOutAvailable: boolean,
* _remoteVideosVisible: boolean,
* _toolboxVisible: boolean
* _remoteVideosVisible: boolean
* }}
*/
function _mapStateToProps(state) {
@@ -248,13 +231,11 @@ function _mapStateToProps(state) {
return {
_hideInviteButton: iAmRecorder
|| (!isAddToCallAvailable && !isDialOutAvailable)
|| interfaceConfig._USE_NEW_TOOLBOX,
|| (!isAddToCallAvailable && !isDialOutAvailable),
_hovered: hovered,
_isAddToCallAvailable: isAddToCallAvailable,
_isDialOutAvailable: isDialOutAvailable,
_remoteVideosVisible: shouldRemoteVideosBeVisible(state),
_toolboxVisible: state['features/toolbox'].visible
_remoteVideosVisible: shouldRemoteVideosBeVisible(state)
};
}

View File

@@ -5,7 +5,8 @@ import {
UPDATE_DIAL_IN_NUMBERS_FAILED,
UPDATE_DIAL_IN_NUMBERS_SUCCESS
} from './actionTypes';
import { getDialInConferenceID, getDialInNumbers } from './functions';
declare var $: Function;
/**
* Opens the inline conference info dialog.
@@ -47,10 +48,12 @@ export function updateDialInNumbers() {
}
const { room } = state['features/base/conference'];
const conferenceIDURL
= `${dialInConfCodeUrl}?conference=${room}@${mucURL}`;
Promise.all([
getDialInNumbers(dialInNumbersUrl),
getDialInConferenceID(dialInConfCodeUrl, room, mucURL)
$.getJSON(dialInNumbersUrl),
$.getJSON(conferenceIDURL)
])
.then(([ dialInNumbers, { conference, id, message } ]) => {
if (!conference || !id) {

View File

@@ -2,11 +2,11 @@
import Avatar from '@atlaskit/avatar';
import InlineMessage from '@atlaskit/inline-message';
import { Immutable } from 'nuclear-js';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createInviteDialogEvent, sendAnalytics } from '../../analytics';
import { getInviteURL } from '../../base/connection';
import { Dialog, hideDialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
@@ -114,10 +114,16 @@ class AddPeopleDialog extends Component<*, *> {
*/
addToCallInProgress: false,
// FIXME: Remove usage of Immutable. {@code MultiSelectAutocomplete}
// will default to having its internal implementation use a plain array
// if no {@link defaultValue} is passed in. As such is the case, this
// instance of Immutable.List gets overridden with an array on the first
// search.
/**
* The list of invite items.
*/
inviteItems: []
inviteItems: new Immutable.List()
};
/**
@@ -144,17 +150,6 @@ class AddPeopleDialog extends Component<*, *> {
};
}
/**
* Sends an analytics event to record the dialog has been shown.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
sendAnalytics(createInviteDialogEvent(
'invite.dialog.opened', 'dialog'));
}
/**
* React Component method that executes once component is updated.
*
@@ -174,17 +169,6 @@ class AddPeopleDialog extends Component<*, *> {
}
}
/**
* Sends an analytics event to record the dialog has been closed.
*
* @inheritdoc
* @returns {void}
*/
componentWillUnmount() {
sendAnalytics(createInviteDialogEvent(
'invite.dialog.closed', 'dialog'));
}
/**
* Renders the content of this component.
*
@@ -254,32 +238,6 @@ class AddPeopleDialog extends Component<*, *> {
return text.replace(/\D/g, '');
}
/**
* Helper for determining how many of each type of user is being invited.
* Used for logging and sending analytics related to invites.
*
* @param {Array} inviteItems - An array with the invite items, as created
* in {@link _parseQueryResults}.
* @private
* @returns {Object} An object with keys as user types and values as the
* number of invites for that type.
*/
_getInviteTypeCounts(inviteItems = []) {
const inviteTypeCounts = {};
inviteItems.forEach(i => {
const type = i.item.type;
if (!inviteTypeCounts[type]) {
inviteTypeCounts[type] = 0;
}
inviteTypeCounts[type]++;
});
return inviteTypeCounts;
}
_isAddDisabled: () => boolean;
/**
@@ -362,15 +320,6 @@ class AddPeopleDialog extends Component<*, *> {
* @returns {void}
*/
_onSubmit() {
const inviteTypeCounts
= this._getInviteTypeCounts(this.state.inviteItems);
sendAnalytics(createInviteDialogEvent(
'clicked', 'inviteButton', {
...inviteTypeCounts,
inviteAllowed: this._isAddDisabled()
}));
if (this._isAddDisabled()) {
return;
}
@@ -451,16 +400,7 @@ class AddPeopleDialog extends Component<*, *> {
// If any invites are left that means something failed to send
// so treat it as an error.
if (invitesLeftToSend.length) {
const erroredInviteTypeCounts
= this._getInviteTypeCounts(invitesLeftToSend);
logger.error(`${invitesLeftToSend.length} invites failed`,
erroredInviteTypeCounts);
sendAnalytics(createInviteDialogEvent(
'error', 'invite', {
...erroredInviteTypeCounts
}));
logger.error(`${invitesLeftToSend.length} invites failed`);
this.setState({
addToCallInProgress: false,
@@ -568,7 +508,7 @@ class AddPeopleDialog extends Component<*, *> {
let peopleSearchPromise;
if (this.props.enableAddPeople && text) {
if (this.props.enableAddPeople) {
peopleSearchPromise = searchDirectory(
_peopleSearchUrl,
_jwt,

View File

@@ -5,15 +5,9 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createToolbarEvent, sendAnalytics } from '../../analytics';
import { translate } from '../../base/i18n';
import {
ToolbarButton,
ToolbarButtonV2,
TOOLTIP_TO_POPUP_POSITION
} from '../../toolbox';
import { ToolbarButton, TOOLTIP_TO_POPUP_POSITION } from '../../toolbox';
import { setInfoDialogVisibility, updateDialInNumbers } from '../actions';
import { setInfoDialogVisibility } from '../actions';
import { InfoDialog } from './info-dialog';
const { INITIAL_TOOLBAR_TIMEOUT } = interfaceConfig;
@@ -45,15 +39,6 @@ class InfoDialogButton extends Component {
* @static
*/
static propTypes = {
/**
* Phone numbers for dialing into the conference.
*/
_dialInNumbers: PropTypes.oneOfType([
PropTypes.object,
PropTypes.array
]),
/**
* Whether or not the {@code InfoDialog} should close by itself after a
* a timeout.
@@ -76,11 +61,6 @@ class InfoDialogButton extends Component {
*/
dispatch: PropTypes.func,
/**
* Invoked to obtain translated strings.
*/
t: PropTypes.func,
/**
* From which side tooltips should display. Will be re-used for
* displaying the inline dialog for video quality adjustment.
@@ -120,10 +100,6 @@ class InfoDialogButton extends Component {
if (this.props._shouldAutoClose) {
this._setAutoCloseTimeout();
}
if (!this.props._dialInNumbers) {
this.props.dispatch(updateDialInNumbers());
}
}
/**
@@ -169,9 +145,29 @@ class InfoDialogButton extends Component {
* @returns {ReactElement}
*/
render() {
return interfaceConfig._USE_NEW_TOOLBOX
? this._renderNewToolbarButton()
: this._renderOldToolbarButton();
const { _showDialog, _toolboxVisible, tooltipPosition } = this.props;
const buttonConfiguration = {
...DEFAULT_BUTTON_CONFIGURATION,
classNames: [
...DEFAULT_BUTTON_CONFIGURATION.classNames,
_showDialog ? 'toggled button-active' : ''
]
};
return (
<InlineDialog
content = { <InfoDialog
onClose = { this._onDialogClose }
onMouseOver = { this._onDialogMouseOver } /> }
isOpen = { _toolboxVisible && _showDialog }
onClose = { this._onDialogClose }
position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }>
<ToolbarButton
button = { buttonConfiguration }
onClick = { this._onDialogToggle }
tooltipPosition = { tooltipPosition } />
</InlineDialog>
);
}
/**
@@ -212,76 +208,9 @@ class InfoDialogButton extends Component {
* @returns {void}
*/
_onDialogToggle() {
sendAnalytics(createToolbarEvent('info'));
this.props.dispatch(setInfoDialogVisibility(!this.props._showDialog));
}
/**
* Renders a React Element for the {@code InfoDialog} using legacy
* {@code ToolbarButton}.
*
* @private
* @returns {ReactElement}
*/
_renderOldToolbarButton() {
const { _showDialog, _toolboxVisible, tooltipPosition } = this.props;
const buttonConfiguration = {
...DEFAULT_BUTTON_CONFIGURATION,
classNames: [
...DEFAULT_BUTTON_CONFIGURATION.classNames,
_showDialog ? 'toggled button-active' : ''
]
};
return (
<InlineDialog
content = { <InfoDialog
autoUpdateNumbers = { false }
onClose = { this._onDialogClose }
onMouseOver = { this._onDialogMouseOver } /> }
isOpen = { _toolboxVisible && _showDialog }
onClose = { this._onDialogClose }
position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }>
<ToolbarButton
button = { buttonConfiguration }
onClick = { this._onDialogToggle }
tooltipPosition = { tooltipPosition } />
</InlineDialog>
);
}
/**
* Renders a React Element for the {@code InfoDialog} using the newer
* {@code ToolbarButtonV2}.
*
* @private
* @returns {ReactElement}
*/
_renderNewToolbarButton() {
const { _showDialog, _toolboxVisible, t } = this.props;
const iconClass = `icon-info ${_showDialog ? 'toggled' : ''}`;
return (
<div className = 'toolbox-button-wth-dialog'>
<InlineDialog
content = { <InfoDialog
autoUpdateNumbers = { false }
onClose = { this._onDialogClose }
onMouseOver = { this._onDialogMouseOver } /> }
isOpen = { _toolboxVisible && _showDialog }
onClose = { this._onDialogClose }
position = { 'top right' }>
<ToolbarButtonV2
accessibilityLabel = 'Info'
iconName = { iconClass }
onClick = { this._onDialogToggle }
tooltip = { t('info.tooltip') } />
</InlineDialog>
</div>
);
}
/**
* Set a timeout to automatically hide the {@code InfoDialog}.
*
@@ -306,7 +235,6 @@ class InfoDialogButton extends Component {
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _dialInNumbers: Array,
* _shouldAutoClose: boolean,
* _showDialog: boolean,
* _toolboxVisible: boolean
@@ -315,16 +243,14 @@ class InfoDialogButton extends Component {
function _mapStateToProps(state) {
const {
infoDialogVisible,
infoDialogWillAutoClose,
numbers
infoDialogWillAutoClose
} = state['features/invite'];
return {
_dialInNumbers: numbers,
_shouldAutoClose: infoDialogWillAutoClose,
_showDialog: infoDialogVisible,
_toolboxVisible: state['features/toolbox'].visible
};
}
export default translate(connect(_mapStateToProps)(InfoDialogButton));
export default connect(_mapStateToProps)(InfoDialogButton);

View File

@@ -24,15 +24,6 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
* @extends Component
*/
class InfoDialog extends Component {
/**
* Default values for {@code InfoDialog} component's properties.
*
* @static
*/
static defaultProps = {
autoUpdateNumbers: true
};
/**
* {@code InfoDialog} component's property types.
*
@@ -78,13 +69,6 @@ class InfoDialog extends Component {
*/
_password: PropTypes.string,
/**
* Whether or not this component should make a request for dial-in
* numbers. If false, this component will rely on an outside source
* updating and passing in numbers through the _dialIn prop.
*/
autoUpdateNumbers: PropTypes.bool,
/**
* Invoked to open a dialog for adding participants to the conference.
*/
@@ -164,7 +148,7 @@ class InfoDialog extends Component {
* @returns {void}
*/
componentDidMount() {
if (!this.state.phoneNumber && this.props.autoUpdateNumbers) {
if (!this.state.phoneNumber) {
this.props.dispatch(updateDialInNumbers());
}
}

View File

@@ -1,44 +1,8 @@
// @flow
import { doGetJSON } from '../base/util';
declare var $: Function;
declare var interfaceConfig: Object;
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* Sends a GET request to obtain the conference ID necessary for identifying
* which conference to join after diaing the dial-in service.
*
* @param {string} baseUrl - The url for obtaining the conference ID (pin) for
* dialing into a conference.
* @param {string} roomName - The conference name to find the associated
* conference ID.
* @param {string} mucURL - In which MUC the conference exists.
* @returns {Promise} - The promise created by the request.
*/
export function getDialInConferenceID(
baseUrl: string,
roomName: string,
mucURL: string): Promise<Object> {
const conferenceIDURL = `${baseUrl}?conference=${roomName}@${mucURL}`;
return doGetJSON(conferenceIDURL);
}
/**
* Sends a GET request for phone numbers used to dial into a conference.
*
* @param {string} url - The service that returns confernce dial-in numbers.
* @returns {Promise} - The promise created by the request. The returned numbers
* may be an array of numbers or an object with countries as keys and arrays of
* phone number strings.
*/
export function getDialInNumbers(url: string): Promise<*> {
return doGetJSON(url);
}
/**
* Get the position of the invite option in the interfaceConfig.INVITE_OPTIONS
* list.
@@ -66,7 +30,7 @@ export function invitePeopleAndChatRooms( // eslint-disable-line max-params
inviteServiceUrl: string,
inviteUrl: string,
jwt: string,
inviteItems: Array<Object>): Promise<void> {
inviteItems: Object): Promise<void> {
if (!inviteItems || inviteItems.length === 0) {
return Promise.resolve();
}
@@ -114,24 +78,13 @@ export function searchDirectory( // eslint-disable-line max-params
): Promise<Array<Object>> {
const queryTypesString = JSON.stringify(queryTypes);
return fetch(`${serviceUrl}?query=${encodeURIComponent(text)}&queryTypes=${
queryTypesString}&jwt=${jwt}`)
.then(response => {
const jsonify = response.json();
if (response.ok) {
return jsonify;
}
return jsonify
.then(result => Promise.reject(result));
})
.catch(error => {
logger.error(
'Error searching directory:', error);
return Promise.reject(error);
});
return new Promise((resolve, reject) => {
$.getJSON(
`${serviceUrl}?query=${encodeURIComponent(text)}&queryTypes=${
queryTypesString}&jwt=${jwt}`,
resolve)
.catch((jqxhr, textStatus, error) => reject(error));
});
}
/**

View File

@@ -1,10 +0,0 @@
/**
* The type of the action which signals the keyboard shortcuts dialog should
* be displayed.
*
* {
* type: OPEN_KEYBOARD_SHORTCUTS_DIALOG
* }
*/
export const OPEN_KEYBOARD_SHORTCUTS_DIALOG
= Symbol('OPEN_KEYBOARD_SHORTCUTS_DIALOG');

View File

@@ -1,14 +0,0 @@
import { OPEN_KEYBOARD_SHORTCUTS_DIALOG } from './actionTypes';
/**
* Opens the dialog showing available keyboard shortcuts.
*
* @returns {{
* type: OPEN_KEYBOARD_SHORTCUTS_DIALOG
* }}
*/
export function openKeyboardShortcutsDialog() {
return {
type: OPEN_KEYBOARD_SHORTCUTS_DIALOG
};
}

View File

@@ -1,4 +1 @@
export * from './actions';
export * from './components';
import './middleware';

View File

@@ -1,26 +0,0 @@
// @flow
import { MiddlewareRegistry } from '../base/redux';
import { OPEN_KEYBOARD_SHORTCUTS_DIALOG } from './actionTypes';
declare var APP: Object;
/**
* Implements the middleware of the feature keyboard-shortcuts.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
// eslint-disable-next-line no-unused-vars
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case OPEN_KEYBOARD_SHORTCUTS_DIALOG:
if (typeof APP === 'object') {
APP.keyboardshortcut.openDialog();
}
break;
}
return next(action);
});

View File

@@ -1,31 +0,0 @@
// @flow
import { Alert, Linking, NativeModules } from 'react-native';
import { Platform } from '../../base/react';
/**
* Opens the settings panel for the current platform.
*
* @private
* @returns {void}
*/
export function openSettings() {
switch (Platform.OS) {
case 'android':
NativeModules.AndroidSettings.open().catch(() => {
Alert.alert(
'Error opening settings',
'Please open settings and grant the required permissions',
[
{ text: 'OK' }
]
);
});
break;
case 'ios':
Linking.openURL('app-settings:');
break;
}
}

View File

@@ -1,3 +1 @@
export * from './functions';
import './middleware';

View File

@@ -1,10 +1,9 @@
/* @flow */
import { Alert } from 'react-native';
import { openSettings } from './functions';
import { Alert, Linking, NativeModules } from 'react-native';
import { isRoomValid } from '../../base/conference';
import { Platform } from '../../base/react';
import { MiddlewareRegistry } from '../../base/redux';
import { TRACK_CREATE_ERROR } from '../../base/tracks';
@@ -65,9 +64,35 @@ function _alertPermissionErrorWithSettings(trackType) {
[
{ text: 'Cancel' },
{
onPress: openSettings,
onPress: _openSettings,
text: 'Settings'
}
],
{ cancelable: false });
}
/**
* Opens the settings panel for the current platform.
*
* @private
* @returns {void}
*/
function _openSettings() {
switch (Platform.OS) {
case 'android':
NativeModules.AndroidSettings.open().catch(() => {
Alert.alert(
'Error opening settings',
'Please open settings and grant the required permissions',
[
{ text: 'OK' }
]
);
});
break;
case 'ios':
Linking.openURL('app-settings:');
break;
}
}

View File

@@ -20,25 +20,3 @@ export const HIDE_RECORDING_LABEL = Symbol('HIDE_RECORDING_LABEL');
* @public
*/
export const RECORDING_STATE_UPDATED = Symbol('RECORDING_STATE_UPDATED');
/**
* The type of Redux action which updates the current known type of configured
* recording. For example, type "jibri" is used for live streaming.
*
* {
* type: RECORDING_STATE_UPDATED,
* recordingType: string
* }
* @public
*/
export const SET_RECORDING_TYPE = Symbol('SET_RECORDING_TYPE');
/**
* The type of Redux action triggers the flow to start or stop recording.
*
* {
* type: TOGGLE_RECORDING
* }
* @public
*/
export const TOGGLE_RECORDING = Symbol('TOGGLE_RECORDING');

View File

@@ -1,9 +1,4 @@
import {
HIDE_RECORDING_LABEL,
RECORDING_STATE_UPDATED,
SET_RECORDING_TYPE,
TOGGLE_RECORDING
} from './actionTypes';
import { HIDE_RECORDING_LABEL, RECORDING_STATE_UPDATED } from './actionTypes';
/**
* Hides any displayed recording label, regardless of current recording state.
@@ -18,36 +13,6 @@ export function hideRecordingLabel() {
};
}
/**
* Sets what type of recording service will be used.
*
* @param {string} recordingType - The type of recording service to be used.
* Should be one of the enumerated types in {@link RECORDING_TYPES}.
* @returns {{
* type: SET_RECORDING_TYPE,
* recordingType: string
* }}
*/
export function setRecordingType(recordingType) {
return {
type: SET_RECORDING_TYPE,
recordingType
};
}
/**
* Start or stop recording.
*
* @returns {{
* type: TOGGLE_RECORDING
* }}
*/
export function toggleRecording() {
return {
type: TOGGLE_RECORDING
};
}
/**
* Updates the redux state for the recording feature.
*

View File

@@ -1,12 +0,0 @@
// @flow
/**
* Expected supported recording types. JIBRI is known to support live streaming
* whereas JIRECON is for recording.
*
* @type {Object}
*/
export const RECORDING_TYPES = {
JIBRI: 'jibri',
JIRECON: 'jirecon'
};

View File

@@ -1,6 +1,4 @@
export * from './actions';
export * from './components';
export * from './constants';
import './middleware';
import './reducer';

View File

@@ -1,27 +0,0 @@
// @flow
import { MiddlewareRegistry } from '../base/redux';
import UIEvents from '../../../service/UI/UIEvents';
import { TOGGLE_RECORDING } from './actionTypes';
declare var APP: Object;
/**
* Implements the middleware of the feature recording.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
// eslint-disable-next-line no-unused-vars
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case TOGGLE_RECORDING:
if (typeof APP === 'object') {
APP.UI.emitEvent(UIEvents.TOGGLE_RECORDING);
}
break;
}
return next(action);
});

View File

@@ -1,9 +1,5 @@
import { ReducerRegistry } from '../base/redux';
import {
HIDE_RECORDING_LABEL,
RECORDING_STATE_UPDATED,
SET_RECORDING_TYPE
} from './actionTypes';
import { HIDE_RECORDING_LABEL, RECORDING_STATE_UPDATED } from './actionTypes';
/**
* Reduces the Redux actions of the feature features/recording.
@@ -22,12 +18,6 @@ ReducerRegistry.register('features/recording', (state = {}, action) => {
...action.recordingState
};
case SET_RECORDING_TYPE:
return {
...state,
recordingType: action.recordingType
};
default:
return state;
}

View File

@@ -151,15 +151,12 @@ class SettingsView extends AbstractSettingsView {
fieldSeparator = { true }
i18nLabel = 'settingsView.displayName'>
<TextInput
autoCorrect = { false }
onChangeText = { this._onChangeDisplayName }
placeholder = 'John Doe'
value = { _profile.displayName } />
</FormRow>
<FormRow i18nLabel = 'settingsView.email'>
<TextInput
autoCapitalize = 'none'
autoCorrect = { false }
keyboardType = { 'email-address' }
onChangeText = { this._onChangeEmail }
placeholder = 'email@example.com'
@@ -172,7 +169,6 @@ class SettingsView extends AbstractSettingsView {
i18nLabel = 'settingsView.serverURL'>
<TextInput
autoCapitalize = 'none'
autoCorrect = { false }
onBlur = { this._onBlurServerURL }
onChangeText = { this._onChangeServerURL }
placeholder = { this.props._serverURL }

View File

@@ -1,20 +0,0 @@
/**
* The type of the action which signals to update the current known state of the
* shared YouTube video.
*
* {
* type: SET_SHARED_VIDEO_STATUS,
* status: string
* }
*/
export const SET_SHARED_VIDEO_STATUS = Symbol('SET_SHARED_VIDEO_STATUS');
/**
* The type of the action which signals to start the flow for starting or
* stopping a shared YouTube video.
*
* {
* type: TOGGLE_SHARED_VIDEO
* }
*/
export const TOGGLE_SHARED_VIDEO = Symbol('TOGGLE_SHARED_VIDEO');

View File

@@ -1,31 +0,0 @@
import { SET_SHARED_VIDEO_STATUS, TOGGLE_SHARED_VIDEO } from './actionTypes';
/**
* Updates the current known status of the shared YouTube video.
*
* @param {string} status - The current status of the YouTube video being
* shared.
* @returns {{
* type: SET_SHARED_VIDEO_STATUS,
* status: string
* }}
*/
export function setSharedVideoStatus(status) {
return {
type: SET_SHARED_VIDEO_STATUS,
status
};
}
/**
* Starts the flow for starting or stopping a shared YouTube video.
*
* @returns {{
* type: TOGGLE_SHARED_VIDEO
* }}
*/
export function toggleSharedVideo() {
return {
type: TOGGLE_SHARED_VIDEO
};
}

View File

@@ -1,5 +0,0 @@
export * from './actions';
export * from './actionTypes';
import './middleware';
import './reducer';

Some files were not shown because too many files have changed in this diff Show More