]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/assets/player/videojs-components/settings-menu-button.ts
Use source sans 3 font
[github/Chocobozzz/PeerTube.git] / client / src / assets / player / videojs-components / settings-menu-button.ts
CommitLineData
f5fcd9f7 1// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu
c6352f2c 2import { SettingsMenuItem } from './settings-menu-item'
2adfc7ea 3import { toTitleCase } from '../utils'
512decf3 4import videojs from 'video.js'
f5fcd9f7
C
5
6import { SettingsDialog } from './settings-dialog'
7import { SettingsPanel } from './settings-panel'
8import { SettingsPanelChild } from './settings-panel-child'
c6352f2c 9
f5fcd9f7
C
10const Button = videojs.getComponent('Button')
11const Menu = videojs.getComponent('Menu')
12const Component = videojs.getComponent('Component')
13
14export interface SettingsButtonOptions extends videojs.ComponentOptions {
15 entries: any[]
16 setup?: {
17 maxHeightOffset: number
18 }
19}
c6352f2c
C
20
21class SettingsButton extends Button {
f5fcd9f7
C
22 dialog: SettingsDialog
23 dialogEl: HTMLElement
24 menu: videojs.Menu
25 panel: SettingsPanel
26 panelChild: SettingsPanelChild
27
28 addSettingsItemHandler: typeof SettingsButton.prototype.onAddSettingsItem
29 disposeSettingsItemHandler: typeof SettingsButton.prototype.onDisposeSettingsItem
30 playerClickHandler: typeof SettingsButton.prototype.onPlayerClick
31 userInactiveHandler: typeof SettingsButton.prototype.onUserInactive
32
33 private settingsButtonOptions: SettingsButtonOptions
34
7e37e111 35 constructor (player: videojs.Player, options?: SettingsButtonOptions) {
c6352f2c
C
36 super(player, options)
37
f5fcd9f7
C
38 this.settingsButtonOptions = options
39
40 this.controlText('Settings')
41
42 this.dialog = this.player().addChild('settingsDialog')
43 this.dialogEl = this.dialog.el() as HTMLElement
c6352f2c
C
44 this.menu = null
45 this.panel = this.dialog.addChild('settingsPanel')
46 this.panelChild = this.panel.addChild('settingsPanelChild')
47
48 this.addClass('vjs-settings')
f5fcd9f7 49 this.el().setAttribute('aria-label', 'Settings Button')
c6352f2c
C
50
51 // Event handlers
52 this.addSettingsItemHandler = this.onAddSettingsItem.bind(this)
53 this.disposeSettingsItemHandler = this.onDisposeSettingsItem.bind(this)
54 this.playerClickHandler = this.onPlayerClick.bind(this)
55 this.userInactiveHandler = this.onUserInactive.bind(this)
56
57 this.buildMenu()
58 this.bindEvents()
59
b891f9bc 60 // Prepare the dialog
c6352f2c
C
61 this.player().one('play', () => this.hideDialog())
62 }
63
64 onPlayerClick (event: MouseEvent) {
65 const element = event.target as HTMLElement
66 if (element.classList.contains('vjs-settings') || element.parentElement.classList.contains('vjs-settings')) {
67 return
68 }
69
70 if (!this.dialog.hasClass('vjs-hidden')) {
71 this.hideDialog()
72 }
73 }
74
c199c427 75 onDisposeSettingsItem (event: any, name: string) {
c6352f2c 76 if (name === undefined) {
c4710631 77 const children = this.menu.children()
c6352f2c
C
78
79 while (children.length > 0) {
80 children[0].dispose()
81 this.menu.removeChild(children[0])
82 }
83
84 this.addClass('vjs-hidden')
85 } else {
c4710631 86 const item = this.menu.getChild(name)
c6352f2c
C
87
88 if (item) {
89 item.dispose()
90 this.menu.removeChild(item)
91 }
92 }
93
94 this.hideDialog()
95
f5fcd9f7 96 if (this.settingsButtonOptions.entries.length === 0) {
c6352f2c
C
97 this.addClass('vjs-hidden')
98 }
99 }
100
c199c427 101 onAddSettingsItem (event: any, data: any) {
c6352f2c
C
102 const [ entry, options ] = data
103
104 this.addMenuItem(entry, options)
105 this.removeClass('vjs-hidden')
106 }
107
108 onUserInactive () {
109 if (!this.dialog.hasClass('vjs-hidden')) {
110 this.hideDialog()
111 }
112 }
113
114 bindEvents () {
f5fcd9f7
C
115 this.player().on('click', this.playerClickHandler)
116 this.player().on('addsettingsitem', this.addSettingsItemHandler)
117 this.player().on('disposesettingsitem', this.disposeSettingsItemHandler)
118 this.player().on('userinactive', this.userInactiveHandler)
c6352f2c
C
119 }
120
121 buildCSSClass () {
122 return `vjs-icon-settings ${super.buildCSSClass()}`
123 }
124
125 handleClick () {
126 if (this.dialog.hasClass('vjs-hidden')) {
127 this.showDialog()
128 } else {
129 this.hideDialog()
130 }
131 }
132
133 showDialog () {
f5fcd9f7 134 this.player().peertube().onMenuOpen();
d1f21ebb 135
f5fcd9f7 136 (this.menu.el() as HTMLElement).style.opacity = '1'
c6352f2c
C
137 this.dialog.show()
138
139 this.setDialogSize(this.getComponentSize(this.menu))
140 }
141
142 hideDialog () {
d1f21ebb
C
143 this.player_.peertube().onMenuClosed()
144
c6352f2c 145 this.dialog.hide()
f5fcd9f7
C
146 this.setDialogSize(this.getComponentSize(this.menu));
147 (this.menu.el() as HTMLElement).style.opacity = '1'
c6352f2c
C
148 this.resetChildren()
149 }
150
f5fcd9f7 151 getComponentSize (element: videojs.Component | HTMLElement) {
c6352f2c
C
152 let width: number = null
153 let height: number = null
154
155 // Could be component or just DOM element
156 if (element instanceof Component) {
f5fcd9f7
C
157 const el = element.el() as HTMLElement
158
159 width = el.offsetWidth
160 height = el.offsetHeight;
c6352f2c 161
f5fcd9f7
C
162 (element as any).width = width;
163 (element as any).height = height
c6352f2c
C
164 } else {
165 width = element.offsetWidth
166 height = element.offsetHeight
167 }
168
169 return [ width, height ]
170 }
171
172 setDialogSize ([ width, height ]: number[]) {
173 if (typeof height !== 'number') {
174 return
175 }
176
f5fcd9f7
C
177 const offset = this.settingsButtonOptions.setup.maxHeightOffset
178 const maxHeight = (this.player().el() as HTMLElement).offsetHeight - offset // FIXME: typings
179
180 const panelEl = this.panel.el() as HTMLElement
c6352f2c
C
181
182 if (height > maxHeight) {
183 height = maxHeight
184 width += 17
f5fcd9f7
C
185 panelEl.style.maxHeight = `${height}px`
186 } else if (panelEl.style.maxHeight !== '') {
187 panelEl.style.maxHeight = ''
c6352f2c
C
188 }
189
190 this.dialogEl.style.width = `${width}px`
191 this.dialogEl.style.height = `${height}px`
192 }
193
194 buildMenu () {
195 this.menu = new Menu(this.player())
196 this.menu.addClass('vjs-main-menu')
f5fcd9f7 197 const entries = this.settingsButtonOptions.entries
c6352f2c
C
198
199 if (entries.length === 0) {
200 this.addClass('vjs-hidden')
201 this.panelChild.addChild(this.menu)
202 return
203 }
204
c4710631 205 for (const entry of entries) {
f5fcd9f7 206 this.addMenuItem(entry, this.settingsButtonOptions)
c6352f2c
C
207 }
208
209 this.panelChild.addChild(this.menu)
210 }
211
244b4ae3 212 addMenuItem (entry: any, options: any) {
951ef829 213 const openSubMenu = function (this: any) {
f5fcd9f7
C
214 if (videojs.dom.hasClass(this.el_, 'open')) {
215 videojs.dom.removeClass(this.el_, 'open')
c6352f2c 216 } else {
f5fcd9f7 217 videojs.dom.addClass(this.el_, 'open')
c6352f2c
C
218 }
219 }
220
221 options.name = toTitleCase(entry)
f5fcd9f7
C
222
223 const newOptions = Object.assign({}, options, { entry, menuButton: this })
224 const settingsMenuItem = new SettingsMenuItem(this.player(), newOptions)
c6352f2c
C
225
226 this.menu.addChild(settingsMenuItem)
227
228 // Hide children to avoid sub menus stacking on top of each other
229 // or having multiple menus open
230 settingsMenuItem.on('click', videojs.bind(this, this.hideChildren))
231
232 // Whether to add or remove selected class on the settings sub menu element
233 settingsMenuItem.on('click', openSubMenu)
234 }
235
236 resetChildren () {
c4710631 237 for (const menuChild of this.menu.children()) {
f5fcd9f7 238 (menuChild as SettingsMenuItem).reset()
c6352f2c
C
239 }
240 }
241
242 /**
243 * Hide all the sub menus
244 */
245 hideChildren () {
c4710631 246 for (const menuChild of this.menu.children()) {
f5fcd9f7 247 (menuChild as SettingsMenuItem).hideSubMenu()
c6352f2c
C
248 }
249 }
250
251}
252
c6352f2c 253Component.registerComponent('SettingsButton', SettingsButton)
c6352f2c 254
f5fcd9f7 255export { SettingsButton }