[RN] Add invite screen
This commit is contained in:
parent
38b1be1291
commit
b6e2701991
|
@ -25,6 +25,18 @@
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-phone:before {
|
||||||
|
content: "\e0cd";
|
||||||
|
}
|
||||||
|
.icon-radio_button_unchecked:before {
|
||||||
|
content: "\e836";
|
||||||
|
}
|
||||||
|
.icon-radio_button_checked:before {
|
||||||
|
content: "\e837";
|
||||||
|
}
|
||||||
|
.icon-search:before {
|
||||||
|
content: "\e8b6";
|
||||||
|
}
|
||||||
.icon-chat-unread:before {
|
.icon-chat-unread:before {
|
||||||
content: "\e0b7";
|
content: "\e0b7";
|
||||||
}
|
}
|
||||||
|
|
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
|
@ -8,6 +8,7 @@
|
||||||
<missing-glyph horiz-adv-x="1024" />
|
<missing-glyph horiz-adv-x="1024" />
|
||||||
<glyph unicode=" " d="" />
|
<glyph unicode=" " d="" />
|
||||||
<glyph unicode="" glyph-name="chat-unread" d="M768 682v86h-512v-86h512zM598 426v86h-342v-86h342zM256 640v-86h512v86h-512zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
|
<glyph unicode="" glyph-name="chat-unread" d="M768 682v86h-512v-86h512zM598 426v86h-342v-86h342zM256 640v-86h512v86h-512zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
|
||||||
|
<glyph unicode="" glyph-name="phone" d="M282 564c62-120 162-220 282-282l94 94c12 12 30 16 44 10 48-16 100-24 152-24 24 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-44z" />
|
||||||
<glyph unicode="" glyph-name="invite" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
|
<glyph unicode="" glyph-name="invite" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
|
||||||
<glyph unicode="" glyph-name="add" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
|
<glyph unicode="" glyph-name="add" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
|
||||||
<glyph unicode="" glyph-name="bluetooth" d="M550 328l-80 82v-162zM470 776v-162l80 82zM670 696l-184-184 184-184-244-242h-42v324l-196-196-60 60 238 238-238 238 60 60 196-196v324h42zM834 738c40-64 62-142 62-222 0-84-24-160-66-226l-50 50c26 52 42 110 42 172s-16 120-42 172zM608 512l98 98c12-30 20-64 20-98s-8-70-20-100z" />
|
<glyph unicode="" glyph-name="bluetooth" d="M550 328l-80 82v-162zM470 776v-162l80 82zM670 696l-184-184 184-184-244-242h-42v324l-196-196-60 60 238 238-238 238 60 60 196-196v324h42zM834 738c40-64 62-142 62-222 0-84-24-160-66-226l-50 50c26 52 42 110 42 172s-16 120-42 172zM608 512l98 98c12-30 20-64 20-98s-8-70-20-100z" />
|
||||||
|
@ -21,8 +22,11 @@
|
||||||
<glyph unicode="" glyph-name="event_note" d="M598 426v-84h-300v84h300zM810 214v468h-596v-468h596zM810 896c46 0 86-40 86-86v-596c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h42v86h86v-86h340v86h86v-86h42zM726 598v-86h-428v86h428z" />
|
<glyph unicode="" glyph-name="event_note" d="M598 426v-84h-300v84h300zM810 214v468h-596v-468h596zM810 896c46 0 86-40 86-86v-596c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h42v86h86v-86h340v86h86v-86h42zM726 598v-86h-428v86h428z" />
|
||||||
<glyph unicode="" glyph-name="phone-talk" d="M640 512c0 70-58 128-128 128v86c118 0 214-96 214-214h-86zM810 512c0 166-132 298-298 298v86c212 0 384-172 384-384h-86zM854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24z" />
|
<glyph unicode="" glyph-name="phone-talk" d="M640 512c0 70-58 128-128 128v86c118 0 214-96 214-214h-86zM810 512c0 166-132 298-298 298v86c212 0 384-172 384-384h-86zM854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24z" />
|
||||||
<glyph unicode="" glyph-name="public" d="M764 282c56 60 90 142 90 230 0 142-88 266-214 316v-18c0-46-40-84-86-84h-84v-86c0-24-20-42-44-42h-84v-86h256c24 0 42-18 42-42v-128h42c38 0 70-26 82-60zM470 174v82c-46 0-86 40-86 86v42l-204 204c-6-24-10-50-10-76 0-174 132-318 300-338zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
<glyph unicode="" glyph-name="public" d="M764 282c56 60 90 142 90 230 0 142-88 266-214 316v-18c0-46-40-84-86-84h-84v-86c0-24-20-42-44-42h-84v-86h256c24 0 42-18 42-42v-128h42c38 0 70-26 82-60zM470 174v82c-46 0-86 40-86 86v42l-204 204c-6-24-10-50-10-76 0-174 132-318 300-338zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
||||||
|
<glyph unicode="" glyph-name="radio_button_unchecked" d="M512 170c188 0 342 154 342 342s-154 342-342 342-342-154-342-342 154-342 342-342zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
||||||
|
<glyph unicode="" glyph-name="radio_button_checked" d="M512 170c188 0 342 154 342 342s-154 342-342 342-342-154-342-342 154-342 342-342zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426zM512 726c118 0 214-96 214-214s-96-214-214-214-214 96-214 214 96 214 214 214z" />
|
||||||
<glyph unicode="" glyph-name="open_in_new" d="M598 896h298v-298h-86v152l-418-418-60 60 418 418h-152v86zM810 214v298h86v-298c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h298v-86h-298v-596h596z" />
|
<glyph unicode="" glyph-name="open_in_new" d="M598 896h298v-298h-86v152l-418-418-60 60 418 418h-152v86zM810 214v298h86v-298c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h298v-86h-298v-596h596z" />
|
||||||
<glyph unicode="" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
|
<glyph unicode="" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
|
||||||
|
<glyph unicode="" glyph-name="search" d="M406 426c106 0 192 86 192 192s-86 192-192 192-192-86-192-192 86-192 192-192zM662 426l212-212-64-64-212 212v34l-12 12c-48-42-112-66-180-66-154 0-278 122-278 276s124 278 278 278 276-124 276-278c0-68-24-132-66-180l12-12h34z" />
|
||||||
<glyph unicode="" glyph-name="AUD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM308.25 387.3h57.225l-87.675 252.525h-62.125l-87.675-252.525h53.025l19.425 60.2h88.725l19.075-60.2zM461.9 639.825h-52.85v-165.375c0-56 41.125-93.625 105.7-93.625 64.75 0 105.875 37.625 105.875 93.625v165.375h-52.85v-159.95c0-31.85-19.075-52.15-53.025-52.15-33.775 0-52.85 20.3-52.85 52.15v159.95zM682.225 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM735.075 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15zM243.5 587.325l-31.675-99.050h66.15l-31.325 99.050h-3.15z" />
|
<glyph unicode="" glyph-name="AUD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM308.25 387.3h57.225l-87.675 252.525h-62.125l-87.675-252.525h53.025l19.425 60.2h88.725l19.075-60.2zM461.9 639.825h-52.85v-165.375c0-56 41.125-93.625 105.7-93.625 64.75 0 105.875 37.625 105.875 93.625v165.375h-52.85v-159.95c0-31.85-19.075-52.15-53.025-52.15-33.775 0-52.85 20.3-52.85 52.15v159.95zM682.225 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM735.075 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15zM243.5 587.325l-31.675-99.050h66.15l-31.325 99.050h-3.15z" />
|
||||||
<glyph unicode="" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />
|
<glyph unicode="" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />
|
||||||
<glyph unicode="" glyph-name="kick" d="M512 810l284-426h-568zM214 298h596v-84h-596v84z" />
|
<glyph unicode="" glyph-name="kick" d="M512 810l284-426h-568zM214 298h596v-84h-596v84z" />
|
||||||
|
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
File diff suppressed because one or more lines are too long
|
@ -364,6 +364,16 @@
|
||||||
"title": "Share",
|
"title": "Share",
|
||||||
"tooltip": "Share link and dial-in info for this meeting"
|
"tooltip": "Share link and dial-in info for this meeting"
|
||||||
},
|
},
|
||||||
|
"inviteDialog": {
|
||||||
|
"alertOk": "Ok",
|
||||||
|
"alertText": "Failed to invite some participants.",
|
||||||
|
"alertTitle": "Invite",
|
||||||
|
"header": "Invite",
|
||||||
|
"searchCallOnlyPlaceholder": "Enter phone number",
|
||||||
|
"searchPeopleOnlyPlaceholder": "Search for participants",
|
||||||
|
"searchPlaceholder": "Participant or phone number",
|
||||||
|
"send": "Send"
|
||||||
|
},
|
||||||
"inlineDialogFailure": {
|
"inlineDialogFailure": {
|
||||||
"msg": "We stumbled a bit.",
|
"msg": "We stumbled a bit.",
|
||||||
"retry": "Try again",
|
"retry": "Try again",
|
||||||
|
|
|
@ -7,6 +7,11 @@ import type { ComponentType, Element } from 'react';
|
||||||
*/
|
*/
|
||||||
export type Item = {
|
export type Item = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The avatar URL or icon name.
|
||||||
|
*/
|
||||||
|
avatar: ?string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the color base of the avatar
|
* the color base of the avatar
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default class BackButton extends Component<Props> {
|
||||||
<Icon
|
<Icon
|
||||||
name = { 'arrow_back' }
|
name = { 'arrow_back' }
|
||||||
style = { [
|
style = { [
|
||||||
styles.headerButton,
|
styles.headerButtonIcon,
|
||||||
this.props.style
|
this.props.style
|
||||||
] } />
|
] } />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Text, TouchableOpacity } from 'react-native';
|
||||||
|
|
||||||
|
import { translate } from '../../../i18n';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the React {@code Component} props of {@link ForwardButton}
|
||||||
|
*/
|
||||||
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the nutton should be disabled.
|
||||||
|
*/
|
||||||
|
disabled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The i18n label key of the button.
|
||||||
|
*/
|
||||||
|
labelKey: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The action to be performed when the button is pressed.
|
||||||
|
*/
|
||||||
|
onPress: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An external style object passed to the component.
|
||||||
|
*/
|
||||||
|
style?: Object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to be used to translate i18n labels.
|
||||||
|
*/
|
||||||
|
t: Function
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component rendering a forward/next/action button.
|
||||||
|
*/
|
||||||
|
class ForwardButton extends Component<Props> {
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}, renders the button.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
accessibilityLabel = { 'Forward' }
|
||||||
|
disabled = { this.props.disabled }
|
||||||
|
onPress = { this.props.onPress } >
|
||||||
|
<Text
|
||||||
|
style = { [
|
||||||
|
styles.headerButtonText,
|
||||||
|
this.props.disabled && styles.disabledButtonText,
|
||||||
|
this.props.style
|
||||||
|
] }>
|
||||||
|
{ this.props.t(this.props.labelKey) }
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(ForwardButton);
|
|
@ -38,7 +38,7 @@ export default class Header extends Component<Props> {
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
static get buttonStyle(): Object {
|
static get buttonStyle(): Object {
|
||||||
return styles.headerButton;
|
return styles.headerButtonIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
|
|
||||||
import { translate } from '../../../i18n';
|
import { translate } from '../../../i18n';
|
||||||
|
|
||||||
|
@ -35,12 +35,16 @@ class HeaderLabel extends Component<Props> {
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Text
|
<View
|
||||||
style = { [
|
pointerEvents = 'box-none'
|
||||||
styles.headerText
|
style = { styles.headerTextWrapper }>
|
||||||
] }>
|
<Text
|
||||||
{ this.props.t(this.props.labelKey) }
|
style = { [
|
||||||
</Text>
|
styles.headerText
|
||||||
|
] }>
|
||||||
|
{ this.props.t(this.props.labelKey) }
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
export { default as AvatarListItem } from './AvatarListItem';
|
export { default as AvatarListItem } from './AvatarListItem';
|
||||||
export { default as BackButton } from './BackButton';
|
export { default as BackButton } from './BackButton';
|
||||||
export { default as Container } from './Container';
|
export { default as Container } from './Container';
|
||||||
|
export { default as ForwardButton } from './ForwardButton';
|
||||||
export { default as Header } from './Header';
|
export { default as Header } from './Header';
|
||||||
export { default as HeaderLabel } from './HeaderLabel';
|
export { default as HeaderLabel } from './HeaderLabel';
|
||||||
export { default as Link } from './Link';
|
export { default as Link } from './Link';
|
||||||
|
|
|
@ -19,16 +19,26 @@ export const SIDEBAR_WIDTH = 250;
|
||||||
export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
|
export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
|
||||||
|
|
||||||
const HEADER_STYLES = {
|
const HEADER_STYLES = {
|
||||||
|
|
||||||
|
disabledButtonText: {
|
||||||
|
opacity: 0.6
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Platform specific header button (e.g. back, menu, etc).
|
* Platform specific header button (e.g. back, menu, etc).
|
||||||
*/
|
*/
|
||||||
headerButton: {
|
headerButtonIcon: {
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
color: ColorPalette.white,
|
color: ColorPalette.white,
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
paddingRight: 22
|
paddingRight: 22
|
||||||
},
|
},
|
||||||
|
|
||||||
|
headerButtonText: {
|
||||||
|
color: ColorPalette.white,
|
||||||
|
fontSize: 20
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Style of the header overlay to cover the unsafe areas.
|
* Style of the header overlay to cover the unsafe areas.
|
||||||
*/
|
*/
|
||||||
|
@ -44,6 +54,14 @@ const HEADER_STYLES = {
|
||||||
fontSize: 20
|
fontSize: 20
|
||||||
},
|
},
|
||||||
|
|
||||||
|
headerTextWrapper: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
left: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The top-level element of a page.
|
* The top-level element of a page.
|
||||||
*/
|
*/
|
||||||
|
@ -63,7 +81,7 @@ const HEADER_STYLES = {
|
||||||
backgroundColor: HEADER_COLOR,
|
backgroundColor: HEADER_COLOR,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
height: HEADER_HEIGHT,
|
height: HEADER_HEIGHT,
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'space-between',
|
||||||
padding: HEADER_PADDING
|
padding: HEADER_PADDING
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
TileView
|
TileView
|
||||||
} from '../../../filmstrip';
|
} from '../../../filmstrip';
|
||||||
import { LargeVideo } from '../../../large-video';
|
import { LargeVideo } from '../../../large-video';
|
||||||
import { CalleeInfoContainer } from '../../../invite';
|
import { AddPeopleDialog, CalleeInfoContainer } from '../../../invite';
|
||||||
import { Captions } from '../../../subtitles';
|
import { Captions } from '../../../subtitles';
|
||||||
import { setToolboxVisible, Toolbox } from '../../../toolbox';
|
import { setToolboxVisible, Toolbox } from '../../../toolbox';
|
||||||
import { shouldDisplayTileView } from '../../../video-layout';
|
import { shouldDisplayTileView } from '../../../video-layout';
|
||||||
|
@ -255,6 +255,7 @@ class Conference extends Component<Props> {
|
||||||
translucent = { true } />
|
translucent = { true } />
|
||||||
|
|
||||||
<Chat />
|
<Chat />
|
||||||
|
<AddPeopleDialog />
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
* The LargeVideo is the lowermost stacking layer.
|
* The LargeVideo is the lowermost stacking layer.
|
||||||
|
|
|
@ -41,6 +41,16 @@ export const REMOVE_PENDING_INVITE_REQUESTS
|
||||||
*/
|
*/
|
||||||
export const SET_CALLEE_INFO_VISIBLE = Symbol('SET_CALLEE_INFO_VISIBLE');
|
export const SET_CALLEE_INFO_VISIBLE = Symbol('SET_CALLEE_INFO_VISIBLE');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of redux action which sets the invite dialog visible or invisible.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_INVITE_DIALOG_VISIBLE,
|
||||||
|
* visible: boolean
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_INVITE_DIALOG_VISIBLE = Symbol('SET_INVITE_DIALOG_VISIBLE');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the action which signals an error occurred while requesting dial-
|
* The type of the action which signals an error occurred while requesting dial-
|
||||||
* in numbers.
|
* in numbers.
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
BEGIN_ADD_PEOPLE,
|
BEGIN_ADD_PEOPLE,
|
||||||
REMOVE_PENDING_INVITE_REQUESTS,
|
REMOVE_PENDING_INVITE_REQUESTS,
|
||||||
SET_CALLEE_INFO_VISIBLE,
|
SET_CALLEE_INFO_VISIBLE,
|
||||||
|
SET_INVITE_DIALOG_VISIBLE,
|
||||||
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
||||||
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
@ -197,6 +198,22 @@ export function updateDialInNumbers() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the visibility of the invite dialog.
|
||||||
|
*
|
||||||
|
* @param {boolean} visible - The visibility to set.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_INVITE_DIALOG_VISIBLE,
|
||||||
|
* visible: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setAddPeopleDialogVisible(visible: boolean) {
|
||||||
|
return {
|
||||||
|
type: SET_INVITE_DIALOG_VISIBLE,
|
||||||
|
visible
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the visibility of {@code CalleeInfo}.
|
* Sets the visibility of {@code CalleeInfo}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -8,29 +8,21 @@ import { AbstractButton } from '../../base/toolbox';
|
||||||
import type { AbstractButtonProps } from '../../base/toolbox';
|
import type { AbstractButtonProps } from '../../base/toolbox';
|
||||||
import { beginShareRoom } from '../../share-room';
|
import { beginShareRoom } from '../../share-room';
|
||||||
|
|
||||||
import { beginAddPeople } from '../actions';
|
import { setAddPeopleDialogVisible } from '../actions';
|
||||||
import { isAddPeopleEnabled, isDialOutEnabled } from '../functions';
|
import { isAddPeopleEnabled, isDialOutEnabled } from '../functions';
|
||||||
|
|
||||||
type Props = AbstractButtonProps & {
|
type Props = AbstractButtonProps & {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the feature to directly invite people into the
|
* Whether or not the feature to invite people to join the
|
||||||
* conference is available.
|
* conference is available.
|
||||||
*/
|
*/
|
||||||
_addPeopleEnabled: boolean,
|
_addPeopleEnabled: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the feature to dial out to number to join the
|
* Opens the add people dialog.
|
||||||
* conference is available.
|
|
||||||
*/
|
*/
|
||||||
_dialOutEnabled: boolean,
|
_onOpenAddPeopleDialog: Function,
|
||||||
|
|
||||||
/**
|
|
||||||
* Launches native invite dialog.
|
|
||||||
*
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
_onAddPeople: Function,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Begins the UI procedure to share the conference/room URL.
|
* Begins the UI procedure to share the conference/room URL.
|
||||||
|
@ -54,12 +46,17 @@ class InviteButton extends AbstractButton<Props, *> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_handleClick() {
|
_handleClick() {
|
||||||
// FIXME: dispatch _onAddPeople here, when we have a dialog for it.
|
|
||||||
const {
|
const {
|
||||||
|
_addPeopleEnabled,
|
||||||
|
_onOpenAddPeopleDialog,
|
||||||
_onShareRoom
|
_onShareRoom
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
_onShareRoom();
|
if (_addPeopleEnabled) {
|
||||||
|
_onOpenAddPeopleDialog();
|
||||||
|
} else {
|
||||||
|
_onShareRoom();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,22 +66,23 @@ class InviteButton extends AbstractButton<Props, *> {
|
||||||
*
|
*
|
||||||
* @param {Function} dispatch - The redux action {@code dispatch} function.
|
* @param {Function} dispatch - The redux action {@code dispatch} function.
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _onAddPeople,
|
* _onOpenAddPeopleDialog,
|
||||||
* _onShareRoom
|
* _onShareRoom
|
||||||
* }}
|
* }}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function _mapDispatchToProps(dispatch: Dispatch<*>) {
|
function _mapDispatchToProps(dispatch: Dispatch<*>) {
|
||||||
return {
|
return {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches the add people dialog.
|
* Opens the add people dialog.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
* @type {Function}
|
* @type {Function}
|
||||||
*/
|
*/
|
||||||
_onAddPeople() {
|
_onOpenAddPeopleDialog() {
|
||||||
dispatch(beginAddPeople());
|
dispatch(setAddPeopleDialogVisible(true));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,25 +105,12 @@ function _mapDispatchToProps(dispatch: Dispatch<*>) {
|
||||||
* @param {Object} state - The redux store/state.
|
* @param {Object} state - The redux store/state.
|
||||||
* @private
|
* @private
|
||||||
* @returns {{
|
* @returns {{
|
||||||
|
* _addPeopleEnabled: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
/**
|
_addPeopleEnabled: isAddPeopleEnabled(state) || isDialOutEnabled(state)
|
||||||
* Whether or not the feature to directly invite people into the
|
|
||||||
* conference is available.
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
_addPeopleEnabled: isAddPeopleEnabled(state),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the feature to dial out to number to join the
|
|
||||||
* conference is available.
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
_dialOutEnabled: isDialOutEnabled(state)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { Component } from 'react';
|
||||||
|
|
||||||
|
import { createInviteDialogEvent, sendAnalytics } from '../../../analytics';
|
||||||
|
|
||||||
|
import { invite } from '../../actions';
|
||||||
|
import {
|
||||||
|
getInviteResultsForQuery,
|
||||||
|
getInviteTypeCounts,
|
||||||
|
isAddPeopleEnabled,
|
||||||
|
isDialOutEnabled
|
||||||
|
} from '../../functions';
|
||||||
|
|
||||||
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not to show Add People functionality.
|
||||||
|
*/
|
||||||
|
_addPeopleEnabled: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL for validating if a phone number can be called.
|
||||||
|
*/
|
||||||
|
_dialOutAuthUrl: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not to show Dial Out functionality.
|
||||||
|
*/
|
||||||
|
_dialOutEnabled: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The JWT token.
|
||||||
|
*/
|
||||||
|
_jwt: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query types used when searching people.
|
||||||
|
*/
|
||||||
|
_peopleSearchQueryTypes: Array<string>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL pointing to the service allowing for people search.
|
||||||
|
*/
|
||||||
|
_peopleSearchUrl: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Redux dispatch function.
|
||||||
|
*/
|
||||||
|
dispatch: Function
|
||||||
|
};
|
||||||
|
|
||||||
|
export type State = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicating that an error occurred when adding people to the call.
|
||||||
|
*/
|
||||||
|
addToCallError: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicating that we're currently adding the new people to the
|
||||||
|
* call.
|
||||||
|
*/
|
||||||
|
addToCallInProgress: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of invite items.
|
||||||
|
*/
|
||||||
|
inviteItems: Array<Object>,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements an abstract dialog to invite people to the conference.
|
||||||
|
*/
|
||||||
|
export default class AbstractAddPeopleDialog<P: Props, S: State>
|
||||||
|
extends Component<P, S> {
|
||||||
|
/**
|
||||||
|
* Constructor of the component.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
constructor(props: P) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._query = this._query.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invite people and numbers to the conference. The logic works by inviting
|
||||||
|
* numbers, people/rooms, and videosipgw in parallel. All invitees are
|
||||||
|
* stored in an array. As each invite succeeds, the invitee is removed
|
||||||
|
* from the array. After all invites finish, close the modal if there are
|
||||||
|
* no invites left to send. If any are left, that means an invite failed
|
||||||
|
* and an error state should display.
|
||||||
|
*
|
||||||
|
* @param {Array<Object>} invitees - The items to be invited.
|
||||||
|
* @returns {Promise<Array<Object>>}
|
||||||
|
*/
|
||||||
|
_invite(invitees) {
|
||||||
|
const inviteTypeCounts = getInviteTypeCounts(invitees);
|
||||||
|
|
||||||
|
sendAnalytics(createInviteDialogEvent(
|
||||||
|
'clicked', 'inviteButton', {
|
||||||
|
...inviteTypeCounts,
|
||||||
|
inviteAllowed: this._isAddDisabled()
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (this._isAddDisabled()) {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
addToCallInProgress: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
|
return dispatch(invite(invitees))
|
||||||
|
.then(invitesLeftToSend => {
|
||||||
|
this.setState({
|
||||||
|
addToCallInProgress: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// If any invites are left that means something failed to send
|
||||||
|
// so treat it as an error.
|
||||||
|
if (invitesLeftToSend.length) {
|
||||||
|
const erroredInviteTypeCounts
|
||||||
|
= getInviteTypeCounts(invitesLeftToSend);
|
||||||
|
|
||||||
|
logger.error(`${invitesLeftToSend.length} invites failed`,
|
||||||
|
erroredInviteTypeCounts);
|
||||||
|
|
||||||
|
sendAnalytics(createInviteDialogEvent(
|
||||||
|
'error', 'invite', {
|
||||||
|
...erroredInviteTypeCounts
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
addToCallError: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return invitesLeftToSend;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the Add button should be disabled.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {boolean} - True to indicate that the Add button should
|
||||||
|
* be disabled, false otherwise.
|
||||||
|
*/
|
||||||
|
_isAddDisabled() {
|
||||||
|
return !this.state.inviteItems.length
|
||||||
|
|| this.state.addToCallInProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
_query: (string) => Promise<Array<Object>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a people and phone number search request.
|
||||||
|
*
|
||||||
|
* @param {string} query - The search text.
|
||||||
|
* @private
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
_query(query = '') {
|
||||||
|
const {
|
||||||
|
_addPeopleEnabled: addPeopleEnabled,
|
||||||
|
_dialOutAuthUrl: dialOutAuthUrl,
|
||||||
|
_dialOutEnabled: dialOutEnabled,
|
||||||
|
_jwt: jwt,
|
||||||
|
_peopleSearchQueryTypes: peopleSearchQueryTypes,
|
||||||
|
_peopleSearchUrl: peopleSearchUrl
|
||||||
|
} = this.props;
|
||||||
|
const options = {
|
||||||
|
addPeopleEnabled,
|
||||||
|
dialOutAuthUrl,
|
||||||
|
dialOutEnabled,
|
||||||
|
jwt,
|
||||||
|
peopleSearchQueryTypes,
|
||||||
|
peopleSearchUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
return getInviteResultsForQuery(query, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the props of this component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _addPeopleEnabled: boolean,
|
||||||
|
* _dialOutAuthUrl: string,
|
||||||
|
* _dialOutEnabled: boolean,
|
||||||
|
* _jwt: string,
|
||||||
|
* _peopleSearchQueryTypes: Array<string>,
|
||||||
|
* _peopleSearchUrl: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function _mapStateToProps(state: Object) {
|
||||||
|
const {
|
||||||
|
dialOutAuthUrl,
|
||||||
|
peopleSearchQueryTypes,
|
||||||
|
peopleSearchUrl
|
||||||
|
} = state['features/base/config'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
_addPeopleEnabled: isAddPeopleEnabled(state),
|
||||||
|
_dialOutAuthUrl: dialOutAuthUrl,
|
||||||
|
_dialOutEnabled: isDialOutEnabled(state),
|
||||||
|
_jwt: state['features/base/jwt'].jwt,
|
||||||
|
_peopleSearchQueryTypes: peopleSearchQueryTypes,
|
||||||
|
_peopleSearchUrl: peopleSearchUrl
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export * from './native';
|
|
@ -0,0 +1,3 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export * from './web';
|
|
@ -0,0 +1,469 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
Alert,
|
||||||
|
FlatList,
|
||||||
|
SafeAreaView,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
|
View
|
||||||
|
} from 'react-native';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { Icon } from '../../../../base/font-icons';
|
||||||
|
import { translate } from '../../../../base/i18n';
|
||||||
|
import {
|
||||||
|
AvatarListItem,
|
||||||
|
BackButton,
|
||||||
|
ForwardButton,
|
||||||
|
Header,
|
||||||
|
HeaderLabel,
|
||||||
|
Modal,
|
||||||
|
type Item
|
||||||
|
} from '../../../../base/react';
|
||||||
|
|
||||||
|
import { setAddPeopleDialogVisible } from '../../../actions';
|
||||||
|
|
||||||
|
import AbstractAddPeopleDialog, {
|
||||||
|
type Props as AbstractProps,
|
||||||
|
type State as AbstractState,
|
||||||
|
_mapStateToProps as _abstractMapStateToProps
|
||||||
|
} from '../AbstractAddPeopleDialog';
|
||||||
|
|
||||||
|
import styles, {
|
||||||
|
AVATAR_SIZE,
|
||||||
|
DARK_GREY
|
||||||
|
} from './styles';
|
||||||
|
|
||||||
|
type Props = AbstractProps & {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the invite dialog should be open, false otherwise.
|
||||||
|
*/
|
||||||
|
_isVisible: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function used to translate i18n labels.
|
||||||
|
*/
|
||||||
|
t: Function
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = AbstractState & {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if a search is in progress, false otherwise.
|
||||||
|
*/
|
||||||
|
searchInprogress: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of items that are selectable on this dialog. This is usually
|
||||||
|
* populated by an async search.
|
||||||
|
*/
|
||||||
|
selectableItems: Array<Object>
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a special dialog to invite people from a directory service.
|
||||||
|
*/
|
||||||
|
class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
||||||
|
/**
|
||||||
|
* Default state object to reset the state to when needed.
|
||||||
|
*/
|
||||||
|
defaultState = {
|
||||||
|
addToCallError: false,
|
||||||
|
addToCallInProgress: false,
|
||||||
|
inviteItems: [],
|
||||||
|
searchInprogress: false,
|
||||||
|
selectableItems: []
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref of the search field.
|
||||||
|
*/
|
||||||
|
inputFieldRef: ?TextInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TimeoutID to delay the search for the time the user is probably typing.
|
||||||
|
*/
|
||||||
|
searchTimeout: TimeoutID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrustor of the component.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = this.defaultState;
|
||||||
|
|
||||||
|
this._keyExtractor = this._keyExtractor.bind(this);
|
||||||
|
this._renderItem = this._renderItem.bind(this);
|
||||||
|
this._renderSeparator = this._renderSeparator.bind(this);
|
||||||
|
this._onCloseAddPeopleDialog = this._onCloseAddPeopleDialog.bind(this);
|
||||||
|
this._onInvite = this._onInvite.bind(this);
|
||||||
|
this._onPressItem = this._onPressItem.bind(this);
|
||||||
|
this._onTypeQuery = this._onTypeQuery.bind(this);
|
||||||
|
this._setFieldRef = this._setFieldRef.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@code Component#componentDidUpdate}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps._isVisible !== this.props._isVisible) {
|
||||||
|
// Clear state
|
||||||
|
this._clearState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@code Component#render}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
_addPeopleEnabled,
|
||||||
|
_dialOutEnabled
|
||||||
|
} = this.props;
|
||||||
|
const { inviteItems } = this.state;
|
||||||
|
|
||||||
|
let placeholderKey = 'searchPlaceholder';
|
||||||
|
|
||||||
|
if (!_addPeopleEnabled) {
|
||||||
|
placeholderKey = 'searchCallOnlyPlaceholder';
|
||||||
|
} else if (!_dialOutEnabled) {
|
||||||
|
placeholderKey = 'searchPeopleOnlyPlaceholder';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
onRequestClose = { this._onCloseAddPeopleDialog }
|
||||||
|
visible = { this.props._isVisible }>
|
||||||
|
<Header>
|
||||||
|
<BackButton onPress = { this._onCloseAddPeopleDialog } />
|
||||||
|
<HeaderLabel labelKey = 'inviteDialog.header' />
|
||||||
|
<ForwardButton
|
||||||
|
disabled = { this._isAddDisabled() }
|
||||||
|
labelKey = 'inviteDialog.send'
|
||||||
|
onPress = { this._onInvite } />
|
||||||
|
</Header>
|
||||||
|
<SafeAreaView style = { styles.dialogWrapper }>
|
||||||
|
<View
|
||||||
|
style = { styles.searchFieldWrapper }>
|
||||||
|
<View style = { styles.searchIconWrapper }>
|
||||||
|
{ this.state.searchInprogress
|
||||||
|
? <ActivityIndicator
|
||||||
|
color = { DARK_GREY }
|
||||||
|
size = 'small' />
|
||||||
|
: <Icon
|
||||||
|
name = { 'search' }
|
||||||
|
style = { styles.searchIcon } />}
|
||||||
|
</View>
|
||||||
|
<TextInput
|
||||||
|
autoCorrect = { false }
|
||||||
|
editable = { !this.state.searchInprogress }
|
||||||
|
onChangeText = { this._onTypeQuery }
|
||||||
|
placeholder = {
|
||||||
|
this.props.t(`inviteDialog.${placeholderKey}`)
|
||||||
|
}
|
||||||
|
ref = { this._setFieldRef }
|
||||||
|
style = { styles.searchField } />
|
||||||
|
</View>
|
||||||
|
<FlatList
|
||||||
|
ItemSeparatorComponent = { this._renderSeparator }
|
||||||
|
data = { this.state.selectableItems }
|
||||||
|
extraData = { inviteItems }
|
||||||
|
keyExtractor = { this._keyExtractor }
|
||||||
|
renderItem = { this._renderItem }
|
||||||
|
style = { styles.resultList } />
|
||||||
|
</SafeAreaView>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the dialog content.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_clearState() {
|
||||||
|
this.setState(this.defaultState);
|
||||||
|
}
|
||||||
|
|
||||||
|
_invite: Array<Object> => Promise<Array<Object>>
|
||||||
|
|
||||||
|
_isAddDisabled: () => boolean;
|
||||||
|
|
||||||
|
_keyExtractor: Object => string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key extractor for the flatlist.
|
||||||
|
*
|
||||||
|
* @param {Object} item - The flatlist item that we need the key to be
|
||||||
|
* generated for.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
_keyExtractor(item) {
|
||||||
|
return item.type === 'user' ? item.user_id : item.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onCloseAddPeopleDialog: () => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the dialog.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onCloseAddPeopleDialog() {
|
||||||
|
this.props.dispatch(setAddPeopleDialogVisible(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
_onInvite: () => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invites the selected entries.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onInvite() {
|
||||||
|
this._invite(this.state.inviteItems)
|
||||||
|
.then(invitesLeftToSend => {
|
||||||
|
if (invitesLeftToSend.length) {
|
||||||
|
this.setState({
|
||||||
|
inviteItems: invitesLeftToSend
|
||||||
|
});
|
||||||
|
this._showFailedInviteAlert();
|
||||||
|
} else {
|
||||||
|
this._onCloseAddPeopleDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPressItem: Item => Function
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to preapre a callback for the onPress event of the touchable.
|
||||||
|
*
|
||||||
|
* @param {Item} item - The item on which onPress was invoked.
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
_onPressItem(item) {
|
||||||
|
return () => {
|
||||||
|
const { inviteItems } = this.state;
|
||||||
|
const finderKey = item.type === 'phone' ? 'number' : 'user_id';
|
||||||
|
|
||||||
|
if (inviteItems.find(
|
||||||
|
_.matchesProperty(finderKey, item[finderKey]))) {
|
||||||
|
// Item is already selected, need to unselect it.
|
||||||
|
this.setState({
|
||||||
|
inviteItems: inviteItems.filter(
|
||||||
|
element => item[finderKey] !== element[finderKey])
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Item is not selected yet, need to add to the list.
|
||||||
|
this.setState({
|
||||||
|
inviteItems: _.orderBy(
|
||||||
|
inviteItems.concat(item), [ 'name' ], [ 'asc' ])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_onTypeQuery: string => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the typing event of the text field on the dialog and performs the
|
||||||
|
* search.
|
||||||
|
*
|
||||||
|
* @param {string} query - The query that is typed in the field.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onTypeQuery(query) {
|
||||||
|
clearTimeout(this.searchTimeout);
|
||||||
|
this.searchTimeout = setTimeout(() => {
|
||||||
|
this.setState({
|
||||||
|
searchInprogress: true
|
||||||
|
}, () => {
|
||||||
|
this._performSearch(query);
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the actual search.
|
||||||
|
*
|
||||||
|
* @param {string} query - The query to search for.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_performSearch(query) {
|
||||||
|
this._query(query).then(results => {
|
||||||
|
const { inviteItems } = this.state;
|
||||||
|
|
||||||
|
let selectableItems = results.filter(result => {
|
||||||
|
switch (result.type) {
|
||||||
|
case 'phone':
|
||||||
|
return result.allowed && result.number
|
||||||
|
&& !inviteItems.find(
|
||||||
|
_.matchesProperty('number', result.number));
|
||||||
|
case 'user':
|
||||||
|
return result.user_id && !inviteItems.find(
|
||||||
|
_.matchesProperty('user_id', result.user_id));
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
selectableItems
|
||||||
|
= _.orderBy(
|
||||||
|
this.state.inviteItems.concat(selectableItems),
|
||||||
|
[ 'name' ], [ 'asc' ]);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectableItems
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.setState({
|
||||||
|
searchInprogress: false
|
||||||
|
}, () => {
|
||||||
|
this.inputFieldRef && this.inputFieldRef.focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_query: (string) => Promise<Array<Object>>;
|
||||||
|
|
||||||
|
_renderItem: Object => ?React$Element<*>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a single item in the {@code FlatList}.
|
||||||
|
*
|
||||||
|
* @param {Object} flatListItem - An item of the data array of the
|
||||||
|
* {@code FlatList}.
|
||||||
|
* @param {number} index - The index of the currently rendered item.
|
||||||
|
* @returns {?React$Element<*>}
|
||||||
|
*/
|
||||||
|
_renderItem(flatListItem, index) {
|
||||||
|
const { item } = flatListItem;
|
||||||
|
const { inviteItems } = this.state;
|
||||||
|
let selected = false;
|
||||||
|
let renderableItem;
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
case 'phone':
|
||||||
|
selected
|
||||||
|
= inviteItems.find(_.matchesProperty('number', item.number));
|
||||||
|
renderableItem = {
|
||||||
|
avatar: 'phone',
|
||||||
|
key: item.number,
|
||||||
|
title: item.number
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'user':
|
||||||
|
selected
|
||||||
|
= inviteItems.find(_.matchesProperty('user_id', item.user_id));
|
||||||
|
renderableItem = {
|
||||||
|
avatar: item.avatar,
|
||||||
|
key: item.user_id,
|
||||||
|
title: item.name
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress = { this._onPressItem(item) } >
|
||||||
|
<View
|
||||||
|
pointerEvents = 'box-only'
|
||||||
|
style = { styles.itemWrapper }>
|
||||||
|
<Icon
|
||||||
|
name = { selected
|
||||||
|
? 'radio_button_checked'
|
||||||
|
: 'radio_button_unchecked' }
|
||||||
|
style = { styles.radioButton } />
|
||||||
|
<AvatarListItem
|
||||||
|
avatarSize = { AVATAR_SIZE }
|
||||||
|
avatarStyle = { styles.avatar }
|
||||||
|
avatarTextStyle = { styles.avatarText }
|
||||||
|
item = { renderableItem }
|
||||||
|
key = { index }
|
||||||
|
linesStyle = { styles.itemLinesStyle }
|
||||||
|
titleStyle = { styles.itemText } />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderSeparator: () => ?React$Element<*>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the item separator.
|
||||||
|
*
|
||||||
|
* @returns {?React$Element<*>}
|
||||||
|
*/
|
||||||
|
_renderSeparator() {
|
||||||
|
return (
|
||||||
|
<View style = { styles.separator } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_setFieldRef: ?TextInput => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a reference to the input field for later use.
|
||||||
|
*
|
||||||
|
* @param {?TextInput} input - The reference to the input field.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_setFieldRef(input) {
|
||||||
|
this.inputFieldRef = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows an alert telling the user that some invitees were failed to be
|
||||||
|
* invited.
|
||||||
|
*
|
||||||
|
* NOTE: We're using an Alert here because we're on a modal and it makes
|
||||||
|
* using our dialogs a tad more difficult.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_showFailedInviteAlert() {
|
||||||
|
const { t } = this.props;
|
||||||
|
|
||||||
|
Alert.alert(
|
||||||
|
t('inviteDialog.alertTitle'),
|
||||||
|
t('inviteDialog.alertText'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t('inviteDialog.alertOk')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps part of the Redux state to the props of this component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @returns {{
|
||||||
|
* _isVisible: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state: Object) {
|
||||||
|
return {
|
||||||
|
..._abstractMapStateToProps(state),
|
||||||
|
_isVisible: state['features/invite'].inviteDialogVisible
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(AddPeopleDialog));
|
|
@ -0,0 +1,3 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export { default as AddPeopleDialog } from './AddPeopleDialog';
|
|
@ -0,0 +1,91 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { ColorPalette } from '../../../../base/styles';
|
||||||
|
|
||||||
|
export const AVATAR_SIZE = 40;
|
||||||
|
export const DARK_GREY = 'rgb(28, 32, 37)';
|
||||||
|
export const LIGHT_GREY = 'rgb(209, 219, 232)';
|
||||||
|
export const ICON_SIZE = 15;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
avatar: {
|
||||||
|
backgroundColor: LIGHT_GREY
|
||||||
|
},
|
||||||
|
|
||||||
|
avatarText: {
|
||||||
|
color: 'rgb(28, 32, 37)',
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
|
||||||
|
dialogWrapper: {
|
||||||
|
alignItems: 'stretch',
|
||||||
|
backgroundColor: ColorPalette.white,
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
|
||||||
|
itemLinesStyle: {
|
||||||
|
color: 'rgb(118, 136, 152)',
|
||||||
|
fontSize: 13
|
||||||
|
},
|
||||||
|
|
||||||
|
itemText: {
|
||||||
|
color: DARK_GREY,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'normal'
|
||||||
|
},
|
||||||
|
|
||||||
|
itemWrapper: {
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingLeft: 5
|
||||||
|
},
|
||||||
|
|
||||||
|
radioButton: {
|
||||||
|
color: DARK_GREY,
|
||||||
|
fontSize: 16,
|
||||||
|
padding: 2
|
||||||
|
},
|
||||||
|
|
||||||
|
resultList: {
|
||||||
|
padding: 5
|
||||||
|
},
|
||||||
|
|
||||||
|
searchField: {
|
||||||
|
backgroundColor: 'rgb(240, 243, 247)',
|
||||||
|
borderBottomRightRadius: 10,
|
||||||
|
borderTopRightRadius: 10,
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 17,
|
||||||
|
paddingVertical: 7
|
||||||
|
},
|
||||||
|
|
||||||
|
separator: {
|
||||||
|
borderBottomColor: LIGHT_GREY,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
marginLeft: 85
|
||||||
|
},
|
||||||
|
|
||||||
|
searchFieldWrapper: {
|
||||||
|
alignItems: 'stretch',
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 52,
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
paddingVertical: 8
|
||||||
|
},
|
||||||
|
|
||||||
|
searchIcon: {
|
||||||
|
color: DARK_GREY,
|
||||||
|
fontSize: ICON_SIZE
|
||||||
|
},
|
||||||
|
|
||||||
|
searchIconWrapper: {
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'rgb(240, 243, 247)',
|
||||||
|
borderBottomLeftRadius: 10,
|
||||||
|
borderTopLeftRadius: 10,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: ICON_SIZE + 16
|
||||||
|
}
|
||||||
|
};
|
|
@ -2,26 +2,27 @@
|
||||||
|
|
||||||
import Avatar from '@atlaskit/avatar';
|
import Avatar from '@atlaskit/avatar';
|
||||||
import InlineMessage from '@atlaskit/inline-message';
|
import InlineMessage from '@atlaskit/inline-message';
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { createInviteDialogEvent, sendAnalytics } from '../../analytics';
|
import { createInviteDialogEvent, sendAnalytics } from '../../../../analytics';
|
||||||
import { Dialog, hideDialog } from '../../base/dialog';
|
import { Dialog, hideDialog } from '../../../../base/dialog';
|
||||||
import { translate, translateToHTML } from '../../base/i18n';
|
import { translate, translateToHTML } from '../../../../base/i18n';
|
||||||
import { getLocalParticipant } from '../../base/participants';
|
import { getLocalParticipant } from '../../../../base/participants';
|
||||||
import { MultiSelectAutocomplete } from '../../base/react';
|
import { MultiSelectAutocomplete } from '../../../../base/react';
|
||||||
|
|
||||||
import { invite } from '../actions';
|
import AbstractAddPeopleDialog, {
|
||||||
import { getInviteResultsForQuery, getInviteTypeCounts } from '../functions';
|
type Props as AbstractProps,
|
||||||
|
type State,
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
_mapStateToProps as _abstractMapStateToProps
|
||||||
|
} from '../AbstractAddPeopleDialog';
|
||||||
|
|
||||||
declare var interfaceConfig: Object;
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of {@link AddPeopleDialog}.
|
* The type of the React {@code Component} props of {@link AddPeopleDialog}.
|
||||||
*/
|
*/
|
||||||
type Props = {
|
type Props = AbstractProps & {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link JitsiMeetConference} which will be used to invite "room"
|
* The {@link JitsiMeetConference} which will be used to invite "room"
|
||||||
|
@ -29,41 +30,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_conference: Object,
|
_conference: Object,
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL for validating if a phone number can be called.
|
|
||||||
*/
|
|
||||||
_dialOutAuthUrl: string,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to show a footer text after the search results as a last element.
|
* Whether to show a footer text after the search results as a last element.
|
||||||
*/
|
*/
|
||||||
_footerTextEnabled: boolean,
|
_footerTextEnabled: boolean,
|
||||||
|
|
||||||
/**
|
|
||||||
* The JWT token.
|
|
||||||
*/
|
|
||||||
_jwt: string,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The query types used when searching people.
|
|
||||||
*/
|
|
||||||
_peopleSearchQueryTypes: Array<string>,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL pointing to the service allowing for people search.
|
|
||||||
*/
|
|
||||||
_peopleSearchUrl: string,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not to show Add People functionality.
|
|
||||||
*/
|
|
||||||
addPeopleEnabled: boolean,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not to show Dial Out functionality.
|
|
||||||
*/
|
|
||||||
dialOutEnabled: boolean,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The redux {@code dispatch} function.
|
* The redux {@code dispatch} function.
|
||||||
*/
|
*/
|
||||||
|
@ -75,32 +46,10 @@ type Props = {
|
||||||
t: Function,
|
t: Function,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the React {@code Component} state of {@link AddPeopleDialog}.
|
|
||||||
*/
|
|
||||||
type State = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicating that an error occurred when adding people to the call.
|
|
||||||
*/
|
|
||||||
addToCallError: boolean,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicating that we're currently adding the new people to the
|
|
||||||
* call.
|
|
||||||
*/
|
|
||||||
addToCallInProgress: boolean,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of invite items.
|
|
||||||
*/
|
|
||||||
inviteItems: Array<Object>
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The dialog that allows to invite people to the call.
|
* The dialog that allows to invite people to the call.
|
||||||
*/
|
*/
|
||||||
class AddPeopleDialog extends Component<Props, State> {
|
class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
||||||
_multiselect = null;
|
_multiselect = null;
|
||||||
|
|
||||||
_resourceClient: Object;
|
_resourceClient: Object;
|
||||||
|
@ -121,12 +70,10 @@ class AddPeopleDialog extends Component<Props, State> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
// Bind event handlers so they are only bound once per instance.
|
// Bind event handlers so they are only bound once per instance.
|
||||||
this._isAddDisabled = this._isAddDisabled.bind(this);
|
|
||||||
this._onItemSelected = this._onItemSelected.bind(this);
|
this._onItemSelected = this._onItemSelected.bind(this);
|
||||||
this._onSelectionChange = this._onSelectionChange.bind(this);
|
this._onSelectionChange = this._onSelectionChange.bind(this);
|
||||||
this._onSubmit = this._onSubmit.bind(this);
|
this._onSubmit = this._onSubmit.bind(this);
|
||||||
this._parseQueryResults = this._parseQueryResults.bind(this);
|
this._parseQueryResults = this._parseQueryResults.bind(this);
|
||||||
this._query = this._query.bind(this);
|
|
||||||
this._setMultiSelectElement = this._setMultiSelectElement.bind(this);
|
this._setMultiSelectElement = this._setMultiSelectElement.bind(this);
|
||||||
|
|
||||||
this._resourceClient = {
|
this._resourceClient = {
|
||||||
|
@ -183,25 +130,27 @@ class AddPeopleDialog extends Component<Props, State> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { _footerTextEnabled,
|
const {
|
||||||
addPeopleEnabled,
|
_addPeopleEnabled,
|
||||||
dialOutEnabled,
|
_dialOutEnabled,
|
||||||
t } = this.props;
|
_footerTextEnabled,
|
||||||
|
t
|
||||||
|
} = this.props;
|
||||||
let isMultiSelectDisabled = this.state.addToCallInProgress || false;
|
let isMultiSelectDisabled = this.state.addToCallInProgress || false;
|
||||||
let placeholder;
|
let placeholder;
|
||||||
let loadingMessage;
|
let loadingMessage;
|
||||||
let noMatches;
|
let noMatches;
|
||||||
let footerText;
|
let footerText;
|
||||||
|
|
||||||
if (addPeopleEnabled && dialOutEnabled) {
|
if (_addPeopleEnabled && _dialOutEnabled) {
|
||||||
loadingMessage = 'addPeople.loading';
|
loadingMessage = 'addPeople.loading';
|
||||||
noMatches = 'addPeople.noResults';
|
noMatches = 'addPeople.noResults';
|
||||||
placeholder = 'addPeople.searchPeopleAndNumbers';
|
placeholder = 'addPeople.searchPeopleAndNumbers';
|
||||||
} else if (addPeopleEnabled) {
|
} else if (_addPeopleEnabled) {
|
||||||
loadingMessage = 'addPeople.loadingPeople';
|
loadingMessage = 'addPeople.loadingPeople';
|
||||||
noMatches = 'addPeople.noResults';
|
noMatches = 'addPeople.noResults';
|
||||||
placeholder = 'addPeople.searchPeople';
|
placeholder = 'addPeople.searchPeople';
|
||||||
} else if (dialOutEnabled) {
|
} else if (_dialOutEnabled) {
|
||||||
loadingMessage = 'addPeople.loadingNumber';
|
loadingMessage = 'addPeople.loadingNumber';
|
||||||
noMatches = 'addPeople.noValidNumbers';
|
noMatches = 'addPeople.noValidNumbers';
|
||||||
placeholder = 'addPeople.searchNumbers';
|
placeholder = 'addPeople.searchNumbers';
|
||||||
|
@ -250,19 +199,9 @@ class AddPeopleDialog extends Component<Props, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_isAddDisabled: () => boolean;
|
_invite: Array<Object> => Promise<*>
|
||||||
|
|
||||||
/**
|
_isAddDisabled: () => boolean;
|
||||||
* Indicates if the Add button should be disabled.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {boolean} - True to indicate that the Add button should
|
|
||||||
* be disabled, false otherwise.
|
|
||||||
*/
|
|
||||||
_isAddDisabled() {
|
|
||||||
return !this.state.inviteItems.length
|
|
||||||
|| this.state.addToCallInProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onItemSelected: (Object) => Object;
|
_onItemSelected: (Object) => Object;
|
||||||
|
|
||||||
|
@ -300,12 +239,7 @@ class AddPeopleDialog extends Component<Props, State> {
|
||||||
_onSubmit: () => void;
|
_onSubmit: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invite people and numbers to the conference. The logic works by inviting
|
* Submits the selection for inviting.
|
||||||
* numbers, people/rooms, and videosipgw in parallel. All invitees are
|
|
||||||
* stored in an array. As each invite succeeds, the invitee is removed
|
|
||||||
* from the array. After all invites finish, close the modal if there are
|
|
||||||
* no invites left to send. If any are left, that means an invite failed
|
|
||||||
* and an error state should display.
|
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
@ -313,45 +247,10 @@ class AddPeopleDialog extends Component<Props, State> {
|
||||||
_onSubmit() {
|
_onSubmit() {
|
||||||
const { inviteItems } = this.state;
|
const { inviteItems } = this.state;
|
||||||
const invitees = inviteItems.map(({ item }) => item);
|
const invitees = inviteItems.map(({ item }) => item);
|
||||||
const inviteTypeCounts = getInviteTypeCounts(invitees);
|
|
||||||
|
|
||||||
sendAnalytics(createInviteDialogEvent(
|
this._invite(invitees)
|
||||||
'clicked', 'inviteButton', {
|
|
||||||
...inviteTypeCounts,
|
|
||||||
inviteAllowed: this._isAddDisabled()
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (this._isAddDisabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
addToCallInProgress: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
|
|
||||||
dispatch(invite(invitees))
|
|
||||||
.then(invitesLeftToSend => {
|
.then(invitesLeftToSend => {
|
||||||
// If any invites are left that means something failed to send
|
|
||||||
// so treat it as an error.
|
|
||||||
if (invitesLeftToSend.length) {
|
if (invitesLeftToSend.length) {
|
||||||
const erroredInviteTypeCounts
|
|
||||||
= getInviteTypeCounts(invitesLeftToSend);
|
|
||||||
|
|
||||||
logger.error(`${invitesLeftToSend.length} invites failed`,
|
|
||||||
erroredInviteTypeCounts);
|
|
||||||
|
|
||||||
sendAnalytics(createInviteDialogEvent(
|
|
||||||
'error', 'invite', {
|
|
||||||
...erroredInviteTypeCounts
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
addToCallInProgress: false,
|
|
||||||
addToCallError: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const unsentInviteIDs
|
const unsentInviteIDs
|
||||||
= invitesLeftToSend.map(invitee =>
|
= invitesLeftToSend.map(invitee =>
|
||||||
invitee.id || invitee.number);
|
invitee.id || invitee.number);
|
||||||
|
@ -362,15 +261,9 @@ class AddPeopleDialog extends Component<Props, State> {
|
||||||
if (this._multiselect) {
|
if (this._multiselect) {
|
||||||
this._multiselect.setSelectedItems(itemsToSelect);
|
this._multiselect.setSelectedItems(itemsToSelect);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return;
|
this.props.dispatch(hideDialog());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
|
||||||
addToCallInProgress: false
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(hideDialog());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,34 +335,6 @@ class AddPeopleDialog extends Component<Props, State> {
|
||||||
|
|
||||||
_query: (string) => Promise<Array<Object>>;
|
_query: (string) => Promise<Array<Object>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a people and phone number search request.
|
|
||||||
*
|
|
||||||
* @param {string} query - The search text.
|
|
||||||
* @private
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
_query(query = '') {
|
|
||||||
const {
|
|
||||||
addPeopleEnabled,
|
|
||||||
dialOutEnabled,
|
|
||||||
_dialOutAuthUrl,
|
|
||||||
_jwt,
|
|
||||||
_peopleSearchQueryTypes,
|
|
||||||
_peopleSearchUrl
|
|
||||||
} = this.props;
|
|
||||||
const options = {
|
|
||||||
dialOutAuthUrl: _dialOutAuthUrl,
|
|
||||||
addPeopleEnabled,
|
|
||||||
dialOutEnabled,
|
|
||||||
jwt: _jwt,
|
|
||||||
peopleSearchQueryTypes: _peopleSearchQueryTypes,
|
|
||||||
peopleSearchUrl: _peopleSearchUrl
|
|
||||||
};
|
|
||||||
|
|
||||||
return getInviteResultsForQuery(query, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the error message if the add doesn't succeed.
|
* Renders the error message if the add doesn't succeed.
|
||||||
*
|
*
|
||||||
|
@ -557,10 +422,7 @@ class AddPeopleDialog extends Component<Props, State> {
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
const {
|
const {
|
||||||
dialOutAuthUrl,
|
enableFeaturesBasedOnToken
|
||||||
enableFeaturesBasedOnToken,
|
|
||||||
peopleSearchQueryTypes,
|
|
||||||
peopleSearchUrl
|
|
||||||
} = state['features/base/config'];
|
} = state['features/base/config'];
|
||||||
let footerTextEnabled = false;
|
let footerTextEnabled = false;
|
||||||
|
|
||||||
|
@ -573,11 +435,8 @@ function _mapStateToProps(state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_dialOutAuthUrl: dialOutAuthUrl,
|
..._abstractMapStateToProps(state),
|
||||||
_footerTextEnabled: footerTextEnabled,
|
_footerTextEnabled: footerTextEnabled
|
||||||
_jwt: state['features/base/jwt'].jwt,
|
|
||||||
_peopleSearchQueryTypes: peopleSearchQueryTypes,
|
|
||||||
_peopleSearchUrl: peopleSearchUrl
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export { default as AddPeopleDialog } from './AddPeopleDialog';
|
|
@ -1,4 +1,6 @@
|
||||||
export { default as AddPeopleDialog } from './AddPeopleDialog';
|
// @flow
|
||||||
|
|
||||||
|
export * from './add-people-dialog';
|
||||||
export { DialInSummary } from './dial-in-summary';
|
export { DialInSummary } from './dial-in-summary';
|
||||||
export { default as InfoDialogButton } from './InfoDialogButton';
|
export { default as InfoDialogButton } from './InfoDialogButton';
|
||||||
export { default as InviteButton } from './InviteButton';
|
export { default as InviteButton } from './InviteButton';
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
|
||||||
import { BEGIN_ADD_PEOPLE } from './actionTypes';
|
import { BEGIN_ADD_PEOPLE } from './actionTypes';
|
||||||
import { AddPeopleDialog } from './components';
|
import { AddPeopleDialog } from './components';
|
||||||
import { isAddPeopleEnabled, isDialOutEnabled } from './functions';
|
|
||||||
import './middleware.any';
|
import './middleware.any';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,15 +35,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
* @private
|
* @private
|
||||||
* @returns {*} The value returned by {@code next(action)}.
|
* @returns {*} The value returned by {@code next(action)}.
|
||||||
*/
|
*/
|
||||||
function _beginAddPeople({ dispatch, getState }, next, action) {
|
function _beginAddPeople({ dispatch }, next, action) {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
|
||||||
const state = getState();
|
dispatch(openDialog(AddPeopleDialog));
|
||||||
|
|
||||||
dispatch(openDialog(AddPeopleDialog, {
|
|
||||||
addPeopleEnabled: isAddPeopleEnabled(state),
|
|
||||||
dialOutEnabled: isDialOutEnabled(state)
|
|
||||||
}));
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
ADD_PENDING_INVITE_REQUEST,
|
ADD_PENDING_INVITE_REQUEST,
|
||||||
REMOVE_PENDING_INVITE_REQUESTS,
|
REMOVE_PENDING_INVITE_REQUESTS,
|
||||||
SET_CALLEE_INFO_VISIBLE,
|
SET_CALLEE_INFO_VISIBLE,
|
||||||
|
SET_INVITE_DIALOG_VISIBLE,
|
||||||
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
||||||
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
@ -20,7 +21,7 @@ const DEFAULT_STATE = {
|
||||||
* @type {boolean|undefined}
|
* @type {boolean|undefined}
|
||||||
*/
|
*/
|
||||||
calleeInfoVisible: false,
|
calleeInfoVisible: false,
|
||||||
|
inviteDialogVisible: false,
|
||||||
numbersEnabled: true,
|
numbersEnabled: true,
|
||||||
pendingInviteRequests: []
|
pendingInviteRequests: []
|
||||||
};
|
};
|
||||||
|
@ -49,6 +50,12 @@ ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
|
||||||
initialCalleeInfo: action.initialCalleeInfo
|
initialCalleeInfo: action.initialCalleeInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case SET_INVITE_DIALOG_VISIBLE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
inviteDialogVisible: action.visible
|
||||||
|
};
|
||||||
|
|
||||||
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
Loading…
Reference in New Issue