1 import { intoArray, toBoolean } from '@app/helpers'
2 import { AttributesOnly } from '@shared/core-utils'
3 import { BooleanBothQuery, NSFWPolicyType, VideoFilter, VideoSortField } from '@shared/models'
5 type VideoFiltersKeys = {
6 [ id in keyof AttributesOnly<VideoFilters> ]: any
9 export type VideoFilterScope = 'local' | 'federated'
11 export class VideoFilters {
13 nsfw: BooleanBothQuery
15 languageOneOf: string[]
16 categoryOneOf: number[]
18 scope: VideoFilterScope
21 live: BooleanBothQuery
25 private defaultValues = new Map<keyof VideoFilters, any>([
26 [ 'sort', '-publishedAt' ],
28 [ 'languageOneOf', undefined ],
29 [ 'categoryOneOf', undefined ],
30 [ 'scope', 'federated' ],
31 [ 'allVideos', false ],
35 private activeFilters: { key: string, canRemove: boolean, label: string, value?: string }[] = []
36 private defaultNSFWPolicy: NSFWPolicyType
38 private onChangeCallbacks: Array<() => void> = []
39 private oldFormObjectString: string
41 private readonly hiddenFields: string[] = []
43 constructor (defaultSort: string, defaultScope: VideoFilterScope, hiddenFields: string[] = []) {
44 this.setDefaultSort(defaultSort)
45 this.setDefaultScope(defaultScope)
47 this.hiddenFields = hiddenFields
52 onChange (cb: () => void) {
53 this.onChangeCallbacks.push(cb)
57 // Don't run on change if the values did not change
58 const currentFormObjectString = JSON.stringify(this.toFormObject())
59 if (this.oldFormObjectString && currentFormObjectString === this.oldFormObjectString) return
61 this.oldFormObjectString = currentFormObjectString
63 for (const cb of this.onChangeCallbacks) {
68 setDefaultScope (scope: VideoFilterScope) {
69 this.defaultValues.set('scope', scope)
72 setDefaultSort (sort: string) {
73 this.defaultValues.set('sort', sort)
76 setNSFWPolicy (nsfwPolicy: NSFWPolicyType) {
77 this.updateDefaultNSFW(nsfwPolicy)
80 reset (specificKey?: string) {
81 for (const [ key, value ] of this.defaultValues) {
82 if (specificKey && specificKey !== key) continue
85 this[key as any] = value
88 this.buildActiveFilters()
91 load (obj: Partial<AttributesOnly<VideoFilters>>) {
92 if (obj.sort !== undefined) this.sort = obj.sort
94 if (obj.nsfw !== undefined) this.nsfw = obj.nsfw
96 if (obj.languageOneOf !== undefined) this.languageOneOf = intoArray(obj.languageOneOf)
97 if (obj.categoryOneOf !== undefined) this.categoryOneOf = intoArray(obj.categoryOneOf)
99 if (obj.scope !== undefined) this.scope = obj.scope
100 if (obj.allVideos !== undefined) this.allVideos = toBoolean(obj.allVideos)
102 if (obj.live !== undefined) this.live = obj.live
104 if (obj.search !== undefined) this.search = obj.search
106 this.buildActiveFilters()
109 buildActiveFilters () {
110 this.activeFilters = []
112 this.activeFilters.push({
115 label: $localize`Sensitive content`,
116 value: this.getNSFWValue()
119 this.activeFilters.push({
122 label: $localize`Scope`,
123 value: this.scope === 'federated'
124 ? $localize`Federated`
128 if (this.languageOneOf && this.languageOneOf.length !== 0) {
129 this.activeFilters.push({
130 key: 'languageOneOf',
132 label: $localize`Languages`,
133 value: this.languageOneOf.map(l => l.toUpperCase()).join(', ')
137 if (this.categoryOneOf && this.categoryOneOf.length !== 0) {
138 this.activeFilters.push({
139 key: 'categoryOneOf',
141 label: $localize`Categories`,
142 value: this.categoryOneOf.join(', ')
146 if (this.allVideos) {
147 this.activeFilters.push({
150 label: $localize`All videos`
154 if (this.live === 'true') {
155 this.activeFilters.push({
158 label: $localize`Live videos`
160 } else if (this.live === 'false') {
161 this.activeFilters.push({
164 label: $localize`VOD videos`
168 this.activeFilters = this.activeFilters
169 .filter(a => this.hiddenFields.includes(a.key) === false)
172 getActiveFilters () {
173 return this.activeFilters
176 toFormObject (): VideoFiltersKeys {
177 const result: Partial<VideoFiltersKeys> = {}
179 for (const [ key ] of this.defaultValues) {
180 result[key] = this[key]
183 return result as VideoFiltersKeys
187 const result: { [ id: string ]: any } = {}
189 for (const [ key, defaultValue ] of this.defaultValues) {
190 if (this[key] !== defaultValue) {
191 result[key] = this[key]
198 toVideosAPIObject () {
199 let filter: VideoFilter
201 if (this.scope === 'local' && this.allVideos) {
203 } else if (this.scope === 'federated' && this.allVideos) {
205 } else if (this.scope === 'local') {
210 if (this.live === 'true') isLive = true
211 else if (this.live === 'false') isLive = false
216 languageOneOf: this.languageOneOf,
217 categoryOneOf: this.categoryOneOf,
224 getNSFWDisplayLabel () {
225 if (this.defaultNSFWPolicy === 'blur') return $localize`Blurred`
227 return $localize`Displayed`
230 private getNSFWValue () {
231 if (this.nsfw === 'false') return $localize`hidden`
232 if (this.defaultNSFWPolicy === 'blur') return $localize`blurred`
234 return $localize`displayed`
237 private updateDefaultNSFW (nsfwPolicy: NSFWPolicyType) {
238 const nsfw = nsfwPolicy === 'do_not_list'
242 this.defaultValues.set('nsfw', nsfw)
243 this.defaultNSFWPolicy = nsfwPolicy