]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/assets/player/videojs-components/settings-menu-item.ts
Update Angular -> 8.2.0
[github/Chocobozzz/PeerTube.git] / client / src / assets / player / videojs-components / settings-menu-item.ts
CommitLineData
c6352f2c
C
1// Author: Yanko Shterev
2// Thanks https://github.com/yshterev/videojs-settings-menu
3
c199c427
C
4// FIXME: something weird with our path definition in tsconfig and typings
5// @ts-ignore
6import * as videojs from 'video.js'
7
2adfc7ea
C
8import { toTitleCase } from '../utils'
9import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
c6352f2c
C
10
11const MenuItem: VideoJSComponentInterface = videojsUntyped.getComponent('MenuItem')
12const component: VideoJSComponentInterface = videojsUntyped.getComponent('Component')
13
14class SettingsMenuItem extends MenuItem {
16b55259
C
15 settingsButton: any
16 dialog: any
17 mainMenu: any
18 panel: any
19 panelChild: any
20 panelChildEl: any
21 size: any
22 menuToLoad: string
23 subMenu: any
24
25 submenuClickHandler: Function
26 transitionEndHandler: Function
27
28 settingsSubMenuTitleEl_: any
29 settingsSubMenuValueEl_: any
30 settingsSubMenuEl_: any
c6352f2c 31
c199c427 32 constructor (player: videojs.Player, options: any, entry: string, menuButton: VideoJSComponentInterface) {
c6352f2c
C
33 super(player, options)
34
35 this.settingsButton = menuButton
36 this.dialog = this.settingsButton.dialog
37 this.mainMenu = this.settingsButton.menu
38 this.panel = this.dialog.getChild('settingsPanel')
39 this.panelChild = this.panel.getChild('settingsPanelChild')
40 this.panelChildEl = this.panelChild.el_
41
42 this.size = null
43
44 // keep state of what menu type is loading next
45 this.menuToLoad = 'mainmenu'
46
47 const subMenuName = toTitleCase(entry)
48 const SubMenuComponent = videojsUntyped.getComponent(subMenuName)
49
50 if (!SubMenuComponent) {
51 throw new Error(`Component ${subMenuName} does not exist`)
52 }
53 this.subMenu = new SubMenuComponent(this.player(), options, menuButton, this)
16f7022b
C
54 const subMenuClass = this.subMenu.buildCSSClass().split(' ')[0]
55 this.settingsSubMenuEl_.className += ' ' + subMenuClass
c6352f2c
C
56
57 this.eventHandlers()
58
59 player.ready(() => {
b335ccec
C
60 // Voodoo magic for IOS
61 setTimeout(() => {
bfbd9128
C
62 // Player was destroyed
63 if (!this.player_) return
64
b335ccec 65 this.build()
5363a766
C
66
67 // Update on rate change
68 player.on('ratechange', this.submenuClickHandler)
69
c32bf839
C
70 if (subMenuName === 'CaptionsButton') {
71 // Hack to regenerate captions on HTTP fallback
72 player.on('captionsChanged', () => {
73 setTimeout(() => {
74 this.settingsSubMenuEl_.innerHTML = ''
75 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
76 this.update()
77 this.bindClickEvents()
78
79 }, 0)
80 })
81 }
82
b335ccec
C
83 this.reset()
84 }, 0)
c6352f2c
C
85 })
86 }
87
88 eventHandlers () {
89 this.submenuClickHandler = this.onSubmenuClick.bind(this)
90 this.transitionEndHandler = this.onTransitionEnd.bind(this)
91 }
92
244b4ae3 93 onSubmenuClick (event: any) {
c6352f2c
C
94 let target = null
95
96 if (event.type === 'tap') {
97 target = event.target
98 } else {
99 target = event.currentTarget
100 }
101
5363a766 102 if (target && target.classList.contains('vjs-back-button')) {
c6352f2c
C
103 this.loadMainMenu()
104 return
105 }
106
107 // To update the sub menu value on click, setTimeout is needed because
108 // updating the value is not instant
109 setTimeout(() => this.update(event), 0)
110 }
111
112 /**
113 * Create the component's DOM element
114 *
115 * @return {Element}
116 * @method createEl
117 */
118 createEl () {
119 const el = videojsUntyped.dom.createEl('li', {
120 className: 'vjs-menu-item'
121 })
122
123 this.settingsSubMenuTitleEl_ = videojsUntyped.dom.createEl('div', {
124 className: 'vjs-settings-sub-menu-title'
125 })
126
127 el.appendChild(this.settingsSubMenuTitleEl_)
128
129 this.settingsSubMenuValueEl_ = videojsUntyped.dom.createEl('div', {
130 className: 'vjs-settings-sub-menu-value'
131 })
132
133 el.appendChild(this.settingsSubMenuValueEl_)
134
135 this.settingsSubMenuEl_ = videojsUntyped.dom.createEl('div', {
136 className: 'vjs-settings-sub-menu'
137 })
138
139 return el
140 }
141
142 /**
143 * Handle click on menu item
144 *
145 * @method handleClick
146 */
147 handleClick () {
148 this.menuToLoad = 'submenu'
149 // Remove open class to ensure only the open submenu gets this class
150 videojsUntyped.dom.removeClass(this.el_, 'open')
151
152 super.handleClick()
153
154 this.mainMenu.el_.style.opacity = '0'
155 // Whether to add or remove vjs-hidden class on the settingsSubMenuEl element
156 if (videojsUntyped.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
157 videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
158
159 // animation not played without timeout
160 setTimeout(() => {
161 this.settingsSubMenuEl_.style.opacity = '1'
162 this.settingsSubMenuEl_.style.marginRight = '0px'
163 }, 0)
164
165 this.settingsButton.setDialogSize(this.size)
166 } else {
167 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
168 }
169 }
170
171 /**
172 * Create back button
173 *
174 * @method createBackButton
175 */
176 createBackButton () {
177 const button = this.subMenu.menu.addChild('MenuItem', {}, 0)
178 button.name_ = 'BackButton'
179 button.addClass('vjs-back-button')
e945b184 180 button.el_.innerHTML = this.player_.localize(this.subMenu.controlText_)
c6352f2c
C
181 }
182
183 /**
184 * Add/remove prefixed event listener for CSS Transition
185 *
186 * @method PrefixedEvent
187 */
244b4ae3 188 PrefixedEvent (element: any, type: any, callback: any, action = 'addEvent') {
c4710631 189 const prefix = ['webkit', 'moz', 'MS', 'o', '']
c6352f2c
C
190
191 for (let p = 0; p < prefix.length; p++) {
192 if (!prefix[p]) {
193 type = type.toLowerCase()
194 }
195
196 if (action === 'addEvent') {
197 element.addEventListener(prefix[p] + type, callback, false)
198 } else if (action === 'removeEvent') {
199 element.removeEventListener(prefix[p] + type, callback, false)
200 }
201 }
202 }
203
244b4ae3 204 onTransitionEnd (event: any) {
c6352f2c
C
205 if (event.propertyName !== 'margin-right') {
206 return
207 }
208
209 if (this.menuToLoad === 'mainmenu') {
210 // hide submenu
211 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
212
213 // reset opacity to 0
214 this.settingsSubMenuEl_.style.opacity = '0'
215 }
216 }
217
218 reset () {
219 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
220 this.settingsSubMenuEl_.style.opacity = '0'
221 this.setMargin()
222 }
223
224 loadMainMenu () {
225 this.menuToLoad = 'mainmenu'
226 this.mainMenu.show()
227 this.mainMenu.el_.style.opacity = '0'
228
229 // back button will always take you to main menu, so set dialog sizes
230 this.settingsButton.setDialogSize([this.mainMenu.width, this.mainMenu.height])
231
232 // animation not triggered without timeout (some async stuff ?!?)
233 setTimeout(() => {
234 // animate margin and opacity before hiding the submenu
235 // this triggers CSS Transition event
236 this.setMargin()
237 this.mainMenu.el_.style.opacity = '1'
238 }, 0)
239 }
240
241 build () {
2adfc7ea 242 this.subMenu.on('updateLabel', () => {
c6352f2c 243 this.update()
2adfc7ea 244 })
3b6f205c
C
245 this.subMenu.on('menuChanged', () => {
246 this.bindClickEvents()
247 this.setSize()
248 this.update()
249 })
c6352f2c 250
e945b184 251 this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_)
c6352f2c
C
252 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
253 this.panelChildEl.appendChild(this.settingsSubMenuEl_)
254 this.update()
255
256 this.createBackButton()
3b6f205c 257 this.setSize()
c6352f2c
C
258 this.bindClickEvents()
259
260 // prefixed event listeners for CSS TransitionEnd
261 this.PrefixedEvent(
262 this.settingsSubMenuEl_,
263 'TransitionEnd',
264 this.transitionEndHandler,
265 'addEvent'
266 )
267 }
268
244b4ae3 269 update (event?: any) {
c199c427 270 let target: HTMLElement = null
c4710631 271 const subMenu = this.subMenu.name()
c6352f2c
C
272
273 if (event && event.type === 'tap') {
274 target = event.target
275 } else if (event) {
276 target = event.currentTarget
277 }
278
279 // Playback rate menu button doesn't get a vjs-selected class
280 // or sets options_['selected'] on the selected playback rate.
281 // Thus we get the submenu value based on the labelEl of playbackRateMenuButton
282 if (subMenu === 'PlaybackRateMenuButton') {
283 setTimeout(() => this.settingsSubMenuValueEl_.innerHTML = this.subMenu.labelEl_.innerHTML, 250)
284 } else {
285 // Loop trough the submenu items to find the selected child
c4710631 286 for (const subMenuItem of this.subMenu.menu.children_) {
c6352f2c
C
287 if (!(subMenuItem instanceof component)) {
288 continue
289 }
290
a8462c8e
C
291 if (subMenuItem.hasClass('vjs-selected')) {
292 // Prefer to use the function
293 if (typeof subMenuItem.getLabel === 'function') {
294 this.settingsSubMenuValueEl_.innerHTML = subMenuItem.getLabel()
c6352f2c 295 break
a8462c8e 296 }
c6352f2c 297
a8462c8e 298 this.settingsSubMenuValueEl_.innerHTML = subMenuItem.options_.label
c6352f2c
C
299 }
300 }
301 }
302
303 if (target && !target.classList.contains('vjs-back-button')) {
304 this.settingsButton.hideDialog()
305 }
306 }
307
308 bindClickEvents () {
c4710631 309 for (const item of this.subMenu.menu.children()) {
c6352f2c
C
310 if (!(item instanceof component)) {
311 continue
312 }
313 item.on(['tap', 'click'], this.submenuClickHandler)
314 }
315 }
316
317 // save size of submenus on first init
318 // if number of submenu items change dynamically more logic will be needed
3b6f205c 319 setSize () {
c6352f2c 320 this.dialog.removeClass('vjs-hidden')
3b6f205c 321 videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
c6352f2c
C
322 this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_)
323 this.setMargin()
324 this.dialog.addClass('vjs-hidden')
325 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
326 }
327
328 setMargin () {
c4710631 329 const [ width ] = this.size
c6352f2c
C
330
331 this.settingsSubMenuEl_.style.marginRight = `-${width}px`
332 }
333
334 /**
335 * Hide the sub menu
336 */
337 hideSubMenu () {
338 // after removing settings item this.el_ === null
339 if (!this.el_) {
340 return
341 }
342
343 if (videojsUntyped.dom.hasClass(this.el_, 'open')) {
344 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
345 videojsUntyped.dom.removeClass(this.el_, 'open')
346 }
347 }
348
349}
350
351SettingsMenuItem.prototype.contentElType = 'button'
352videojsUntyped.registerComponent('SettingsMenuItem', SettingsMenuItem)
353
354export { SettingsMenuItem }