diff options
Diffstat (limited to 'client/src/assets/player/videojs-components/settings-menu-item.ts')
-rw-r--r-- | client/src/assets/player/videojs-components/settings-menu-item.ts | 378 |
1 files changed, 0 insertions, 378 deletions
diff --git a/client/src/assets/player/videojs-components/settings-menu-item.ts b/client/src/assets/player/videojs-components/settings-menu-item.ts deleted file mode 100644 index 31d42c456..000000000 --- a/client/src/assets/player/videojs-components/settings-menu-item.ts +++ /dev/null | |||
@@ -1,378 +0,0 @@ | |||
1 | import videojs from 'video.js' | ||
2 | // Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu | ||
3 | import { toTitleCase } from '../utils' | ||
4 | import { SettingsDialog } from './settings-dialog' | ||
5 | import { SettingsButton } from './settings-menu-button' | ||
6 | import { SettingsPanel } from './settings-panel' | ||
7 | import { SettingsPanelChild } from './settings-panel-child' | ||
8 | |||
9 | const MenuItem = videojs.getComponent('MenuItem') | ||
10 | const component = videojs.getComponent('Component') | ||
11 | |||
12 | export interface SettingsMenuItemOptions extends videojs.MenuItemOptions { | ||
13 | entry: string | ||
14 | menuButton: SettingsButton | ||
15 | } | ||
16 | |||
17 | class SettingsMenuItem extends MenuItem { | ||
18 | settingsButton: SettingsButton | ||
19 | dialog: SettingsDialog | ||
20 | mainMenu: videojs.Menu | ||
21 | panel: SettingsPanel | ||
22 | panelChild: SettingsPanelChild | ||
23 | panelChildEl: HTMLElement | ||
24 | size: number[] | ||
25 | menuToLoad: string | ||
26 | subMenu: SettingsButton | ||
27 | |||
28 | submenuClickHandler: typeof SettingsMenuItem.prototype.onSubmenuClick | ||
29 | transitionEndHandler: typeof SettingsMenuItem.prototype.onTransitionEnd | ||
30 | |||
31 | settingsSubMenuTitleEl_: HTMLElement | ||
32 | settingsSubMenuValueEl_: HTMLElement | ||
33 | settingsSubMenuEl_: HTMLElement | ||
34 | |||
35 | constructor (player: videojs.Player, options?: SettingsMenuItemOptions) { | ||
36 | super(player, options) | ||
37 | |||
38 | this.settingsButton = options.menuButton | ||
39 | this.dialog = this.settingsButton.dialog | ||
40 | this.mainMenu = this.settingsButton.menu | ||
41 | this.panel = this.dialog.getChild('settingsPanel') | ||
42 | this.panelChild = this.panel.getChild('settingsPanelChild') | ||
43 | this.panelChildEl = this.panelChild.el() as HTMLElement | ||
44 | |||
45 | this.size = null | ||
46 | |||
47 | // keep state of what menu type is loading next | ||
48 | this.menuToLoad = 'mainmenu' | ||
49 | |||
50 | const subMenuName = toTitleCase(options.entry) | ||
51 | const SubMenuComponent = videojs.getComponent(subMenuName) | ||
52 | |||
53 | if (!SubMenuComponent) { | ||
54 | throw new Error(`Component ${subMenuName} does not exist`) | ||
55 | } | ||
56 | |||
57 | const newOptions = Object.assign({}, options, { entry: options.menuButton, menuButton: this }) | ||
58 | |||
59 | this.subMenu = new SubMenuComponent(this.player(), newOptions) as SettingsButton | ||
60 | const subMenuClass = this.subMenu.buildCSSClass().split(' ')[0] | ||
61 | this.settingsSubMenuEl_.className += ' ' + subMenuClass | ||
62 | |||
63 | this.eventHandlers() | ||
64 | |||
65 | player.ready(() => { | ||
66 | // Voodoo magic for IOS | ||
67 | setTimeout(() => { | ||
68 | // Player was destroyed | ||
69 | if (!this.player_) return | ||
70 | |||
71 | this.build() | ||
72 | |||
73 | // Update on rate change | ||
74 | player.on('ratechange', this.submenuClickHandler) | ||
75 | |||
76 | if (subMenuName === 'CaptionsButton') { | ||
77 | // Hack to regenerate captions on HTTP fallback | ||
78 | player.on('captionsChanged', () => { | ||
79 | setTimeout(() => { | ||
80 | this.settingsSubMenuEl_.innerHTML = '' | ||
81 | this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el()) | ||
82 | this.update() | ||
83 | this.bindClickEvents() | ||
84 | }, 0) | ||
85 | }) | ||
86 | } | ||
87 | |||
88 | this.reset() | ||
89 | }, 0) | ||
90 | }) | ||
91 | } | ||
92 | |||
93 | eventHandlers () { | ||
94 | this.submenuClickHandler = this.onSubmenuClick.bind(this) | ||
95 | this.transitionEndHandler = this.onTransitionEnd.bind(this) | ||
96 | } | ||
97 | |||
98 | onSubmenuClick (event: any) { | ||
99 | let target = null | ||
100 | |||
101 | if (event.type === 'tap') { | ||
102 | target = event.target | ||
103 | } else { | ||
104 | target = event.currentTarget || event.target | ||
105 | } | ||
106 | |||
107 | if (target?.classList.contains('vjs-back-button')) { | ||
108 | this.loadMainMenu() | ||
109 | return | ||
110 | } | ||
111 | |||
112 | // To update the sub menu value on click, setTimeout is needed because | ||
113 | // updating the value is not instant | ||
114 | setTimeout(() => this.update(event), 0) | ||
115 | |||
116 | // Seems like videojs adds a vjs-hidden class on the caption menu after a click | ||
117 | // We don't need it | ||
118 | this.subMenu.menu.removeClass('vjs-hidden') | ||
119 | } | ||
120 | |||
121 | /** | ||
122 | * Create the component's DOM element | ||
123 | * | ||
124 | */ | ||
125 | createEl () { | ||
126 | const el = videojs.dom.createEl('li', { | ||
127 | className: 'vjs-menu-item', | ||
128 | tabIndex: -1 | ||
129 | }) | ||
130 | |||
131 | this.settingsSubMenuTitleEl_ = videojs.dom.createEl('div', { | ||
132 | className: 'vjs-settings-sub-menu-title' | ||
133 | }) as HTMLElement | ||
134 | |||
135 | el.appendChild(this.settingsSubMenuTitleEl_) | ||
136 | |||
137 | this.settingsSubMenuValueEl_ = videojs.dom.createEl('div', { | ||
138 | className: 'vjs-settings-sub-menu-value' | ||
139 | }) as HTMLElement | ||
140 | |||
141 | el.appendChild(this.settingsSubMenuValueEl_) | ||
142 | |||
143 | this.settingsSubMenuEl_ = videojs.dom.createEl('div', { | ||
144 | className: 'vjs-settings-sub-menu' | ||
145 | }) as HTMLElement | ||
146 | |||
147 | return el as HTMLLIElement | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Handle click on menu item | ||
152 | * | ||
153 | * @method handleClick | ||
154 | */ | ||
155 | handleClick (event: videojs.EventTarget.Event) { | ||
156 | this.menuToLoad = 'submenu' | ||
157 | // Remove open class to ensure only the open submenu gets this class | ||
158 | videojs.dom.removeClass(this.el(), 'open') | ||
159 | |||
160 | super.handleClick(event); | ||
161 | |||
162 | (this.mainMenu.el() as HTMLElement).style.opacity = '0' | ||
163 | // Whether to add or remove vjs-hidden class on the settingsSubMenuEl element | ||
164 | if (videojs.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) { | ||
165 | videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden') | ||
166 | |||
167 | // animation not played without timeout | ||
168 | setTimeout(() => { | ||
169 | this.settingsSubMenuEl_.style.opacity = '1' | ||
170 | this.settingsSubMenuEl_.style.marginRight = '0px' | ||
171 | }, 0) | ||
172 | |||
173 | this.settingsButton.setDialogSize(this.size) | ||
174 | |||
175 | const firstChild = this.subMenu.menu.children()[0] | ||
176 | if (firstChild) firstChild.focus() | ||
177 | } else { | ||
178 | videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') | ||
179 | } | ||
180 | } | ||
181 | |||
182 | /** | ||
183 | * Create back button | ||
184 | * | ||
185 | * @method createBackButton | ||
186 | */ | ||
187 | createBackButton () { | ||
188 | const button = this.subMenu.menu.addChild('MenuItem', {}, 0) | ||
189 | |||
190 | button.addClass('vjs-back-button'); | ||
191 | (button.el() as HTMLElement).innerHTML = this.player().localize(this.subMenu.controlText()) | ||
192 | } | ||
193 | |||
194 | /** | ||
195 | * Add/remove prefixed event listener for CSS Transition | ||
196 | * | ||
197 | * @method PrefixedEvent | ||
198 | */ | ||
199 | PrefixedEvent (element: any, type: any, callback: any, action = 'addEvent') { | ||
200 | const prefix = [ 'webkit', 'moz', 'MS', 'o', '' ] | ||
201 | |||
202 | for (let p = 0; p < prefix.length; p++) { | ||
203 | if (!prefix[p]) { | ||
204 | type = type.toLowerCase() | ||
205 | } | ||
206 | |||
207 | if (action === 'addEvent') { | ||
208 | element.addEventListener(prefix[p] + type, callback, false) | ||
209 | } else if (action === 'removeEvent') { | ||
210 | element.removeEventListener(prefix[p] + type, callback, false) | ||
211 | } | ||
212 | } | ||
213 | } | ||
214 | |||
215 | onTransitionEnd (event: any) { | ||
216 | if (event.propertyName !== 'margin-right') { | ||
217 | return | ||
218 | } | ||
219 | |||
220 | if (this.menuToLoad === 'mainmenu') { | ||
221 | // hide submenu | ||
222 | videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') | ||
223 | |||
224 | // reset opacity to 0 | ||
225 | this.settingsSubMenuEl_.style.opacity = '0' | ||
226 | } | ||
227 | } | ||
228 | |||
229 | reset () { | ||
230 | videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') | ||
231 | this.settingsSubMenuEl_.style.opacity = '0' | ||
232 | this.setMargin() | ||
233 | } | ||
234 | |||
235 | loadMainMenu () { | ||
236 | const mainMenuEl = this.mainMenu.el() as HTMLElement | ||
237 | this.menuToLoad = 'mainmenu' | ||
238 | this.mainMenu.show() | ||
239 | mainMenuEl.style.opacity = '0' | ||
240 | |||
241 | // back button will always take you to main menu, so set dialog sizes | ||
242 | const mainMenuAny = this.mainMenu as any | ||
243 | this.settingsButton.setDialogSize([ mainMenuAny.width, mainMenuAny.height ]) | ||
244 | |||
245 | // animation not triggered without timeout (some async stuff ?!?) | ||
246 | setTimeout(() => { | ||
247 | // animate margin and opacity before hiding the submenu | ||
248 | // this triggers CSS Transition event | ||
249 | this.setMargin() | ||
250 | mainMenuEl.style.opacity = '1' | ||
251 | |||
252 | const firstChild = this.mainMenu.children()[0] | ||
253 | if (firstChild) firstChild.focus() | ||
254 | }, 0) | ||
255 | } | ||
256 | |||
257 | build () { | ||
258 | this.subMenu.on('labelUpdated', () => { | ||
259 | this.update() | ||
260 | }) | ||
261 | this.subMenu.on('menuChanged', () => { | ||
262 | this.bindClickEvents() | ||
263 | this.setSize() | ||
264 | this.update() | ||
265 | }) | ||
266 | |||
267 | this.settingsSubMenuTitleEl_.innerHTML = this.player().localize(this.subMenu.controlText()) | ||
268 | this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el()) | ||
269 | this.panelChildEl.appendChild(this.settingsSubMenuEl_) | ||
270 | this.update() | ||
271 | |||
272 | this.createBackButton() | ||
273 | this.setSize() | ||
274 | this.bindClickEvents() | ||
275 | |||
276 | // prefixed event listeners for CSS TransitionEnd | ||
277 | this.PrefixedEvent( | ||
278 | this.settingsSubMenuEl_, | ||
279 | 'TransitionEnd', | ||
280 | this.transitionEndHandler, | ||
281 | 'addEvent' | ||
282 | ) | ||
283 | } | ||
284 | |||
285 | update (event?: any) { | ||
286 | let target: HTMLElement = null | ||
287 | const subMenu = this.subMenu.name() | ||
288 | |||
289 | if (event && event.type === 'tap') { | ||
290 | target = event.target | ||
291 | } else if (event) { | ||
292 | target = event.currentTarget | ||
293 | } | ||
294 | |||
295 | // Playback rate menu button doesn't get a vjs-selected class | ||
296 | // or sets options_['selected'] on the selected playback rate. | ||
297 | // Thus we get the submenu value based on the labelEl of playbackRateMenuButton | ||
298 | if (subMenu === 'PlaybackRateMenuButton') { | ||
299 | const html = (this.subMenu as any).labelEl_.innerHTML | ||
300 | |||
301 | setTimeout(() => { | ||
302 | this.settingsSubMenuValueEl_.innerHTML = html | ||
303 | }, 250) | ||
304 | } else { | ||
305 | // Loop trough the submenu items to find the selected child | ||
306 | for (const subMenuItem of this.subMenu.menu.children_) { | ||
307 | if (!(subMenuItem instanceof component)) { | ||
308 | continue | ||
309 | } | ||
310 | |||
311 | if (subMenuItem.hasClass('vjs-selected')) { | ||
312 | const subMenuItemUntyped = subMenuItem as any | ||
313 | |||
314 | // Prefer to use the function | ||
315 | if (typeof subMenuItemUntyped.getLabel === 'function') { | ||
316 | this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.getLabel() | ||
317 | break | ||
318 | } | ||
319 | |||
320 | this.settingsSubMenuValueEl_.innerHTML = this.player().localize(subMenuItemUntyped.options_.label) | ||
321 | } | ||
322 | } | ||
323 | } | ||
324 | |||
325 | if (target && !target.classList.contains('vjs-back-button')) { | ||
326 | this.settingsButton.hideDialog() | ||
327 | } | ||
328 | } | ||
329 | |||
330 | bindClickEvents () { | ||
331 | for (const item of this.subMenu.menu.children()) { | ||
332 | if (!(item instanceof component)) { | ||
333 | continue | ||
334 | } | ||
335 | item.on([ 'tap', 'click' ], this.submenuClickHandler) | ||
336 | } | ||
337 | } | ||
338 | |||
339 | // save size of submenus on first init | ||
340 | // if number of submenu items change dynamically more logic will be needed | ||
341 | setSize () { | ||
342 | this.dialog.removeClass('vjs-hidden') | ||
343 | videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden') | ||
344 | this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_) | ||
345 | this.setMargin() | ||
346 | this.dialog.addClass('vjs-hidden') | ||
347 | videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') | ||
348 | } | ||
349 | |||
350 | setMargin () { | ||
351 | if (!this.size) return | ||
352 | |||
353 | const [ width ] = this.size | ||
354 | |||
355 | this.settingsSubMenuEl_.style.marginRight = `-${width}px` | ||
356 | } | ||
357 | |||
358 | /** | ||
359 | * Hide the sub menu | ||
360 | */ | ||
361 | hideSubMenu () { | ||
362 | // after removing settings item this.el_ === null | ||
363 | if (!this.el()) { | ||
364 | return | ||
365 | } | ||
366 | |||
367 | if (videojs.dom.hasClass(this.el(), 'open')) { | ||
368 | videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') | ||
369 | videojs.dom.removeClass(this.el(), 'open') | ||
370 | } | ||
371 | } | ||
372 | |||
373 | } | ||
374 | |||
375 | (SettingsMenuItem as any).prototype.contentElType = 'button' | ||
376 | videojs.registerComponent('SettingsMenuItem', SettingsMenuItem) | ||
377 | |||
378 | export { SettingsMenuItem } | ||