wilderness-core
Version:
The SVG animation engine behind Wilderness
518 lines (449 loc) • 14.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.validEventName = exports.unsubscribe = exports.timeToSamePosition = exports.timeToPosition = exports.subscribe = exports.positionTimestamps = exports.playbackOptionsChanged = exports.oldest = exports.eventQueue = exports.event = exports.activeEventNames = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /* globals __DEV__ */
var _timeline = require('./timeline');
/**
* An event.
*
* @typedef {Object} Event
*
* @property {number} at - The time the event occured.
* @property {string} name - The event name.
* @property {Object} options - Any additional event data.
*/
/**
* A Timeline event subscription.
*
* @typedef {Object} EventSubscription
*
* @property {function} callback
* @property {string} name
* @property {number} token
*/
/**
* An object to hold Timeline EventSubscriptions, and subscribe/unsubscribe functions.
*
* @typedef {Object} EventObject
*
* @property {Object} previousPlaybackOptions
* @property {Object} previousState
* @property {function} subscribe - A function to subscribe to Timeline events.
* @property {EventSubscription[]} subscriptions
* @property {function} unsubscribe - A function to unsubscribe to Timeline events.
*/
/**
* Token incrementor.
*/
var t = 0;
/**
* Accepted event names.
*/
var acceptedEventNames = ['timeline.start', 'timeline.finish', 'shape.start', 'shape.finish', 'keyframe', 'frame'];
/**
* An EventObject creator.
*
* @param {Timeline} timeline
*
* @returns {EventObject}
*
* @example
* event(timeline)
*/
var event = function event(timeline) {
return {
previousPlaybackOptions: {},
previousState: {},
subscribe: subscribe(timeline),
subscriptions: [],
unsubscribe: unsubscribe(timeline)
};
};
/**
* Is a Timeline active?
*
* @param {Timeline} timeline
*
* @returns {boolean}
*
* @example
* active(timeline)
*/
var active = function active(_ref) {
var event = _ref.event,
state = _ref.state;
return state.started && (!state.finished || typeof event.previousState === 'undefined' || !event.previousState.finished);
};
/**
* A unique list of Timeline EventSubscription names.
*
* @param {Timeline} timeline
*
* @returns {string[]}
*
* @example
* activeEventNames(timeline)
*/
var activeEventNames = function activeEventNames(_ref2) {
var subscriptions = _ref2.event.subscriptions;
var s = [];
for (var i = 0, l = subscriptions.length; i < l; i++) {
var name = subscriptions[i].name;
if (s.indexOf(name) === -1) {
s.push(name);
}
}
return s;
};
/**
* Run EventSubscription callbacks for every event that has occured since last check.
*
* @param {Timeline} timeline
*
* @example
* events(timeline)
*/
var events = function events(timeline) {
if (playbackOptionsChanged(timeline)) {
timeline.event.previousPlaybackOptions = {};
timeline.event.previousState = {};
}
var subscriptions = timeline.event.subscriptions;
if (subscriptions.length && active(timeline)) {
var eventNames = activeEventNames(timeline);
var queue = eventQueue(timeline, eventNames);
for (var i = 0, l = queue.length; i < l; i++) {
var _event = queue[i];
var eventName = _event.name;
var options = _event.options || {};
for (var _i = 0, _l = subscriptions.length; _i < _l; _i++) {
var subscription = subscriptions[_i];
if (eventName === subscription.name) {
subscription.callback(options);
}
}
}
}
timeline.event.previousPlaybackOptions = _extends({}, timeline.playbackOptions);
timeline.event.previousState = _extends({}, timeline.state);
};
/**
* An array of Events that have occured since last checked.
*
* @param {Timeline} timeline
* @param {string[]} eventNames
*
* @returns {Event[]}
*
* @example
* eventQueue(timeline, eventNames)
*/
var eventQueue = function eventQueue(_ref3, eventNames) {
var previousState = _ref3.event.previousState,
playbackOptions = _ref3.playbackOptions,
state = _ref3.state,
timelineShapes = _ref3.timelineShapes;
var queue = [];
var alternate = playbackOptions.alternate,
duration = playbackOptions.duration,
initialIterations = playbackOptions.initialIterations,
iterations = playbackOptions.iterations,
reverse = playbackOptions.reverse,
started = playbackOptions.started;
var max = started + duration * state.iterationsComplete;
var min = typeof previousState.iterationsComplete !== 'undefined' ? started + duration * previousState.iterationsComplete + 1 : 0;
var getTimestamps = function getTimestamps(pos) {
return positionTimestamps({
alternate: alternate,
duration: duration,
initialIterations: initialIterations,
iterations: iterations,
max: max,
min: min,
position: pos,
reverse: reverse,
started: started
});
};
if (eventNames.indexOf('timeline.start') !== -1) {
var timestamps = getTimestamps(0);
for (var i = 0, l = timestamps.length; i < l; i++) {
queue.push({ name: 'timeline.start', at: timestamps[i] });
}
}
if (eventNames.indexOf('timeline.finish') !== -1) {
var _timestamps = getTimestamps(1);
for (var _i2 = 0, _l2 = _timestamps.length; _i2 < _l2; _i2++) {
queue.push({ name: 'timeline.finish', at: _timestamps[_i2] });
}
}
if (eventNames.indexOf('shape.start') !== -1) {
for (var _i3 = 0, _l3 = timelineShapes.length; _i3 < _l3; _i3++) {
var _timelineShapes$_i = timelineShapes[_i3],
shapeName = _timelineShapes$_i.shape.name,
start = _timelineShapes$_i.timelinePosition.start;
var _timestamps2 = getTimestamps(start);
for (var _i = 0, _l = _timestamps2.length; _i < _l; _i++) {
queue.push({ name: 'shape.start', at: _timestamps2[_i], options: { shapeName: shapeName } });
}
}
}
if (eventNames.indexOf('shape.finish') !== -1) {
for (var _i4 = 0, _l4 = timelineShapes.length; _i4 < _l4; _i4++) {
var _timelineShapes$_i2 = timelineShapes[_i4],
shapeName = _timelineShapes$_i2.shape.name,
finish = _timelineShapes$_i2.timelinePosition.finish;
var _timestamps3 = getTimestamps(finish);
for (var _i5 = 0, _l5 = _timestamps3.length; _i5 < _l5; _i5++) {
queue.push({ name: 'shape.finish', at: _timestamps3[_i5], options: { shapeName: shapeName } });
}
}
}
if (eventNames.indexOf('keyframe') !== -1) {
for (var _i6 = 0, _l6 = timelineShapes.length; _i6 < _l6; _i6++) {
var _timelineShapes$_i3 = timelineShapes[_i6],
_timelineShapes$_i3$s = _timelineShapes$_i3.shape,
shapeName = _timelineShapes$_i3$s.name,
keyframes = _timelineShapes$_i3$s.keyframes,
_timelineShapes$_i3$t = _timelineShapes$_i3.timelinePosition,
start = _timelineShapes$_i3$t.start,
finish = _timelineShapes$_i3$t.finish;
for (var _i7 = 0, _l7 = keyframes.length; _i7 < _l7; _i7++) {
var _keyframes$_i = keyframes[_i7],
keyframeName = _keyframes$_i.name,
position = _keyframes$_i.position;
var keyframePosition = start + (finish - start) * position;
var _timestamps4 = getTimestamps(keyframePosition);
for (var __i = 0, __l = _timestamps4.length; __i < __l; __i++) {
queue.push({ name: 'keyframe', at: _timestamps4[__i], options: { keyframeName: keyframeName, shapeName: shapeName } });
}
}
}
}
if (eventNames.indexOf('frame') !== -1) {
queue.push({ name: 'frame', at: max });
}
return queue.sort(oldest);
};
/**
* A sort function for Events.
*
* @param {Event} a
* @param {Event} b
*
* @returns {number}
*
* @example
* oldest(event1, event2)
*/
var oldest = function oldest(a, b) {
return a.at === b.at ? 0 : a.at < b.at ? -1 : 1;
};
/**
* Have playbackOptions changed since last check?
*
* @param {Timeline} timeline
*
* @return {boolean}
*
* @example
* playbackOptionsChanged(timeline)
*/
var playbackOptionsChanged = function playbackOptionsChanged(timeline) {
return JSON.stringify(timeline.playbackOptions) !== JSON.stringify(timeline.event.previousPlaybackOptions);
};
/**
* Timestamps at which a Timeline was at a Position.
*
* @param {Object} opts
* @param {boolean} opts.alternate
* @param {number} opts.duration
* @param {number} initialIterations
* @param {number} iterations
* @param {number} opts.max - The maximum bound within which to look for timestamps.
* @param {number} opts.min - The minimum bound within which to look for timestamps.
* @param {Position} opts.position - The Position in question.
* @param {boolean} opts.reverse
* @param {number} opts.started
*
* @returns {number[]}
*
* @example
* positionTimestamps(opts)
*/
var positionTimestamps = function positionTimestamps(_ref4) {
var alternate = _ref4.alternate,
duration = _ref4.duration,
initialIterations = _ref4.initialIterations,
iterations = _ref4.iterations,
max = _ref4.max,
min = _ref4.min,
position = _ref4.position,
reverse = _ref4.reverse,
started = _ref4.started;
var startedPosition = (0, _timeline.position)(initialIterations, reverse);
var finishedTimestamp = started + duration * iterations;
var timestamps = function timestamps(timestamp) {
if (timestamp <= max) {
var timestampReverse = (0, _timeline.currentReverse)({
alternate: alternate,
initialIterations: initialIterations,
iterations: iterations,
reverse: reverse
}, (0, _timeline.iterationsComplete)({ duration: duration, iterations: iterations, started: started }, timestamp));
var positionAtEnd = position === 0 || position === 1;
var timelineFinished = timestamp === finishedTimestamp;
var finishedAtPosition = position === 0 && timestampReverse || position === 1 && !timestampReverse;
if (timestamp <= finishedTimestamp && (!positionAtEnd || !timelineFinished || finishedAtPosition)) {
var _t = timestamp >= min ? [timestamp] : [];
return _t.concat(timestamps(timestamp + timeToSamePosition({
alternate: alternate,
duration: duration,
position: position,
reverse: timestampReverse
})));
}
}
return [];
};
return timestamps(started + timeToPosition({
alternate: alternate,
duration: duration,
from: startedPosition,
reverse: reverse,
to: position
}));
};
/**
* The number of milliseconds between two Positions during Timeline playback.
*
* @param {Object} opts
* @param {boolean} opts.alternate
* @param {number} opts.duration
* @param {Position} opts.from - The from Position.
* @param {boolean} opts.reverse - Is Timeline in reverse at the from Position?
* @param {Position} opts.to - The to Position.
*
* @returns {number}
*
* @example
* timeToPosition(opts)
*/
var timeToPosition = function timeToPosition(_ref5) {
var alternate = _ref5.alternate,
duration = _ref5.duration,
from = _ref5.from,
reverse = _ref5.reverse,
to = _ref5.to;
return duration * (alternate ? reverse ? from < to ? to + from : from - to : from > to ? 2 - (to + from) : to - from : reverse ? from === 1 && to === 0 ? 1 : (1 - to + from) % 1 : from === 0 && to === 1 ? 1 : (1 - from + to) % 1);
};
/**
* The number of milliseconds between the same Position during Timeline playback.
*
* @param {Object} opts
* @param {boolean} opts.alternate
* @param {number} opts.duration
* @param {Position} opts.position
* @param {boolean} opts.reverse - Is Timeline in reverse at the Position?
*
* @returns {number}
*
* @example
* timeToSamePosition(opts)
*/
var timeToSamePosition = function timeToSamePosition(_ref6) {
var alternate = _ref6.alternate,
duration = _ref6.duration,
position = _ref6.position,
reverse = _ref6.reverse;
return duration * (alternate ? reverse ? (position === 0 ? 1 : position) * 2 : 2 - (position === 1 ? 0 : position) * 2 : 1);
};
/**
* Creates a subscribe function.
* The created function adds an EventSubscription to the subscriptions
* property of an EventObject.
*
* @param {Timeline} timeline
*
* @returns {function}
*
* @example
* subscribe(timeline)('timeline.start', () => console.log('timeline.start'))
*/
var subscribe = function subscribe(timeline) {
return function (name, callback) {
if (validEventName(name)) {
if (process.env.NODE_ENV !== 'production' && typeof callback !== 'function') {
throw new TypeError('The subscribe functions second argument must be of type function');
}
var token = ++t;
timeline.event.subscriptions.push({ name: name, callback: callback, token: token });
return token;
}
};
};
/**
* Is an event name valid?
*
* @param {string} name
*
* @throws {TypeError} Throws if not valid
*
* @returns {true}
*
* @example
* validEventName('timeline.start')
*/
var validEventName = function validEventName(name) {
if (process.env.NODE_ENV !== 'production') {
if (typeof name !== 'string') {
throw new TypeError('The subscribe functions first argument must be of type string');
}
if (acceptedEventNames.indexOf(name) === -1) {
throw new TypeError('The subscribe functions first argument was not a valid event name');
}
}
return true;
};
/**
* Creates an unsubscribe function.
* Created function removes an EventSubscription from the subscriptions
* property of an EventObject, given the Event token.
*
* @param {Timeline} timeline
*
* @returns {function}
*
* @example
* unsubscribe(timeline)(token)
*/
var unsubscribe = function unsubscribe(timeline) {
return function (token) {
var subscriptions = timeline.event.subscriptions;
var matchIndex = void 0;
for (var i = 0, l = subscriptions.length; i < l; i++) {
if (subscriptions[i].token === token) {
matchIndex = i;
}
}
if (typeof matchIndex !== 'undefined') {
timeline.event.subscriptions.splice(matchIndex, 1);
return true;
}
return false;
};
};
exports.activeEventNames = activeEventNames;
exports.event = event;
exports.eventQueue = eventQueue;
exports.oldest = oldest;
exports.playbackOptionsChanged = playbackOptionsChanged;
exports.positionTimestamps = positionTimestamps;
exports.subscribe = subscribe;
exports.timeToPosition = timeToPosition;
exports.timeToSamePosition = timeToSamePosition;
exports.unsubscribe = unsubscribe;
exports.validEventName = validEventName;
exports.default = events;