import _ from 'lodash' ; import { Reducer } from 'redux' ; import { nanoid } from 'nanoid' ; import Logs from 'constants/logs' ; import LogType from 'constants/logType' ; import { SerializedMessage } from 'libs/Message' ; import { SerializedNotice } from 'libs/Notice' ; import { SerializedNotification } from 'libs/Notification' ; import { SerializedRejectedMessage } from 'libs/RejectedMessage' ; import { createAction } from 'utils/redux' ; import { padTimeUnit } from 'utils/time' ; param state return function shouldRotateLogs ( state ) { return ! state . isAutoScrollPaused && state . allIds . length > Logs . Max + Logs . Threshold ; } param state return function rotateLogs ( state ) { const rotationIndex = state . allIds . length - Logs . Max ; const allIds = state . allIds . slice ( rotationIndex ) ; const byId = _ . reduce ( allIds , ( newById , id ) => { newById [ id ] = state . byId [ id ] ; return newById ; } , { } as LogsState [ 'byId' ] ) ; return { ... state , allIds , byId , } ; } /**
 * Actions types.
 */ export enum Actions { ADD = 'logs/ADD' , PURGE = 'logs/PURGE' , PURGE_LOG = 'logs/PURGE_LOG' , CLEAR = 'logs/CLEAR' , PAUSE_AUTO_SCROLL = 'logs/PAUSE_AUTO_SCROLL' , ADD_MARKER = 'logs/ADD_MARKER' , UNSHIFT = 'logs/UNSHIFT' , MARK_AS_READ = 'logs/MARK_AS_READ' , MARK_REJECTED_MESSAGE_AS_HANDLED = 'logs/MARK_REJECTED_MESSAGE_AS_HANDLED' } /**
 * Initial state.
 */ export const initialState = { allIds : [ ] , byId : { } , isAutoScrollPaused : false , lastReadId : null , } ; param state param action return const logsReducer = ( state = initialState , action ) => { switch ( action . type ) { case Actions . ADD : { const { log } = action . payload ; const newState = shouldRotateLogs ( state ) ? rotateLogs ( state ) : state ; return { ... newState , allIds : [ ... newState . allIds , log . id ] , byId : { ... newState . byId , [ log . id ] : log } , } ; } case Actions . UNSHIFT : { const { log } = action . payload ; return { ... state , allIds : [ log . id , ... state . allIds ] , byId : { ... state . byId , [ log . id ] : log } , } ; } case Actions . PURGE : { const updatedMessages = _ . reduce ( action . payload . logs , ( messages , id ) => { const message = _ . get ( state . byId , id ) ; if ( _ . isNil ( message ) ) { return messages ; } return { ... messages , [ id ] : { ... message , purged : true } } ; } , { } ) ; return { ... state , byId : { ... state . byId , ... updatedMessages } } ; } case Actions . PURGE_LOG : { const id = action . payload . log ; const message = _ . get ( state . byId , id ) ; if ( _ . isNil ( message ) ) { return state ; } return { ... state , byId : { ... state . byId , [ id ] : { ... message , purged : true } } } ; } case Actions . ADD_MARKER : { const id = nanoid ( ) ; const date = new Date ( ) ; const time = `${ padTimeUnit ( date . getHours ( ) ) }:${ padTimeUnit ( date . getMinutes ( ) ) }` ; return { ... state , allIds : [ ... state . allIds , id ] , byId : { ... state . byId , [ id ] : { id , time , type : LogType . Marker } } , } ; } case Actions . CLEAR : { return initialState ; } case Actions . PAUSE_AUTO_SCROLL : { return { ... state , isAutoScrollPaused : action . payload . pause , } ; } case Actions . MARK_AS_READ : { const { allIds , byId , lastReadId } = state ; const { id } = action . payload ; const clickedLog = byId [ id ] ; if ( ! isMessage ( clickedLog ) || ( clickedLog . read && ! clickedLog . self ) ) { return state ; } const startIndex = _ . isNil ( lastReadId ) ? 0 : _ . indexOf ( allIds , lastReadId ) + 1 ; const endIndex = _ . indexOf ( allIds , id ) ; if ( endIndex < startIndex ) { return state ; } const ids = _ . slice ( allIds , startIndex , endIndex + 1 ) ; if ( ids . length === 0 ) { return { ... state , lastReadId : id } ; } const newById = _ . reduce ( allIds , ( acc , logId ) => { const log = byId [ logId ] ; if ( _ . includes ( ids , log . id ) && isMessage ( log ) ) { return { ... acc , [ logId ] : { ... log , read : true } } ; } return acc ; } , byId ) ; return { ... state , byId : newById , lastReadId : id , } ; } case Actions . MARK_REJECTED_MESSAGE_AS_HANDLED : { const { id } = action . payload ; const rejectedMessage = _ . get ( state . byId , id ) ; if ( _ . isNil ( rejectedMessage ) ) { return state ; } return { ... state , byId : { ... state . byId , [ id ] : { ... rejectedMessage , handled : true } } } ; } default : { return state ; } } } ; export default logsReducer ; param log return export const addLog = ( log ) => createAction ( Actions . ADD , { log , } ) ; param log return export const unshiftLog = ( log ) => createAction ( Actions . UNSHIFT , { log , } ) ; param logs return export const purgeLogs = ( logs ) => createAction ( Actions . PURGE , { logs , } ) ; param log return export const purgeLog = ( log ) => createAction ( Actions . PURGE_LOG , { log , } ) ; return export const clearLogs = ( ) => createAction ( Actions . CLEAR ) ; param pause return export const pauseAutoScroll = ( pause ) => createAction ( Actions . PAUSE_AUTO_SCROLL , { pause , } ) ; return export const addMarker = ( ) => createAction ( Actions . ADD_MARKER ) ; param id return export const markAsRead = ( id ) => createAction ( Actions . MARK_AS_READ , { id , } ) ; param id return export const markRejectedMessageAsHandled = ( id ) => createAction ( Actions . MARK_REJECTED_MESSAGE_AS_HANDLED , { id , } ) ; /**
 * Logs actions.
 */ export type LogsActions = ReturnType < typeof addLog > | ReturnType < typeof purgeLogs > | ReturnType < typeof clearLogs > | ReturnType < typeof pauseAutoScroll > | ReturnType < typeof addMarker > | ReturnType < typeof purgeLog > | ReturnType < typeof unshiftLog > | ReturnType < typeof markAsRead > | ReturnType < typeof markRejectedMessageAsHandled > ; /**
 * Logs state.
 */ export type LogsState = { /**
     * All logs keyed by ids.
     */ byId : Record < string , Log > ; /**
     * All logs ordered by ids.
     */ allIds : string [ ] ; /**
     * Defines if auto-scroll is paused or not.
     */ isAutoScrollPaused : boolean ; /**
     * The id of the last message marked as read.
     */ lastReadId : string | null ; } ; /**
 * Log possible types.
 */ export type Log = SerializedMessage | SerializedNotice | SerializedNotification | SerializedMarker | SerializedRejectedMessage ; /**
 * Marker.
 */ export type SerializedMarker = { time : string ; id : string ; type : LogType . Marker ; } ; param item return export function isLog ( item ) { return isMessage ( item ) || isNotice ( item ) || isNotification ( item ) || isWhisper ( item ) || isMarker ( item ) ; } param log return export function isMessage ( log ) { return log . type === LogType . Action || log . type === LogType . Chat || log . type === LogType . Cheer ; } param log return export function isNotice ( log ) { return log . type === LogType . Notice ; } param log return export function isNotification ( log ) { return log . type === LogType . Notification ; } param log return export function isWhisper ( log ) { return log . type === LogType . Whisper ; } param log return export function isMarker ( log ) { return log . type === LogType . Marker ; } param log return export function isRejectedMessage ( log ) { return log . type === LogType . RejectedMessage ; }