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