Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
294 lines
7.7 KiB
JavaScript
Executable file
294 lines
7.7 KiB
JavaScript
Executable file
define(function(require) {
|
|
'use strict';
|
|
|
|
var asyncStorage = require('shared/js/async_storage');
|
|
var Utils = require('utils');
|
|
|
|
var timerPrivate = new WeakMap();
|
|
|
|
/**
|
|
* Timer
|
|
*
|
|
* Create new or revive existing timer objects.
|
|
*
|
|
* @param {Object} opts Optional timer object to create or revive
|
|
* a new or existing timer object.
|
|
* - startTime, number time in ms.
|
|
* - duration, time to count from `start`.
|
|
* - configuredDuration, time requested by user.
|
|
* - sound, string sound name.
|
|
* - vibrate, boolean, vibrate or not.
|
|
* - id, integer, mozAlarm API id number.
|
|
*/
|
|
function Timer(opts) {
|
|
opts = opts || {};
|
|
|
|
var now = Date.now();
|
|
if (opts.id !== undefined) {
|
|
delete opts.id;
|
|
}
|
|
// private properties
|
|
timerPrivate.set(this, Utils.extend({
|
|
state: Timer.INITIAL
|
|
}, extractProtected(opts)));
|
|
// public properties
|
|
Utils.extend(this, {
|
|
startTime: now,
|
|
duration: null,
|
|
configuredDuration: null,
|
|
sound: 'ac_awake.opus',
|
|
vibrate: true
|
|
}, opts);
|
|
}
|
|
|
|
Timer.prototype.constructor = Timer;
|
|
|
|
/**
|
|
* request - get the persisted Timer object.
|
|
*
|
|
* @param {function} [callback] - called with (err, timer_raw).
|
|
*/
|
|
Timer.getFromStorage = function(callback) {
|
|
asyncStorage.getItem('active_timer', function(timer) {
|
|
if (timer) {
|
|
// Normalize the timer data. Pre-April-2014 code may have stored
|
|
// 'vibrate' and 'sound' as the string "0".
|
|
timer.sound = (timer.sound !== '0' ? timer.sound : null);
|
|
timer.vibrate = (timer.vibrate && timer.vibrate !== '0');
|
|
}
|
|
callback && callback(timer || null);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* singleton - get the unique persisted Timer object.
|
|
*
|
|
* @param {function} [callback] - called with (err, timer).
|
|
*/
|
|
var timerSingleton = Utils.singleton(Timer);
|
|
Timer.singleton = function tm_singleton(callback) {
|
|
Timer.getFromStorage(function(err, obj) {
|
|
var ts = timerSingleton(obj);
|
|
callback && callback(null, ts);
|
|
});
|
|
};
|
|
|
|
function extractProtected(config) {
|
|
var ret = {};
|
|
var protectedProperties = new Set(['state']);
|
|
for (var i in config) {
|
|
if (protectedProperties.has(i)) {
|
|
ret[i] = config[i];
|
|
delete config[i];
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* toSerializable - convert `this` to a serialized format.
|
|
*
|
|
* @return {object} - object representation of this Timer.
|
|
*/
|
|
Timer.prototype.toSerializable = function timerToSerializable() {
|
|
var timer = Utils.extend({}, this, timerPrivate.get(this));
|
|
|
|
// Normalize the data. TODO: Perform this normalization immediately
|
|
// at the getter/setter level when this class is refactored.
|
|
return {
|
|
startTime: timer.startTime,
|
|
duration: timer.duration,
|
|
configuredDuration: timer.configuredDuration,
|
|
sound: (timer.sound !== '0' ? timer.sound : null),
|
|
vibrate: (timer.vibrate !== '0' ? timer.vibrate : null),
|
|
state: timer.state
|
|
};
|
|
};
|
|
|
|
/**
|
|
* save - Save the timer to the database.
|
|
*
|
|
* @param {function} [callback] - callback to call after the timer
|
|
* has been saved.
|
|
*/
|
|
Timer.prototype.save = function timerSave(callback) {
|
|
asyncStorage.setItem('active_timer', this.toSerializable(), function() {
|
|
callback && callback(null, this);
|
|
}.bind(this));
|
|
};
|
|
|
|
/**
|
|
* register - Register the timer with mozAlarm API.
|
|
*
|
|
* @param {function} [callback] - callback to call after the timer
|
|
* has been registered.
|
|
*/
|
|
Timer.prototype.register = function timerRegister(callback) {
|
|
var data = {
|
|
type: 'timer'
|
|
};
|
|
var request;
|
|
|
|
// Remove previously-created mozAlarm for this alarm, if necessary.
|
|
this.unregister();
|
|
|
|
request = navigator.mozAlarms.add(
|
|
new Date(Date.now() + this.remaining), 'ignoreTimezone', data
|
|
);
|
|
|
|
request.onsuccess = (function(ev) {
|
|
this.id = ev.target.result;
|
|
callback && callback(null, this);
|
|
}.bind(this));
|
|
request.onerror = function(ev) {
|
|
callback && callback(ev.target.error);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* commit - save and register the timer as necessary.
|
|
*
|
|
* @param {function} [callback] - callback to call after the timer
|
|
* has been registered.
|
|
*/
|
|
Timer.prototype.commit = function timerCommit(callback) {
|
|
var saveSelf = this.save.bind(this, callback);
|
|
if (this.state === Timer.STARTED) {
|
|
this.register(saveSelf);
|
|
} else {
|
|
this.unregister();
|
|
saveSelf();
|
|
}
|
|
};
|
|
|
|
Timer.prototype.unregister = function timerUnregister() {
|
|
if (typeof this.id === 'number') {
|
|
navigator.mozAlarms.remove(this.id);
|
|
}
|
|
};
|
|
|
|
Object.defineProperty(Timer.prototype, 'remaining', {
|
|
get: function() {
|
|
if (this.state === Timer.INITIAL) {
|
|
return this.configuredDuration;
|
|
} else if (this.state === Timer.PAUSED) {
|
|
return this.duration;
|
|
} else if (this.state === Timer.STARTED) {
|
|
if (typeof this.startTime === 'undefined' ||
|
|
typeof this.duration === 'undefined') {
|
|
return 0;
|
|
}
|
|
var r = (this.startTime + this.duration) - Date.now();
|
|
return r >= 0 ? r : 0;
|
|
}
|
|
}
|
|
});
|
|
|
|
Object.defineProperty(Timer.prototype, 'state', {
|
|
get: function() {
|
|
var priv = timerPrivate.get(this);
|
|
return priv.state;
|
|
}
|
|
});
|
|
|
|
Timer.prototype.start = function timerStart() {
|
|
if (this.state !== Timer.STARTED) {
|
|
var priv = timerPrivate.get(this);
|
|
priv.state = Timer.STARTED;
|
|
this.startTime = Date.now();
|
|
this.duration = (typeof this.duration === 'number') ? this.duration :
|
|
this.configuredDuration;
|
|
window.dispatchEvent(new CustomEvent('timer-start', {
|
|
detail: { remaining: this.remaining }
|
|
}));
|
|
this._tick();
|
|
}
|
|
};
|
|
|
|
Timer.prototype.pause = function timerPause() {
|
|
if (this.state === Timer.STARTED) {
|
|
this.duration = this.remaining; // remaining getter observes private state
|
|
var priv = timerPrivate.get(this);
|
|
priv.state = Timer.PAUSED;
|
|
this.startTime = null;
|
|
this._cancelTick();
|
|
window.dispatchEvent(new CustomEvent('timer-pause', {
|
|
detail: { remaining: this.remaining }
|
|
}));
|
|
}
|
|
};
|
|
|
|
Timer.prototype.cancel = function timerReset() {
|
|
if (this.state !== Timer.INITIAL) {
|
|
var priv = timerPrivate.get(this);
|
|
priv.state = Timer.INITIAL;
|
|
this.startTime = null;
|
|
this.duration = this.configuredDuration;
|
|
this._cancelTick();
|
|
this._dispatchEnd();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* plus Increase the duration and extend the endAt time
|
|
*
|
|
* @param {Number} seconds The time in seconds to add.
|
|
*
|
|
* @return {Timer} Timer instance.
|
|
*/
|
|
Timer.prototype.plus = function timerPlus(seconds) {
|
|
// Convert to ms
|
|
var ms = seconds * 1000;
|
|
|
|
this.duration += ms;
|
|
|
|
return this;
|
|
};
|
|
|
|
Timer.prototype._dispatchEnd = function timerDispatchEnd() {
|
|
// cancel and end are basically the same thing
|
|
window.dispatchEvent(new CustomEvent('timer-end', {
|
|
detail: { remaining: 0 }
|
|
}));
|
|
};
|
|
|
|
Timer.prototype._tick = function timerTick() {
|
|
if (this.state !== Timer.STARTED) {
|
|
return;
|
|
}
|
|
|
|
// we cache the previous value posted to avoid triggering multiple "tick"
|
|
// messages for the same second
|
|
var remaining = this.remaining;
|
|
var current = Math.round(remaining / 1000);
|
|
if (current !== this._previousTick) {
|
|
this._previousTick = current;
|
|
window.dispatchEvent(new CustomEvent('timer-tick', {
|
|
detail: { remaining: remaining }
|
|
}));
|
|
}
|
|
|
|
if (remaining > 0) {
|
|
// use setTimeout instead of requestAnimationFrame because window will
|
|
// probably be hidden when handling connections
|
|
this._tickTimeout = window.setTimeout(() => this._tick(), 100);
|
|
} else {
|
|
this._dispatchEnd();
|
|
}
|
|
};
|
|
|
|
Timer.prototype._cancelTick = function timerCancelTick() {
|
|
window.clearTimeout(this._tickTimeout);
|
|
};
|
|
|
|
/**
|
|
* Static "const" Timer states.
|
|
*/
|
|
Object.defineProperties(Timer, {
|
|
INITIAL: { value: 0 },
|
|
STARTED: { value: 1 },
|
|
PAUSED: { value: 2 }
|
|
});
|
|
|
|
return Timer;
|
|
});
|