-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
22ef920
commit e9484ee
Showing
4 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { install } from "../../shared/install" | ||
import { type Player } from "@flowplayer/player" | ||
import { type FlowplayerMenu, MenuDialog } from "./menuDialog" | ||
|
||
export default class CombinedMenuControl extends HTMLElement{ | ||
dialog: MenuDialog | ||
|
||
constructor(player: Player) { | ||
super() | ||
this.classList.add("fp-controls", "fp-togglable") | ||
|
||
// default components | ||
this.append(...player.createComponents( | ||
flowplayer.defaultElements.CONTROL_BUTTONS | ||
, flowplayer.defaultElements.LIVE_STATUS | ||
, flowplayer.defaultElements.ELAPSED | ||
, flowplayer.defaultElements.TIMELINE | ||
, flowplayer.defaultElements.CONTROL_DURATION | ||
, flowplayer.defaultElements.VOLUME_CONTROL | ||
)) | ||
|
||
// menu dialog | ||
window.customElements.define("flowplayer-menu-dialog", MenuDialog) | ||
this.dialog = new (window.customElements.get("flowplayer-menu-dialog") as CustomElementConstructor)(player) as MenuDialog | ||
this.append(this.dialog) | ||
} | ||
|
||
append(...nodes: (Node | string)[]) { | ||
// append menus to the menu dialog. | ||
nodes.forEach((node) => { | ||
(node !== this.dialog && (node as Element)?.querySelector(".fp-menu")) ? this.dialog?.onSubMenuCreated(node as FlowplayerMenu) : super.append(node) | ||
}) | ||
} | ||
} | ||
|
||
install("flowplayer-control", "combined-menu-controls", CombinedMenuControl) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
.fp-menu-dialog summary { | ||
list-style: none; | ||
width: 1.2em; | ||
height:1.2em; | ||
filter: drop-shadow(0 0 2px rgba(0,0,0,0.4)); | ||
background-repeat: no-repeat; | ||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='white' viewBox='0 0 512 512'%3E%3Cpath d='M256 0c17 0 33.6 1.7 49.8 4.8c7.9 1.5 21.8 6.1 29.4 20.1c2 3.7 3.6 7.6 4.6 11.8l9.3 38.5C350.5 81 360.3 86.7 366 85l38-11.2c4-1.2 8.1-1.8 12.2-1.9c16.1-.5 27 9.4 32.3 15.4c22.1 25.1 39.1 54.6 49.9 86.3c2.6 7.6 5.6 21.8-2.7 35.4c-2.2 3.6-4.9 7-8 10L459 246.3c-4.2 4-4.2 15.5 0 19.5l28.7 27.3c3.1 3 5.8 6.4 8 10c8.2 13.6 5.2 27.8 2.7 35.4c-10.8 31.7-27.8 61.1-49.9 86.3c-5.3 6-16.3 15.9-32.3 15.4c-4.1-.1-8.2-.8-12.2-1.9L366 427c-5.7-1.7-15.5 4-16.9 9.8l-9.3 38.5c-1 4.2-2.6 8.2-4.6 11.8c-7.7 14-21.6 18.5-29.4 20.1C289.6 510.3 273 512 256 512s-33.6-1.7-49.8-4.8c-7.9-1.5-21.8-6.1-29.4-20.1c-2-3.7-3.6-7.6-4.6-11.8l-9.3-38.5c-1.4-5.8-11.2-11.5-16.9-9.8l-38 11.2c-4 1.2-8.1 1.8-12.2 1.9c-16.1 .5-27-9.4-32.3-15.4c-22-25.1-39.1-54.6-49.9-86.3c-2.6-7.6-5.6-21.8 2.7-35.4c2.2-3.6 4.9-7 8-10L53 265.7c4.2-4 4.2-15.5 0-19.5L24.2 218.9c-3.1-3-5.8-6.4-8-10C8 195.3 11 181.1 13.6 173.6c10.8-31.7 27.8-61.1 49.9-86.3c5.3-6 16.3-15.9 32.3-15.4c4.1 .1 8.2 .8 12.2 1.9L146 85c5.7 1.7 15.5-4 16.9-9.8l9.3-38.5c1-4.2 2.6-8.2 4.6-11.8c7.7-14 21.6-18.5 29.4-20.1C222.4 1.7 239 0 256 0zM218.1 51.4l-8.5 35.1c-7.8 32.3-45.3 53.9-77.2 44.6L97.9 120.9c-16.5 19.3-29.5 41.7-38 65.7l26.2 24.9c24 22.8 24 66.2 0 89L59.9 325.4c8.5 24 21.5 46.4 38 65.7l34.6-10.2c31.8-9.4 69.4 12.3 77.2 44.6l8.5 35.1c24.6 4.5 51.3 4.5 75.9 0l8.5-35.1c7.8-32.3 45.3-53.9 77.2-44.6l34.6 10.2c16.5-19.3 29.5-41.7 38-65.7l-26.2-24.9c-24-22.8-24-66.2 0-89l26.2-24.9c-8.5-24-21.5-46.4-38-65.7l-34.6 10.2c-31.8 9.4-69.4-12.3-77.2-44.6l-8.5-35.1c-24.6-4.5-51.3-4.5-75.9 0zM208 256a48 48 0 1 0 96 0 48 48 0 1 0 -96 0zm48 96a96 96 0 1 1 0-192 96 96 0 1 1 0 192z'/%3E%3C/svg%3E"); | ||
} | ||
|
||
.fp-back-button { | ||
display: block !important; | ||
} | ||
|
||
.flowplayer .fp-menu-dialog .fp-menu.fp-submenu .fp-back-button { | ||
display: none !important; | ||
} | ||
|
||
.fp-menu-dialog .fp-menu-header { | ||
padding: .9em 5.7em; | ||
} | ||
|
||
.fp-menu-dialog .fp-subtitles-menu { | ||
left: 1.5em; | ||
} | ||
|
||
.fp-menu.is-close { | ||
display: none !important; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
import {type Player} from "@flowplayer/player" | ||
|
||
export type FlowplayerMenu = HTMLElement & { | ||
menu: HTMLDivElement | ||
menuHeader: HTMLElement | ||
} | ||
|
||
enum FlowplayerSubtitlesMenuState { | ||
main = 0 | ||
, tracks = 1 | ||
, style = 2 | ||
, styleOpt = 3 | ||
} | ||
|
||
enum MenuType { | ||
asel = 0 | ||
, subtitles = 1 | ||
, qsel = 2 | ||
, vtsel = 3 | ||
, speed = 4 | ||
} | ||
|
||
type FlowplayerSubtitlesMenu = FlowplayerMenu & { | ||
createMenu: (state: FlowplayerSubtitlesMenuState)=> void | ||
} | ||
|
||
const MENU_CLASS = "fp-menu" | ||
, MENU_CONTAINER_CLASS = "fp-menu-container" | ||
, MENU_HEADER_CLASS = "fp-menu-header" | ||
, MENU_CLOSE_ICON_CLASS = "fp-close" | ||
, MENU_CLOSE = "is-close" | ||
|
||
export class MenuDialog extends HTMLElement { | ||
menuContainer: HTMLDetailsElement | ||
summaryEle: HTMLElement | ||
mainMenu: HTMLDivElement | ||
menuHeader: HTMLDivElement | ||
menuTitle: HTMLHeadElement | ||
olEle: HTMLOListElement | ||
closeEle: HTMLSpanElement | ||
|
||
player | ||
|
||
constructor(player: Player) { | ||
super() | ||
this.className = "fp-menu-dialog" | ||
this.player = player | ||
|
||
this.menuContainer = document.createElement("details") | ||
this.summaryEle = document.createElement("summary") | ||
this.menuHeader = document.createElement("div") | ||
this.menuTitle = document.createElement("h3") | ||
this.olEle = document.createElement("ol") | ||
this.mainMenu = document.createElement("div") | ||
this.closeEle = document.createElement("span") | ||
|
||
this.menuHeader.classList.add(MENU_HEADER_CLASS) | ||
this.menuHeader.append(this.menuTitle, this.closeEle) | ||
|
||
this.mainMenu.classList.add(MENU_CLASS, "fp-main-menu") | ||
this.mainMenu.append(this.menuHeader, this.olEle) | ||
|
||
this.closeEle.classList.add(MENU_CLOSE_ICON_CLASS) | ||
this.closeEle.textContent = "×" | ||
|
||
this.menuContainer.classList.add(MENU_CONTAINER_CLASS) | ||
this.menuContainer.append(this.summaryEle, this.mainMenu) | ||
|
||
//TODO add settings translation | ||
this.menuTitle.textContent = this.player.i18n("core.settings", "Settings") | ||
|
||
//Accessibility | ||
this.olEle.setAttribute("aria-labelledby", this.summaryEle.id) | ||
this.olEle.setAttribute("role", "menu") | ||
this.summaryEle.setAttribute("aria-haspopup", "true") | ||
this.summaryEle.setAttribute("aria-controls", this.olEle.id) | ||
this.summaryEle.setAttribute("tabindex", "0") | ||
this.summaryEle.setAttribute("aria-expanded", "false") | ||
this.summaryEle.setAttribute("role", "button") | ||
//TODO add translation | ||
this.summaryEle.setAttribute("aria-label", "Settings") | ||
|
||
this.append(this.menuContainer) | ||
this.toggleVisibility() | ||
|
||
//listeners | ||
player.on("keyboard:close:menus", this.onKeyboardCloseMenu.bind(this)) | ||
player.root.addEventListener("click", this.onRootClick.bind(this)) | ||
this.addEventListener("click", this.onMenuClick.bind(this)); | ||
["focusin", "focusout"].forEach((ev)=> this.mainMenu.addEventListener(ev as any, this.onFocus.bind(this))) | ||
} | ||
|
||
onSubMenuCreated(menuComponent: FlowplayerMenu | FlowplayerSubtitlesMenu) { | ||
this.createDialogOpt(menuComponent) | ||
this.addSubMenuBackButton(menuComponent.menuHeader) | ||
this.menuContainer.append(menuComponent.menu) | ||
} | ||
|
||
//create new main-menu opt for a submenu | ||
createDialogOpt(menuComponent: FlowplayerMenu | FlowplayerSubtitlesMenu) { | ||
const opt = document.createElement("li") | ||
opt.textContent = menuComponent.menuHeader.querySelector("h3")?.textContent || "" | ||
|
||
const type = this.detectSubMenuType(menuComponent.classList) as MenuType | ||
this.toggleDialogOpt(menuComponent.menu, opt, type) | ||
|
||
menuComponent.addEventListener(this.findSubMenuOptionsEvent(type) as any, ()=> { | ||
this.toggleDialogOpt(menuComponent.menu, opt, type) | ||
}) | ||
|
||
opt.onclick = type === MenuType.subtitles | ||
? this.onSubtitlesOptClick.bind(this, menuComponent as FlowplayerSubtitlesMenu) | ||
: this.onOptClick.bind(this, menuComponent) | ||
|
||
//accessibility | ||
opt.setAttribute("role", "menuitem") | ||
opt.setAttribute("aria-selected", "false") | ||
opt.setAttribute("tabindex", "0") | ||
opt.setAttribute("aria-haspopup", "true") | ||
opt.setAttribute("aria-label", menuComponent.menuHeader.querySelector("h3")?.textContent || "") | ||
} | ||
|
||
// remove/append dialog opt based on the number of the submenu opts | ||
toggleDialogOpt(submenu: HTMLDivElement, dialogOpt: HTMLLIElement, type?: MenuType) { | ||
try { | ||
submenu.querySelectorAll("li").length > (type === MenuType.subtitles ? 0 : 1) | ||
? this.olEle.appendChild(dialogOpt) | ||
: this.olEle.removeChild(dialogOpt) | ||
} catch (e) { } | ||
|
||
this.toggleVisibility() | ||
} | ||
|
||
// adds a back button to the header of a submenu | ||
addSubMenuBackButton(menuHeader: HTMLElement) { | ||
const back_button = document.createElement("div") | ||
back_button.className = "fp-icon fp-menu-back fp-back-button" | ||
back_button.setAttribute("aria-hidden", "true") | ||
menuHeader.append(back_button) | ||
} | ||
|
||
// opens a menu and hide the rest of dialog menus | ||
openMenu(menu_to_open: HTMLElement){ | ||
this.querySelectorAll(".fp-menu")?.forEach((menu)=> { | ||
if (menu === menu_to_open) return | ||
(menu as HTMLElement)?.classList.add(MENU_CLOSE) | ||
}) | ||
|
||
menu_to_open.classList.remove(MENU_CLOSE) | ||
menu_to_open.querySelector("li")?.focus() | ||
} | ||
|
||
//open/close dialog | ||
toggleMenuDialog(open: boolean) { | ||
this.menuContainer.open = open | ||
// TODO replace has-open-menu state, with flowplayer Constant | ||
this.player.root.classList.toggle("has-menu-opened" , open) | ||
this.summaryEle.setAttribute("aria-expanded", open + "") | ||
} | ||
|
||
//hide/show dialog if there are no available options to any of the submenus | ||
toggleVisibility() { | ||
const hide = !this.olEle.querySelectorAll("li").length | ||
this.style.setProperty("display", hide ? "none": "block") | ||
// close menu dialog | ||
if (hide && this.menuContainer.open) this.toggleMenuDialog(false) | ||
} | ||
|
||
//dialog's click listener | ||
onMenuClick(ev: MouseEvent){ | ||
if (ev.defaultPrevented) return | ||
ev.preventDefault() | ||
|
||
const target = ev.target as HTMLElement | ||
if (target?.classList?.contains("fp-menu-back") || target?.closest("li")) return this.openMenu(this.mainMenu) | ||
|
||
const should_open = !this.menuContainer.open | ||
if (should_open) this.openMenu(this.mainMenu) | ||
this.toggleMenuDialog(should_open) | ||
} | ||
|
||
//dialog's focus listener | ||
onFocus(ev: FocusEvent) { | ||
const target = ev.target | ||
if (!(target instanceof HTMLLIElement)) return | ||
target.setAttribute("aria-selected", ev.type === "focusin" ? "true" : "false") | ||
} | ||
|
||
//main menu's opt click listener | ||
onOptClick(menuComponent: FlowplayerMenu, ev: MouseEvent) { | ||
ev.preventDefault() | ||
this.openMenu(menuComponent.menu) | ||
} | ||
|
||
//main menu's subtitle opt click listener | ||
onSubtitlesOptClick(menuComponent: FlowplayerSubtitlesMenu, ev: MouseEvent) { | ||
//Create main-subtitles-menu before opening subtitles menu | ||
if (!this.player.opt("subtitles.native")) menuComponent.createMenu(0) | ||
this.onOptClick(menuComponent, ev) | ||
} | ||
|
||
//player's root click listener | ||
onRootClick(ev: MouseEvent) { | ||
if (ev.composedPath().includes(this)) return | ||
this.toggleMenuDialog(false) | ||
} | ||
|
||
onKeyboardCloseMenu(ev: Event) { | ||
if (ev.defaultPrevented) return | ||
ev.preventDefault() | ||
|
||
if (this.mainMenu.classList.contains(MENU_CLOSE)) return this.openMenu(this.mainMenu) | ||
this.toggleMenuDialog(false) | ||
this.summaryEle.focus() | ||
} | ||
|
||
detectSubMenuType(classList: DOMTokenList) { | ||
//audio-menu | ||
if (classList.contains("fp-asel")) return MenuType.asel | ||
//quality menu | ||
if (classList.contains("fp-qsel")) return MenuType.qsel | ||
//speed menu | ||
if (classList.contains("fp-speed")) return MenuType.speed | ||
//subtitles menu | ||
if (classList.contains("fp-cc")) return MenuType.subtitles | ||
//video tracks menu | ||
if (classList.contains("fp-vsel")) return MenuType.vtsel | ||
} | ||
|
||
findSubMenuOptionsEvent(type: MenuType) { | ||
switch (type) { | ||
case MenuType.asel: | ||
return "audio:tracks" | ||
case MenuType.qsel: | ||
return "quality:tracks" | ||
case MenuType.speed: | ||
return "speed:options" | ||
case MenuType.subtitles: | ||
return "subs:tracks" | ||
case MenuType.vtsel: | ||
return "video:tracks" | ||
default: | ||
return "" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "@flowplayer/components-combined-menu-controls", | ||
"main": "./index.ts", | ||
"description": "A control bar with a single combined menu for all settings", | ||
"flowplayer": { | ||
"componentName": "combined-menu-controls", | ||
"overridenComponent": "flowplayer-control", | ||
"className": "CombinedMenuControl" | ||
} | ||
} |