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