import { EventEmitter , Injectable } from '@angular/core' ; import { BehaviorSubject } from 'rxjs' ; import { ActivityEntity } from '../../modules/newsfeed/activity/activity.service' ; import { Client } from '../../services/api/client' ; import { EntitiesService } from './entities.service' ; /**
 * Specifies how to interpret base entity and which correlation to use
 *
 * Container can be a user or group
 */ export type RelatedContentContext = 'container' ; /**
 * Specifies the object returned to consumers
 */ export interface RelatedContentObject { index : number ; entity : BehaviorSubject < any > ; } /**
 * Response from navigational methods
 */ export type RelatedContentResponse = RelatedContentObject | null ; /**
 * Stores per-side pools and its attributes for the base entity
 */ export interface RelatedContentPool { entities : any [ ] ; moreData : boolean ; offset ? : any ; } /**
 * Horizontal feed pools
 */ export interface RelatedContentPools { prev : RelatedContentPool ; next : RelatedContentPool ; } /**
 * Change event payload
 */ interface RelatedContentChange { context : RelatedContentContext ; cursor : number ; lastUpdate : number ; } /**
 * Types of filter for feed, appended to end of v2/feeds/container URL.
 */ type FilterType = 'all' | 'videos' | 'activities' ; /**
 * Default value for filter.
 */ const DEFAULT_FILTER_VALUE = 'activities' ; todo @ Injectable ( ) export class RelatedContentService { protected context : RelatedContentContext ; protected baseEntity : any ; protected limit : number = 600 ; protected cursor : number = 0 ; public pools : RelatedContentPools = { next : { entities : [ ] , moreData : true , } , prev : { entities : [ ] , moreData : true , } , } ; protected onChangeEmitter : EventEmitter < RelatedContentChange > = new EventEmitter < RelatedContentChange > ( ) ; constructor ( protected client , protected entities ) { } /**
     * Filter the feed by filter type.
     */ private filter : FilterType = DEFAULT_FILTER_VALUE ; param context setContext ( context ) { this . context = context ; this . reset ( ) ; return this ; } returns getBaseEntity ( ) { return this . baseEntity ; } param entity setBaseEntity ( entity ) { this . baseEntity = entity ; this . reset ( ) ; return this ; } param limit setLimit ( limit ) { this . limit = limit ; return this ; } param { FilterType } filter setFilter ( filter ) { this . filter = filter ; return this ; } /**
     * Reset the cursor and caches
     */ reset ( ) { this . cursor = 0 ; this . pools = { next : { entities : [ ] , moreData : true , } , prev : { entities : [ ] , moreData : true , } , } ; if ( this . context ) { this . _emitChange ( ) ; } return this ; } param index async go ( index ) { await this . fetch ( ) ; if ( ! ( await this . has ( index ) ) ) { return null ; } this . cursor = index ; this . _emitChange ( ) ; if ( index === 0 ) { return { index , entity : new BehaviorSubject ( this . baseEntity ) , } ; } const entities = index < 0 ? this . pools . prev . entities : this . pools . next . entities ; const entity = entities [ Math . abs ( index ) - 1 ] || null ; if ( entity && entity . entity ) { return { index , entity : new BehaviorSubject ( entity . entity ) , } ; } return { index : index , entity : this . entities . single ( entity . urn ) , } ; } param index param lazy async has ( index , lazy = false ) { if ( ! lazy ) { await this . fetch ( ) ; } if ( ! this . baseEntity ) { return false ; } else if ( index === 0 ) { return Boolean ( this . baseEntity ) ; } else if ( Math . abs ( index ) < this . limit ) { const entities = index < 0 ? this . pools . prev . entities : this . pools . next . entities ; return typeof entities [ Math . abs ( index ) - 1 ] !== 'undefined' ; } return false ; } /**
     * Shortcut for go(index - 1)
     */ prev ( ) { return this . go ( this . cursor - 1 ) ; } param lazy hasPrev ( lazy = false ) { return this . has ( this . cursor - 1 , lazy ) ; } /**
     * Shortcut for go(index + 1)
     */ next ( ) { return this . go ( this . cursor + 1 ) ; } param lazy hasNext ( lazy = false ) { return this . has ( this . cursor + 1 , lazy ) ; } /**
     * Returns an event emitter that will fire an event when something (cursor, pools) change
     */ onChange ( ) { return this . onChangeEmitter ; } returns { ActivityEntity } getNextEntity ( ) { try { const index = this . cursor + 1 ; if ( index === 0 ) { return this . baseEntity ; } const entities = index < 0 ? this . pools . prev . entities : this . pools . next . entities ; const entity = entities [ Math . abs ( index ) - 1 ] || null ; return entity . entity ; } catch ( e ) { return null ; } } private protected _emitChange ( ) { this . onChangeEmitter . next ( { context : this . context , cursor : this . cursor , lastUpdate : + Date . now ( ) , } ) ; } /**
     * Fetches based on context and entity, if necessary
     */ async fetch ( ) { if ( ! this . baseEntity ) { this . pools = { next : { entities : [ ] , moreData : false , } , prev : { entities : [ ] , moreData : false , } , } ; return ; } switch ( this . context ) { case 'container' : await this . _fetchContainer ( ) ; break ; default : throw new Error ( 'Unknown Horizontal Feed context' ) ; } } private protected async _fetchContainer ( ) { const baseEntity = this . baseEntity ; const baseEntityTimestamp = baseEntity . time_created * 1000 ; const guid = baseEntity . container_guid || baseEntity . owner_guid ; const filter = this . filter ? this . filter : 'activities' ; const endpoint = `api/v2/feeds/container/${ guid }/${ filter }` ; const params = { sync : 1 , force_public : 1 , limit : this . limit , from_timestamp : baseEntityTimestamp , } ; const [ prev , next ] = await Promise . all ( [ this . pools . prev . moreData ? this . _fetchFromServer ( endpoint , { ... params , reverse_sort : 1 , } ) : Promise . resolve ( null ) , this . pools . next . moreData ? this . _fetchFromServer ( endpoint , params ) : Promise . resolve ( null ) , ] ) ; let changed = false ; if ( prev !== null ) { this . pools . prev = { entities : this . _removeDuplicates ( prev ) , moreData : false , } ; changed = true ; } if ( next !== null ) { this . pools . next = { entities : this . _removeDuplicates ( next ) , moreData : false , } ; changed = true ; } if ( changed ) { this . _emitChange ( ) ; } } protected _removeDuplicates ( entities ) { const baseRemind = this . baseEntity . remind_object || null ; const baseGuids = [ this . baseEntity . guid , this . baseEntity . entity_guid , baseRemind && baseRemind . guid ? baseRemind . guid : false , baseRemind && baseRemind . entity_guid ? baseRemind . entity_guid : false , ] . filter ( Boolean ) ; const uniqueEntities = entities . filter ( entity => { let duplicateGuid , duplicateEntityGuid = true ; if ( entity && entity . guid ) { duplicateGuid = baseGuids . includes ( entity . guid ) ; if ( entity . entity ) { duplicateEntityGuid = baseGuids . includes ( entity . entity . entity_guid ) ; } } if ( ! duplicateGuid && ! duplicateEntityGuid ) { return entity ; } } ) ; return uniqueEntities ; } param endpoint param params private protected async _fetchFromServer ( endpoint , params ) { const response = ( await this . client . get ( endpoint , params , { cache : true , } ) ) as any ; if ( this . filter === 'videos' ) { return response . entities . length ? response . entities : [ ] ; } if ( response && response . entities && response . entities . length ) { const responseEntities = response . entities . filter ( e => e . entity && ! e . entity . remind_object && ( e . entity . entity_guid || e . entity . message ) ) ; if ( response . entities . length ) { return response . entities ; } } return [ ] ; } }