import _ from 'lodash' ; import { createRef , Component } from 'react' ; import ReactTooltip from 'react-tooltip' ; import { AutoSizer } from 'react-virtualized/dist/es/AutoSizer' ; import { CellMeasurer , CellMeasurerCache } from 'react-virtualized/dist/es/CellMeasurer' ; import { List , ListRowRenderer } from 'react-virtualized/dist/es/List' ; import FlexContent from 'components/FlexContent' ; import Marker from 'components/Marker' ; import Message from 'components/Message' ; import Notice from 'components/Notice' ; import Notification from 'components/Notification' ; import RejectedMessage from 'components/RejectedMessage' ; import Whisper from 'components/Whisper' ; import { ActionHandler } from 'libs/Action' ; import { SerializedChatter } from 'libs/Chatter' ; import { SerializedMessage } from 'libs/Message' ; import { ChattersState } from 'store/ducks/chatters' ; import { isMarker , isMessage , isNotice , isNotification , isRejectedMessage , isWhisper , Log } from 'store/ducks/logs' ; import styled , { ifProp , size , theme , ThemeProps , withTheme } from 'styled' ; /**
 * Wrapper component.
 */ const Wrapper = styled ( FlexContent ) < WrapperProps > `
  border-bottom: ${ size ( 'log.border.bottom' ) } solid ${ ifProp ( 'pauseAutoScroll' , theme ( 'log.pause' ) , 'transparent' ) };
  border-top: ${ size ( 'log.border.top' ) } solid ${ ifProp ( 'pauseAutoScroll' , theme ( 'log.pause' ) , 'transparent' ) };
  font-size: 0.82rem;
  line-height: 1.4rem;
  overflow: hidden;
` ; /**
 * Logs Component.
 */ export class Logs extends Component < Props > { public list = createRef < List > ( ) ; private pauseAutoScroll : boolean = false ; private previousPauseAutoScroll : boolean = false ; private logMeasureCache : CellMeasurerCache ; param props constructor ( props ) { super ( props ) ; this . logMeasureCache = new CellMeasurerCache ( { defaultHeight : props . theme . log . minHeight , fixedWidth : true , keyMapper : ( index ) => _ . get ( this . props . logs [ index ] , 'id' ) , minHeight : props . theme . log . minHeight , } ) ; } /**
     * Lifecycle: componentDidUpdate.
     */ public componentDidUpdate ( ) { ReactTooltip . rebuild ( ) ; } return public render ( ) { const { alternateMessageBackgrounds , copyMessageOnDoubleClick , increaseTwitchHighlight , lastReadId , logs , markNewAsUnread , purgedCount , showContextMenu , unhandledRejectedMessageCount , } = this . props ; const { bottom , top } = this . props . theme . log . border ; const scrollToIndex = this . pauseAutoScroll ? undefined : logs . length - 1 ; return ( < Wrapper pauseAutoScroll = { this . pauseAutoScroll } > < AutoSizer onResize = { this . onResize } > { ( { height , width } ) => ( < List unhandledRejectedMessageCount = { unhandledRejectedMessageCount } alternateMessageBackgrounds = { alternateMessageBackgrounds } copyMessageOnDoubleClick = { copyMessageOnDoubleClick } increaseTwitchHighlight = { increaseTwitchHighlight } deferredMeasurementCache = { this . logMeasureCache } rowHeight = { this . logMeasureCache . rowHeight } showContextMenu = { showContextMenu } markNewAsUnread = { markNewAsUnread } height = { height - bottom - top } rowRenderer = { this . logRenderer } scrollToIndex = { scrollToIndex } purgedCount = { purgedCount } onScroll = { this . onScroll } lastReadId = { lastReadId } rowCount = { logs . length } overscanRowCount = { 10 } ref = { this . list } width = { width } / > ) } < /AutoSizer> < /Wrapper>); } private onScroll = ( { clientHeight , scrollHeight , scrollTop , } ) => { const offset = scrollHeight - scrollTop ; const pauseAutoScroll = offset - clientHeight > 10 ; if ( pauseAutoScroll !== this . pauseAutoScroll ) { this . togglePauseAutoScroll ( pauseAutoScroll ) ; } } ; private onResize = ( ) => { this . logMeasureCache . clearAll ( ) ; } ; private onToggleContextMenu = ( open ) => { if ( open ) { this . previousPauseAutoScroll = this . pauseAutoScroll ; this . togglePauseAutoScroll ( open ) ; } else if ( ! open && ! this . previousPauseAutoScroll ) { this . togglePauseAutoScroll ( open ) ; this . props . scrollToNewestLog ( ) ; } } ; private togglePauseAutoScroll ( pause ) { this . pauseAutoScroll = pause ; this . props . pauseAutoScroll ( pause ) ; } private onClickMessage = ( id ) => { const { markAsRead , markNewAsUnread } = this . props ; if ( markNewAsUnread ) { markAsRead ( id ) ; } } ; private logRenderer : ListRowRenderer = ( { key , index , parent , style } ) => { const log = this . props . logs [ index ] ; let LogComponent ; const { actionHandler , addHighlightsIgnoredUser , alternateMessageBackgrounds , ban , canModerate , chatters , copyMessageOnDoubleClick , copyMessageToClipboard , copyToClipboard , deleteMessage , focusChatter , focusEmote , increaseTwitchHighlight , markNewAsUnread , markRejectedMessageAsHandled , openTwitchViewerCard , reply , showContextMenu , timeout , unban , whisper , } = this . props ; if ( isMessage ( log ) ) { const chatter = _ . get ( chatters , log . user . id ) ; const isBanned = _ . isNil ( chatter ) ? false : chatter . banned ; const useAlternate = alternateMessageBackgrounds && index % 2 === 0 ; LogComponent = ( < Message addHighlightsIgnoredUser = { addHighlightsIgnoredUser } copyMessageOnDoubleClick = { copyMessageOnDoubleClick } increaseTwitchHighlight = { increaseTwitchHighlight } copyMessageToClipboard = { copyMessageToClipboard } onToggleContextMenu = { this . onToggleContextMenu } openTwitchViewerCard = { openTwitchViewerCard } showUnbanContextMenuItem = { isBanned } copyToClipboard = { copyToClipboard } showContextMenu = { showContextMenu } markNewAsUnread = { markNewAsUnread } actionHandler = { actionHandler } deleteMessage = { deleteMessage } onClick = { this . onClickMessage } focusChatter = { focusChatter } useAlternate = { useAlternate } canModerate = { canModerate } focusEmote = { focusEmote } timeout = { timeout } whisper = { whisper } reply = { reply } unban = { unban } style = { style } message = { log } ban = { ban } / > ) ; } else if ( isNotice ( log ) ) { LogComponent = < Notice style = { style } notice = { log } / > ; } else if ( isNotification ( log ) ) { LogComponent = < Notification style = { style } notification = { log } / > ; } else if ( isWhisper ( log ) ) { LogComponent = ( < Whisper copyMessageOnDoubleClick = { copyMessageOnDoubleClick } copyMessageToClipboard = { copyMessageToClipboard } focusEmote = { focusEmote } style = { style } whisper = { log } / > ) ; } else if ( isMarker ( log ) ) { LogComponent = < Marker style = { style } marker = { log } / > ; } else if ( isRejectedMessage ( log ) ) { LogComponent = ( < RejectedMessage markRejectedMessageAsHandled = { markRejectedMessageAsHandled } rejectedMessage = { log } style = { style } / > ) ; } if ( _ . isNil ( LogComponent ) ) { return null ; } return ( < CellMeasurer cache = { this . logMeasureCache } columnIndex = { 0 } key = { key } parent = { parent } rowIndex = { index } > { LogComponent } < /CellMeasurer>); } ; } export default withTheme ( Logs ) ; /**
 * React Props.
 */ interface Props extends ThemeProps { actionHandler : ActionHandler ; addHighlightsIgnoredUser : ( username : string ) => void ; alternateMessageBackgrounds : boolean ; ban : ( username : string ) => void ; canModerate : ( chatter : SerializedChatter ) => boolean ; chatters : ChattersState [ 'byId' ] ; copyMessageOnDoubleClick : boolean ; copyMessageToClipboard : ( message : SerializedMessage ) => void ; copyToClipboard : ( message : string ) => void ; deleteMessage : ( id : string ) => void ; focusChatter : ( chatter : SerializedChatter ) => void ; focusEmote : ( id : string , name : string , provider : string ) => void ; increaseTwitchHighlight : boolean ; lastReadId : string | null ; logs : Log [ ] ; markAsRead : ( id : string ) => void ; markRejectedMessageAsHandled : ( id : string ) => void ; markNewAsUnread : boolean ; openTwitchViewerCard : ( user : Optional < SerializedChatter > ) => void ; pauseAutoScroll : ( pause : boolean ) => void ; purgedCount : number ; reply : ( message : SerializedMessage ) => void ; scrollToNewestLog : ( ) => void ; showContextMenu : boolean ; timeout : ( username : string , duration : number ) => void ; unban : ( username : string ) => void ; unhandledRejectedMessageCount : number ; whisper : ( username : string ) => void ; } /**
 * React Props.
 */ interface WrapperProps { pauseAutoScroll : boolean ; }