UNPKG

cloudinary-video-player

Version:
573 lines (554 loc) 20 kB
import { _ as _vjs } from './_videojs-proxy.js'; import { P as PLAYER_EVENT, f as find, I as ImageSource } from './index2.js'; import { t as throttle } from './throttle.js'; // Convert time string i.e. '2:40' to seconds number (160) // Also allows h:m:s format and mm:ss, m:s etc. const parseTime = function (hms) { const [seconds, minutes, hours] = hms.split(':').reverse(); let sum = null; if (!isNaN(seconds)) { sum = (+hours || 0) * 60 * 60 + (+minutes || 0) * 60 + +seconds; } return sum; }; const SHOPPABLE_WIDGET_OPTIONS_DEFAULTS = { location: 'right', toggleIcon: '', width: '20%', startState: 'openOnPlay', autoClose: 2, transformation: { quality: 'auto', width: 'auto', fetch_format: 'auto', crop: 'scale' }, products: [], showPostPlayOverlay: false }; const SHOPPABLE_CLICK_ACTIONS = { GO_TO: 'goto', SEEk: 'seek' }; const SHOPPABLE_HOVER_ACTIONS = { OVERLAY: 'overlay' }; const SHOPPABLE_PANEL_VISIBLE_CLASS = 'shoppable-panel-visible'; const SHOPPABLE_PANEL_HIDDEN_CLASS = 'shoppable-panel-hidden'; const SHOPPABLE_PRODUCTS_OVERLAY_CLASS = 'shoppable-products-overlay'; const CLD_SPBL_PANEL_CLASS = 'cld-spbl-panel'; const CLD_SPBL_TOGGLE_CLASS = 'cld-spbl-toggle'; const CLD_SPBL_TOGGLE_ICON_CLASS = 'cld-spbl-toggle-icon'; const CLD_SPBL_INNER_BAR = 'cld-spbl-bar-inner'; const CLD_SPBL_TOGGLE_CUSTOM_ICON_CLASS = 'cld-spbl-toggle-custom-icon'; const ICON_CART_CLASS = 'vjs-icon-cart'; const CLOSE_ICON_CLASS = 'vjs-icon-close'; const SHOPPABLE_ANIMATION_CLASS = 'animate'; const CLD_SPBL_ITEM = 'cld-spbl-item'; const CLD_SPBL_IMAGE = 'cld-spbl-img'; const dom$4 = _vjs.dom || _vjs; const Component$2 = _vjs.getComponent('Component'); class ShoppableProductsOverlay extends Component$2 { constructor(player) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; super(player, options); this.options_ = options; this.player_ = player; this.player_.on(PLAYER_EVENT.SHOW_PRODUCTS_OVERLAY, this.renderProducts); this.dispose = () => { this.layout_.dispose(); }; } renderProducts = () => { // Close products side-panel this.player_.removeClass(SHOPPABLE_PANEL_VISIBLE_CLASS); this.player_.addClass(SHOPPABLE_PANEL_HIDDEN_CLASS); this.player_.addClass(SHOPPABLE_PRODUCTS_OVERLAY_CLASS); this.layout_.innerHTML = ''; // Filter products with appearance on currentTime const currentTime = this.player_.currentTime(); const currentProducts = this.options_.products.filter(product => product.hotspots && product.hotspots.some(a => parseTime(a.time) === currentTime)); currentProducts.forEach(product => { const hotspot = find(product.hotspots, hs => parseTime(hs.time) === currentTime); const productName = dom$4.createEl('div', { className: 'cld-spbl-product-hotspot-name' }, {}, product.productName); const productTooltip = dom$4.createEl('div', { className: 'cld-spbl-product-tooltip cld-spbl-product-tooltip-' + hotspot.tooltipPosition }, {}, productName); const productHotSpot = dom$4.createEl('a', { className: 'cld-spbl-product-hotspot accent-color-text', href: hotspot.clickUrl, target: '_blank' }, { style: 'left:' + hotspot.x + '; top:' + hotspot.y + ';' }, productTooltip); this.layout_.appendChild(productHotSpot); }); // Remove this.player_.one(PLAYER_EVENT.SEEKING, this.clearLayout); this.player_.one(PLAYER_EVENT.PLAY, this.clearLayout); }; clearLayout = () => { this.layout_.innerHTML = ''; this.player_.removeClass(SHOPPABLE_PRODUCTS_OVERLAY_CLASS); }; createEl() { const dimensions = this.player_.currentDimensions(); this.layout_ = dom$4.createEl('div', { className: 'cld-spbl-products-overlay', style: `padding-top: ${dimensions.height / dimensions.width * 100}%;` }); return this.layout_; } } _vjs.registerComponent('ShoppableProductsOverlay', ShoppableProductsOverlay); const dom$3 = _vjs.dom || _vjs; const ClickableComponent$1 = _vjs.getComponent('ClickableComponent'); class ShoppablePanelToggle extends ClickableComponent$1 { constructor(player, options) { super(player, options); this.options_ = options; } handleClick(event) { event.preventDefault(); event.stopPropagation(); this.options_.clickHandler(); } createEl() { let iconProps = {}; let iconAttrs = {}; if (this.options_.toggleIcon) { iconProps = { className: `${CLD_SPBL_TOGGLE_ICON_CLASS} ${CLD_SPBL_TOGGLE_CUSTOM_ICON_CLASS} ${CLOSE_ICON_CLASS}` }; iconAttrs = { style: `background-image: url(${this.options_.toggleIcon})` }; } else { iconProps = { className: `${CLD_SPBL_TOGGLE_ICON_CLASS} ${ICON_CART_CLASS}` }; } const icon = dom$3.createEl('span', iconProps, iconAttrs); const el = super.createEl('a', { className: `${CLD_SPBL_TOGGLE_CLASS} base-color-bg` }); el.appendChild(icon); this.player_.on(PLAYER_EVENT.PRODUCT_BAR_MIN, () => { setTimeout(() => { icon.classList.add(SHOPPABLE_ANIMATION_CLASS); setTimeout(() => { icon.classList.remove(SHOPPABLE_ANIMATION_CLASS); }, 1000); }, 500); }); return el; } } _vjs.registerComponent('shoppablePanelToggle', ShoppablePanelToggle); const dom$2 = _vjs.dom || _vjs; const Component$1 = _vjs.getComponent('Component'); class ShoppableBarLayout extends Component$1 { constructor(player, options) { super(player, options); this.player_ = player; this.player().addClass('cld-shoppable-panel'); this.player().addClass(SHOPPABLE_PANEL_HIDDEN_CLASS); this.contentWrpEl_ = dom$2.createEl('div', { className: 'cld-spbl-bar' }); this.contentBannerEl_ = dom$2.createEl('div', { className: 'cld-spbl-banner-msg base-color-text' }, {}, this.options_.bannerMsg || 'Shop the Video'); this.contentWrpEl_.appendChild(this.contentBannerEl_); const productsOverlay = new ShoppableProductsOverlay(this.player_, this.options_); this.contentWrpEl_.appendChild(productsOverlay.el_); this.contentEl_ = dom$2.createEl('div', { className: CLD_SPBL_INNER_BAR }); this.contentWrpEl_.appendChild(this.contentEl_); this.player().el().appendChild(this.contentWrpEl_); this.addChild(new ShoppablePanelToggle(this.player_, { toggleIcon: this.options_.toggleIcon, clickHandler: () => { this.togglePanel(); } })); this.addChild('ShoppablePanel', this.options_); this.dispose = () => { this.removeLayout(); super.dispose(); }; this.togglePanel = open => { if (open === true) { // Open this.player().removeClass(SHOPPABLE_PANEL_HIDDEN_CLASS); this.player().addClass(SHOPPABLE_PANEL_VISIBLE_CLASS); } else if (open === false) { // Close this.player().removeClass(SHOPPABLE_PANEL_VISIBLE_CLASS); this.player().addClass(SHOPPABLE_PANEL_HIDDEN_CLASS); } else { // Toggle this.player().toggleClass(SHOPPABLE_PANEL_HIDDEN_CLASS); this.player().toggleClass(SHOPPABLE_PANEL_VISIBLE_CLASS); } let eventName = this.player().hasClass(SHOPPABLE_PANEL_VISIBLE_CLASS) ? 'productBarMax' : 'productBarMin'; this.player().trigger(eventName); }; // Open shoppable if (this.options_.startState === 'open') { this.togglePanel(true); } // On play start this.player_.on(PLAYER_EVENT.PLAY, () => { if (this.player_.currentTime() < 0.01) { // Open shoppable on-play if (this.options_.startState === 'openOnPlay') { this.togglePanel(true, this.options_.autoClose); } // Auto-close shoppable if (this.options_.autoClose && this.options_.startState.indexOf('open') !== -1) { setTimeout(() => { // Keep it open while hovered if (!this.contentEl_.matches(':hover')) { this.togglePanel(false); } else { this.contentEl_.addEventListener('mouseleave', () => { this.togglePanel(false); }, { once: true }); } }, this.options_.autoClose * 1000); } } }); } createEl() { const el = super.createEl('div'); return el; } } _vjs.registerComponent('shoppableBarLayout', ShoppableBarLayout); const ClickableComponent = _vjs.getComponent('ClickableComponent'); const dom$1 = _vjs.dom || _vjs; const widthTransformation = { width: 132 }; class ShoppablePanelItem extends ClickableComponent { constructor(player, initOptions) { super(player, initOptions); this.options_ = initOptions; this.isDragged = false; } handleClick(event) { event.preventDefault(); event.stopPropagation(); if (!this.el_.matches(`.dragged .${CLD_SPBL_ITEM}`)) { // Prevent click event if dragged this.options_.clickHandler(event); } this.isDragged = false; } getTitle() { return this.options_.conf.title; } createEl() { const el = super.createEl('a', { className: `${CLD_SPBL_ITEM} base-color-bg accent-color-text`, href: '#' }); el.setAttribute('data-product-id', this.options_.conf.productId || ''); el.setAttribute('data-product-name', this.options_.conf.productName || ''); if (this.options_.conf.onHover) { addOnHover(el, this.options_.conf.onHover, this.options_.item.cloudinaryConfig()); } if (this.options_.conf.onClick) { addOnClick(el, this.options_.conf.onClick); } const img = super.createEl('img', { className: CLD_SPBL_IMAGE }, { src: this.options_.item.url(widthTransformation) }); el.appendChild(img); if (this.getTitle()) { const info = dom$1.createEl('div', { className: 'cld-spbl-item-info base-color-semi-bg text-color-text' }); const title = dom$1.createEl('span', { className: 'cld-spbl-item-title' }, {}, this.getTitle()); info.appendChild(title); el.appendChild(info); } return el; } } const addOnHover = (el, conf, cldConf) => { el.setAttribute('data-hover-action', conf.action); if (conf.action === SHOPPABLE_HOVER_ACTIONS.OVERLAY) { const overlayText = dom$1.createEl('span', { className: 'cld-spbl-overlay-text base-color-text' }, {}, conf.args); const overlay = dom$1.createEl('span', { className: 'cld-spbl-overlay text-color-semi-bg base-color-text' }, { title: conf.args }, overlayText); el.appendChild(overlay); } else { const switchImgSource = new ImageSource(conf.args.publicId, { cloudinaryConfig: cldConf, transformation: conf.args.transformation }); const hoverImg = dom$1.createEl('img', { className: `${CLD_SPBL_IMAGE} cld-spbl-hover-img` }, { src: switchImgSource.url(widthTransformation) }); el.appendChild(hoverImg); } }; const addOnClick = (el, conf) => { el.setAttribute('data-click-action', conf.action); el.setAttribute('data-pause', conf.pause); if (conf.action === SHOPPABLE_CLICK_ACTIONS.SEEk) { el.setAttribute('data-seek', conf.args.time); } else if (conf.action === SHOPPABLE_CLICK_ACTIONS.GO_TO) { el.setAttribute('data-goto-url', conf.args.url); } }; _vjs.registerComponent('shoppablePanelItem', ShoppablePanelItem); const Component = _vjs.getComponent('Component'); class ShoppablePanel extends Component { constructor(player) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; super(player, options); this.options = options; const itemChangeHandler = () => { this.render(); }; player.on(PLAYER_EVENT.SHOPPABLE_ITEM_CHANGED, itemChangeHandler); this.render(); this.dispose = () => { super.dispose(); player.off(PLAYER_EVENT.SHOPPABLE_ITEM_CHANGED, itemChangeHandler); }; } createEl() { const el = super.createEl(); [CLD_SPBL_PANEL_CLASS, 'base-color-bg'].map(cls => el.classList.add(cls)); return el; } removeAll() { const childrens = this.children(); for (let i = childrens.length - 1; i >= 0; --i) { this.removeChild(childrens[i]); } } getItems() { const cloudinaryConfig = this.player_.cloudinary.cloudinaryConfig(); return this.options.products.map(product => { if (product.onHover && typeof product.onHover.args === 'object') { product.onHover.args.transformation = Object.assign({}, this.options.transformation, product.onHover.args.transformation); } const conf = { productId: product.productId, productName: product.productName, title: product.title, onHover: product.onHover, onClick: product.onClick, startTime: product.startTime, endTime: product.endTime }; const imageSource = new ImageSource(product.publicId, { cloudinaryConfig: cloudinaryConfig, transformation: Object.assign({}, this.options.transformation, product.transformation) }); return { imageSrc: imageSource, conf: conf }; }); } scrollToActiveItem() { const activeItems = this.el_.getElementsByClassName('active'); if (activeItems.length > 0) { const toScroll = activeItems[0].offsetTop - 12; // Test for native scrollTo support (IE will fail) if ('scrollBehavior' in document.documentElement.style) { this.el_.scrollTo({ top: toScroll, behavior: 'smooth' }); } else { this.el_.scrollTop = toScroll; } } } render() { this.removeAll(); const items = this.getItems(); const throttledScrollToActiveItem = throttle(() => this.scrollToActiveItem(), 1000); items.forEach((item, index) => { const shoppablePanelItem = new ShoppablePanelItem(this.player(), { item: item.imageSrc, conf: item.conf, next: index === 1, current: index === 0, clickHandler: e => { let target = e.currentTarget || e.target; let evName = this.player_.ended() ? 'productClickPost' : 'productClick'; this.player_.trigger(evName, { productId: target.dataset.productId, productName: target.dataset.productName }); // Go to URL, or seek video (set currentTime) if (target.dataset.clickAction === SHOPPABLE_CLICK_ACTIONS.GO_TO) { window.open(target.dataset.gotoUrl, '_blank'); } else if (target.dataset.clickAction === SHOPPABLE_CLICK_ACTIONS.SEEk) { const gotoSecs = parseTime(target.dataset.seek); if (gotoSecs !== null) { this.player_.addClass('vjs-has-started'); // Hide the poster image if (this.player_.postModal) { this.player_.postModal.close(); } this.player_.currentTime(gotoSecs); // Close products side-panel this.player_.removeClass(SHOPPABLE_PANEL_VISIBLE_CLASS); this.player_.addClass(SHOPPABLE_PANEL_HIDDEN_CLASS); this.player_.addClass(SHOPPABLE_PRODUCTS_OVERLAY_CLASS); // Wait for the time update and show the tooltips this.player_.one('seeked', () => this.player_.trigger('showProductsOverlay')); } } // pause - true (default), false, or number of seconds if (target.dataset.pause !== 'false') { this.player_.pause(); if (parseTime(target.dataset.pause)) { setTimeout(() => { this.player_.play(); }, parseTime(target.dataset.pause) * 1000); } } } }); shoppablePanelItem.on('mouseover', e => { let target = e.currentTarget || e.target; let evName = this.player_.ended() ? 'productHoverPost' : 'productHover'; this.player_.trigger(evName, { productId: target.dataset.productId, productName: target.dataset.productName }); }); if (typeof item.conf.startTime !== 'undefined' && typeof item.conf.endTime !== 'undefined') { this.player_.on(PLAYER_EVENT.TIME_UPDATE, () => { const time = this.player_.currentTime(); if (time >= item.conf.startTime && time < item.conf.endTime) { shoppablePanelItem.el_.classList.add('active'); throttledScrollToActiveItem(); } else if (shoppablePanelItem.el_.classList.contains('active')) { shoppablePanelItem.el_.classList.remove('active'); } }); } this.addChild(shoppablePanelItem); }); } } _vjs.registerComponent('shoppablePanel', ShoppablePanel); const dom = _vjs.dom || _vjs; class ShoppablePostWidget { constructor(player) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; this.options_ = { ...options, postPlay: true }; this.player_ = player; this.render(); // Handle drag-to-scroll this.handleDragToScroll(); this.dispose = () => { this.layout_.dispose(); }; } handleDragToScroll() { const postModal = this.player_.postModal.el_; const slider = postModal.querySelector(`.${CLD_SPBL_PANEL_CLASS}`); let isDown = false; let startX = 0; let scrollLeft = 0; slider.addEventListener('mousedown', e => { isDown = true; startX = e.pageX - slider.offsetLeft; scrollLeft = slider.scrollLeft; }); document.addEventListener('mouseup', e => { isDown = false; setTimeout(() => { slider.classList.remove('dragged'); }, 300); const x = e.pageX - slider.offsetLeft; const walk = x - startX; if (Math.abs(walk) > 5) { e.preventDefault(); } }); document.addEventListener('mousemove', e => { if (!isDown) { return; } e.preventDefault(); const x = e.pageX - slider.offsetLeft; const walk = x - startX; slider.scrollLeft = scrollLeft - walk; if (Math.abs(walk) > 5 && !slider.classList.contains('dragged')) { slider.classList.add('dragged'); } }); } render() { this.player_.postModal = null; const el = dom.createEl('div', { className: 'cld-spbl-post-play' }); const panel = new ShoppablePanel(this.player_, this.options_); const title = dom.createEl('div', { className: 'cld-spbl-post-title base-color-text' }, {}, this.options_.bannerMsg || 'Shop the Video'); // Background - poster + blur effect const bgSrc = this.player_.cloudinary.currentPoster(); bgSrc.transformation([bgSrc.transformation().toOptions ? bgSrc.transformation().toOptions() : {}, { effect: 'blur:3000' }]); const panelBg = dom.createEl('div', { className: 'cld-spbl-post-play-bg', style: `background-image: url("${bgSrc.url()}")` }); const replayBtn = dom.createEl('button', { className: 'cld-spbl-replay-btn base-color-bg vjs-icon-replay', onclick: () => { this.player_.trigger('replay'); this.player_.postModal.close(); this.player_.play(); } }, {}, 'Replay'); el.appendChild(panelBg); el.appendChild(title); el.appendChild(panel.el()); el.appendChild(replayBtn); this.player_.postModal = this.player_.createModal(el, { name: 'postModal', uncloseable: true }); this.player_.addClass('cld-spbl-post-modal'); this.player_.postModal.on('beforemodalclose', () => { this.player_.removeClass('cld-spbl-post-modal'); }); } } export { CLD_SPBL_INNER_BAR as C, SHOPPABLE_WIDGET_OPTIONS_DEFAULTS as S, ShoppablePostWidget as a, SHOPPABLE_PANEL_VISIBLE_CLASS as b, CLD_SPBL_TOGGLE_CLASS as c, CLD_SPBL_PANEL_CLASS as d, ShoppableBarLayout as e };