mirror of https://github.com/jitsi/jitsi-meet
feat(toolbox) allow any toolbox button to be displayed in main toolbar (#9488)
* feat(toolbox) allow any toolbox button to be displayed as main fixes the previous behaviour where only a certain set of buttons were whitelisted for being displayed in the main toolbar * code review * code review - fix avatar icon positionpull/9523/head
parent
074a783bd9
commit
62c78950cd
@ -0,0 +1,109 @@ |
||||
// @flow
|
||||
|
||||
import React from 'react'; |
||||
|
||||
import { translate } from '../../../base/i18n'; |
||||
import { IconChat } from '../../../base/icons'; |
||||
import { connect } from '../../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; |
||||
|
||||
import ChatCounter from './ChatCounter'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link ChatButton}. |
||||
*/ |
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* Whether or not the chat feature is currently displayed. |
||||
*/ |
||||
_chatOpen: boolean, |
||||
|
||||
/** |
||||
* External handler for click action. |
||||
*/ |
||||
handleClick: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implementation of a button for accessing chat pane. |
||||
*/ |
||||
class ChatButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.chat'; |
||||
icon = IconChat; |
||||
label = 'toolbar.openChat'; |
||||
toggledLabel = 'toolbar.closeChat'; |
||||
|
||||
/** |
||||
* Retrieves tooltip dynamically. |
||||
*/ |
||||
get tooltip() { |
||||
if (this._isToggled()) { |
||||
return 'toolbar.closeChat'; |
||||
} |
||||
|
||||
return 'toolbar.openChat'; |
||||
} |
||||
|
||||
/** |
||||
* Required by linter due to AbstractButton overwritten prop being writable. |
||||
* |
||||
* @param {string} value - The value. |
||||
*/ |
||||
set tooltip(value) { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
this.props.handleClick(); |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether this button is in toggled state or not. |
||||
* |
||||
* @override |
||||
* @protected |
||||
* @returns {boolean} |
||||
*/ |
||||
_isToggled() { |
||||
return this.props._chatOpen; |
||||
} |
||||
|
||||
/** |
||||
* Overrides AbstractButton's {@link Component#render()}. |
||||
* |
||||
* @override |
||||
* @protected |
||||
* @returns {boReact$Nodeolean} |
||||
*/ |
||||
render(): React$Node { |
||||
return ( |
||||
<div |
||||
className = 'toolbar-button-with-badge' |
||||
key = 'chatcontainer'> |
||||
{super.render()} |
||||
<ChatCounter /> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Function that maps parts of Redux state tree into component props. |
||||
* |
||||
* @param {Object} state - Redux state. |
||||
* @returns {Object} |
||||
*/ |
||||
const mapStateToProps = state => { |
||||
return { |
||||
_chatOpen: state['features/chat'].isOpen |
||||
}; |
||||
}; |
||||
|
||||
export default translate(connect(mapStateToProps)(ChatButton)); |
||||
@ -1,5 +1,6 @@ |
||||
// @flow
|
||||
|
||||
export { default as Chat } from './Chat'; |
||||
export { default as ChatButton } from './ChatButton'; |
||||
export { default as ChatCounter } from './ChatCounter'; |
||||
export { default as ChatPrivacyDialog } from './ChatPrivacyDialog'; |
||||
|
||||
@ -0,0 +1,46 @@ |
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics'; |
||||
import { openDialog } from '../../base/dialog'; |
||||
import { translate } from '../../base/i18n'; |
||||
import { IconCodeBlock } from '../../base/icons'; |
||||
import { connect } from '../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components'; |
||||
|
||||
import EmbedMeetingDialog from './EmbedMeetingDialog'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link EmbedMeetingButton}. |
||||
*/ |
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* The redux {@code dispatch} function. |
||||
*/ |
||||
dispatch: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implementation of a button for opening embed meeting dialog. |
||||
*/ |
||||
class EmbedMeetingButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.embedMeeting'; |
||||
icon = IconCodeBlock; |
||||
label = 'toolbar.embedMeeting'; |
||||
tooltip = 'toolbar.embedMeeting'; |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
const { dispatch } = this.props; |
||||
|
||||
sendAnalytics(createToolbarEvent('embed.meeting')); |
||||
dispatch(openDialog(EmbedMeetingDialog)); |
||||
} |
||||
} |
||||
|
||||
export default translate(connect()(EmbedMeetingButton)); |
||||
@ -1 +1,2 @@ |
||||
export { default as EmbedMeetingButton } from './EmbedMeetingButton'; |
||||
export { default as EmbedMeetingDialog } from './EmbedMeetingDialog'; |
||||
|
||||
@ -0,0 +1,56 @@ |
||||
|
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics'; |
||||
import { translate } from '../../base/i18n'; |
||||
import { IconFeedback } from '../../base/icons'; |
||||
import { connect } from '../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components'; |
||||
import { openFeedbackDialog } from '../actions'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link FeedbackButton}. |
||||
*/ |
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* The {@code JitsiConference} for the current conference. |
||||
*/ |
||||
_conference: Object, |
||||
|
||||
/** |
||||
* The redux {@code dispatch} function. |
||||
*/ |
||||
dispatch: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implementation of a button for opening feedback dialog. |
||||
*/ |
||||
class FeedbackButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.feedback'; |
||||
icon = IconFeedback; |
||||
label = 'toolbar.feedback'; |
||||
tooltip = 'toolbar.feedback'; |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
const { _conference, dispatch } = this.props; |
||||
|
||||
sendAnalytics(createToolbarEvent('feedback')); |
||||
dispatch(openFeedbackDialog(_conference)); |
||||
} |
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
return { |
||||
_conference: state['features/base/conference'].conference |
||||
}; |
||||
}; |
||||
|
||||
export default translate(connect(mapStateToProps)(FeedbackButton)); |
||||
@ -1 +1,2 @@ |
||||
export { default as FeedbackButton } from './FeedbackButton'; |
||||
export { default as FeedbackDialog } from './FeedbackDialog'; |
||||
|
||||
@ -0,0 +1,44 @@ |
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics'; |
||||
import { translate } from '../../base/i18n'; |
||||
import { IconDeviceDocument } from '../../base/icons'; |
||||
import { connect } from '../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components'; |
||||
import { openKeyboardShortcutsDialog } from '../actions'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link KeyboardShortcutsButton}. |
||||
*/ |
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* The redux {@code dispatch} function. |
||||
*/ |
||||
dispatch: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implementation of a button for opening keyboard shortcuts dialog. |
||||
*/ |
||||
class KeyboardShortcutsButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.shortcuts'; |
||||
icon = IconDeviceDocument; |
||||
label = 'toolbar.shortcuts'; |
||||
tooltip = 'toolbar.shortcuts'; |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
const { dispatch } = this.props; |
||||
|
||||
sendAnalytics(createToolbarEvent('shortcuts')); |
||||
dispatch(openKeyboardShortcutsDialog()); |
||||
} |
||||
} |
||||
|
||||
export default translate(connect()(KeyboardShortcutsButton)); |
||||
@ -1 +1,2 @@ |
||||
export { default as KeyboardShortcutsButton } from './KeyboardShortcutsButton'; |
||||
export { default as KeyboardShortcutsDialog } from './KeyboardShortcutsDialog'; |
||||
|
||||
@ -1,85 +1,46 @@ |
||||
/* @flow */ |
||||
|
||||
import React, { Component } from 'react'; |
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics'; |
||||
import { openDialog } from '../../base/dialog'; |
||||
import { translate } from '../../base/i18n'; |
||||
import { IconRec } from '../../base/icons'; |
||||
import { ToolbarButton } from '../../toolbox/components/web'; |
||||
import { connect } from '../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components'; |
||||
|
||||
import LocalRecordingInfoDialog from './LocalRecordingInfoDialog'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} state of |
||||
* {@link LocalRecordingButton}. |
||||
* The type of the React {@code Component} props of {@link LocalRecording}. |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* Whether or not {@link LocalRecordingInfoDialog} should be displayed. |
||||
*/ |
||||
isDialogShown: boolean, |
||||
|
||||
/** |
||||
* Callback function called when {@link LocalRecordingButton} is clicked. |
||||
*/ |
||||
onClick: Function, |
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
* The redux {@code dispatch} function. |
||||
*/ |
||||
t: Function |
||||
} |
||||
dispatch: Function |
||||
}; |
||||
|
||||
/** |
||||
* A React {@code Component} for opening or closing the |
||||
* {@code LocalRecordingInfoDialog}. |
||||
* |
||||
* @extends Component |
||||
* Implementation of a button for opening local recording dialog. |
||||
*/ |
||||
class LocalRecordingButton extends Component<Props> { |
||||
class LocalRecording extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.localRecording'; |
||||
icon = IconRec; |
||||
label = 'localRecording.dialogTitle'; |
||||
tooltip = 'localRecording.dialogTitle'; |
||||
|
||||
/** |
||||
* Initializes a new {@code LocalRecordingButton} instance. |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @param {Object} props - The read-only properties with which the new |
||||
* instance is to be initialized. |
||||
*/ |
||||
constructor(props: Props) { |
||||
super(props); |
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onClick = this._onClick.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { isDialogShown, t } = this.props; |
||||
|
||||
return ( |
||||
<ToolbarButton |
||||
accessibilityLabel |
||||
= { t('toolbar.accessibilityLabel.localRecording') } |
||||
icon = { IconRec } |
||||
onClick = { this._onClick } |
||||
toggled = { isDialogShown } |
||||
tooltip = { t('localRecording.dialogTitle') } /> |
||||
); |
||||
} |
||||
|
||||
_onClick: () => void; |
||||
|
||||
/** |
||||
* Callback invoked when the Toolbar button is clicked. |
||||
* |
||||
* @private |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_onClick() { |
||||
this.props.onClick(); |
||||
_handleClick() { |
||||
const { dispatch } = this.props; |
||||
|
||||
sendAnalytics(createToolbarEvent('local.recording')); |
||||
dispatch(openDialog(LocalRecordingInfoDialog)); |
||||
} |
||||
} |
||||
|
||||
export default translate(LocalRecordingButton); |
||||
export default translate(connect()(LocalRecording)); |
||||
|
||||
@ -0,0 +1,39 @@ |
||||
// @flow
|
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
import { IconParticipants } from '../../base/icons'; |
||||
import { connect } from '../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link ParticipantsPaneButton}. |
||||
*/ |
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* External handler for click action. |
||||
*/ |
||||
handleClick: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implementation of a button for accessing participants pane. |
||||
*/ |
||||
class ParticipantsPaneButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.participants'; |
||||
icon = IconParticipants; |
||||
label = 'toolbar.participants'; |
||||
tooltip = 'toolbar.participants'; |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
this.props.handleClick(); |
||||
} |
||||
} |
||||
|
||||
export default translate(connect()(ParticipantsPaneButton)); |
||||
@ -0,0 +1,67 @@ |
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics'; |
||||
import { openDialog } from '../../base/dialog'; |
||||
import { translate } from '../../base/i18n'; |
||||
import { IconPresentation } from '../../base/icons'; |
||||
import { connect } from '../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components'; |
||||
|
||||
import SpeakerStats from './SpeakerStats'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link SpeakerStatsButton}. |
||||
*/ |
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* The {@code JitsiConference} for the current conference. |
||||
*/ |
||||
_conference: Object, |
||||
|
||||
/** |
||||
* The redux {@code dispatch} function. |
||||
*/ |
||||
dispatch: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implementation of a button for opening speaker stats dialog. |
||||
*/ |
||||
class SpeakerStatsButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.speakerStats'; |
||||
icon = IconPresentation; |
||||
label = 'toolbar.speakerStats'; |
||||
tooltip = 'toolbar.speakerStats'; |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
const { _conference, dispatch } = this.props; |
||||
|
||||
sendAnalytics(createToolbarEvent('speaker.stats')); |
||||
dispatch(openDialog(SpeakerStats, { |
||||
conference: _conference |
||||
})); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated |
||||
* {@code SpeakerStatsButton} component's props. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @private |
||||
* @returns {Object} |
||||
*/ |
||||
function mapStateToProps(state) { |
||||
return { |
||||
_conference: state['features/base/conference'].conference |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(mapStateToProps)(SpeakerStatsButton)); |
||||
@ -1 +1,2 @@ |
||||
export { default as SpeakerStatsButton } from './SpeakerStatsButton'; |
||||
export { default as SpeakerStats } from './SpeakerStats'; |
||||
|
||||
@ -0,0 +1,103 @@ |
||||
// @flow
|
||||
|
||||
import { translate } from '../../../base/i18n'; |
||||
import { IconExitFullScreen, IconFullScreen } from '../../../base/icons'; |
||||
import { connect } from '../../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; |
||||
|
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* Whether or not the app is currently in full screen. |
||||
*/ |
||||
_fullScreen: boolean, |
||||
|
||||
/** |
||||
* External handler for click action. |
||||
*/ |
||||
handleClick: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implementation of a button for toggling fullscreen state. |
||||
*/ |
||||
class FullscreenButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.fullScreen'; |
||||
label = 'toolbar.enterFullScreen'; |
||||
toggledLabel = 'toolbar.exitFullScreen' |
||||
|
||||
/** |
||||
* Retrieves icon dynamically. |
||||
*/ |
||||
get icon() { |
||||
if (this._isToggled()) { |
||||
return IconExitFullScreen; |
||||
} |
||||
|
||||
return IconFullScreen; |
||||
} |
||||
|
||||
/** |
||||
* Required by linter due to AbstractButton overwritten prop being writable. |
||||
* |
||||
* @param {string} value - The value. |
||||
*/ |
||||
set icon(value) { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Retrieves icon dynamically. |
||||
*/ |
||||
get tooltip() { |
||||
if (this._isToggled()) { |
||||
return 'toolbar.exitFullScreen'; |
||||
} |
||||
|
||||
return 'toolbar.enterFullScreen'; |
||||
} |
||||
|
||||
/** |
||||
* Required by linter due to AbstractButton overwritten prop being writable. |
||||
* |
||||
* @param {string} value - The value. |
||||
*/ |
||||
set tooltip(value) { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
this.props.handleClick(); |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether this button is in toggled state or not. |
||||
* |
||||
* @override |
||||
* @protected |
||||
* @returns {boolean} |
||||
*/ |
||||
_isToggled() { |
||||
return this.props._fullScreen; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Function that maps parts of Redux state tree into component props. |
||||
* |
||||
* @param {Object} state - Redux state. |
||||
* @returns {Object} |
||||
*/ |
||||
const mapStateToProps = state => { |
||||
return { |
||||
_fullScreen: state['features/toolbox'].fullScreen |
||||
}; |
||||
}; |
||||
|
||||
export default translate(connect(mapStateToProps)(FullscreenButton)); |
||||
@ -1,148 +0,0 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { Avatar } from '../../../base/avatar'; |
||||
import { translate } from '../../../base/i18n'; |
||||
import { getLocalParticipant } from '../../../base/participants'; |
||||
import { connect } from '../../../base/redux'; |
||||
|
||||
declare var interfaceConfig: Object; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of |
||||
* {@link OverflowMenuProfileItem}. |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* The redux representation of the local participant. |
||||
*/ |
||||
_localParticipant: Object, |
||||
|
||||
/** |
||||
* Whether the button support clicking or not. |
||||
*/ |
||||
_unclickable: boolean, |
||||
|
||||
/** |
||||
* The callback to invoke when {@code OverflowMenuProfileItem} is |
||||
* clicked. |
||||
*/ |
||||
onClick: Function, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: Function |
||||
}; |
||||
|
||||
/** |
||||
* A React {@code Component} for displaying a link with a profile avatar as an |
||||
* icon. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class OverflowMenuProfileItem extends Component<Props> { |
||||
/** |
||||
* Initializes a new {@code OverflowMenuProfileItem} instance. |
||||
* |
||||
* @param {Object} props - The read-only properties with which the new |
||||
* instance is to be initialized. |
||||
*/ |
||||
constructor(props: Props) { |
||||
super(props); |
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this); |
||||
this._onKeyPress = this._onKeyPress.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { _localParticipant, _unclickable, t } = this.props; |
||||
const classNames = `overflow-menu-item ${ |
||||
_unclickable ? 'unclickable' : ''}`;
|
||||
let displayName; |
||||
|
||||
if (_localParticipant && _localParticipant.name) { |
||||
displayName = _localParticipant.name; |
||||
} else { |
||||
displayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME; |
||||
} |
||||
|
||||
return ( |
||||
<li |
||||
aria-label = { t('toolbar.accessibilityLabel.profile') } |
||||
className = { classNames } |
||||
onClick = { this._onClick } |
||||
onKeyPress = { this._onKeyPress } |
||||
role = 'menuitem' |
||||
tabIndex = { 0 }> |
||||
<span className = 'overflow-menu-item-icon'> |
||||
<Avatar |
||||
participantId = { _localParticipant.id } |
||||
size = { 20 } /> |
||||
</span> |
||||
<span className = 'profile-text'> |
||||
{ displayName } |
||||
</span> |
||||
</li> |
||||
); |
||||
} |
||||
|
||||
_onClick: () => void; |
||||
|
||||
/** |
||||
* Invokes an on click callback if clicking is allowed. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
_onClick() { |
||||
if (!this.props._unclickable) { |
||||
this.props.onClick(); |
||||
} |
||||
} |
||||
|
||||
_onKeyPress: (Object) => void; |
||||
|
||||
/** |
||||
* KeyPress handler for accessibility. |
||||
* |
||||
* @param {Object} e - The key event to handle. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
_onKeyPress(e) { |
||||
if (!this.props._unclickable && (e.key === ' ' || e.key === 'Enter')) { |
||||
e.preventDefault(); |
||||
this.props.onClick(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated |
||||
* {@code OverflowMenuProfileItem} component's props. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _localParticipant: Object, |
||||
* _unclickable: boolean |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
return { |
||||
_localParticipant: getLocalParticipant(state), |
||||
_unclickable: state['features/base/config'].disableProfile |
||||
|| !interfaceConfig.SETTINGS_SECTIONS.includes('profile') |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps)(OverflowMenuProfileItem)); |
||||
@ -0,0 +1,123 @@ |
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../analytics'; |
||||
import { translate } from '../../../base/i18n'; |
||||
import { getLocalParticipant } from '../../../base/participants'; |
||||
import { connect } from '../../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; |
||||
import { openSettingsDialog, SETTINGS_TABS } from '../../../settings'; |
||||
|
||||
import ProfileButtonAvatar from './ProfileButtonAvatar'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link ProfileButton}. |
||||
*/ |
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* The redux representation of the local participant. |
||||
*/ |
||||
_localParticipant: Object, |
||||
|
||||
/** |
||||
* Whether the button support clicking or not. |
||||
*/ |
||||
_unclickable: boolean, |
||||
|
||||
/** |
||||
* The redux {@code dispatch} function. |
||||
*/ |
||||
dispatch: Function |
||||
}; |
||||
|
||||
declare var interfaceConfig: Object; |
||||
|
||||
/** |
||||
* Implementation of a button for opening profile dialog. |
||||
*/ |
||||
class ProfileButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.profile'; |
||||
icon = ProfileButtonAvatar; |
||||
|
||||
/** |
||||
* Retrieves the label. |
||||
*/ |
||||
get label() { |
||||
const { _localParticipant } = this.props; |
||||
let displayName; |
||||
|
||||
if (_localParticipant && _localParticipant.name) { |
||||
displayName = _localParticipant.name; |
||||
} else { |
||||
displayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME; |
||||
} |
||||
|
||||
return displayName; |
||||
} |
||||
|
||||
/** |
||||
* Required by linter due to AbstractButton overwritten prop being writable. |
||||
* |
||||
* @param {string} value - The value. |
||||
*/ |
||||
set label(value) { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Retrieves the tooltip. |
||||
*/ |
||||
get tooltip() { |
||||
return this.label; |
||||
} |
||||
|
||||
/** |
||||
* Required by linter due to AbstractButton overwritten prop being writable. |
||||
* |
||||
* @param {string} value - The value. |
||||
*/ |
||||
set tooltip(value) { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
const { dispatch, _unclickable } = this.props; |
||||
|
||||
if (!_unclickable) { |
||||
sendAnalytics(createToolbarEvent('profile')); |
||||
dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether the button should be disabled or not. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_isDisabled() { |
||||
return this.props._unclickable; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Function that maps parts of Redux state tree into component props. |
||||
* |
||||
* @param {Object} state - Redux state. |
||||
* @returns {Object} |
||||
*/ |
||||
const mapStateToProps = state => { |
||||
return { |
||||
_localParticipant: getLocalParticipant(state), |
||||
_unclickable: !interfaceConfig.SETTINGS_SECTIONS.includes('profile'), |
||||
customClass: 'profile-button-avatar' |
||||
}; |
||||
}; |
||||
|
||||
export default translate(connect(mapStateToProps)(ProfileButton)); |
||||
@ -0,0 +1,63 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { Avatar } from '../../../base/avatar'; |
||||
import { translate } from '../../../base/i18n'; |
||||
import { getLocalParticipant } from '../../../base/participants'; |
||||
import { connect } from '../../../base/redux'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of |
||||
* {@link ProfileButtonAvatar}. |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* The redux representation of the local participant. |
||||
*/ |
||||
_localParticipant: Object, |
||||
|
||||
}; |
||||
|
||||
/** |
||||
* A React {@code Component} for displaying a profile avatar as an |
||||
* icon. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class ProfileButtonAvatar extends Component<Props> { |
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { _localParticipant } = this.props; |
||||
|
||||
return ( |
||||
<Avatar |
||||
participantId = { _localParticipant.id } |
||||
size = { 20 } /> |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated |
||||
* {@code ProfileButtonAvatar} component's props. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _localParticipant: Object, |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
return { |
||||
_localParticipant: getLocalParticipant(state) |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps)(ProfileButtonAvatar)); |
||||
@ -0,0 +1,83 @@ |
||||
// @flow
|
||||
|
||||
import { translate } from '../../../base/i18n'; |
||||
import { IconRaisedHand } from '../../../base/icons'; |
||||
import { getLocalParticipant } from '../../../base/participants'; |
||||
import { connect } from '../../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; |
||||
|
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* Whether or not the local participant's hand is raised. |
||||
*/ |
||||
_raisedHand: boolean, |
||||
|
||||
/** |
||||
* External handler for click action. |
||||
*/ |
||||
handleClick: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implementation of a button for toggling raise hand functionality. |
||||
*/ |
||||
class RaiseHandButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand'; |
||||
icon = IconRaisedHand |
||||
label = 'toolbar.raiseYourHand'; |
||||
toggledLabel = 'toolbar.lowerYourHand' |
||||
|
||||
/** |
||||
* Retrieves tooltip dynamically. |
||||
*/ |
||||
get tooltip() { |
||||
return this.props._raisedHand ? 'toolbar.lowerYourHand' : 'toolbar.raiseYourHand'; |
||||
} |
||||
|
||||
/** |
||||
* Required by linter due to AbstractButton overwritten prop being writable. |
||||
* |
||||
* @param {string} value - The value. |
||||
*/ |
||||
set tooltip(value) { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
this.props.handleClick(); |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether this button is in toggled state or not. |
||||
* |
||||
* @override |
||||
* @protected |
||||
* @returns {boolean} |
||||
*/ |
||||
_isToggled() { |
||||
return this.props._raisedHand; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Function that maps parts of Redux state tree into component props. |
||||
* |
||||
* @param {Object} state - Redux state. |
||||
* @returns {Object} |
||||
*/ |
||||
const mapStateToProps = state => { |
||||
const localParticipant = getLocalParticipant(state); |
||||
|
||||
return { |
||||
_raisedHand: localParticipant.raisedHand |
||||
}; |
||||
}; |
||||
|
||||
export default translate(connect(mapStateToProps)(RaiseHandButton)); |
||||
@ -0,0 +1,3 @@ |
||||
import React from 'react'; |
||||
|
||||
export default () => <hr className = 'overflow-menu-hr' />; |
||||
@ -0,0 +1,139 @@ |
||||
// @flow
|
||||
|
||||
import { translate } from '../../../base/i18n'; |
||||
import { IconShareDesktop } from '../../../base/icons'; |
||||
import JitsiMeetJS from '../../../base/lib-jitsi-meet/_'; |
||||
import { getParticipants } from '../../../base/participants'; |
||||
import { connect } from '../../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; |
||||
import { getLocalVideoTrack } from '../../../base/tracks'; |
||||
import { isScreenAudioShared } from '../../../screen-share'; |
||||
|
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* Whether or not screensharing is initialized. |
||||
*/ |
||||
_desktopSharingEnabled: boolean, |
||||
|
||||
/** |
||||
* The tooltip key to use when screensharing is disabled. Or undefined |
||||
* if non to be shown and the button to be hidden. |
||||
*/ |
||||
_desktopSharingDisabledTooltipKey: string, |
||||
|
||||
/** |
||||
* Whether or not the local participant is screensharing. |
||||
*/ |
||||
_screensharing: boolean, |
||||
|
||||
/** |
||||
* The redux {@code dispatch} function. |
||||
*/ |
||||
dispatch: Function, |
||||
|
||||
/** |
||||
* External handler for click action. |
||||
*/ |
||||
handleClick: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implementation of a button for sharing desktop / windows. |
||||
*/ |
||||
class ShareDesktopButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen'; |
||||
label = 'toolbar.startScreenSharing'; |
||||
icon = IconShareDesktop; |
||||
toggledLabel = 'toolbar.stopScreenSharing' |
||||
tooltip = 'toolbar.accessibilityLabel.shareYourScreen'; |
||||
|
||||
/** |
||||
* Retrieves tooltip dynamically. |
||||
*/ |
||||
get tooltip() { |
||||
const { _desktopSharingDisabledTooltipKey, _desktopSharingEnabled, _screensharing } = this.props; |
||||
|
||||
if (_desktopSharingEnabled) { |
||||
if (_screensharing) { |
||||
return 'toolbar.stopScreenSharing'; |
||||
} |
||||
|
||||
return 'toolbar.startScreenSharing'; |
||||
} |
||||
|
||||
return _desktopSharingDisabledTooltipKey; |
||||
} |
||||
|
||||
/** |
||||
* Required by linter due to AbstractButton overwritten prop being writable. |
||||
* |
||||
* @param {string} value - The icon value. |
||||
*/ |
||||
set tooltip(value) { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
this.props.handleClick(); |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether this button is in toggled state or not. |
||||
* |
||||
* @override |
||||
* @protected |
||||
* @returns {boolean} |
||||
*/ |
||||
_isToggled() { |
||||
return this.props._screensharing; |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether this button is in disabled state or not. |
||||
* |
||||
* @override |
||||
* @protected |
||||
* @returns {boolean} |
||||
*/ |
||||
_isDisabled() { |
||||
return !this.props._desktopSharingEnabled; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Function that maps parts of Redux state tree into component props. |
||||
* |
||||
* @param {Object} state - Redux state. |
||||
* @returns {Object} |
||||
*/ |
||||
const mapStateToProps = state => { |
||||
const localVideo = getLocalVideoTrack(state['features/base/tracks']); |
||||
let desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled(); |
||||
const { enableFeaturesBasedOnToken } = state['features/base/config']; |
||||
|
||||
let desktopSharingDisabledTooltipKey; |
||||
|
||||
if (enableFeaturesBasedOnToken) { |
||||
// we enable desktop sharing if any participant already have this
|
||||
// feature enabled
|
||||
desktopSharingEnabled = getParticipants(state) |
||||
.find(({ features = {} }) => |
||||
String(features['screen-sharing']) === 'true') !== undefined; |
||||
desktopSharingDisabledTooltipKey = 'dialog.shareYourScreenDisabled'; |
||||
} |
||||
|
||||
return { |
||||
_desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey, |
||||
_desktopSharingEnabled: desktopSharingEnabled, |
||||
_screensharing: (localVideo && localVideo.videoType === 'desktop') || isScreenAudioShared(state) |
||||
}; |
||||
}; |
||||
|
||||
export default translate(connect(mapStateToProps)(ShareDesktopButton)); |
||||
@ -1,75 +0,0 @@ |
||||
// @flow
|
||||
|
||||
import { translate } from '../../../base/i18n'; |
||||
import { IconCameraRefresh } from '../../../base/icons'; |
||||
import { connect } from '../../../base/redux'; |
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; |
||||
import { isLocalCameraTrackMuted, isToggleCameraEnabled, toggleCamera } from '../../../base/tracks'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link ToggleCameraButton}. |
||||
*/ |
||||
type Props = AbstractButtonProps & { |
||||
|
||||
/** |
||||
* Whether the current conference is in audio only mode or not. |
||||
*/ |
||||
_audioOnly: boolean, |
||||
|
||||
/** |
||||
* Whether video is currently muted or not. |
||||
*/ |
||||
_videoMuted: boolean, |
||||
|
||||
/** |
||||
* The Redux dispatch function. |
||||
*/ |
||||
dispatch: Function |
||||
}; |
||||
|
||||
/** |
||||
* An implementation of a button for toggling the camera facing mode. |
||||
*/ |
||||
class ToggleCameraButton extends AbstractButton<Props, any> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.toggleCamera'; |
||||
icon = IconCameraRefresh; |
||||
label = 'toolbar.toggleCamera'; |
||||
|
||||
/** |
||||
* Handles clicking/pressing the button. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
this.props.dispatch(toggleCamera()); |
||||
} |
||||
|
||||
/** |
||||
* Whether this button is disabled or not. |
||||
* |
||||
* @returns {boolean} |
||||
*/ |
||||
_isDisabled() { |
||||
return this.props._audioOnly || this.props._videoMuted; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the redux state to the associated props for the |
||||
* {@code ToggleCameraButton} component. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @returns {Props} |
||||
*/ |
||||
function mapStateToProps(state): Object { |
||||
const { enabled: audioOnly } = state['features/base/audio-only']; |
||||
const tracks = state['features/base/tracks']; |
||||
|
||||
return { |
||||
_audioOnly: Boolean(audioOnly), |
||||
_videoMuted: isLocalCameraTrackMuted(tracks), |
||||
visible: isToggleCameraEnabled(state) |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(mapStateToProps)(ToggleCameraButton)); |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@ |
||||
/** |
||||
* Thresholds for displaying toolbox buttons |
||||
*/ |
||||
export const THRESHOLDS = [ |
||||
{ |
||||
width: 520, |
||||
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants', 'tileview' ] |
||||
}, |
||||
{ |
||||
width: 470, |
||||
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants' ] |
||||
}, |
||||
{ |
||||
width: 420, |
||||
order: [ 'microphone', 'camera', 'desktop', 'chat', 'participants' ] |
||||
}, |
||||
{ |
||||
width: 370, |
||||
order: [ 'microphone', 'camera', 'chat', 'participants' ] |
||||
}, |
||||
{ |
||||
width: 320, |
||||
order: [ 'microphone', 'camera', 'chat' ] |
||||
}, |
||||
{ |
||||
width: 270, |
||||
order: [ 'microphone', 'camera' ] |
||||
} |
||||
]; |
||||
Loading…
Reference in new issue