mirror of https://github.com/jitsi/jitsi-meet
parent
ae0bf876a8
commit
b096622995
@ -0,0 +1,269 @@ |
||||
// @flow
|
||||
import React, { Component } from 'react'; |
||||
import { |
||||
SafeAreaView, |
||||
SectionList, |
||||
Text, |
||||
TouchableHighlight, |
||||
View |
||||
} from 'react-native'; |
||||
|
||||
import styles, { UNDERLAY_COLOR } from './styles'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Indicates if the list is disabled or not. |
||||
*/ |
||||
disabled: boolean, |
||||
|
||||
/** |
||||
* Function to be invoked when an item is pressed. The item's URL is passed. |
||||
*/ |
||||
onPress: Function, |
||||
|
||||
/** |
||||
* Sections to be rendered in the following format: |
||||
* |
||||
* [ |
||||
* { |
||||
* title: string, <- section title |
||||
* key: string, <- unique key for the section |
||||
* data: [ <- Array of items in the section |
||||
* { |
||||
* colorBase: string, <- the color base of the avatar |
||||
* title: string, <- item title |
||||
* url: string, <- item url |
||||
* lines: Array<string> <- additional lines to be rendered |
||||
* } |
||||
* ] |
||||
* } |
||||
* ] |
||||
*/ |
||||
sections: Array<Object> |
||||
} |
||||
|
||||
/** |
||||
* Implements a general section list to display items that have a URL |
||||
* property and navigates to (probably) meetings, such as the recent list |
||||
* or the meeting list components. |
||||
*/ |
||||
export default class NavigateSectionList extends Component<Props> { |
||||
/** |
||||
* Constructor of the NavigateSectionList component. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
constructor(props: Props) { |
||||
super(props); |
||||
|
||||
this._getAvatarColor = this._getAvatarColor.bind(this); |
||||
this._getItemKey = this._getItemKey.bind(this); |
||||
this._onPress = this._onPress.bind(this); |
||||
this._renderItem = this._renderItem.bind(this); |
||||
this._renderItemLine = this._renderItemLine.bind(this); |
||||
this._renderItemLines = this._renderItemLines.bind(this); |
||||
this._renderSection = this._renderSection.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's Component.render function. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
render() { |
||||
const { sections } = this.props; |
||||
|
||||
return ( |
||||
<SafeAreaView |
||||
style = { styles.container } > |
||||
<SectionList |
||||
keyExtractor = { this._getItemKey } |
||||
renderItem = { this._renderItem } |
||||
renderSectionHeader = { this._renderSection } |
||||
sections = { sections } |
||||
style = { styles.list } /> |
||||
</SafeAreaView> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Creates an empty section object. |
||||
* |
||||
* @private |
||||
* @param {string} title - The title of the section. |
||||
* @param {string} key - The key of the section. It must be unique. |
||||
* @returns {Object} |
||||
*/ |
||||
static createSection(title, key) { |
||||
return { |
||||
data: [], |
||||
key, |
||||
title |
||||
}; |
||||
} |
||||
|
||||
_getAvatarColor: string => Object |
||||
|
||||
/** |
||||
* Returns a style (color) based on the string that determines the |
||||
* color of the avatar. |
||||
* |
||||
* @param {string} colorBase - The string that is the base of the color. |
||||
* @private |
||||
* @returns {Object} |
||||
*/ |
||||
_getAvatarColor(colorBase) { |
||||
if (!colorBase) { |
||||
return null; |
||||
} |
||||
|
||||
let nameHash = 0; |
||||
|
||||
for (let i = 0; i < colorBase.length; i++) { |
||||
nameHash += colorBase.codePointAt(i); |
||||
} |
||||
|
||||
return styles[`avatarColor${(nameHash % 5) + 1}`]; |
||||
} |
||||
|
||||
_getItemKey: (Object, number) => string; |
||||
|
||||
/** |
||||
* Generates a unique id to every item. |
||||
* |
||||
* @private |
||||
* @param {Object} item - The item. |
||||
* @param {number} index - The item index. |
||||
* @returns {string} |
||||
*/ |
||||
_getItemKey(item, index) { |
||||
return `${index}-${item.key}`; |
||||
} |
||||
|
||||
_onPress: string => Function |
||||
|
||||
/** |
||||
* Returns a function that is used in the onPress callback of the items. |
||||
* |
||||
* @private |
||||
* @param {string} url - The URL of the item to navigate to. |
||||
* @returns {Function} |
||||
*/ |
||||
_onPress(url) { |
||||
return () => { |
||||
const { disabled, onPress } = this.props; |
||||
|
||||
!disabled && url && typeof onPress === 'function' && onPress(url); |
||||
}; |
||||
} |
||||
|
||||
_renderItem: Object => Object; |
||||
|
||||
/** |
||||
* Renders a single item in the list. |
||||
* |
||||
* @private |
||||
* @param {Object} listItem - The item to render. |
||||
* @returns {Component} |
||||
*/ |
||||
_renderItem(listItem) { |
||||
const { item } = listItem; |
||||
|
||||
return ( |
||||
<TouchableHighlight |
||||
onPress = { this._onPress(item.url) } |
||||
underlayColor = { UNDERLAY_COLOR }> |
||||
<View style = { styles.listItem }> |
||||
<View style = { styles.avatarContainer } > |
||||
<View |
||||
style = { [ |
||||
styles.avatar, |
||||
this._getAvatarColor(item.colorBase) |
||||
] } > |
||||
<Text style = { styles.avatarContent }> |
||||
{ item.title.substr(0, 1).toUpperCase() } |
||||
</Text> |
||||
</View> |
||||
</View> |
||||
<View style = { styles.listItemDetails }> |
||||
<Text |
||||
numberOfLines = { 1 } |
||||
style = { [ |
||||
styles.listItemText, |
||||
styles.listItemTitle |
||||
] }> |
||||
{ item.title } |
||||
</Text> |
||||
{ |
||||
this._renderItemLines(item.lines) |
||||
} |
||||
</View> |
||||
</View> |
||||
</TouchableHighlight> |
||||
); |
||||
} |
||||
|
||||
_renderItemLine: (string, number) => React$Node; |
||||
|
||||
/** |
||||
* Renders a single line from the additional lines. |
||||
* |
||||
* @private |
||||
* @param {string} line - The line text. |
||||
* @param {number} index - The index of the line. |
||||
* @returns {React$Node} |
||||
*/ |
||||
_renderItemLine(line, index) { |
||||
if (!line) { |
||||
return null; |
||||
} |
||||
|
||||
return ( |
||||
<Text |
||||
key = { index } |
||||
numberOfLines = { 1 } |
||||
style = { styles.listItemText }> |
||||
{ line } |
||||
</Text> |
||||
); |
||||
} |
||||
|
||||
_renderItemLines: (Array<string>) => Array<React$Node>; |
||||
|
||||
/** |
||||
* Renders the additional item lines, if any. |
||||
* |
||||
* @private |
||||
* @param {Array<string>} lines - The lines to render. |
||||
* @returns {Array<React$Node>} |
||||
*/ |
||||
_renderItemLines(lines) { |
||||
if (lines && lines.length) { |
||||
return lines.map((line, index) => |
||||
this._renderItemLine(line, index) |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
_renderSection: Object => Object |
||||
|
||||
/** |
||||
* Renders a section title. |
||||
* |
||||
* @private |
||||
* @param {Object} section - The section being rendered. |
||||
* @returns {React$Node} |
||||
*/ |
||||
_renderSection(section) { |
||||
return ( |
||||
<View style = { styles.listSection }> |
||||
<Text style = { styles.listSectionText }> |
||||
{ section.section.title } |
||||
</Text> |
||||
</View> |
||||
); |
||||
} |
||||
} |
@ -1,75 +0,0 @@ |
||||
// @flow
|
||||
|
||||
import { Component } from 'react'; |
||||
|
||||
import { appNavigate } from '../../app'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link AbstractRecentList} |
||||
*/ |
||||
type Props = { |
||||
_defaultURL: string, |
||||
|
||||
_recentList: Array<Object>, |
||||
|
||||
/** |
||||
* Indicates if the list is disabled or not. |
||||
*/ |
||||
disabled: boolean, |
||||
|
||||
/** |
||||
* The redux store's {@code dispatch} function. |
||||
*/ |
||||
dispatch: Dispatch<*> |
||||
}; |
||||
|
||||
/** |
||||
* Implements a React {@link Component} which represents the list of conferences |
||||
* recently joined, similar to how a list of last dialed numbers list would do |
||||
* on a mobile device. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
export default class AbstractRecentList extends Component<Props> { |
||||
|
||||
/** |
||||
* Joins the selected room. |
||||
* |
||||
* @param {string} room - The selected room. |
||||
* @protected |
||||
* @returns {void} |
||||
*/ |
||||
_onJoin(room) { |
||||
const { dispatch, disabled } = this.props; |
||||
|
||||
!disabled && room && dispatch(appNavigate(room)); |
||||
} |
||||
|
||||
/** |
||||
* Creates a bound onPress action for the list item. |
||||
* |
||||
* @param {string} room - The selected room. |
||||
* @protected |
||||
* @returns {Function} |
||||
*/ |
||||
_onSelect(room) { |
||||
return this._onJoin.bind(this, room); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the redux state into {@code AbstractRecentList}'s React |
||||
* {@code Component} props. |
||||
* |
||||
* @param {Object} state - The redux state. |
||||
* @returns {{ |
||||
* _defaultURL: string, |
||||
* _recentList: Array |
||||
* }} |
||||
*/ |
||||
export function _mapStateToProps(state: Object) { |
||||
return { |
||||
_defaultURL: state['features/app'].app._getDefaultURL(), |
||||
_recentList: state['features/recent-list'] |
||||
}; |
||||
} |
@ -1,208 +1,240 @@ |
||||
import React from 'react'; |
||||
import { |
||||
ListView, |
||||
SafeAreaView, |
||||
Text, |
||||
TouchableHighlight, |
||||
View |
||||
} from 'react-native'; |
||||
// @flow
|
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { Icon } from '../../base/font-icons'; |
||||
|
||||
import AbstractRecentList, { _mapStateToProps } from './AbstractRecentList'; |
||||
import { getRecentRooms } from '../functions'; |
||||
import styles, { UNDERLAY_COLOR } from './styles'; |
||||
import { appNavigate } from '../../app'; |
||||
import { translate } from '../../base/i18n'; |
||||
import { NavigateSectionList } from '../../base/react'; |
||||
import { |
||||
getLocalizedDateFormatter, |
||||
getLocalizedDurationFormatter, |
||||
parseURIString |
||||
} from '../../base/util'; |
||||
|
||||
/** |
||||
* The native container rendering the list of the recently joined rooms. |
||||
* |
||||
* @extends AbstractRecentList |
||||
* The type of the React {@code Component} props of {@link RecentList} |
||||
*/ |
||||
class RecentList extends AbstractRecentList { |
||||
type Props = { |
||||
|
||||
/** |
||||
* Renders the list disabled. |
||||
*/ |
||||
disabled: boolean, |
||||
|
||||
/** |
||||
* The redux store's {@code dispatch} function. |
||||
*/ |
||||
dispatch: Dispatch<*>, |
||||
|
||||
/** |
||||
* The translate function. |
||||
*/ |
||||
t: Function, |
||||
|
||||
/** |
||||
* The datasource wrapper to be used for the display. |
||||
* The default server URL. |
||||
*/ |
||||
dataSource = new ListView.DataSource({ |
||||
rowHasChanged: (r1, r2) => |
||||
r1.conference !== r2.conference |
||||
&& r1.dateTimeStamp !== r2.dateTimeStamp |
||||
}); |
||||
_defaultServerURL: string, |
||||
|
||||
/** |
||||
* The recent list from the Redux store. |
||||
*/ |
||||
_recentList: Array<Object> |
||||
}; |
||||
|
||||
/** |
||||
* The native container rendering the list of the recently joined rooms. |
||||
* |
||||
*/ |
||||
class RecentList extends Component<Props> { |
||||
/** |
||||
* Initializes a new {@code RecentList} instance. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
constructor(props) { |
||||
constructor(props: Props) { |
||||
super(props); |
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._getAvatarStyle = this._getAvatarStyle.bind(this); |
||||
this._onSelect = this._onSelect.bind(this); |
||||
this._renderConfDuration = this._renderConfDuration.bind(this); |
||||
this._renderRow = this._renderRow.bind(this); |
||||
this._renderServerInfo = this._renderServerInfo.bind(this); |
||||
this._onPress = this._onPress.bind(this); |
||||
this._toDateString = this._toDateString.bind(this); |
||||
this._toDurationString = this._toDurationString.bind(this); |
||||
this._toDisplayableItem = this._toDisplayableItem.bind(this); |
||||
this._toDisplayableList = this._toDisplayableList.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. Renders a list of recently |
||||
* joined rooms. |
||||
* Implements the React Components's render method. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { disabled, _recentList } = this.props; |
||||
|
||||
if (!_recentList) { |
||||
return null; |
||||
} |
||||
|
||||
const listViewDataSource |
||||
= this.dataSource.cloneWithRows(getRecentRooms(_recentList)); |
||||
const { disabled } = this.props; |
||||
|
||||
return ( |
||||
<SafeAreaView |
||||
style = { [ |
||||
styles.container, |
||||
disabled ? styles.containerDisabled : null |
||||
] }> |
||||
<ListView |
||||
dataSource = { listViewDataSource } |
||||
enableEmptySections = { true } |
||||
renderRow = { this._renderRow } /> |
||||
</SafeAreaView> |
||||
<NavigateSectionList |
||||
disabled = { disabled } |
||||
onPress = { this._onPress } |
||||
sections = { this._toDisplayableList() } /> |
||||
); |
||||
} |
||||
|
||||
_onPress: string => Function |
||||
|
||||
/** |
||||
* Assembles the style array of the avatar based on if the conference was |
||||
* hosted on the default Jitsi Meet deployment or on a non-default one |
||||
* (based on current app setting). |
||||
* Handles the list's navigate action. |
||||
* |
||||
* @param {Object} recentListEntry - The recent list entry being rendered. |
||||
* @private |
||||
* @returns {Array<Object>} |
||||
* @param {string} url - The url string to navigate to. |
||||
* @returns {void} |
||||
*/ |
||||
_getAvatarStyle({ baseURL, serverName }) { |
||||
const avatarStyles = [ styles.avatar ]; |
||||
_onPress(url) { |
||||
const { dispatch } = this.props; |
||||
|
||||
if (baseURL !== this.props._defaultURL) { |
||||
avatarStyles.push(this._getColorForServerName(serverName)); |
||||
} |
||||
|
||||
return avatarStyles; |
||||
dispatch(appNavigate(url)); |
||||
} |
||||
|
||||
_toDisplayableItem: Object => Object |
||||
|
||||
/** |
||||
* Returns a style (color) based on the server name, so then the same server |
||||
* will always be rendered with the same avatar color. |
||||
* Creates a displayable list item of a recent list entry. |
||||
* |
||||
* @param {string} serverName - The recent list entry being rendered. |
||||
* @private |
||||
* @param {Object} item - The recent list entry. |
||||
* @returns {Object} |
||||
*/ |
||||
_getColorForServerName(serverName) { |
||||
let nameHash = 0; |
||||
|
||||
for (let i = 0; i < serverName.length; i++) { |
||||
nameHash += serverName.codePointAt(i); |
||||
} |
||||
|
||||
return styles[`avatarRemoteServer${(nameHash % 5) + 1}`]; |
||||
_toDisplayableItem(item) { |
||||
const { _defaultServerURL } = this.props; |
||||
const location = parseURIString(item.conference); |
||||
const baseURL = `${location.protocol}//${location.host}`; |
||||
const serverName = baseURL === _defaultServerURL ? null : location.host; |
||||
|
||||
return { |
||||
colorBase: serverName, |
||||
key: `key-${item.conference}-${item.date}`, |
||||
lines: [ |
||||
this._toDateString(item.date), |
||||
this._toDurationString(item.duration), |
||||
serverName |
||||
], |
||||
title: location.room, |
||||
url: item.conference |
||||
}; |
||||
} |
||||
|
||||
_toDisplayableList: () => Array<Object> |
||||
|
||||
/** |
||||
* Renders the conference duration if available. |
||||
* Transforms the history list to a displayable list |
||||
* with sections. |
||||
* |
||||
* @param {Object} recentListEntry - The recent list entry being rendered. |
||||
* @private |
||||
* @returns {ReactElement} |
||||
* @returns {Array<Object>} |
||||
*/ |
||||
_renderConfDuration({ durationString }) { |
||||
if (durationString) { |
||||
return ( |
||||
<View style = { styles.infoWithIcon } > |
||||
<Icon |
||||
name = 'timer' |
||||
style = { styles.inlineIcon } /> |
||||
<Text style = { styles.confLength }> |
||||
{ durationString } |
||||
</Text> |
||||
</View> |
||||
); |
||||
_toDisplayableList() { |
||||
const { _recentList, t } = this.props; |
||||
const todaySection = NavigateSectionList.createSection( |
||||
t('recentList.today'), |
||||
'today' |
||||
); |
||||
const yesterdaySection = NavigateSectionList.createSection( |
||||
t('recentList.yesterday'), |
||||
'yesterday' |
||||
); |
||||
const earlierSection = NavigateSectionList.createSection( |
||||
t('recentList.earlier'), |
||||
'earlier' |
||||
); |
||||
const today = new Date().toDateString(); |
||||
const yesterdayDate = new Date(); |
||||
|
||||
yesterdayDate.setDate(yesterdayDate.getDate() - 1); |
||||
|
||||
const yesterday = yesterdayDate.toDateString(); |
||||
|
||||
for (const item of _recentList) { |
||||
const itemDay = new Date(item.date).toDateString(); |
||||
const displayableItem = this._toDisplayableItem(item); |
||||
|
||||
if (itemDay === today) { |
||||
todaySection.data.push(displayableItem); |
||||
} else if (itemDay === yesterday) { |
||||
yesterdaySection.data.push(displayableItem); |
||||
} else { |
||||
earlierSection.data.push(displayableItem); |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
const displayableList = []; |
||||
|
||||
if (todaySection.data.length) { |
||||
todaySection.data.reverse(); |
||||
displayableList.push(todaySection); |
||||
} |
||||
if (yesterdaySection.data.length) { |
||||
yesterdaySection.data.reverse(); |
||||
displayableList.push(yesterdaySection); |
||||
} |
||||
if (earlierSection.data.length) { |
||||
earlierSection.data.reverse(); |
||||
displayableList.push(earlierSection); |
||||
} |
||||
|
||||
return displayableList; |
||||
} |
||||
|
||||
_toDateString: number => string |
||||
|
||||
/** |
||||
* Renders the list of recently joined rooms. |
||||
* Generates a date string for the item. |
||||
* |
||||
* @param {Object} data - The row data to be rendered. |
||||
* @private |
||||
* @returns {ReactElement} |
||||
* @param {number} itemDate - The item's timestamp. |
||||
* @returns {string} |
||||
*/ |
||||
_renderRow(data) { |
||||
return ( |
||||
<TouchableHighlight |
||||
onPress = { this._onSelect(data.conference) } |
||||
underlayColor = { UNDERLAY_COLOR } > |
||||
<View style = { styles.row } > |
||||
<View style = { styles.avatarContainer } > |
||||
<View style = { this._getAvatarStyle(data) } > |
||||
<Text style = { styles.avatarContent }> |
||||
{ data.initials } |
||||
</Text> |
||||
</View> |
||||
</View> |
||||
<View style = { styles.detailsContainer } > |
||||
<Text |
||||
numberOfLines = { 1 } |
||||
style = { styles.roomName }> |
||||
{ data.room } |
||||
</Text> |
||||
<View style = { styles.infoWithIcon } > |
||||
<Icon |
||||
name = 'event_note' |
||||
style = { styles.inlineIcon } /> |
||||
<Text style = { styles.date }> |
||||
{ data.dateString } |
||||
</Text> |
||||
</View> |
||||
{ this._renderConfDuration(data) } |
||||
{ this._renderServerInfo(data) } |
||||
</View> |
||||
</View> |
||||
</TouchableHighlight> |
||||
); |
||||
_toDateString(itemDate) { |
||||
const date = new Date(itemDate); |
||||
const m = getLocalizedDateFormatter(itemDate); |
||||
|
||||
if (date.toDateString() === new Date().toDateString()) { |
||||
// The date is today, we use fromNow format.
|
||||
return m.fromNow(); |
||||
} |
||||
|
||||
return m.format('lll'); |
||||
} |
||||
|
||||
_toDurationString: number => string |
||||
|
||||
/** |
||||
* Renders the server info component based on whether the entry was on a |
||||
* different server. |
||||
* Generates a duration string for the item. |
||||
* |
||||
* @param {Object} recentListEntry - The recent list entry being rendered. |
||||
* @private |
||||
* @returns {ReactElement} |
||||
* @param {number} duration - The item's duration. |
||||
* @returns {string} |
||||
*/ |
||||
_renderServerInfo({ baseURL, serverName }) { |
||||
if (baseURL !== this.props._defaultURL) { |
||||
return ( |
||||
<View style = { styles.infoWithIcon } > |
||||
<Icon |
||||
name = 'public' |
||||
style = { styles.inlineIcon } /> |
||||
<Text style = { styles.serverName }> |
||||
{ serverName } |
||||
</Text> |
||||
</View> |
||||
); |
||||
_toDurationString(duration) { |
||||
if (duration) { |
||||
return getLocalizedDurationFormatter(duration).humanize(); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
||||
|
||||
export default connect(_mapStateToProps)(RecentList); |
||||
/** |
||||
* Maps redux state to component props. |
||||
* |
||||
* @param {Object} state - The redux state. |
||||
* @returns {{ |
||||
* _defaultServerURL: string, |
||||
* _recentList: Array |
||||
* }} |
||||
*/ |
||||
export function _mapStateToProps(state: Object) { |
||||
return { |
||||
_defaultServerURL: state['features/app'].app._getDefaultURL(), |
||||
_recentList: state['features/recent-list'] |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps)(RecentList)); |
||||
|
@ -1,166 +0,0 @@ |
||||
// @flow
|
||||
|
||||
import moment from 'moment'; |
||||
|
||||
// MomentJS uses static language bundle loading, so in order to support dynamic
|
||||
// language selection in the app we need to load all bundles that we support in
|
||||
// the app.
|
||||
// FIXME: If we decide to support MomentJS in other features as well we may need
|
||||
// to move this import and the lenient matcher to the i18n feature.
|
||||
require('moment/locale/bg'); |
||||
require('moment/locale/de'); |
||||
require('moment/locale/eo'); |
||||
require('moment/locale/es'); |
||||
require('moment/locale/fr'); |
||||
require('moment/locale/hy-am'); |
||||
require('moment/locale/it'); |
||||
require('moment/locale/nb'); |
||||
|
||||
// OC is not available. Please submit OC translation to the MomentJS project.
|
||||
require('moment/locale/pl'); |
||||
require('moment/locale/pt'); |
||||
require('moment/locale/pt-br'); |
||||
require('moment/locale/ru'); |
||||
require('moment/locale/sk'); |
||||
require('moment/locale/sl'); |
||||
require('moment/locale/sv'); |
||||
require('moment/locale/tr'); |
||||
require('moment/locale/zh-cn'); |
||||
|
||||
import { i18next } from '../base/i18n'; |
||||
import { parseURIString } from '../base/util'; |
||||
|
||||
/** |
||||
* Retrieves the recent room list and generates all the data needed to be |
||||
* displayed. |
||||
* |
||||
* @param {Array<Object>} list - The stored recent list retrieved from redux. |
||||
* @returns {Array} |
||||
*/ |
||||
export function getRecentRooms(list: Array<Object>): Array<Object> { |
||||
const recentRoomDS = []; |
||||
|
||||
if (list.length) { |
||||
// We init the locale on every list render, so then it changes
|
||||
// immediately if a language change happens in the app.
|
||||
const locale = _getSupportedLocale(); |
||||
|
||||
for (const e of list) { |
||||
const uri = parseURIString(e.conference); |
||||
|
||||
if (uri && uri.room && uri.hostname) { |
||||
const duration |
||||
= e.duration || /* legacy */ e.conferenceDuration || 0; |
||||
|
||||
recentRoomDS.push({ |
||||
baseURL: `${uri.protocol}//${uri.host}`, |
||||
conference: e.conference, |
||||
dateString: _getDateString(e.date, locale), |
||||
dateTimeStamp: e.date, |
||||
duration, |
||||
durationString: _getDurationString(duration, locale), |
||||
initials: _getInitials(uri.room), |
||||
room: uri.room, |
||||
serverName: uri.hostname |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return recentRoomDS.reverse(); |
||||
} |
||||
|
||||
/** |
||||
* Returns a well formatted date string to be displayed in the list. |
||||
* |
||||
* @param {number} dateTimeStamp - The UTC timestamp to be converted to String. |
||||
* @param {string} locale - The locale to init the formatter with. Note: This |
||||
* locale must be supported by the formatter so ensure this prerequisite before |
||||
* invoking the function. |
||||
* @private |
||||
* @returns {string} |
||||
*/ |
||||
function _getDateString(dateTimeStamp: number, locale: string) { |
||||
const date = new Date(dateTimeStamp); |
||||
const m = _getLocalizedFormatter(date, locale); |
||||
|
||||
if (date.toDateString() === new Date().toDateString()) { |
||||
// The date is today, we use fromNow format.
|
||||
return m.fromNow(); |
||||
} |
||||
|
||||
return m.format('lll'); |
||||
} |
||||
|
||||
/** |
||||
* Returns a well formatted duration string to be displayed as the conference |
||||
* length. |
||||
* |
||||
* @param {number} duration - The duration in MS. |
||||
* @param {string} locale - The locale to init the formatter with. Note: This |
||||
* locale must be supported by the formatter so ensure this prerequisite before |
||||
* invoking the function. |
||||
* @private |
||||
* @returns {string} |
||||
*/ |
||||
function _getDurationString(duration: number, locale: string) { |
||||
return _getLocalizedFormatter(duration, locale).humanize(); |
||||
} |
||||
|
||||
/** |
||||
* Returns the initials supposed to be used based on the room name. |
||||
* |
||||
* @param {string} room - The room name. |
||||
* @private |
||||
* @returns {string} |
||||
*/ |
||||
function _getInitials(room: string) { |
||||
return room && room.charAt(0) ? room.charAt(0).toUpperCase() : '?'; |
||||
} |
||||
|
||||
/** |
||||
* Returns a localized date formatter initialized with a specific {@code Date} |
||||
* or duration ({@code number}). |
||||
* |
||||
* @private |
||||
* @param {Date|number} dateOrDuration - The date or duration to format. |
||||
* @param {string} locale - The locale to init the formatter with. Note: The |
||||
* specified locale must be supported by the formatter so ensure the |
||||
* prerequisite is met before invoking the function. |
||||
* @returns {Object} |
||||
*/ |
||||
function _getLocalizedFormatter(dateOrDuration: Date | number, locale: string) { |
||||
const m |
||||
= typeof dateOrDuration === 'number' |
||||
? moment.duration(dateOrDuration) |
||||
: moment(dateOrDuration); |
||||
|
||||
return m.locale(locale); |
||||
} |
||||
|
||||
/** |
||||
* A lenient locale matcher to match language and dialect if possible. |
||||
* |
||||
* @private |
||||
* @returns {string} |
||||
*/ |
||||
function _getSupportedLocale() { |
||||
const i18nLocale = i18next.language; |
||||
let supportedLocale; |
||||
|
||||
if (i18nLocale) { |
||||
const localeRegexp = new RegExp('^([a-z]{2,2})(-)*([a-z]{2,2})*$'); |
||||
const localeResult = localeRegexp.exec(i18nLocale.toLowerCase()); |
||||
|
||||
if (localeResult) { |
||||
const currentLocaleRegexp |
||||
= new RegExp( |
||||
`^${localeResult[1]}(-)*${`(${localeResult[3]})*` || ''}`); |
||||
|
||||
supportedLocale |
||||
= moment.locales().find(lang => currentLocaleRegexp.exec(lang)); |
||||
} |
||||
} |
||||
|
||||
return supportedLocale || 'en'; |
||||
} |
Loading…
Reference in new issue