import { isPlatformBrowser } from '@angular/common' ; import { AfterViewInit , Component , ElementRef , Inject , PLATFORM_ID , ViewChild , } from '@angular/core' ; import { BehaviorSubject , combineLatest , fromEvent , Observable , of , Subscription , } from 'rxjs' ; import { catchError , debounceTime , distinctUntilChanged , filter , map , switchMap , take , } from 'rxjs/operators' ; import { ApiService } from '../../../../../common/api/api.service' ; import { ConfigsService } from '../../../../../common/services/configs.service' ; import { MindsUser } from '../../../../../interfaces/entities' ; import { BoostModalService } from '../../boost-modal.service' ; /**
 * Type-ahead search response from server.
 */ export type UserSearchResponse = { status : string ; entities : MindsUser [ ] ; } ; /**
 * Input for the target of a boost offer - drops down with suggestions from server.
 */ @ Component ( { selector : 'm-boostModal__offer-target-input' , template : `
    <h2 class="m-boostOfferTarget__title" i18n="@@BOOST_MODAL__TARGET_CHANNEL">
      Target Channel
    </h2>
    <input
      [ngModel]="username$ | async"
      (ngModelChange)="onUsernameChange($event)"
      class="m-boostOfferTarget__input"
      type="text"
      #searchBox
      data-cy="data-minds-boost-modal-target-input"
    />
    <ul
      *ngIf="showPopout$ | async"
      class="m-boostOfferTarget__matchesList"
      #matchesPopout
    >
      <li
        *ngFor="let match of matches$ | async"
        (click)="onMatchSelect(match)"
        class="m-boostOfferTarget__matchesListItem"
      >
        <img
          class="m-boostOfferTarget__matchesListAvatar"
          (click)="onAvatarClick($event, match.username)"
          src="{{ cdnUrl }}icon/{{ match.guid }}/small/{{ match.icontime }}"
        />
        <span
          class="m-boostOfferTarget__matchesListText m-boostOfferTarget__matchesListText--primary"
          >{{ match.name }}</span
        >
        <span
          class="m-boostOfferTarget__matchesListText m-boostOfferTarget__matchesListText--secondary"
          >&nbsp;·&nbsp;@{{ match.username }}</span
        >
      </li>
      <li *ngIf="inProgress$ | async">
        <m-loadingSpinner [inProgress]="true"></m-loadingSpinner>
      </li>
    </ul>
  ` , styleUrls : [ './offer-target-input.component.ng.scss' ] , } ) export class BoostModalOfferTargetInputComponent implements AfterViewInit { private subscriptions : Subscription [ ] = [ ] ; /**
     * Type-ahead matches from server.
     */ public readonly matches$ : BehaviorSubject < MindsUser [ ] > = new BehaviorSubject < MindsUser [ ] > ( [ ] ) ; /**
     * Users username, held as the value in the components main input.
     */ public readonly username$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; /**
     * Force the suggestions dropdown to close.
     */ public readonly forceClose$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( false ) ; /**
     * Holds true temporarily when matches popout has been clicked inside.
     */ private clickedInside$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( false ) ; /**
     * Whether a request is in progress.
     */ private inProgress$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( false ) ; /**
     * Search-box input reference.
     */ @ ViewChild ( 'searchBox' ) searchBox : ElementRef ; /**
     * Matches popout - note this will be destroyed when there are no matches.
     * Subscriptions will have to be set-up again on change and you should make sure that it exists before using.
     */ @ ViewChild ( 'matchesPopout' ) matchesPopout : ElementRef ; /**
     * CDN url
     */ readonly cdnUrl : string ; constructor ( private api , private service , private element , @ Inject ( PLATFORM_ID ) private platformId , configs ) { this . cdnUrl = configs . get ( 'cdn_url' ) ; } ngAfterViewInit ( ) { this . subscriptions . push ( fromEvent ( this . searchBox . nativeElement , 'input' ) . pipe ( map ( ( e ) => ( e . target as HTMLInputElement ) . value ) , filter ( text => text . length > 2 ) , debounceTime ( 100 ) , distinctUntilChanged ( ) , switchMap ( searchTerm => { this . inProgress$ . next ( true ) ; this . matches$ . next ( [ ] ) ; return this . api . get ( `api/v2/search/suggest/user` , { q : searchTerm , limit : 8 , hydrate : 1 , } ) ; } ) , catchError ( e => { console . error ( e ) ; return of ( [ ] ) ; } ) ) . subscribe ( ( response ) => { this . inProgress$ . next ( false ) ; if ( response . status === 'success' && response . entities . length > 0 ) { this . matches$ . next ( response . entities ) ; } } ) , this . showPopout$ . subscribe ( ( ) => { this . setupMatchesSubscription ( ) ; } ) , fromEvent ( document , 'click' , { capture : true } ) . subscribe ( $event => { setTimeout ( ( ) => { if ( ! this . clickedInside$ . getValue ( ) ) { this . forceClose$ . next ( true ) ; } this . clickedInside$ . next ( false ) ; } , 0 ) ; } ) ) ; } returns { Observable < boolean > } get showPopout$ ( ) : Observable < boolean > { return combineLatest ( [ this . matches$ , this . forceClose$ , this . username$ , this . inProgress$ , ] ) . pipe ( take ( 1 ) , map ( ( [ matches , forceClose , username , inProgress ] ) => { return ( ( matches . length > 0 || inProgress ) && username . length > 2 && ! forceClose ) ; } ) ) ; } returns { void } private setupMatchesSubscription ( ) { if ( this . matchesPopout && this . matchesPopout . nativeElement ) { this . subscriptions . push ( fromEvent ( this . matchesPopout . nativeElement , 'click' ) . subscribe ( ( $event ) => { $event . stopPropagation ( ) ; this . clickedInside$ . next ( true ) ; } ) ) ; } } ngOnDestroy ( ) { for ( let subscription of this . subscriptions ) { subscription . unsubscribe ( ) ; } } param { MouseEvent } $event param { username } username returns { void } public onAvatarClick ( $event , username ) { $event . stopPropagation ( ) ; if ( isPlatformBrowser ( this . platformId ) ) { window . open ( `/${ username }` , '_blank' ) ; } } param { MindsUser } match returns { void } public onMatchSelect ( match ) { this . username$ . next ( match . username ) ; this . service . targetUser$ . next ( match ) ; this . forceClose$ . next ( true ) ; } param { string } $event returns { void } public onUsernameChange ( $event ) { this . forceClose$ . next ( false ) ; this . username$ . next ( $event ) ; this . setTargetUser ( ) ; } /**
     * Sets target user in service from current typed username.
     */ private setTargetUser ( ) { this . subscriptions . push ( combineLatest ( [ this . username$ , this . matches$ ] ) . pipe ( take ( 1 ) , map ( ( [ username , matches ] ) => { return matches . filter ( match => { return match . username === username ; } ) [ 0 ] ; } ) ) . subscribe ( user => { this . service . targetUser$ . next ( user ) ; } ) ) ; } }