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