react/features/base/react/components/web/MeetingsList.js (191 lines of code) (raw):
// @flow
import React, { Component } from 'react';
import {
getLocalizedDateFormatter,
getLocalizedDurationFormatter,
translate
} from '../../../i18n';
import { Icon, IconTrash } from '../../../icons';
import Container from './Container';
import Text from './Text';
type Props = {
/**
* Indicates if the list is disabled or not.
*/
disabled: boolean,
/**
* Indicates if the URL should be hidden or not.
*/
hideURL: boolean,
/**
* Function to be invoked when an item is pressed. The item's URL is passed.
*/
onPress: Function,
/**
* Rendered when the list is empty. Should be a rendered element.
*/
listEmptyComponent: Object,
/**
* An array of meetings.
*/
meetings: Array<Object>,
/**
* Handler for deleting an item.
*/
onItemDelete?: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
/**
* Generates a date string for a given date.
*
* @param {Object} date - The date.
* @private
* @returns {string}
*/
function _toDateString(date) {
return getLocalizedDateFormatter(date).format('ll');
}
/**
* Generates a time (interval) string for a given times.
*
* @param {Array<Date>} times - Array of times.
* @private
* @returns {string}
*/
function _toTimeString(times) {
if (times && times.length > 0) {
return (
times
.map(time => getLocalizedDateFormatter(time).format('LT'))
.join(' - '));
}
return undefined;
}
/**
* Implements a React/Web {@link Component} for displaying a list with
* meetings.
*
* @augments Component
*/
class MeetingsList extends Component<Props> {
/**
* Constructor of the MeetingsList component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onPress = this._onPress.bind(this);
this._renderItem = this._renderItem.bind(this);
}
/**
* Renders the content of this component.
*
* @returns {React.ReactNode}
*/
render() {
const { listEmptyComponent, meetings, t } = this.props;
/**
* If there are no recent meetings we don't want to display anything.
*/
if (meetings) {
return (
<Container
aria-label = { t('welcomepage.recentList') }
className = 'meetings-list'
role = 'menu'
tabIndex = '-1'>
{
meetings.length === 0
? listEmptyComponent
: meetings.map(this._renderItem)
}
</Container>
);
}
return null;
}
_onPress: string => Function;
/**
* Returns a function that is used in the onPress callback of the items.
*
* @param {string} url - The URL of the item to navigate to.
* @private
* @returns {Function}
*/
_onPress(url) {
const { disabled, onPress } = this.props;
if (!disabled && url && typeof onPress === 'function') {
return () => onPress(url);
}
return null;
}
_onKeyPress: string => Function;
/**
* Returns a function that is used in the onPress callback of the items.
*
* @param {string} url - The URL of the item to navigate to.
* @private
* @returns {Function}
*/
_onKeyPress(url) {
const { disabled, onPress } = this.props;
if (!disabled && url && typeof onPress === 'function') {
return e => {
if (e.key === ' ' || e.key === 'Enter') {
onPress(url);
}
};
}
return null;
}
_onDelete: Object => Function;
/**
* Returns a function that is used on the onDelete callback.
*
* @param {Object} item - The item to be deleted.
* @private
* @returns {Function}
*/
_onDelete(item) {
const { onItemDelete } = this.props;
return evt => {
evt.stopPropagation();
onItemDelete && onItemDelete(item);
};
}
_onDeleteKeyPress: Object => Function;
/**
* Returns a function that is used on the onDelete keypress callback.
*
* @param {Object} item - The item to be deleted.
* @private
* @returns {Function}
*/
_onDeleteKeyPress(item) {
const { onItemDelete } = this.props;
return e => {
if (onItemDelete && (e.key === ' ' || e.key === 'Enter')) {
e.preventDefault();
e.stopPropagation();
onItemDelete(item);
}
};
}
_renderItem: (Object, number) => React$Node;
/**
* Renders an item for the list.
*
* @param {Object} meeting - Information about the meeting.
* @param {number} index - The index of the item.
* @returns {Node}
*/
_renderItem(meeting, index) {
const {
date,
duration,
elementAfter,
time,
title,
url
} = meeting;
const { hideURL = false, onItemDelete, t } = this.props;
const onPress = this._onPress(url);
const onKeyPress = this._onKeyPress(url);
const rootClassName
= `item ${
onPress ? 'with-click-handler' : 'without-click-handler'}`;
return (
<Container
aria-label = { title }
className = { rootClassName }
key = { index }
onClick = { onPress }
onKeyPress = { onKeyPress }
role = 'menuitem'
tabIndex = { 0 }>
<Container className = 'left-column'>
<Text className = 'title'>
{ _toDateString(date) }
</Text>
<Text className = 'subtitle'>
{ _toTimeString(time) }
</Text>
</Container>
<Container className = 'right-column'>
<Text className = 'title'>
{ title }
</Text>
{
hideURL || !url ? null : (
<Text>
{ url }
</Text>)
}
{
typeof duration === 'number' ? (
<Text className = 'subtitle'>
{ getLocalizedDurationFormatter(duration) }
</Text>) : null
}
</Container>
<Container className = 'actions'>
{ elementAfter || null }
{ onItemDelete && <Icon
ariaLabel = { t('welcomepage.recentListDelete') }
className = 'delete-meeting'
onClick = { this._onDelete(meeting) }
onKeyPress = { this._onDeleteKeyPress(meeting) }
role = 'button'
src = { IconTrash }
tabIndex = { 0 } />}
</Container>
</Container>
);
}
}
export default translate(MeetingsList);