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