/// import { Options, Element, OnLayout, Animate, AnimateProps } from 'opentok-layout-js'; import getLayout from './getLayout'; export default (container: HTMLElement, opts: Options) => { function css(el: HTMLElement, propertyName: Record): typeof NaN function css(el: HTMLElement, propertyName: string, value: string): typeof NaN function css(el: HTMLElement, propertyName: string): string function css(el: HTMLElement, propertyName: string | Record, value?: string) { if (typeof propertyName === 'string' && value) { // We are setting one css property el.style[propertyName] = value; return NaN; } if (typeof propertyName === 'object') { // We are setting several CSS properties at once Object.keys(propertyName).forEach((key) => { css(el, key, propertyName[key]); }); return NaN; } // We are getting the css property const computedStyle = ((opts && opts.window) || window).getComputedStyle(el); let currentValue = computedStyle.getPropertyValue(propertyName); if (currentValue === '') { currentValue = el.style[propertyName]; } return currentValue; } const filterDisplayNone = element => css(element, 'display') !== 'none'; function height(el) { if (el.offsetHeight > 0) { return `${el.offsetHeight}px`; } return css(el, 'height'); } function width(el) { if (el.offsetWidth > 0) { return `${el.offsetWidth}px`; } return css(el, 'width'); } const positionElement = function positionElement(elem: HTMLElement, x: number, y: number, w: number, h: number, animate: Animate, onLayout: OnLayout) { const targetPosition = { left: `${x}px`, top: `${y}px`, width: `${w}px`, height: `${h}px`, }; const fixAspectRatio = function fixAspectRatio() { const sub = elem.querySelector('.OT_root') as HTMLElement; if (sub) { // If this is the parent of a subscriber or publisher then we need // to force the mutation observer on the publisher or subscriber to // trigger to get it to fix it's layout const oldWidth = sub.style.width; sub.style.width = `${w}px`; // sub.style.height = height + 'px'; sub.style.width = oldWidth || ''; } }; if (animate && typeof $ !== 'undefined') { $(elem).stop(); const animateProps: AnimateProps = typeof animate === 'boolean' ? { duration: 200, easing: 'swing' } : animate; $(elem).animate(targetPosition, animateProps.duration, animateProps.easing, () => { fixAspectRatio(); if (animateProps.complete) animateProps.complete.call(this); if (onLayout) { onLayout(elem, { left: x, top: y, width: w, height: h, }); } }); } else { css(elem, targetPosition); if (!elem.classList.contains('ot-layout')) { elem.classList.add('ot-layout'); } if (onLayout) { onLayout(elem, { left: x, top: y, width: w, height: h, }); } } fixAspectRatio(); }; const getChildDims = function getChildDims(child: HTMLVideoElement | HTMLElement): Omit { if (child) { if ((child as HTMLVideoElement).videoHeight && (child as HTMLVideoElement).videoWidth) { return { height: (child as HTMLVideoElement).videoHeight, width: (child as HTMLVideoElement).videoWidth, }; } const video = child.querySelector('video'); if (video && video.videoHeight && video.videoWidth) { return { height: video.videoHeight, width: video.videoWidth, }; } } return { height: 480, width: 640, }; }; const getCSSNumber = function getCSSNumber(elem: HTMLElement, prop: string) { const cssStr = css(elem, prop); return cssStr ? parseInt(cssStr, 10) : 0; }; // Really cheap UUID function const cheapUUID = function cheapUUID() { return (Math.random() * 100000000).toFixed(0); }; const getHeight = function getHeight(elem) { const heightStr = height(elem); return heightStr ? parseInt(heightStr, 10) : 0; }; const getWidth = function getWidth(elem) { const widthStr = width(elem); return widthStr ? parseInt(widthStr, 10) : 0; }; const { animate = false, bigClass = 'OT_big', ignoreClass = 'OT_ignore', fixedRatioClass = 'OT_fixedRatio', } = opts; if (css(container, 'display') === 'none') { return; } let id = container.getAttribute('id'); if (!id) { id = `OT_${cheapUUID()}`; container.setAttribute('id', id); } opts.containerHeight = getHeight(container) - getCSSNumber(container, 'border-top') - getCSSNumber(container, 'border-bottom'); opts.containerWidth = getWidth(container) - getCSSNumber(container, 'border-left') - getCSSNumber(container, 'border-right'); const children: HTMLElement[] = Array.prototype.filter.call( container.querySelectorAll(`#${id}>*:not(.${ignoreClass})`), filterDisplayNone ); const elements: Element[] = children.map((element) => { return { ...getChildDims(element), big: element.classList.contains(bigClass), fixedRatio: element.classList.contains(fixedRatioClass), }; }); const layout = getLayout(opts, elements); layout.boxes.forEach((box, idx) => { const elem = children[idx]; css(elem, 'position', 'absolute'); const actualWidth = box.width - getCSSNumber(elem, 'margin-left') - getCSSNumber(elem, 'margin-right') - (css(elem, 'box-sizing') !== 'border-box' ? (getCSSNumber(elem, 'padding-left') + getCSSNumber(elem, 'padding-right') + getCSSNumber(elem, 'border-left') + getCSSNumber(elem, 'border-right')) : 0); const actualHeight = box.height - getCSSNumber(elem, 'margin-top') - getCSSNumber(elem, 'margin-bottom') - (css(elem, 'box-sizing') !== 'border-box' ? (getCSSNumber(elem, 'padding-top') + getCSSNumber(elem, 'padding-bottom') + getCSSNumber(elem, 'border-top') + getCSSNumber(elem, 'border-bottom')) : 0); positionElement(elem, box.left, box.top, actualWidth, actualHeight, animate, opts.onLayout); }); };