mirror of https://github.com/jitsi/jitsi-meet
parent
af7c69a1aa
commit
046b06e436
@ -0,0 +1,43 @@ |
||||
%navigate-section-list-text { |
||||
width: 100%; |
||||
font-size: 14px; |
||||
line-height: 20px; |
||||
color: $welcomePageTitleColor; |
||||
text-align: left; |
||||
font-family: 'open_sanslight', Helvetica, sans-serif; |
||||
} |
||||
%navigate-section-list-tile-text { |
||||
@extend %navigate-section-list-text; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
float: left; |
||||
} |
||||
.navigate-section-list-tile { |
||||
height: 90px; |
||||
width: 260px; |
||||
border-radius: 4px; |
||||
background-color: #1754A9; |
||||
margin-right: 8px; |
||||
padding: 16px; |
||||
display: inline-block; |
||||
box-sizing: border-box; |
||||
cursor: pointer; |
||||
} |
||||
.navigate-section-tile-body { |
||||
@extend %navigate-section-list-tile-text; |
||||
font-weight: normal; |
||||
} |
||||
.navigate-section-tile-title { |
||||
@extend %navigate-section-list-tile-text; |
||||
font-weight: bold; |
||||
} |
||||
.navigate-section-section-header { |
||||
@extend %navigate-section-list-text; |
||||
font-weight: bold; |
||||
margin-bottom: 16px; |
||||
} |
||||
.navigate-section-list { |
||||
position: relative; |
||||
margin-top: 36px; |
||||
margin-bottom: 36px; |
||||
} |
@ -0,0 +1,77 @@ |
||||
// @flow
|
||||
/** |
||||
* item data for NavigateSectionList |
||||
*/ |
||||
import type { |
||||
ComponentType, |
||||
Element |
||||
} from 'react'; |
||||
|
||||
export type Item = { |
||||
|
||||
/** |
||||
* the color base of the avatar |
||||
*/ |
||||
colorBase: string, |
||||
|
||||
/** |
||||
* Item title |
||||
*/ |
||||
title: string, |
||||
|
||||
/** |
||||
* Item url |
||||
*/ |
||||
url: string, |
||||
|
||||
/** |
||||
* lines[0] - date |
||||
* lines[1] - duration |
||||
* lines[2] - server name |
||||
*/ |
||||
lines: Array<string> |
||||
} |
||||
|
||||
/** |
||||
* web implementation of section data for NavigateSectionList |
||||
*/ |
||||
export type Section = { |
||||
|
||||
/** |
||||
* section title |
||||
*/ |
||||
title: string, |
||||
|
||||
/** |
||||
* unique key for the section |
||||
*/ |
||||
key?: string, |
||||
|
||||
/** |
||||
* Array of items in the section |
||||
*/ |
||||
data: $ReadOnlyArray<Item>, |
||||
|
||||
/** |
||||
* Optional properties added only to fix some flow errors thrown by React |
||||
* SectionList |
||||
*/ |
||||
ItemSeparatorComponent?: ?ComponentType<any>, |
||||
|
||||
keyExtractor?: (item: Object) => string, |
||||
|
||||
renderItem?: ?(info: Object) => ?Element<any> |
||||
|
||||
} |
||||
|
||||
/** |
||||
* native implementation of section data for NavigateSectionList |
||||
* |
||||
* When react-native's SectionList component parses through an array of sections |
||||
* it passes the section nested within the section property of another object |
||||
* to the renderSection method (on web for our own implementation of SectionList |
||||
* this nesting is not implemented as there is no need for nesting) |
||||
*/ |
||||
export type SetionListSection = { |
||||
section: Section |
||||
} |
@ -0,0 +1,229 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { translate } from '../../i18n'; |
||||
|
||||
import { |
||||
NavigateSectionListEmptyComponent, |
||||
NavigateSectionListItem, |
||||
NavigateSectionListSectionHeader, |
||||
SectionList |
||||
} from './_'; |
||||
import type { Section } from '../Types'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Indicates if the list is disabled or not. |
||||
*/ |
||||
disabled: boolean, |
||||
|
||||
/** |
||||
* The translate function. |
||||
*/ |
||||
t: Function, |
||||
|
||||
/** |
||||
* Function to be invoked when an item is pressed. The item's URL is passed. |
||||
*/ |
||||
onPress: Function, |
||||
|
||||
/** |
||||
* Function to be invoked when pull-to-refresh is performed. |
||||
*/ |
||||
onRefresh: Function, |
||||
|
||||
/** |
||||
* Function to override the rendered default empty list component. |
||||
*/ |
||||
renderListEmptyComponent: Function, |
||||
|
||||
/** |
||||
* An array of sections |
||||
*/ |
||||
sections: Array<Section> |
||||
}; |
||||
|
||||
/** |
||||
* 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. |
||||
*/ |
||||
class NavigateSectionList extends Component<Props> { |
||||
/** |
||||
* Creates an empty section object. |
||||
* |
||||
* @param {string} title - The title of the section. |
||||
* @param {string} key - The key of the section. It must be unique. |
||||
* @private |
||||
* @returns {Object} |
||||
*/ |
||||
static createSection(title, key) { |
||||
return { |
||||
data: [], |
||||
key, |
||||
title |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Constructor of the NavigateSectionList component. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
constructor(props: Props) { |
||||
super(props); |
||||
this._getItemKey = this._getItemKey.bind(this); |
||||
this._onPress = this._onPress.bind(this); |
||||
this._onRefresh = this._onRefresh.bind(this); |
||||
this._renderItem = this._renderItem.bind(this); |
||||
this._renderListEmptyComponent |
||||
= this._renderListEmptyComponent.bind(this); |
||||
this._renderSectionHeader = this._renderSectionHeader.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's Component.render. |
||||
* Note: we don't use the refreshing value yet, because refreshing of these |
||||
* lists is super quick, no need to complicate the code - yet. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
render() { |
||||
const { |
||||
renderListEmptyComponent = this._renderListEmptyComponent, |
||||
sections |
||||
} = this.props; |
||||
|
||||
return ( |
||||
<SectionList |
||||
ListEmptyComponent = { renderListEmptyComponent } |
||||
keyExtractor = { this._getItemKey } |
||||
onItemClick = { this.props.onPress } |
||||
onRefresh = { this._onRefresh } |
||||
refreshing = { false } |
||||
renderItem = { this._renderItem } |
||||
renderSectionHeader = { this._renderSectionHeader } |
||||
sections = { sections } /> |
||||
); |
||||
} |
||||
|
||||
_getItemKey: (Object, number) => string; |
||||
|
||||
/** |
||||
* Generates a unique id to every item. |
||||
* |
||||
* @param {Object} item - The item. |
||||
* @param {number} index - The item index. |
||||
* @private |
||||
* @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. |
||||
* |
||||
* @param {string} url - The URL of the item to navigate to. |
||||
* @private |
||||
* @returns {Function} |
||||
*/ |
||||
_onPress(url) { |
||||
return () => { |
||||
const { disabled, onPress } = this.props; |
||||
|
||||
!disabled && url && typeof onPress === 'function' && onPress(url); |
||||
}; |
||||
} |
||||
|
||||
_onRefresh: () => void; |
||||
|
||||
/** |
||||
* Invokes the onRefresh callback if present. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onRefresh() { |
||||
const { onRefresh } = this.props; |
||||
|
||||
if (typeof onRefresh === 'function') { |
||||
onRefresh(); |
||||
} |
||||
} |
||||
|
||||
_renderItem: Object => Object; |
||||
|
||||
/** |
||||
* Renders a single item in the list. |
||||
* |
||||
* @param {Object} listItem - The item to render. |
||||
* @param {string} key - The item needed for rendering using map on web. |
||||
* @private |
||||
* @returns {Component} |
||||
*/ |
||||
_renderItem(listItem, key = '') { |
||||
const { item } = listItem; |
||||
const { url } = item; |
||||
|
||||
// XXX The value of title cannot be undefined; otherwise, react-native
|
||||
// will throw a TypeError: Cannot read property of undefined. While it's
|
||||
// difficult to get an undefined title and very likely requires the
|
||||
// execution of incorrect source code, it is undesirable to break the
|
||||
// whole app because of an undefined string.
|
||||
if (typeof item.title === 'undefined') { |
||||
return null; |
||||
} |
||||
|
||||
return ( |
||||
<NavigateSectionListItem |
||||
item = { item } |
||||
key = { key } |
||||
onPress = { this._onPress(url) } /> |
||||
); |
||||
} |
||||
|
||||
_renderListEmptyComponent: () => Object; |
||||
|
||||
/** |
||||
* Renders a component to display when the list is empty. |
||||
* |
||||
* @param {Object} section - The section being rendered. |
||||
* @private |
||||
* @returns {React$Node} |
||||
*/ |
||||
_renderListEmptyComponent() { |
||||
const { t, onRefresh } = this.props; |
||||
|
||||
if (typeof onRefresh === 'function') { |
||||
return ( |
||||
<NavigateSectionListEmptyComponent |
||||
t = { t } /> |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
_renderSectionHeader: Object => Object; |
||||
|
||||
/** |
||||
* Renders a section header. |
||||
* |
||||
* @param {Object} section - The section being rendered. |
||||
* @private |
||||
* @returns {React$Node} |
||||
*/ |
||||
_renderSectionHeader(section) { |
||||
return ( |
||||
<NavigateSectionListSectionHeader |
||||
section = { section } /> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default translate(NavigateSectionList); |
@ -1 +1,2 @@ |
||||
export * from './_'; |
||||
export { default as NavigateSectionList } from './NavigateSectionList'; |
||||
|
@ -1,346 +0,0 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
import { |
||||
SafeAreaView, |
||||
SectionList, |
||||
Text, |
||||
TouchableHighlight, |
||||
View |
||||
} from 'react-native'; |
||||
|
||||
import { Icon } from '../../../font-icons'; |
||||
import { translate } from '../../../i18n'; |
||||
|
||||
import styles, { UNDERLAY_COLOR } from './styles'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Indicates if the list is disabled or not. |
||||
*/ |
||||
disabled: boolean, |
||||
|
||||
/** |
||||
* The translate function. |
||||
*/ |
||||
t: Function, |
||||
|
||||
/** |
||||
* Function to be invoked when an item is pressed. The item's URL is passed. |
||||
*/ |
||||
onPress: Function, |
||||
|
||||
/** |
||||
* Function to be invoked when pull-to-refresh is performed. |
||||
*/ |
||||
onRefresh: Function, |
||||
|
||||
/** |
||||
* Function to override the rendered default empty list component. |
||||
*/ |
||||
renderListEmptyComponent: 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. |
||||
*/ |
||||
class NavigateSectionList extends Component<Props> { |
||||
/** |
||||
* Creates an empty section object. |
||||
* |
||||
* @param {string} title - The title of the section. |
||||
* @param {string} key - The key of the section. It must be unique. |
||||
* @private |
||||
* @returns {Object} |
||||
*/ |
||||
static createSection(title, key) { |
||||
return { |
||||
data: [], |
||||
key, |
||||
title |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* 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._onRefresh = this._onRefresh.bind(this); |
||||
this._renderItem = this._renderItem.bind(this); |
||||
this._renderItemLine = this._renderItemLine.bind(this); |
||||
this._renderItemLines = this._renderItemLines.bind(this); |
||||
this._renderListEmptyComponent |
||||
= this._renderListEmptyComponent.bind(this); |
||||
this._renderSection = this._renderSection.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's Component.render. |
||||
* Note: we don't use the refreshing value yet, because refreshing of these |
||||
* lists is super quick, no need to complicate the code - yet. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
render() { |
||||
const { |
||||
renderListEmptyComponent = this._renderListEmptyComponent, |
||||
sections |
||||
} = this.props; |
||||
|
||||
return ( |
||||
<SafeAreaView |
||||
style = { styles.container } > |
||||
<SectionList |
||||
ListEmptyComponent = { renderListEmptyComponent } |
||||
keyExtractor = { this._getItemKey } |
||||
onRefresh = { this._onRefresh } |
||||
refreshing = { false } |
||||
renderItem = { this._renderItem } |
||||
renderSectionHeader = { this._renderSection } |
||||
sections = { sections } |
||||
style = { styles.list } /> |
||||
</SafeAreaView> |
||||
); |
||||
} |
||||
|
||||
_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. |
||||
* |
||||
* @param {Object} item - The item. |
||||
* @param {number} index - The item index. |
||||
* @private |
||||
* @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. |
||||
* |
||||
* @param {string} url - The URL of the item to navigate to. |
||||
* @private |
||||
* @returns {Function} |
||||
*/ |
||||
_onPress(url) { |
||||
return () => { |
||||
const { disabled, onPress } = this.props; |
||||
|
||||
!disabled && url && typeof onPress === 'function' && onPress(url); |
||||
}; |
||||
} |
||||
|
||||
_onRefresh: () => void; |
||||
|
||||
/** |
||||
* Invokes the onRefresh callback if present. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onRefresh() { |
||||
const { onRefresh } = this.props; |
||||
|
||||
if (typeof onRefresh === 'function') { |
||||
onRefresh(); |
||||
} |
||||
} |
||||
|
||||
_renderItem: Object => Object; |
||||
|
||||
/** |
||||
* Renders a single item in the list. |
||||
* |
||||
* @param {Object} listItem - The item to render. |
||||
* @private |
||||
* @returns {Component} |
||||
*/ |
||||
_renderItem(listItem) { |
||||
const { item: { colorBase, lines, title, url } } = listItem; |
||||
|
||||
// XXX The value of title cannot be undefined; otherwise, react-native
|
||||
// will throw a TypeError: Cannot read property of undefined. While it's
|
||||
// difficult to get an undefined title and very likely requires the
|
||||
// execution of incorrect source code, it is undesirable to break the
|
||||
// whole app because of an undefined string.
|
||||
if (typeof title === 'undefined') { |
||||
return null; |
||||
} |
||||
|
||||
return ( |
||||
<TouchableHighlight |
||||
onPress = { this._onPress(url) } |
||||
underlayColor = { UNDERLAY_COLOR }> |
||||
<View style = { styles.listItem }> |
||||
<View style = { styles.avatarContainer } > |
||||
<View |
||||
style = { [ |
||||
styles.avatar, |
||||
this._getAvatarColor(colorBase) |
||||
] } > |
||||
<Text style = { styles.avatarContent }> |
||||
{ title.substr(0, 1).toUpperCase() } |
||||
</Text> |
||||
</View> |
||||
</View> |
||||
<View style = { styles.listItemDetails }> |
||||
<Text |
||||
numberOfLines = { 1 } |
||||
style = { [ |
||||
styles.listItemText, |
||||
styles.listItemTitle |
||||
] }> |
||||
{ title } |
||||
</Text> |
||||
{ this._renderItemLines(lines) } |
||||
</View> |
||||
</View> |
||||
</TouchableHighlight> |
||||
); |
||||
} |
||||
|
||||
_renderItemLine: (string, number) => React$Node; |
||||
|
||||
/** |
||||
* Renders a single line from the additional lines. |
||||
* |
||||
* @param {string} line - The line text. |
||||
* @param {number} index - The index of the line. |
||||
* @private |
||||
* @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. |
||||
* |
||||
* @param {Array<string>} lines - The lines to render. |
||||
* @private |
||||
* @returns {Array<React$Node>} |
||||
*/ |
||||
_renderItemLines(lines) { |
||||
return lines && lines.length ? lines.map(this._renderItemLine) : null; |
||||
} |
||||
|
||||
_renderListEmptyComponent: () => Object; |
||||
|
||||
/** |
||||
* Renders a component to display when the list is empty. |
||||
* |
||||
* @param {Object} section - The section being rendered. |
||||
* @private |
||||
* @returns {React$Node} |
||||
*/ |
||||
_renderListEmptyComponent() { |
||||
const { t, onRefresh } = this.props; |
||||
|
||||
if (typeof onRefresh === 'function') { |
||||
return ( |
||||
<View style = { styles.pullToRefresh }> |
||||
<Text style = { styles.pullToRefreshText }> |
||||
{ t('sectionList.pullToRefresh') } |
||||
</Text> |
||||
<Icon |
||||
name = 'menu-down' |
||||
style = { styles.pullToRefreshIcon } /> |
||||
</View> |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
_renderSection: Object => Object; |
||||
|
||||
/** |
||||
* Renders a section title. |
||||
* |
||||
* @param {Object} section - The section being rendered. |
||||
* @private |
||||
* @returns {React$Node} |
||||
*/ |
||||
_renderSection(section) { |
||||
return ( |
||||
<View style = { styles.listSection }> |
||||
<Text style = { styles.listSectionText }> |
||||
{ section.section.title } |
||||
</Text> |
||||
</View> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default translate(NavigateSectionList); |
@ -0,0 +1,51 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
import { |
||||
Text, |
||||
View |
||||
} from 'react-native'; |
||||
|
||||
import { Icon } from '../../../font-icons/index'; |
||||
import { translate } from '../../../i18n/index'; |
||||
|
||||
import styles from './styles'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* The translate function. |
||||
*/ |
||||
t: Function, |
||||
}; |
||||
|
||||
/** |
||||
* Implements a React Native {@link Component} that is to be displayed when the |
||||
* list is empty |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class NavigateSectionListEmptyComponent extends Component<Props> { |
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { t } = this.props; |
||||
|
||||
return ( |
||||
<View style = { styles.pullToRefresh }> |
||||
<Text style = { styles.pullToRefreshText }> |
||||
{ t('sectionList.pullToRefresh') } |
||||
</Text> |
||||
<Icon |
||||
name = 'menu-down' |
||||
style = { styles.pullToRefreshIcon } /> |
||||
</View> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default translate(NavigateSectionListEmptyComponent); |
@ -0,0 +1,145 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
import { TouchableHighlight } from 'react-native'; |
||||
|
||||
import { |
||||
Text, |
||||
Container |
||||
} from './index'; |
||||
import styles, { UNDERLAY_COLOR } from './styles'; |
||||
import type { Item } from '../../Types'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* item containing data to be rendered |
||||
*/ |
||||
item: Item, |
||||
|
||||
/** |
||||
* Function to be invoked when an Item is pressed. The Item's URL is passed. |
||||
*/ |
||||
onPress: Function |
||||
} |
||||
|
||||
/** |
||||
* Implements a React/Native {@link Component} that renders the Navigate Section |
||||
* List Item |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
export default class NavigateSectionListItem extends Component<Props> { |
||||
/** |
||||
* Constructor of the NavigateSectionList component. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
constructor(props: Props) { |
||||
super(props); |
||||
this._getAvatarColor = this._getAvatarColor.bind(this); |
||||
this._renderItemLine = this._renderItemLine.bind(this); |
||||
this._renderItemLines = this._renderItemLines.bind(this); |
||||
} |
||||
|
||||
_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}`]; |
||||
} |
||||
|
||||
_renderItemLine: (string, number) => React$Node; |
||||
|
||||
/** |
||||
* Renders a single line from the additional lines. |
||||
* |
||||
* @param {string} line - The line text. |
||||
* @param {number} index - The index of the line. |
||||
* @private |
||||
* @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. |
||||
* |
||||
* @param {Array<string>} lines - The lines to render. |
||||
* @private |
||||
* @returns {Array<React$Node>} |
||||
*/ |
||||
_renderItemLines(lines) { |
||||
return lines && lines.length ? lines.map(this._renderItemLine) : null; |
||||
} |
||||
|
||||
/** |
||||
* Renders the content of this component. |
||||
* |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { colorBase, lines, title } = this.props.item; |
||||
|
||||
return ( |
||||
<TouchableHighlight |
||||
onPress = { this.props.onPress } |
||||
underlayColor = { UNDERLAY_COLOR }> |
||||
<Container style = { styles.listItem }> |
||||
<Container style = { styles.avatarContainer }> |
||||
<Container |
||||
style = { [ |
||||
styles.avatar, |
||||
this._getAvatarColor(colorBase) |
||||
] }> |
||||
<Text style = { styles.avatarContent }> |
||||
{title.substr(0, 1).toUpperCase()} |
||||
</Text> |
||||
</Container> |
||||
</Container> |
||||
<Container style = { styles.listItemDetails }> |
||||
<Text |
||||
numberOfLines = { 1 } |
||||
style = { [ |
||||
styles.listItemText, |
||||
styles.listItemTitle |
||||
] }> |
||||
{title} |
||||
</Text> |
||||
{this._renderItemLines(lines)} |
||||
</Container> |
||||
</Container> |
||||
</TouchableHighlight> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { Text, Container } from './index'; |
||||
import styles from './styles'; |
||||
import type { SetionListSection } from '../../Types'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* A section containing the data to be rendered |
||||
*/ |
||||
section: SetionListSection |
||||
} |
||||
|
||||
/** |
||||
* Implements a React/Native {@link Component} that renders the section header |
||||
* of the list |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
export default class NavigateSectionListSectionHeader extends Component<Props> { |
||||
/** |
||||
* Renders the content of this component. |
||||
* |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { section } = this.props.section; |
||||
|
||||
return ( |
||||
<Container style = { styles.listSection }> |
||||
<Text style = { styles.listSectionText }> |
||||
{ section.title } |
||||
</Text> |
||||
</Container> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,91 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
import { |
||||
SafeAreaView, |
||||
SectionList as ReactNativeSectionList |
||||
} from 'react-native'; |
||||
|
||||
import styles from './styles'; |
||||
import type { Section } from '../../Types'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link SectionList} |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* Rendered when the list is empty. Can be a React Component Class, a render |
||||
* function, or a rendered element. |
||||
*/ |
||||
ListEmptyComponent: Object, |
||||
|
||||
/** |
||||
* |
||||
* Used to extract a unique key for a given item at the specified index. |
||||
* Key is used for caching and as the react key to track item re-ordering. |
||||
*/ |
||||
keyExtractor: Function, |
||||
|
||||
/** |
||||
* |
||||
* Functions that defines what happens when the list is pulled for refresh |
||||
*/ |
||||
onRefresh: Function, |
||||
|
||||
/** |
||||
* |
||||
* A boolean that is set true while waiting for new data from a refresh. |
||||
*/ |
||||
refreshing: ?boolean, |
||||
|
||||
/** |
||||
* |
||||
* Default renderer for every item in every section. |
||||
*/ |
||||
renderItem: Function, |
||||
|
||||
/** |
||||
* |
||||
* A component rendered at the top of each section. These stick to the top |
||||
* of the ScrollView by default on iOS. |
||||
*/ |
||||
renderSectionHeader: Object, |
||||
|
||||
/** |
||||
* An array of sections |
||||
*/ |
||||
sections: Array<Section> |
||||
}; |
||||
|
||||
/** |
||||
* Implements a React Native {@link Component} that wraps the React Native |
||||
* SectionList component in a SafeAreaView so that it renders the sectionlist |
||||
* within the safe area of the device |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
export default class SectionList extends Component<Props> { |
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
return ( |
||||
<SafeAreaView |
||||
style = { styles.container } > |
||||
<ReactNativeSectionList |
||||
ListEmptyComponent = { this.props.ListEmptyComponent } |
||||
keyExtractor = { this.props.keyExtractor } |
||||
onRefresh = { this.props.onRefresh } |
||||
refreshing = { this.props.refreshing } |
||||
renderItem = { this.props.renderItem } |
||||
renderSectionHeader = { this.props.renderSectionHeader } |
||||
sections = { this.props.sections } |
||||
style = { styles.list } /> |
||||
</SafeAreaView> |
||||
); |
||||
} |
||||
} |
@ -1,10 +1,16 @@ |
||||
export { default as Container } from './Container'; |
||||
export { default as Header } from './Header'; |
||||
export { default as NavigateSectionList } from './NavigateSectionList'; |
||||
export { default as Link } from './Link'; |
||||
export { default as LoadingIndicator } from './LoadingIndicator'; |
||||
export { default as NavigateSectionListEmptyComponent } from |
||||
'./NavigateSectionListEmptyComponent'; |
||||
export { default as NavigateSectionListItem } |
||||
from './NavigateSectionListItem'; |
||||
export { default as NavigateSectionListSectionHeader } |
||||
from './NavigateSectionListSectionHeader'; |
||||
export { default as PagedList } from './PagedList'; |
||||
export { default as Pressable } from './Pressable'; |
||||
export { default as SideBar } from './SideBar'; |
||||
export { default as Text } from './Text'; |
||||
export { default as SectionList } from './SectionList'; |
||||
export { default as TintedView } from './TintedView'; |
||||
|
@ -0,0 +1,73 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { Text, Container } from './index'; |
||||
import type { Item } from '../../Types'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Function to be invoked when an item is pressed. The item's URL is passed. |
||||
*/ |
||||
onPress: Function, |
||||
|
||||
/** |
||||
* A item containing data to be rendered |
||||
*/ |
||||
item: Item |
||||
} |
||||
|
||||
/** |
||||
* Implements a React/Web {@link Component} for displaying an item in a |
||||
* NavigateSectionList |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
export default class NavigateSectionListItem extends Component<Props> { |
||||
/** |
||||
* Renders the content of this component. |
||||
* |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { lines, title } = this.props.item; |
||||
const { onPress } = this.props; |
||||
|
||||
/** |
||||
* Initiliazes the date and duration of the conference to the an empty |
||||
* string in case for some reason there is an error where the item data |
||||
* lines doesn't contain one or both of those values (even though this |
||||
* unlikely the app shouldn't break because of it) |
||||
* @type {string} |
||||
*/ |
||||
let date = ''; |
||||
let duration = ''; |
||||
|
||||
if (lines[0]) { |
||||
date = lines[0]; |
||||
} |
||||
if (lines[1]) { |
||||
duration = lines[1]; |
||||
} |
||||
|
||||
return ( |
||||
<Container |
||||
className = 'navigate-section-list-tile' |
||||
onClick = { onPress }> |
||||
<Text |
||||
className = 'navigate-section-tile-title'> |
||||
{ title } |
||||
</Text> |
||||
<Text |
||||
className = 'navigate-section-tile-body'> |
||||
{ date } |
||||
</Text> |
||||
<Text |
||||
className = 'navigate-section-tile-body'> |
||||
{ duration } |
||||
</Text> |
||||
</Container> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { Text } from './index'; |
||||
import type { Section } from '../../Types'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* A section containing the data to be rendered |
||||
*/ |
||||
section: Section |
||||
} |
||||
|
||||
/** |
||||
* Implements a React/Web {@link Component} that renders the section header of |
||||
* the list |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
export default class NavigateSectionListSectionHeader extends Component<Props> { |
||||
/** |
||||
* Renders the content of this component. |
||||
* |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
return ( |
||||
<Text className = 'navigate-section-section-header'> |
||||
{ this.props.section.title } |
||||
</Text> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,92 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { Container } from './index'; |
||||
import type { Section } from '../../Types'; |
||||
|
||||
type Props = { |
||||
|
||||
/** |
||||
* Used to extract a unique key for a given item at the specified index. |
||||
* Key is used for caching and as the react key to track item re-ordering. |
||||
*/ |
||||
keyExtractor: Function, |
||||
|
||||
/** |
||||
* Returns a React component that renders each Item in the list |
||||
*/ |
||||
renderItem: Function, |
||||
|
||||
/** |
||||
* Returns a React component that renders the header for every section |
||||
*/ |
||||
renderSectionHeader: Function, |
||||
|
||||
/** |
||||
* An array of sections |
||||
*/ |
||||
sections: Array<Section>, |
||||
|
||||
/** |
||||
* defines what happens when an item in the section list is clicked |
||||
*/ |
||||
onItemClick: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implements a React/Web {@link Component} for displaying a list with |
||||
* sections similar to React Native's {@code SectionList} in order to |
||||
* faciliate cross-platform source code. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
export default class SectionList extends Component<Props> { |
||||
/** |
||||
* Renders the content of this component. |
||||
* |
||||
* @returns {React.ReactNode} |
||||
*/ |
||||
render() { |
||||
const { |
||||
renderSectionHeader, |
||||
renderItem, |
||||
sections, |
||||
keyExtractor |
||||
} = this.props; |
||||
|
||||
/** |
||||
* If there are no recent items we dont want to display anything |
||||
*/ |
||||
if (sections) { |
||||
return ( |
||||
/* eslint-disable no-extra-parens */ |
||||
<Container |
||||
className = 'navigate-section-list'> |
||||
{ |
||||
sections.map((section, sectionIndex) => ( |
||||
<Container |
||||
key = { sectionIndex }> |
||||
{ renderSectionHeader(section) } |
||||
{ section.data |
||||
.map((item, listIndex) => { |
||||
const listItem = { |
||||
item |
||||
}; |
||||
|
||||
return renderItem(listItem, |
||||
keyExtractor(section, |
||||
listIndex)); |
||||
}) } |
||||
</Container> |
||||
) |
||||
) |
||||
} |
||||
</Container> |
||||
/* eslint-enable no-extra-parens */ |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
@ -1,4 +1,11 @@ |
||||
export { default as Container } from './Container'; |
||||
export { default as MultiSelectAutocomplete } from './MultiSelectAutocomplete'; |
||||
export { default as NavigateSectionListEmptyComponent } from |
||||
'./NavigateSectionListEmptyComponent'; |
||||
export { default as NavigateSectionListItem } from |
||||
'./NavigateSectionListItem'; |
||||
export { default as NavigateSectionListSectionHeader } |
||||
from './NavigateSectionListSectionHeader'; |
||||
export { default as SectionList } from './SectionList'; |
||||
export { default as Text } from './Text'; |
||||
export { default as Watermarks } from './Watermarks'; |
||||
|
@ -0,0 +1,109 @@ |
||||
// @flow
|
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { appNavigate, getDefaultURL } from '../../app'; |
||||
import { translate } from '../../base/i18n'; |
||||
import type { Section } from '../../base/react/Types'; |
||||
import { NavigateSectionList } from '../../base/react'; |
||||
|
||||
import { toDisplayableList } from '../functions'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link RecentList} |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* Renders the list disabled. |
||||
*/ |
||||
disabled: boolean, |
||||
|
||||
/** |
||||
* The redux store's {@code dispatch} function. |
||||
*/ |
||||
dispatch: Dispatch<*>, |
||||
|
||||
/** |
||||
* The translate function. |
||||
*/ |
||||
t: Function, |
||||
|
||||
/** |
||||
* The default server URL. |
||||
*/ |
||||
_defaultServerURL: string, |
||||
|
||||
/** |
||||
* The recent list from the Redux store. |
||||
*/ |
||||
_recentList: Array<Section> |
||||
}; |
||||
|
||||
/** |
||||
* The cross platform container rendering the list of the recently joined rooms. |
||||
* |
||||
*/ |
||||
class RecentList extends Component<Props> { |
||||
/** |
||||
* Initializes a new {@code RecentList} instance. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
constructor(props: Props) { |
||||
super(props); |
||||
|
||||
this._onPress = this._onPress.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements the React Components's render method. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
render() { |
||||
const { disabled, t, _defaultServerURL, _recentList } = this.props; |
||||
const recentList = toDisplayableList(_recentList, t, _defaultServerURL); |
||||
|
||||
return ( |
||||
<NavigateSectionList |
||||
disabled = { disabled } |
||||
onPress = { this._onPress } |
||||
sections = { recentList } /> |
||||
); |
||||
} |
||||
|
||||
_onPress: string => Function; |
||||
|
||||
/** |
||||
* Handles the list's navigate action. |
||||
* |
||||
* @private |
||||
* @param {string} url - The url string to navigate to. |
||||
* @returns {void} |
||||
*/ |
||||
_onPress(url) { |
||||
const { dispatch } = this.props; |
||||
|
||||
dispatch(appNavigate(url)); |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Maps redux state to component props. |
||||
* |
||||
* @param {Object} state - The redux state. |
||||
* @returns {{ |
||||
* _defaultServerURL: string, |
||||
* _recentList: Array |
||||
* }} |
||||
*/ |
||||
export function _mapStateToProps(state: Object) { |
||||
return { |
||||
_defaultServerURL: getDefaultURL(state), |
||||
_recentList: state['features/recent-list'] |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps)(RecentList)); |
@ -1,234 +0,0 @@ |
||||
// @flow
|
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { appNavigate, getDefaultURL } from '../../app'; |
||||
import { |
||||
getLocalizedDateFormatter, |
||||
getLocalizedDurationFormatter, |
||||
translate |
||||
} from '../../base/i18n'; |
||||
import { NavigateSectionList } from '../../base/react'; |
||||
import { parseURIString } from '../../base/util'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link RecentList} |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* Renders the list disabled. |
||||
*/ |
||||
disabled: boolean, |
||||
|
||||
/** |
||||
* The redux store's {@code dispatch} function. |
||||
*/ |
||||
dispatch: Dispatch<*>, |
||||
|
||||
/** |
||||
* The translate function. |
||||
*/ |
||||
t: Function, |
||||
|
||||
/** |
||||
* The default server URL. |
||||
*/ |
||||
_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: Props) { |
||||
super(props); |
||||
|
||||
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 the React Components's render method. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
render() { |
||||
const { disabled } = this.props; |
||||
|
||||
return ( |
||||
<NavigateSectionList |
||||
disabled = { disabled } |
||||
onPress = { this._onPress } |
||||
sections = { this._toDisplayableList() } /> |
||||
); |
||||
} |
||||
|
||||
_onPress: string => Function; |
||||
|
||||
/** |
||||
* Handles the list's navigate action. |
||||
* |
||||
* @private |
||||
* @param {string} url - The url string to navigate to. |
||||
* @returns {void} |
||||
*/ |
||||
_onPress(url) { |
||||
const { dispatch } = this.props; |
||||
|
||||
dispatch(appNavigate(url)); |
||||
} |
||||
|
||||
_toDisplayableItem: Object => Object; |
||||
|
||||
/** |
||||
* Creates a displayable list item of a recent list entry. |
||||
* |
||||
* @private |
||||
* @param {Object} item - The recent list entry. |
||||
* @returns {Object} |
||||
*/ |
||||
_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>; |
||||
|
||||
/** |
||||
* Transforms the history list to a displayable list |
||||
* with sections. |
||||
* |
||||
* @private |
||||
* @returns {Array<Object>} |
||||
*/ |
||||
_toDisplayableList() { |
||||
const { _recentList, t } = this.props; |
||||
const { createSection } = NavigateSectionList; |
||||
const todaySection = createSection(t('recentList.today'), 'today'); |
||||
const yesterdaySection |
||||
= createSection(t('recentList.yesterday'), 'yesterday'); |
||||
const earlierSection |
||||
= 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); |
||||
} |
||||
} |
||||
|
||||
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; |
||||
|
||||
/** |
||||
* Generates a date string for the item. |
||||
* |
||||
* @private |
||||
* @param {number} itemDate - The item's timestamp. |
||||
* @returns {string} |
||||
*/ |
||||
_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; |
||||
|
||||
/** |
||||
* Generates a duration string for the item. |
||||
* |
||||
* @private |
||||
* @param {number} duration - The item's duration. |
||||
* @returns {string} |
||||
*/ |
||||
_toDurationString(duration) { |
||||
if (duration) { |
||||
return getLocalizedDurationFormatter(duration).humanize(); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps redux state to component props. |
||||
* |
||||
* @param {Object} state - The redux state. |
||||
* @returns {{ |
||||
* _defaultServerURL: string, |
||||
* _recentList: Array |
||||
* }} |
||||
*/ |
||||
export function _mapStateToProps(state: Object) { |
||||
return { |
||||
_defaultServerURL: getDefaultURL(state), |
||||
_recentList: state['features/recent-list'] |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps)(RecentList)); |
@ -0,0 +1,7 @@ |
||||
/** |
||||
* Everything about recent list on web should be behind a feature flag and in |
||||
* order to share code, this alias for the feature flag on mobile is always true |
||||
* because we dont need a feature flag for recent list on mobile |
||||
* @type {boolean} |
||||
*/ |
||||
export const RECENT_LIST_ENABLED = true; |
@ -0,0 +1,10 @@ |
||||
// @flow
|
||||
declare var interfaceConfig: Object; |
||||
|
||||
/** |
||||
* Everything about recent list on web should be behind a feature flag and in |
||||
* order to share code, this alias for the feature flag on mobile is set to the |
||||
* value defined in interface_config |
||||
* @type {boolean} |
||||
*/ |
||||
export const { RECENT_LIST_ENABLED } = interfaceConfig; |
@ -0,0 +1,81 @@ |
||||
import { getLocalizedDateFormatter, getLocalizedDurationFormatter } |
||||
from '../base/i18n/index'; |
||||
import { parseURIString } from '../base/util/index'; |
||||
|
||||
/** |
||||
* Creates a displayable list item of a recent list entry. |
||||
* |
||||
* @private |
||||
* @param {Object} item - The recent list entry. |
||||
* @param {string} defaultServerURL - The default server URL. |
||||
* @param {Function} t - The translate function. |
||||
* @returns {Object} |
||||
*/ |
||||
export function toDisplayableItem(item, defaultServerURL, t) { |
||||
// 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: [ |
||||
_toDateString(item.date, t), |
||||
_toDurationString(item.duration), |
||||
serverName |
||||
], |
||||
title: location.room, |
||||
url: item.conference |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Generates a duration string for the item. |
||||
* |
||||
* @private |
||||
* @param {number} duration - The item's duration. |
||||
* @returns {string} |
||||
*/ |
||||
export function _toDurationString(duration) { |
||||
if (duration) { |
||||
return getLocalizedDurationFormatter(duration); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Generates a date string for the item. |
||||
* |
||||
* @private |
||||
* @param {number} itemDate - The item's timestamp. |
||||
* @param {Function} t - The translate function. |
||||
* @returns {string} |
||||
*/ |
||||
export function _toDateString(itemDate, t) { |
||||
const date = new Date(itemDate); |
||||
const dateString = date.toDateString(); |
||||
const m = getLocalizedDateFormatter(itemDate); |
||||
const yesterday = new Date(); |
||||
|
||||
yesterday.setDate(yesterday.getDate() - 1); |
||||
const yesterdayString = yesterday.toDateString(); |
||||
const today = new Date(); |
||||
const todayString = today.toDateString(); |
||||
const currentYear = today.getFullYear(); |
||||
const year = date.getFullYear(); |
||||
|
||||
if (dateString === todayString) { |
||||
// The date is today, we use fromNow format.
|
||||
return m.fromNow(); |
||||
} else if (dateString === yesterdayString) { |
||||
return t('dateUtils.yesterday'); |
||||
} else if (year !== currentYear) { |
||||
// we only want to include the year in the date if its not the current
|
||||
// year
|
||||
return m.format('ddd, MMMM DD h:mm A, gggg'); |
||||
} |
||||
|
||||
return m.format('ddd, MMMM DD h:mm A'); |
||||
} |
@ -0,0 +1,62 @@ |
||||
import { NavigateSectionList } from '../base/react/index'; |
||||
|
||||
import { toDisplayableItem } from './functions.all'; |
||||
|
||||
/** |
||||
* Transforms the history list to a displayable list |
||||
* with sections. |
||||
* |
||||
* @private |
||||
* @param {Array<Object>} recentList - The recent list form the redux store. |
||||
* @param {Function} t - The translate function. |
||||
* @param {string} defaultServerURL - The default server URL. |
||||
* @returns {Array<Object>} |
||||
*/ |
||||
export function toDisplayableList(recentList, t, defaultServerURL) { |
||||
const { createSection } = NavigateSectionList; |
||||
const todaySection = createSection(t('dateUtils.today'), 'today'); |
||||
const yesterdaySection |
||||
= createSection(t('dateUtils.yesterday'), 'yesterday'); |
||||
const earlierSection |
||||
= createSection(t('dateUtils.earlier'), 'earlier'); |
||||
const today = new Date(); |
||||
const todayString = today.toDateString(); |
||||
const yesterday = new Date(); |
||||
|
||||
yesterday.setDate(yesterday.getDate() - 1); |
||||
const yesterdayString = yesterday.toDateString(); |
||||
|
||||
for (const item of recentList) { |
||||
const itemDateString = new Date(item.date).toDateString(); |
||||
const displayableItem = toDisplayableItem(item, defaultServerURL, t); |
||||
|
||||
if (itemDateString === todayString) { |
||||
todaySection.data.push(displayableItem); |
||||
} else if (itemDateString === yesterdayString) { |
||||
yesterdaySection.data.push(displayableItem); |
||||
} else { |
||||
earlierSection.data.push(displayableItem); |
||||
} |
||||
} |
||||
const displayableList = []; |
||||
|
||||
// the recent list in the redux store has the latest date in the last index
|
||||
// therefore all the sectionLists' data that was created by parsing through
|
||||
// the recent list is in reverse order and must be reversed for the most
|
||||
// item to show first
|
||||
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; |
||||
} |
||||
|
@ -0,0 +1,34 @@ |
||||
import { NavigateSectionList } from '../base/react/index'; |
||||
|
||||
import { toDisplayableItem } from './functions.all'; |
||||
|
||||
/** |
||||
* Transforms the history list to a displayable list |
||||
* with sections. |
||||
* |
||||
* @private |
||||
* @param {Array<Object>} recentList - The recent list form the redux store. |
||||
* @param {Function} t - The translate function. |
||||
* @param {string} defaultServerURL - The default server URL. |
||||
* @returns {Array<Object>} |
||||
*/ |
||||
export function toDisplayableList(recentList, t, defaultServerURL) { |
||||
const { createSection } = NavigateSectionList; |
||||
const section = createSection(t('recentList.joinPastMeeting'), 'all'); |
||||
|
||||
// we only want the last three conferences we were in for web
|
||||
for (const item of recentList.slice(1).slice(-3)) { |
||||
const displayableItem = toDisplayableItem(item, defaultServerURL, t); |
||||
|
||||
section.data.push(displayableItem); |
||||
} |
||||
const displayableList = []; |
||||
|
||||
if (section.data.length) { |
||||
section.data.reverse(); |
||||
displayableList.push(section); |
||||
} |
||||
|
||||
return displayableList; |
||||
} |
||||
|
Loading…
Reference in new issue