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