import { InputGroup } from '@blueprintjs/core' ; import _ from 'lodash' ; import * as React from 'react' ; import { AutoSizer } from 'react-virtualized/dist/es/AutoSizer' ; import { List , ListRowRenderer } from 'react-virtualized/dist/es/List' ; import ExternalLink from 'components/ExternalLink' ; import NonIdealState from 'components/NonIdealState' ; import Spinner from 'components/Spinner' ; import { ToggleableProps } from 'constants/toggleable' ; import Dialog from 'containers/Dialog' ; import Twitch , { RawChatters } from 'libs/Twitch' ; import styled , { theme , ThemeProps , withTheme } from 'styled' ; /**
 * Content component.
 */ const Content = styled . div `
  height: 80vh;
  margin-bottom: 30px;
` ; /**
 * Chatter component.
 */ const Chatter = styled . div `
  font-size: 0.8rem;
  padding-left: 15px;
` ; /**
 * Type component.
 */ const Group = styled . div `
  color: ${ theme ( 'chattersList.typeColor' ) };
  font-weight: bold;
  padding-left: 15px;
` ; /**
 * Search component.
 */ const Search = styled ( InputGroup ) `
  margin: 10px;
  width: calc(100% - 20px);
` ; /**
 * Row types.
 */ enum RowType { Chatter , Group , Separator } /**
 * Group order used when rendering chatters.
 */ const GroupOrder = [ 'broadcaster' , 'staff' , 'global_mods' , 'admins' , 'moderators' , 'vips' , 'viewers' , ] ; /**
 * React State.
 */ const initialState = { chatters : undefined as Optional < ChattersRows > , count : undefined as Optional < number > , didFail : false , filter : '' , filteredChatters : undefined as Optional < ChattersRows > , } ; type State = Readonly < typeof initialState > ; /**
 * Chatters Component.
 */ class Chatters extends React . Component < Props , State > { public state : State = initialState ; param prevProps public async componentDidUpdate ( prevProps ) { if ( prevProps . visible !== this . props . visible ) { if ( this . props . visible ) { try { const response = await Twitch . fetchChatters ( this . props . channel ) ; const chatters = this . parseChatters ( response . chatters ) ; this . setState ( ( ) => ( { didFail : false , count : response . chatter_count , chatters } ) ) ; } catch { this . setState ( ( ) => ( { didFail : true } ) ) ; } } else { this . setState ( initialState ) ; } } } return public render ( ) { const { toggle , visible } = this . props ; const { count , didFail } = this . state ; let content ; if ( didFail ) { content = < NonIdealState retry / > ; } else if ( ! _ . isNil ( count ) && count === 0 ) { content = < NonIdealState title = "Looks like you're alone!" retry / > ; } else if ( ! _ . isNil ( count ) ) { content = this . renderList ( ) ; } else { content = < Spinner />; } const title = `Chatters List${ ! _ . isNil ( count ) ? ` - ${ count . toLocaleString ( ) }` : '' }` ; return ( < Dialog isOpen = { visible } onClose = { toggle } icon = "people" title = { title } > < Content > { content } < /Content> < /Dialog>); } return private renderList ( ) { const { filter } = this . state ; const rows = this . getRowsToRender ( ) ; return ( < > < Search onChange = { this . onChangeFilter } placeholder = "Filter…" leftIcon = "search" value = { filter } type = "search" autoFocus / > < AutoSizer > { ( { height , width } ) => ( < List rowHeight = { this . props . theme . chattersList . height } noRowsRenderer = { this . noRowsRenderer } rowRenderer = { this . rowRenderer } rowCount = { rows . length } overscanRowCount = { 5 } height = { height } width = { width } / > ) } < /AutoSizer> < />); } private rowRenderer : ListRowRenderer = ( { index , key , style } ) => { const rows = this . getRowsToRender ( ) ; const chatter = rows [ index ] ; const props = { key , style } ; if ( chatter . type === RowType . Chatter ) { return ( < Chatter { ... props } > < ExternalLink href = { `https://www.twitch.tv/${ chatter . name }` } > { chatter . name } < /ExternalLink> < /Chatter>); } else if ( chatter . type === RowType . Separator ) { return < div { ... props } / > ; } return < Group { ... props } > { chatter . name } < /Group>; } ; private noRowsRenderer = ( ) => < NonIdealState title = "No result!" details = "Please try again with a new query." / > ; private onChangeFilter = ( event ) => { const filter = event . currentTarget . value ; let filteredChatters ; const { chatters } = this . state ; if ( ! _ . isNil ( chatters ) && filter . length > 0 ) { const sanitizedFilter = filter . toLowerCase ( ) ; filteredChatters = _ . filter ( chatters , ( chatter ) => { return chatter . type === RowType . Chatter && chatter . name . includes ( sanitizedFilter ) ; } ) ; if ( filteredChatters . length > 0 ) { filteredChatters . unshift ( this . createRow ( RowType . Separator ) ) ; } } this . setState ( ( ) => ( { filter , filteredChatters } ) ) ; } ; private getRowsToRender ( ) { const { chatters , filter , filteredChatters } = this . state ; return filter . length > 0 && ! _ . isNil ( filteredChatters ) ? filteredChatters : chatters || [ ] ; } private parseChatters ( chatters ) { let rows = [ ] ; _ . forEach ( GroupOrder , ( group ) => { const groupChatters = chatters [ group ] ; if ( groupChatters . length > 0 ) { rows . push ( this . createRow ( RowType . Separator ) ) ; rows . push ( this . createRow ( RowType . Group , this . sanitizeGroup ( group ) ) ) ; rows = _ . concat ( rows , _ . map ( groupChatters , ( chatter ) => this . createRow ( RowType . Chatter , chatter ) ) ) ; } } ) ; return rows ; } private createRow ( type ) ; private createRow ( type , name ) ; private createRow ( type , name ) ; private createRow ( type , name ? ) { if ( type === RowType . Separator ) { return { type : RowType . Separator } ; } return { type , name } ; } private sanitizeGroup ( group ) { return _ . replace ( group , '_' , ' ' ) . toUpperCase ( ) ; } } export default withTheme ( Chatters ) ; /**
 * React Props.
 */ interface Props extends ToggleableProps , ThemeProps { channel : string ; } /**
 * Chatters, groups & separators.
 */ type ChatterRow = { type : RowType . Chatter ; name : string ; } ; type GroupRow = { type : RowType . Group ; name : string ; } ; type SeparatorRow = { type : RowType . Separator ; } ; type ChattersRows = Array < ChatterRow | GroupRow | SeparatorRow > ;