This commit is contained in:
408
react/features/base/toolbox/components/AbstractButton.tsx
Normal file
408
react/features/base/toolbox/components/AbstractButton.tsx
Normal file
@@ -0,0 +1,408 @@
|
||||
import React, { Component, ReactElement, ReactNode } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { GestureResponderEvent } from 'react-native';
|
||||
|
||||
import { IStore } from '../../../app/types';
|
||||
import { NOTIFY_CLICK_MODE } from '../../../toolbox/types';
|
||||
import { combineStyles } from '../../styles/functions.any';
|
||||
|
||||
import { Styles } from './AbstractToolboxItem';
|
||||
import ToolboxItem from './ToolboxItem';
|
||||
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Function to be called after the click handler has been processed.
|
||||
*/
|
||||
afterClick?: Function;
|
||||
|
||||
/**
|
||||
* The button's background color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
buttonKey?: string;
|
||||
|
||||
/**
|
||||
* Whether or not the button is displayed in a context menu.
|
||||
*/
|
||||
contextMenu?: boolean;
|
||||
|
||||
/**
|
||||
* An extra class name to be added at the end of the element's class name
|
||||
* in order to enable custom styling.
|
||||
*/
|
||||
customClass?: string;
|
||||
|
||||
/**
|
||||
* Extra styles which will be applied in conjunction with `styles` or
|
||||
* `toggledStyles` when the button is disabled;.
|
||||
*/
|
||||
disabledStyles?: Styles;
|
||||
|
||||
/**
|
||||
* Redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* External handler for click action.
|
||||
*/
|
||||
handleClick?: Function;
|
||||
|
||||
/**
|
||||
* Whether the button open a menu or not.
|
||||
*/
|
||||
isMenuButton?: boolean;
|
||||
|
||||
/**
|
||||
* Notify mode for `toolbarButtonClicked` event -
|
||||
* whether to only notify or to also prevent button click routine.
|
||||
*/
|
||||
notifyMode?: string;
|
||||
|
||||
/**
|
||||
* Whether to show the label or not.
|
||||
*/
|
||||
showLabel?: boolean;
|
||||
|
||||
/**
|
||||
* Collection of styles for the button.
|
||||
*/
|
||||
styles?: Styles;
|
||||
|
||||
/**
|
||||
* Collection of styles for the button, when in toggled state.
|
||||
*/
|
||||
toggledStyles?: Styles;
|
||||
|
||||
/**
|
||||
* From which direction the tooltip should appear, relative to the button.
|
||||
*/
|
||||
tooltipPosition?: string;
|
||||
|
||||
/**
|
||||
* Whether this button is visible or not.
|
||||
*/
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default style for disabled buttons.
|
||||
*/
|
||||
export const defaultDisabledButtonStyles = {
|
||||
iconStyle: {
|
||||
opacity: 0.5
|
||||
},
|
||||
labelStyle: {
|
||||
opacity: 0.5
|
||||
},
|
||||
style: undefined,
|
||||
underlayColor: undefined
|
||||
};
|
||||
|
||||
/**
|
||||
* An abstract implementation of a button.
|
||||
*/
|
||||
export default class AbstractButton<P extends IProps, S = any> extends Component<P, S> {
|
||||
static defaultProps = {
|
||||
afterClick: undefined,
|
||||
disabledStyles: defaultDisabledButtonStyles,
|
||||
showLabel: false,
|
||||
styles: undefined,
|
||||
toggledStyles: undefined,
|
||||
tooltipPosition: 'top',
|
||||
visible: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The button's background color.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* A succinct description of what the button does. Used by accessibility
|
||||
* tools and torture tests.
|
||||
*
|
||||
* If `toggledAccessibilityLabel` is defined, this is used only when the
|
||||
* button is not toggled on.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
accessibilityLabel: string;
|
||||
|
||||
/**
|
||||
* This is the same as `accessibilityLabel`, replacing it when the button
|
||||
* is toggled on.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
toggledAccessibilityLabel: string;
|
||||
|
||||
labelProps: Object;
|
||||
|
||||
/**
|
||||
* The icon of this button.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
icon: Object;
|
||||
|
||||
/**
|
||||
* The text associated with this button. When `showLabel` is set to
|
||||
* {@code true}, it will be displayed alongside the icon.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* The label for this button, when toggled.
|
||||
*/
|
||||
toggledLabel: string;
|
||||
|
||||
/**
|
||||
* The icon of this button, when toggled.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
toggledIcon: Object;
|
||||
|
||||
/**
|
||||
* The text to display in the tooltip. Used only on web.
|
||||
*
|
||||
* If `toggleTooltip` is defined, this is used only when the button is not
|
||||
* toggled on.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
tooltip?: string;
|
||||
|
||||
/**
|
||||
* The text to display in the tooltip when the button is toggled on.
|
||||
*
|
||||
* Used only on web.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
toggledTooltip?: string;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractButton} instance.
|
||||
*
|
||||
* @param {IProps} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractButton} instance with.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which should be used
|
||||
* to handle a key being down.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyDown() {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which should be used
|
||||
* to handle the button being clicked / pressed.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which may return a
|
||||
* new React Element to be appended at the end of the button.
|
||||
*
|
||||
* @protected
|
||||
* @returns {ReactElement|null}
|
||||
*/
|
||||
_getElementAfter(): ReactElement | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current icon, taking the toggled state into account. If no
|
||||
* toggled icon is provided, the regular icon will also be used in the
|
||||
* toggled state.
|
||||
*
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_getIcon() {
|
||||
return (
|
||||
this._isToggled() ? this.toggledIcon : this.icon
|
||||
) || this.icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current label, taking the toggled state into account. If no
|
||||
* toggled label is provided, the regular label will also be used in the
|
||||
* toggled state.
|
||||
*
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_getLabel() {
|
||||
return (this._isToggled() ? this.toggledLabel : this.label)
|
||||
|| this.label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current accessibility label, taking the toggled state into
|
||||
* account. If no toggled label is provided, the regular accessibility label
|
||||
* will also be used in the toggled state.
|
||||
*
|
||||
* The accessibility label is not visible in the UI, it is meant to be
|
||||
* used by assistive technologies, mainly screen readers.
|
||||
*
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_getAccessibilityLabel() {
|
||||
return (this._isToggled()
|
||||
? this.toggledAccessibilityLabel
|
||||
: this.accessibilityLabel
|
||||
) || this.accessibilityLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current styles, taking the toggled state into account. If no
|
||||
* toggled styles are provided, the regular styles will also be used in the
|
||||
* toggled state.
|
||||
*
|
||||
* @private
|
||||
* @returns {?Styles}
|
||||
*/
|
||||
_getStyles(): Styles | undefined {
|
||||
const { disabledStyles, styles, toggledStyles } = this.props;
|
||||
const buttonStyles
|
||||
= (this._isToggled() ? toggledStyles : styles) || styles;
|
||||
|
||||
if (this._isDisabled() && buttonStyles && disabledStyles) {
|
||||
return {
|
||||
iconStyle: combineStyles(
|
||||
buttonStyles.iconStyle ?? {}, disabledStyles.iconStyle ?? {}),
|
||||
labelStyle: combineStyles(
|
||||
buttonStyles.labelStyle ?? {}, disabledStyles.labelStyle ?? {}),
|
||||
style: combineStyles(
|
||||
buttonStyles.style ?? {}, disabledStyles.style ?? {}),
|
||||
underlayColor:
|
||||
disabledStyles.underlayColor || buttonStyles.underlayColor
|
||||
};
|
||||
}
|
||||
|
||||
return buttonStyles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tooltip to display when hovering over the button.
|
||||
*
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_getTooltip() {
|
||||
return (this._isToggled() ? this.toggledTooltip : this.tooltip)
|
||||
|| this.tooltip
|
||||
|| '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* boolean value indicating if this button is disabled or not.
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* {@code boolean} value indicating if this button is toggled or not or
|
||||
* undefined if the button is not toggleable.
|
||||
*
|
||||
* @protected
|
||||
* @returns {?boolean}
|
||||
*/
|
||||
_isToggled(): boolean | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @param {Object} e - Event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick(e?: React.MouseEvent | GestureResponderEvent) {
|
||||
const { afterClick, buttonKey, handleClick, notifyMode } = this.props;
|
||||
|
||||
if (typeof APP !== 'undefined' && notifyMode) {
|
||||
APP.API.notifyToolbarButtonClicked(
|
||||
buttonKey, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
);
|
||||
}
|
||||
|
||||
if (notifyMode !== NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
|
||||
if (handleClick) {
|
||||
handleClick();
|
||||
}
|
||||
|
||||
this._handleClick();
|
||||
}
|
||||
|
||||
afterClick?.(e);
|
||||
|
||||
// blur after click to release focus from button to allow PTT.
|
||||
// @ts-ignore
|
||||
e?.currentTarget?.blur?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
override render(): ReactNode {
|
||||
const props: any = {
|
||||
...this.props,
|
||||
accessibilityLabel: this._getAccessibilityLabel(),
|
||||
elementAfter: this._getElementAfter(),
|
||||
icon: this._getIcon(),
|
||||
label: this._getLabel(),
|
||||
labelProps: this.labelProps,
|
||||
styles: this._getStyles(),
|
||||
toggled: this._isToggled(),
|
||||
tooltip: this._getTooltip()
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolboxItem
|
||||
disabled = { this._isDisabled() }
|
||||
onClick = { this._onClick }
|
||||
onKeyDown = { this._onKeyDown }
|
||||
{ ...props } />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { IconHangup } from '../../icons/svg';
|
||||
|
||||
import AbstractButton, { IProps } from './AbstractButton';
|
||||
|
||||
/**
|
||||
* An abstract implementation of a button for disconnecting a conference.
|
||||
*/
|
||||
export default class AbstractHangupButton<P extends IProps, S=any>
|
||||
extends AbstractButton<P, S> {
|
||||
|
||||
override icon = IconHangup;
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and disconnects the conference.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
this._doHangup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform the actual hangup action.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_doHangup() {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
}
|
||||
229
react/features/base/toolbox/components/AbstractToolboxItem.tsx
Normal file
229
react/features/base/toolbox/components/AbstractToolboxItem.tsx
Normal file
@@ -0,0 +1,229 @@
|
||||
import React, { Component, ReactElement, ReactNode } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { GestureResponderEvent } from 'react-native';
|
||||
|
||||
import type { StyleType } from '../../styles/functions.any';
|
||||
import { TOOLTIP_POSITION } from '../../ui/constants.any';
|
||||
|
||||
export type Styles = {
|
||||
|
||||
/**
|
||||
* Style for the item's icon.
|
||||
*/
|
||||
iconStyle?: StyleType;
|
||||
|
||||
/**
|
||||
* Style for the item's label.
|
||||
*/
|
||||
labelStyle?: StyleType;
|
||||
|
||||
/**
|
||||
* Style for the item itself.
|
||||
*/
|
||||
style?: StyleType;
|
||||
|
||||
/**
|
||||
* Color for the item underlay (shows when clicked).
|
||||
*/
|
||||
underlayColor?: string;
|
||||
};
|
||||
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* A succinct description of what the item does. Used by accessibility
|
||||
* tools and torture tests.
|
||||
*/
|
||||
accessibilityLabel: string;
|
||||
|
||||
/**
|
||||
* An extra class name to be added at the end of the element's class name
|
||||
* in order to enable custom styling.
|
||||
*/
|
||||
customClass?: string;
|
||||
|
||||
/**
|
||||
* Whether this item is disabled or not. When disabled, clicking an the item
|
||||
* has no effect, and it may reflect on its style.
|
||||
*/
|
||||
disabled: boolean;
|
||||
|
||||
/**
|
||||
* A React Element to display at the end of {@code ToolboxItem}.
|
||||
*/
|
||||
elementAfter?: ReactNode;
|
||||
|
||||
/**
|
||||
* The icon to render for this {@code ToolboxItem}.
|
||||
*/
|
||||
icon: Function;
|
||||
|
||||
/**
|
||||
* The text associated with this item. When `showLabel` is set to
|
||||
* {@code true}, it will be displayed alongside the icon.
|
||||
*/
|
||||
label: string;
|
||||
|
||||
labelProps: any;
|
||||
|
||||
/**
|
||||
* On click handler.
|
||||
*/
|
||||
onClick: (e?: React.MouseEvent<HTMLElement> | GestureResponderEvent) => void;
|
||||
|
||||
/**
|
||||
* Whether to show the label or not.
|
||||
*/
|
||||
showLabel: boolean;
|
||||
|
||||
/**
|
||||
* Collection of styles for the item. Used only on native.
|
||||
*/
|
||||
styles?: Styles;
|
||||
|
||||
/**
|
||||
* True if the item is toggled, false otherwise.
|
||||
*/
|
||||
toggled?: boolean;
|
||||
|
||||
/**
|
||||
* The text to display in the tooltip. Used only on web.
|
||||
*/
|
||||
tooltip?: string;
|
||||
|
||||
/**
|
||||
* From which direction the tooltip should appear, relative to the
|
||||
* item. Used only on web.
|
||||
*/
|
||||
tooltipPosition: TOOLTIP_POSITION;
|
||||
|
||||
/**
|
||||
* Whether this item is visible or not.
|
||||
*/
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract (base) class for an item in {@link Toolbox}. The item can be located
|
||||
* anywhere in the {@link Toolbox}, it will morph its shape to accommodate it.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
export default class AbstractToolboxItem<P extends IProps> extends Component<P> {
|
||||
/**
|
||||
* Default values for {@code AbstractToolboxItem} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
disabled: false,
|
||||
label: '',
|
||||
showLabel: false,
|
||||
t: undefined,
|
||||
tooltip: '',
|
||||
tooltipPosition: 'top',
|
||||
visible: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractToolboxItem} instance.
|
||||
*
|
||||
* @param {Object} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractToolboxItem} instance with.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper property to get the item label. If a translation function was
|
||||
* provided then it will be translated using it.
|
||||
*
|
||||
* @protected
|
||||
* @returns {?string}
|
||||
*/
|
||||
get label(): string | undefined {
|
||||
return this._maybeTranslateAttribute(this.props.label, this.props.labelProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper property to get the item tooltip. If a translation function was
|
||||
* provided then it will be translated using it.
|
||||
*
|
||||
* @protected
|
||||
* @returns {?string}
|
||||
*/
|
||||
get tooltip(): string | undefined {
|
||||
return this._maybeTranslateAttribute(this.props.tooltip ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper property to get the item accessibilityLabel. If a translation
|
||||
* function was provided then it will be translated using it.
|
||||
*
|
||||
* @protected
|
||||
* @returns {?string}
|
||||
*/
|
||||
get accessibilityLabel(): string {
|
||||
return this._maybeTranslateAttribute(this.props.accessibilityLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to translate the given string, if a translation
|
||||
* function is available.
|
||||
*
|
||||
* @param {string} text - What needs translating.
|
||||
* @param {string} textProps - Additional properties for translation text.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_maybeTranslateAttribute(text: string, textProps?: any) {
|
||||
const { t } = this.props;
|
||||
|
||||
if (textProps) {
|
||||
|
||||
return typeof t === 'function' ? t(text, textProps) : `${text} ${textProps}`;
|
||||
}
|
||||
|
||||
return typeof t === 'function' ? t(text) : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking/pressing this {@code AbstractToolboxItem} by
|
||||
* forwarding the event to the {@code onClick} prop of this instance if any.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick(...args: any) {
|
||||
const { disabled, onClick } = this.props;
|
||||
|
||||
disabled || onClick?.(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders this {@code AbstractToolboxItem} (if it is {@code visible}). To
|
||||
* be implemented/overridden by extenders. The default implementation of
|
||||
* {@code AbstractToolboxItem} does nothing.
|
||||
*
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderItem(): ReactElement | null {
|
||||
// To be implemented by a subclass.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
return this.props.visible ? this._renderItem() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { IconMic, IconMicSlash } from '../../icons/svg';
|
||||
|
||||
import AbstractButton, { IProps } from './AbstractButton';
|
||||
|
||||
/**
|
||||
* An abstract implementation of a button for toggling audio mute.
|
||||
*/
|
||||
export default class BaseAudioMuteButton<P extends IProps, S=any>
|
||||
extends AbstractButton<P, S> {
|
||||
|
||||
override icon = IconMic;
|
||||
override toggledIcon = IconMicSlash;
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and toggles the audio mute state
|
||||
* accordingly.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
this._setAudioMuted(!this._isAudioMuted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* boolean value indicating if audio is muted or not.
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isAudioMuted() {
|
||||
// To be implemented by subclass.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _isToggled() {
|
||||
return this._isAudioMuted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform the actual setting of the audio mute / unmute
|
||||
* action.
|
||||
*
|
||||
* @param {boolean} _audioMuted - Whether audio should be muted or not.
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_setAudioMuted(_audioMuted: boolean) {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { IconVideo, IconVideoOff } from '../../icons/svg';
|
||||
|
||||
import AbstractButton, { IProps } from './AbstractButton';
|
||||
|
||||
/**
|
||||
* An abstract implementation of a button for toggling video mute.
|
||||
*/
|
||||
export default class BaseVideoMuteButton<P extends IProps, S=any>
|
||||
extends AbstractButton<P, S> {
|
||||
|
||||
override icon = IconVideo;
|
||||
override toggledIcon = IconVideoOff;
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and toggles the video mute state
|
||||
* accordingly.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
this._setVideoMuted(!this._isVideoMuted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _isToggled() {
|
||||
return this._isVideoMuted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* {@code boolean} value indicating if video is muted or not.
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isVideoMuted() {
|
||||
// To be implemented by subclass.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform the actual setting of the video mute / unmute
|
||||
* action.
|
||||
*
|
||||
* @param {boolean} _videoMuted - Whether video should be muted or not.
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_setVideoMuted(_videoMuted: boolean) {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { Text, TextStyle, TouchableHighlight, View, ViewStyle } from 'react-native';
|
||||
|
||||
import Icon from '../../icons/components/Icon';
|
||||
|
||||
import AbstractToolboxItem, { IProps } from './AbstractToolboxItem';
|
||||
|
||||
/**
|
||||
* Native implementation of {@code AbstractToolboxItem}.
|
||||
*/
|
||||
export default class ToolboxItem extends AbstractToolboxItem<IProps> {
|
||||
/**
|
||||
* Renders the {@code Icon} part of this {@code ToolboxItem}.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderIcon() {
|
||||
const { styles } = this.props;
|
||||
|
||||
return (
|
||||
<Icon
|
||||
src = { this.props.icon }
|
||||
style = { styles?.iconStyle } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders this {@code ToolboxItem}. Invoked by {@link AbstractToolboxItem}.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderItem() {
|
||||
const {
|
||||
disabled,
|
||||
elementAfter,
|
||||
onClick,
|
||||
showLabel,
|
||||
styles,
|
||||
toggled
|
||||
} = this.props;
|
||||
|
||||
let children = this._renderIcon();
|
||||
|
||||
// XXX When using a wrapper View, apply the style to it instead of
|
||||
// applying it to the TouchableHighlight.
|
||||
let style = styles?.style;
|
||||
|
||||
if (showLabel) {
|
||||
// XXX TouchableHighlight requires 1 child. If there's a need to
|
||||
// show both the icon and the label, then these two need to be
|
||||
// wrapped in a View.
|
||||
children = (
|
||||
<View style = { style as ViewStyle }>
|
||||
{ children }
|
||||
<Text style = { styles?.labelStyle as TextStyle }>
|
||||
{ this.label }
|
||||
</Text>
|
||||
{ elementAfter }
|
||||
</View>
|
||||
);
|
||||
|
||||
// XXX As stated earlier, the style was applied to the wrapper View
|
||||
// (above).
|
||||
style = undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
accessibilityLabel = { this.accessibilityLabel }
|
||||
accessibilityRole = { 'button' }
|
||||
accessibilityState = {{ 'selected': Boolean(toggled) }}
|
||||
disabled = { disabled }
|
||||
onPress = { onClick }
|
||||
style = { style as ViewStyle }
|
||||
underlayColor = { styles?.underlayColor } >
|
||||
{ children }
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
||||
161
react/features/base/toolbox/components/ToolboxItem.web.tsx
Normal file
161
react/features/base/toolbox/components/ToolboxItem.web.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import Icon from '../../icons/components/Icon';
|
||||
import Tooltip from '../../tooltip/components/Tooltip';
|
||||
import ContextMenuItem from '../../ui/components/web/ContextMenuItem';
|
||||
|
||||
import {
|
||||
default as AbstractToolboxItem,
|
||||
type IProps as AbstractToolboxItemProps
|
||||
} from './AbstractToolboxItem';
|
||||
|
||||
interface IProps extends AbstractToolboxItemProps {
|
||||
|
||||
/**
|
||||
* The button's background color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Whether or not the item is displayed in a context menu.
|
||||
*/
|
||||
contextMenu?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the button open a menu or not.
|
||||
*/
|
||||
isMenuButton?: boolean;
|
||||
|
||||
/**
|
||||
* On key down handler.
|
||||
*/
|
||||
onKeyDown: (e?: React.KeyboardEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Web implementation of {@code AbstractToolboxItem}.
|
||||
*/
|
||||
export default class ToolboxItem extends AbstractToolboxItem<IProps> {
|
||||
/**
|
||||
* Initializes a new {@code ToolboxItem} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles 'Enter' and Space key on the button to trigger onClick for accessibility.
|
||||
*
|
||||
* @param {Object} event - The key event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(event?: React.KeyboardEvent) {
|
||||
if (event?.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles rendering of the actual item. If the label is being shown, which
|
||||
* is controlled with the `showLabel` prop, the item is rendered for its
|
||||
* display in an overflow menu, otherwise it will only have an icon, which
|
||||
* can be displayed on any toolbar.
|
||||
*
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override _renderItem() {
|
||||
const {
|
||||
backgroundColor,
|
||||
contextMenu,
|
||||
isMenuButton,
|
||||
disabled,
|
||||
elementAfter,
|
||||
icon,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
showLabel,
|
||||
tooltipPosition,
|
||||
toggled
|
||||
} = this.props;
|
||||
const className = showLabel ? 'overflow-menu-item' : 'toolbox-button';
|
||||
const buttonAttribute = isMenuButton ? 'aria-expanded' : 'aria-pressed';
|
||||
const props = {
|
||||
[buttonAttribute]: toggled,
|
||||
'aria-disabled': disabled,
|
||||
'aria-label': this.accessibilityLabel,
|
||||
className: className + (disabled ? ' disabled' : ''),
|
||||
onClick: disabled ? undefined : onClick,
|
||||
onKeyDown: disabled ? undefined : onKeyDown,
|
||||
onKeyPress: this._onKeyPress,
|
||||
tabIndex: 0,
|
||||
role: 'button'
|
||||
};
|
||||
|
||||
const elementType = showLabel ? 'li' : 'div';
|
||||
const useTooltip = this.tooltip && this.tooltip.length > 0;
|
||||
|
||||
if (contextMenu) {
|
||||
return (
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { this.accessibilityLabel }
|
||||
backgroundColor = { backgroundColor }
|
||||
disabled = { disabled }
|
||||
icon = { icon }
|
||||
onClick = { onClick }
|
||||
onKeyDown = { onKeyDown }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
text = { this.label } />
|
||||
);
|
||||
}
|
||||
let children = (
|
||||
<Fragment>
|
||||
{ this._renderIcon() }
|
||||
{ showLabel && <span>
|
||||
{ this.label }
|
||||
</span> }
|
||||
{ elementAfter }
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
if (useTooltip) {
|
||||
children = (
|
||||
<Tooltip
|
||||
content = { this.tooltip ?? '' }
|
||||
position = { tooltipPosition }>
|
||||
{ children }
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return React.createElement(elementType, props, children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to render the item's icon.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderIcon() {
|
||||
const { backgroundColor, customClass, disabled, icon, showLabel, toggled } = this.props;
|
||||
const iconComponent = (<Icon
|
||||
size = { showLabel ? undefined : 24 }
|
||||
src = { icon } />);
|
||||
const elementType = showLabel ? 'span' : 'div';
|
||||
const className = `${showLabel ? 'overflow-menu-item-icon' : 'toolbox-icon'} ${
|
||||
toggled ? 'toggled' : ''} ${disabled ? 'disabled' : ''} ${customClass ?? ''}`;
|
||||
const style = backgroundColor && !showLabel ? { backgroundColor } : {};
|
||||
|
||||
return React.createElement(elementType, {
|
||||
className,
|
||||
style
|
||||
}, iconComponent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import React from 'react';
|
||||
|
||||
import { NOTIFY_CLICK_MODE } from '../../../../toolbox/types';
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import Tooltip from '../../../tooltip/components/Tooltip';
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The id of the element this button icon controls.
|
||||
*/
|
||||
ariaControls?: string;
|
||||
|
||||
/**
|
||||
* Whether the element popup is expanded.
|
||||
*/
|
||||
ariaExpanded?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the element has a popup.
|
||||
*/
|
||||
ariaHasPopup?: boolean;
|
||||
|
||||
/**
|
||||
* Aria label for the Icon.
|
||||
*/
|
||||
ariaLabel?: string;
|
||||
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
buttonKey?: string;
|
||||
|
||||
/**
|
||||
* The decorated component (ToolboxButton).
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
|
||||
/**
|
||||
* Icon of the button.
|
||||
*/
|
||||
icon: Function;
|
||||
|
||||
/**
|
||||
* Flag used for disabling the small icon.
|
||||
*/
|
||||
iconDisabled: boolean;
|
||||
|
||||
/**
|
||||
* The ID of the icon button.
|
||||
*/
|
||||
iconId: string;
|
||||
|
||||
/**
|
||||
* The tooltip used for the icon.
|
||||
*/
|
||||
iconTooltip: string;
|
||||
|
||||
/**
|
||||
* Notify mode for `toolbarButtonClicked` event -
|
||||
* whether to only notify or to also prevent button click routine.
|
||||
*/
|
||||
notifyMode?: string;
|
||||
|
||||
/**
|
||||
* Click handler for the small icon.
|
||||
*/
|
||||
onIconClick: Function;
|
||||
|
||||
/**
|
||||
* Keydown handler for icon.
|
||||
*/
|
||||
onIconKeyDown?: Function;
|
||||
|
||||
/**
|
||||
* Additional styles.
|
||||
*/
|
||||
styles?: Object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the `ToolboxButtonWithIcon` component.
|
||||
*
|
||||
* @param {Object} props - Component's props.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
export default function ToolboxButtonWithIcon(props: IProps) {
|
||||
const {
|
||||
children,
|
||||
icon,
|
||||
iconDisabled,
|
||||
iconTooltip,
|
||||
buttonKey,
|
||||
notifyMode,
|
||||
onIconClick,
|
||||
onIconKeyDown,
|
||||
styles,
|
||||
ariaLabel,
|
||||
ariaHasPopup,
|
||||
ariaControls,
|
||||
ariaExpanded,
|
||||
iconId
|
||||
} = props;
|
||||
|
||||
const iconProps: {
|
||||
ariaControls?: string;
|
||||
ariaExpanded?: boolean;
|
||||
containerId?: string;
|
||||
onClick?: (e?: React.MouseEvent) => void;
|
||||
onKeyDown?: Function;
|
||||
role?: string;
|
||||
tabIndex?: number;
|
||||
} = {};
|
||||
let className = '';
|
||||
|
||||
if (iconDisabled) {
|
||||
className
|
||||
= 'settings-button-small-icon settings-button-small-icon--disabled';
|
||||
} else {
|
||||
className = 'settings-button-small-icon';
|
||||
iconProps.onClick = (e?: React.MouseEvent) => {
|
||||
if (typeof APP !== 'undefined' && notifyMode) {
|
||||
APP.API.notifyToolbarButtonClicked(
|
||||
buttonKey, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
);
|
||||
}
|
||||
|
||||
if (notifyMode !== NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
|
||||
onIconClick(e);
|
||||
}
|
||||
};
|
||||
iconProps.onKeyDown = onIconKeyDown;
|
||||
iconProps.role = 'button';
|
||||
iconProps.tabIndex = 0;
|
||||
iconProps.ariaControls = ariaControls;
|
||||
iconProps.ariaExpanded = ariaExpanded;
|
||||
iconProps.containerId = iconId;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'settings-button-container'
|
||||
style = { styles }>
|
||||
{children}
|
||||
|
||||
<div>
|
||||
<Tooltip
|
||||
containerClassName = { className }
|
||||
content = { iconTooltip }
|
||||
position = 'top'>
|
||||
<Icon
|
||||
{ ...iconProps }
|
||||
ariaHasPopup = { ariaHasPopup }
|
||||
ariaLabel = { ariaLabel }
|
||||
size = { 16 }
|
||||
src = { icon } />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import Popover from '../../../popover/components/Popover.web';
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Aria label for the Icon.
|
||||
*/
|
||||
ariaLabel?: string;
|
||||
|
||||
/**
|
||||
* The decorated component (ToolboxButton).
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
|
||||
/**
|
||||
* Icon of the button.
|
||||
*/
|
||||
icon?: Function;
|
||||
|
||||
/**
|
||||
* Flag used for disabling the small icon.
|
||||
*/
|
||||
iconDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* Popover close callback.
|
||||
*/
|
||||
onPopoverClose: Function;
|
||||
|
||||
/**
|
||||
* Popover open callback.
|
||||
*/
|
||||
onPopoverOpen: Function;
|
||||
|
||||
/**
|
||||
* The content that will be displayed inside the popover.
|
||||
*/
|
||||
popoverContent: React.ReactNode;
|
||||
|
||||
/**
|
||||
* Additional styles.
|
||||
*/
|
||||
styles?: Object;
|
||||
|
||||
/**
|
||||
* Whether the trigger for open/ close should be click or hover.
|
||||
*/
|
||||
trigger?: 'hover' | 'click';
|
||||
|
||||
/**
|
||||
* Whether or not the popover is visible.
|
||||
*/
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the `ToolboxButtonWithIcon` component.
|
||||
*
|
||||
* @param {Object} props - Component's props.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
export default function ToolboxButtonWithPopup(props: IProps) {
|
||||
const {
|
||||
ariaLabel,
|
||||
children,
|
||||
icon,
|
||||
iconDisabled,
|
||||
onPopoverClose,
|
||||
onPopoverOpen,
|
||||
popoverContent,
|
||||
styles,
|
||||
trigger,
|
||||
visible
|
||||
} = props;
|
||||
|
||||
if (!icon) {
|
||||
return (
|
||||
<div
|
||||
className = 'settings-button-container'
|
||||
style = { styles }>
|
||||
<Popover
|
||||
content = { popoverContent }
|
||||
headingLabel = { ariaLabel }
|
||||
onPopoverClose = { onPopoverClose }
|
||||
onPopoverOpen = { onPopoverOpen }
|
||||
position = 'top'
|
||||
trigger = { trigger }
|
||||
visible = { visible }>
|
||||
{children}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'settings-button-container'
|
||||
style = { styles }>
|
||||
{children}
|
||||
<div className = 'settings-button-small-icon-container'>
|
||||
<Popover
|
||||
content = { popoverContent }
|
||||
headingLabel = { ariaLabel }
|
||||
onPopoverClose = { onPopoverClose }
|
||||
onPopoverOpen = { onPopoverOpen }
|
||||
position = 'top'
|
||||
visible = { visible }>
|
||||
<Icon
|
||||
alt = { ariaLabel }
|
||||
className = { `settings-button-small-icon ${iconDisabled
|
||||
? 'settings-button-small-icon--disabled'
|
||||
: ''}` }
|
||||
size = { 16 }
|
||||
src = { icon } />
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user