diff options
Diffstat (limited to 'client/src/app/helpers/utils')
-rw-r--r-- | client/src/app/helpers/utils/channel.ts | 34 | ||||
-rw-r--r-- | client/src/app/helpers/utils/date.ts | 25 | ||||
-rw-r--r-- | client/src/app/helpers/utils/html.ts | 18 | ||||
-rw-r--r-- | client/src/app/helpers/utils/index.ts | 7 | ||||
-rw-r--r-- | client/src/app/helpers/utils/object.ts | 47 | ||||
-rw-r--r-- | client/src/app/helpers/utils/ui.ts | 33 | ||||
-rw-r--r-- | client/src/app/helpers/utils/upload.ts | 37 | ||||
-rw-r--r-- | client/src/app/helpers/utils/url.ts | 71 |
8 files changed, 272 insertions, 0 deletions
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 @@ | |||
1 | import { first, map } from 'rxjs/operators' | ||
2 | import { SelectChannelItem } from 'src/types/select-options-item.model' | ||
3 | import { AuthService } from '../../core/auth' | ||
4 | |||
5 | function 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 | |||
32 | export { | ||
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 @@ | |||
1 | import { DatePipe } from '@angular/common' | ||
2 | |||
3 | const datePipe = new DatePipe('en') | ||
4 | function dateToHuman (date: string) { | ||
5 | return datePipe.transform(date, 'medium') | ||
6 | } | ||
7 | |||
8 | function 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 | |||
22 | export { | ||
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 @@ | |||
1 | import { immutableAssign } from './object' | ||
2 | |||
3 | function objectLineFeedToHtml (obj: any, keyToNormalize: string) { | ||
4 | return immutableAssign(obj, { | ||
5 | [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize]) | ||
6 | }) | ||
7 | } | ||
8 | |||
9 | function lineFeedToHtml (text: string) { | ||
10 | if (!text) return text | ||
11 | |||
12 | return text.replace(/\r?\n|\r/g, '<br />') | ||
13 | } | ||
14 | |||
15 | export { | ||
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 @@ | |||
1 | export * from './channel' | ||
2 | export * from './date' | ||
3 | export * from './html' | ||
4 | export * from './object' | ||
5 | export * from './ui' | ||
6 | export * from './upload' | ||
7 | export * 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 @@ | |||
1 | function immutableAssign <A, B> (target: A, source: B) { | ||
2 | return Object.assign({}, target, source) | ||
3 | } | ||
4 | |||
5 | function removeElementFromArray <T> (arr: T[], elem: T) { | ||
6 | const index = arr.indexOf(elem) | ||
7 | if (index !== -1) arr.splice(index, 1) | ||
8 | } | ||
9 | |||
10 | function 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 | |||
21 | function 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 | |||
30 | function 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 | |||
41 | export { | ||
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 @@ | |||
1 | function scrollToTop (behavior: 'auto' | 'smooth' = 'auto') { | ||
2 | window.scrollTo({ | ||
3 | left: 0, | ||
4 | top: 0, | ||
5 | behavior | ||
6 | }) | ||
7 | } | ||
8 | |||
9 | function 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 | |||
19 | function 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 | |||
29 | export { | ||
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 @@ | |||
1 | import { HttpErrorResponse } from '@angular/common/http' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { HttpStatusCode } from '@shared/models' | ||
4 | |||
5 | function 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 | |||
35 | export { | ||
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 @@ | |||
1 | import { environment } from '../../../environments/environment' | ||
2 | |||
3 | // Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript | ||
4 | function 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 | |||
17 | function 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 | |||
30 | function 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 | ||
41 | function 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 | |||
66 | export { | ||
67 | getParameterByName, | ||
68 | objectToFormData, | ||
69 | getAbsoluteAPIUrl, | ||
70 | getAbsoluteEmbedUrl | ||
71 | } | ||