@nent/core
Version:
354 lines (349 loc) • 14.1 kB
JavaScript
/*!
* NENT 2022
*/
import { r as registerInstance, h, a as getElement, H as Host } from './index-916ca544.js';
import { a as actionBus, e as eventBus } from './index-f7016b94.js';
import { f as debugIf, e as error, w as warn } from './logging-5a93c8af.js';
import { a as state$1 } from './state-27a8a5bc.js';
import { R as ROUTE_EVENTS } from './interfaces-3b78db83.js';
import { s as state } from './state-adf07580.js';
import { a as activateActionActivators, s as sendActions } from './elements-4818d39b.js';
import { A as ActionActivationStrategy } from './interfaces-837cdb60.js';
import { A as ANALYTICS_TOPIC, b as ANALYTICS_COMMANDS } from './interfaces-89f61157.js';
import { T as TIMER_EVENTS } from './interfaces-aed4a5ac.js';
import './index-4bfabbbd.js';
/* istanbul ignore file */
/**
* It takes a root element and a default duration, and returns an array of objects that describe the
* elements that have `n-in-time` and `n-out-time` attributes
* @param {HTMLElement} rootElement - The root element to search for timed nodes.
* @param {number} defaultDuration - The default duration of the animation.
* @returns An array of TimedNode objects.
*/
function captureElementChildTimedNodes(rootElement, defaultDuration) {
var _a;
const timedNodes = [];
(_a = rootElement
.querySelectorAll('[n-in-time], [n-out-time]')) === null || _a === void 0 ? void 0 : _a.forEach(element => {
const startAttribute = element.getAttribute('n-in-time');
const start = startAttribute
? Number.parseFloat(startAttribute)
: 0;
const endAttribute = element.getAttribute('n-out-time');
const end = endAttribute
? Number.parseFloat(endAttribute)
: defaultDuration;
timedNodes.push({
start,
end,
classIn: element.getAttribute('n-in-class'),
classOut: element.getAttribute('n-out-class'),
element,
});
});
return timedNodes;
}
/**
* It resolves the `n-time-to` and `n-percentage-to` attributes, and it resolves the `n-time-in` and
* `n-time-out` attributes
* @param {HTMLElement} rootElement - The root element of the component.
* @param {TimedNode[]} timedNodes - An array of TimedNode objects, which are defined as:
* @param {number} elapsedSeconds - The number of seconds that have elapsed since the start of the
* video.
* @param {number} percentage - The percentage of the video that has elapsed.
*/
function resolveElementChildTimedNodesByTime(rootElement, timedNodes, elapsedSeconds, percentage) {
timedNodes === null || timedNodes === void 0 ? void 0 : timedNodes.forEach(node => {
if (node.start > -1 &&
elapsedSeconds >= node.start &&
(node.end > -1 ? elapsedSeconds < node.end : true)) {
// Time is after start and before end, if it exists
if (node.classIn &&
!node.element.classList.contains(node.classIn)) {
node.element.classList.add(node.classIn);
}
if (node.element.hasAttribute('hidden')) {
// Otherwise, if there's a hidden attribute, remove it
node.element.removeAttribute('hidden');
}
}
if (node.end > -1 && elapsedSeconds >= node.end) {
// Time is after end, if it exists
if (node.classIn &&
node.element.classList.contains(node.classIn)) {
// Remove the in class, if it exists
node.element.classList.remove(node.classIn);
}
if (node.classOut) {
// If a class-out was specified and isn't on the element, add it
if (!node.element.classList.contains(node.classOut)) {
node.element.classList.add(node.classOut);
}
}
else if (!node.element.hasAttribute('hidden')) {
// Otherwise, if there's no hidden attribute, add it
node.element.setAttribute('hidden', '');
}
}
});
// Resolve n-time-to
const timeValueElements = rootElement.querySelectorAll('[n-time-to]');
timeValueElements === null || timeValueElements === void 0 ? void 0 : timeValueElements.forEach(el => {
const seconds = elapsedSeconds;
const attributeName = el.getAttribute('n-time-to');
if (attributeName) {
el.setAttribute(attributeName, seconds.toString());
}
else {
el.childNodes.forEach(cn => cn.remove());
el.append(document.createTextNode(seconds.toString()));
}
});
// Resolve n-percentage-to
const timePercentageValueElements = rootElement.querySelectorAll('[n-percentage-to]');
timePercentageValueElements === null || timePercentageValueElements === void 0 ? void 0 : timePercentageValueElements.forEach(element => {
const attributeName = element.getAttribute('n-percentage-to');
if (attributeName) {
element.setAttribute(attributeName, percentage.toFixed(2));
}
else {
element.childNodes.forEach(cn => cn.remove());
element.append(document.createTextNode(`${Math.round(percentage * 100)}%`));
}
});
}
/**
* It removes the `classIn` and `classOut` classes from the `timedNodes` and resets the `n-time-to` and
* `n-percentage-to` attributes to their initial values
* @param {HTMLElement} rootElement - The root element of the component.
* @param {TimedNode[]} timedNodes - This is an array of TimedNode objects.
*/
function restoreElementChildTimedNodes(rootElement, timedNodes) {
timedNodes === null || timedNodes === void 0 ? void 0 : timedNodes.forEach(node => {
if (node.classIn &&
node.element.classList.contains(node.classIn)) {
node.element.classList.remove(node.classIn);
}
if (node.classOut &&
node.element.classList.contains(node.classOut)) {
node.element.classList.remove(node.classOut);
}
});
// Resolve n-time-to
const timeValueElements = rootElement.querySelectorAll('[n-time-to]');
timeValueElements === null || timeValueElements === void 0 ? void 0 : timeValueElements.forEach(el => {
const attributeName = el.getAttribute('n-time-to');
if (attributeName) {
el.setAttribute(attributeName, '0');
}
else {
el.childNodes.forEach(cn => cn.remove());
el.append(document.createTextNode('0'));
}
});
// Resolve n-percentage-to
const timePercentageValueElements = rootElement.querySelectorAll('[n-percentage-to]');
timePercentageValueElements === null || timePercentageValueElements === void 0 ? void 0 : timePercentageValueElements.forEach(el => {
const attributeName = el.getAttribute('n-percentage-to');
if (attributeName) {
el.setAttribute(attributeName, '0');
}
else {
el.childNodes.forEach(cn => cn.remove());
el.append(document.createTextNode('100%'));
}
});
}
/* It subscribes to the timer's `OnInterval` and `OnEnd` events, and when those events are emitted, it
activates any `n-action-activator` elements that are configured to activate at that time, and it
also sends any `n-presentation-action` elements that are configured to send at that time */
class PresentationService {
/**
* > This function creates a new instance of the Presentation class
* @param {HTMLElement} el - HTMLElement - the element that will be the root of the presentation
* @param {ITimer} timeEmitter - ITimer
* @param {boolean} [elements=false] - boolean = false
* @param {string | null} [analyticsEvent=null] - string | null = null
* @param {(() => void) | null} [onEnd=null] - (() => void) | null = null,
* @param {boolean} [debug=false] - boolean = false,
*/
constructor(el, timeEmitter, elements = false, analyticsEvent = null, onEnd = null, debug = false) {
this.el = el;
this.timeEmitter = timeEmitter;
this.elements = elements;
this.analyticsEvent = analyticsEvent;
this.onEnd = onEnd;
this.debug = debug;
this.timedNodes = [];
this.activatedActions = [];
if (this.elements) {
this.timedNodes = captureElementChildTimedNodes(this.el, this.timeEmitter.durationSeconds);
debugIf(this.debug, `presentation: found ${this.timedNodes.length} timed-child elements`);
}
debugIf(this.debug, `presentation: service created`);
}
get actionActivators() {
return Array.from(this.el.querySelectorAll('n-action-activator'));
}
get actions() {
return Array.from(this.el.querySelectorAll('n-presentation-action')).map(a => a);
}
async handleInterval(time) {
if (this.elements) {
resolveElementChildTimedNodesByTime(this.el, this.timedNodes, time.elapsedSeconds, time.percentage);
}
if (this.analyticsEvent) {
const data = {
event: this.analyticsEvent,
time,
};
actionBus.emit(ANALYTICS_TOPIC, {
topic: ANALYTICS_TOPIC,
command: ANALYTICS_COMMANDS.SendViewTime,
data,
});
}
await activateActionActivators(this.actionActivators, ActionActivationStrategy.AtTime, activator => {
if (this.activatedActions.includes(activator))
return false;
if (activator.time && time.elapsedSeconds >= activator.time) {
this.activatedActions.push(activator);
return true;
}
return false;
});
await sendActions(this.actions, action => {
return action.time && time.elapsedSeconds >= action.time;
});
}
async handleEnded(time) {
var _a;
if (this.elements) {
resolveElementChildTimedNodesByTime(this.el, this.timedNodes, time.elapsedSeconds, time.percentage);
}
await activateActionActivators(this.actionActivators, ActionActivationStrategy.AtTimeEnd);
await sendActions(this.actions, action => {
return action.time == 'end';
});
(_a = this.onEnd) === null || _a === void 0 ? void 0 : _a.call(this);
}
/**
* > The function subscribes to the `timeEmitter` and listens for the `OnInterval` and `OnEnd` events
*/
subscribe() {
this.intervalSubscription = this.timeEmitter.on(TIMER_EVENTS.OnInterval, (time) => {
this.handleInterval(time).catch(e => error(e));
});
this.endSubscription = this.timeEmitter.on(TIMER_EVENTS.OnEnd, (time) => {
debugIf(this.debug, `presentation: ended`);
this.handleEnded(time).catch(e => error(e));
});
}
/**
* It unsubscribes from the interval and end subscriptions.
*/
unsubscribe() {
var _a, _b;
if (this.elements) {
restoreElementChildTimedNodes(this.el, this.timedNodes);
}
(_a = this.intervalSubscription) === null || _a === void 0 ? void 0 : _a.call(this);
(_b = this.endSubscription) === null || _b === void 0 ? void 0 : _b.call(this);
}
}
const Presentation = class {
constructor(hostRef) {
registerInstance(this, hostRef);
this.elementWithTimer = null;
this.timer = null;
/**
* The element selector for the timer-element to
* bind for interval events. If left blank, it looks
* first an n-timer, then for the first n-video.
*
* If none are found, it creates one manually and starts
* it immediately
*/
this.timerElement = null;
/**
* To debug timed elements, set this value to true.
*/
this.debug = false;
/**
* Go to the next view after the timer ends
*/
this.nextAfter = false;
}
get currentRoute() {
var _a;
const parent = this.el.closest('n-view-prompt') || this.el.closest('n-view');
if (parent)
return parent.route;
return ((_a = state.router) === null || _a === void 0 ? void 0 : _a.exactRoute) || null;
}
componentWillLoad() {
debugIf(this.debug, `n-presentation: loading`);
let element = this.timerElement
? this.el.querySelector(this.timerElement) ||
this.el.ownerDocument.querySelector(this.timerElement) ||
null
: this.el.querySelector('n-presentation-timer') ||
this.el.ownerDocument.querySelector('n-presentation-timer') ||
this.el.querySelector('n-video') ||
this.el.ownerDocument.querySelector('n-video') ||
null;
this.elementWithTimer =
element || null;
if (this.elementWithTimer == null) {
warn(`n-presentation: no timer element found`);
return;
}
else {
this.elementWithTimer.addEventListener('ready', () => {
var _a, _b, _c, _d;
debugIf(this.debug, `n-presentation: element ready`);
this.timer = this.elementWithTimer.timer;
this.presentation = new PresentationService(this.el, this.timer, state$1.elementsEnabled, this.analyticsEvent, () => {
var _a;
if (this.currentRoute && this.nextAfter) {
(_a = this.presentation) === null || _a === void 0 ? void 0 : _a.unsubscribe();
if (typeof this.nextAfter == 'string') {
this.currentRoute.router.goToRoute(this.nextAfter);
}
else {
this.currentRoute.router.goNext();
}
}
}, this.debug);
if (this.currentRoute) {
debugIf(this.debug, `n-presentation: syncing to route ${this.currentRoute.path}`);
if ((_b = (_a = this.currentRoute) === null || _a === void 0 ? void 0 : _a.match) === null || _b === void 0 ? void 0 : _b.isExact) {
(_c = this.presentation) === null || _c === void 0 ? void 0 : _c.subscribe();
}
this.navigationSubscription = eventBus.on(ROUTE_EVENTS.RouteChanged, () => {
var _a, _b, _c, _d;
if ((_b = (_a = this.currentRoute) === null || _a === void 0 ? void 0 : _a.match) === null || _b === void 0 ? void 0 : _b.isExact) {
(_c = this.presentation) === null || _c === void 0 ? void 0 : _c.subscribe();
}
else {
(_d = this.presentation) === null || _d === void 0 ? void 0 : _d.unsubscribe();
}
});
}
else {
(_d = this.presentation) === null || _d === void 0 ? void 0 : _d.subscribe();
}
});
}
}
render() {
return h(Host, null);
}
disconnectedCallback() {
var _a, _b;
(_a = this.presentation) === null || _a === void 0 ? void 0 : _a.unsubscribe();
(_b = this.navigationSubscription) === null || _b === void 0 ? void 0 : _b.call(this);
}
get el() { return getElement(this); }
};
export { Presentation as n_presentation };