]>
Commit | Line | Data |
---|---|---|
f5fcd9f7 | 1 | // Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu |
c6352f2c | 2 | import { SettingsMenuItem } from './settings-menu-item' |
2adfc7ea | 3 | import { toTitleCase } from '../utils' |
f5fcd9f7 C |
4 | import videojs, { VideoJsPlayer } from 'video.js' |
5 | ||
6 | import { SettingsDialog } from './settings-dialog' | |
7 | import { SettingsPanel } from './settings-panel' | |
8 | import { SettingsPanelChild } from './settings-panel-child' | |
c6352f2c | 9 | |
f5fcd9f7 C |
10 | const Button = videojs.getComponent('Button') |
11 | const Menu = videojs.getComponent('Menu') | |
12 | const Component = videojs.getComponent('Component') | |
13 | ||
14 | export interface SettingsButtonOptions extends videojs.ComponentOptions { | |
15 | entries: any[] | |
16 | setup?: { | |
17 | maxHeightOffset: number | |
18 | } | |
19 | } | |
c6352f2c C |
20 | |
21 | class 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 | ||
35 | constructor (player: VideoJsPlayer, 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 | 253 | Component.registerComponent('SettingsButton', SettingsButton) |
c6352f2c | 254 | |
f5fcd9f7 | 255 | export { SettingsButton } |