react-sequencer
Version:
Step based sequencer to give your components reliable states for transitions and animations.
206 lines • 7.75 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var types_1 = require("./types");
var ticker_1 = __importDefault(require("./ticker"));
var ticker = new ticker_1.default();
var Sequencer = /** @class */ (function () {
function Sequencer(props) {
var _this = this;
this.onLoop = function (now) {
if (_this.status !== types_1.PlayStatus.PLAYING) {
return;
}
_this.currentTimeIn = now - _this.startedAt;
var currentStep = _this.getCurrentStep();
var completesAt = currentStep.endPos;
if (_this.currentTimeIn >= completesAt) {
if (_this.currentStepIndex === _this.steps.length - 1) {
if (_this.endMode === 'start') {
_this.stop();
return;
}
if (_this.endMode === 'end') {
_this.complete();
return;
}
if (_this.endMode === 'loop') {
_this.startedAt = now;
_this.goToStepByIndex(0);
_this.notifyChange();
}
}
else {
_this.currentStepIndex++;
_this.notifyChange();
}
}
};
this.onChange = function (fn) {
_this.subscriptions.push(fn);
return function () {
var index = _this.subscriptions.findIndex(function (f) { return f === fn; });
if (index !== -1) {
_this.subscriptions.splice(index, 1);
}
};
};
this.play = function () {
if (_this.status === types_1.PlayStatus.PLAYING) {
return;
}
if (_this.isComplete()) {
_this.goToStepByIndex(0);
}
_this.status = types_1.PlayStatus.PLAYING;
ticker.onTick(_this.onLoop);
_this.startedAt = ticker.currentTimeStamp - _this.currentTimeIn;
_this.notifyChange();
};
this.pause = function () {
if (_this.status === types_1.PlayStatus.PLAYING) {
_this.status = types_1.PlayStatus.IDLE;
ticker.offTick(_this.onLoop);
_this.notifyChange();
}
};
this.stop = function () {
_this.goToStepByIndex(0);
_this.status = types_1.PlayStatus.IDLE;
ticker.offTick(_this.onLoop);
_this.notifyChange();
};
this.complete = function () {
_this.currentStepIndex = _this.steps.length - 1;
_this.currentTimeIn = _this.totalDuration;
_this.status = types_1.PlayStatus.IDLE;
ticker.offTick(_this.onLoop);
_this.notifyChange();
};
this.isStopped = function () {
return _this.currentTimeIn === 0 && _this.status === types_1.PlayStatus.IDLE;
};
this.isComplete = function () {
return _this.currentTimeIn >= _this.totalDuration;
};
this.isPlaying = function () {
return _this.status === types_1.PlayStatus.PLAYING;
};
this.isBefore = function (stepName) {
var stepIndex = _this.steps.findIndex(function (step) { return step.name === stepName; });
if (stepIndex === -1) {
throw new Error("Sequencer step " + stepName + " not found.");
}
return _this.currentStepIndex < stepIndex;
};
this.isAfter = function (stepName) {
var stepIndex = _this.steps.findIndex(function (step) { return step.name === stepName; });
if (stepIndex === -1) {
throw new Error("Sequencer step " + stepName + " not found.");
}
return _this.currentStepIndex > stepIndex;
};
this.getState = function () {
var state = {
current: _this.steps[_this.currentStepIndex].name,
index: _this.currentStepIndex,
isPlaying: _this.isPlaying(),
isComplete: _this.isComplete(),
isStopped: _this.isStopped(),
};
return state;
};
this.getApi = function () {
var _a = _this, play = _a.play, pause = _a.pause, stop = _a.stop, complete = _a.complete, isBefore = _a.isBefore, isAfter = _a.isAfter;
var api = {
play: play,
pause: pause,
stop: stop,
complete: complete,
isBefore: isBefore,
isAfter: isAfter,
};
return api;
};
var defaults = {
steps: [],
loop: false,
complete: false,
endMode: 'end',
autoplay: false,
};
var options = __assign(__assign({}, defaults), props);
if (options.steps.length === 0) {
throw new Error('React Sequencer: At least one step required in options');
}
this.steps = this.generateSteps(options.steps);
this.totalDuration = this.steps[this.steps.length - 1].endPos;
this.currentStepIndex = 0;
this.currentTimeIn = 0;
this.startedAt = 0;
this.endMode = options.loop ? 'loop' : options.endMode;
this.status = types_1.PlayStatus.IDLE;
this.subscriptions = [];
if (options.complete === true) {
this.goToStepByIndex(this.steps.length - 1);
}
if (options.autoplay === true) {
this.play();
}
this.prevState = this.getState();
}
Sequencer.prototype.generateSteps = function (stepsInput) {
if (!stepsInput) {
throw new Error('Invalid format.');
}
var prev = 0;
var steps = stepsInput.map(function (step) {
if (!Array.isArray(step) || step.length !== 2) {
throw new Error('Invalid format. See docs for correct structure.');
}
var startPos = prev;
var endPos = step[1] + prev;
prev = endPos;
return {
startPos: startPos,
endPos: endPos,
name: step[0],
};
});
return steps;
};
Sequencer.prototype.notifyChange = function () {
var state = this.getState();
if (!this.prevState ||
state.index !== this.prevState.index ||
state.isPlaying !== this.prevState.isPlaying) {
this.subscriptions.forEach(function (fn) {
fn(state);
});
}
this.prevState = state;
};
Sequencer.prototype.goToStepByIndex = function (index) {
this.currentStepIndex = index;
this.currentTimeIn = this.steps[index].startPos;
};
Sequencer.prototype.getCurrentStep = function () {
return this.steps[this.currentStepIndex];
};
return Sequencer;
}());
exports.default = Sequencer;
//# sourceMappingURL=sequencer.js.map