]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - client/src/assets/player/videojs-components/settings-menu-item.ts
Fix live duration in player
[github/Chocobozzz/PeerTube.git] / client / src / assets / player / videojs-components / settings-menu-item.ts
index f14959f9cf4e0b958933cd0ff9e7fd3d63364796..31d42c456dabd73236e8b68fbb0b6189afff90f6 100644 (file)
@@ -1,40 +1,62 @@
-// Author: Yanko Shterev
-// Thanks https://github.com/yshterev/videojs-settings-menu
-
-// FIXME: something weird with our path definition in tsconfig and typings
-// @ts-ignore
-import * as videojs from 'video.js'
-
+import videojs from 'video.js'
+// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu
 import { toTitleCase } from '../utils'
-import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
+import { SettingsDialog } from './settings-dialog'
+import { SettingsButton } from './settings-menu-button'
+import { SettingsPanel } from './settings-panel'
+import { SettingsPanelChild } from './settings-panel-child'
 
-const MenuItem: VideoJSComponentInterface = videojsUntyped.getComponent('MenuItem')
-const component: VideoJSComponentInterface = videojsUntyped.getComponent('Component')
+const MenuItem = videojs.getComponent('MenuItem')
+const component = videojs.getComponent('Component')
 
-class SettingsMenuItem extends MenuItem {
+export interface SettingsMenuItemOptions extends videojs.MenuItemOptions {
+  entry: string
+  menuButton: SettingsButton
+}
 
-  constructor (player: videojs.Player, options: any, entry: string, menuButton: VideoJSComponentInterface) {
+class SettingsMenuItem extends MenuItem {
+  settingsButton: SettingsButton
+  dialog: SettingsDialog
+  mainMenu: videojs.Menu
+  panel: SettingsPanel
+  panelChild: SettingsPanelChild
+  panelChildEl: HTMLElement
+  size: number[]
+  menuToLoad: string
+  subMenu: SettingsButton
+
+  submenuClickHandler: typeof SettingsMenuItem.prototype.onSubmenuClick
+  transitionEndHandler: typeof SettingsMenuItem.prototype.onTransitionEnd
+
+  settingsSubMenuTitleEl_: HTMLElement
+  settingsSubMenuValueEl_: HTMLElement
+  settingsSubMenuEl_: HTMLElement
+
+  constructor (player: videojs.Player, options?: SettingsMenuItemOptions) {
     super(player, options)
 
-    this.settingsButton = menuButton
+    this.settingsButton = options.menuButton
     this.dialog = this.settingsButton.dialog
     this.mainMenu = this.settingsButton.menu
     this.panel = this.dialog.getChild('settingsPanel')
     this.panelChild = this.panel.getChild('settingsPanelChild')
-    this.panelChildEl = this.panelChild.el_
+    this.panelChildEl = this.panelChild.el() as HTMLElement
 
     this.size = null
 
     // keep state of what menu type is loading next
     this.menuToLoad = 'mainmenu'
 
-    const subMenuName = toTitleCase(entry)
-    const SubMenuComponent = videojsUntyped.getComponent(subMenuName)
+    const subMenuName = toTitleCase(options.entry)
+    const SubMenuComponent = videojs.getComponent(subMenuName)
 
     if (!SubMenuComponent) {
       throw new Error(`Component ${subMenuName} does not exist`)
     }
-    this.subMenu = new SubMenuComponent(this.player(), options, menuButton, this)
+
+    const newOptions = Object.assign({}, options, { entry: options.menuButton, menuButton: this })
+
+    this.subMenu = new SubMenuComponent(this.player(), newOptions) as SettingsButton
     const subMenuClass = this.subMenu.buildCSSClass().split(' ')[0]
     this.settingsSubMenuEl_.className += ' ' + subMenuClass
 
@@ -43,6 +65,9 @@ class SettingsMenuItem extends MenuItem {
     player.ready(() => {
       // Voodoo magic for IOS
       setTimeout(() => {
+        // Player was destroyed
+        if (!this.player_) return
+
         this.build()
 
         // Update on rate change
@@ -53,10 +78,9 @@ class SettingsMenuItem extends MenuItem {
           player.on('captionsChanged', () => {
             setTimeout(() => {
               this.settingsSubMenuEl_.innerHTML = ''
-              this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
+              this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
               this.update()
               this.bindClickEvents()
-
             }, 0)
           })
         }
@@ -77,10 +101,10 @@ class SettingsMenuItem extends MenuItem {
     if (event.type === 'tap') {
       target = event.target
     } else {
-      target = event.currentTarget
+      target = event.currentTarget || event.target
     }
 
-    if (target && target.classList.contains('vjs-back-button')) {
+    if (target?.classList.contains('vjs-back-button')) {
       this.loadMainMenu()
       return
     }
@@ -88,36 +112,39 @@ class SettingsMenuItem extends MenuItem {
     // To update the sub menu value on click, setTimeout is needed because
     // updating the value is not instant
     setTimeout(() => this.update(event), 0)
+
+    // Seems like videojs adds a vjs-hidden class on the caption menu after a click
+    // We don't need it
+    this.subMenu.menu.removeClass('vjs-hidden')
   }
 
   /**
    * Create the component's DOM element
    *
-   * @return {Element}
-   * @method createEl
    */
   createEl () {
-    const el = videojsUntyped.dom.createEl('li', {
-      className: 'vjs-menu-item'
+    const el = videojs.dom.createEl('li', {
+      className: 'vjs-menu-item',
+      tabIndex: -1
     })
 
-    this.settingsSubMenuTitleEl_ = videojsUntyped.dom.createEl('div', {
+    this.settingsSubMenuTitleEl_ = videojs.dom.createEl('div', {
       className: 'vjs-settings-sub-menu-title'
-    })
+    }) as HTMLElement
 
     el.appendChild(this.settingsSubMenuTitleEl_)
 
-    this.settingsSubMenuValueEl_ = videojsUntyped.dom.createEl('div', {
+    this.settingsSubMenuValueEl_ = videojs.dom.createEl('div', {
       className: 'vjs-settings-sub-menu-value'
-    })
+    }) as HTMLElement
 
     el.appendChild(this.settingsSubMenuValueEl_)
 
-    this.settingsSubMenuEl_ = videojsUntyped.dom.createEl('div', {
+    this.settingsSubMenuEl_ = videojs.dom.createEl('div', {
       className: 'vjs-settings-sub-menu'
-    })
+    }) as HTMLElement
 
-    return el
+    return el as HTMLLIElement
   }
 
   /**
@@ -125,17 +152,17 @@ class SettingsMenuItem extends MenuItem {
    *
    * @method handleClick
    */
-  handleClick () {
+  handleClick (event: videojs.EventTarget.Event) {
     this.menuToLoad = 'submenu'
     // Remove open class to ensure only the open submenu gets this class
-    videojsUntyped.dom.removeClass(this.el_, 'open')
+    videojs.dom.removeClass(this.el(), 'open')
 
-    super.handleClick()
+    super.handleClick(event);
 
-    this.mainMenu.el_.style.opacity = '0'
+    (this.mainMenu.el() as HTMLElement).style.opacity = '0'
     // Whether to add or remove vjs-hidden class on the settingsSubMenuEl element
-    if (videojsUntyped.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
-      videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
+    if (videojs.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
+      videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
 
       // animation not played without timeout
       setTimeout(() => {
@@ -144,8 +171,11 @@ class SettingsMenuItem extends MenuItem {
       }, 0)
 
       this.settingsButton.setDialogSize(this.size)
+
+      const firstChild = this.subMenu.menu.children()[0]
+      if (firstChild) firstChild.focus()
     } else {
-      videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
+      videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
     }
   }
 
@@ -156,9 +186,9 @@ class SettingsMenuItem extends MenuItem {
    */
   createBackButton () {
     const button = this.subMenu.menu.addChild('MenuItem', {}, 0)
-    button.name_ = 'BackButton'
-    button.addClass('vjs-back-button')
-    button.el_.innerHTML = this.player_.localize(this.subMenu.controlText_)
+
+    button.addClass('vjs-back-button');
+    (button.el() as HTMLElement).innerHTML = this.player().localize(this.subMenu.controlText())
   }
 
   /**
@@ -167,7 +197,7 @@ class SettingsMenuItem extends MenuItem {
    * @method PrefixedEvent
    */
   PrefixedEvent (element: any, type: any, callback: any, action = 'addEvent') {
-    let prefix = ['webkit', 'moz', 'MS', 'o', '']
+    const prefix = [ 'webkit', 'moz', 'MS', 'o', '' ]
 
     for (let p = 0; p < prefix.length; p++) {
       if (!prefix[p]) {
@@ -189,7 +219,7 @@ class SettingsMenuItem extends MenuItem {
 
     if (this.menuToLoad === 'mainmenu') {
       // hide submenu
-      videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
+      videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
 
       // reset opacity to 0
       this.settingsSubMenuEl_.style.opacity = '0'
@@ -197,30 +227,35 @@ class SettingsMenuItem extends MenuItem {
   }
 
   reset () {
-    videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
+    videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
     this.settingsSubMenuEl_.style.opacity = '0'
     this.setMargin()
   }
 
   loadMainMenu () {
+    const mainMenuEl = this.mainMenu.el() as HTMLElement
     this.menuToLoad = 'mainmenu'
     this.mainMenu.show()
-    this.mainMenu.el_.style.opacity = '0'
+    mainMenuEl.style.opacity = '0'
 
     // back button will always take you to main menu, so set dialog sizes
-    this.settingsButton.setDialogSize([this.mainMenu.width, this.mainMenu.height])
+    const mainMenuAny = this.mainMenu as any
+    this.settingsButton.setDialogSize([ mainMenuAny.width, mainMenuAny.height ])
 
     // animation not triggered without timeout (some async stuff ?!?)
     setTimeout(() => {
       // animate margin and opacity before hiding the submenu
       // this triggers CSS Transition event
       this.setMargin()
-      this.mainMenu.el_.style.opacity = '1'
+      mainMenuEl.style.opacity = '1'
+
+      const firstChild = this.mainMenu.children()[0]
+      if (firstChild) firstChild.focus()
     }, 0)
   }
 
   build () {
-    this.subMenu.on('updateLabel', () => {
+    this.subMenu.on('labelUpdated', () => {
       this.update()
     })
     this.subMenu.on('menuChanged', () => {
@@ -229,8 +264,8 @@ class SettingsMenuItem extends MenuItem {
       this.update()
     })
 
-    this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_)
-    this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
+    this.settingsSubMenuTitleEl_.innerHTML = this.player().localize(this.subMenu.controlText())
+    this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
     this.panelChildEl.appendChild(this.settingsSubMenuEl_)
     this.update()
 
@@ -249,7 +284,7 @@ class SettingsMenuItem extends MenuItem {
 
   update (event?: any) {
     let target: HTMLElement = null
-    let subMenu = this.subMenu.name()
+    const subMenu = this.subMenu.name()
 
     if (event && event.type === 'tap') {
       target = event.target
@@ -261,22 +296,28 @@ class SettingsMenuItem extends MenuItem {
     // or sets options_['selected'] on the selected playback rate.
     // Thus we get the submenu value based on the labelEl of playbackRateMenuButton
     if (subMenu === 'PlaybackRateMenuButton') {
-      setTimeout(() => this.settingsSubMenuValueEl_.innerHTML = this.subMenu.labelEl_.innerHTML, 250)
+      const html = (this.subMenu as any).labelEl_.innerHTML
+
+      setTimeout(() => {
+        this.settingsSubMenuValueEl_.innerHTML = html
+      }, 250)
     } else {
       // Loop trough the submenu items to find the selected child
-      for (let subMenuItem of this.subMenu.menu.children_) {
+      for (const subMenuItem of this.subMenu.menu.children_) {
         if (!(subMenuItem instanceof component)) {
           continue
         }
 
         if (subMenuItem.hasClass('vjs-selected')) {
+          const subMenuItemUntyped = subMenuItem as any
+
           // Prefer to use the function
-          if (typeof subMenuItem.getLabel === 'function') {
-            this.settingsSubMenuValueEl_.innerHTML = subMenuItem.getLabel()
+          if (typeof subMenuItemUntyped.getLabel === 'function') {
+            this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.getLabel()
             break
           }
 
-          this.settingsSubMenuValueEl_.innerHTML = subMenuItem.options_.label
+          this.settingsSubMenuValueEl_.innerHTML = this.player().localize(subMenuItemUntyped.options_.label)
         }
       }
     }
@@ -287,11 +328,11 @@ class SettingsMenuItem extends MenuItem {
   }
 
   bindClickEvents () {
-    for (let item of this.subMenu.menu.children()) {
+    for (const item of this.subMenu.menu.children()) {
       if (!(item instanceof component)) {
         continue
       }
-      item.on(['tap', 'click'], this.submenuClickHandler)
+      item.on([ 'tap', 'click' ], this.submenuClickHandler)
     }
   }
 
@@ -299,15 +340,17 @@ class SettingsMenuItem extends MenuItem {
   // if number of submenu items change dynamically more logic will be needed
   setSize () {
     this.dialog.removeClass('vjs-hidden')
-    videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
+    videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
     this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_)
     this.setMargin()
     this.dialog.addClass('vjs-hidden')
-    videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
+    videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
   }
 
   setMargin () {
-    let [width] = this.size
+    if (!this.size) return
+
+    const [ width ] = this.size
 
     this.settingsSubMenuEl_.style.marginRight = `-${width}px`
   }
@@ -317,19 +360,19 @@ class SettingsMenuItem extends MenuItem {
    */
   hideSubMenu () {
     // after removing settings item this.el_ === null
-    if (!this.el_) {
+    if (!this.el()) {
       return
     }
 
-    if (videojsUntyped.dom.hasClass(this.el_, 'open')) {
-      videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
-      videojsUntyped.dom.removeClass(this.el_, 'open')
+    if (videojs.dom.hasClass(this.el(), 'open')) {
+      videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
+      videojs.dom.removeClass(this.el(), 'open')
     }
   }
 
 }
 
-SettingsMenuItem.prototype.contentElType = 'button'
-videojsUntyped.registerComponent('SettingsMenuItem', SettingsMenuItem)
+(SettingsMenuItem as any).prototype.contentElType = 'button'
+videojs.registerComponent('SettingsMenuItem', SettingsMenuItem)
 
 export { SettingsMenuItem }