]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/shared/shared-video-miniature/video-filters.model.ts
Merge branch 'release/5.0.0' into develop
[github/Chocobozzz/PeerTube.git] / client / src / app / shared / shared-video-miniature / video-filters.model.ts
1 import { splitIntoArray, toBoolean } from '@app/helpers'
2 import { getAllPrivacies } from '@shared/core-utils'
3 import { escapeHTML } from '@shared/core-utils/renderer'
4 import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoPrivacy, VideoSortField } from '@shared/models'
5 import { AttributesOnly } from '@shared/typescript-utils'
6
7 type VideoFiltersKeys = {
8 [ id in keyof AttributesOnly<VideoFilters> ]: any
9 }
10
11 export type VideoFilterScope = 'local' | 'federated'
12
13 export class VideoFilters {
14 sort: VideoSortField
15 nsfw: BooleanBothQuery
16
17 languageOneOf: string[]
18 categoryOneOf: number[]
19
20 scope: VideoFilterScope
21 allVideos: boolean
22
23 live: BooleanBothQuery
24
25 search: string
26
27 private defaultValues = new Map<keyof VideoFilters, any>([
28 [ 'sort', '-publishedAt' ],
29 [ 'nsfw', 'false' ],
30 [ 'languageOneOf', undefined ],
31 [ 'categoryOneOf', undefined ],
32 [ 'scope', 'federated' ],
33 [ 'allVideos', false ],
34 [ 'live', 'both' ]
35 ])
36
37 private activeFilters: { key: string, canRemove: boolean, label: string, value?: string }[] = []
38 private defaultNSFWPolicy: NSFWPolicyType
39
40 private onChangeCallbacks: Array<() => void> = []
41 private oldFormObjectString: string
42
43 private readonly hiddenFields: string[] = []
44
45 constructor (defaultSort: string, defaultScope: VideoFilterScope, hiddenFields: string[] = []) {
46 this.setDefaultSort(defaultSort)
47 this.setDefaultScope(defaultScope)
48
49 this.hiddenFields = hiddenFields
50
51 this.reset()
52 }
53
54 onChange (cb: () => void) {
55 this.onChangeCallbacks.push(cb)
56 }
57
58 triggerChange () {
59 // Don't run on change if the values did not change
60 const currentFormObjectString = JSON.stringify(this.toFormObject())
61 if (this.oldFormObjectString && currentFormObjectString === this.oldFormObjectString) return
62
63 this.oldFormObjectString = currentFormObjectString
64
65 for (const cb of this.onChangeCallbacks) {
66 cb()
67 }
68 }
69
70 setDefaultScope (scope: VideoFilterScope) {
71 this.defaultValues.set('scope', scope)
72 }
73
74 setDefaultSort (sort: string) {
75 this.defaultValues.set('sort', sort)
76 }
77
78 setNSFWPolicy (nsfwPolicy: NSFWPolicyType) {
79 this.updateDefaultNSFW(nsfwPolicy)
80 }
81
82 reset (specificKey?: string) {
83 for (const [ key, value ] of this.defaultValues) {
84 if (specificKey && specificKey !== key) continue
85
86 // FIXME: typings
87 this[key as any] = value
88 }
89
90 this.buildActiveFilters()
91 }
92
93 load (obj: Partial<AttributesOnly<VideoFilters>>) {
94 // FIXME: We may use <ng-option> that doesn't escape HTML so prefer to escape things
95 // https://github.com/ng-select/ng-select/issues/1363
96
97 const escapeIfNeeded = (value: any) => {
98 if (typeof value === 'string') return escapeHTML(value)
99
100 return value
101 }
102
103 if (obj.sort !== undefined) this.sort = escapeIfNeeded(obj.sort) as VideoSortField
104
105 if (obj.nsfw !== undefined) this.nsfw = escapeIfNeeded(obj.nsfw) as BooleanBothQuery
106
107 if (obj.languageOneOf !== undefined) this.languageOneOf = splitIntoArray(escapeIfNeeded(obj.languageOneOf))
108 if (obj.categoryOneOf !== undefined) this.categoryOneOf = splitIntoArray(escapeIfNeeded(obj.categoryOneOf))
109
110 if (obj.scope !== undefined) this.scope = escapeIfNeeded(obj.scope) as VideoFilterScope
111 if (obj.allVideos !== undefined) this.allVideos = toBoolean(obj.allVideos)
112
113 if (obj.live !== undefined) this.live = escapeIfNeeded(obj.live) as BooleanBothQuery
114
115 if (obj.search !== undefined) this.search = escapeIfNeeded(obj.search)
116
117 this.buildActiveFilters()
118 }
119
120 buildActiveFilters () {
121 this.activeFilters = []
122
123 this.activeFilters.push({
124 key: 'nsfw',
125 canRemove: false,
126 label: $localize`Sensitive content`,
127 value: this.getNSFWValue()
128 })
129
130 this.activeFilters.push({
131 key: 'scope',
132 canRemove: false,
133 label: $localize`Scope`,
134 value: this.scope === 'federated'
135 ? $localize`Federated`
136 : $localize`Local`
137 })
138
139 if (this.languageOneOf && this.languageOneOf.length !== 0) {
140 this.activeFilters.push({
141 key: 'languageOneOf',
142 canRemove: true,
143 label: $localize`Languages`,
144 value: this.languageOneOf.map(l => l.toUpperCase()).join(', ')
145 })
146 }
147
148 if (this.categoryOneOf && this.categoryOneOf.length !== 0) {
149 this.activeFilters.push({
150 key: 'categoryOneOf',
151 canRemove: true,
152 label: $localize`Categories`,
153 value: this.categoryOneOf.join(', ')
154 })
155 }
156
157 if (this.allVideos) {
158 this.activeFilters.push({
159 key: 'allVideos',
160 canRemove: true,
161 label: $localize`All videos`
162 })
163 }
164
165 if (this.live === 'true') {
166 this.activeFilters.push({
167 key: 'live',
168 canRemove: true,
169 label: $localize`Live videos`
170 })
171 } else if (this.live === 'false') {
172 this.activeFilters.push({
173 key: 'live',
174 canRemove: true,
175 label: $localize`VOD videos`
176 })
177 }
178
179 this.activeFilters = this.activeFilters
180 .filter(a => this.hiddenFields.includes(a.key) === false)
181 }
182
183 getActiveFilters () {
184 return this.activeFilters
185 }
186
187 toFormObject (): VideoFiltersKeys {
188 const result: Partial<VideoFiltersKeys> = {}
189
190 for (const [ key ] of this.defaultValues) {
191 result[key] = this[key]
192 }
193
194 return result as VideoFiltersKeys
195 }
196
197 toUrlObject () {
198 const result: { [ id: string ]: any } = {}
199
200 for (const [ key, defaultValue ] of this.defaultValues) {
201 if (this[key] !== defaultValue) {
202 result[key] = this[key]
203 }
204 }
205
206 return result
207 }
208
209 toVideosAPIObject () {
210 let isLocal: boolean
211 let include: VideoInclude
212 let privacyOneOf: VideoPrivacy[]
213
214 if (this.scope === 'local') {
215 isLocal = true
216 }
217
218 if (this.allVideos) {
219 include = VideoInclude.NOT_PUBLISHED_STATE
220 privacyOneOf = getAllPrivacies()
221 }
222
223 let isLive: boolean
224 if (this.live === 'true') isLive = true
225 else if (this.live === 'false') isLive = false
226
227 return {
228 sort: this.sort,
229 nsfw: this.nsfw,
230 languageOneOf: this.languageOneOf,
231 categoryOneOf: this.categoryOneOf,
232 search: this.search,
233 isLocal,
234 include,
235 privacyOneOf,
236 isLive
237 }
238 }
239
240 getNSFWDisplayLabel () {
241 if (this.defaultNSFWPolicy === 'blur') return $localize`Blurred`
242
243 return $localize`Displayed`
244 }
245
246 private getNSFWValue () {
247 if (this.nsfw === 'false') return $localize`hidden`
248 if (this.defaultNSFWPolicy === 'blur') return $localize`blurred`
249
250 return $localize`displayed`
251 }
252
253 private updateDefaultNSFW (nsfwPolicy: NSFWPolicyType) {
254 const nsfw = nsfwPolicy === 'do_not_list'
255 ? 'false'
256 : 'both'
257
258 this.defaultValues.set('nsfw', nsfw)
259 this.defaultNSFWPolicy = nsfwPolicy
260
261 this.reset('nsfw')
262 }
263 }