Show default avatar on network error for comments
[oweals/peertube.git] / client / src / app / shared / misc / utils.ts
1 // Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
2
3 import { DatePipe } from '@angular/common'
4 import { environment } from '../../../environments/environment'
5 import { AuthService } from '../../core/auth'
6
7 type ElementEvent = Omit<Event, 'target'> & {
8   target: HTMLElement
9 }
10
11 function 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
24 function populateAsyncUserVideoChannels (authService: AuthService, channel: { id: number, label: string, support?: string }[]) {
25   return new Promise(res => {
26     authService.userInformationLoaded
27       .subscribe(
28         () => {
29           const user = authService.getUser()
30           if (!user) return
31
32           const videoChannels = user.videoChannels
33           if (Array.isArray(videoChannels) === false) return
34
35           videoChannels.forEach(c => channel.push({ id: c.id, label: c.displayName, support: c.support }))
36
37           return res()
38         }
39       )
40   })
41 }
42
43 function getAbsoluteAPIUrl () {
44   let absoluteAPIUrl = environment.apiUrl
45   if (!absoluteAPIUrl) {
46     // The API is on the same domain
47     absoluteAPIUrl = window.location.origin
48   }
49
50   return absoluteAPIUrl
51 }
52
53 const datePipe = new DatePipe('en')
54 function dateToHuman (date: string) {
55   return datePipe.transform(date, 'medium')
56 }
57
58 function durationToString (duration: number) {
59   const hours = Math.floor(duration / 3600)
60   const minutes = Math.floor((duration % 3600) / 60)
61   const seconds = duration % 60
62
63   const minutesPadding = minutes >= 10 ? '' : '0'
64   const secondsPadding = seconds >= 10 ? '' : '0'
65   const displayedHours = hours > 0 ? hours.toString() + ':' : ''
66
67   return displayedHours + minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
68 }
69
70 function immutableAssign <A, B> (target: A, source: B) {
71   return Object.assign({}, target, source)
72 }
73
74 function objectToUrlEncoded (obj: any) {
75   const str: string[] = []
76   for (const key of Object.keys(obj)) {
77     str.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
78   }
79
80   return str.join('&')
81 }
82
83 // Thanks: https://gist.github.com/ghinda/8442a57f22099bdb2e34
84 function objectToFormData (obj: any, form?: FormData, namespace?: string) {
85   const fd = form || new FormData()
86   let formKey
87
88   for (const key of Object.keys(obj)) {
89     if (namespace) formKey = `${namespace}[${key}]`
90     else formKey = key
91
92     if (obj[key] === undefined) continue
93
94     if (Array.isArray(obj[key]) && obj[key].length === 0) {
95       fd.append(key, null)
96       continue
97     }
98
99     if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) {
100       objectToFormData(obj[ key ], fd, formKey)
101     } else {
102       fd.append(formKey, obj[ key ])
103     }
104   }
105
106   return fd
107 }
108
109 function objectLineFeedToHtml (obj: any, keyToNormalize: string) {
110   return immutableAssign(obj, {
111     [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize])
112   })
113 }
114
115 function lineFeedToHtml (text: string) {
116   if (!text) return text
117
118   return text.replace(/\r?\n|\r/g, '<br />')
119 }
120
121 function removeElementFromArray <T> (arr: T[], elem: T) {
122   const index = arr.indexOf(elem)
123   if (index !== -1) arr.splice(index, 1)
124 }
125
126 function sortBy (obj: any[], key1: string, key2?: string) {
127   return obj.sort((a, b) => {
128     const elem1 = key2 ? a[key1][key2] : a[key1]
129     const elem2 = key2 ? b[key1][key2] : b[key1]
130
131     if (elem1 < elem2) return -1
132     if (elem1 === elem2) return 0
133     return 1
134   })
135 }
136
137 function scrollToTop () {
138   window.scroll(0, 0)
139 }
140
141 // Thanks: https://github.com/uupaa/dynamic-import-polyfill
142 function importModule (path: string) {
143   return new Promise((resolve, reject) => {
144     const vector = '$importModule$' + Math.random().toString(32).slice(2)
145     const script = document.createElement('script')
146
147     const destructor = () => {
148       delete window[ vector ]
149       script.onerror = null
150       script.onload = null
151       script.remove()
152       URL.revokeObjectURL(script.src)
153       script.src = ''
154     }
155
156     script.defer = true
157     script.type = 'module'
158
159     script.onerror = () => {
160       reject(new Error(`Failed to import: ${path}`))
161       destructor()
162     }
163     script.onload = () => {
164       resolve(window[ vector ])
165       destructor()
166     }
167     const absURL = (environment.apiUrl || window.location.origin) + path
168     const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module
169     const blob = new Blob([ loader ], { type: 'text/javascript' })
170     script.src = URL.createObjectURL(blob)
171
172     document.head.appendChild(script)
173   })
174 }
175
176 function isInViewport (el: HTMLElement) {
177   const bounding = el.getBoundingClientRect()
178   return (
179       bounding.top >= 0 &&
180       bounding.left >= 0 &&
181       bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
182       bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
183   )
184 }
185
186 function isXPercentInViewport (el: HTMLElement, percentVisible: number) {
187   const rect = el.getBoundingClientRect()
188   const windowHeight = (window.innerHeight || document.documentElement.clientHeight)
189
190   return !(
191     Math.floor(100 - (((rect.top >= 0 ? 0 : rect.top) / +-(rect.height / 1)) * 100)) < percentVisible ||
192     Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100) < percentVisible
193   )
194 }
195
196 export {
197   ElementEvent,
198   sortBy,
199   durationToString,
200   lineFeedToHtml,
201   objectToUrlEncoded,
202   getParameterByName,
203   populateAsyncUserVideoChannels,
204   getAbsoluteAPIUrl,
205   dateToHuman,
206   immutableAssign,
207   objectToFormData,
208   objectLineFeedToHtml,
209   removeElementFromArray,
210   importModule,
211   scrollToTop,
212   isInViewport,
213   isXPercentInViewport
214 }