]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - client/src/app/shared/shared-forms/advanced-input-filter.component.ts
Add more when deleting a video
[github/Chocobozzz/PeerTube.git] / client / src / app / shared / shared-forms / advanced-input-filter.component.ts
index 1b0eed34b3e8cb52eb89d1fa5d712801d093f363..d8aeaa0fa3fceb133d2176192a87c8913cdbfb6f 100644 (file)
@@ -1,12 +1,19 @@
 import * as debug from 'debug'
 import { Subject } from 'rxjs'
 import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
-import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
+import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
 import { ActivatedRoute, Params, Router } from '@angular/router'
+import { RestService } from '@app/core'
 
 export type AdvancedInputFilter = {
+  title: string
+
+  children: AdvancedInputFilterChild[]
+}
+
+export type AdvancedInputFilterChild = {
   label: string
-  queryParams: Params
+  value: string
 }
 
 const logger = debug('peertube:AdvancedInputFilterComponent')
@@ -16,17 +23,24 @@ const logger = debug('peertube:AdvancedInputFilterComponent')
   templateUrl: './advanced-input-filter.component.html',
   styleUrls: [ './advanced-input-filter.component.scss' ]
 })
-export class AdvancedInputFilterComponent implements OnInit {
+export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
   @Input() filters: AdvancedInputFilter[] = []
+  @Input() emitOnInit = true
 
   @Output() search = new EventEmitter<string>()
 
   searchValue: string
 
+  private enabledFilters = new Set<string>()
+
   private searchStream: Subject<string>
 
+  private viewInitialized = false
+  private emitSearchAfterViewInit = false
+
   constructor (
     private route: ActivatedRoute,
+    private restService: RestService,
     private router: Router
   ) { }
 
@@ -35,23 +49,50 @@ export class AdvancedInputFilterComponent implements OnInit {
     this.listenToRouteSearchChange()
   }
 
+  ngAfterViewInit () {
+    this.viewInitialized = true
+
+    // Init after view init to not send an event too early
+    if (this.emitOnInit && this.emitSearchAfterViewInit) this.emitSearch()
+  }
+
   onInputSearch (event: Event) {
-    this.updateSearch((event.target as HTMLInputElement).value)
+    this.scheduleSearchUpdate((event.target as HTMLInputElement).value)
   }
 
   onResetTableFilter () {
-    this.updateSearch('')
+    this.immediateSearchUpdate('')
   }
 
   hasFilters () {
-    return this.filters.length !== 0
+    return this.filters && this.filters.length !== 0
+  }
+
+  isFilterEnabled (filter: AdvancedInputFilterChild) {
+    return this.enabledFilters.has(filter.value)
   }
 
-  private updateSearch (value: string) {
+  onFilterClick (filter: AdvancedInputFilterChild) {
+    const newSearch = this.isFilterEnabled(filter)
+      ? this.removeFilterToSearch(this.searchValue, filter)
+      : this.addFilterToSearch(this.searchValue, filter)
+
+    this.router.navigate([ '.' ], { relativeTo: this.route, queryParams: { search: newSearch.trim() } })
+  }
+
+  private scheduleSearchUpdate (value: string) {
     this.searchValue = value
     this.searchStream.next(this.searchValue)
   }
 
+  private immediateSearchUpdate (value: string) {
+    this.searchValue = value
+
+    this.setQueryParams(this.searchValue)
+    this.parseFilters(this.searchValue)
+    this.emitSearch()
+  }
+
   private listenToRouteSearchChange () {
     this.route.queryParams
       .subscribe(params => {
@@ -59,7 +100,13 @@ export class AdvancedInputFilterComponent implements OnInit {
 
         logger('On route search change "%s".', search)
 
-        this.updateSearch(search)
+        if (this.searchValue === search) return
+
+        this.searchValue = search
+
+        this.parseFilters(this.searchValue)
+
+        this.emitSearch()
       })
   }
 
@@ -68,21 +115,62 @@ export class AdvancedInputFilterComponent implements OnInit {
 
     this.searchStream
       .pipe(
-        debounceTime(200),
+        debounceTime(300),
         distinctUntilChanged()
       )
       .subscribe(() => {
-        logger('On search "%s".', this.searchValue)
-
         this.setQueryParams(this.searchValue)
-        this.search.emit(this.searchValue)
+        this.parseFilters(this.searchValue)
+
+        this.emitSearch()
       })
   }
 
+  private emitSearch () {
+    if (!this.viewInitialized) {
+      this.emitSearchAfterViewInit = true
+      return
+    }
+
+    logger('On search "%s".', this.searchValue)
+
+    this.search.emit(this.searchValue)
+  }
+
   private setQueryParams (search: string) {
     const queryParams: Params = {}
 
     if (search) Object.assign(queryParams, { search })
     this.router.navigate([ ], { queryParams })
   }
+
+  private removeFilterToSearch (search: string, removedFilter: AdvancedInputFilterChild) {
+    return search.replace(removedFilter.value, '')
+  }
+
+  private addFilterToSearch (search: string, newFilter: AdvancedInputFilterChild) {
+    const prefix = newFilter.value.split(':').shift()
+
+    // Tokenize search and remove a potential existing filter
+    const tokens = this.restService.tokenizeString(search)
+                                   .filter(t => !t.startsWith(prefix))
+
+    tokens.push(newFilter.value)
+
+    return tokens.join(' ')
+  }
+
+  private parseFilters (search: string) {
+    const tokens = this.restService.tokenizeString(search)
+
+    this.enabledFilters = new Set()
+
+    for (const group of this.filters) {
+      for (const filter of group.children) {
+        if (tokens.includes(filter.value)) {
+          this.enabledFilters.add(filter.value)
+        }
+      }
+    }
+  }
 }