johnny-five
Advanced tools
+109
| const SI = 1.70158; | ||
| const SIO = 1.70158 * 1.525; | ||
| const SB = 7.5625; | ||
| const HALF = 0.5; | ||
| const { | ||
| PI, | ||
| cos, | ||
| sin, | ||
| sqrt, | ||
| } = Math; | ||
| const ease = {}; | ||
| ease.linear = n => n; | ||
| ease.inQuad = n => n ** 2; | ||
| ease.outQuad = n => n * (2 - n); | ||
| ease.inOutQuad = n => { | ||
| n *= 2; | ||
| return n < 1 ? | ||
| HALF * n * n : | ||
| -HALF * (--n * (n - 2) - 1); | ||
| }; | ||
| ease.inCube = n => n ** 3; | ||
| ease.outCube = n => --n * n * n + 1; | ||
| ease.inOutCube = n => { | ||
| n *= 2; | ||
| return n < 1 ? | ||
| HALF * n ** 3 : | ||
| HALF * ((n -= 2) * n * n + 2); | ||
| }; | ||
| ease.inQuart = n => n ** 4; | ||
| ease.outQuart = n => 1 - (--n * n ** 3); | ||
| ease.inOutQuart = n => { | ||
| n *= 2; | ||
| return n < 1 ? | ||
| HALF * n ** 4 : | ||
| -HALF * ((n -= 2) * n ** 3 - 2); | ||
| }; | ||
| ease.inQuint = n => n ** 5; | ||
| ease.outQuint = n => --n * n ** 4 + 1; | ||
| ease.inOutQuint = n => { | ||
| n *= 2; | ||
| return n < 1 ? | ||
| HALF * n ** 5 : | ||
| HALF * ((n -= 2) * n ** 4 + 2); | ||
| }; | ||
| ease.inSine = n => 1 - cos(n * PI / 2); | ||
| ease.outSine = n => sin(n * PI / 2); | ||
| ease.inOutSine = n => HALF * (1 - cos(PI * n)); | ||
| ease.inExpo = n => 0 === n ? 0 : 1024 ** (n - 1); | ||
| ease.outExpo = n => 1 === n ? n : 1 - 2 ** (-10 * n); | ||
| ease.inOutExpo = n => { | ||
| if (n === 0) { return 0; } | ||
| if (n === 1) { return 1; } | ||
| return (n *= 2) < 1 ? | ||
| HALF * (1024 ** (n - 1)) : | ||
| HALF * (-(2 ** (-10 * (n - 1))) + 2); | ||
| }; | ||
| ease.inCirc = n => 1 - sqrt(1 - n * n); | ||
| ease.outCirc = n => sqrt(1 - (--n * n)); | ||
| ease.inOutCirc = n => { | ||
| n *= 2; | ||
| return (n < 1) ? | ||
| -HALF * (sqrt(1 - n * n) - 1) : | ||
| HALF * (sqrt(1 - (n -= 2) * n) + 1); | ||
| }; | ||
| ease.inBack = n => n * n * ((SI + 1) * n - SI); | ||
| ease.outBack = n => --n * n * ((SI + 1) * n + SI) + 1; | ||
| ease.inOutBack = n => { | ||
| return (n *= 2) < 1 ? | ||
| HALF * (n * n * ((SIO + 1) * n - SIO)) : | ||
| HALF * ((n -= 2) * n * ((SIO + 1) * n + SIO) + 2); | ||
| }; | ||
| ease.outBounce = n => { | ||
| if (n < (1 / 2.75)) { | ||
| return SB * n * n; | ||
| } else if (n < (2 / 2.75)) { | ||
| return SB * (n -= (1.5 / 2.75)) * n + 0.75; | ||
| } else if (n < (2.5 / 2.75)) { | ||
| return SB * (n -= (2.25 / 2.75)) * n + 0.9375; | ||
| } else { | ||
| return SB * (n -= (2.625 / 2.75)) * n + 0.984375; | ||
| } | ||
| }; | ||
| ease.inBounce = n => 1 - ease.outBounce(1 - n); | ||
| ease.inOutBounce = n => { | ||
| return n < HALF ? | ||
| ease.inBounce(n * 2) * HALF : | ||
| ease.outBounce(n * 2 - 1) * HALF + HALF; | ||
| }; | ||
| const exportables = {}; | ||
| Object.keys(ease).forEach(key => { | ||
| exportables[key.toLowerCase()] = ease[key]; | ||
| }); | ||
| module.exports = new Proxy({}, { | ||
| get(target, property) { | ||
| return exportables[property.replace(/([a-z])([A-Z])/g, "$1$2").toLowerCase()]; | ||
| } | ||
| }); |
+383
-404
@@ -0,14 +1,11 @@ | ||
| const Emitter = require("events"); | ||
| const ease = require("./easing"); | ||
| const { cloneDeep, constrain } = require("./fn"); | ||
| let temporal; | ||
| // TODO list | ||
| // Use functions as keyFrames | ||
| // Test metronomic on real animation | ||
| // Create jquery FX like queue | ||
| var Emitter = require("events").EventEmitter; | ||
| var util = require("util"); | ||
| var ease = require("ease-component"); | ||
| var Fn = require("./fn"); | ||
| var temporal; | ||
| /** | ||
@@ -20,30 +17,5 @@ * The max time we want to allow a temporal animation segment to run. | ||
| **/ | ||
| var temporalTTL = 5000; | ||
| let temporalTTL = 5000; | ||
| /** | ||
| * Placeholders for Symbol | ||
| */ | ||
| Animation.keys = "@@keys"; | ||
| Animation.normalize = "@@normalize"; | ||
| Animation.render = "@@render"; | ||
| /** | ||
| * Temporal will run up the CPU. temporalFallback is used | ||
| * for long running animations. | ||
| */ | ||
| Animation.TemporalFallback = function(animation) { | ||
| this.interval = setInterval(function() { | ||
| animation.loopFunction({ | ||
| calledAt: Date.now() | ||
| }); | ||
| }, animation.rate); | ||
| }; | ||
| Animation.TemporalFallback.prototype.stop = function() { | ||
| if (this.interval) { | ||
| clearInterval(this.interval); | ||
| } | ||
| }; | ||
| /** | ||
| * Animation | ||
@@ -82,490 +54,497 @@ * @constructor | ||
| function Animation(target) { | ||
| class Animation extends Emitter { | ||
| constructor(target) { | ||
| super(); | ||
| // Necessary to avoid loading temporal unless necessary | ||
| if (!temporal) { | ||
| temporal = require("temporal"); | ||
| } | ||
| // Necessary to avoid loading temporal unless necessary | ||
| if (!temporal) { | ||
| temporal = require("temporal"); | ||
| } | ||
| if (!(this instanceof Animation)) { | ||
| return new Animation(target); | ||
| Object.assign(this, new Animation.Segment()); | ||
| this.defaultTarget = target || {}; | ||
| } | ||
| /** | ||
| * Add an animation segment to the animation queue | ||
| * @param {Object} options Options: cuePoints, keyFrames, duration, | ||
| * easing, loop, metronomic, progress, fps, onstart, onpause, | ||
| * onstop, oncomplete, onloop | ||
| */ | ||
| enqueue(options = {}) { | ||
| /* istanbul ignore else */ | ||
| if (typeof options.target === "undefined") { | ||
| options.target = this.defaultTarget; | ||
| } | ||
| Animation.Segment.call(this); | ||
| this.segments.push(options); | ||
| this.defaultTarget = target || {}; | ||
| } | ||
| /* istanbul ignore if */ | ||
| if (!this.paused && !this.isRunning) { | ||
| this.next(); | ||
| } | ||
| util.inherits(Animation, Emitter); | ||
| return this; | ||
| } | ||
| /** | ||
| * Animation.Segment() | ||
| * | ||
| * Create a defaulted segment. | ||
| * | ||
| * Every property ever used on an animation segment | ||
| * MUST be listed here, otherwise properties will | ||
| * persist across segments. This default object is | ||
| * primarily for resetting state. | ||
| * | ||
| */ | ||
| Animation.Segment = function(options) { | ||
| this.cuePoints = [0, 1]; | ||
| this.duration = 1000; | ||
| this.easing = "linear"; | ||
| this.loop = false; | ||
| this.loopback = 0; | ||
| this.metronomic = false; | ||
| this.currentSpeed = 1; | ||
| this.progress = 0; | ||
| this.fps = 60; | ||
| this.rate = 1000 / 60; | ||
| this.paused = false; | ||
| this.isRunning = false; | ||
| this.segments = []; | ||
| this.onstart = null; | ||
| this.onpause = null; | ||
| this.onstop = null; | ||
| this.oncomplete = null; | ||
| this.onloop = null; | ||
| /** | ||
| * Plays next segment in queue | ||
| * Users need not call this. It's automatic | ||
| */ | ||
| next() { | ||
| if (options) { | ||
| Object.assign(this, options); | ||
| if (options.segments) { | ||
| this.segments = options.segments.slice(); | ||
| if (this.isRunning) { | ||
| return this; | ||
| } else { | ||
| this.isRunning = true; | ||
| } | ||
| } | ||
| }; | ||
| if (this.segments.length > 0) { | ||
| Object.assign(this, new Animation.Segment(this.segments.shift())); | ||
| this.paused = this.currentSpeed === 0 ? true : false; | ||
| /** | ||
| * Add an animation segment to the animation queue | ||
| * @param {Object} opts Options: cuePoints, keyFrames, duration, | ||
| * easing, loop, metronomic, progress, fps, onstart, onpause, | ||
| * onstop, oncomplete, onloop | ||
| */ | ||
| Animation.prototype.enqueue = function(opts) { | ||
| if (this.onstart) { | ||
| this.onstart(); | ||
| } | ||
| opts = opts || {}; | ||
| this.normalizeKeyframes(); | ||
| /* istanbul ignore else */ | ||
| if (typeof opts.target === "undefined") { | ||
| opts.target = this.defaultTarget; | ||
| } | ||
| if (this.reverse) { | ||
| this.currentSpeed *= -1; | ||
| } | ||
| this.segments.push(opts); | ||
| if (this.currentSpeed !== 0) { | ||
| this.play(); | ||
| } else { | ||
| this.paused = true; | ||
| } | ||
| } else { | ||
| this.playLoop.stop(); | ||
| } | ||
| /* istanbul ignore if */ | ||
| if (!this.paused && !this.isRunning) { | ||
| this.next(); | ||
| return this; | ||
| } | ||
| return this; | ||
| /** | ||
| * pause | ||
| * | ||
| * Pause animation while maintaining progress, speed and segment queue | ||
| * | ||
| */ | ||
| pause() { | ||
| }; | ||
| this.emit("animation:pause"); | ||
| /** | ||
| * Plays next segment in queue | ||
| * Users need not call this. It's automatic | ||
| */ | ||
| Animation.prototype.next = function() { | ||
| if (this.playLoop) { | ||
| this.playLoop.stop(); | ||
| } | ||
| this.paused = true; | ||
| if (this.isRunning) { | ||
| return this; | ||
| } else { | ||
| this.isRunning = true; | ||
| if (this.onpause) { | ||
| this.onpause(); | ||
| } | ||
| } | ||
| if (this.segments.length > 0) { | ||
| var segment = new Animation.Segment(this.segments.shift()); | ||
| Object.assign(this, segment); | ||
| this.paused = this.currentSpeed === 0 ? true : false; | ||
| /** | ||
| * stop | ||
| * | ||
| * Stop all animations | ||
| * | ||
| */ | ||
| stop() { | ||
| if (this.onstart) { | ||
| this.onstart(); | ||
| this.emit("animation:stop"); | ||
| this.segments = []; | ||
| this.isRunning = false; | ||
| if (this.playLoop) { | ||
| this.playLoop.stop(); | ||
| } | ||
| this.normalizeKeyframes(); | ||
| if (this.reverse) { | ||
| this.currentSpeed *= -1; | ||
| if (this.onstop) { | ||
| this.onstop(); | ||
| } | ||
| if (this.currentSpeed !== 0) { | ||
| this.play(); | ||
| } else { | ||
| this.paused = true; | ||
| } | ||
| } else { | ||
| this.playLoop.stop(); | ||
| } | ||
| return this; | ||
| }; | ||
| /** | ||
| * speed | ||
| * | ||
| * Get or set the current playback speed | ||
| * | ||
| * @param {Number} speed | ||
| * | ||
| */ | ||
| speed(speed) { | ||
| /** | ||
| * pause | ||
| * | ||
| * Pause animation while maintaining progress, speed and segment queue | ||
| * | ||
| */ | ||
| if (typeof speed === "undefined") { | ||
| return this.currentSpeed; | ||
| } else { | ||
| this.currentSpeed = speed; | ||
| Animation.prototype.pause = function() { | ||
| // Find our timeline endpoints and refresh rate | ||
| this.scaledDuration = this.duration / Math.abs(this.currentSpeed); | ||
| this.startTime = Date.now() - this.scaledDuration * this.progress; | ||
| this.endTime = this.startTime + this.scaledDuration; | ||
| this.emit("animation:pause"); | ||
| if (this.playLoop) { | ||
| this.playLoop.stop(); | ||
| if (!this.paused) { | ||
| this.play(); | ||
| } | ||
| return this; | ||
| } | ||
| } | ||
| this.paused = true; | ||
| if (this.onpause) { | ||
| this.onpause(); | ||
| } | ||
| /** | ||
| * This function is called in each frame of our animation | ||
| * Users need not call this. It's automatic | ||
| */ | ||
| loopFunction({calledAt}) { | ||
| }; | ||
| // Find the current timeline progress | ||
| const progress = this.calculateProgress(calledAt); | ||
| /** | ||
| * stop | ||
| * | ||
| * Stop all animations | ||
| * | ||
| */ | ||
| // Find the left and right cuePoints/keyFrames; | ||
| const indices = this.findIndices(progress); | ||
| Animation.prototype.stop = function() { | ||
| // call render function with tweened value | ||
| this.target[Animation.render](this.tweenedValue(indices, progress)); | ||
| this.emit("animation:stop"); | ||
| /** | ||
| * If this animation has been running in temporal for too long | ||
| * fall back to using setInterval so we don't melt the user's CPU | ||
| **/ | ||
| if (calledAt > this.fallBackTime) { | ||
| this.fallBackTime = Infinity; | ||
| if (this.playLoop) { | ||
| this.playLoop.stop(); | ||
| } | ||
| this.playLoop = new Animation.TemporalFallback(this); | ||
| } | ||
| this.segments = []; | ||
| this.isRunning = false; | ||
| if (this.playLoop) { | ||
| this.playLoop.stop(); | ||
| } | ||
| // See if we have reached the end of the animation | ||
| /* istanbul ignore else */ | ||
| if ((this.progress === 1 && !this.reverse) || (progress === this.loopback && this.reverse)) { | ||
| if (this.onstop) { | ||
| this.onstop(); | ||
| } | ||
| if (this.loop || (this.metronomic && !this.reverse)) { | ||
| }; | ||
| if (this.onloop) { | ||
| this.onloop(); | ||
| } | ||
| /** | ||
| * speed | ||
| * | ||
| * Get or set the current playback speed | ||
| * | ||
| * @param {Number} speed | ||
| * | ||
| */ | ||
| if (this.metronomic) { | ||
| this.reverse = this.reverse ? false : true; | ||
| } | ||
| Animation.prototype.speed = function(speed) { | ||
| this.normalizeKeyframes(); | ||
| this.progress = this.loopback; | ||
| this.startTime = Date.now() - this.scaledDuration * this.progress; | ||
| this.endTime = this.startTime + this.scaledDuration; | ||
| } else { | ||
| if (typeof speed === "undefined") { | ||
| return this.currentSpeed; | ||
| } else { | ||
| this.currentSpeed = speed; | ||
| this.isRunning = false; | ||
| // Find our timeline endpoints and refresh rate | ||
| this.scaledDuration = this.duration / Math.abs(this.currentSpeed); | ||
| this.startTime = Date.now() - this.scaledDuration * this.progress; | ||
| this.endTime = this.startTime + this.scaledDuration; | ||
| if (this.oncomplete) { | ||
| process.nextTick(this.oncomplete.bind(this)); | ||
| } | ||
| if (!this.paused) { | ||
| this.play(); | ||
| if (this.segments.length > 0) { | ||
| process.nextTick(() => { this.next(); }); | ||
| } else { | ||
| this.stop(); | ||
| } | ||
| } | ||
| } | ||
| return this; | ||
| } | ||
| }; | ||
| /** | ||
| * This function is called in each frame of our animation | ||
| * Users need not call this. It's automatic | ||
| */ | ||
| /** | ||
| * play | ||
| * | ||
| * Start a segment | ||
| */ | ||
| play() { | ||
| const now = Date.now(); | ||
| Animation.prototype.loopFunction = function(loop) { | ||
| // Find the current timeline progress | ||
| var progress = this.calculateProgress(loop.calledAt); | ||
| // Find the left and right cuePoints/keyFrames; | ||
| var indices = this.findIndices(progress); | ||
| // call render function with tweened value | ||
| this.target[Animation.render](this.tweenedValue(indices, progress)); | ||
| /** | ||
| * If this animation has been running in temporal for too long | ||
| * fall back to using setInterval so we don't melt the user's CPU | ||
| **/ | ||
| if (loop.calledAt > this.fallBackTime) { | ||
| this.fallBackTime = Infinity; | ||
| if (this.playLoop) { | ||
| this.playLoop.stop(); | ||
| } | ||
| this.playLoop = new Animation.TemporalFallback(this); | ||
| } | ||
| // See if we have reached the end of the animation | ||
| /* istanbul ignore else */ | ||
| if ((this.progress === 1 && !this.reverse) || (progress === this.loopback && this.reverse)) { | ||
| this.paused = false; | ||
| this.isRunning = true; | ||
| if (this.loop || (this.metronomic && !this.reverse)) { | ||
| // Find our timeline endpoints and refresh rate | ||
| this.scaledDuration = this.duration / Math.abs(this.currentSpeed); | ||
| this.startTime = now - this.scaledDuration * this.progress; | ||
| this.endTime = this.startTime + this.scaledDuration; | ||
| if (this.onloop) { | ||
| this.onloop(); | ||
| } | ||
| // If our animation runs for more than 5 seconds switch to setTimeout | ||
| this.fallBackTime = now + temporalTTL; | ||
| this.frameCount = 0; | ||
| if (this.metronomic) { | ||
| this.reverse = this.reverse ? false : true; | ||
| } | ||
| this.normalizeKeyframes(); | ||
| this.progress = this.loopback; | ||
| this.startTime = Date.now() - this.scaledDuration * this.progress; | ||
| this.endTime = this.startTime + this.scaledDuration; | ||
| } else { | ||
| this.isRunning = false; | ||
| if (this.oncomplete) { | ||
| process.nextTick(this.oncomplete.bind(this)); | ||
| } | ||
| if (this.segments.length > 0) { | ||
| process.nextTick(() => { this.next(); }); | ||
| } else { | ||
| this.stop(); | ||
| } | ||
| /* istanbul ignore else */ | ||
| if (this.fps) { | ||
| this.rate = 1000 / this.fps; | ||
| } | ||
| } | ||
| }; | ||
| /** | ||
| * play | ||
| * | ||
| * Start a segment | ||
| */ | ||
| this.rate = this.rate | 0; | ||
| Animation.prototype.play = function() { | ||
| var now = Date.now(); | ||
| if (this.playLoop) { | ||
| this.playLoop.stop(); | ||
| this.playLoop = temporal.loop(this.rate, this.loopFunction.bind(this)); | ||
| } | ||
| this.paused = false; | ||
| this.isRunning = true; | ||
| findIndices(progress) { | ||
| const indices = { | ||
| left: null, | ||
| right: null | ||
| }; | ||
| // Find our timeline endpoints and refresh rate | ||
| this.scaledDuration = this.duration / Math.abs(this.currentSpeed); | ||
| this.startTime = now - this.scaledDuration * this.progress; | ||
| this.endTime = this.startTime + this.scaledDuration; | ||
| // Find our current before and after cuePoints | ||
| indices.right = this.cuePoints.findIndex(point => point >= progress); | ||
| // If our animation runs for more than 5 seconds switch to setTimeout | ||
| this.fallBackTime = now + temporalTTL; | ||
| this.frameCount = 0; | ||
| indices.left = indices.right === 0 ? /* istanbul ignore next */ 0 : indices.right - 1; | ||
| /* istanbul ignore else */ | ||
| if (this.fps) { | ||
| this.rate = 1000 / this.fps; | ||
| return indices; | ||
| } | ||
| this.rate = this.rate | 0; | ||
| calculateProgress(calledAt) { | ||
| this.playLoop = temporal.loop(this.rate, this.loopFunction.bind(this)); | ||
| }; | ||
| let progress = (calledAt - this.startTime) / this.scaledDuration; | ||
| Animation.prototype.findIndices = function(progress) { | ||
| var indices = { | ||
| left: null, | ||
| right: null | ||
| }; | ||
| if (progress > 1) { | ||
| progress = 1; | ||
| } | ||
| // Find our current before and after cuePoints | ||
| indices.right = this.cuePoints.findIndex(function(point) { | ||
| return point >= progress; | ||
| }); | ||
| this.progress = progress; | ||
| indices.left = indices.right === 0 ? /* istanbul ignore next */ 0 : indices.right - 1; | ||
| if (this.reverse) { | ||
| progress = 1 - progress; | ||
| } | ||
| return indices; | ||
| }; | ||
| // Ease the timeline | ||
| // to do: When reverse replace inFoo with outFoo and vice versa. skip inOutFoo | ||
| return constrain(ease[this.easing](progress), 0, 1); | ||
| } | ||
| Animation.prototype.calculateProgress = function(calledAt) { | ||
| tweenedValue(indices, progress) { | ||
| var progress = (calledAt - this.startTime) / this.scaledDuration; | ||
| const tween = { | ||
| duration: null, | ||
| progress: null | ||
| }; | ||
| if (progress > 1) { | ||
| progress = 1; | ||
| } | ||
| const result = this.normalizedKeyFrames.map(keyFrame => { | ||
| const kIndices = { | ||
| left: null, | ||
| right: null | ||
| }; | ||
| this.progress = progress; | ||
| // If the keyframe at indices.left is null, move left | ||
| for (kIndices.left = indices.left; kIndices.left > -1; kIndices.left--) { | ||
| /* istanbul ignore else */ | ||
| if (keyFrame[kIndices.left] !== null) { | ||
| break; | ||
| } | ||
| } | ||
| if (this.reverse) { | ||
| progress = 1 - progress; | ||
| } | ||
| // If the keyframe at indices.right is null, move right | ||
| kIndices.right = keyFrame.findIndex((frame, index) => | ||
| index >= indices.right && frame !== null | ||
| ); | ||
| // Ease the timeline | ||
| // to do: When reverse replace inFoo with outFoo and vice versa. skip inOutFoo | ||
| progress = ease[this.easing](progress); | ||
| progress = Fn.constrain(progress, 0, 1); | ||
| // Find our progress for the current tween | ||
| tween.duration = this.cuePoints[kIndices.right] - this.cuePoints[kIndices.left]; | ||
| tween.progress = (progress - this.cuePoints[kIndices.left]) / tween.duration; | ||
| return progress; | ||
| }; | ||
| // Catch divide by zero | ||
| if (!Number.isFinite(tween.progress)) { | ||
| /* istanbul ignore next */ | ||
| tween.progress = this.reverse ? 0 : 1; | ||
| } | ||
| Animation.prototype.tweenedValue = function(indices, progress) { | ||
| const left = keyFrame[kIndices.left]; | ||
| const right = keyFrame[kIndices.right]; | ||
| var tween = { | ||
| duration: null, | ||
| progress: null | ||
| }; | ||
| // Apply tween easing to tween.progress | ||
| // to do: When reverse replace inFoo with outFoo and vice versa. skip inOutFoo | ||
| tween.progress = ease[right.easing](tween.progress); | ||
| var result = this.normalizedKeyFrames.map(function(keyFrame) { | ||
| // Note: "this" is bound to the animation object | ||
| // Calculate this tween value | ||
| let calcValue; | ||
| var memberIndices = { | ||
| left: null, | ||
| right: null | ||
| }; | ||
| // If the keyframe at indices.left is null, move left | ||
| for (memberIndices.left = indices.left; memberIndices.left > -1; memberIndices.left--) { | ||
| /* istanbul ignore else */ | ||
| if (keyFrame[memberIndices.left] !== null) { | ||
| break; | ||
| if (right.position) { | ||
| // This is a tuple | ||
| calcValue = right.position.map((value, index) => (value - left.position[index]) * | ||
| tween.progress + left.position[index]); | ||
| } else { | ||
| if (typeof right.value === "number" && typeof left.value === "number") { | ||
| calcValue = (right.value - left.value) * tween.progress + left.value; | ||
| } else { | ||
| calcValue = this.target[Animation.keys].reduce((accum, key) => { | ||
| accum[key] = (right.value[key] - left.value[key]) * tween.progress + left.value[key]; | ||
| return accum; | ||
| }, {}); | ||
| } | ||
| } | ||
| } | ||
| // If the keyframe at indices.right is null, move right | ||
| memberIndices.right = keyFrame.findIndex(function(frame, index) { | ||
| return index >= indices.right && frame !== null; | ||
| return calcValue; | ||
| }); | ||
| // Find our progress for the current tween | ||
| tween.duration = this.cuePoints[memberIndices.right] - this.cuePoints[memberIndices.left]; | ||
| tween.progress = (progress - this.cuePoints[memberIndices.left]) / tween.duration; | ||
| return result; | ||
| } | ||
| // Catch divide by zero | ||
| if (!Number.isFinite(tween.progress)) { | ||
| /* istanbul ignore next */ | ||
| tween.progress = this.reverse ? 0 : 1; | ||
| } | ||
| // Make sure our keyframes conform to a standard | ||
| normalizeKeyframes() { | ||
| let previousVal; | ||
| let keyFrameSet = cloneDeep(this.keyFrames); | ||
| const cuePoints = this.cuePoints; | ||
| var left = keyFrame[memberIndices.left], | ||
| right = keyFrame[memberIndices.right]; | ||
| // Run through the target's normalization | ||
| keyFrameSet = this.target[Animation.normalize](keyFrameSet); | ||
| // Apply tween easing to tween.progress | ||
| // to do: When reverse replace inFoo with outFoo and vice versa. skip inOutFoo | ||
| tween.progress = ease[right.easing](tween.progress); | ||
| // keyFrames can be passed as a single dimensional array if | ||
| // there is just one servo/device. If the first element is not an | ||
| // array, nest keyFrameSet so we only have to deal with one format | ||
| if (!Array.isArray(keyFrameSet[0])) { | ||
| keyFrameSet = [keyFrameSet]; | ||
| } | ||
| // Calculate this tween value | ||
| var calcValue; | ||
| keyFrameSet.forEach(function(keyFrames) { | ||
| if (right.position) { | ||
| // This is a tuple | ||
| calcValue = right.position.map(function(value, index) { | ||
| return (value - left.position[index]) * | ||
| tween.progress + left.position[index]; | ||
| }); | ||
| } else { | ||
| if (typeof right.value === "number" && typeof left.value === "number") { | ||
| calcValue = (right.value - left.value) * tween.progress + left.value; | ||
| } else { | ||
| calcValue = this.target[Animation.keys].reduce(function(accum, key) { | ||
| accum[key] = (right.value[key] - left.value[key]) * tween.progress + left.value[key]; | ||
| return accum; | ||
| }, {}); | ||
| // Pad the right side of keyFrames arrays with null | ||
| for (let i = keyFrames.length; i < cuePoints.length; i++) { | ||
| keyFrames.push(null); | ||
| } | ||
| } | ||
| return calcValue; | ||
| }, this); | ||
| keyFrames.forEach((keyFrame, i, source) => { | ||
| return result; | ||
| }; | ||
| if (keyFrame !== null) { | ||
| // Make sure our keyframes conform to a standard | ||
| Animation.prototype.normalizeKeyframes = function() { | ||
| // keyFrames need to be converted to objects | ||
| if (typeof keyFrame !== "object") { | ||
| keyFrame = { | ||
| step: keyFrame, | ||
| easing: "linear" | ||
| }; | ||
| } | ||
| var previousVal, | ||
| keyFrameSet = Fn.cloneDeep(this.keyFrames), | ||
| cuePoints = this.cuePoints; | ||
| // Replace step values | ||
| if (typeof keyFrame.step !== "undefined") { | ||
| keyFrame.value = keyFrame.step === false ? | ||
| previousVal : previousVal + keyFrame.step; | ||
| } | ||
| // Run through the target's normalization | ||
| keyFrameSet = this.target[Animation.normalize](keyFrameSet); | ||
| // Set a default easing function | ||
| if (!keyFrame.easing) { | ||
| keyFrame.easing = "linear"; | ||
| } | ||
| // keyFrames can be passed as a single dimensional array if | ||
| // there is just one servo/device. If the first element is not an | ||
| // array, nest keyFrameSet so we only have to deal with one format | ||
| if (!Array.isArray(keyFrameSet[0])) { | ||
| keyFrameSet = [keyFrameSet]; | ||
| } | ||
| // Copy value from another frame | ||
| /* istanbul ignore if */ | ||
| if (typeof keyFrame.copyValue !== "undefined") { | ||
| keyFrame.value = source[keyFrame.copyValue].value; | ||
| } | ||
| keyFrameSet.forEach(function(keyFrames) { | ||
| // Copy everything from another keyframe in this array | ||
| /* istanbul ignore if */ | ||
| if (keyFrame.copyFrame) { | ||
| keyFrame = source[keyFrame.copyFrame]; | ||
| } | ||
| // Pad the right side of keyFrames arrays with null | ||
| for (var i = keyFrames.length; i < cuePoints.length; i++) { | ||
| keyFrames.push(null); | ||
| } | ||
| previousVal = keyFrame.value; | ||
| keyFrames.forEach(function(keyFrame, i, source) { | ||
| } else { | ||
| if (keyFrame !== null) { | ||
| if (i === source.length - 1) { | ||
| keyFrame = { | ||
| value: previousVal, | ||
| easing: "linear" | ||
| }; | ||
| } else { | ||
| keyFrame = null; | ||
| } | ||
| // keyFrames need to be converted to objects | ||
| if (typeof keyFrame !== "object") { | ||
| keyFrame = { | ||
| step: keyFrame, | ||
| easing: "linear" | ||
| }; | ||
| } | ||
| source[i] = keyFrame; | ||
| // Replace step values | ||
| if (typeof keyFrame.step !== "undefined") { | ||
| keyFrame.value = keyFrame.step === false ? | ||
| previousVal : previousVal + keyFrame.step; | ||
| } | ||
| }, this); | ||
| }); | ||
| // Set a default easing function | ||
| if (!keyFrame.easing) { | ||
| keyFrame.easing = "linear"; | ||
| } | ||
| this.normalizedKeyFrames = keyFrameSet; | ||
| // Copy value from another frame | ||
| /* istanbul ignore if */ | ||
| if (typeof keyFrame.copyValue !== "undefined") { | ||
| keyFrame.value = source[keyFrame.copyValue].value; | ||
| } | ||
| return this; | ||
| } | ||
| } | ||
| // Copy everything from another keyframe in this array | ||
| /* istanbul ignore if */ | ||
| if (keyFrame.copyFrame) { | ||
| keyFrame = source[keyFrame.copyFrame]; | ||
| } | ||
| previousVal = keyFrame.value; | ||
| /** | ||
| * Placeholders for Symbol | ||
| */ | ||
| Animation.keys = "@@keys"; | ||
| Animation.normalize = "@@normalize"; | ||
| Animation.render = "@@render"; | ||
| } else { | ||
| /** | ||
| * Temporal will run up the CPU. temporalFallback is used | ||
| * for long running animations. | ||
| */ | ||
| Animation.TemporalFallback = class { | ||
| constructor(animation) { | ||
| this.interval = setInterval(() => { | ||
| animation.loopFunction({ | ||
| calledAt: Date.now() | ||
| }); | ||
| }, animation.rate); | ||
| } | ||
| stop() { | ||
| if (this.interval) { | ||
| clearInterval(this.interval); | ||
| } | ||
| } | ||
| }; | ||
| if (i === source.length - 1) { | ||
| keyFrame = { | ||
| value: previousVal, | ||
| easing: "linear" | ||
| }; | ||
| } else { | ||
| keyFrame = null; | ||
| } | ||
| /** | ||
| * Animation.Segment() | ||
| * | ||
| * Create a defaulted segment. | ||
| * | ||
| * Every property ever used on an animation segment | ||
| * MUST be listed here, otherwise properties will | ||
| * persist across segments. This default object is | ||
| * primarily for resetting state. | ||
| * | ||
| */ | ||
| Animation.Segment = class { | ||
| constructor(options) { | ||
| this.cuePoints = [0, 1]; | ||
| this.duration = 1000; | ||
| this.easing = "linear"; | ||
| this.loop = false; | ||
| this.loopback = 0; | ||
| this.metronomic = false; | ||
| this.currentSpeed = 1; | ||
| this.progress = 0; | ||
| this.fps = 60; | ||
| this.rate = 1000 / 60; | ||
| this.paused = false; | ||
| this.isRunning = false; | ||
| this.segments = []; | ||
| this.onstart = null; | ||
| this.onpause = null; | ||
| this.onstop = null; | ||
| this.oncomplete = null; | ||
| this.onloop = null; | ||
| if (options) { | ||
| Object.assign(this, options); | ||
| if (options.segments) { | ||
| this.segments = options.segments.slice(); | ||
| } | ||
| source[i] = keyFrame; | ||
| } | ||
| } | ||
| }; | ||
| }, this); | ||
| }); | ||
| this.normalizedKeyFrames = keyFrameSet; | ||
| return this; | ||
| }; | ||
| module.exports = Animation; |
+5
-8
@@ -42,6 +42,3 @@ var IS_TEST_MODE = !!process.env.IS_TEST_MODE; | ||
| // via known path pattern match. | ||
| serialport.list(function(err, result) { | ||
| // serialport.list() will never result in an error. | ||
| // On failure, an empty array is returned. (#768) | ||
| serialport.list().then(result => { | ||
| var ports = result.filter(function(val) { | ||
@@ -52,3 +49,3 @@ var available = true; | ||
| // ttyUSB#, cu.usbmodem#, COM# | ||
| if (!rport.test(val.comName)) { | ||
| if (!rport.test(val.path)) { | ||
| available = false; | ||
@@ -58,3 +55,3 @@ } | ||
| // Don't allow already used/encountered usb device paths | ||
| if (Serial.used.includes(val.comName)) { | ||
| if (Serial.used.includes(val.path)) { | ||
| available = false; | ||
@@ -65,3 +62,3 @@ } | ||
| }).map(function(val) { | ||
| return val.comName; | ||
| return val.path; | ||
| }); | ||
@@ -108,3 +105,3 @@ | ||
| callback.call(this, ports[0]); | ||
| }.bind(this)); | ||
| }); | ||
| }, | ||
@@ -111,0 +108,0 @@ |
+169
-190
@@ -1,16 +0,12 @@ | ||
| var Board = require("./board"); | ||
| var EVS = require("./evshield"); | ||
| var within = require("./mixins/within"); | ||
| var Fn = require("./fn"); | ||
| var Emitter = require("events").EventEmitter; | ||
| var util = require("util"); | ||
| var priv = new Map(); | ||
| // var int16 = Fn.int16; | ||
| var uint16 = Fn.uint16; | ||
| var toFixed = Fn.toFixed; | ||
| const Board = require("./board"); | ||
| const EVS = require("./evshield"); | ||
| const within = require("./mixins/within"); | ||
| const { uint16, toFixed, scale } = require("./fn"); | ||
| const Emitter = require("events"); | ||
| const priv = new Map(); | ||
| var Controllers = { | ||
| const Controllers = { | ||
| DEFAULT: { | ||
| initialize: { | ||
| value: function(opts, dataHandler) { | ||
| value(options, dataHandler) { | ||
| this.io.pinMode(this.pin, this.io.MODES.ANALOG); | ||
@@ -21,4 +17,4 @@ this.io.analogRead(this.pin, dataHandler); | ||
| toIntensityLevel: { | ||
| value: function(raw) { | ||
| return toFixed(Fn.scale(raw, 0, 1023, 0, 100) / 100, 2); | ||
| value(raw) { | ||
| return toFixed(scale(raw, 0, 1023, 0, 100) / 100, 2); | ||
| } | ||
@@ -29,13 +25,13 @@ } | ||
| initialize: { | ||
| value: function(opts, dataHandler) { | ||
| var state = priv.get(this); | ||
| value(options, dataHandler) { | ||
| const state = priv.get(this); | ||
| if (opts.mode) { | ||
| opts.mode = opts.mode.toUpperCase(); | ||
| if (options.mode) { | ||
| options.mode = options.mode.toUpperCase(); | ||
| } | ||
| state.mode = opts.mode === "REFLECTED" ? EVS.Type_EV3_LIGHT_REFLECTED : EVS.Type_EV3_LIGHT; | ||
| state.mode = options.mode === "REFLECTED" ? EVS.Type_EV3_LIGHT_REFLECTED : EVS.Type_EV3_LIGHT; | ||
| state.shield = EVS.shieldPort(opts.pin); | ||
| state.ev3 = new EVS(Object.assign(opts, { | ||
| state.shield = EVS.shieldPort(options.pin); | ||
| state.ev3 = new EVS(Object.assign(options, { | ||
| io: this.io | ||
@@ -45,4 +41,4 @@ })); | ||
| state.ev3.write(state.shield, 0x81 + state.shield.offset, state.mode); | ||
| state.ev3.read(state.shield, EVS.Light, EVS.Light_Bytes, function(data) { | ||
| var value = data[0] | (data[1] << 8); | ||
| state.ev3.read(state.shield, EVS.Light, EVS.Light_Bytes, (data) => { | ||
| const value = data[0] | (data[1] << 8); | ||
| dataHandler(value); | ||
@@ -53,3 +49,3 @@ }); | ||
| toIntensityLevel: { | ||
| value: function(raw) { | ||
| value(raw) { | ||
| return toFixed(raw / 100, 2); | ||
@@ -61,19 +57,18 @@ } | ||
| initialize: { | ||
| value: function(opts, dataHandler) { | ||
| var state = priv.get(this); | ||
| value(options, dataHandler) { | ||
| const state = priv.get(this); | ||
| if (opts.mode) { | ||
| opts.mode = opts.mode.toUpperCase(); | ||
| if (options.mode) { | ||
| options.mode = options.mode.toUpperCase(); | ||
| } | ||
| state.mode = opts.mode === "REFLECTED" ? EVS.Type_NXT_LIGHT_REFLECTED : EVS.Type_NXT_LIGHT; | ||
| state.mode = options.mode === "REFLECTED" ? EVS.Type_NXT_LIGHT_REFLECTED : EVS.Type_NXT_LIGHT; | ||
| state.shield = EVS.shieldPort(opts.pin); | ||
| state.ev3 = new EVS(Object.assign(opts, { | ||
| state.shield = EVS.shieldPort(options.pin); | ||
| state.ev3 = new EVS(Object.assign(options, { | ||
| io: this.io | ||
| })); | ||
| state.ev3.setup(state.shield, state.mode); | ||
| state.ev3.read(state.shield, state.shield.analog, EVS.Analog_Bytes, function(data) { | ||
| var value = data[0] | (data[1] << 8); | ||
| dataHandler(value); | ||
| state.ev3.read(state.shield, state.shield.analog, EVS.Analog_Bytes, (data) => { | ||
| dataHandler(data[0] | (data[1] << 8)); | ||
| }); | ||
@@ -83,4 +78,4 @@ } | ||
| toIntensityLevel: { | ||
| value: function(raw) { | ||
| return toFixed(Fn.scale(raw, 0, 1023, 100, 0) / 100, 2); | ||
| value(raw) { | ||
| return toFixed(scale(raw, 0, 1023, 100, 0) / 100, 2); | ||
| } | ||
@@ -103,12 +98,9 @@ } | ||
| initialize: { | ||
| value: function(opts, dataHandler) { | ||
| var address = opts.address || 0x39; | ||
| var command = function(byte) { | ||
| // byte | 0b10000000; | ||
| return byte | 0x80; | ||
| }; | ||
| value(options, dataHandler) { | ||
| const address = options.address || 0x39; | ||
| const command = byte => byte | 0b10000000; | ||
| opts.address = address; | ||
| options.address = address; | ||
| this.io.i2cConfig(opts); | ||
| this.io.i2cConfig(options); | ||
@@ -133,31 +125,31 @@ // Page 15 | ||
| // Integration time scaling factors | ||
| var LUX_SCALE = 14; // scale by (2 ** 14) | ||
| var RATIO_SCALE = 9; // scale ratio by (2 ** 9) | ||
| const LUX_SCALE = 14; // scale by (2 ** 14) | ||
| const RATIO_SCALE = 9; // scale ratio by (2 ** 9) | ||
| // Page 24 | ||
| // T, FN, and CL Package coefficients | ||
| var K1T = 0x0040; // 0.125 * (2 ** RATIO_SCALE) | ||
| var B1T = 0x01F2; // 0.0304 * (2 ** LUX_SCALE) | ||
| var M1T = 0x01BE; // 0.0272 * (2 ** LUX_SCALE) | ||
| var K2T = 0x0080; // 0.250 * (2 ** RATIO_SCALE) | ||
| var B2T = 0x0214; // 0.0325 * (2 ** LUX_SCALE) | ||
| var M2T = 0x02D1; // 0.0440 * (2 ** LUX_SCALE) | ||
| var K3T = 0x00C0; // 0.375 * (2 ** RATIO_SCALE) | ||
| var B3T = 0x023F; // 0.0351 * (2 ** LUX_SCALE) | ||
| var M3T = 0x037B; // 0.0544 * (2 ** LUX_SCALE) | ||
| var K4T = 0x0100; // 0.50 * (2 ** RATIO_SCALE) | ||
| var B4T = 0x0270; // 0.0381 * (2 ** LUX_SCALE) | ||
| var M4T = 0x03FE; // 0.0624 * (2 ** LUX_SCALE) | ||
| var K5T = 0x0138; // 0.61 * (2 ** RATIO_SCALE) | ||
| var B5T = 0x016F; // 0.0224 * (2 ** LUX_SCALE) | ||
| var M5T = 0x01FC; // 0.0310 * (2 ** LUX_SCALE) | ||
| var K6T = 0x019A; // 0.80 * (2 ** RATIO_SCALE) | ||
| var B6T = 0x00D2; // 0.0128 * (2 ** LUX_SCALE) | ||
| var M6T = 0x00FB; // 0.0153 * (2 ** LUX_SCALE) | ||
| var K7T = 0x029A; // 1.3 * (2 ** RATIO_SCALE) | ||
| var B7T = 0x0018; // 0.00146 * (2 ** LUX_SCALE) | ||
| var M7T = 0x0012; // 0.00112 * (2 ** LUX_SCALE) | ||
| var K8T = 0x029A; // 1.3 * (2 ** RATIO_SCALE) | ||
| var B8T = 0x0000; // 0.000 * (2 ** LUX_SCALE) | ||
| var M8T = 0x0000; // 0.000 * (2 ** LUX_SCALE) | ||
| const K1T = 0x0040; // 0.125 * (2 ** RATIO_SCALE) | ||
| const B1T = 0x01F2; // 0.0304 * (2 ** LUX_SCALE) | ||
| const M1T = 0x01BE; // 0.0272 * (2 ** LUX_SCALE) | ||
| const K2T = 0x0080; // 0.250 * (2 ** RATIO_SCALE) | ||
| const B2T = 0x0214; // 0.0325 * (2 ** LUX_SCALE) | ||
| const M2T = 0x02D1; // 0.0440 * (2 ** LUX_SCALE) | ||
| const K3T = 0x00C0; // 0.375 * (2 ** RATIO_SCALE) | ||
| const B3T = 0x023F; // 0.0351 * (2 ** LUX_SCALE) | ||
| const M3T = 0x037B; // 0.0544 * (2 ** LUX_SCALE) | ||
| const K4T = 0x0100; // 0.50 * (2 ** RATIO_SCALE) | ||
| const B4T = 0x0270; // 0.0381 * (2 ** LUX_SCALE) | ||
| const M4T = 0x03FE; // 0.0624 * (2 ** LUX_SCALE) | ||
| const K5T = 0x0138; // 0.61 * (2 ** RATIO_SCALE) | ||
| const B5T = 0x016F; // 0.0224 * (2 ** LUX_SCALE) | ||
| const M5T = 0x01FC; // 0.0310 * (2 ** LUX_SCALE) | ||
| const K6T = 0x019A; // 0.80 * (2 ** RATIO_SCALE) | ||
| const B6T = 0x00D2; // 0.0128 * (2 ** LUX_SCALE) | ||
| const M6T = 0x00FB; // 0.0153 * (2 ** LUX_SCALE) | ||
| const K7T = 0x029A; // 1.3 * (2 ** RATIO_SCALE) | ||
| const B7T = 0x0018; // 0.00146 * (2 ** LUX_SCALE) | ||
| const M7T = 0x0012; // 0.00112 * (2 ** LUX_SCALE) | ||
| const K8T = 0x029A; // 1.3 * (2 ** RATIO_SCALE) | ||
| const B8T = 0x0000; // 0.000 * (2 ** LUX_SCALE) | ||
| const M8T = 0x0000; // 0.000 * (2 ** LUX_SCALE) | ||
@@ -200,4 +192,4 @@ // Auto-gain thresholds | ||
| var GAIN_1X = 0x00; | ||
| var GAIN_16X = 0x10; | ||
| const GAIN_1X = 0x00; | ||
| const GAIN_16X = 0x10; | ||
@@ -208,3 +200,3 @@ // var TI_13MS = 0x00; | ||
| var TintMs = [ | ||
| const TintMs = [ | ||
| // 0, TI_13MS | ||
@@ -218,3 +210,3 @@ 13, | ||
| var TintDelayMs = [ | ||
| const TintDelayMs = [ | ||
| // 0, TI_13MS | ||
@@ -234,3 +226,3 @@ 15, | ||
| var chScales = [ | ||
| const chScales = [ | ||
| // 0, TI_13MS | ||
@@ -245,15 +237,15 @@ 0x07517, | ||
| // Gain and Tint defaults; | ||
| var gain = GAIN_16X; | ||
| var TintIndex = 0; | ||
| var Tint = TintMs[TintIndex]; | ||
| var lux = 0; | ||
| let gain = GAIN_16X; | ||
| let TintIndex = 0; | ||
| let Tint = TintMs[TintIndex]; | ||
| let lux = 0; | ||
| // if (typeof opts.gain !== "undefined") { | ||
| // if (typeof options.gain !== "undefined") { | ||
| // isAutoGain = false; | ||
| // gain = opts.gain; | ||
| // gain = options.gain; | ||
| // } | ||
| // if (typeof opts.integration !== "undefined") { | ||
| // if (typeof options.integration !== "undefined") { | ||
| // isAutoGain = false; | ||
| // Tint = opts.integration; | ||
| // Tint = options.integration; | ||
| // } | ||
@@ -265,6 +257,6 @@ | ||
| gain: { | ||
| get: function() { | ||
| get() { | ||
| return gain; | ||
| }, | ||
| set: function(value) { | ||
| set(value) { | ||
| if (value !== GAIN_1X && value !== GAIN_16X) { | ||
@@ -279,6 +271,6 @@ throw new RangeError("Invalid gain. Expected one of: 0, 16"); | ||
| integration: { | ||
| get: function() { | ||
| get() { | ||
| return Tint; | ||
| }, | ||
| set: function(value) { | ||
| set(value) { | ||
| TintIndex = TintMs.indexOf(value); | ||
@@ -296,3 +288,3 @@ | ||
| lux: { | ||
| get: function() { | ||
| get() { | ||
| return lux; | ||
@@ -318,17 +310,17 @@ } | ||
| var read = function() { | ||
| setTimeout(function() { | ||
| const read = () => { | ||
| setTimeout(() => { | ||
| // Page 19 | ||
| // Read ADC Channels Using Read Word Protocol − RECOMMENDED | ||
| this.io.i2cReadOnce(address, command(this.REGISTER.READ), 4, function(data) { | ||
| this.io.i2cReadOnce(address, command(this.REGISTER.READ), 4, (data) => { | ||
| // Page 23 - 28 | ||
| // Simplified Lux Calculation | ||
| var ch0 = uint16(data[1], data[0]); | ||
| var ch1 = uint16(data[3], data[2]); | ||
| var b = 0; | ||
| var m = 0; | ||
| let ch0 = uint16(data[1], data[0]); | ||
| let ch1 = uint16(data[3], data[2]); | ||
| let b = 0; | ||
| let m = 0; | ||
| // Page 26 | ||
| // CalculateLux(...) | ||
| var chScale = chScales[TintIndex]; | ||
| let chScale = chScales[TintIndex]; | ||
@@ -345,3 +337,3 @@ | ||
| var ratio1 = 0; | ||
| let ratio1 = 0; | ||
@@ -354,3 +346,3 @@ if (ch0) { | ||
| var ratio = (ratio1 + 1) >> 1; | ||
| const ratio = (ratio1 + 1) >> 1; | ||
@@ -384,3 +376,3 @@ if (ratio >= 0 && ratio <= K1T) { | ||
| var temp = (ch0 * b) - (ch1 * m); | ||
| let temp = (ch0 * b) - (ch1 * m); | ||
@@ -400,4 +392,4 @@ if (temp < 0) { | ||
| }); | ||
| }.bind(this), TintDelayMs[TintIndex]); | ||
| }.bind(this); | ||
| }, TintDelayMs[TintIndex]); | ||
| }; | ||
@@ -408,3 +400,3 @@ read(); | ||
| toLux: { | ||
| value: function(raw) { | ||
| value(raw) { | ||
| return raw; | ||
@@ -414,4 +406,4 @@ }, | ||
| toIntensityLevel: { | ||
| value: function(raw) { | ||
| return toFixed(Fn.scale(raw, 0, 17000, 0, 100) / 100, 2); | ||
| value(raw) { | ||
| return toFixed(scale(raw, 0, 17000, 0, 100) / 100, 2); | ||
| }, | ||
@@ -427,19 +419,16 @@ }, | ||
| initialize: { | ||
| value: function(opts, dataHandler) { | ||
| var address = opts.address || 0x23; | ||
| var mode = opts.mode || 0x10; | ||
| opts.address = address; | ||
| this.io.i2cConfig(opts); | ||
| value(options, dataHandler) { | ||
| const address = options.address || 0x23; | ||
| const mode = options.mode || 0x10; | ||
| options.address = address; | ||
| this.io.i2cConfig(options); | ||
| this.io.i2cWrite(address, mode); | ||
| var read = function() { | ||
| setTimeout(function() { | ||
| this.io.i2cReadOnce(address, 2, function(data) { | ||
| var raw = data[0]; | ||
| raw <<= 8; | ||
| raw |= data[1]; | ||
| dataHandler(raw); | ||
| const read = () => { | ||
| setTimeout(() => { | ||
| this.io.i2cReadOnce(address, 2, (data) => { | ||
| dataHandler((data[0] << 8) | data[1]); | ||
| read(); | ||
| }); | ||
| }.bind(this), 120); | ||
| }.bind(this); | ||
| }, 120); | ||
| }; | ||
| read(); | ||
@@ -449,3 +438,3 @@ }, | ||
| toLux: { | ||
| value: function(raw) { | ||
| value(raw) { | ||
| // Page 2 | ||
@@ -457,4 +446,4 @@ // H-Resolution Mode Resolution rHR - 1 - lx | ||
| toIntensityLevel: { | ||
| value: function(raw) { | ||
| return toFixed(Fn.scale(raw / 1.2, 0, 65535, 0, 100) / 100, 2); | ||
| value(raw) { | ||
| return toFixed(scale(raw / 1.2, 0, 65535, 0, 100) / 100, 2); | ||
| }, | ||
@@ -474,88 +463,78 @@ }, | ||
| function Light(opts) { | ||
| class Light extends Emitter { | ||
| constructor(options) { | ||
| super(); | ||
| if (!(this instanceof Light)) { | ||
| return new Light(opts); | ||
| } | ||
| let controller = null; | ||
| let raw = 0; | ||
| let last = 0; | ||
| const freq = options.freq || 25; | ||
| var controller = null; | ||
| var state = {}; | ||
| var raw = 0; | ||
| var last = 0; | ||
| var freq = opts.freq || 25; | ||
| Board.Component.call( | ||
| this, options = Board.Options(options) | ||
| ); | ||
| Board.Component.call( | ||
| this, opts = Board.Options(opts) | ||
| ); | ||
| if (typeof options.controller === "string") { | ||
| controller = Controllers[options.controller]; | ||
| } else { | ||
| controller = options.controller || Controllers.DEFAULT; | ||
| } | ||
| if (typeof opts.controller === "string") { | ||
| controller = Controllers[opts.controller]; | ||
| } else { | ||
| controller = opts.controller || Controllers.DEFAULT; | ||
| } | ||
| Board.Controller.call(this, controller, options); | ||
| Board.Controller.call(this, controller, opts); | ||
| if (!this.toIntensityLevel) { | ||
| this.toIntensityLevel = options.toIntensityLevel || (x => x); | ||
| } | ||
| if (!this.toIntensityLevel) { | ||
| this.toIntensityLevel = opts.toIntensityLevel || function(x) { | ||
| return x; | ||
| }; | ||
| } | ||
| if (!this.toLux) { | ||
| this.toLux = options.toLux || (x => x); | ||
| } | ||
| if (!this.toLux) { | ||
| this.toLux = opts.toLux || function(x) { | ||
| return x; | ||
| }; | ||
| } | ||
| Object.defineProperties(this, { | ||
| value: { | ||
| get: function() { | ||
| return raw; | ||
| Object.defineProperties(this, { | ||
| value: { | ||
| get() { | ||
| return raw; | ||
| }, | ||
| }, | ||
| }, | ||
| level: { | ||
| get: function() { | ||
| return this.toIntensityLevel(raw); | ||
| level: { | ||
| get() { | ||
| return this.toIntensityLevel(raw); | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| }); | ||
| priv.set(this, state); | ||
| priv.set(this, {}); | ||
| /* istanbul ignore else */ | ||
| if (typeof this.initialize === "function") { | ||
| this.initialize(opts, function(data) { | ||
| raw = data; | ||
| }); | ||
| } | ||
| /* istanbul ignore else */ | ||
| if (typeof this.initialize === "function") { | ||
| this.initialize(options, data => raw = data); | ||
| } | ||
| if (typeof this.lux === "undefined") { | ||
| Object.defineProperty(this, "lux", { | ||
| get: function() { | ||
| return this.toLux(raw); | ||
| }, | ||
| }); | ||
| } | ||
| if (typeof this.lux === "undefined") { | ||
| Object.defineProperty(this, "lux", { | ||
| get() { | ||
| return this.toLux(raw); | ||
| }, | ||
| }); | ||
| } | ||
| var data = { | ||
| level: 0, | ||
| lux: 0, | ||
| }; | ||
| const data = { | ||
| level: 0, | ||
| lux: 0, | ||
| }; | ||
| setInterval(function() { | ||
| data.level = this.level; | ||
| data.lux = this.lux; | ||
| setInterval(() => { | ||
| data.level = this.level; | ||
| data.lux = this.lux; | ||
| this.emit("data", data); | ||
| this.emit("data", data); | ||
| if (raw !== last) { | ||
| last = raw; | ||
| this.emit("change", data); | ||
| } | ||
| }.bind(this), freq); | ||
| if (raw !== last) { | ||
| last = raw; | ||
| this.emit("change", data); | ||
| } | ||
| }, freq); | ||
| } | ||
| } | ||
| util.inherits(Light, Emitter); | ||
| Object.assign(Light.prototype, within); | ||
@@ -565,3 +544,3 @@ | ||
| /* istanbul ignore else */ | ||
| if (!!process.env.IS_TEST_MODE) { | ||
| if (process.env.IS_TEST_MODE) { | ||
| Light.Controllers = Controllers; | ||
@@ -568,0 +547,0 @@ Light.purge = function() { |
+46
-2
@@ -122,2 +122,32 @@ var Board = require("./board"); | ||
| }, | ||
| PCA9685_Hybrid: { | ||
| setPWM: { | ||
| writable: true, | ||
| value: function(pin, speed) { | ||
| var state = priv.get(this); | ||
| state.expander.analogWrite(pin, speed); | ||
| } | ||
| }, | ||
| initialize: { | ||
| value: function(opts) { | ||
| var state = priv.get(this); | ||
| this.address = opts.address || 0x40; | ||
| this.pwmRange = opts.pwmRange || [0, 4080]; | ||
| this.frequency = opts.frequency || 50; | ||
| state.expander = Expander.get({ | ||
| address: this.address, | ||
| controller: "PCA9685", | ||
| bus: this.bus, | ||
| pwmRange: this.pwmRange, | ||
| frequency: this.frequency, | ||
| }); | ||
| this.pins.pwm = state.expander.normalize(this.pins.pwm); | ||
| } | ||
| } | ||
| }, | ||
| EVS_EV3: { | ||
@@ -1155,6 +1185,20 @@ initialize: { | ||
| }, | ||
| PICAR_V: { | ||
| A: { | ||
| controller: "PCA9685_Hybrid", | ||
| pins: { | ||
| pwm: 4, | ||
| dir: "GPIO17" | ||
| } | ||
| }, | ||
| B: { | ||
| controller: "PCA9685_Hybrid", | ||
| pins: { | ||
| pwm: 5, | ||
| dir: "GPIO27" | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| /** | ||
@@ -1161,0 +1205,0 @@ * Motors() |
+6
-7
| { | ||
| "name": "johnny-five", | ||
| "description": "The JavaScript Robotics and Hardware Programming Framework. Use with: Arduino (all models), Electric Imp, Beagle Bone, Intel Galileo & Edison, Linino One, Pinoccio, pcDuino3, Raspberry Pi, Particle/Spark Core & Photon, Tessel 2, TI Launchpad and more!", | ||
| "version": "1.3.1", | ||
| "version": "1.4.0", | ||
| "homepage": "https://johnny-five.io", | ||
@@ -155,3 +155,2 @@ "author": "Rick Waldron <waldron.rick@gmail.com>", | ||
| "color-convert": "~1.2.2", | ||
| "ease-component": "latest", | ||
| "lodash.clonedeep": "^4.3.0", | ||
@@ -164,4 +163,4 @@ "lodash.debounce": "^4.0.3", | ||
| "browser-serialport": "latest", | ||
| "firmata": "^2.0.0", | ||
| "serialport": "^7.1.5" | ||
| "firmata": "^2.2.0", | ||
| "serialport": "^8.0.5" | ||
| }, | ||
@@ -173,5 +172,5 @@ "devDependencies": { | ||
| "coveralls": "^3.0.2", | ||
| "grunt": "^1.0.3", | ||
| "grunt": "^1.0.4", | ||
| "grunt-cli": "^1.2.0", | ||
| "grunt-contrib-jshint": "^1.1.0", | ||
| "grunt-contrib-jshint": "^2.1.0", | ||
| "grunt-contrib-nodeunit": "^2.0.0", | ||
@@ -183,3 +182,3 @@ "grunt-contrib-watch": "^1.1.0", | ||
| "mock-firmata": "latest", | ||
| "nyc": "^12.0.2", | ||
| "nyc": "^14.1.1", | ||
| "optimist": "^0.6.1", | ||
@@ -186,0 +185,0 @@ "sinon": "~1.10.2" |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
707266
0.41%9
-10%61
1.67%22704
0.49%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed