paella-core
Version:
Multistream HTML video player
552 lines (492 loc) • 17 kB
JavaScript
import { DomClass, createElementWithHtmlText } from 'paella-core/js/core/dom';
import Events, { triggerEvent } from 'paella-core/js/core/Events';
import 'paella-core/styles/PopUp.css';
import defaultMinimizeIcon from 'paella-core/icons/minimize-3.svg';
import defaultCloseButton from 'paella-core/icons/close.svg';
const g_popUps = [];
function placePopUp(player, anchorElement, contentElement) {
if (anchorElement) {
anchorElement.setAttribute("aria-pressed","");
const { top, left, right, bottom, width, height } = anchorElement.getBoundingClientRect();
const centerX = left + width / 2;
const centerY = top + height / 2;
const scroll = document.body.scrollTop;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const viewportCenterX = window.innerWidth / 2;
const viewportCenterY = window.innerHeight / 2;
const scrollTop = document.scrollingElement.scrollTop;
// Decide where to attach the popup depending on the anchor position
contentElement.style.left = "";
contentElement.style.right = "";
contentElement.style.bottom = "";
contentElement.style.top = "";
contentElement.style.width = "";
contentElement.style.height = "";
contentElement.classList.remove("static-position");
if (viewportCenterX>centerX && viewportCenterY<=centerY) {
// bottom left
const b = viewportHeight - (bottom - height);
contentElement.style.left = `${ left }px`;
contentElement.style.bottom = `${ b - scrollTop }px`;
contentElement.style.maxHeight = `calc(100vh - ${ b }px - 10px)`;
}
else if (viewportCenterX>centerX && viewportCenterY>centerY) {
// top left quadrant
contentElement.style.left = `${ left }px`;
contentElement.style.top = `${ top + height + scrollTop }px`;
contentElement.style.maxHeight = `calc(100vh - ${ top + height }px - 10px)`;
}
else if (viewportCenterX<=centerX && viewportCenterY>centerY) {
// top right quadrant
contentElement.style.right = `${ viewportWidth - right }px`;
contentElement.style.top = `${ top + height + scrollTop }px`;
contentElement.style.maxHeight = `calc(100vh - ${ top + height }px - 10px)`;
}
else if (viewportCenterX<=centerX && viewportCenterY<=centerY) {
// bottom right quadrant
const b = viewportHeight - (bottom - height);
contentElement.style.right = `${ viewportWidth - right }px`;
contentElement.style.bottom = `${ b - scrollTop }px`;
contentElement.style.maxHeight = `calc(100vh - ${ b }px - 10px)`;
}
setTimeout(() => {
if (contentElement.offsetTop<0) {
contentElement.style.top = "0px";
}
}, 100);
}
}
function enableHidePopUpActionContainer(player) {
if (!player.__hidePopUpActionContainer) {
player.__hidePopUpActionContainer = createElementWithHtmlText('<div class="hide-popup-action-container"></div>');
player.videoContainer.element.appendChild(player.__hidePopUpActionContainer);
player.__hidePopUpActionContainer.style.position = "absolute";
player.__hidePopUpActionContainer.style.left = "0px";
player.__hidePopUpActionContainer.style.top = "0px";
player.__hidePopUpActionContainer.style.right = "0px";
player.__hidePopUpActionContainer.style.bottom = "0px";
player.__hidePopUpActionContainer.style.zIndex = 500;
player.__hidePopUpActionContainer.addEventListener("click", evt => {
PopUp.HideAllPopUps(false);
evt.stopPropagation();
});
}
player.__hidePopUpActionContainer.style.display = "block";
}
function disableHidePopUpActionContainer(player) {
if (player.__hidePopUpActionContainer) {
player.__hidePopUpActionContainer.style.display = "none";
}
}
function getDragAction(rect,click,titleHeight,resizeable) {
const topBorder = 10;
const leftBorder = 10;
const rightBorder = 10;
const bottomBorder = 10;
const left = click.left - rect.x;
const top = click.top - rect.y;
const right = rect.width - left;
const bottom = rect.height - top;
switch (true) {
case left <= leftBorder && top <= topBorder && resizeable:
return 'RESIZE_NW';
case left <= leftBorder && bottom <= bottomBorder && resizeable:
return 'RESIZE_SW';
case left <= leftBorder && resizeable:
return 'RESIZE_W';
case right <= rightBorder && top <= topBorder && resizeable:
return 'RESIZE_NE';
case right <= rightBorder && bottom <= bottomBorder && resizeable:
return 'RESIZE_SE';
case right <= rightBorder && resizeable:
return 'RESIZE_E';
case top <= topBorder && resizeable:
return 'RESIZE_N';
case bottom <= bottomBorder && resizeable:
return 'RESIZE_S';
case top <= topBorder + titleHeight:
return 'MOVE';
default:
return '';
}
}
export default class PopUp extends DomClass {
static GetPopUps() {
return g_popUps;
}
static IsSomePopUpVisible() {
return g_popUps.some(p => p.isVisible);
}
static GetPopUp(id) {
return g_popUps.find(p => p.id === id);
}
static Contains(element) {
return g_popUps.some(popUp => {
return popUp.element.contains(element);
})
}
static HideAllPopUps(onlyModal = true) {
g_popUps.forEach(p => {
if (onlyModal && p.isModal || !onlyModal) {
if (p._closeOnClickOut) {
p.hide();
}
}
});
}
static HideTopPopUp() {
if (g_popUps.length) {
let topPopUp = null;
g_popUps.slice().reverse().some(popUp => {
if (popUp.isVisible) {
topPopUp = popUp;
}
return topPopUp !== null;
});
if (topPopUp && topPopUp._closeOnClickOut) {
topPopUp.hide();
return true;
}
}
return false;
}
static Unload() {
g_popUps.forEach(p => {
p.removeFromParent();
});
g_popUps.slice(0);
}
static HideNonAncestors(popup) {
g_popUps.forEach(otherPopUp => {
if (popup.isParent && !popup.isParent(otherPopUp) && otherPopUp._closeOnClickOut) {
otherPopUp.hide();
}
});
}
constructor(player, parent, anchorElement = null, contextObject = null, modal = true, moveable = false, resizeable = false, customClass = "") {
const attributes = {
"class": `${ modal ? "popup-container" : "popup-container no-modal" } ${customClass}`
};
moveable = moveable || resizeable;
const minimizeButton = player.getCustomPluginIcon("paella-core","dock-popup") || defaultMinimizeIcon;
const closeButtonIcon = player.getCustomPluginIcon("paella-core","close-popup") || defaultCloseButton;
const children = `
<div class="popup-content${ resizeable ? " resizeable" : "" }${ moveable ? " moveable" : " fixed" }">
<div class="border-top-left"></div><div class="border-top-center"></div><div class="border-top-right"></div>
<div class="title-bar">
<div class="title-bar-content"></div>
<div class="popup-action-buttons">
<button class="popup-action-button dock-button"><i>${minimizeButton}</i></button>
<button class="popup-action-button close-button"><i>${closeButtonIcon}</i></button>
</div>
</div>
<div class="center-container"></div>
<div class="border-bottom-left"></div><div class="border-bottom-center"></div><div class="border-bottom-right"></div>
</div>
`;
super(player,{ attributes, children, parent });
this._lastFocusElement = document.activeElement;
this._modal = modal;
this._contextObject = contextObject;
this._dragActionData = null;
this._moveable = moveable || resizeable;
this._resizeable = resizeable;
this._id = Symbol(this);
g_popUps.push(this);
const dockButton = this.element.getElementsByClassName("dock-button")[0];
dockButton.addEventListener('click', evt => {
this.dock();
});
const closeButton = this.element.getElementsByClassName("close-button")[0];
closeButton.addEventListener('click', () => this.hide());
closeButton.addEventListener('mousedown', evt => evt.stopPropagation());
this._closeButton = closeButton;
this.element.addEventListener("click", () => {
if (this._closeOnClickOut) {
this.hide();
}
});
this._contentElement = this.element.getElementsByClassName("popup-content")[0];
this._centerContainer = this.element.getElementsByClassName("center-container")[0];
this._titleBar = this.element.getElementsByClassName("title-bar")[0];
this._centerContainer.addEventListener("mousedown", evt => {
evt.stopPropagation();
});
this._contentElement.addEventListener("mousedown", (event) => {
if (this.moveable || this.resizeable) {
this._element.style.pointerEvents = "all";
this._moved = true;
// Make static the current position and size of the pop up window
const rect = this._contentElement.getBoundingClientRect();
this._contentElement.classList.add("static-position");
this._contentElement.style.top = rect.top + 'px';
this._contentElement.style.left = rect.left + 'px';
this._contentElement.style.width = rect.width + 'px';
this._contentElement.style.height = rect.height + 'px';
this._contentElement.style.maxHeight = "unset";
// We don't know the actual size of the title bar by CSS, so we have
// to adjust the height of the container inline
const titleRect = this._titleBar.getBoundingClientRect();
const titleBarHeight = titleRect.height;
this._centerContainer.style.height = `calc(100% - var(--popup-resizeable-border) * 2 - ${titleBarHeight}px)`;
const initialPosition = {
left: event.clientX,
top: event.clientY
};
this._dragActionData = {
popUp: this,
action: getDragAction(rect, initialPosition, titleBarHeight, this._resizeable),
event,
initialPosition
}
}
event.stopPropagation();
});
this.element.addEventListener("mouseup", evt => {
this._element.style.pointerEvents = "";
if (this.moveable || this.resizeable) {
this._dragActionData = null;
}
})
this.element.addEventListener('mousemove', evt => {
if (this._dragActionData) {
const offset = {
left: evt.clientX - this._dragActionData.initialPosition.left,
top: evt.clientY - this._dragActionData.initialPosition.top
};
this._dragActionData.initialPosition = {
left: evt.clientX,
top: evt.clientY
};
const rect = this._contentElement.getBoundingClientRect();
// TODO: Check minimum size
if (this._dragActionData.action === 'MOVE') {
this._contentElement.style.top = `${ rect.top + offset.top }px`;
this._contentElement.style.left = `${ rect.left + offset.left }px`;
this._contentElement.style.height = `${ rect.height }px`;
this._contentElement.style.width = `${ rect.width }px`;
}
else if (this._dragActionData.action === 'RESIZE_N') {
this._contentElement.style.height = `${ rect.height - offset.top}px`;
this._contentElement.style.top = `${ rect.top + offset.top }px`;
}
else if (this._dragActionData.action === 'RESIZE_NE') {
this._contentElement.style.height = `${ rect.height - offset.top}px`;
this._contentElement.style.top = `${ rect.top + offset.top }px`;
this._contentElement.style.width = `${ rect.width + offset.left}px`;
this._contentElement.style.left = `${ rect.left }px`;
}
else if (this._dragActionData.action === 'RESIZE_E') {
this._contentElement.style.width = `${ rect.width + offset.left}px`;
this._contentElement.style.left = `${ rect.left }px`;
}
else if (this._dragActionData.action === 'RESIZE_SE') {
this._contentElement.style.top = `${ rect.top }px`;
this._contentElement.style.left = `${ rect.left }px`;
this._contentElement.style.width = `${ rect.width + offset.left}px`;
this._contentElement.style.height = `${ rect.height + offset.top}px`;
}
else if (this._dragActionData.action === 'RESIZE_S') {
this._contentElement.style.top = `${ rect.top }px`;
this._contentElement.style.height = `${ rect.height + offset.top}px`;
}
else if (this._dragActionData.action === 'RESIZE_SW') {
this._contentElement.style.top = `${ rect.top }px`;
this._contentElement.style.height = `${ rect.height + offset.top}px`;
this._contentElement.style.width = `${ rect.width - offset.left}px`;
this._contentElement.style.left = `${ rect.left + offset.left}px`;
}
else if (this._dragActionData.action === 'RESIZE_NW') {
this._contentElement.style.width = `${ rect.width - offset.left}px`;
this._contentElement.style.left = `${ rect.left + offset.left}px`;
this._contentElement.style.height = `${ rect.height - offset.top}px`;
this._contentElement.style.top = `${ rect.top + offset.top }px`;
}
else if (this._dragActionData.action === 'RESIZE_W') {
this._contentElement.style.width = `${ rect.width - offset.left}px`;
this._contentElement.style.left = `${ rect.left + offset.left}px`;
}
}
});
this._contentElement.addEventListener("mouseup", (evt) => {
this._dragActionData = null;
this._element.style.pointerEvents = "";
evt.stopPropagation();
});
this._contentElement.addEventListener("click", evt => {
evt.stopPropagation();
})
this._anchorElement = anchorElement;
if (anchorElement) {
placePopUp(player, anchorElement, this.contentElement);
}
this._parentPopUp = null;
this.hide();
}
dock() {
this._moved = false;
this._centerContainer.style.height = "";
this.hide();
this.show();
}
get lastFocusElement() {
return this._lastFocusElement;
}
get isModal() {
return this._modal;
}
get contextObject() {
return this._contextObject;
}
get id() {
return this._id;
}
// This is the popup window
get contentElement() {
return this._contentElement;
}
get centerContainer() {
return this._centerContainer;
}
// This is the content element you set with setContent()
get content() {
return this._popupContent;
}
get parentPopUp() {
return this._parentPopUp;
}
get moveable() {
return this._moveable;
}
get resizeable() {
return this._resizeable;
}
get titleBar() {
return this._titleBar;
}
set title(titleData) {
this._title = titleData;
this._titleBar.classList.remove("not-empty");
const titleBarContent = this._titleBar.getElementsByClassName('title-bar-content')[0];
if (titleData !== null && titleData instanceof Element) {
titleBarContent.innerHTML = "";
titleBarContent.appendChild(titleData);
this._titleBar.classList.add("not-empty");
}
else if (titleData !== null) {
titleBarContent.innerHTML = "";
titleBarContent.innerHTML = this.player.translate(titleData);
this._titleBar.classList.add("not-empty");
}
}
get title() {
return this._title;
}
setCloseActions({ clickOutside = true, closeButton = false }) {
this._closeOnClickOut = clickOutside;
this._closeOnButton = closeButton;
if (this._closeOnButton) {
this._closeButton.style.display = "block";
}
else {
this._closeButton.style.display = "none";
}
}
isParent(otherPopUp) {
if (otherPopUp === this) {
return true;
}
else if (this.parentPopUp === null) {
return false;
}
else if (this.parentPopUp === otherPopUp) {
return true;
}
else {
return this.parentPopUp.isParent(otherPopUp);
}
}
setContent(domElement) {
this.centerContainer.innerHTML = "";
if (typeof(domElement) === "string") {
this._popupContent = createElementWithHtmlText(domElement, this.centerContainer);
}
else {
this._popupContent = domElement;
this.centerContainer.appendChild(domElement);
}
}
show(parent = null, parentPopUp = null) {
if (this._anchorElement && !this._moved) {
placePopUp(this.player, this._anchorElement, this.contentElement);
}
if (parent) {
this.setParent(parent);
}
this._parentPopUp = parentPopUp;
if (parentPopUp) {
parentPopUp.addChild(this);
}
super.show();
PopUp.HideNonAncestors(this);
if (this._closeOnClickOut) {
enableHidePopUpActionContainer(this.player);
}
triggerEvent(this.player, Events.SHOW_POPUP, {
popUp: this,
plugin: this.contextObject
});
}
hide() {
if (this.isVisible) {
if (this._children) {
this._children.forEach(child => {
if (child._closeOnClickOut) {
child.hide();
}
});
}
if (this._parentPopUp) {
this._parentPopUp.removeChild(this);
}
triggerEvent(this.player, Events.HIDE_POPUP, {
popUp: this,
plugin: this.contextObject
});
if (this._anchorElement) {
try {
this._anchorElement.removeAttribute("aria-expanded");
} catch {}
}
super.hide();
if (this.lastFocusElement) {
this.lastFocusElement.focus();
}
}
if (!g_popUps.some(p => {
return p.isVisible && p._closeOnClickOut;
})) {
disableHidePopUpActionContainer(this.player);
}
}
// Child popUp management
addChild(childPopUp) {
this._children = this._children || [];
if (!this._children.find(child => child === childPopUp)) {
this._children.push(childPopUp);
}
}
removeChild(childPopUp) {
if (this._children) {
this._children = this._children.filter(child => child !== childPopUp);
}
}
destroy() {
const index = g_popUps.indexOf(this);
if (index !== -1) {
g_popUps.splice(index,1);
this.removeFromParent();
}
}
}