fix(Drawer): Close drawer on item click
Clicking on an item when the popup drawer is displayed would keep it open. Now clicking on any item should automatically close the drawer. Popup was also refactored and no longer uses refs.
This commit is contained in:
parent
cd6a814978
commit
8983ea41fd
|
@ -11,12 +11,6 @@
|
||||||
{
|
{
|
||||||
@extend %connection-info;
|
@extend %connection-info;
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply negative margin to reduce the appearance of padding in AtlasKit
|
|
||||||
* InlineDialog.
|
|
||||||
*/
|
|
||||||
margin: -15px;
|
|
||||||
|
|
||||||
> table {
|
> table {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@extend %connection-info;
|
@extend %connection-info;
|
||||||
|
|
|
@ -46,3 +46,7 @@
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
z-index: $popoverZ;
|
z-index: $popoverZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.padded-content {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
.popupmenu {
|
.popupmenu {
|
||||||
background-color: $menuBG;
|
background-color: $menuBG;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
list-style-type: none;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
@ -38,6 +39,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
|
|
@ -152,7 +152,9 @@ const ContextMenu = ({
|
||||||
<Drawer
|
<Drawer
|
||||||
isOpen = { isDrawerOpen && _overflowDrawer }
|
isOpen = { isDrawerOpen && _overflowDrawer }
|
||||||
onClose = { onDrawerClose }>
|
onClose = { onDrawerClose }>
|
||||||
<div className = { styles.drawer }>
|
<div
|
||||||
|
className = { styles.drawer }
|
||||||
|
onClick = { onDrawerClose }>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
import clsx from 'clsx';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { Drawer, JitsiPortal, DialogPortal } from '../../../toolbox/components/web';
|
import { Drawer, JitsiPortal, DialogPortal } from '../../../toolbox/components/web';
|
||||||
import { isMobileBrowser } from '../../environment/utils';
|
import { isMobileBrowser } from '../../environment/utils';
|
||||||
|
import { connect } from '../../redux';
|
||||||
import { getContextMenuStyle } from '../functions.web';
|
import { getContextMenuStyle } from '../functions.web';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +58,17 @@ type Props = {
|
||||||
* From which side of the dialog trigger the dialog should display. The
|
* From which side of the dialog trigger the dialog should display. The
|
||||||
* value will be passed to {@code InlineDialog}.
|
* value will be passed to {@code InlineDialog}.
|
||||||
*/
|
*/
|
||||||
position: string
|
position: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the content show have some padding.
|
||||||
|
*/
|
||||||
|
paddedContent: ?boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the popover is visible or not.
|
||||||
|
*/
|
||||||
|
visible: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,12 +79,7 @@ type State = {
|
||||||
/**
|
/**
|
||||||
* The style to apply to the context menu in order to position it correctly.
|
* The style to apply to the context menu in order to position it correctly.
|
||||||
*/
|
*/
|
||||||
contextMenuStyle: Object,
|
contextMenuStyle: Object
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the {@code InlineDialog} should be displayed.
|
|
||||||
*/
|
|
||||||
showDialog: boolean
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -110,7 +116,6 @@ class Popover extends Component<Props, State> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
showDialog: false,
|
|
||||||
contextMenuStyle: null
|
contextMenuStyle: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -127,16 +132,6 @@ class Popover extends Component<Props, State> {
|
||||||
this._getCustomDialogStyle = this._getCustomDialogStyle.bind(this);
|
this._getCustomDialogStyle = this._getCustomDialogStyle.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Public method for triggering showing the context menu dialog.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
showDialog() {
|
|
||||||
this._onShowDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a touch event listener to attach.
|
* Sets up a touch event listener to attach.
|
||||||
*
|
*
|
||||||
|
@ -164,7 +159,7 @@ class Popover extends Component<Props, State> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { children, className, content, id, overflowDrawer } = this.props;
|
const { children, className, content, id, overflowDrawer, visible } = this.props;
|
||||||
|
|
||||||
if (overflowDrawer) {
|
if (overflowDrawer) {
|
||||||
return (
|
return (
|
||||||
|
@ -175,7 +170,7 @@ class Popover extends Component<Props, State> {
|
||||||
{ children }
|
{ children }
|
||||||
<JitsiPortal>
|
<JitsiPortal>
|
||||||
<Drawer
|
<Drawer
|
||||||
isOpen = { this.state.showDialog }
|
isOpen = { visible }
|
||||||
onClose = { this._onHideDialog }>
|
onClose = { this._onHideDialog }>
|
||||||
{ content }
|
{ content }
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
@ -193,7 +188,7 @@ class Popover extends Component<Props, State> {
|
||||||
onMouseEnter = { this._onShowDialog }
|
onMouseEnter = { this._onShowDialog }
|
||||||
onMouseLeave = { this._onHideDialog }
|
onMouseLeave = { this._onHideDialog }
|
||||||
ref = { this._containerRef }>
|
ref = { this._containerRef }>
|
||||||
{ this.state.showDialog && (
|
{ visible && (
|
||||||
<DialogPortal
|
<DialogPortal
|
||||||
getRef = { this._setContextMenuRef }
|
getRef = { this._setContextMenuRef }
|
||||||
setSize = { this._setContextMenuStyle }
|
setSize = { this._setContextMenuStyle }
|
||||||
|
@ -244,7 +239,7 @@ class Popover extends Component<Props, State> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onTouchStart(event) {
|
_onTouchStart(event) {
|
||||||
if (this.state.showDialog
|
if (this.props.visible
|
||||||
&& !this.props.overflowDrawer
|
&& !this.props.overflowDrawer
|
||||||
&& this._contextMenuRef
|
&& this._contextMenuRef
|
||||||
&& this._contextMenuRef.contains
|
&& this._contextMenuRef.contains
|
||||||
|
@ -263,7 +258,6 @@ class Popover extends Component<Props, State> {
|
||||||
*/
|
*/
|
||||||
_onHideDialog() {
|
_onHideDialog() {
|
||||||
this.setState({
|
this.setState({
|
||||||
showDialog: false,
|
|
||||||
contextMenuStyle: null
|
contextMenuStyle: null
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -284,14 +278,11 @@ class Popover extends Component<Props, State> {
|
||||||
*/
|
*/
|
||||||
_onShowDialog(event) {
|
_onShowDialog(event) {
|
||||||
event && event.stopPropagation();
|
event && event.stopPropagation();
|
||||||
if (!this.props.disablePopover) {
|
|
||||||
this.setState({ showDialog: true });
|
|
||||||
|
|
||||||
if (this.props.onPopoverOpen) {
|
if (!this.props.disablePopover) {
|
||||||
this.props.onPopoverOpen();
|
this.props.onPopoverOpen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_onThumbClick: (Object) => void;
|
_onThumbClick: (Object) => void;
|
||||||
|
|
||||||
|
@ -319,7 +310,7 @@ class Popover extends Component<Props, State> {
|
||||||
_onKeyPress(e) {
|
_onKeyPress(e) {
|
||||||
if (e.key === ' ' || e.key === 'Enter') {
|
if (e.key === ' ' || e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.state.showDialog) {
|
if (this.props.visible) {
|
||||||
this._onHideDialog();
|
this._onHideDialog();
|
||||||
} else {
|
} else {
|
||||||
this._onShowDialog(e);
|
this._onShowDialog(e);
|
||||||
|
@ -340,7 +331,7 @@ class Popover extends Component<Props, State> {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (this.state.showDialog) {
|
if (this.props.visible) {
|
||||||
this._onHideDialog();
|
this._onHideDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -373,11 +364,15 @@ class Popover extends Component<Props, State> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
_renderContent() {
|
_renderContent() {
|
||||||
const { content } = this.props;
|
const { content, paddedContent } = this.props;
|
||||||
|
const className = clsx(
|
||||||
|
'popover popupmenu',
|
||||||
|
paddedContent && 'padded-content'
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className = 'popover popupmenu'
|
className = { className }
|
||||||
onKeyDown = { this._onEscKey }>
|
onKeyDown = { this._onEscKey }>
|
||||||
{ content }
|
{ content }
|
||||||
{!isMobileBrowser() && (
|
{!isMobileBrowser() && (
|
||||||
|
@ -392,4 +387,18 @@ class Popover extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Popover;
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated {@code Popover}'s props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @param {Object} ownProps - The own props of the component.
|
||||||
|
* @private
|
||||||
|
* @returns {Props}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
overflowDrawer: state['features/toolbox'].overflowDrawer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(Popover);
|
||||||
|
|
|
@ -109,13 +109,21 @@ type Props = AbstractProps & {
|
||||||
t: Function,
|
t: Function,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type State = AbstractState & {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether popover is ivisible or not.
|
||||||
|
*/
|
||||||
|
popoverVisible: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a React {@link Component} which displays the current connection
|
* Implements a React {@link Component} which displays the current connection
|
||||||
* quality percentage and has a popover to show more detailed connection stats.
|
* quality percentage and has a popover to show more detailed connection stats.
|
||||||
*
|
*
|
||||||
* @extends {Component}
|
* @extends {Component}
|
||||||
*/
|
*/
|
||||||
class ConnectionIndicator extends AbstractConnectionIndicator<Props, AbstractState> {
|
class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||||
/**
|
/**
|
||||||
* Initializes a new {@code ConnectionIndicator} instance.
|
* Initializes a new {@code ConnectionIndicator} instance.
|
||||||
*
|
*
|
||||||
|
@ -126,10 +134,12 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, AbstractSta
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
autoHideTimeout: undefined,
|
|
||||||
showIndicator: false,
|
showIndicator: false,
|
||||||
stats: {}
|
stats: {},
|
||||||
|
popoverVisible: false
|
||||||
};
|
};
|
||||||
|
this._onShowPopover = this._onShowPopover.bind(this);
|
||||||
|
this._onHidePopover = this._onHidePopover.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,6 +149,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, AbstractSta
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
|
const { iconSize, enableStatsDisplay, participantId, statsPopoverPosition } = this.props;
|
||||||
const visibilityClass = this._getVisibilityClass();
|
const visibilityClass = this._getVisibilityClass();
|
||||||
const rootClassNames = `indicator-container ${visibilityClass}`;
|
const rootClassNames = `indicator-container ${visibilityClass}`;
|
||||||
|
|
||||||
|
@ -151,13 +162,19 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, AbstractSta
|
||||||
className = { rootClassNames }
|
className = { rootClassNames }
|
||||||
content = { <ConnectionIndicatorContent
|
content = { <ConnectionIndicatorContent
|
||||||
inheritedStats = { this.state.stats }
|
inheritedStats = { this.state.stats }
|
||||||
participantId = { this.props.participantId } /> }
|
participantId = { participantId } /> }
|
||||||
disablePopover = { !this.props.enableStatsDisplay }
|
disablePopover = { !enableStatsDisplay }
|
||||||
position = { this.props.statsPopoverPosition }>
|
id = 'participant-connection-indicator'
|
||||||
|
noPaddingContent = { true }
|
||||||
|
onPopoverClose = { this._onHidePopover }
|
||||||
|
onPopoverOpen = { this._onShowPopover }
|
||||||
|
paddedContent = { true }
|
||||||
|
position = { statsPopoverPosition }
|
||||||
|
visible = { this.state.popoverVisible }>
|
||||||
<div className = 'popover-trigger'>
|
<div className = 'popover-trigger'>
|
||||||
<div
|
<div
|
||||||
className = { indicatorContainerClassNames }
|
className = { indicatorContainerClassNames }
|
||||||
style = {{ fontSize: this.props.iconSize }}>
|
style = {{ fontSize: iconSize }}>
|
||||||
<div className = 'connection indicatoricon'>
|
<div className = 'connection indicatoricon'>
|
||||||
{ this._renderIcon() }
|
{ this._renderIcon() }
|
||||||
</div>
|
</div>
|
||||||
|
@ -226,6 +243,18 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, AbstractSta
|
||||||
? 'show-connection-indicator' : 'hide-connection-indicator';
|
? 'show-connection-indicator' : 'hide-connection-indicator';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onHidePopover: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides popover.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onHidePopover() {
|
||||||
|
this.setState({ popoverVisible: false });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a ReactElement for displaying an icon that represents the current
|
* Creates a ReactElement for displaying an icon that represents the current
|
||||||
* connection quality.
|
* connection quality.
|
||||||
|
@ -286,6 +315,18 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, AbstractSta
|
||||||
</span>
|
</span>
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onShowPopover: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows popover.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onShowPopover() {
|
||||||
|
this.setState({ popoverVisible: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -66,7 +66,12 @@ export type State = {|
|
||||||
/**
|
/**
|
||||||
* Indicates whether the thumbnail is hovered or not.
|
* Indicates whether the thumbnail is hovered or not.
|
||||||
*/
|
*/
|
||||||
isHovered: boolean
|
isHovered: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether popover is visible or not.
|
||||||
|
*/
|
||||||
|
popoverVisible: boolean
|
||||||
|};
|
|};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -277,18 +282,19 @@ class Thumbnail extends Component<Props, State> {
|
||||||
audioLevel: 0,
|
audioLevel: 0,
|
||||||
canPlayEventReceived: false,
|
canPlayEventReceived: false,
|
||||||
isHovered: false,
|
isHovered: false,
|
||||||
displayMode: DISPLAY_VIDEO
|
displayMode: DISPLAY_VIDEO,
|
||||||
|
popoverVisible: false
|
||||||
};
|
};
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...state,
|
...state,
|
||||||
displayMode: computeDisplayMode(Thumbnail.getDisplayModeInput(props, state))
|
displayMode: computeDisplayMode(Thumbnail.getDisplayModeInput(props, state)),
|
||||||
|
popoverVisible: false
|
||||||
};
|
};
|
||||||
this.timeoutHandle = null;
|
this.timeoutHandle = null;
|
||||||
this.videoMenuTriggerRef = null;
|
this.videoMenuTriggerRef = null;
|
||||||
|
|
||||||
this._clearDoubleClickTimeout = this._clearDoubleClickTimeout.bind(this);
|
this._clearDoubleClickTimeout = this._clearDoubleClickTimeout.bind(this);
|
||||||
this._setInstance = this._setInstance.bind(this);
|
|
||||||
this._updateAudioLevel = this._updateAudioLevel.bind(this);
|
this._updateAudioLevel = this._updateAudioLevel.bind(this);
|
||||||
this._onCanPlay = this._onCanPlay.bind(this);
|
this._onCanPlay = this._onCanPlay.bind(this);
|
||||||
this._onClick = this._onClick.bind(this);
|
this._onClick = this._onClick.bind(this);
|
||||||
|
@ -299,7 +305,8 @@ class Thumbnail extends Component<Props, State> {
|
||||||
this._onTouchStart = this._onTouchStart.bind(this);
|
this._onTouchStart = this._onTouchStart.bind(this);
|
||||||
this._onTouchEnd = this._onTouchEnd.bind(this);
|
this._onTouchEnd = this._onTouchEnd.bind(this);
|
||||||
this._onTouchMove = this._onTouchMove.bind(this);
|
this._onTouchMove = this._onTouchMove.bind(this);
|
||||||
this._showPopupMenu = this._showPopupMenu.bind(this);
|
this._showPopover = this._showPopover.bind(this);
|
||||||
|
this._hidePopover = this._hidePopover.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -503,6 +510,34 @@ class Thumbnail extends Component<Props, State> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_showPopover: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows popover.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_showPopover() {
|
||||||
|
this.setState({
|
||||||
|
popoverVisible: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_hidePopover: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides popover.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_hidePopover() {
|
||||||
|
this.setState({
|
||||||
|
popoverVisible: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object with the styles for thumbnail.
|
* Returns an object with the styles for thumbnail.
|
||||||
*
|
*
|
||||||
|
@ -611,19 +646,6 @@ class Thumbnail extends Component<Props, State> {
|
||||||
this.setState({ isHovered: false });
|
this.setState({ isHovered: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
_showPopupMenu: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers showing the popover context menu.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_showPopupMenu() {
|
|
||||||
if (this.videoMenuTriggerRef) {
|
|
||||||
this.videoMenuTriggerRef.showContextMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onTouchStart: () => void;
|
_onTouchStart: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -632,7 +654,7 @@ class Thumbnail extends Component<Props, State> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onTouchStart() {
|
_onTouchStart() {
|
||||||
this.timeoutHandle = setTimeout(this._showPopupMenu, SHOW_TOOLBAR_CONTEXT_MENU_AFTER);
|
this.timeoutHandle = setTimeout(this._showPopover, SHOW_TOOLBAR_CONTEXT_MENU_AFTER);
|
||||||
|
|
||||||
if (this._firstTap) {
|
if (this._firstTap) {
|
||||||
this._clearDoubleClickTimeout();
|
this._clearDoubleClickTimeout();
|
||||||
|
@ -889,7 +911,9 @@ class Thumbnail extends Component<Props, State> {
|
||||||
</span>
|
</span>
|
||||||
<span className = 'localvideomenu'>
|
<span className = 'localvideomenu'>
|
||||||
<LocalVideoMenuTriggerButton
|
<LocalVideoMenuTriggerButton
|
||||||
getRef = { this._setInstance } />
|
hidePopover = { this._hidePopover }
|
||||||
|
popoverVisible = { this.state.popoverVisible }
|
||||||
|
showPopover = { this._showPopover } />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
@ -935,19 +959,6 @@ class Thumbnail extends Component<Props, State> {
|
||||||
dispatch(updateLastTrackVideoMediaEvent(jitsiVideoTrack, event.type));
|
dispatch(updateLastTrackVideoMediaEvent(jitsiVideoTrack, event.type));
|
||||||
}
|
}
|
||||||
|
|
||||||
_setInstance: Object => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the local or remote video menu button instance in a variable.
|
|
||||||
*
|
|
||||||
* @param {Object} instance - The local or remote video menu trigger instance.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_setInstance(instance) {
|
|
||||||
this.videoMenuTriggerRef = instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a remote participant's 'thumbnail.
|
* Renders a remote participant's 'thumbnail.
|
||||||
*
|
*
|
||||||
|
@ -1030,10 +1041,12 @@ class Thumbnail extends Component<Props, State> {
|
||||||
</span>
|
</span>
|
||||||
<span className = 'remotevideomenu'>
|
<span className = 'remotevideomenu'>
|
||||||
<RemoteVideoMenuTriggerButton
|
<RemoteVideoMenuTriggerButton
|
||||||
getRef = { this._setInstance }
|
hidePopover = { this._hidePopover }
|
||||||
initialVolumeValue = { _volume }
|
initialVolumeValue = { _volume }
|
||||||
onVolumeChange = { onVolumeChange }
|
onVolumeChange = { onVolumeChange }
|
||||||
participantID = { id } />
|
participantID = { id }
|
||||||
|
popoverVisible = { this.state.popoverVisible }
|
||||||
|
showPopover = { this._showPopover } />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -260,10 +260,9 @@ class MeetingParticipantContextMenu extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onSendPrivateMessage() {
|
_onSendPrivateMessage() {
|
||||||
const { closeDrawer, dispatch, overflowDrawer } = this.props;
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
dispatch(openChatById(this._getCurrentParticipantId()));
|
dispatch(openChatById(this._getCurrentParticipantId()));
|
||||||
overflowDrawer && closeDrawer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onVolumeChange: (number) => void;
|
_onVolumeChange: (number) => void;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import { batch } from 'react-redux';
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -11,6 +11,7 @@ import { translate } from '../../base/i18n';
|
||||||
import { IconTileView } from '../../base/icons';
|
import { IconTileView } from '../../base/icons';
|
||||||
import { connect } from '../../base/redux';
|
import { connect } from '../../base/redux';
|
||||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
||||||
|
import { setOverflowMenuVisible } from '../../toolbox/actions';
|
||||||
import { setTileView } from '../actions';
|
import { setTileView } from '../actions';
|
||||||
import { shouldDisplayTileView } from '../functions';
|
import { shouldDisplayTileView } from '../functions';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
@ -68,7 +69,11 @@ class TileViewButton<P: Props> extends AbstractButton<P, *> {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
logger.debug(`Tile view ${value ? 'enable' : 'disable'}`);
|
logger.debug(`Tile view ${value ? 'enable' : 'disable'}`);
|
||||||
|
batch(() => {
|
||||||
dispatch(setTileView(value));
|
dispatch(setTileView(value));
|
||||||
|
dispatch(setOverflowMenuVisible(false));
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,6 +23,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
dispatch: Function,
|
dispatch: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click handler executed aside from the main action.
|
||||||
|
*/
|
||||||
|
onClick?: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked to obtain translated strings.
|
* Invoked to obtain translated strings.
|
||||||
*/
|
*/
|
||||||
|
@ -77,8 +82,9 @@ class FlipLocalVideoButton extends PureComponent<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onClick() {
|
_onClick() {
|
||||||
const { _localFlipX, dispatch } = this.props;
|
const { _localFlipX, dispatch, onClick } = this.props;
|
||||||
|
|
||||||
|
onClick && onClick();
|
||||||
dispatch(updateSettings({
|
dispatch(updateSettings({
|
||||||
localFlipX: !_localFlipX
|
localFlipX: !_localFlipX
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -38,6 +38,21 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
getRef: Function,
|
getRef: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides popover.
|
||||||
|
*/
|
||||||
|
hidePopover: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the popover is visible or not.
|
||||||
|
*/
|
||||||
|
popoverVisible: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows popover.
|
||||||
|
*/
|
||||||
|
showPopover: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the local participant.
|
* The id of the local participant.
|
||||||
*/
|
*/
|
||||||
|
@ -78,10 +93,6 @@ type Props = {
|
||||||
* @extends {Component}
|
* @extends {Component}
|
||||||
*/
|
*/
|
||||||
class LocalVideoMenuTriggerButton extends Component<Props> {
|
class LocalVideoMenuTriggerButton extends Component<Props> {
|
||||||
/**
|
|
||||||
* Reference to the Popover instance.
|
|
||||||
*/
|
|
||||||
popoverRef: Object;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new LocalVideoMenuTriggerButton instance.
|
* Initializes a new LocalVideoMenuTriggerButton instance.
|
||||||
|
@ -92,45 +103,10 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.popoverRef = React.createRef();
|
|
||||||
this._onPopoverClose = this._onPopoverClose.bind(this);
|
this._onPopoverClose = this._onPopoverClose.bind(this);
|
||||||
this._onPopoverOpen = this._onPopoverOpen.bind(this);
|
this._onPopoverOpen = this._onPopoverOpen.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers showing the popover's context menu.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
showContextMenu() {
|
|
||||||
if (this.popoverRef && this.popoverRef.current) {
|
|
||||||
this.popoverRef.current.showDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls the ref(instance) getter.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
if (this.props.getRef) {
|
|
||||||
this.props.getRef(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls the ref(instance) getter.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.props.getRef) {
|
|
||||||
this.props.getRef(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements React's {@link Component#render()}.
|
* Implements React's {@link Component#render()}.
|
||||||
|
@ -145,6 +121,8 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
|
||||||
_showConnectionInfo,
|
_showConnectionInfo,
|
||||||
_overflowDrawer,
|
_overflowDrawer,
|
||||||
_showLocalVideoFlipButton,
|
_showLocalVideoFlipButton,
|
||||||
|
hidePopover,
|
||||||
|
popoverVisible,
|
||||||
t
|
t
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -152,7 +130,7 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
|
||||||
? <ConnectionIndicatorContent participantId = { _localParticipantId } />
|
? <ConnectionIndicatorContent participantId = { _localParticipantId } />
|
||||||
: (
|
: (
|
||||||
<VideoMenu id = 'localVideoMenu'>
|
<VideoMenu id = 'localVideoMenu'>
|
||||||
<FlipLocalVideoButton />
|
<FlipLocalVideoButton onClick = { hidePopover } />
|
||||||
{ isMobileBrowser()
|
{ isMobileBrowser()
|
||||||
&& <ConnectionStatusButton participantId = { _localParticipantId } />
|
&& <ConnectionStatusButton participantId = { _localParticipantId } />
|
||||||
}
|
}
|
||||||
|
@ -163,11 +141,12 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
|
||||||
isMobileBrowser() || _showLocalVideoFlipButton
|
isMobileBrowser() || _showLocalVideoFlipButton
|
||||||
? <Popover
|
? <Popover
|
||||||
content = { content }
|
content = { content }
|
||||||
|
id = 'local-video-menu-trigger'
|
||||||
onPopoverClose = { this._onPopoverClose }
|
onPopoverClose = { this._onPopoverClose }
|
||||||
onPopoverOpen = { this._onPopoverOpen }
|
onPopoverOpen = { this._onPopoverOpen }
|
||||||
overflowDrawer = { _overflowDrawer }
|
overflowDrawer = { _overflowDrawer }
|
||||||
position = { _menuPosition }
|
position = { _menuPosition }
|
||||||
ref = { this.popoverRef }>
|
visible = { popoverVisible }>
|
||||||
{!_overflowDrawer && (
|
{!_overflowDrawer && (
|
||||||
<span
|
<span
|
||||||
className = 'popover-trigger local-video-menu-trigger'>
|
className = 'popover-trigger local-video-menu-trigger'>
|
||||||
|
@ -194,7 +173,10 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onPopoverOpen() {
|
_onPopoverOpen() {
|
||||||
this.props.dispatch(setParticipantContextMenuOpen(true));
|
const { dispatch, showPopover } = this.props;
|
||||||
|
|
||||||
|
showPopover();
|
||||||
|
dispatch(setParticipantContextMenuOpen(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPopoverClose: () => void;
|
_onPopoverClose: () => void;
|
||||||
|
@ -205,8 +187,9 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onPopoverClose() {
|
_onPopoverClose() {
|
||||||
const { dispatch } = this.props;
|
const { hidePopover, dispatch } = this.props;
|
||||||
|
|
||||||
|
hidePopover();
|
||||||
batch(() => {
|
batch(() => {
|
||||||
dispatch(setParticipantContextMenuOpen(false));
|
dispatch(setParticipantContextMenuOpen(false));
|
||||||
dispatch(renderConnectionStatus(false));
|
dispatch(renderConnectionStatus(false));
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
/* eslint-disable react/jsx-handler-names */
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { batch } from 'react-redux';
|
import { batch } from 'react-redux';
|
||||||
|
|
||||||
|
@ -41,6 +42,21 @@ declare var $: Object;
|
||||||
*/
|
*/
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides popover.
|
||||||
|
*/
|
||||||
|
hidePopover: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the popover is visible or not.
|
||||||
|
*/
|
||||||
|
popoverVisible: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows popover.
|
||||||
|
*/
|
||||||
|
showPopover: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not to display the kick button.
|
* Whether or not to display the kick button.
|
||||||
*/
|
*/
|
||||||
|
@ -128,10 +144,6 @@ type Props = {
|
||||||
* @extends {Component}
|
* @extends {Component}
|
||||||
*/
|
*/
|
||||||
class RemoteVideoMenuTriggerButton extends Component<Props> {
|
class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
/**
|
|
||||||
* Reference to the Popover instance.
|
|
||||||
*/
|
|
||||||
popoverRef: Object;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new RemoteVideoMenuTriggerButton instance.
|
* Initializes a new RemoteVideoMenuTriggerButton instance.
|
||||||
|
@ -142,46 +154,10 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.popoverRef = React.createRef();
|
|
||||||
this._onPopoverClose = this._onPopoverClose.bind(this);
|
this._onPopoverClose = this._onPopoverClose.bind(this);
|
||||||
this._onPopoverOpen = this._onPopoverOpen.bind(this);
|
this._onPopoverOpen = this._onPopoverOpen.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers showing the popover's context menu.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
showContextMenu() {
|
|
||||||
if (this.popoverRef && this.popoverRef.current) {
|
|
||||||
this.popoverRef.current.showDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls the ref(instance) getter.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
if (this.props.getRef) {
|
|
||||||
this.props.getRef(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls the ref(instance) getter.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.props.getRef) {
|
|
||||||
this.props.getRef(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements React's {@link Component#render()}.
|
* Implements React's {@link Component#render()}.
|
||||||
*
|
*
|
||||||
|
@ -189,7 +165,13 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { _overflowDrawer, _showConnectionInfo, _participantDisplayName, participantID } = this.props;
|
const {
|
||||||
|
_overflowDrawer,
|
||||||
|
_showConnectionInfo,
|
||||||
|
_participantDisplayName,
|
||||||
|
participantID,
|
||||||
|
popoverVisible
|
||||||
|
} = this.props;
|
||||||
const content = _showConnectionInfo
|
const content = _showConnectionInfo
|
||||||
? <ConnectionIndicatorContent participantId = { participantID } />
|
? <ConnectionIndicatorContent participantId = { participantID } />
|
||||||
: this._renderRemoteVideoMenu();
|
: this._renderRemoteVideoMenu();
|
||||||
|
@ -203,11 +185,11 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
content = { content }
|
content = { content }
|
||||||
|
id = 'remote-video-menu-trigger'
|
||||||
onPopoverClose = { this._onPopoverClose }
|
onPopoverClose = { this._onPopoverClose }
|
||||||
onPopoverOpen = { this._onPopoverOpen }
|
onPopoverOpen = { this._onPopoverOpen }
|
||||||
overflowDrawer = { _overflowDrawer }
|
|
||||||
position = { this.props._menuPosition }
|
position = { this.props._menuPosition }
|
||||||
ref = { this.popoverRef }>
|
visible = { popoverVisible }>
|
||||||
{!_overflowDrawer && (
|
{!_overflowDrawer && (
|
||||||
<span className = 'popover-trigger remote-video-menu-trigger'>
|
<span className = 'popover-trigger remote-video-menu-trigger'>
|
||||||
{!isMobileBrowser() && <Icon
|
{!isMobileBrowser() && <Icon
|
||||||
|
@ -232,7 +214,10 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onPopoverOpen() {
|
_onPopoverOpen() {
|
||||||
this.props.dispatch(setParticipantContextMenuOpen(true));
|
const { dispatch, showPopover } = this.props;
|
||||||
|
|
||||||
|
showPopover();
|
||||||
|
dispatch(setParticipantContextMenuOpen(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPopoverClose: () => void;
|
_onPopoverClose: () => void;
|
||||||
|
@ -243,8 +228,9 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onPopoverClose() {
|
_onPopoverClose() {
|
||||||
const { dispatch } = this.props;
|
const { dispatch, hidePopover } = this.props;
|
||||||
|
|
||||||
|
hidePopover();
|
||||||
batch(() => {
|
batch(() => {
|
||||||
dispatch(setParticipantContextMenuOpen(false));
|
dispatch(setParticipantContextMenuOpen(false));
|
||||||
dispatch(renderConnectionStatus(false));
|
dispatch(renderConnectionStatus(false));
|
||||||
|
@ -271,6 +257,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
participantID
|
participantID
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const actions = [];
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
const showVolumeSlider = !isIosMobileBrowser()
|
const showVolumeSlider = !isIosMobileBrowser()
|
||||||
&& onVolumeChange
|
&& onVolumeChange
|
||||||
|
@ -343,7 +330,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isMobileBrowser()) {
|
if (isMobileBrowser()) {
|
||||||
buttons.push(
|
actions.push(
|
||||||
<ConnectionStatusButton
|
<ConnectionStatusButton
|
||||||
key = 'conn-status'
|
key = 'conn-status'
|
||||||
participantId = { participantID } />
|
participantId = { participantID } />
|
||||||
|
@ -351,7 +338,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showVolumeSlider) {
|
if (showVolumeSlider) {
|
||||||
buttons.push(
|
actions.push(
|
||||||
<VolumeSlider
|
<VolumeSlider
|
||||||
initialValue = { initialVolumeValue }
|
initialValue = { initialVolumeValue }
|
||||||
key = 'volume-slider'
|
key = 'volume-slider'
|
||||||
|
@ -359,10 +346,27 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buttons.length > 0) {
|
if (buttons.length > 0 || actions.length > 0) {
|
||||||
return (
|
return (
|
||||||
<VideoMenu id = { participantID }>
|
<VideoMenu id = { participantID }>
|
||||||
|
<>
|
||||||
|
{ buttons.length > 0
|
||||||
|
&& <li onClick = { this.props.hidePopover }>
|
||||||
|
<ul className = 'popupmenu__list'>
|
||||||
{ buttons }
|
{ buttons }
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
<>
|
||||||
|
{ actions.length > 0
|
||||||
|
&& <li>
|
||||||
|
<ul className = 'popupmenu__list'>
|
||||||
|
{actions}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</>
|
||||||
</VideoMenu>
|
</VideoMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -437,3 +441,4 @@ function _mapStateToProps(state, ownProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate(connect(_mapStateToProps)(RemoteVideoMenuTriggerButton));
|
export default translate(connect(_mapStateToProps)(RemoteVideoMenuTriggerButton));
|
||||||
|
/* eslint-enable react/jsx-handler-names */
|
||||||
|
|
|
@ -61,8 +61,7 @@ export default class VideoMenuButton extends Component<Props> {
|
||||||
/**
|
/**
|
||||||
* KeyPress handler for accessibility.
|
* KeyPress handler for accessibility.
|
||||||
*
|
*
|
||||||
* @param {Object} e - The key event to handle.
|
* @param {Object} e - The synthetic event.
|
||||||
*
|
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onKeyPress(e) {
|
_onKeyPress(e) {
|
||||||
|
|
Loading…
Reference in New Issue