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