aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/helpers')
-rw-r--r--client/src/app/helpers/utils.ts226
-rw-r--r--client/src/app/helpers/utils/channel.ts34
-rw-r--r--client/src/app/helpers/utils/date.ts25
-rw-r--r--client/src/app/helpers/utils/html.ts18
-rw-r--r--client/src/app/helpers/utils/index.ts7
-rw-r--r--client/src/app/helpers/utils/object.ts47
-rw-r--r--client/src/app/helpers/utils/ui.ts33
-rw-r--r--client/src/app/helpers/utils/upload.ts37
-rw-r--r--client/src/app/helpers/utils/url.ts71
9 files changed, 272 insertions, 226 deletions
diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts
deleted file mode 100644
index 8636f3a55..000000000
--- a/client/src/app/helpers/utils.ts
+++ /dev/null
@@ -1,226 +0,0 @@
1import { first, map } from 'rxjs/operators'
2import { SelectChannelItem } from 'src/types/select-options-item.model'
3import { DatePipe } from '@angular/common'
4import { HttpErrorResponse } from '@angular/common/http'
5import { Notifier } from '@app/core'
6import { HttpStatusCode } from '@shared/models'
7import { environment } from '../../environments/environment'
8import { AuthService } from '../core/auth'
9
10// Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
11function getParameterByName (name: string, url: string) {
12 if (!url) url = window.location.href
13 name = name.replace(/[[\]]/g, '\\$&')
14
15 const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)')
16 const results = regex.exec(url)
17
18 if (!results) return null
19 if (!results[2]) return ''
20
21 return decodeURIComponent(results[2].replace(/\+/g, ' '))
22}
23
24function listUserChannels (authService: AuthService) {
25 return authService.userInformationLoaded
26 .pipe(
27 first(),
28 map(() => {
29 const user = authService.getUser()
30 if (!user) return undefined
31
32 const videoChannels = user.videoChannels
33 if (Array.isArray(videoChannels) === false) return undefined
34
35 return videoChannels
36 .sort((a, b) => {
37 if (a.updatedAt < b.updatedAt) return 1
38 if (a.updatedAt > b.updatedAt) return -1
39 return 0
40 })
41 .map(c => ({
42 id: c.id,
43 label: c.displayName,
44 support: c.support,
45 avatarPath: c.avatar?.path
46 }) as SelectChannelItem)
47 })
48 )
49}
50
51function getAbsoluteAPIUrl () {
52 let absoluteAPIUrl = environment.hmr === true
53 ? 'http://localhost:9000'
54 : environment.apiUrl
55
56 if (!absoluteAPIUrl) {
57 // The API is on the same domain
58 absoluteAPIUrl = window.location.origin
59 }
60
61 return absoluteAPIUrl
62}
63
64function getAbsoluteEmbedUrl () {
65 let absoluteEmbedUrl = environment.originServerUrl
66 if (!absoluteEmbedUrl) {
67 // The Embed is on the same domain
68 absoluteEmbedUrl = window.location.origin
69 }
70
71 return absoluteEmbedUrl
72}
73
74const datePipe = new DatePipe('en')
75function dateToHuman (date: string) {
76 return datePipe.transform(date, 'medium')
77}
78
79function durationToString (duration: number) {
80 const hours = Math.floor(duration / 3600)
81 const minutes = Math.floor((duration % 3600) / 60)
82 const seconds = duration % 60
83
84 const minutesPadding = minutes >= 10 ? '' : '0'
85 const secondsPadding = seconds >= 10 ? '' : '0'
86 const displayedHours = hours > 0 ? hours.toString() + ':' : ''
87
88 return (
89 displayedHours + minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
90 ).replace(/^0/, '')
91}
92
93function immutableAssign <A, B> (target: A, source: B) {
94 return Object.assign({}, target, source)
95}
96
97// Thanks: https://gist.github.com/ghinda/8442a57f22099bdb2e34
98function objectToFormData (obj: any, form?: FormData, namespace?: string) {
99 const fd = form || new FormData()
100 let formKey
101
102 for (const key of Object.keys(obj)) {
103 if (namespace) formKey = `${namespace}[${key}]`
104 else formKey = key
105
106 if (obj[key] === undefined) continue
107
108 if (Array.isArray(obj[key]) && obj[key].length === 0) {
109 fd.append(key, null)
110 continue
111 }
112
113 if (obj[key] !== null && typeof obj[key] === 'object' && !(obj[key] instanceof File)) {
114 objectToFormData(obj[key], fd, formKey)
115 } else {
116 fd.append(formKey, obj[key])
117 }
118 }
119
120 return fd
121}
122
123function objectLineFeedToHtml (obj: any, keyToNormalize: string) {
124 return immutableAssign(obj, {
125 [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize])
126 })
127}
128
129function lineFeedToHtml (text: string) {
130 if (!text) return text
131
132 return text.replace(/\r?\n|\r/g, '<br />')
133}
134
135function removeElementFromArray <T> (arr: T[], elem: T) {
136 const index = arr.indexOf(elem)
137 if (index !== -1) arr.splice(index, 1)
138}
139
140function sortBy (obj: any[], key1: string, key2?: string) {
141 return obj.sort((a, b) => {
142 const elem1 = key2 ? a[key1][key2] : a[key1]
143 const elem2 = key2 ? b[key1][key2] : b[key1]
144
145 if (elem1 < elem2) return -1
146 if (elem1 === elem2) return 0
147 return 1
148 })
149}
150
151function scrollToTop (behavior: 'auto' | 'smooth' = 'auto') {
152 window.scrollTo({
153 left: 0,
154 top: 0,
155 behavior
156 })
157}
158
159function isInViewport (el: HTMLElement) {
160 const bounding = el.getBoundingClientRect()
161 return (
162 bounding.top >= 0 &&
163 bounding.left >= 0 &&
164 bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
165 bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
166 )
167}
168
169function isXPercentInViewport (el: HTMLElement, percentVisible: number) {
170 const rect = el.getBoundingClientRect()
171 const windowHeight = (window.innerHeight || document.documentElement.clientHeight)
172
173 return !(
174 Math.floor(100 - (((rect.top >= 0 ? 0 : rect.top) / +-(rect.height / 1)) * 100)) < percentVisible ||
175 Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100) < percentVisible
176 )
177}
178
179function genericUploadErrorHandler (parameters: {
180 err: Pick<HttpErrorResponse, 'message' | 'status' | 'headers'>
181 name: string
182 notifier: Notifier
183 sticky?: boolean
184}) {
185 const { err, name, notifier, sticky } = { sticky: false, ...parameters }
186 const title = $localize`The upload failed`
187 let message = err.message
188
189 if (err instanceof ErrorEvent) { // network error
190 message = $localize`The connection was interrupted`
191 notifier.error(message, title, null, sticky)
192 } else if (err.status === HttpStatusCode.INTERNAL_SERVER_ERROR_500) {
193 message = $localize`The server encountered an error`
194 notifier.error(message, title, null, sticky)
195 } else if (err.status === HttpStatusCode.REQUEST_TIMEOUT_408) {
196 message = $localize`Your ${name} file couldn't be transferred before the set timeout (usually 10min)`
197 notifier.error(message, title, null, sticky)
198 } else if (err.status === HttpStatusCode.PAYLOAD_TOO_LARGE_413) {
199 const maxFileSize = err.headers?.get('X-File-Maximum-Size') || '8G'
200 message = $localize`Your ${name} file was too large (max. size: ${maxFileSize})`
201 notifier.error(message, title, null, sticky)
202 } else {
203 notifier.error(err.message, title)
204 }
205
206 return message
207}
208
209export {
210 sortBy,
211 durationToString,
212 lineFeedToHtml,
213 getParameterByName,
214 getAbsoluteAPIUrl,
215 dateToHuman,
216 immutableAssign,
217 objectToFormData,
218 getAbsoluteEmbedUrl,
219 objectLineFeedToHtml,
220 removeElementFromArray,
221 scrollToTop,
222 isInViewport,
223 isXPercentInViewport,
224 listUserChannels,
225 genericUploadErrorHandler
226}
diff --git a/client/src/app/helpers/utils/channel.ts b/client/src/app/helpers/utils/channel.ts
new file mode 100644
index 000000000..93863a8af
--- /dev/null
+++ b/client/src/app/helpers/utils/channel.ts
@@ -0,0 +1,34 @@
1import { first, map } from 'rxjs/operators'
2import { SelectChannelItem } from 'src/types/select-options-item.model'
3import { AuthService } from '../../core/auth'
4
5function listUserChannels (authService: AuthService) {
6 return authService.userInformationLoaded
7 .pipe(
8 first(),
9 map(() => {
10 const user = authService.getUser()
11 if (!user) return undefined
12
13 const videoChannels = user.videoChannels
14 if (Array.isArray(videoChannels) === false) return undefined
15
16 return videoChannels
17 .sort((a, b) => {
18 if (a.updatedAt < b.updatedAt) return 1
19 if (a.updatedAt > b.updatedAt) return -1
20 return 0
21 })
22 .map(c => ({
23 id: c.id,
24 label: c.displayName,
25 support: c.support,
26 avatarPath: c.avatar?.path
27 }) as SelectChannelItem)
28 })
29 )
30}
31
32export {
33 listUserChannels
34}
diff --git a/client/src/app/helpers/utils/date.ts b/client/src/app/helpers/utils/date.ts
new file mode 100644
index 000000000..012b959ea
--- /dev/null
+++ b/client/src/app/helpers/utils/date.ts
@@ -0,0 +1,25 @@
1import { DatePipe } from '@angular/common'
2
3const datePipe = new DatePipe('en')
4function dateToHuman (date: string) {
5 return datePipe.transform(date, 'medium')
6}
7
8function durationToString (duration: number) {
9 const hours = Math.floor(duration / 3600)
10 const minutes = Math.floor((duration % 3600) / 60)
11 const seconds = duration % 60
12
13 const minutesPadding = minutes >= 10 ? '' : '0'
14 const secondsPadding = seconds >= 10 ? '' : '0'
15 const displayedHours = hours > 0 ? hours.toString() + ':' : ''
16
17 return (
18 displayedHours + minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
19 ).replace(/^0/, '')
20}
21
22export {
23 durationToString,
24 dateToHuman
25}
diff --git a/client/src/app/helpers/utils/html.ts b/client/src/app/helpers/utils/html.ts
new file mode 100644
index 000000000..2d520aee9
--- /dev/null
+++ b/client/src/app/helpers/utils/html.ts
@@ -0,0 +1,18 @@
1import { immutableAssign } from './object'
2
3function objectLineFeedToHtml (obj: any, keyToNormalize: string) {
4 return immutableAssign(obj, {
5 [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize])
6 })
7}
8
9function lineFeedToHtml (text: string) {
10 if (!text) return text
11
12 return text.replace(/\r?\n|\r/g, '<br />')
13}
14
15export {
16 objectLineFeedToHtml,
17 lineFeedToHtml
18}
diff --git a/client/src/app/helpers/utils/index.ts b/client/src/app/helpers/utils/index.ts
new file mode 100644
index 000000000..dc09c92ab
--- /dev/null
+++ b/client/src/app/helpers/utils/index.ts
@@ -0,0 +1,7 @@
1export * from './channel'
2export * from './date'
3export * from './html'
4export * from './object'
5export * from './ui'
6export * from './upload'
7export * from './url'
diff --git a/client/src/app/helpers/utils/object.ts b/client/src/app/helpers/utils/object.ts
new file mode 100644
index 000000000..1ca4a23ac
--- /dev/null
+++ b/client/src/app/helpers/utils/object.ts
@@ -0,0 +1,47 @@
1function immutableAssign <A, B> (target: A, source: B) {
2 return Object.assign({}, target, source)
3}
4
5function removeElementFromArray <T> (arr: T[], elem: T) {
6 const index = arr.indexOf(elem)
7 if (index !== -1) arr.splice(index, 1)
8}
9
10function sortBy (obj: any[], key1: string, key2?: string) {
11 return obj.sort((a, b) => {
12 const elem1 = key2 ? a[key1][key2] : a[key1]
13 const elem2 = key2 ? b[key1][key2] : b[key1]
14
15 if (elem1 < elem2) return -1
16 if (elem1 === elem2) return 0
17 return 1
18 })
19}
20
21function intoArray (value: any) {
22 if (!value) return undefined
23 if (Array.isArray(value)) return value
24
25 if (typeof value === 'string') return value.split(',')
26
27 return [ value ]
28}
29
30function toBoolean (value: any) {
31 if (!value) return undefined
32
33 if (typeof value === 'boolean') return value
34
35 if (value === 'true') return true
36 if (value === 'false') return false
37
38 return undefined
39}
40
41export {
42 sortBy,
43 immutableAssign,
44 removeElementFromArray,
45 intoArray,
46 toBoolean
47}
diff --git a/client/src/app/helpers/utils/ui.ts b/client/src/app/helpers/utils/ui.ts
new file mode 100644
index 000000000..ac8298926
--- /dev/null
+++ b/client/src/app/helpers/utils/ui.ts
@@ -0,0 +1,33 @@
1function scrollToTop (behavior: 'auto' | 'smooth' = 'auto') {
2 window.scrollTo({
3 left: 0,
4 top: 0,
5 behavior
6 })
7}
8
9function isInViewport (el: HTMLElement) {
10 const bounding = el.getBoundingClientRect()
11 return (
12 bounding.top >= 0 &&
13 bounding.left >= 0 &&
14 bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
15 bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
16 )
17}
18
19function isXPercentInViewport (el: HTMLElement, percentVisible: number) {
20 const rect = el.getBoundingClientRect()
21 const windowHeight = (window.innerHeight || document.documentElement.clientHeight)
22
23 return !(
24 Math.floor(100 - (((rect.top >= 0 ? 0 : rect.top) / +-(rect.height / 1)) * 100)) < percentVisible ||
25 Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100) < percentVisible
26 )
27}
28
29export {
30 scrollToTop,
31 isInViewport,
32 isXPercentInViewport
33}
diff --git a/client/src/app/helpers/utils/upload.ts b/client/src/app/helpers/utils/upload.ts
new file mode 100644
index 000000000..a3fce7fee
--- /dev/null
+++ b/client/src/app/helpers/utils/upload.ts
@@ -0,0 +1,37 @@
1import { HttpErrorResponse } from '@angular/common/http'
2import { Notifier } from '@app/core'
3import { HttpStatusCode } from '@shared/models'
4
5function genericUploadErrorHandler (parameters: {
6 err: Pick<HttpErrorResponse, 'message' | 'status' | 'headers'>
7 name: string
8 notifier: Notifier
9 sticky?: boolean
10}) {
11 const { err, name, notifier, sticky } = { sticky: false, ...parameters }
12 const title = $localize`The upload failed`
13 let message = err.message
14
15 if (err instanceof ErrorEvent) { // network error
16 message = $localize`The connection was interrupted`
17 notifier.error(message, title, null, sticky)
18 } else if (err.status === HttpStatusCode.INTERNAL_SERVER_ERROR_500) {
19 message = $localize`The server encountered an error`
20 notifier.error(message, title, null, sticky)
21 } else if (err.status === HttpStatusCode.REQUEST_TIMEOUT_408) {
22 message = $localize`Your ${name} file couldn't be transferred before the set timeout (usually 10min)`
23 notifier.error(message, title, null, sticky)
24 } else if (err.status === HttpStatusCode.PAYLOAD_TOO_LARGE_413) {
25 const maxFileSize = err.headers?.get('X-File-Maximum-Size') || '8G'
26 message = $localize`Your ${name} file was too large (max. size: ${maxFileSize})`
27 notifier.error(message, title, null, sticky)
28 } else {
29 notifier.error(err.message, title)
30 }
31
32 return message
33}
34
35export {
36 genericUploadErrorHandler
37}
diff --git a/client/src/app/helpers/utils/url.ts b/client/src/app/helpers/utils/url.ts
new file mode 100644
index 000000000..82d9cc11b
--- /dev/null
+++ b/client/src/app/helpers/utils/url.ts
@@ -0,0 +1,71 @@
1import { environment } from '../../../environments/environment'
2
3// Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
4function getParameterByName (name: string, url: string) {
5 if (!url) url = window.location.href
6 name = name.replace(/[[\]]/g, '\\$&')
7
8 const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)')
9 const results = regex.exec(url)
10
11 if (!results) return null
12 if (!results[2]) return ''
13
14 return decodeURIComponent(results[2].replace(/\+/g, ' '))
15}
16
17function getAbsoluteAPIUrl () {
18 let absoluteAPIUrl = environment.hmr === true
19 ? 'http://localhost:9000'
20 : environment.apiUrl
21
22 if (!absoluteAPIUrl) {
23 // The API is on the same domain
24 absoluteAPIUrl = window.location.origin
25 }
26
27 return absoluteAPIUrl
28}
29
30function getAbsoluteEmbedUrl () {
31 let absoluteEmbedUrl = environment.originServerUrl
32 if (!absoluteEmbedUrl) {
33 // The Embed is on the same domain
34 absoluteEmbedUrl = window.location.origin
35 }
36
37 return absoluteEmbedUrl
38}
39
40// Thanks: https://gist.github.com/ghinda/8442a57f22099bdb2e34
41function objectToFormData (obj: any, form?: FormData, namespace?: string) {
42 const fd = form || new FormData()
43 let formKey
44
45 for (const key of Object.keys(obj)) {
46 if (namespace) formKey = `${namespace}[${key}]`
47 else formKey = key
48
49 if (obj[key] === undefined) continue
50
51 if (Array.isArray(obj[key]) && obj[key].length === 0) {
52 fd.append(key, null)
53 continue
54 }
55
56 if (obj[key] !== null && typeof obj[key] === 'object' && !(obj[key] instanceof File)) {
57 objectToFormData(obj[key], fd, formKey)
58 } else {
59 fd.append(formKey, obj[key])
60 }
61 }
62
63 return fd
64}
65
66export {
67 getParameterByName,
68 objectToFormData,
69 getAbsoluteAPIUrl,
70 getAbsoluteEmbedUrl
71}