From 9af2accee68082e4e1160a4e4a7036451262be02 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 14 Mar 2022 10:27:05 +0100 Subject: Reorganize videojs components --- .../player/settings/resolution-menu-button.ts | 86 +++++ .../assets/player/settings/resolution-menu-item.ts | 77 +++++ .../src/assets/player/settings/settings-dialog.ts | 35 ++ .../assets/player/settings/settings-menu-button.ts | 279 +++++++++++++++ .../assets/player/settings/settings-menu-item.ts | 378 +++++++++++++++++++++ .../assets/player/settings/settings-panel-child.ts | 18 + .../src/assets/player/settings/settings-panel.ts | 18 + 7 files changed, 891 insertions(+) create mode 100644 client/src/assets/player/settings/resolution-menu-button.ts create mode 100644 client/src/assets/player/settings/resolution-menu-item.ts create mode 100644 client/src/assets/player/settings/settings-dialog.ts create mode 100644 client/src/assets/player/settings/settings-menu-button.ts create mode 100644 client/src/assets/player/settings/settings-menu-item.ts create mode 100644 client/src/assets/player/settings/settings-panel-child.ts create mode 100644 client/src/assets/player/settings/settings-panel.ts (limited to 'client/src/assets/player/settings') diff --git a/client/src/assets/player/settings/resolution-menu-button.ts b/client/src/assets/player/settings/resolution-menu-button.ts new file mode 100644 index 000000000..8bd5b4f03 --- /dev/null +++ b/client/src/assets/player/settings/resolution-menu-button.ts @@ -0,0 +1,86 @@ +import videojs from 'video.js' +import { ResolutionMenuItem } from './resolution-menu-item' + +const Menu = videojs.getComponent('Menu') +const MenuButton = videojs.getComponent('MenuButton') +class ResolutionMenuButton extends MenuButton { + labelEl_: HTMLElement + + constructor (player: videojs.Player, options?: videojs.MenuButtonOptions) { + super(player, options) + + this.controlText('Quality') + + player.peertubeResolutions().on('resolutionsAdded', () => this.buildQualities()) + + // For parent + player.peertubeResolutions().on('resolutionChanged', () => { + setTimeout(() => this.trigger('labelUpdated')) + }) + } + + createEl () { + const el = super.createEl() + + this.labelEl_ = videojs.dom.createEl('div', { + className: 'vjs-resolution-value' + }) as HTMLElement + + el.appendChild(this.labelEl_) + + return el + } + + updateARIAAttributes () { + this.el().setAttribute('aria-label', 'Quality') + } + + createMenu () { + return new Menu(this.player_) + } + + buildCSSClass () { + return super.buildCSSClass() + ' vjs-resolution-button' + } + + buildWrapperCSSClass () { + return 'vjs-resolution-control ' + super.buildWrapperCSSClass() + } + + private addClickListener (component: any) { + component.on('click', () => { + const children = this.menu.children() + + for (const child of children) { + if (component !== child) { + (child as videojs.MenuItem).selected(false) + } + } + }) + } + + private buildQualities () { + for (const d of this.player().peertubeResolutions().getResolutions()) { + const label = d.label === '0p' + ? this.player().localize('Audio-only') + : d.label + + this.menu.addChild(new ResolutionMenuItem( + this.player_, + { + id: d.id, + label, + selected: d.selected + }) + ) + } + + for (const m of this.menu.children()) { + this.addClickListener(m) + } + + this.trigger('menuChanged') + } +} + +videojs.registerComponent('ResolutionMenuButton', ResolutionMenuButton) diff --git a/client/src/assets/player/settings/resolution-menu-item.ts b/client/src/assets/player/settings/resolution-menu-item.ts new file mode 100644 index 000000000..6047f52f7 --- /dev/null +++ b/client/src/assets/player/settings/resolution-menu-item.ts @@ -0,0 +1,77 @@ +import videojs from 'video.js' + +const MenuItem = videojs.getComponent('MenuItem') + +export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions { + id: number +} + +class ResolutionMenuItem extends MenuItem { + private readonly resolutionId: number + private readonly label: string + + private autoResolutionEnabled: boolean + private autoResolutionChosen: string + + constructor (player: videojs.Player, options?: ResolutionMenuItemOptions) { + options.selectable = true + + super(player, options) + + this.autoResolutionEnabled = true + this.autoResolutionChosen = '' + + this.resolutionId = options.id + this.label = options.label + + player.peertubeResolutions().on('resolutionChanged', () => this.updateSelection()) + + // We only want to disable the "Auto" item + if (this.resolutionId === -1) { + player.peertubeResolutions().on('autoResolutionEnabledChanged', () => this.updateAutoResolution()) + } + } + + handleClick (event: any) { + // Auto button disabled? + if (this.autoResolutionEnabled === false && this.resolutionId === -1) return + + super.handleClick(event) + + this.player().peertubeResolutions().select({ id: this.resolutionId, byEngine: false }) + } + + updateSelection () { + const selectedResolution = this.player().peertubeResolutions().getSelected() + + if (this.resolutionId === -1) { + this.autoResolutionChosen = this.player().peertubeResolutions().getAutoResolutionChosen()?.label + } + + this.selected(this.resolutionId === selectedResolution.id) + } + + updateAutoResolution () { + const enabled = this.player().peertubeResolutions().isAutoResolutionEnabeld() + + // Check if the auto resolution is enabled or not + if (enabled === false) { + this.addClass('disabled') + } else { + this.removeClass('disabled') + } + + this.autoResolutionEnabled = enabled + } + + getLabel () { + if (this.resolutionId === -1) { + return this.label + ' ' + this.autoResolutionChosen + '' + } + + return this.label + } +} +videojs.registerComponent('ResolutionMenuItem', ResolutionMenuItem) + +export { ResolutionMenuItem } diff --git a/client/src/assets/player/settings/settings-dialog.ts b/client/src/assets/player/settings/settings-dialog.ts new file mode 100644 index 000000000..8cd98967f --- /dev/null +++ b/client/src/assets/player/settings/settings-dialog.ts @@ -0,0 +1,35 @@ +import videojs from 'video.js' + +const Component = videojs.getComponent('Component') + +class SettingsDialog extends Component { + constructor (player: videojs.Player) { + super(player) + + this.hide() + } + + /** + * Create the component's DOM element + * + */ + 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 + }) + } +} + +Component.registerComponent('SettingsDialog', SettingsDialog) + +export { SettingsDialog } diff --git a/client/src/assets/player/settings/settings-menu-button.ts b/client/src/assets/player/settings/settings-menu-button.ts new file mode 100644 index 000000000..6de390f4d --- /dev/null +++ b/client/src/assets/player/settings/settings-menu-button.ts @@ -0,0 +1,279 @@ +// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu +import { SettingsMenuItem } from './settings-menu-item' +import { toTitleCase } from '../utils' +import videojs from 'video.js' + +import { SettingsDialog } from './settings-dialog' +import { SettingsPanel } from './settings-panel' +import { SettingsPanelChild } from './settings-panel-child' + +const Button = videojs.getComponent('Button') +const Menu = videojs.getComponent('Menu') +const Component = videojs.getComponent('Component') + +export interface SettingsButtonOptions extends videojs.ComponentOptions { + entries: any[] + setup?: { + maxHeightOffset: number + } +} + +class SettingsButton extends Button { + dialog: SettingsDialog + dialogEl: HTMLElement + menu: videojs.Menu + panel: SettingsPanel + panelChild: SettingsPanelChild + + addSettingsItemHandler: typeof SettingsButton.prototype.onAddSettingsItem + disposeSettingsItemHandler: typeof SettingsButton.prototype.onDisposeSettingsItem + documentClickHandler: typeof SettingsButton.prototype.onDocumentClick + userInactiveHandler: typeof SettingsButton.prototype.onUserInactive + + private settingsButtonOptions: SettingsButtonOptions + + constructor (player: videojs.Player, options?: SettingsButtonOptions) { + super(player, options) + + this.settingsButtonOptions = options + + this.controlText('Settings') + + this.dialog = this.player().addChild('settingsDialog') + this.dialogEl = this.dialog.el() as HTMLElement + 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.documentClickHandler = this.onDocumentClick.bind(this) + this.userInactiveHandler = this.onUserInactive.bind(this) + + this.buildMenu() + this.bindEvents() + + // Prepare the dialog + this.player().one('play', () => this.hideDialog()) + } + + onDocumentClick (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() + } + } + + onDisposeSettingsItem (event: any, name: string) { + if (name === undefined) { + const children = this.menu.children() + + while (children.length > 0) { + children[0].dispose() + this.menu.removeChild(children[0]) + } + + this.addClass('vjs-hidden') + } else { + const item = this.menu.getChild(name) + + if (item) { + item.dispose() + this.menu.removeChild(item) + } + } + + this.hideDialog() + + if (this.settingsButtonOptions.entries.length === 0) { + this.addClass('vjs-hidden') + } + } + + dispose () { + document.removeEventListener('click', this.documentClickHandler) + + if (this.isInIframe()) { + window.removeEventListener('blur', this.documentClickHandler) + } + } + + onAddSettingsItem (event: any, data: any) { + const [ entry, options ] = data + + this.addMenuItem(entry, options) + this.removeClass('vjs-hidden') + } + + onUserInactive () { + if (!this.dialog.hasClass('vjs-hidden')) { + this.hideDialog() + } + } + + bindEvents () { + document.addEventListener('click', this.documentClickHandler) + if (this.isInIframe()) { + window.addEventListener('blur', this.documentClickHandler) + } + + this.player().on('addsettingsitem', this.addSettingsItemHandler) + this.player().on('disposesettingsitem', this.disposeSettingsItemHandler) + this.player().on('userinactive', this.userInactiveHandler) + } + + buildCSSClass () { + return `vjs-icon-settings ${super.buildCSSClass()}` + } + + handleClick () { + if (this.dialog.hasClass('vjs-hidden')) { + this.showDialog() + } else { + this.hideDialog() + } + } + + showDialog () { + this.player().peertube().onMenuOpened(); + + (this.menu.el() as HTMLElement).style.opacity = '1' + + this.dialog.show() + this.el().setAttribute('aria-expanded', 'true') + + this.setDialogSize(this.getComponentSize(this.menu)) + + const firstChild = this.menu.children()[0] + if (firstChild) firstChild.focus() + } + + hideDialog () { + this.player_.peertube().onMenuClosed() + + this.dialog.hide() + this.el().setAttribute('aria-expanded', 'false') + + this.setDialogSize(this.getComponentSize(this.menu)); + (this.menu.el() as HTMLElement).style.opacity = '1' + this.resetChildren() + } + + getComponentSize (element: videojs.Component | HTMLElement) { + let width: number = null + let height: number = null + + // Could be component or just DOM element + if (element instanceof Component) { + const el = element.el() as HTMLElement + + width = el.offsetWidth + height = el.offsetHeight; + + (element as any).width = width; + (element as any).height = height + } else { + width = element.offsetWidth + height = element.offsetHeight + } + + return [ width, height ] + } + + setDialogSize ([ width, height ]: number[]) { + if (typeof height !== 'number') { + return + } + + const offset = this.settingsButtonOptions.setup.maxHeightOffset + const maxHeight = (this.player().el() as HTMLElement).offsetHeight - offset + + const panelEl = this.panel.el() as HTMLElement + + if (height > maxHeight) { + height = maxHeight + width += 17 + panelEl.style.maxHeight = `${height}px` + } else if (panelEl.style.maxHeight !== '') { + panelEl.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') + const entries = this.settingsButtonOptions.entries + + if (entries.length === 0) { + this.addClass('vjs-hidden') + this.panelChild.addChild(this.menu) + return + } + + for (const entry of entries) { + this.addMenuItem(entry, this.settingsButtonOptions) + } + + this.panelChild.addChild(this.menu) + } + + addMenuItem (entry: any, options: any) { + const openSubMenu = function (this: any) { + if (videojs.dom.hasClass(this.el_, 'open')) { + videojs.dom.removeClass(this.el_, 'open') + } else { + videojs.dom.addClass(this.el_, 'open') + } + } + + options.name = toTitleCase(entry) + + const newOptions = Object.assign({}, options, { entry, menuButton: this }) + const settingsMenuItem = new SettingsMenuItem(this.player(), newOptions) + + 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 () { + for (const menuChild of this.menu.children()) { + (menuChild as SettingsMenuItem).reset() + } + } + + /** + * Hide all the sub menus + */ + hideChildren () { + for (const menuChild of this.menu.children()) { + (menuChild as SettingsMenuItem).hideSubMenu() + } + } + + isInIframe () { + return window.self !== window.top + } + +} + +Component.registerComponent('SettingsButton', SettingsButton) + +export { SettingsButton } diff --git a/client/src/assets/player/settings/settings-menu-item.ts b/client/src/assets/player/settings/settings-menu-item.ts new file mode 100644 index 000000000..31d42c456 --- /dev/null +++ b/client/src/assets/player/settings/settings-menu-item.ts @@ -0,0 +1,378 @@ +import videojs from 'video.js' +// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu +import { toTitleCase } from '../utils' +import { SettingsDialog } from './settings-dialog' +import { SettingsButton } from './settings-menu-button' +import { SettingsPanel } from './settings-panel' +import { SettingsPanelChild } from './settings-panel-child' + +const MenuItem = videojs.getComponent('MenuItem') +const component = videojs.getComponent('Component') + +export interface SettingsMenuItemOptions extends videojs.MenuItemOptions { + entry: string + menuButton: SettingsButton +} + +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 = 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() as HTMLElement + + this.size = null + + // keep state of what menu type is loading next + this.menuToLoad = 'mainmenu' + + const subMenuName = toTitleCase(options.entry) + const SubMenuComponent = videojs.getComponent(subMenuName) + + if (!SubMenuComponent) { + throw new Error(`Component ${subMenuName} does not exist`) + } + + 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 + + this.eventHandlers() + + player.ready(() => { + // Voodoo magic for IOS + setTimeout(() => { + // Player was destroyed + if (!this.player_) return + + this.build() + + // Update on rate change + player.on('ratechange', this.submenuClickHandler) + + if (subMenuName === 'CaptionsButton') { + // Hack to regenerate captions on HTTP fallback + player.on('captionsChanged', () => { + setTimeout(() => { + this.settingsSubMenuEl_.innerHTML = '' + this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el()) + this.update() + this.bindClickEvents() + }, 0) + }) + } + + this.reset() + }, 0) + }) + } + + eventHandlers () { + this.submenuClickHandler = this.onSubmenuClick.bind(this) + this.transitionEndHandler = this.onTransitionEnd.bind(this) + } + + onSubmenuClick (event: any) { + let target = null + + if (event.type === 'tap') { + target = event.target + } else { + target = event.currentTarget || event.target + } + + if (target?.classList.contains('vjs-back-button')) { + this.loadMainMenu() + return + } + + // 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 + * + */ + createEl () { + const el = videojs.dom.createEl('li', { + className: 'vjs-menu-item', + tabIndex: -1 + }) + + this.settingsSubMenuTitleEl_ = videojs.dom.createEl('div', { + className: 'vjs-settings-sub-menu-title' + }) as HTMLElement + + el.appendChild(this.settingsSubMenuTitleEl_) + + this.settingsSubMenuValueEl_ = videojs.dom.createEl('div', { + className: 'vjs-settings-sub-menu-value' + }) as HTMLElement + + el.appendChild(this.settingsSubMenuValueEl_) + + this.settingsSubMenuEl_ = videojs.dom.createEl('div', { + className: 'vjs-settings-sub-menu' + }) as HTMLElement + + return el as HTMLLIElement + } + + /** + * Handle click on menu item + * + * @method handleClick + */ + handleClick (event: videojs.EventTarget.Event) { + this.menuToLoad = 'submenu' + // Remove open class to ensure only the open submenu gets this class + videojs.dom.removeClass(this.el(), 'open') + + super.handleClick(event); + + (this.mainMenu.el() as HTMLElement).style.opacity = '0' + // Whether to add or remove vjs-hidden class on the settingsSubMenuEl element + if (videojs.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) { + videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden') + + // animation not played without timeout + setTimeout(() => { + this.settingsSubMenuEl_.style.opacity = '1' + this.settingsSubMenuEl_.style.marginRight = '0px' + }, 0) + + this.settingsButton.setDialogSize(this.size) + + const firstChild = this.subMenu.menu.children()[0] + if (firstChild) firstChild.focus() + } else { + videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') + } + } + + /** + * Create back button + * + * @method createBackButton + */ + createBackButton () { + const button = this.subMenu.menu.addChild('MenuItem', {}, 0) + + button.addClass('vjs-back-button'); + (button.el() as HTMLElement).innerHTML = this.player().localize(this.subMenu.controlText()) + } + + /** + * Add/remove prefixed event listener for CSS Transition + * + * @method PrefixedEvent + */ + PrefixedEvent (element: any, type: any, callback: any, action = 'addEvent') { + const prefix = [ 'webkit', 'moz', 'MS', 'o', '' ] + + for (let p = 0; p < prefix.length; p++) { + if (!prefix[p]) { + type = type.toLowerCase() + } + + if (action === 'addEvent') { + element.addEventListener(prefix[p] + type, callback, false) + } else if (action === 'removeEvent') { + element.removeEventListener(prefix[p] + type, callback, false) + } + } + } + + onTransitionEnd (event: any) { + if (event.propertyName !== 'margin-right') { + return + } + + if (this.menuToLoad === 'mainmenu') { + // hide submenu + videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') + + // reset opacity to 0 + this.settingsSubMenuEl_.style.opacity = '0' + } + } + + reset () { + 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() + mainMenuEl.style.opacity = '0' + + // back button will always take you to main menu, so set dialog sizes + 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() + mainMenuEl.style.opacity = '1' + + const firstChild = this.mainMenu.children()[0] + if (firstChild) firstChild.focus() + }, 0) + } + + build () { + this.subMenu.on('labelUpdated', () => { + this.update() + }) + this.subMenu.on('menuChanged', () => { + this.bindClickEvents() + this.setSize() + this.update() + }) + + this.settingsSubMenuTitleEl_.innerHTML = this.player().localize(this.subMenu.controlText()) + this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el()) + this.panelChildEl.appendChild(this.settingsSubMenuEl_) + this.update() + + this.createBackButton() + this.setSize() + this.bindClickEvents() + + // prefixed event listeners for CSS TransitionEnd + this.PrefixedEvent( + this.settingsSubMenuEl_, + 'TransitionEnd', + this.transitionEndHandler, + 'addEvent' + ) + } + + update (event?: any) { + let target: HTMLElement = null + const subMenu = this.subMenu.name() + + if (event && event.type === 'tap') { + target = event.target + } else if (event) { + target = event.currentTarget + } + + // Playback rate menu button doesn't get a vjs-selected class + // or sets options_['selected'] on the selected playback rate. + // Thus we get the submenu value based on the labelEl of playbackRateMenuButton + if (subMenu === 'PlaybackRateMenuButton') { + 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 (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 subMenuItemUntyped.getLabel === 'function') { + this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.getLabel() + break + } + + this.settingsSubMenuValueEl_.innerHTML = this.player().localize(subMenuItemUntyped.options_.label) + } + } + } + + if (target && !target.classList.contains('vjs-back-button')) { + this.settingsButton.hideDialog() + } + } + + bindClickEvents () { + for (const item of this.subMenu.menu.children()) { + if (!(item instanceof component)) { + continue + } + item.on([ 'tap', 'click' ], this.submenuClickHandler) + } + } + + // save size of submenus on first init + // if number of submenu items change dynamically more logic will be needed + setSize () { + this.dialog.removeClass('vjs-hidden') + videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden') + this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_) + this.setMargin() + this.dialog.addClass('vjs-hidden') + videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') + } + + setMargin () { + if (!this.size) return + + const [ width ] = this.size + + this.settingsSubMenuEl_.style.marginRight = `-${width}px` + } + + /** + * Hide the sub menu + */ + hideSubMenu () { + // after removing settings item this.el_ === null + if (!this.el()) { + return + } + + if (videojs.dom.hasClass(this.el(), 'open')) { + videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') + videojs.dom.removeClass(this.el(), 'open') + } + } + +} + +(SettingsMenuItem as any).prototype.contentElType = 'button' +videojs.registerComponent('SettingsMenuItem', SettingsMenuItem) + +export { SettingsMenuItem } diff --git a/client/src/assets/player/settings/settings-panel-child.ts b/client/src/assets/player/settings/settings-panel-child.ts new file mode 100644 index 000000000..161420c38 --- /dev/null +++ b/client/src/assets/player/settings/settings-panel-child.ts @@ -0,0 +1,18 @@ +import videojs from 'video.js' + +const Component = videojs.getComponent('Component') + +class SettingsPanelChild extends Component { + + createEl () { + return super.createEl('div', { + className: 'vjs-settings-panel-child', + innerHTML: '', + tabIndex: -1 + }) + } +} + +Component.registerComponent('SettingsPanelChild', SettingsPanelChild) + +export { SettingsPanelChild } diff --git a/client/src/assets/player/settings/settings-panel.ts b/client/src/assets/player/settings/settings-panel.ts new file mode 100644 index 000000000..28b579bdd --- /dev/null +++ b/client/src/assets/player/settings/settings-panel.ts @@ -0,0 +1,18 @@ +import videojs from 'video.js' + +const Component = videojs.getComponent('Component') + +class SettingsPanel extends Component { + + createEl () { + return super.createEl('div', { + className: 'vjs-settings-panel', + innerHTML: '', + tabIndex: -1 + }) + } +} + +Component.registerComponent('SettingsPanel', SettingsPanel) + +export { SettingsPanel } -- cgit v1.2.3