]>
Commit | Line | Data |
---|---|---|
690bb8f9 | 1 | import { splitIntoArray, toBoolean } from '@app/helpers' |
f304a158 | 2 | import { getAllPrivacies } from '@shared/core-utils' |
2198bb5a | 3 | import { escapeHTML } from '@shared/core-utils/renderer' |
527a52ac | 4 | import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoPrivacy, VideoSortField } from '@shared/models' |
2198bb5a | 5 | import { AttributesOnly } from '@shared/typescript-utils' |
dd24f1bb C |
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 | ||
1b206245 C |
43 | private readonly hiddenFields: string[] = [] |
44 | ||
45 | constructor (defaultSort: string, defaultScope: VideoFilterScope, hiddenFields: string[] = []) { | |
dd24f1bb C |
46 | this.setDefaultSort(defaultSort) |
47 | this.setDefaultScope(defaultScope) | |
48 | ||
1b206245 C |
49 | this.hiddenFields = hiddenFields |
50 | ||
dd24f1bb C |
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>>) { | |
2198bb5a C |
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 | |
dd24f1bb | 104 | |
2198bb5a | 105 | if (obj.nsfw !== undefined) this.nsfw = escapeIfNeeded(obj.nsfw) as BooleanBothQuery |
dd24f1bb | 106 | |
2198bb5a C |
107 | if (obj.languageOneOf !== undefined) this.languageOneOf = splitIntoArray(escapeIfNeeded(obj.languageOneOf)) |
108 | if (obj.categoryOneOf !== undefined) this.categoryOneOf = splitIntoArray(escapeIfNeeded(obj.categoryOneOf)) | |
dd24f1bb | 109 | |
2198bb5a | 110 | if (obj.scope !== undefined) this.scope = escapeIfNeeded(obj.scope) as VideoFilterScope |
dd24f1bb C |
111 | if (obj.allVideos !== undefined) this.allVideos = toBoolean(obj.allVideos) |
112 | ||
2198bb5a | 113 | if (obj.live !== undefined) this.live = escapeIfNeeded(obj.live) as BooleanBothQuery |
dd24f1bb | 114 | |
2198bb5a | 115 | if (obj.search !== undefined) this.search = escapeIfNeeded(obj.search) |
dd24f1bb C |
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 | } | |
1b206245 C |
178 | |
179 | this.activeFilters = this.activeFilters | |
180 | .filter(a => this.hiddenFields.includes(a.key) === false) | |
dd24f1bb C |
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 () { | |
2760b454 C |
210 | let isLocal: boolean |
211 | let include: VideoInclude | |
527a52ac | 212 | let privacyOneOf: VideoPrivacy[] |
2760b454 C |
213 | |
214 | if (this.scope === 'local') { | |
215 | isLocal = true | |
216 | } | |
217 | ||
218 | if (this.allVideos) { | |
527a52ac C |
219 | include = VideoInclude.NOT_PUBLISHED_STATE |
220 | privacyOneOf = getAllPrivacies() | |
dd24f1bb C |
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, | |
2760b454 C |
233 | isLocal, |
234 | include, | |
527a52ac | 235 | privacyOneOf, |
dd24f1bb C |
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 | } |