2018-03-30 17:40:00 +02:00
|
|
|
// Author: Yanko Shterev
|
|
|
|
// Thanks https://github.com/yshterev/videojs-settings-menu
|
|
|
|
|
2018-10-18 14:35:31 +02:00
|
|
|
// FIXME: something weird with our path definition in tsconfig and typings
|
|
|
|
// @ts-ignore
|
|
|
|
import * as videojs from 'video.js'
|
|
|
|
|
2018-03-30 17:40:00 +02:00
|
|
|
import { SettingsMenuItem } from './settings-menu-item'
|
2019-01-23 15:36:45 +01:00
|
|
|
import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
|
|
|
import { toTitleCase } from '../utils'
|
2018-03-30 17:40:00 +02:00
|
|
|
|
|
|
|
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
|
|
|
|
const Menu: VideoJSComponentInterface = videojsUntyped.getComponent('Menu')
|
|
|
|
const Component: VideoJSComponentInterface = videojsUntyped.getComponent('Component')
|
|
|
|
|
|
|
|
class SettingsButton extends Button {
|
2019-08-01 11:38:26 +02:00
|
|
|
playerComponent = videojs.Player
|
|
|
|
dialog: any
|
|
|
|
dialogEl: any
|
|
|
|
menu: any
|
|
|
|
panel: any
|
|
|
|
panelChild: any
|
|
|
|
|
|
|
|
addSettingsItemHandler: Function
|
|
|
|
disposeSettingsItemHandler: Function
|
|
|
|
playerClickHandler: Function
|
|
|
|
userInactiveHandler: Function
|
|
|
|
|
2018-10-18 14:35:31 +02:00
|
|
|
constructor (player: videojs.Player, options: any) {
|
2018-03-30 17:40:00 +02:00
|
|
|
super(player, options)
|
|
|
|
|
|
|
|
this.playerComponent = player
|
|
|
|
this.dialog = this.playerComponent.addChild('settingsDialog')
|
|
|
|
this.dialogEl = this.dialog.el_
|
|
|
|
this.menu = null
|
|
|
|
this.panel = this.dialog.addChild('settingsPanel')
|
|
|
|
this.panelChild = this.panel.addChild('settingsPanelChild')
|
|
|
|
|
|
|
|
this.addClass('vjs-settings')
|
|
|
|
this.el_.setAttribute('aria-label', 'Settings Button')
|
|
|
|
|
|
|
|
// Event handlers
|
|
|
|
this.addSettingsItemHandler = this.onAddSettingsItem.bind(this)
|
|
|
|
this.disposeSettingsItemHandler = this.onDisposeSettingsItem.bind(this)
|
|
|
|
this.playerClickHandler = this.onPlayerClick.bind(this)
|
|
|
|
this.userInactiveHandler = this.onUserInactive.bind(this)
|
|
|
|
|
|
|
|
this.buildMenu()
|
|
|
|
this.bindEvents()
|
|
|
|
|
2018-04-03 17:33:39 +02:00
|
|
|
// Prepare the dialog
|
2018-03-30 17:40:00 +02:00
|
|
|
this.player().one('play', () => this.hideDialog())
|
|
|
|
}
|
|
|
|
|
|
|
|
onPlayerClick (event: MouseEvent) {
|
|
|
|
const element = event.target as HTMLElement
|
|
|
|
if (element.classList.contains('vjs-settings') || element.parentElement.classList.contains('vjs-settings')) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.dialog.hasClass('vjs-hidden')) {
|
|
|
|
this.hideDialog()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-18 14:35:31 +02:00
|
|
|
onDisposeSettingsItem (event: any, name: string) {
|
2018-03-30 17:40:00 +02:00
|
|
|
if (name === undefined) {
|
2019-04-02 18:30:26 +02:00
|
|
|
const children = this.menu.children()
|
2018-03-30 17:40:00 +02:00
|
|
|
|
|
|
|
while (children.length > 0) {
|
|
|
|
children[0].dispose()
|
|
|
|
this.menu.removeChild(children[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
this.addClass('vjs-hidden')
|
|
|
|
} else {
|
2019-04-02 18:30:26 +02:00
|
|
|
const item = this.menu.getChild(name)
|
2018-03-30 17:40:00 +02:00
|
|
|
|
|
|
|
if (item) {
|
|
|
|
item.dispose()
|
|
|
|
this.menu.removeChild(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.hideDialog()
|
|
|
|
|
|
|
|
if (this.options_.entries.length === 0) {
|
|
|
|
this.addClass('vjs-hidden')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-18 14:35:31 +02:00
|
|
|
onAddSettingsItem (event: any, data: any) {
|
2018-03-30 17:40:00 +02:00
|
|
|
const [ entry, options ] = data
|
|
|
|
|
|
|
|
this.addMenuItem(entry, options)
|
|
|
|
this.removeClass('vjs-hidden')
|
|
|
|
}
|
|
|
|
|
|
|
|
onUserInactive () {
|
|
|
|
if (!this.dialog.hasClass('vjs-hidden')) {
|
|
|
|
this.hideDialog()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bindEvents () {
|
|
|
|
this.playerComponent.on('click', this.playerClickHandler)
|
|
|
|
this.playerComponent.on('addsettingsitem', this.addSettingsItemHandler)
|
|
|
|
this.playerComponent.on('disposesettingsitem', this.disposeSettingsItemHandler)
|
|
|
|
this.playerComponent.on('userinactive', this.userInactiveHandler)
|
|
|
|
}
|
|
|
|
|
|
|
|
buildCSSClass () {
|
|
|
|
return `vjs-icon-settings ${super.buildCSSClass()}`
|
|
|
|
}
|
|
|
|
|
|
|
|
handleClick () {
|
|
|
|
if (this.dialog.hasClass('vjs-hidden')) {
|
|
|
|
this.showDialog()
|
|
|
|
} else {
|
|
|
|
this.hideDialog()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
showDialog () {
|
2019-08-07 10:17:19 +02:00
|
|
|
this.player_.peertube().onMenuOpen()
|
|
|
|
|
2018-03-30 17:40:00 +02:00
|
|
|
this.menu.el_.style.opacity = '1'
|
|
|
|
this.dialog.show()
|
|
|
|
|
|
|
|
this.setDialogSize(this.getComponentSize(this.menu))
|
|
|
|
}
|
|
|
|
|
|
|
|
hideDialog () {
|
2019-08-07 10:17:19 +02:00
|
|
|
this.player_.peertube().onMenuClosed()
|
|
|
|
|
2018-03-30 17:40:00 +02:00
|
|
|
this.dialog.hide()
|
|
|
|
this.setDialogSize(this.getComponentSize(this.menu))
|
|
|
|
this.menu.el_.style.opacity = '1'
|
|
|
|
this.resetChildren()
|
|
|
|
}
|
|
|
|
|
2018-10-18 09:08:59 +02:00
|
|
|
getComponentSize (element: any) {
|
2018-03-30 17:40:00 +02:00
|
|
|
let width: number = null
|
|
|
|
let height: number = null
|
|
|
|
|
|
|
|
// Could be component or just DOM element
|
|
|
|
if (element instanceof Component) {
|
|
|
|
width = element.el_.offsetWidth
|
|
|
|
height = element.el_.offsetHeight
|
|
|
|
|
|
|
|
// keep width/height as properties for direct use
|
|
|
|
element.width = width
|
|
|
|
element.height = height
|
|
|
|
} else {
|
|
|
|
width = element.offsetWidth
|
|
|
|
height = element.offsetHeight
|
|
|
|
}
|
|
|
|
|
|
|
|
return [ width, height ]
|
|
|
|
}
|
|
|
|
|
|
|
|
setDialogSize ([ width, height ]: number[]) {
|
|
|
|
if (typeof height !== 'number') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-02 18:30:26 +02:00
|
|
|
const offset = this.options_.setup.maxHeightOffset
|
|
|
|
const maxHeight = this.playerComponent.el_.offsetHeight - offset
|
2018-03-30 17:40:00 +02:00
|
|
|
|
|
|
|
if (height > maxHeight) {
|
|
|
|
height = maxHeight
|
|
|
|
width += 17
|
|
|
|
this.panel.el_.style.maxHeight = `${height}px`
|
|
|
|
} else if (this.panel.el_.style.maxHeight !== '') {
|
|
|
|
this.panel.el_.style.maxHeight = ''
|
|
|
|
}
|
|
|
|
|
|
|
|
this.dialogEl.style.width = `${width}px`
|
|
|
|
this.dialogEl.style.height = `${height}px`
|
|
|
|
}
|
|
|
|
|
|
|
|
buildMenu () {
|
|
|
|
this.menu = new Menu(this.player())
|
|
|
|
this.menu.addClass('vjs-main-menu')
|
2019-04-02 18:30:26 +02:00
|
|
|
const entries = this.options_.entries
|
2018-03-30 17:40:00 +02:00
|
|
|
|
|
|
|
if (entries.length === 0) {
|
|
|
|
this.addClass('vjs-hidden')
|
|
|
|
this.panelChild.addChild(this.menu)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-02 18:30:26 +02:00
|
|
|
for (const entry of entries) {
|
2018-03-30 17:40:00 +02:00
|
|
|
this.addMenuItem(entry, this.options_)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.panelChild.addChild(this.menu)
|
|
|
|
}
|
|
|
|
|
2018-10-18 09:08:59 +02:00
|
|
|
addMenuItem (entry: any, options: any) {
|
2018-10-24 21:50:18 +02:00
|
|
|
const openSubMenu = function (this: any) {
|
2018-03-30 17:40:00 +02:00
|
|
|
if (videojsUntyped.dom.hasClass(this.el_, 'open')) {
|
|
|
|
videojsUntyped.dom.removeClass(this.el_, 'open')
|
|
|
|
} else {
|
|
|
|
videojsUntyped.dom.addClass(this.el_, 'open')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
options.name = toTitleCase(entry)
|
2019-04-02 18:30:26 +02:00
|
|
|
const settingsMenuItem = new SettingsMenuItem(this.player(), options, entry, this as any)
|
2018-03-30 17:40:00 +02:00
|
|
|
|
|
|
|
this.menu.addChild(settingsMenuItem)
|
|
|
|
|
|
|
|
// Hide children to avoid sub menus stacking on top of each other
|
|
|
|
// or having multiple menus open
|
|
|
|
settingsMenuItem.on('click', videojs.bind(this, this.hideChildren))
|
|
|
|
|
|
|
|
// Whether to add or remove selected class on the settings sub menu element
|
|
|
|
settingsMenuItem.on('click', openSubMenu)
|
|
|
|
}
|
|
|
|
|
|
|
|
resetChildren () {
|
2019-04-02 18:30:26 +02:00
|
|
|
for (const menuChild of this.menu.children()) {
|
2018-03-30 17:40:00 +02:00
|
|
|
menuChild.reset()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hide all the sub menus
|
|
|
|
*/
|
|
|
|
hideChildren () {
|
2019-04-02 18:30:26 +02:00
|
|
|
for (const menuChild of this.menu.children()) {
|
2018-03-30 17:40:00 +02:00
|
|
|
menuChild.hideSubMenu()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class SettingsPanel extends Component {
|
2018-10-18 14:35:31 +02:00
|
|
|
constructor (player: videojs.Player, options: any) {
|
2018-03-30 17:40:00 +02:00
|
|
|
super(player, options)
|
|
|
|
}
|
|
|
|
|
|
|
|
createEl () {
|
|
|
|
return super.createEl('div', {
|
|
|
|
className: 'vjs-settings-panel',
|
|
|
|
innerHTML: '',
|
|
|
|
tabIndex: -1
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SettingsPanelChild extends Component {
|
2018-10-18 14:35:31 +02:00
|
|
|
constructor (player: videojs.Player, options: any) {
|
2018-03-30 17:40:00 +02:00
|
|
|
super(player, options)
|
|
|
|
}
|
|
|
|
|
|
|
|
createEl () {
|
|
|
|
return super.createEl('div', {
|
|
|
|
className: 'vjs-settings-panel-child',
|
|
|
|
innerHTML: '',
|
|
|
|
tabIndex: -1
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SettingsDialog extends Component {
|
2018-10-18 14:35:31 +02:00
|
|
|
constructor (player: videojs.Player, options: any) {
|
2018-03-30 17:40:00 +02:00
|
|
|
super(player, options)
|
|
|
|
this.hide()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create the component's DOM element
|
|
|
|
*
|
|
|
|
* @return {Element}
|
|
|
|
* @method createEl
|
|
|
|
*/
|
|
|
|
createEl () {
|
|
|
|
const uniqueId = this.id_
|
|
|
|
const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId
|
|
|
|
const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId
|
|
|
|
|
|
|
|
return super.createEl('div', {
|
|
|
|
className: 'vjs-settings-dialog vjs-modal-overlay',
|
|
|
|
innerHTML: '',
|
|
|
|
tabIndex: -1
|
|
|
|
}, {
|
|
|
|
'role': 'dialog',
|
|
|
|
'aria-labelledby': dialogLabelId,
|
|
|
|
'aria-describedby': dialogDescriptionId
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-06-06 14:23:40 +02:00
|
|
|
SettingsButton.prototype.controlText_ = 'Settings'
|
2018-03-30 17:40:00 +02:00
|
|
|
|
|
|
|
Component.registerComponent('SettingsButton', SettingsButton)
|
|
|
|
Component.registerComponent('SettingsDialog', SettingsDialog)
|
|
|
|
Component.registerComponent('SettingsPanel', SettingsPanel)
|
|
|
|
Component.registerComponent('SettingsPanelChild', SettingsPanelChild)
|
|
|
|
|
|
|
|
export { SettingsButton, SettingsDialog, SettingsPanel, SettingsPanelChild }
|