feat(lobby,notifications) refactor lobby notifications
This commit is contained in:
parent
a84130d67d
commit
28f5ddc81d
|
@ -41,64 +41,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
#notification-participant-list {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border: 1px solid rgba(255, 255, 255, .4);
|
||||
border-radius: 8px;
|
||||
left: 0;
|
||||
margin: 20px;
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
z-index: $toolbarZ + 1;
|
||||
|
||||
&:empty {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.toolbox-visible {
|
||||
// Same as toolbox subject position
|
||||
top: 120px;
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: rgba(0, 0, 0, .2);
|
||||
font-size: 1.2em;
|
||||
padding: 15px
|
||||
}
|
||||
|
||||
button {
|
||||
align-self: stretch;
|
||||
margin-bottom: 8px 0;
|
||||
padding: 12px;
|
||||
transition: .2s transform ease;
|
||||
|
||||
&:disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
|
||||
&:disabled {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.borderLess {
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: rgb(3, 118, 218);
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.knocking-participants-container {
|
||||
list-style-type: none;
|
||||
padding: 0 15px 15px 15px;
|
||||
|
|
|
@ -642,6 +642,8 @@
|
|||
"oldElectronClientDescription1": "You appear to be using an old version of the Jitsi Meet client which has known security vulnerabilities. Please make sure you update to our ",
|
||||
"oldElectronClientDescription2": "latest build",
|
||||
"oldElectronClientDescription3": " now!",
|
||||
"participantWantsToJoin": "Wants to join the meeting",
|
||||
"participantsWantToJoin": "Want to join the meeting",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removed by another participant",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) set by another participant",
|
||||
"raiseHandAction": "Raise hand",
|
||||
|
@ -661,7 +663,9 @@
|
|||
"videoMutedRemotelyDescription": "You can always turn it on again.",
|
||||
"videoMutedRemotelyTitle": "Your video has been turned off by {{participantDisplayName}}",
|
||||
"videoUnmuteBlockedDescription": "Camera unmute and desktop sharing operation have been temporarily blocked because of system limits.",
|
||||
"videoUnmuteBlockedTitle": "Camera unmute and desktop sharing blocked!"
|
||||
"videoUnmuteBlockedTitle": "Camera unmute and desktop sharing blocked!",
|
||||
"viewLobby": "View lobby",
|
||||
"waitingParticipants": "{{waitingParticipants}} people"
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from '../../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { KnockingParticipantList } from '../../../lobby';
|
||||
import { KnockingParticipantList } from '../../../lobby/components/native';
|
||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||
import { BackButtonRegistry } from '../../../mobile/back-button';
|
||||
import { Captions } from '../../../subtitles';
|
||||
|
|
|
@ -14,10 +14,9 @@ import { Chat } from '../../../chat';
|
|||
import { Filmstrip } from '../../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { KnockingParticipantList, LobbyScreen } from '../../../lobby';
|
||||
import { LobbyScreen } from '../../../lobby';
|
||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||
import { ParticipantsPane } from '../../../participants-pane/components/web';
|
||||
import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
|
||||
import { Prejoin, isPrejoinPageVisible } from '../../../prejoin';
|
||||
import { toggleToolboxVisible } from '../../../toolbox/actions.any';
|
||||
import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
|
||||
|
@ -72,11 +71,6 @@ type Props = AbstractProps & {
|
|||
*/
|
||||
_backgroundAlpha: number,
|
||||
|
||||
/**
|
||||
* If participants pane is visible or not.
|
||||
*/
|
||||
_isParticipantsPaneVisible: boolean,
|
||||
|
||||
/**
|
||||
* The CSS class to apply to the root of {@link Conference} to modify the
|
||||
* application layout.
|
||||
|
@ -216,7 +210,6 @@ class Conference extends AbstractConference<Props, *> {
|
|||
*/
|
||||
render() {
|
||||
const {
|
||||
_isParticipantsPaneVisible,
|
||||
_layoutClassName,
|
||||
_notificationsVisible,
|
||||
_overflowDrawer,
|
||||
|
@ -242,10 +235,6 @@ class Conference extends AbstractConference<Props, *> {
|
|||
id = 'videospace'
|
||||
onTouchStart = { this._onVidespaceTouchStart }>
|
||||
<LargeVideo />
|
||||
{!_isParticipantsPaneVisible
|
||||
&& <div id = 'notification-participant-list'>
|
||||
<KnockingParticipantList />
|
||||
</div>}
|
||||
<Filmstrip />
|
||||
</div>
|
||||
|
||||
|
@ -401,7 +390,6 @@ function _mapStateToProps(state) {
|
|||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
_backgroundAlpha: backgroundAlpha,
|
||||
_isParticipantsPaneVisible: getParticipantsPaneOpen(state),
|
||||
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
|
||||
_mouseMoveCallbackInterval: mouseMoveCallbackInterval,
|
||||
_overflowDrawer: overflowDrawer,
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { isLocalParticipantModerator } from '../../base/participants';
|
||||
import { setKnockingParticipantApproval } from '../actions';
|
||||
import { getKnockingParticipants, getLobbyEnabled } from '../functions';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The list of participants.
|
||||
*/
|
||||
_participants: Array<Object>,
|
||||
|
||||
/**
|
||||
* True if the list should be rendered.
|
||||
*/
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class to encapsulate the platform common code of the {@code KnockingParticipantList}.
|
||||
*/
|
||||
export default class AbstractKnockingParticipantList<P: Props = Props> extends PureComponent<P> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this._onRespondToParticipant = this._onRespondToParticipant.bind(this);
|
||||
}
|
||||
|
||||
_onRespondToParticipant: (string, boolean) => Function;
|
||||
|
||||
/**
|
||||
* Function that constructs a callback for the response handler button.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @param {boolean} approve - The response for the knocking.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onRespondToParticipant(id, approve) {
|
||||
return () => {
|
||||
this.props.dispatch(setKnockingParticipantApproval(id, approve));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function mapStateToProps(state: Object): $Shape<Props> {
|
||||
const lobbyEnabled = getLobbyEnabled(state);
|
||||
const knockingParticipants = getKnockingParticipants(state);
|
||||
|
||||
return {
|
||||
_participants: knockingParticipants,
|
||||
_visible: lobbyEnabled && isLocalParticipantModerator(state)
|
||||
&& Boolean(knockingParticipants && knockingParticipants.length)
|
||||
};
|
||||
}
|
|
@ -1,23 +1,60 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { ScrollView, Text, View, TouchableOpacity } from 'react-native';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { setKnockingParticipantApproval } from '../../actions';
|
||||
import { HIDDEN_EMAILS } from '../../constants';
|
||||
import AbstractKnockingParticipantList, {
|
||||
mapStateToProps as abstractMapStateToProps,
|
||||
type Props
|
||||
} from '../AbstractKnockingParticipantList';
|
||||
import { getKnockingParticipants, getLobbyEnabled } from '../../functions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Props type of the component.
|
||||
*/
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The list of participants.
|
||||
*/
|
||||
_participants: Array<Object>,
|
||||
|
||||
/**
|
||||
* True if the list should be rendered.
|
||||
*/
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render a list for the actively knocking participants.
|
||||
*/
|
||||
class KnockingParticipantList extends AbstractKnockingParticipantList {
|
||||
class KnockingParticipantList extends PureComponent<Props> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onRespondToParticipant = this._onRespondToParticipant.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
|
@ -78,6 +115,19 @@ class KnockingParticipantList extends AbstractKnockingParticipantList {
|
|||
}
|
||||
|
||||
_onRespondToParticipant: (string, boolean) => Function;
|
||||
|
||||
/**
|
||||
* Function that constructs a callback for the response handler button.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @param {boolean} approve - The response for the knocking.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onRespondToParticipant(id, approve) {
|
||||
return () => {
|
||||
this.props.dispatch(setKnockingParticipantApproval(id, approve));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,14 +136,15 @@ class KnockingParticipantList extends AbstractKnockingParticipantList {
|
|||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: Object): $Shape<Props> {
|
||||
const abstractProps = abstractMapStateToProps(state);
|
||||
function _mapStateToProps(state): Object {
|
||||
const lobbyEnabled = getLobbyEnabled(state);
|
||||
const knockingParticipants = getKnockingParticipants(state);
|
||||
|
||||
return {
|
||||
...abstractProps,
|
||||
_visible: lobbyEnabled && isLocalParticipantModerator(state),
|
||||
|
||||
// On mobile we only show a portion of the list for screen real estate reasons
|
||||
_participants: abstractProps._participants.slice(0, 2)
|
||||
_participants: knockingParticipants.slice(0, 2)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import NotificationWithParticipants from '../../../notifications/components/web/NotificationWithParticipants';
|
||||
import { approveKnockingParticipant, rejectKnockingParticipant } from '../../actions';
|
||||
import AbstractKnockingParticipantList, {
|
||||
mapStateToProps as abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractKnockingParticipantList';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* True if the toolbox is visible, so we need to adjust the position.
|
||||
*/
|
||||
_toolboxVisible: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render a list for the actively knocking participants.
|
||||
*/
|
||||
class KnockingParticipantList extends AbstractKnockingParticipantList<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _participants, _visible, t } = this.props;
|
||||
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id = 'knocking-participant-list'>
|
||||
<div className = 'title'>
|
||||
{ t('lobby.knockingParticipantList') }
|
||||
</div>
|
||||
<NotificationWithParticipants
|
||||
approveButtonText = { t('lobby.allow') }
|
||||
onApprove = { approveKnockingParticipant }
|
||||
onReject = { rejectKnockingParticipant }
|
||||
participants = { _participants }
|
||||
rejectButtonText = { t('lobby.reject') }
|
||||
testIdPrefix = 'lobby' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_onRespondToParticipant: (string, boolean) => Function;
|
||||
}
|
||||
|
||||
export default translate(connect(abstractMapStateToProps)(KnockingParticipantList));
|
|
@ -1,5 +1,4 @@
|
|||
// @flow
|
||||
|
||||
export { default as KnockingParticipantList } from './KnockingParticipantList';
|
||||
export { default as LobbySection } from './LobbySection';
|
||||
export { default as LobbyScreen } from './LobbyScreen';
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import i18n from 'i18next';
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
|
||||
|
@ -13,11 +14,17 @@ import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/pa
|
|||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds';
|
||||
import { isTestModeEnabled } from '../base/testing';
|
||||
import { approveKnockingParticipant, rejectKnockingParticipant } from '../lobby/actions';
|
||||
import {
|
||||
LOBBY_NOTIFICATION_ID,
|
||||
NOTIFICATION_ICON,
|
||||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
NOTIFICATION_TYPE,
|
||||
hideNotification,
|
||||
showNotification
|
||||
} from '../notifications';
|
||||
import { open as openParticipantsPane } from '../participants-pane/actions';
|
||||
import { getParticipantsPaneOpen } from '../participants-pane/functions';
|
||||
import { shouldAutoKnock } from '../prejoin/functions';
|
||||
|
||||
import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED } from './actionTypes';
|
||||
|
@ -31,6 +38,7 @@ import {
|
|||
setPasswordJoinFailed
|
||||
} from './actions';
|
||||
import { KNOCKING_PARTICIPANT_SOUND_ID } from './constants';
|
||||
import { getKnockingParticipants } from './functions';
|
||||
import { KNOCKING_PARTICIPANT_FILE } from './sounds';
|
||||
|
||||
declare var APP: Object;
|
||||
|
@ -81,6 +89,55 @@ StateListenerRegistry.register(
|
|||
})
|
||||
);
|
||||
dispatch(playSound(KNOCKING_PARTICIPANT_SOUND_ID));
|
||||
|
||||
const isParticipantsPaneVisible = getParticipantsPaneOpen(getState());
|
||||
|
||||
if (navigator.product === 'ReactNative' || isParticipantsPaneVisible) {
|
||||
return;
|
||||
}
|
||||
let notificationTitle;
|
||||
let customActionNameKey;
|
||||
let customActionHandler;
|
||||
let descriptionKey;
|
||||
let icon;
|
||||
|
||||
const knockingParticipants = getKnockingParticipants(getState());
|
||||
const firstParticipant = knockingParticipants[0];
|
||||
|
||||
if (knockingParticipants.length > 1) {
|
||||
descriptionKey = 'notify.participantsWantToJoin';
|
||||
notificationTitle = i18n.t('notify.waitingParticipants', {
|
||||
waitingParticipants: knockingParticipants.length
|
||||
});
|
||||
icon = NOTIFICATION_ICON.PARTICIPANTS;
|
||||
customActionNameKey = [ 'notify.viewLobby' ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(openParticipantsPane());
|
||||
}) ];
|
||||
} else {
|
||||
descriptionKey = 'notify.participantWantsToJoin';
|
||||
notificationTitle = firstParticipant.name;
|
||||
icon = NOTIFICATION_ICON.PARTICIPANT;
|
||||
customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(approveKnockingParticipant(firstParticipant.id));
|
||||
}),
|
||||
() => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(rejectKnockingParticipant(firstParticipant.id));
|
||||
}) ];
|
||||
}
|
||||
dispatch(showNotification({
|
||||
title: notificationTitle,
|
||||
descriptionKey,
|
||||
uid: LOBBY_NOTIFICATION_ID,
|
||||
customActionNameKey,
|
||||
customActionHandler,
|
||||
icon
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyKnockingParticipant({
|
||||
id,
|
||||
|
|
|
@ -5,6 +5,8 @@ import EditorErrorIcon from '@atlaskit/icon/glyph/editor/error';
|
|||
import EditorInfoIcon from '@atlaskit/icon/glyph/editor/info';
|
||||
import EditorSuccessIcon from '@atlaskit/icon/glyph/editor/success';
|
||||
import EditorWarningIcon from '@atlaskit/icon/glyph/editor/warning';
|
||||
import PeopleIcon from '@atlaskit/icon/glyph/people';
|
||||
import PersonIcon from '@atlaskit/icon/glyph/person';
|
||||
import QuestionsIcon from '@atlaskit/icon/glyph/questions';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -171,6 +173,12 @@ class Notification extends AbstractNotification<Props> {
|
|||
case NOTIFICATION_ICON.MESSAGE:
|
||||
Icon = QuestionsIcon;
|
||||
break;
|
||||
case NOTIFICATION_ICON.PARTICIPANT:
|
||||
Icon = PersonIcon;
|
||||
break;
|
||||
case NOTIFICATION_ICON.PARTICIPANTS:
|
||||
Icon = PeopleIcon;
|
||||
break;
|
||||
default:
|
||||
Icon = EditorInfoIcon;
|
||||
break;
|
||||
|
|
|
@ -53,9 +53,18 @@ export const NOTIFICATION_TYPE_PRIORITIES = {
|
|||
*/
|
||||
export const NOTIFICATION_ICON = {
|
||||
...NOTIFICATION_TYPE,
|
||||
MESSAGE: 'message'
|
||||
MESSAGE: 'message',
|
||||
PARTICIPANT: 'participant',
|
||||
PARTICIPANTS: 'participants'
|
||||
};
|
||||
|
||||
/**
|
||||
* The identifier of the lobby notification.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const LOBBY_NOTIFICATION_ID = 'LOBBY_NOTIFICATION';
|
||||
|
||||
/**
|
||||
* The identifier of the raise hand notification.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue