[Plugins] Bring over timeline, clock plugins

WTD-1239
This commit is contained in:
Victor Woeltjen
2015-09-14 16:45:38 -07:00
parent 8c1b70f085
commit c932e953bc
119 changed files with 10485 additions and 0 deletions

View File

@ -0,0 +1,41 @@
/*global define*/
define(
[],
function () {
"use strict";
/**
* Implements the "Start" and "Restart" action for timers.
*
* Sets the reference timestamp in a timer to the current
* time, such that it begins counting up.
*
* Both "Start" and "Restart" share this implementation, but
* control their visibility with different `appliesTo` behavior.
*
* @implements Action
*/
function AbstractStartTimerAction(now, context) {
var domainObject = context.domainObject;
function doPersist() {
var persistence = domainObject.getCapability('persistence');
return persistence && persistence.persist();
}
function setTimestamp(model) {
model.timestamp = now();
}
return {
perform: function () {
return domainObject.useCapability('mutation', setTimestamp)
.then(doPersist);
}
};
}
return AbstractStartTimerAction;
}
);

View File

@ -0,0 +1,33 @@
/*global define*/
define(
['./AbstractStartTimerAction'],
function (AbstractStartTimerAction) {
"use strict";
/**
* Implements the "Restart at 0" action.
*
* Behaves the same as (and delegates functionality to)
* the "Start" action.
* @implements Action
*/
function RestartTimerAction(now, context) {
return new AbstractStartTimerAction(now, context);
}
RestartTimerAction.appliesTo = function (context) {
var model =
(context.domainObject && context.domainObject.getModel())
|| {};
// We show this variant for timers which already have
// a target time.
return model.type === 'warp.timer' &&
model.timestamp !== undefined;
};
return RestartTimerAction;
}
);

View File

@ -0,0 +1,34 @@
/*global define*/
define(
['./AbstractStartTimerAction'],
function (AbstractStartTimerAction) {
"use strict";
/**
* Implements the "Start" action for timers.
*
* Sets the reference timestamp in a timer to the current
* time, such that it begins counting up.
*
* @implements Action
*/
function StartTimerAction(now, context) {
return new AbstractStartTimerAction(now, context);
}
StartTimerAction.appliesTo = function (context) {
var model =
(context.domainObject && context.domainObject.getModel())
|| {};
// We show this variant for timers which do not yet have
// a target time.
return model.type === 'warp.timer' &&
model.timestamp === undefined;
};
return StartTimerAction;
}
);

View File

@ -0,0 +1,79 @@
/*global define*/
define(
['moment'],
function (moment) {
"use strict";
/**
* Controller for views of a Clock domain object.
*
* @constructor
*/
function ClockController($scope, tickerService) {
var text,
ampm,
use24,
lastTimestamp,
unlisten,
timeFormat;
function update() {
var m = moment.utc(lastTimestamp);
text = timeFormat && m.format(timeFormat);
ampm = m.format("A"); // Just the AM or PM part
}
function tick(timestamp) {
lastTimestamp = timestamp;
update();
}
function updateFormat(clockFormat) {
var baseFormat;
if (clockFormat !== undefined) {
baseFormat = clockFormat[0];
use24 = clockFormat[1] === 'clock24';
timeFormat = use24 ?
baseFormat.replace('hh', "HH") : baseFormat;
update();
}
}
// Pull in the clock format from the domain object model
$scope.$watch('model.clockFormat', updateFormat);
// Listen for clock ticks ... and stop listening on destroy
unlisten = tickerService.listen(tick);
$scope.$on('$destroy', unlisten);
return {
/**
* Get the clock's time zone, as displayable text.
* @returns {string}
*/
zone: function () {
return "UTC";
},
/**
* Get the current time, as displayable text.
* @returns {string}
*/
text: function () {
return text;
},
/**
* Get the text to display to qualify a time as AM or PM.
* @returns {string}
*/
ampm: function () {
return use24 ? '' : ampm;
}
};
}
return ClockController;
}
);

View File

@ -0,0 +1,29 @@
/*global define*/
define(
[],
function () {
"use strict";
/**
* Continually refreshes the represented domain object.
*
* This is a short-term workaround to assure Timer views stay
* up-to-date; should be replaced by a global auto-refresh.
*/
function RefreshingController($scope, tickerService) {
var unlisten;
function triggerRefresh() {
var persistence = $scope.domainObject &&
$scope.domainObject.getCapability('persistence');
return persistence && persistence.refresh();
}
unlisten = tickerService.listen(triggerRefresh);
$scope.$on('$destroy', unlisten);
}
return RefreshingController;
}
);

View File

@ -0,0 +1,146 @@
/*global define*/
define(
['./TimerFormatter'],
function (TimerFormatter) {
"use strict";
var FORMATTER = new TimerFormatter();
/**
* Controller for views of a Timer domain object.
*
* @constructor
*/
function TimerController($scope, $window, now) {
var timerObject,
relevantAction,
sign = '',
text = '',
formatter,
active = true,
relativeTimestamp,
lastTimestamp;
function update() {
var timeDelta = lastTimestamp - relativeTimestamp;
if (formatter && !isNaN(timeDelta)) {
text = formatter(timeDelta);
sign = timeDelta < 0 ? "-" : timeDelta >= 1000 ? "+" : "";
} else {
text = "";
sign = "";
}
}
function updateFormat(key) {
formatter = FORMATTER[key] || FORMATTER.long;
}
function updateTimestamp(timestamp) {
relativeTimestamp = timestamp;
}
function updateObject(domainObject) {
var model = domainObject.getModel(),
timestamp = model.timestamp,
formatKey = model.timerFormat,
actionCapability = domainObject.getCapability('action'),
actionKey = (timestamp === undefined) ?
'warp.timer.start' : 'warp.timer.restart';
updateFormat(formatKey);
updateTimestamp(timestamp);
relevantAction = actionCapability &&
actionCapability.getActions(actionKey)[0];
update();
}
function handleObjectChange(domainObject) {
if (domainObject) {
updateObject(domainObject);
}
}
function handleModification() {
handleObjectChange($scope.domainObject);
}
function tick() {
var lastSign = sign, lastText = text;
lastTimestamp = now();
update();
// We're running in an animation frame, not in a digest cycle.
// We need to trigger a digest cycle if our displayable data
// changes.
if (lastSign !== sign || lastText !== text) {
$scope.$apply();
}
if (active) {
$window.requestAnimationFrame(tick);
}
}
$window.requestAnimationFrame(tick);
// Pull in the timer format from the domain object model
$scope.$watch('domainObject', handleObjectChange);
$scope.$watch('model.modified', handleModification);
// When the scope is destroyed, stop requesting anim. frames
$scope.$on('$destroy', function () {
active = false;
});
return {
/**
* Get the glyph to display for the start/restart button.
* @returns {string} glyph to display
*/
buttonGlyph: function () {
return relevantAction ?
relevantAction.getMetadata().glyph : "";
},
/**
* Get the text to show for the start/restart button
* (e.g. in a tooltip)
* @returns {string} name of the action
*/
buttonText: function () {
return relevantAction ?
relevantAction.getMetadata().name : "";
},
/**
* Perform the action associated with the start/restart button.
*/
clickButton: function () {
if (relevantAction) {
relevantAction.perform();
updateObject($scope.domainObject);
}
},
/**
* Get the sign (+ or -) of the current timer value, as
* displayable text.
* @returns {string} sign of the current timer value
*/
sign: function () {
return sign;
},
/**
* Get the text to display for the current timer value.
* @returns {string} current timer value
*/
text: function () {
return text;
}
};
}
return TimerController;
}
);

View File

@ -0,0 +1,60 @@
/*global define*/
define(
['moment', 'moment-duration-format'],
function (moment) {
"use strict";
var SHORT_FORMAT = "HH:mm:ss",
LONG_FORMAT = "d[D] HH:mm:ss";
/**
* Provides formatting functions for Timers.
*
* Display formats for timers are a little different from what
* moment.js provides, so we have custom logic here. This specifically
* supports `TimerController`.
*
* @constructor
*/
function TimerFormatter() {
// Round this timestamp down to the second boundary
// (e.g. 1124ms goes down to 1000ms, -2400ms goes down to -3000ms)
function toWholeSeconds(duration) {
return Math.abs(Math.floor(duration / 1000) * 1000);
}
// Short-form format, e.g. 02:22:11
function short(duration) {
return moment.duration(toWholeSeconds(duration), 'ms')
.format(SHORT_FORMAT, { trim: false });
}
// Long-form format, e.g. 3d 02:22:11
function long(duration) {
return moment.duration(toWholeSeconds(duration), 'ms')
.format(LONG_FORMAT, { trim: false });
}
return {
/**
* Format a duration for display, using the short form.
* (e.g. 03:33:11)
* @param {number} duration the duration, in milliseconds
* @param {boolean} sign true if positive
*/
short: short,
/**
* Format a duration for display, using the long form.
* (e.g. 0d 03:33:11)
* @param {number} duration the duration, in milliseconds
* @param {boolean} sign true if positive
*/
long: long
};
}
return TimerFormatter;
}
);

View File

@ -0,0 +1,38 @@
/*global define*/
define(
['moment'],
function (moment) {
"use strict";
/**
* Indicator that displays the current UTC time in the status area.
* @implements Indicator
*/
function ClockIndicator(tickerService, CLOCK_INDICATOR_FORMAT) {
var text = "";
tickerService.listen(function (timestamp) {
text = moment.utc(timestamp).format(CLOCK_INDICATOR_FORMAT) + " UTC";
});
return {
getGlyph: function () {
return "C";
},
getGlyphClass: function () {
return "";
},
getText: function () {
return text;
},
getDescription: function () {
return "";
}
};
}
return ClockIndicator;
}
);

View File

@ -0,0 +1,68 @@
/*global define*/
define(
['moment'],
function (moment) {
"use strict";
/**
* Calls functions every second, as close to the actual second
* tick as is feasible.
* @constructor
* @param $timeout Angular's $timeout
* @param {Function} now function to provide the current time in ms
*/
function TickerService($timeout, now) {
var callbacks = [],
last = now() - 1000;
function tick() {
var timestamp = now(),
millis = timestamp % 1000;
// Only update callbacks if a second has actually passed.
if (timestamp >= last + 1000) {
callbacks.forEach(function (callback) {
callback(timestamp);
});
last = timestamp - millis;
}
// Try to update at exactly the next second
$timeout(tick, 1000 - millis, true);
}
tick();
return {
/**
* Listen for clock ticks. The provided callback will
* be invoked with the current timestamp (in milliseconds
* since Jan 1 1970) at regular intervals, as near to the
* second boundary as possible.
*
* @method listen
* @name TickerService#listen
* @param {Function} callback callback to invoke
* @returns {Function} a function to unregister this listener
*/
listen: function (callback) {
callbacks.push(callback);
// Provide immediate feedback
callback(last);
// Provide a deregistration function
return function () {
callbacks = callbacks.filter(function (cb) {
return cb !== callback;
});
};
}
};
}
return TickerService;
}
);