]>
Commit | Line | Data |
---|---|---|
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 | |
6 | import * as videojs from 'video.js' | |
7 | ||
c6352f2c | 8 | import { SettingsMenuItem } from './settings-menu-item' |
2adfc7ea C |
9 | import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' |
10 | import { toTitleCase } from '../utils' | |
c6352f2c C |
11 | |
12 | const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') | |
13 | const Menu: VideoJSComponentInterface = videojsUntyped.getComponent('Menu') | |
14 | const Component: VideoJSComponentInterface = videojsUntyped.getComponent('Component') | |
15 | ||
16 | class 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 | ||
235 | class 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 | ||
249 | class 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 | ||
263 | class 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 | 293 | SettingsButton.prototype.controlText_ = 'Settings' |
c6352f2c C |
294 | |
295 | Component.registerComponent('SettingsButton', SettingsButton) | |
296 | Component.registerComponent('SettingsDialog', SettingsDialog) | |
297 | Component.registerComponent('SettingsPanel', SettingsPanel) | |
298 | Component.registerComponent('SettingsPanelChild', SettingsPanelChild) | |
299 | ||
300 | export { SettingsButton, SettingsDialog, SettingsPanel, SettingsPanelChild } |