]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/assets/player/videojs-components/settings-menu-button.ts
Update Angular -> 8.2.0
[github/Chocobozzz/PeerTube.git] / client / src / assets / player / videojs-components / settings-menu-button.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
c6352f2c 8import { SettingsMenuItem } from './settings-menu-item'
2adfc7ea
C
9import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
10import { toTitleCase } from '../utils'
c6352f2c
C
11
12const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
13const Menu: VideoJSComponentInterface = videojsUntyped.getComponent('Menu')
14const Component: VideoJSComponentInterface = videojsUntyped.getComponent('Component')
15
16class SettingsButton extends Button {
16b55259
C
17 playerComponent = videojs.Player
18 dialog: any
19 dialogEl: any
20 menu: any
21 panel: any
22 panelChild: any
23
24 addSettingsItemHandler: Function
25 disposeSettingsItemHandler: Function
26 playerClickHandler: Function
27 userInactiveHandler: Function
28
c199c427 29 constructor (player: videojs.Player, options: any) {
c6352f2c
C
30 super(player, options)
31
32 this.playerComponent = player
33 this.dialog = this.playerComponent.addChild('settingsDialog')
34 this.dialogEl = this.dialog.el_
35 this.menu = null
36 this.panel = this.dialog.addChild('settingsPanel')
37 this.panelChild = this.panel.addChild('settingsPanelChild')
38
39 this.addClass('vjs-settings')
40 this.el_.setAttribute('aria-label', 'Settings Button')
41
42 // Event handlers
43 this.addSettingsItemHandler = this.onAddSettingsItem.bind(this)
44 this.disposeSettingsItemHandler = this.onDisposeSettingsItem.bind(this)
45 this.playerClickHandler = this.onPlayerClick.bind(this)
46 this.userInactiveHandler = this.onUserInactive.bind(this)
47
48 this.buildMenu()
49 this.bindEvents()
50
b891f9bc 51 // Prepare the dialog
c6352f2c
C
52 this.player().one('play', () => this.hideDialog())
53 }
54
55 onPlayerClick (event: MouseEvent) {
56 const element = event.target as HTMLElement
57 if (element.classList.contains('vjs-settings') || element.parentElement.classList.contains('vjs-settings')) {
58 return
59 }
60
61 if (!this.dialog.hasClass('vjs-hidden')) {
62 this.hideDialog()
63 }
64 }
65
c199c427 66 onDisposeSettingsItem (event: any, name: string) {
c6352f2c 67 if (name === undefined) {
c4710631 68 const children = this.menu.children()
c6352f2c
C
69
70 while (children.length > 0) {
71 children[0].dispose()
72 this.menu.removeChild(children[0])
73 }
74
75 this.addClass('vjs-hidden')
76 } else {
c4710631 77 const item = this.menu.getChild(name)
c6352f2c
C
78
79 if (item) {
80 item.dispose()
81 this.menu.removeChild(item)
82 }
83 }
84
85 this.hideDialog()
86
87 if (this.options_.entries.length === 0) {
88 this.addClass('vjs-hidden')
89 }
90 }
91
c199c427 92 onAddSettingsItem (event: any, data: any) {
c6352f2c
C
93 const [ entry, options ] = data
94
95 this.addMenuItem(entry, options)
96 this.removeClass('vjs-hidden')
97 }
98
99 onUserInactive () {
100 if (!this.dialog.hasClass('vjs-hidden')) {
101 this.hideDialog()
102 }
103 }
104
105 bindEvents () {
106 this.playerComponent.on('click', this.playerClickHandler)
107 this.playerComponent.on('addsettingsitem', this.addSettingsItemHandler)
108 this.playerComponent.on('disposesettingsitem', this.disposeSettingsItemHandler)
109 this.playerComponent.on('userinactive', this.userInactiveHandler)
110 }
111
112 buildCSSClass () {
113 return `vjs-icon-settings ${super.buildCSSClass()}`
114 }
115
116 handleClick () {
117 if (this.dialog.hasClass('vjs-hidden')) {
118 this.showDialog()
119 } else {
120 this.hideDialog()
121 }
122 }
123
124 showDialog () {
125 this.menu.el_.style.opacity = '1'
126 this.dialog.show()
127
128 this.setDialogSize(this.getComponentSize(this.menu))
129 }
130
131 hideDialog () {
132 this.dialog.hide()
133 this.setDialogSize(this.getComponentSize(this.menu))
134 this.menu.el_.style.opacity = '1'
135 this.resetChildren()
136 }
137
244b4ae3 138 getComponentSize (element: any) {
c6352f2c
C
139 let width: number = null
140 let height: number = null
141
142 // Could be component or just DOM element
143 if (element instanceof Component) {
144 width = element.el_.offsetWidth
145 height = element.el_.offsetHeight
146
147 // keep width/height as properties for direct use
148 element.width = width
149 element.height = height
150 } else {
151 width = element.offsetWidth
152 height = element.offsetHeight
153 }
154
155 return [ width, height ]
156 }
157
158 setDialogSize ([ width, height ]: number[]) {
159 if (typeof height !== 'number') {
160 return
161 }
162
c4710631
C
163 const offset = this.options_.setup.maxHeightOffset
164 const maxHeight = this.playerComponent.el_.offsetHeight - offset
c6352f2c
C
165
166 if (height > maxHeight) {
167 height = maxHeight
168 width += 17
169 this.panel.el_.style.maxHeight = `${height}px`
170 } else if (this.panel.el_.style.maxHeight !== '') {
171 this.panel.el_.style.maxHeight = ''
172 }
173
174 this.dialogEl.style.width = `${width}px`
175 this.dialogEl.style.height = `${height}px`
176 }
177
178 buildMenu () {
179 this.menu = new Menu(this.player())
180 this.menu.addClass('vjs-main-menu')
c4710631 181 const entries = this.options_.entries
c6352f2c
C
182
183 if (entries.length === 0) {
184 this.addClass('vjs-hidden')
185 this.panelChild.addChild(this.menu)
186 return
187 }
188
c4710631 189 for (const entry of entries) {
c6352f2c
C
190 this.addMenuItem(entry, this.options_)
191 }
192
193 this.panelChild.addChild(this.menu)
194 }
195
244b4ae3 196 addMenuItem (entry: any, options: any) {
951ef829 197 const openSubMenu = function (this: any) {
c6352f2c
C
198 if (videojsUntyped.dom.hasClass(this.el_, 'open')) {
199 videojsUntyped.dom.removeClass(this.el_, 'open')
200 } else {
201 videojsUntyped.dom.addClass(this.el_, 'open')
202 }
203 }
204
205 options.name = toTitleCase(entry)
c4710631 206 const settingsMenuItem = new SettingsMenuItem(this.player(), options, entry, this as any)
c6352f2c
C
207
208 this.menu.addChild(settingsMenuItem)
209
210 // Hide children to avoid sub menus stacking on top of each other
211 // or having multiple menus open
212 settingsMenuItem.on('click', videojs.bind(this, this.hideChildren))
213
214 // Whether to add or remove selected class on the settings sub menu element
215 settingsMenuItem.on('click', openSubMenu)
216 }
217
218 resetChildren () {
c4710631 219 for (const menuChild of this.menu.children()) {
c6352f2c
C
220 menuChild.reset()
221 }
222 }
223
224 /**
225 * Hide all the sub menus
226 */
227 hideChildren () {
c4710631 228 for (const menuChild of this.menu.children()) {
c6352f2c
C
229 menuChild.hideSubMenu()
230 }
231 }
232
233}
234
235class SettingsPanel extends Component {
c199c427 236 constructor (player: videojs.Player, options: any) {
c6352f2c
C
237 super(player, options)
238 }
239
240 createEl () {
241 return super.createEl('div', {
242 className: 'vjs-settings-panel',
243 innerHTML: '',
244 tabIndex: -1
245 })
246 }
247}
248
249class SettingsPanelChild extends Component {
c199c427 250 constructor (player: videojs.Player, options: any) {
c6352f2c
C
251 super(player, options)
252 }
253
254 createEl () {
255 return super.createEl('div', {
256 className: 'vjs-settings-panel-child',
257 innerHTML: '',
258 tabIndex: -1
259 })
260 }
261}
262
263class SettingsDialog extends Component {
c199c427 264 constructor (player: videojs.Player, options: any) {
c6352f2c
C
265 super(player, options)
266 this.hide()
267 }
268
269 /**
270 * Create the component's DOM element
271 *
272 * @return {Element}
273 * @method createEl
274 */
275 createEl () {
276 const uniqueId = this.id_
277 const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId
278 const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId
279
280 return super.createEl('div', {
281 className: 'vjs-settings-dialog vjs-modal-overlay',
282 innerHTML: '',
283 tabIndex: -1
284 }, {
285 'role': 'dialog',
286 'aria-labelledby': dialogLabelId,
287 'aria-describedby': dialogDescriptionId
288 })
289 }
290
291}
292
e945b184 293SettingsButton.prototype.controlText_ = 'Settings'
c6352f2c
C
294
295Component.registerComponent('SettingsButton', SettingsButton)
296Component.registerComponent('SettingsDialog', SettingsDialog)
297Component.registerComponent('SettingsPanel', SettingsPanel)
298Component.registerComponent('SettingsPanelChild', SettingsPanelChild)
299
300export { SettingsButton, SettingsDialog, SettingsPanel, SettingsPanelChild }