Merge remote-tracking branch 'github/master' into open250

This commit is contained in:
Charles Hacskaylo 2015-11-06 13:32:28 -08:00
commit 76e15f2963
145 changed files with 13539 additions and 182 deletions

View File

@ -345,6 +345,41 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
---
### moment-duration-format
#### Info
* Link: https://github.com/jsmreese/moment-duration-format
* Version: 1.3.0
* Authors: John Madhavan-Reese
* Description: Duration parsing/formatting
#### License
Copyright 2014 John Madhavan-Reese
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
### Json.NET
#### Info

View File

@ -15,6 +15,7 @@
"platform/containment",
"platform/execution",
"platform/telemetry",
"platform/features/clock",
"platform/features/imagery",
"platform/features/layout",
"platform/features/pages",

9
docs/footer.html Normal file
View File

@ -0,0 +1,9 @@
<hr>
<cite>
This document is styled using
<a href="https://github.com/jasonm23/markdown-css-themes">
https://github.com/jasonm23/markdown-css-themes
</a>.
</cite>
</body>
</html>

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global require,process,GLOBAL*/
/*global require,process,__dirname,GLOBAL*/
/*jslint nomen: false */
@ -47,6 +47,8 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define
nomnoml = require('nomnoml'),
toc = require("markdown-toc"),
Canvas = require('canvas'),
header = fs.readFileSync(path.resolve(__dirname, 'header.html')),
footer = fs.readFileSync(path.resolve(__dirname, 'footer.html')),
options = require("minimist")(process.argv.slice(2));
// Convert from nomnoml source to a target PNG file.
@ -115,9 +117,9 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define
// Prepend table of contents
markdown =
[ TOC_HEAD, toc(markdown).content, "", markdown ].join("\n");
this.push("<html><body>\n");
this.push(header);
this.push(marked(markdown));
this.push("\n</body></html>\n");
this.push(footer);
done();
};
return transform;
@ -186,12 +188,12 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define
var destination = file.replace(options['in'], options.out),
destPath = path.dirname(destination),
streamOptions = {};
if (file.match(/png$/)){
if (file.match(/png$/)) {
streamOptions.encoding = null;
} else {
streamOptions.encoding = 'utf8';
}
mkdirp(destPath, function (err) {
fs.createReadStream(file, streamOptions)
.pipe(fs.createWriteStream(destination, streamOptions));

7
docs/header.html Normal file
View File

@ -0,0 +1,7 @@
<html>
<head>
<link rel="stylesheet"
href="http://jasonm23.github.io/markdown-css-themes/avenir-white.css">
</head>
<body>

View File

@ -1106,6 +1106,8 @@ property:
* `stylesheetUrl`: Path and filename, including extension, for the stylesheet to
include. This path is relative to the bundle's resources folder (by default,
`res`)
* `theme`: Optional; if present, this stylesheet will only be included if this
value matches the `THEME` constant.
To control the order of CSS files, use priority (see the section on Extension
Definitions above.)
@ -2323,7 +2325,12 @@ default paths to reach external services are all correct.
### Configuration Constants
The following configuration constants are recognized by Open MCT Web bundles:
* Common UI elements - `platform/commonUI/general`
* `THEME`: A string identifying the current theme symbolically. Individual
stylesheets (the `stylesheets` extension category) may specify an optional
`theme` property which will be matched against this before inclusion.
* CouchDB adapter - `platform/persistence/couch`
* `COUCHDB_PATH`: URL or path to the CouchDB database to be used for domain
object persistence. Should not include a trailing slash.

View File

@ -34,7 +34,6 @@ module.exports = function(config) {
// List of files / patterns to load in the browser.
// By default, files are also included in a script tag.
files: [
'**/moment*',
{pattern: 'example/**/*.js', included: false},
{pattern: 'platform/**/*.js', included: false},
{pattern: 'warp/**/*.js', included: false},

View File

@ -181,7 +181,7 @@ define(
* @typedef DialogOption
* @property {string} label a label to be displayed as the button
* text for this action
* @property {function} action a function to be called when the
* @property {function} callback a function to be called when the
* button is clicked
*/

View File

@ -18,7 +18,7 @@
"runs": [
{
"implementation": "StyleSheetLoader.js",
"depends": [ "stylesheets[]", "$document" ]
"depends": [ "stylesheets[]", "$document", "THEME" ]
}
],
"stylesheets": [
@ -194,6 +194,11 @@
{
"key": "MCT_SCROLL_Y_ATTRIBUTE",
"value": "mctScrollY"
},
{
"key": "THEME",
"value": "unspecified",
"priority": "fallback"
}
],
"containers": [

View File

@ -38,8 +38,9 @@ define(
* @constructor
* @param {object[]} stylesheets stylesheet extension definitions
* @param $document Angular's jqLite-wrapped document element
* @param {string} activeTheme the theme in use
*/
function StyleSheetLoader(stylesheets, $document) {
function StyleSheetLoader(stylesheets, $document, activeTheme) {
var head = $document.find('head'),
document = $document[0];
@ -62,8 +63,15 @@ define(
head.append(link);
}
// Stylesheets which specify themes should only be applied
// when that theme has been declared.
function matchesTheme(stylesheet) {
return stylesheet.theme === undefined ||
stylesheet.theme === activeTheme;
}
// Add all stylesheets from extensions
stylesheets.forEach(addStyleSheet);
stylesheets.filter(matchesTheme).forEach(addStyleSheet);
}
return StyleSheetLoader;

View File

@ -32,10 +32,11 @@ define(
mockPlainDocument,
mockHead,
mockElement,
testBundle,
loader;
beforeEach(function () {
var testBundle = {
testBundle = {
path: "a/b",
resources: "c"
};
@ -72,6 +73,40 @@ define(
expect(mockElement.setAttribute)
.toHaveBeenCalledWith('href', "a/b/c/d.css");
});
describe("for themed stylesheets", function () {
var testTheme = "test-theme";
beforeEach(function () {
testStyleSheets = [{
stylesheetUrl: "themed.css",
bundle: testBundle,
theme: testTheme
}, {
stylesheetUrl: "bad-theme.css",
bundle: testBundle,
theme: 'bad-theme'
}];
loader = new StyleSheetLoader(
testStyleSheets,
mockDocument,
testTheme
);
});
it("includes matching themes", function () {
expect(mockElement.setAttribute)
.toHaveBeenCalledWith('href', "a/b/c/themed.css");
});
it("excludes mismatching themes", function () {
expect(mockElement.setAttribute)
.not.toHaveBeenCalledWith('href', "a/b/c/bad-theme.css");
});
});
});
}
);

View File

@ -85,7 +85,7 @@ define(
function link(scope, element, attrs, ctrl, transclude) {
if (deviceMatches(attrs.mctDevice)) {
transclude(function (clone) {
element.parent().append(clone);
element.replaceWith(clone);
});
}
}

View File

@ -26,13 +26,12 @@ define(
function (MCTDevice) {
"use strict";
var JQLITE_METHODS = [ 'parent', 'append' ];
var JQLITE_METHODS = [ 'replaceWith' ];
describe("The mct-device directive", function () {
var mockAgentService,
mockTransclude,
mockElement,
mockParent,
mockClone,
testAttrs,
directive;
@ -48,11 +47,8 @@ define(
);
mockTransclude = jasmine.createSpy("$transclude");
mockElement = jasmine.createSpyObj(name, JQLITE_METHODS);
mockParent = jasmine.createSpyObj(name, JQLITE_METHODS);
mockClone = jasmine.createSpyObj(name, JQLITE_METHODS);
mockElement.parent.andReturn(mockParent);
mockTransclude.andCallFake(function (fn) {
fn(mockClone);
});
@ -65,6 +61,15 @@ define(
directive = new MCTDevice(mockAgentService);
});
function expectInclusion() {
expect(mockElement.replaceWith)
.toHaveBeenCalledWith(mockClone);
}
function expectExclusion() {
expect(mockElement.replaceWith).not.toHaveBeenCalled();
}
it("is applicable at the attribute level", function () {
expect(directive.restrict).toEqual("A");
});
@ -80,54 +85,54 @@ define(
it("restricts element inclusion for mobile devices", function () {
testAttrs.mctDevice = "mobile";
link();
expect(mockParent.append).not.toHaveBeenCalled();
expectExclusion();
mockAgentService.isMobile.andReturn(true);
link();
expect(mockParent.append).toHaveBeenCalledWith(mockClone);
expectInclusion();
});
it("restricts element inclusion for tablet devices", function () {
testAttrs.mctDevice = "tablet";
mockAgentService.isMobile.andReturn(true);
link();
expect(mockParent.append).not.toHaveBeenCalled();
expectExclusion();
mockAgentService.isTablet.andReturn(true);
link();
expect(mockParent.append).toHaveBeenCalledWith(mockClone);
expectInclusion();
});
it("restricts element inclusion for phone devices", function () {
testAttrs.mctDevice = "phone";
mockAgentService.isMobile.andReturn(true);
link();
expect(mockParent.append).not.toHaveBeenCalled();
expectExclusion();
mockAgentService.isPhone.andReturn(true);
link();
expect(mockParent.append).toHaveBeenCalledWith(mockClone);
expectInclusion();
});
it("restricts element inclusion for desktop devices", function () {
testAttrs.mctDevice = "desktop";
mockAgentService.isMobile.andReturn(true);
link();
expect(mockParent.append).not.toHaveBeenCalled();
expectExclusion();
mockAgentService.isMobile.andReturn(false);
link();
expect(mockParent.append).toHaveBeenCalledWith(mockClone);
expectInclusion();
});
it("restricts element inclusion for portrait orientation", function () {
testAttrs.mctDevice = "portrait";
link();
expect(mockParent.append).not.toHaveBeenCalled();
expectExclusion();
mockAgentService.isPortrait.andReturn(true);
link();
expect(mockParent.append).toHaveBeenCalledWith(mockClone);
expectInclusion();
});
it("restricts element inclusion for landscape orientation", function () {
@ -135,11 +140,11 @@ define(
mockAgentService.isLandscape.andReturn(false);
mockAgentService.isPortrait.andReturn(true);
link();
expect(mockParent.append).not.toHaveBeenCalled();
expectExclusion();
mockAgentService.isLandscape.andReturn(true);
link();
expect(mockParent.append).toHaveBeenCalledWith(mockClone);
expectInclusion();
});
it("allows multiple device characteristics to be requested", function () {
@ -148,17 +153,17 @@ define(
testAttrs.mctDevice = "portrait mobile";
link();
// Neither portrait nor mobile, not called
expect(mockParent.append).not.toHaveBeenCalled();
expectExclusion();
mockAgentService.isPortrait.andReturn(true);
link();
// Was portrait, but not mobile, so no
expect(mockParent.append).not.toHaveBeenCalled();
expectExclusion();
mockAgentService.isMobile.andReturn(true);
link();
expect(mockParent.append).toHaveBeenCalledWith(mockClone);
expectInclusion();
});
});
}

View File

@ -1,12 +1,18 @@
{
"name": "Espresso",
"description": "Espresso theme: dark and rich",
"extensions": {
"stylesheets": [
{
"stylesheetUrl": "css/theme-espresso.css",
"priority": 1000
}
]
}
}
"name": "Espresso",
"description": "Espresso theme: dark and rich",
"extensions": {
"stylesheets": [
{
"stylesheetUrl": "css/theme-espresso.css",
"priority": 1000
}
],
"constants": [
{
"key": "THEME",
"value": "espresso"
}
]
}
}

View File

@ -1,12 +1,18 @@
{
"name": "Sonw",
"description": "Snow theme: light and cool",
"extensions": {
"stylesheets": [
{
"stylesheetUrl": "css/theme-snow.css",
"priority": 1000
}
]
}
}
"name": "Snow",
"description": "Snow theme: light and cool",
"extensions": {
"stylesheets": [
{
"stylesheetUrl": "css/theme-snow.css",
"priority": 1000
}
],
"constants": [
{
"key": "THEME",
"value": "snow"
}
]
}
}

View File

@ -72,20 +72,6 @@ $colorInputBg: $colorGenBg;
$colorInputFg: $colorBodyFg;
$colorFormText: pushBack($colorBodyFg, 10%);
$colorInputIcon: pushBack($colorBodyFg, 25%);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #fff;
$colorStatusDefault: #ccc;
$colorStatusInfo: #60ba7b;
$colorStatusAlert: #ffb66c;
$colorStatusError: #c96b68;
$colorProgressBarOuter: rgba(#000, 0.1);
$colorProgressBarAmt: #0a0;
$progressBarHOverlay: 15px;
$progressBarStripeW: 20px;
$shdwStatusIc: rgba(white, 0.8) 0 0px 5px;
// Selects
$colorSelectBg: #ddd;
$colorSelectFg: $colorBodyFg;

View File

@ -20,7 +20,8 @@
"glyph": "+",
"category": "contextual",
"implementation": "actions/CopyAction.js",
"depends": ["locationService", "copyService"]
"depends": ["$log", "locationService", "copyService",
"dialogService", "notificationService"]
},
{
"key": "link",
@ -84,7 +85,8 @@
"name": "Copy Service",
"description": "Provides a service for copying objects",
"implementation": "services/CopyService.js",
"depends": ["$q", "creationService", "policyService"]
"depends": ["$q", "creationService", "policyService",
"persistenceService", "now"]
},
{
"key": "locationService",

View File

@ -34,16 +34,103 @@ define(
* @constructor
* @memberof platform/entanglement
*/
function CopyAction(locationService, copyService, context) {
return new AbstractComposeAction(
locationService,
copyService,
context,
"Duplicate",
"to a location"
);
function CopyAction($log, locationService, copyService, dialogService,
notificationService, context) {
this.dialog = undefined;
this.notification = undefined;
this.dialogService = dialogService;
this.notificationService = notificationService;
this.$log = $log;
//Extend the behaviour of the Abstract Compose Action
AbstractComposeAction.call(this, locationService, copyService,
context, "Duplicate", "to a location");
}
/**
* Updates user about progress of copy. Should not be invoked by
* client code under any circumstances.
*
* @private
* @param phase
* @param totalObjects
* @param processed
*/
CopyAction.prototype.progress = function(phase, totalObjects, processed){
/*
Copy has two distinct phases. In the first phase a copy plan is
made in memory. During this phase of execution, the user is
shown a blocking 'modal' dialog.
In the second phase, the copying is taking place, and the user
is shown non-invasive banner notifications at the bottom of the screen.
*/
if (phase.toLowerCase() === 'preparing' && !this.dialog){
this.dialog = this.dialogService.showBlockingMessage({
title: "Preparing to copy objects",
unknownProgress: true,
severity: "info"
});
} else if (phase.toLowerCase() === "copying") {
this.dialogService.dismiss();
if (!this.notification) {
this.notification = this.notificationService
.notify({
title: "Copying objects",
unknownProgress: false,
severity: "info"
});
}
this.notification.model.progress = (processed / totalObjects) * 100;
this.notification.model.title = ["Copied ", processed, "of ",
totalObjects, "objects"].join(" ");
}
};
/**
* Executes the CopyAction. The CopyAction uses the default behaviour of
* the AbstractComposeAction, but extends it to support notification
* updates of progress on copy.
*/
CopyAction.prototype.perform = function() {
var self = this;
function success(){
self.notification.dismiss();
self.notificationService.info("Copying complete.");
}
function error(errorDetails){
var errorMessage = {
title: "Error copying objects.",
severity: "error",
hint: errorDetails.message,
minimized: true, // want the notification to be minimized initially (don't show banner)
options: [{
label: "OK",
callback: function() {
self.dialogService.dismiss();
}
}]
};
self.dialogService.dismiss();
if (self.notification) {
self.notification.dismiss(); // Clear the progress notification
}
self.$log.error("Error copying objects. ", errorDetails);
//Show a minimized notification of error for posterity
self.notificationService.notify(errorMessage);
//Display a blocking message
self.dialogService.showBlockingMessage(errorMessage);
}
function notification(details){
self.progress(details.phase, details.totalObjects, details.processed);
}
return AbstractComposeAction.prototype.perform.call(this)
.then(success, error, notification);
};
return CopyAction;
}
);

View File

@ -23,7 +23,11 @@
/*global define */
define(
function () {
[
"uuid",
"./CopyTask"
],
function (uuid, CopyTask) {
"use strict";
/**
@ -34,10 +38,12 @@ define(
* @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService}
*/
function CopyService($q, creationService, policyService) {
function CopyService($q, creationService, policyService, persistenceService, now) {
this.$q = $q;
this.creationService = creationService;
this.policyService = policyService;
this.persistenceService = persistenceService;
this.now = now;
}
CopyService.prototype.validate = function (object, parentCandidate) {
@ -54,45 +60,25 @@ define(
);
};
/**
* Creates a duplicate of the object tree starting at domainObject to
* the new parent specified.
* @param domainObject
* @param parent
* @param progress
* @returns a promise that will be completed with the clone of
* domainObject when the duplication is successful.
*/
CopyService.prototype.perform = function (domainObject, parent) {
var model = JSON.parse(JSON.stringify(domainObject.getModel())),
$q = this.$q,
self = this;
// Wrapper for the recursive step
function duplicateObject(domainObject, parent) {
return self.perform(domainObject, parent);
}
if (!this.validate(domainObject, parent)) {
var $q = this.$q,
copyTask = new CopyTask(domainObject, parent, this.persistenceService, this.$q, this.now);
if (this.validate(domainObject, parent)) {
return copyTask.perform();
} else {
throw new Error(
"Tried to copy objects without validating first."
);
}
if (domainObject.hasCapability('composition')) {
model.composition = [];
}
return this.creationService
.createObject(model, parent)
.then(function (newObject) {
if (!domainObject.hasCapability('composition')) {
return;
}
return domainObject
.useCapability('composition')
.then(function (composees) {
// Duplicate composition serially to prevent
// write conflicts.
return composees.reduce(function (promise, composee) {
return promise.then(function () {
return duplicateObject(composee, newObject);
});
}, $q.when(undefined));
});
});
};
return CopyService;

View File

@ -0,0 +1,198 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define */
define(
["uuid"],
function (uuid) {
"use strict";
/**
* This class encapsulates the process of copying a domain object
* and all of its children.
*
* @param domainObject The object to copy
* @param parent The new location of the cloned object tree
* @param persistenceService
* @param $q
* @param now
* @constructor
*/
function CopyTask (domainObject, parent, persistenceService, $q, now){
this.domainObject = domainObject;
this.parent = parent;
this.$q = $q;
this.deferred = undefined;
this.persistenceService = persistenceService;
this.persisted = 0;
this.now = now;
this.clones = [];
}
function composeChild(child, parent) {
//Once copied, associate each cloned
// composee with its parent clone
child.model.location = parent.id;
parent.model.composition = parent.model.composition || [];
return parent.model.composition.push(child.id);
}
function cloneObjectModel(objectModel) {
var clone = JSON.parse(JSON.stringify(objectModel));
delete clone.composition;
delete clone.persisted;
delete clone.modified;
return clone;
}
/**
* Will persist a list of {@link objectClones}. It will persist all
* simultaneously, irrespective of order in the list. This may
* result in automatic request batching by the browser.
*/
function persistObjects(self) {
return self.$q.all(self.clones.map(function(clone){
clone.model.persisted = self.now();
return self.persistenceService.createObject(clone.persistenceSpace, clone.id, clone.model)
.then(function(){
self.deferred.notify({phase: "copying", totalObjects: self.clones.length, processed: ++self.persisted});
});
})).then(function(){
return self;
});
}
/**
* Will add a list of clones to the specified parent's composition
*/
function addClonesToParent(self) {
var parentClone = self.clones[self.clones.length-1];
if (!self.parent.hasCapability('composition')){
return self.$q.reject();
}
return self.persistenceService
.updateObject(parentClone.persistenceSpace, parentClone.id, parentClone.model)
.then(function(){return self.parent.getCapability("composition").add(parentClone.id);})
.then(function(){return self.parent.getCapability("persistence").persist();})
.then(function(){return parentClone;});
// Ensure the clone of the original domainObject is returned
}
/**
* Given an array of objects composed by a parent, clone them, then
* add them to the parent.
* @private
* @returns {*}
*/
CopyTask.prototype.copyComposees = function(composees, clonedParent, originalParent){
var self = this;
return (composees || []).reduce(function(promise, composee){
//If the composee is composed of other
// objects, chain a promise..
return promise.then(function(){
// ...to recursively copy it (and its children)
return self.copy(composee, originalParent).then(function(composee){
composeChild(composee, clonedParent);
});
});}, self.$q.when(undefined)
);
};
/**
* A recursive function that will perform a bottom-up copy of
* the object tree with originalObject at the root. Recurses to
* the farthest leaf, then works its way back up again,
* cloning objects, and composing them with their child clones
* as it goes
* @private
* @param originalObject
* @param originalParent
* @returns {*}
*/
CopyTask.prototype.copy = function(originalObject, originalParent) {
var self = this,
modelClone = {
id: uuid(),
model: cloneObjectModel(originalObject.getModel()),
persistenceSpace: originalParent.hasCapability('persistence') && originalParent.getCapability('persistence').getSpace()
};
return this.$q.when(originalObject.useCapability('composition')).then(function(composees){
self.deferred.notify({phase: "preparing"});
//Duplicate the object's children, and their children, and
// so on down to the leaf nodes of the tree.
return self.copyComposees(composees, modelClone, originalObject).then(function (){
//Add the clone to the list of clones that will
//be returned by this function
self.clones.push(modelClone);
return modelClone;
});
});
};
/**
* Will build a graph of an object and all of its child objects in
* memory
* @private
* @param domainObject The original object to be copied
* @param parent The parent of the original object to be copied
* @returns {Promise} resolved with an array of clones of the models
* of the object tree being copied. Copying is done in a bottom-up
* fashion, so that the last member in the array is a clone of the model
* object being copied. The clones are all full composed with
* references to their own children.
*/
CopyTask.prototype.buildCopyPlan = function() {
var self = this;
return this.copy(self.domainObject, self.parent).then(function(domainObjectClone){
domainObjectClone.model.location = self.parent.getId();
return self;
});
};
/**
* Execute the copy task with the objects provided in the constructor.
* @returns {promise} Which will resolve with a clone of the object
* once complete.
*/
CopyTask.prototype.perform = function(){
this.deferred = this.$q.defer();
this.buildCopyPlan()
.then(persistObjects)
.then(addClonesToParent)
.then(this.deferred.resolve, this.deferred.reject);
return this.deferred.promise;
};
return CopyTask;
}
);

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,beforeEach,it,jasmine,expect */
/*global define,describe,beforeEach,it,jasmine,expect,spyOn */
define(
[
@ -41,7 +41,13 @@ define(
selectedObject,
selectedObjectContextCapability,
currentParent,
newParent;
newParent,
notificationService,
notification,
dialogService,
mockLog,
abstractComposePromise,
progress = {phase: "copying", totalObjects: 10, processed: 1};
beforeEach(function () {
selectedObjectContextCapability = jasmine.createSpyObj(
@ -87,10 +93,43 @@ define(
]
);
abstractComposePromise = jasmine.createSpyObj(
'abstractComposePromise',
[
'then'
]
);
abstractComposePromise.then.andCallFake(function(success, error, notify){
notify(progress);
success();
});
locationServicePromise.then.andCallFake(function(callback){
callback(newParent);
return abstractComposePromise;
});
locationService
.getLocationFromUser
.andReturn(locationServicePromise);
dialogService = jasmine.createSpyObj('dialogService',
['showBlockingMessage', 'dismiss']
);
notification = jasmine.createSpyObj('notification',
['dismiss', 'model']
);
notificationService = jasmine.createSpyObj('notificationService',
['notify', 'info']
);
notificationService.notify.andReturn(notification);
mockLog = jasmine.createSpyObj('log', ['error']);
copyService = new MockCopyService();
});
@ -102,8 +141,11 @@ define(
};
copyAction = new CopyAction(
mockLog,
locationService,
copyService,
dialogService,
notificationService,
context
);
});
@ -114,6 +156,7 @@ define(
describe("when performed it", function () {
beforeEach(function () {
spyOn(copyAction, 'progress').andCallThrough();
copyAction.perform();
});
@ -132,7 +175,7 @@ define(
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("copys object to selected location", function () {
it("copies object to selected location", function () {
locationServicePromise
.then
.mostRecentCall
@ -141,6 +184,11 @@ define(
expect(copyService.perform)
.toHaveBeenCalledWith(selectedObject, newParent);
});
it("notifies the user of progress", function(){
expect(notificationService.info).toHaveBeenCalled();
});
});
});
@ -152,8 +200,11 @@ define(
};
copyAction = new CopyAction(
mockLog,
locationService,
copyService,
dialogService,
notificationService,
context
);
});

View File

@ -31,6 +31,10 @@ define(
"use strict";
function synchronousPromise(value) {
if (value && value.then) {
return value;
}
var promise = {
then: function (callback) {
return synchronousPromise(callback(value));
@ -122,13 +126,19 @@ define(
describe("perform", function () {
var mockQ,
mockDeferred,
creationService,
createObjectPromise,
copyService,
mockPersistenceService,
mockNow,
object,
newParent,
copyResult,
copyFinished;
copyFinished,
persistObjectPromise,
parentPersistenceCapability,
resolvedValue;
beforeEach(function () {
creationService = jasmine.createSpyObj(
@ -138,44 +148,93 @@ define(
createObjectPromise = synchronousPromise(undefined);
creationService.createObject.andReturn(createObjectPromise);
policyService.allow.andReturn(true);
mockPersistenceService = jasmine.createSpyObj(
'persistenceService',
['createObject', 'updateObject']
);
persistObjectPromise = synchronousPromise(undefined);
mockPersistenceService.createObject.andReturn(persistObjectPromise);
mockPersistenceService.updateObject.andReturn(persistObjectPromise);
parentPersistenceCapability = jasmine.createSpyObj(
"persistence",
[ "persist", "getSpace" ]
);
parentPersistenceCapability.persist.andReturn(persistObjectPromise);
parentPersistenceCapability.getSpace.andReturn("testSpace");
mockNow = jasmine.createSpyObj("mockNow", ["now"]);
mockNow.now.andCallFake(function(){
return 1234;
});
mockDeferred = jasmine.createSpyObj('mockDeferred', ['notify', 'resolve']);
mockDeferred.notify.andCallFake(function(notification){});
mockDeferred.resolve.andCallFake(function(value){resolvedValue = value;});
mockDeferred.promise = {
then: function(callback){
return synchronousPromise(callback(resolvedValue));
}
};
mockQ = jasmine.createSpyObj('mockQ', ['when', 'all', 'reject', 'defer']);
mockQ.when.andCallFake(synchronousPromise);
mockQ.all.andCallFake(function (promises) {
var result = {};
Object.keys(promises).forEach(function (k) {
promises[k].then(function (v) { result[k] = v; });
});
return synchronousPromise(result);
});
mockQ.defer.andReturn(mockDeferred);
});
describe("on domain object without composition", function () {
beforeEach(function () {
object = domainObjectFactory({
name: 'object',
id: 'abc',
model: {
name: 'some object'
}
});
newParent = domainObjectFactory({
name: 'newParent',
id: '456',
model: {
composition: []
},
capabilities: {
persistence: parentPersistenceCapability
}
});
copyService = new CopyService(null, creationService, policyService);
object = domainObjectFactory({
name: 'object',
id: 'abc',
model: {
name: 'some object',
location: newParent.id,
persisted: mockNow.now()
}
});
copyService = new CopyService(mockQ, creationService, policyService, mockPersistenceService, mockNow.now);
copyResult = copyService.perform(object, newParent);
copyFinished = jasmine.createSpy('copyFinished');
copyResult.then(copyFinished);
});
it("uses creation service", function () {
expect(creationService.createObject)
.toHaveBeenCalledWith(jasmine.any(Object), newParent);
expect(createObjectPromise.then)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("uses persistence service", function () {
expect(mockPersistenceService.createObject)
.toHaveBeenCalledWith(parentPersistenceCapability.getSpace(), jasmine.any(String), object.getModel());
expect(persistObjectPromise.then)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("deep clones object model", function () {
var newModel = creationService
//var newModel = creationService
var newModel = mockPersistenceService
.createObject
.mostRecentCall
.args[0];
.args[2];
expect(newModel).toEqual(object.model);
expect(newModel).not.toBe(object.model);
});
@ -191,11 +250,15 @@ define(
var newObject,
childObject,
compositionCapability,
locationCapability,
compositionPromise;
beforeEach(function () {
mockQ = jasmine.createSpyObj('mockQ', ['when']);
mockQ.when.andCallFake(synchronousPromise);
locationCapability = jasmine.createSpyObj('locationCapability', ['isLink']);
locationCapability.isLink.andReturn(true);
childObject = domainObjectFactory({
name: 'childObject',
id: 'def',
@ -205,24 +268,28 @@ define(
});
compositionCapability = jasmine.createSpyObj(
'compositionCapability',
['invoke']
['invoke', 'add']
);
compositionPromise = jasmine.createSpyObj(
'compositionPromise',
['then']
);
compositionCapability
.invoke
.andReturn(compositionPromise);
.andReturn(synchronousPromise([childObject]));
object = domainObjectFactory({
name: 'object',
id: 'abc',
model: {
name: 'some object',
composition: ['def']
composition: ['def'],
location: 'testLocation'
},
capabilities: {
composition: compositionCapability
composition: compositionCapability,
location: locationCapability
}
});
newObject = domainObjectFactory({
@ -241,45 +308,45 @@ define(
id: '456',
model: {
composition: []
},
capabilities: {
composition: compositionCapability,
persistence: parentPersistenceCapability
}
});
createObjectPromise = synchronousPromise(newObject);
creationService.createObject.andReturn(createObjectPromise);
copyService = new CopyService(mockQ, creationService, policyService);
copyResult = copyService.perform(object, newParent);
copyFinished = jasmine.createSpy('copyFinished');
copyResult.then(copyFinished);
copyService = new CopyService(mockQ, creationService, policyService, mockPersistenceService, mockNow.now);
});
it("uses creation service", function () {
expect(creationService.createObject)
.toHaveBeenCalledWith(jasmine.any(Object), newParent);
describe("the cloning process", function(){
beforeEach(function() {
copyResult = copyService.perform(object, newParent);
copyFinished = jasmine.createSpy('copyFinished');
copyResult.then(copyFinished);
});
expect(createObjectPromise.then)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("copies object and children in a bottom-up" +
" fashion", function () {
expect(mockPersistenceService.createObject.calls[0].args[2].name).toEqual(childObject.model.name);
expect(mockPersistenceService.createObject.calls[1].args[2].name).toEqual(object.model.name);
});
it("clears model composition", function () {
var newModel = creationService
.createObject
.mostRecentCall
.args[0];
it("returns a promise", function () {
expect(copyResult.then).toBeDefined();
expect(copyFinished).toHaveBeenCalled();
});
expect(newModel.composition.length).toBe(0);
expect(newModel.name).toBe('some object');
});
it("clears modified and sets persisted", function () {
expect(copyFinished.mostRecentCall.args[0].model.modified).toBeUndefined();
expect(copyFinished.mostRecentCall.args[0].model.persisted).toBe(mockNow.now());
});
it("recursively clones it's children", function () {
expect(creationService.createObject.calls.length).toBe(1);
expect(compositionCapability.invoke).toHaveBeenCalled();
compositionPromise.then.mostRecentCall.args[0]([childObject]);
expect(creationService.createObject.calls.length).toBe(2);
});
it ("correctly locates cloned objects", function() {
expect(mockPersistenceService.createObject.calls[0].args[2].location).toEqual(mockPersistenceService.createObject.calls[1].args[1]);
});
it("returns a promise", function () {
expect(copyResult.then).toBeDefined();
expect(copyFinished).toHaveBeenCalled();
});
});
@ -301,7 +368,7 @@ define(
it("throws an error", function () {
var copyService =
new CopyService(mockQ, creationService, policyService);
new CopyService(mockQ, creationService, policyService, mockPersistenceService, mockNow.now);
function perform() {
copyService.perform(object, newParent);

View File

@ -0,0 +1,178 @@
{
"name": "Clocks/Timers",
"descriptions": "Domain objects for displaying current & relative times.",
"configuration": {
"paths": {
"moment-duration-format": "moment-duration-format"
},
"shim": {
"moment-duration-format": {
"deps": [ "moment" ]
}
}
},
"extensions": {
"constants": [
{
"key": "CLOCK_INDICATOR_FORMAT",
"value": "YYYY/MM/DD HH:mm:ss"
}
],
"indicators": [
{
"implementation": "indicators/ClockIndicator.js",
"depends": [ "tickerService", "CLOCK_INDICATOR_FORMAT" ],
"priority": "preferred"
}
],
"services": [
{
"key": "tickerService",
"implementation": "services/TickerService.js",
"depends": [ "$timeout", "now" ]
}
],
"controllers": [
{
"key": "ClockController",
"implementation": "controllers/ClockController.js",
"depends": [ "$scope", "tickerService" ]
},
{
"key": "TimerController",
"implementation": "controllers/TimerController.js",
"depends": [ "$scope", "$window", "now" ]
},
{
"key": "RefreshingController",
"implementation": "controllers/RefreshingController.js",
"depends": [ "$scope", "tickerService" ]
}
],
"views": [
{
"key": "clock",
"type": "clock",
"templateUrl": "templates/clock.html"
},
{
"key": "timer",
"type": "timer",
"templateUrl": "templates/timer.html"
}
],
"actions": [
{
"key": "timer.start",
"implementation": "actions/StartTimerAction.js",
"depends": ["now"],
"category": "contextual",
"name": "Start",
"glyph": "\u00EF",
"priority": "preferred"
},
{
"key": "timer.restart",
"implementation": "actions/RestartTimerAction.js",
"depends": ["now"],
"category": "contextual",
"name": "Restart at 0",
"glyph": "r",
"priority": "preferred"
}
],
"types": [
{
"key": "clock",
"name": "Clock",
"glyph": "C",
"features": [ "creation" ],
"properties": [
{
"key": "clockFormat",
"name": "Display Format",
"control": "composite",
"items": [
{
"control": "select",
"options": [
{
"value": "YYYY/MM/DD hh:mm:ss",
"name": "YYYY/MM/DD hh:mm:ss"
},
{
"value": "YYYY/DDD hh:mm:ss",
"name": "YYYY/DDD hh:mm:ss"
},
{
"value": "hh:mm:ss",
"name": "hh:mm:ss"
}
]
},
{
"control": "select",
"options": [
{
"value": "clock12",
"name": "12hr"
},
{
"value": "clock24",
"name": "24hr"
}
]
}
]
}
],
"model": {
"clockFormat": [ "YYYY/MM/DD hh:mm:ss", "clock12" ]
}
},
{
"key": "timer",
"name": "Timer",
"glyph": "\u00F5",
"features": [ "creation" ],
"properties": [
{
"key": "timestamp",
"control": "datetime",
"name": "Target"
},
{
"key": "timerFormat",
"control": "select",
"options": [
{
"value": "long",
"name": "DDD hh:mm:ss"
},
{
"value": "short",
"name": "hh:mm:ss"
}
]
}
],
"model": {
"timerFormat": "DDD hh:mm:ss"
}
}
],
"licenses": [
{
"name": "moment-duration-format",
"version": "1.3.0",
"author": "John Madhavan-Reese",
"description": "Duration parsing/formatting",
"website": "https://github.com/jsmreese/moment-duration-format",
"copyright": "Copyright 2014 John Madhavan-Reese",
"license": "license-mit",
"link": "https://github.com/jsmreese/moment-duration-format/blob/master/LICENSE"
}
]
}
}

View File

@ -0,0 +1,482 @@
/*! Moment Duration Format v1.3.0
* https://github.com/jsmreese/moment-duration-format
* Date: 2014-07-15
*
* Duration format plugin function for the Moment.js library
* http://momentjs.com/
*
* Copyright 2014 John Madhavan-Reese
* Released under the MIT license
*/
(function (root, undefined) {
// repeatZero(qty)
// returns "0" repeated qty times
function repeatZero(qty) {
var result = "";
// exit early
// if qty is 0 or a negative number
// or doesn't coerce to an integer
qty = parseInt(qty, 10);
if (!qty || qty < 1) { return result; }
while (qty) {
result += "0";
qty -= 1;
}
return result;
}
// padZero(str, len [, isRight])
// pads a string with zeros up to a specified length
// will not pad a string if its length is aready
// greater than or equal to the specified length
// default output pads with zeros on the left
// set isRight to `true` to pad with zeros on the right
function padZero(str, len, isRight) {
if (str == null) { str = ""; }
str = "" + str;
return (isRight ? str : "") + repeatZero(len - str.length) + (isRight ? "" : str);
}
// isArray
function isArray(array) {
return Object.prototype.toString.call(array) === "[object Array]";
}
// isObject
function isObject(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
}
// findLast
function findLast(array, callback) {
var index = array.length;
while (index -= 1) {
if (callback(array[index])) { return array[index]; }
}
}
// find
function find(array, callback) {
var index = 0,
max = array.length,
match;
if (typeof callback !== "function") {
match = callback;
callback = function (item) {
return item === match;
};
}
while (index < max) {
if (callback(array[index])) { return array[index]; }
index += 1;
}
}
// each
function each(array, callback) {
var index = 0,
max = array.length;
if (!array || !max) { return; }
while (index < max) {
if (callback(array[index], index) === false) { return; }
index += 1;
}
}
// map
function map(array, callback) {
var index = 0,
max = array.length,
ret = [];
if (!array || !max) { return ret; }
while (index < max) {
ret[index] = callback(array[index], index);
index += 1;
}
return ret;
}
// pluck
function pluck(array, prop) {
return map(array, function (item) {
return item[prop];
});
}
// compact
function compact(array) {
var ret = [];
each(array, function (item) {
if (item) { ret.push(item); }
});
return ret;
}
// unique
function unique(array) {
var ret = [];
each(array, function (_a) {
if (!find(ret, _a)) { ret.push(_a); }
});
return ret;
}
// intersection
function intersection(a, b) {
var ret = [];
each(a, function (_a) {
each(b, function (_b) {
if (_a === _b) { ret.push(_a); }
});
});
return unique(ret);
}
// rest
function rest(array, callback) {
var ret = [];
each(array, function (item, index) {
if (!callback(item)) {
ret = array.slice(index);
return false;
}
});
return ret;
}
// initial
function initial(array, callback) {
var reversed = array.slice().reverse();
return rest(reversed, callback).reverse();
}
// extend
function extend(a, b) {
for (var key in b) {
if (b.hasOwnProperty(key)) { a[key] = b[key]; }
}
return a;
}
// define internal moment reference
var moment;
if (typeof require === "function") {
try { moment = require('moment'); }
catch (e) {}
}
if (!moment && root.moment) {
moment = root.moment;
}
if (!moment) {
throw "Moment Duration Format cannot find Moment.js";
}
// moment.duration.format([template] [, precision] [, settings])
moment.duration.fn.format = function () {
var tokenizer, tokens, types, typeMap, momentTypes, foundFirst, trimIndex,
args = [].slice.call(arguments),
settings = extend({}, this.format.defaults),
// keep a shadow copy of this moment for calculating remainders
remainder = moment.duration(this);
// add a reference to this duration object to the settings for use
// in a template function
settings.duration = this;
// parse arguments
each(args, function (arg) {
if (typeof arg === "string" || typeof arg === "function") {
settings.template = arg;
return;
}
if (typeof arg === "number") {
settings.precision = arg;
return;
}
if (isObject(arg)) {
extend(settings, arg);
}
});
// types
types = settings.types = (isArray(settings.types) ? settings.types : settings.types.split(" "));
// template
if (typeof settings.template === "function") {
settings.template = settings.template.apply(settings);
}
// tokenizer regexp
tokenizer = new RegExp(map(types, function (type) {
return settings[type].source;
}).join("|"), "g");
// token type map function
typeMap = function (token) {
return find(types, function (type) {
return settings[type].test(token);
});
};
// tokens array
tokens = map(settings.template.match(tokenizer), function (token, index) {
var type = typeMap(token),
length = token.length;
return {
index: index,
length: length,
// replace escaped tokens with the non-escaped token text
token: (type === "escape" ? token.replace(settings.escape, "$1") : token),
// ignore type on non-moment tokens
type: ((type === "escape" || type === "general") ? null : type)
// calculate base value for all moment tokens
//baseValue: ((type === "escape" || type === "general") ? null : this.as(type))
};
}, this);
// unique moment token types in the template (in order of descending magnitude)
momentTypes = intersection(types, unique(compact(pluck(tokens, "type"))));
// exit early if there are no momentTypes
if (!momentTypes.length) {
return pluck(tokens, "token").join("");
}
// calculate values for each token type in the template
each(momentTypes, function (momentType, index) {
var value, wholeValue, decimalValue, isLeast, isMost;
// calculate integer and decimal value portions
value = remainder.as(momentType);
wholeValue = (value > 0 ? Math.floor(value) : Math.ceil(value));
decimalValue = value - wholeValue;
// is this the least-significant moment token found?
isLeast = ((index + 1) === momentTypes.length);
// is this the most-significant moment token found?
isMost = (!index);
// update tokens array
// using this algorithm to not assume anything about
// the order or frequency of any tokens
each(tokens, function (token) {
if (token.type === momentType) {
extend(token, {
value: value,
wholeValue: wholeValue,
decimalValue: decimalValue,
isLeast: isLeast,
isMost: isMost
});
if (isMost) {
// note the length of the most-significant moment token:
// if it is greater than one and forceLength is not set, default forceLength to `true`
if (settings.forceLength == null && token.length > 1) {
settings.forceLength = true;
}
// rationale is this:
// if the template is "h:mm:ss" and the moment value is 5 minutes, the user-friendly output is "5:00", not "05:00"
// shouldn't pad the `minutes` token even though it has length of two
// if the template is "hh:mm:ss", the user clearly wanted everything padded so we should output "05:00"
// if the user wanted the full padded output, they can set `{ trim: false }` to get "00:05:00"
}
}
});
// update remainder
remainder.subtract(wholeValue, momentType);
});
// trim tokens array
if (settings.trim) {
tokens = (settings.trim === "left" ? rest : initial)(tokens, function (token) {
// return `true` if:
// the token is not the least moment token (don't trim the least moment token)
// the token is a moment token that does not have a value (don't trim moment tokens that have a whole value)
return !(token.isLeast || (token.type != null && token.wholeValue));
});
}
// build output
// the first moment token can have special handling
foundFirst = false;
// run the map in reverse order if trimming from the right
if (settings.trim === "right") {
tokens.reverse();
}
tokens = map(tokens, function (token) {
var val,
decVal;
if (!token.type) {
// if it is not a moment token, use the token as its own value
return token.token;
}
// apply negative precision formatting to the least-significant moment token
if (token.isLeast && (settings.precision < 0)) {
val = (Math.floor(token.wholeValue * Math.pow(10, settings.precision)) * Math.pow(10, -settings.precision)).toString();
} else {
val = token.wholeValue.toString();
}
// remove negative sign from the beginning
val = val.replace(/^\-/, "");
// apply token length formatting
// special handling for the first moment token that is not the most significant in a trimmed template
if (token.length > 1 && (foundFirst || token.isMost || settings.forceLength)) {
val = padZero(val, token.length);
}
// add decimal value if precision > 0
if (token.isLeast && (settings.precision > 0)) {
decVal = token.decimalValue.toString().replace(/^\-/, "").split(/\.|e\-/);
switch (decVal.length) {
case 1:
val += "." + padZero(decVal[0], settings.precision, true).slice(0, settings.precision);
break;
case 2:
val += "." + padZero(decVal[1], settings.precision, true).slice(0, settings.precision);
break;
case 3:
val += "." + padZero(repeatZero((+decVal[2]) - 1) + (decVal[0] || "0") + decVal[1], settings.precision, true).slice(0, settings.precision);
break;
default:
throw "Moment Duration Format: unable to parse token decimal value.";
}
}
// add a negative sign if the value is negative and token is most significant
if (token.isMost && token.value < 0) {
val = "-" + val;
}
foundFirst = true;
return val;
});
// undo the reverse if trimming from the right
if (settings.trim === "right") {
tokens.reverse();
}
return tokens.join("");
};
moment.duration.fn.format.defaults = {
// token definitions
escape: /\[(.+?)\]/,
years: /[Yy]+/,
months: /M+/,
weeks: /[Ww]+/,
days: /[Dd]+/,
hours: /[Hh]+/,
minutes: /m+/,
seconds: /s+/,
milliseconds: /S+/,
general: /.+?/,
// token type names
// in order of descending magnitude
// can be a space-separated token name list or an array of token names
types: "escape years months weeks days hours minutes seconds milliseconds general",
// format options
// trim
// "left" - template tokens are trimmed from the left until the first moment token that has a value >= 1
// "right" - template tokens are trimmed from the right until the first moment token that has a value >= 1
// (the final moment token is not trimmed, regardless of value)
// `false` - template tokens are not trimmed
trim: "left",
// precision
// number of decimal digits to include after (to the right of) the decimal point (positive integer)
// or the number of digits to truncate to 0 before (to the left of) the decimal point (negative integer)
precision: 0,
// force first moment token with a value to render at full length even when template is trimmed and first moment token has length of 1
forceLength: null,
// template used to format duration
// may be a function or a string
// template functions are executed with the `this` binding of the settings object
// so that template strings may be dynamically generated based on the duration object
// (accessible via `this.duration`)
// or any of the other settings
template: function () {
var types = this.types,
dur = this.duration,
lastType = findLast(types, function (type) {
return dur._data[type];
});
// default template strings for each duration dimension type
switch (lastType) {
case "seconds":
return "h:mm:ss";
case "minutes":
return "d[d] h:mm";
case "hours":
return "d[d] h[h]";
case "days":
return "M[m] d[d]";
case "weeks":
return "y[y] w[w]";
case "months":
return "y[y] M[m]";
case "years":
return "y[y]";
default:
return "y[y] M[m] d[d] h:mm:ss";
}
}
};
})(this);

View File

@ -0,0 +1,34 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-time-display l-digital l-clock s-clock" ng-controller="ClockController as clock">
<div class="l-elem-wrapper">
<span class="l-elem timezone">
{{clock.zone()}}
</span>
<span class="l-elem value active">
{{clock.text()}}
</span>
<span class="l-elem ampm">
{{clock.ampm()}}
</span>
</div>
</div>

View File

@ -0,0 +1,42 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-time-display l-digital l-timer s-timer" ng-controller="TimerController as timer">
<div class="l-elem-wrapper">
<a
ng-click="timer.clickButton()"
title="{{timer.buttonText()}}"
class="l-elem l-btn s-btn s-icon-btn s-very-subtle vsm control"
>
<span class="ui-symbol icon">{{timer.buttonGlyph()}}</span>
</a>
<span class="l-elem l-value">
<span class="ui-symbol direction">{{timer.sign()}}</span>
<span
class="value"
ng-class="{ active:timer.text() }"
>{{timer.text() || "--:--:--"}}
</span>
</span>
<span ng-controller="RefreshingController">
</span>
</div>
</div>

View File

@ -0,0 +1,62 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*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,54 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*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 === 'timer' &&
model.timestamp !== undefined;
};
return RestartTimerAction;
}
);

View File

@ -0,0 +1,55 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*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 === 'timer' &&
model.timestamp === undefined;
};
return StartTimerAction;
}
);

View File

@ -0,0 +1,100 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*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,50 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*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,167 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*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) ?
'timer.start' : '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,81 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*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,59 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*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,89 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*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;
}
);

View File

@ -0,0 +1,87 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
["../../src/actions/AbstractStartTimerAction"],
function (AbstractStartTimerAction) {
"use strict";
describe("A timer's start/restart action", function () {
var mockNow,
mockDomainObject,
mockPersistence,
testModel,
action;
function asPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return asPromise(callback(value));
}
};
}
beforeEach(function () {
mockNow = jasmine.createSpy('now');
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getCapability', 'useCapability' ]
);
mockPersistence = jasmine.createSpyObj(
'persistence',
['persist']
);
mockDomainObject.getCapability.andCallFake(function (c) {
return (c === 'persistence') && mockPersistence;
});
mockDomainObject.useCapability.andCallFake(function (c, v) {
if (c === 'mutation') {
testModel = v(testModel) || testModel;
return asPromise(true);
}
});
testModel = {};
action = new AbstractStartTimerAction(mockNow, {
domainObject: mockDomainObject
});
});
it("updates the model with a timestamp and persists", function () {
mockNow.andReturn(12000);
action.perform();
expect(testModel.timestamp).toEqual(12000);
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("does not truncate milliseconds", function () {
mockNow.andReturn(42321);
action.perform();
expect(testModel.timestamp).toEqual(42321);
expect(mockPersistence.persist).toHaveBeenCalled();
});
});
}
);

View File

@ -0,0 +1,97 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
["../../src/actions/RestartTimerAction"],
function (RestartTimerAction) {
"use strict";
describe("A timer's restart action", function () {
var mockNow,
mockDomainObject,
mockPersistence,
testModel,
testContext,
action;
function asPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return asPromise(callback(value));
}
};
}
beforeEach(function () {
mockNow = jasmine.createSpy('now');
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getCapability', 'useCapability', 'getModel' ]
);
mockPersistence = jasmine.createSpyObj(
'persistence',
['persist']
);
mockDomainObject.getCapability.andCallFake(function (c) {
return (c === 'persistence') && mockPersistence;
});
mockDomainObject.useCapability.andCallFake(function (c, v) {
if (c === 'mutation') {
testModel = v(testModel) || testModel;
return asPromise(true);
}
});
mockDomainObject.getModel.andCallFake(function () {
return testModel;
});
testModel = {};
testContext = { domainObject: mockDomainObject };
action = new RestartTimerAction(mockNow, testContext);
});
it("updates the model with a timestamp and persists", function () {
mockNow.andReturn(12000);
action.perform();
expect(testModel.timestamp).toEqual(12000);
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("applies only to timers with a target time", function () {
testModel.type = 'timer';
testModel.timestamp = 12000;
expect(RestartTimerAction.appliesTo(testContext)).toBeTruthy();
testModel.type = 'timer';
testModel.timestamp = undefined;
expect(RestartTimerAction.appliesTo(testContext)).toBeFalsy();
testModel.type = 'clock';
testModel.timestamp = 12000;
expect(RestartTimerAction.appliesTo(testContext)).toBeFalsy();
});
});
}
);

View File

@ -0,0 +1,97 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
["../../src/actions/StartTimerAction"],
function (StartTimerAction) {
"use strict";
describe("A timer's start action", function () {
var mockNow,
mockDomainObject,
mockPersistence,
testModel,
testContext,
action;
function asPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return asPromise(callback(value));
}
};
}
beforeEach(function () {
mockNow = jasmine.createSpy('now');
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getCapability', 'useCapability', 'getModel' ]
);
mockPersistence = jasmine.createSpyObj(
'persistence',
['persist']
);
mockDomainObject.getCapability.andCallFake(function (c) {
return (c === 'persistence') && mockPersistence;
});
mockDomainObject.useCapability.andCallFake(function (c, v) {
if (c === 'mutation') {
testModel = v(testModel) || testModel;
return asPromise(true);
}
});
mockDomainObject.getModel.andCallFake(function () {
return testModel;
});
testModel = {};
testContext = { domainObject: mockDomainObject };
action = new StartTimerAction(mockNow, testContext);
});
it("updates the model with a timestamp and persists", function () {
mockNow.andReturn(12000);
action.perform();
expect(testModel.timestamp).toEqual(12000);
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("applies only to timers without a target time", function () {
testModel.type = 'timer';
testModel.timestamp = 12000;
expect(StartTimerAction.appliesTo(testContext)).toBeFalsy();
testModel.type = 'timer';
testModel.timestamp = undefined;
expect(StartTimerAction.appliesTo(testContext)).toBeTruthy();
testModel.type = 'clock';
testModel.timestamp = 12000;
expect(StartTimerAction.appliesTo(testContext)).toBeFalsy();
});
});
}
);

View File

@ -0,0 +1,104 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
["../../src/controllers/ClockController"],
function (ClockController) {
"use strict";
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000;
describe("A clock view's controller", function () {
var mockScope,
mockTicker,
mockUnticker,
mockDomainObject,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.andReturn(mockUnticker);
controller = new ClockController(mockScope, mockTicker);
});
it("watches for clock format from the domain object model", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"model.clockFormat",
jasmine.any(Function)
);
});
it("subscribes to clock ticks", function () {
expect(mockTicker.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("unsubscribes to ticks when destroyed", function () {
// Make sure $destroy is being listened for...
expect(mockScope.$on.mostRecentCall.args[0]).toEqual('$destroy');
expect(mockUnticker).not.toHaveBeenCalled();
// ...and makes sure that its listener unsubscribes from ticker
mockScope.$on.mostRecentCall.args[1]();
expect(mockUnticker).toHaveBeenCalled();
});
it("formats using the format string from the model", function () {
mockTicker.listen.mostRecentCall.args[0](TEST_TIMESTAMP);
mockScope.$watch.mostRecentCall.args[1]([
"YYYY-DDD hh:mm:ss",
"clock24"
]);
expect(controller.zone()).toEqual("UTC");
expect(controller.text()).toEqual("2015-154 17:56:14");
expect(controller.ampm()).toEqual("");
});
it("formats 12-hour time", function () {
mockTicker.listen.mostRecentCall.args[0](TEST_TIMESTAMP);
mockScope.$watch.mostRecentCall.args[1]([
"YYYY-DDD hh:mm:ss",
"clock12"
]);
expect(controller.zone()).toEqual("UTC");
expect(controller.text()).toEqual("2015-154 05:56:14");
expect(controller.ampm()).toEqual("PM");
});
it("does not throw exceptions when clockFormat is undefined", function () {
mockTicker.listen.mostRecentCall.args[0](TEST_TIMESTAMP);
expect(function () {
mockScope.$watch.mostRecentCall.args[1](undefined);
}).not.toThrow();
});
});
}
);

View File

@ -0,0 +1,84 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
["../../src/controllers/RefreshingController"],
function (RefreshingController) {
"use strict";
describe("The refreshing controller", function () {
var mockScope,
mockTicker,
mockUnticker,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj('$scope', ['$on']);
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.andReturn(mockUnticker);
controller = new RefreshingController(mockScope, mockTicker);
});
it("refreshes the represented object on every tick", function () {
var mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getCapability' ]
),
mockPersistence = jasmine.createSpyObj(
'persistence',
[ 'persist', 'refresh' ]
);
mockDomainObject.getCapability.andCallFake(function (c) {
return (c === 'persistence') && mockPersistence;
});
mockScope.domainObject = mockDomainObject;
mockTicker.listen.mostRecentCall.args[0](12321);
expect(mockPersistence.refresh).toHaveBeenCalled();
expect(mockPersistence.persist).not.toHaveBeenCalled();
});
it("subscribes to clock ticks", function () {
expect(mockTicker.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("unsubscribes to ticks when destroyed", function () {
// Make sure $destroy is being listened for...
expect(mockScope.$on.mostRecentCall.args[0]).toEqual('$destroy');
expect(mockUnticker).not.toHaveBeenCalled();
// ...and makes sure that its listener unsubscribes from ticker
mockScope.$on.mostRecentCall.args[1]();
expect(mockUnticker).toHaveBeenCalled();
});
});
}
);

View File

@ -0,0 +1,199 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
["../../src/controllers/TimerController"],
function (TimerController) {
"use strict";
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000;
describe("A timer view's controller", function () {
var mockScope,
mockWindow,
mockNow,
mockDomainObject,
mockActionCapability,
mockStart,
mockRestart,
testModel,
controller;
function invokeWatch(expr, value) {
mockScope.$watch.calls.forEach(function (call) {
if (call.args[0] === expr) {
call.args[1](value);
}
});
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
'$scope',
['$watch', '$on', '$apply']
);
mockWindow = jasmine.createSpyObj(
'$window',
['requestAnimationFrame']
);
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getCapability', 'useCapability', 'getModel' ]
);
mockActionCapability = jasmine.createSpyObj(
'action',
['getActions']
);
mockStart = jasmine.createSpyObj(
'start',
['getMetadata', 'perform']
);
mockRestart = jasmine.createSpyObj(
'restart',
['getMetadata', 'perform']
);
mockNow = jasmine.createSpy('now');
mockDomainObject.getCapability.andCallFake(function (c) {
return (c === 'action') && mockActionCapability;
});
mockDomainObject.getModel.andCallFake(function () {
return testModel;
});
mockActionCapability.getActions.andCallFake(function (k) {
return [{
'timer.start': mockStart,
'timer.restart': mockRestart
}[k]];
});
mockStart.getMetadata.andReturn({ glyph: "S", name: "Start" });
mockRestart.getMetadata.andReturn({ glyph: "R", name: "Restart" });
mockScope.domainObject = mockDomainObject;
testModel = {};
controller = new TimerController(mockScope, mockWindow, mockNow);
});
it("watches for the domain object in view", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"domainObject",
jasmine.any(Function)
);
});
it("watches for domain object modifications", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"model.modified",
jasmine.any(Function)
);
});
it("updates on a timer", function () {
expect(mockWindow.requestAnimationFrame)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("displays nothing when there is no target", function () {
// Notify that domain object is available via scope
invokeWatch('domainObject', mockDomainObject);
mockNow.andReturn(TEST_TIMESTAMP);
mockWindow.requestAnimationFrame.mostRecentCall.args[0]();
expect(controller.sign()).toEqual("");
expect(controller.text()).toEqual("");
});
it("formats time to display relative to target", function () {
testModel.timestamp = TEST_TIMESTAMP;
testModel.timerFormat = 'long';
// Notify that domain object is available via scope
invokeWatch('domainObject', mockDomainObject);
mockNow.andReturn(TEST_TIMESTAMP + 121000);
mockWindow.requestAnimationFrame.mostRecentCall.args[0]();
expect(controller.sign()).toEqual("+");
expect(controller.text()).toEqual("0D 00:02:01");
mockNow.andReturn(TEST_TIMESTAMP - 121000);
mockWindow.requestAnimationFrame.mostRecentCall.args[0]();
expect(controller.sign()).toEqual("-");
expect(controller.text()).toEqual("0D 00:02:01");
mockNow.andReturn(TEST_TIMESTAMP);
mockWindow.requestAnimationFrame.mostRecentCall.args[0]();
expect(controller.sign()).toEqual("");
expect(controller.text()).toEqual("0D 00:00:00");
});
it("shows glyph & name for the applicable start/restart action", function () {
invokeWatch('domainObject', mockDomainObject);
expect(controller.buttonGlyph()).toEqual("S");
expect(controller.buttonText()).toEqual("Start");
testModel.timestamp = 12321;
invokeWatch('model.modified', 1);
expect(controller.buttonGlyph()).toEqual("R");
expect(controller.buttonText()).toEqual("Restart");
});
it("performs correct start/restart action on click", function () {
invokeWatch('domainObject', mockDomainObject);
expect(mockStart.perform).not.toHaveBeenCalled();
controller.clickButton();
expect(mockStart.perform).toHaveBeenCalled();
testModel.timestamp = 12321;
invokeWatch('model.modified', 1);
expect(mockRestart.perform).not.toHaveBeenCalled();
controller.clickButton();
expect(mockRestart.perform).toHaveBeenCalled();
});
it("stops requesting animation frames when destroyed", function () {
var initialCount = mockWindow.requestAnimationFrame.calls.length;
// First, check that normally new frames keep getting requested
mockWindow.requestAnimationFrame.mostRecentCall.args[0]();
expect(mockWindow.requestAnimationFrame.calls.length)
.toEqual(initialCount + 1);
mockWindow.requestAnimationFrame.mostRecentCall.args[0]();
expect(mockWindow.requestAnimationFrame.calls.length)
.toEqual(initialCount + 2);
// Now, verify that it stops after $destroy
expect(mockScope.$on.mostRecentCall.args[0])
.toEqual('$destroy');
mockScope.$on.mostRecentCall.args[1]();
// Frames should no longer get requested
mockWindow.requestAnimationFrame.mostRecentCall.args[0]();
expect(mockWindow.requestAnimationFrame.calls.length)
.toEqual(initialCount + 2);
mockWindow.requestAnimationFrame.mostRecentCall.args[0]();
expect(mockWindow.requestAnimationFrame.calls.length)
.toEqual(initialCount + 2);
});
});
}
);

View File

@ -0,0 +1,117 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
["../../src/controllers/TimerFormatter"],
function (TimerFormatter) {
"use strict";
var MS_IN_SEC = 1000,
MS_IN_MIN = MS_IN_SEC * 60,
MS_IN_HR = MS_IN_MIN * 60,
MS_IN_DAY = MS_IN_HR * 24;
describe("The timer value formatter", function () {
var formatter = new TimerFormatter();
function sum(a, b) {
return a + b;
}
function toDuration(days, hours, mins, secs) {
return [
days * MS_IN_DAY,
hours * MS_IN_HR,
mins * MS_IN_MIN,
secs * MS_IN_SEC
].reduce(sum, 0);
}
function twoDigits(n) {
return n < 10 ? ('0' + n) : n;
}
it("formats short-form values (no days)", function () {
expect(formatter.short(toDuration(0, 123, 2, 3) + 123))
.toEqual("123:02:03");
});
it("formats negative short-form values (no days)", function () {
expect(formatter.short(-toDuration(0, 123, 2, 3) + 123))
.toEqual("123:02:03");
});
it("formats long-form values (with days)", function () {
expect(formatter.long(toDuration(0, 123, 2, 3) + 123))
.toEqual("5D 03:02:03");
});
it("formats negative long-form values (no days)", function () {
expect(formatter.long(-toDuration(0, 123, 2, 3) + 123))
.toEqual("5D 03:02:03");
});
it("rounds seconds down for positive durations", function () {
expect(formatter.short(MS_IN_SEC + 600))
.toEqual("00:00:01");
});
it("rounds seconds up for negative durations", function () {
expect(formatter.short(-MS_IN_SEC - 600))
.toEqual("00:00:02");
});
it("short-formats correctly around negative time borders", function () {
expect(formatter.short(-1)).toEqual("00:00:01");
expect(formatter.short(-1000)).toEqual("00:00:01");
expect(formatter.short(-1001)).toEqual("00:00:02");
expect(formatter.short(-2000)).toEqual("00:00:02");
expect(formatter.short(-59001)).toEqual("00:01:00");
expect(formatter.short(-60000)).toEqual("00:01:00");
expect(formatter.short(-MS_IN_HR + 999)).toEqual("01:00:00");
expect(formatter.short(-MS_IN_HR)).toEqual("01:00:00");
});
it("differentiates between values around zero", function () {
// These are more than 1000 ms apart so should not appear
// as the same second
expect(formatter.short(-999))
.not.toEqual(formatter.short(999));
});
it("handles negative days", function () {
expect(formatter.long(-10 * MS_IN_DAY))
.toEqual("10D 00:00:00");
expect(formatter.long(-10 * MS_IN_DAY + 100))
.toEqual("10D 00:00:00");
expect(formatter.long(-10 * MS_IN_DAY + 999))
.toEqual("10D 00:00:00");
expect(formatter.short(-10 * MS_IN_DAY + 100))
.toEqual("240:00:00");
});
});
}
);

View File

@ -0,0 +1,61 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
["../../src/indicators/ClockIndicator"],
function (ClockIndicator) {
"use strict";
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000,
TEST_FORMAT = "YYYY-DDD HH:mm:ss";
describe("The clock indicator", function () {
var mockTicker,
mockUnticker,
indicator;
beforeEach(function () {
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.andReturn(mockUnticker);
indicator = new ClockIndicator(mockTicker, TEST_FORMAT);
});
it("displays the current time", function () {
mockTicker.listen.mostRecentCall.args[0](TEST_TIMESTAMP);
expect(indicator.getText()).toEqual("2015-154 17:56:14 UTC");
});
it("implements the Indicator interface", function () {
expect(indicator.getGlyph()).toEqual(jasmine.any(String));
expect(indicator.getGlyphClass()).toEqual(jasmine.any(String));
expect(indicator.getText()).toEqual(jasmine.any(String));
expect(indicator.getDescription()).toEqual(jasmine.any(String));
});
});
}
);

View File

@ -0,0 +1,64 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
["../../src/services/TickerService"],
function (TickerService) {
"use strict";
var TEST_TIMESTAMP = 1433354174000;
describe("The ticker service", function () {
var mockTimeout,
mockNow,
mockCallback,
tickerService;
beforeEach(function () {
mockTimeout = jasmine.createSpy('$timeout');
mockNow = jasmine.createSpy('now');
mockCallback = jasmine.createSpy('callback');
mockNow.andReturn(TEST_TIMESTAMP);
tickerService = new TickerService(mockTimeout, mockNow);
});
it("notifies listeners of clock ticks", function () {
tickerService.listen(mockCallback);
mockNow.andReturn(TEST_TIMESTAMP + 12321);
mockTimeout.mostRecentCall.args[0]();
expect(mockCallback)
.toHaveBeenCalledWith(TEST_TIMESTAMP + 12321);
});
it("allows listeners to unregister", function () {
tickerService.listen(mockCallback)(); // Unregister immediately
mockNow.andReturn(TEST_TIMESTAMP + 12321);
mockTimeout.mostRecentCall.args[0]();
expect(mockCallback).not
.toHaveBeenCalledWith(TEST_TIMESTAMP + 12321);
});
});
}
);

View File

@ -0,0 +1,11 @@
[
"actions/AbstractStartTimerAction",
"actions/RestartTimerAction",
"actions/StartTimerAction",
"controllers/ClockController",
"controllers/RefreshingController",
"controllers/TimerController",
"controllers/TimerFormatter",
"indicators/ClockIndicator",
"services/TickerService"
]

View File

@ -102,7 +102,7 @@ define(
function updateDomain(value) {
var newDomain = conductor.domain(value);
conductorScope.parameters.format = newDomain.format;
repScope.$broadcast('telemetry:display:bounds', bounds());
broadcastBounds();
}
// telemetry domain metadata -> option for a select control
@ -139,8 +139,6 @@ define(
.$watch('ngModel.conductor.inner.end', updateConductorInner);
conductorScope
.$watch('ngModel.domain', updateDomain);
repScope.$on('telemetry:view', updateConductorInner);
};
ConductorRepresenter.prototype.conductorScope = function (s) {

View File

@ -245,9 +245,6 @@ define(
// Unsubscribe when the plot is destroyed
$scope.$on("$destroy", releaseSubscription);
// Notify any external observers that a new telemetry view is here
$scope.$emit("telemetry:view");
}
/**

View File

@ -0,0 +1,70 @@
This bundle provides the Timeline domain object type, as well
as other associated domain object types and relevant views.
# Implementation notes
## Model Properties
The properties below record properties relevant to using and
understanding timelines based on their JSON representation.
Additional common properties, such as `modified`
or `persisted` timestamps, may also be present.
### Timeline Model
A timeline's model looks like:
```
{
"type": "timeline",
"start": {
"timestamp": <number> (milliseconds since epoch),
"epoch": <string> (currently, always "SET")
},
"capacity": <number> (optional; battery capacity in watt-hours)
"composition": <string[]> (array of identifiers for contained objects)
}
```
The identifiers in a timeline's `composition` field should refer to
other Timeline objects, or to Activity objects.
### Activity Model
An activity's model looks like:
```
{
"type": "activity",
"start": {
"timestamp": <number> (milliseconds since epoch),
"epoch": <string> (currently, always "SET")
},
"duration": {
"timestamp": <number> (duration of this activity, in milliseconds)
"epoch": "SET" (this is ignored)
},
"relationships": {
"modes": <string[]> (array of applicable Activity Mode ids)
},
"link": <string> (optional; URL linking to associated external resource)
"composition": <string[]> (array of identifiers for contained objects)
}
```
The identifiers in a timeline's `composition` field should only refer to
other Activity objects.
### Activity Mode Model
An activity mode's model looks like:
```
{
"type": "mode",
"resources": {
"comms": <number> (communications utilization, in Kbps)
"power": <number> (power utilization, in watts)
}
}
```

View File

@ -0,0 +1,372 @@
{
"name": "Timelines",
"description": "Resources, templates, CSS, and code for Timelines.",
"resources": "res",
"extensions": {
"constants": [
{
"key": "TIMELINE_MINIMUM_DURATION",
"description": "The minimum duration to display in a timeline view (one hour.)",
"value": 3600000
},
{
"key": "TIMELINE_MAXIMUM_OFFSCREEN",
"description": "Maximum amount, in pixels, of a Gantt bar which may go off screen.",
"value": 1000
},
{
"key": "TIMELINE_ZOOM_CONFIGURATION",
"description": "Describes major tick sizes in milliseconds, and width in pixels.",
"value": {
"levels": [
1000,
2000,
5000,
10000,
20000,
30000,
60000,
120000,
300000,
600000,
1200000,
1800000,
3600000,
7200000,
14400000,
28800000,
43200000,
86400000
],
"width": 200
}
}
],
"types": [
{
"key": "timeline",
"name": "Timeline",
"glyph": "S",
"description": "A container for arranging Timelines and Activities in time.",
"features": [ "creation" ],
"contains": [ "timeline", "activity" ],
"properties": [
{
"name": "Start date/time",
"control": "datetime",
"required": true,
"property": [ "start" ],
"options": [ "SET" ]
},
{
"name": "Battery capacity (Watt-hours)",
"control": "textfield",
"required": false,
"conversion": "number",
"property": [ "capacity" ],
"pattern": "^-?\\d+(\\.\\d*)?$"
}
],
"model": { "composition": [] }
},
{
"key": "activity",
"name": "Activity",
"glyph": "a",
"features": [ "creation" ],
"contains": [ "activity" ],
"description": "An action that takes place in time. You can define a start time and duration. Activities can be nested within other Activities, or within Timelines.",
"properties": [
{
"name": "Start date/time",
"control": "datetime",
"required": true,
"property": [ "start" ],
"options": [ "SET" ]
},
{
"name": "Duration",
"control": "duration",
"required": true,
"property": [ "duration" ]
}
],
"model": { "composition": [], "relationships": { "modes": [] } }
},
{
"key": "mode",
"name": "Activity Mode",
"glyph": "A",
"features": [ "creation" ],
"description": "Define resource utilizations over time, then apply to an Activity.",
"model": { "resources": { "comms": 0, "power": 0 } },
"properties": [
{
"name": "Comms (Kbps)",
"control": "textfield",
"conversion": "number",
"pattern": "^-?\\d+(\\.\\d*)?$",
"property": [ "resources", "comms" ]
},
{
"name": "Power (watts)",
"control": "textfield",
"conversion": "number",
"pattern": "^-?\\d+(\\.\\d*)?$",
"property": [ "resources", "power" ]
}
]
}
],
"views": [
{
"key": "values",
"name": "Values",
"glyph": "A",
"templateUrl": "templates/values.html",
"type": "mode",
"uses": [ "cost" ],
"editable": false
},
{
"key": "timeline",
"name": "Timeline",
"glyph": "S",
"type": "timeline",
"description": "A timeline view of Timelines and Activities.",
"templateUrl": "templates/timeline.html",
"toolbar": {
"sections": [
{
"items": [
{
"method": "add",
"glyph": "+",
"control": "menu-button",
"text": "Add",
"options": [
{
"name": "Timeline",
"glyph": "S",
"key": "timeline"
},
{
"name": "Activity",
"glyph": "a",
"key": "activity"
}
]
}
]
},
{
"items": [
{
"glyph": "\u00E9",
"description": "Graph resource utilization",
"control": "button",
"method": "toggleGraph"
},
{
"glyph": "A",
"control": "dialog-button",
"description": "Apply Activity Modes...",
"title": "Apply Activity Modes",
"dialog": {
"control": "selector",
"name": "Modes",
"type": "mode"
},
"property": "modes"
},
{
"glyph": "\u00E8",
"description": "Edit Activity Link",
"title": "Activity Link",
"control": "dialog-button",
"dialog": {
"control": "textfield",
"name": "Link",
"pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$"
},
"property": "link"
},
{
"glyph": "\u0047",
"description": "Edit Properties...",
"control": "button",
"method": "properties"
}
]
},
{
"items": [
{
"method": "remove",
"description": "Remove item",
"control": "button",
"glyph": "Z"
}
]
}
]
}
}
],
"representations": [
{
"key": "gantt",
"templateUrl": "templates/activity-gantt.html",
"uses": [ "timespan", "type" ]
}
],
"templates": [
{
"key": "timeline-tabular-swimlane-cols-tree",
"priority": "mandatory",
"templateUrl": "templates/tabular-swimlane-cols-tree.html"
},
{
"key": "timeline-tabular-swimlane-cols-data",
"priority": "mandatory",
"templateUrl": "templates/tabular-swimlane-cols-data.html"
},
{
"key": "timeline-resource-graphs",
"priority": "mandatory",
"templateUrl": "templates/resource-graphs.html"
},
{
"key": "timeline-resource-graph-labels",
"priority": "mandatory",
"templateUrl": "templates/resource-graph-labels.html"
},
{
"key": "timeline-legend-item",
"priority": "mandatory",
"templateUrl": "templates/legend-item.html"
},
{
"key": "timeline-ticks",
"priority": "mandatory",
"templateUrl": "templates/ticks.html"
}
],
"controls": [
{
"key": "datetime",
"templateUrl": "templates/controls/datetime.html"
},
{
"key": "duration",
"templateUrl": "templates/controls/datetime.html"
}
],
"controllers": [
{
"key": "TimelineController",
"implementation": "controllers/TimelineController.js",
"depends": [ "$scope", "$q", "objectLoader", "TIMELINE_MINIMUM_DURATION" ]
},
{
"key": "TimelineGraphController",
"implementation": "controllers/TimelineGraphController.js",
"depends": [ "$scope", "resources[]" ]
},
{
"key": "TimelineDateTimeController",
"implementation": "controllers/TimelineDateTimeController.js",
"depends": [ "$scope" ]
},
{
"key": "TimelineZoomController",
"implementation": "controllers/TimelineZoomController.js",
"depends": [ "$scope", "TIMELINE_ZOOM_CONFIGURATION" ]
},
{
"key": "TimelineTickController",
"implementation": "controllers/TimelineTickController.js"
},
{
"key": "TimelineTableController",
"implementation": "controllers/TimelineTableController.js"
},
{
"key": "TimelineGanttController",
"implementation": "controllers/TimelineGanttController.js",
"depends": [ "TIMELINE_MAXIMUM_OFFSCREEN" ]
},
{
"key": "ActivityModeValuesController",
"implementation": "controllers/ActivityModeValuesController.js",
"depends": [ "resources[]" ]
}
],
"capabilities": [
{
"key": "timespan",
"implementation": "capabilities/ActivityTimespanCapability.js",
"depends": [ "$q" ]
},
{
"key": "timespan",
"implementation": "capabilities/TimelineTimespanCapability.js",
"depends": [ "$q" ]
},
{
"key": "utilization",
"implementation": "capabilities/UtilizationCapability.js",
"depends": [ "$q" ]
},
{
"key": "graph",
"implementation": "capabilities/GraphCapability.js",
"depends": [ "$q" ]
},
{
"key": "cost",
"implementation": "capabilities/CostCapability.js"
}
],
"directives": [
{
"key": "mctSwimlaneDrop",
"implementation": "directives/MCTSwimlaneDrop.js",
"depends": [ "dndService" ]
},
{
"key": "mctSwimlaneDrag",
"implementation": "directives/MCTSwimlaneDrag.js",
"depends": [ "dndService" ]
}
],
"services": [
{
"key": "objectLoader",
"implementation": "services/ObjectLoader.js",
"depends": [ "$q" ]
}
],
"resources": [
{
"key": "power",
"name": "Power",
"units": "watts"
},
{
"key": "comms",
"name": "Comms",
"units": "Kbps"
},
{
"key": "battery",
"name": "Battery State-of-Charge",
"units": "%"
}
]
}
}

View File

@ -0,0 +1,39 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="t-timeline-gantt l-timeline-gantt s-timeline-gantt"
title="{{model.name}}"
ng-controller="TimelineGanttController as gantt"
ng-style="{
left: gantt.left(timespan, parameters.scroll, parameters.toPixels) + 'px',
width: gantt.width(timespan, parameters.scroll, parameters.toPixels) + 'px'
}">
<div class="bar">
<span class="s-activity-type ui-symbol">
{{type.getGlyph()}}
</span>
<span class="s-title">
{{model.name}}
</span>
</div>
</div>

View File

@ -0,0 +1,83 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class='form-control complex datetime'>
<div class='field-hints'>
<span class='hint time sm'>Days</span>
<span class='hint time sm'>Hours</span>
<span class='hint time sm'>Minutes</span>
<span class='hint time sm'>Seconds</span>
<span class='hint' ng-if="structure.options.length > 0">Time System</span>
</div>
<ng-form name="mctControl">
<div class='fields' ng-controller="TimelineDateTimeController">
<span class='field control time sm'>
<input type='text'
name='days'
min='0'
max='9999'
integer
ng-pattern="/\d+/"
ng-model='datetime.days'/>
</span>
<span class='field control time sm'>
<input type='text'
name='hour'
maxlength='2'
min='0'
max='23'
integer
ng-pattern='/\d+/'
ng-model="datetime.hours"/>
</span>
<span class='field control time sm'>
<input type='text'
name='min'
maxlength='2'
min='0'
max='59'
integer
ng-pattern='/\d+/'
ng-model="datetime.minutes"
ng-required='true'/>
</span>
<span class='field control time sm'>
<input type='text'
name='sec'
maxlength='2'
min='0'
max='59'
integer
ng-pattern='/\d+/'
ng-model="datetime.seconds"
ng-required='true'/>
</span>
<span ng-if="structure.options.length > 0"
class='field control'>
SET
</span>
</div>
</ng-form>
</div>

View File

@ -0,0 +1,34 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<!-- TO-DO: make legend item color-swatch dynamic -->
<span
class="legend-item s-legend-item"
title="{{ngModel.path}}{{ngModel.domainObject.getModel().name}}"
>
<span class="color-swatch"
ng-style="{ 'background-color': ngModel.color() }">
</span>
<span class="title-label">
<span class="l-parent-path">{{ngModel.path}}</span>
<span class="l-leaf-title">{{ngModel.domainObject.getModel().name}}</span>
</span>
</span>

View File

@ -0,0 +1,37 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-title s-title">
{{parameters.title}}
</div>
<div class="l-graph-area">
<div class="l-labels-holder">
<div class="tick-label tick-label-y tick-label-1" style="top: 0;">
{{parameters.high}}
</div>
<div class="tick-label tick-label-y" style="top: 50%; margin-top: -0.5em; height: 1em;">
{{parameters.middle}}
</div>
<div class="tick-label tick-label-y" style="top: auto; bottom: 4px; height: 1em;">
{{parameters.low}}
</div>
</div>
</div>

View File

@ -0,0 +1,34 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<span ng-controller="TimelineGraphController as graphController">
<div class="t-graph l-graph" ng-repeat="graph in parameters.graphs">
<div class="l-graph-area l-canvas-holder">
<mct-chart draw="graph.drawingObject"></mct-chart>
</div>
<div class="t-graph-labels l-graph-labels">
<mct-include key="'timeline-resource-graph-labels'"
parameters="graphController.label(graph)"
ng-model="graph">
</mct-include>
</div>
</div>
</span>

View File

@ -0,0 +1,37 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="t-swimlane s-swimlane l-swimlane {{ngModel.activitystate}} {{ngModel.swimlanestate}}"
ng-class="{
exceeded: ngModel.exceeded(),
selected: ngModel.selected(swimlane),
'drop-into': ngModel.highlight(),
'drop-after': ngModel.highlightBottom()
}">
<div
class="l-cols"
ng-controller="TimelineTableController as tabularVal">
<span class="align-right l-col l-start">{{tabularVal.niceTime(ngModel.timespan().getStart())}}</span>
<span class="align-right l-col l-end">{{tabularVal.niceTime(ngModel.timespan().getEnd())}}</span>
<span class="align-right l-col l-duration">{{tabularVal.niceTime(ngModel.timespan().getDuration())}}</span>
<span class="l-col l-activity-modes"></span>
</div>
</div>

View File

@ -0,0 +1,57 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="t-swimlane s-swimlane l-swimlane {{ngModel.activitystate}} {{ngModel.swimlanestate}}"
mct-swimlane-drop="ngModel"
ng-class="{
exceeded: ngModel.exceeded(),
selected: ngModel.selected(swimlane),
'drop-into': ngModel.highlight(),
'drop-after': ngModel.highlightBottom()
}">
<div class="l-cols">
<span class="l-col l-col-icon l-plot-resource"
ng-click="ngModel.toggleGraph()">
<span class="ui-symbol"
ng-show="ngModel.graph()">
&#x00e9;
</span>
</span>
<span class="l-col l-col-icon l-link">
<a class="ui-symbol"
target="_blank"
ng-href="{{ngModel.link()}}"
ng-if="ngModel.link().length > 0"
title="{{ngModel.link()}}"
>
&#x00e8;
</a>
</span>
<span class="l-col l-title"
ng-click="ngModel.select()"
ng-style="{ 'margin-left': 15 * ngModel.depth + 'px' }">
<mct-representation key="'label'"
mct-object="ngModel.domainObject"
mct-swimlane-drag="ngModel">
</mct-representation>
</span>
</div>
</div>

View File

@ -0,0 +1,39 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="t-header l-header s-header"
ng-controller="TimelineTickController as tick"
ng-style="{ width: parameters.fullWidth + 'px' }">
<div class="l-header-elem t-labels l-labels">
<div class="t-label l-label s-label"
ng-repeat="label in tick.labels(parameters.start, parameters.width, parameters.step, parameters.toMillis)"
ng-style="{ left: label.left + 'px' }">
{{label.text}}
</div>
</div>
<div class="t-ticks l-ticks s-ticks"
ng-style="{ 'background-size': parameters.step + 'px 100%' }">
</div>
<div class="t-ticks s-ticks l-subticks"
ng-style="{ 'background-size': (parameters.step / 40) + 'px 100%' }">
</div>
</div>

View File

@ -0,0 +1,218 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="s-timeline l-timeline-holder split-layout vertical"
ng-controller="TimelineController as timelineController">
<mct-split-pane anchor="left" class="abs" position="pane.x">
<!-- LEFT PANE: TABULAR AND RESOURCE LEGEND AREAS -->
<mct-split-pane anchor="bottom"
position="pane.y"
class="abs horizontal split-pane-component l-timeline-pane l-pane-l t-pane-v"
>
<!-- TOP PANE TABULAR AREA. ADD CLASS "hidden" FOR INTERIM NO-TABULAR DELIVERY -->
<div class="split-pane-component s-timeline-tabular l-timeline-pane t-pane-h l-pane-top">
<!-- TABULAR LEFT FIXED AREA -->
<div
class="t-pane-v l-pane-l l-tabular-l"
ng-if="true"
>
<div class="t-header l-header s-header">
<div class="l-cols">
<span class="l-col l-col-icon l-plot-resource ui-symbol">&#x00e9;</span>
<span class="l-col l-col-icon l-col-link ui-symbol">&#x00e8;</span>
<span class="l-col l-title">Title</span>
</div>
</div>
<div class="t-swimlanes-holder l-swimlanes-holder"
mct-scroll-y="scroll.y">
<mct-include key="'timeline-tabular-swimlane-cols-tree'"
ng-repeat="swimlane in timelineController.swimlanes()"
ng-model="swimlane">
</mct-include>
</div>
</div>
<!-- TABULAR RIGHT HORZ SCROLLING AREA -->
<div
class="t-pane-v l-pane-r l-tabular-r"
>
<div class="l-width">
<div class="t-header l-header s-header">
<div class="l-cols">
<span class="l-col l-start">Start</span>
<span class="l-col l-end">End</span>
<span class="l-col l-duration">Duration</span>
<span class="l-col l-activity-modes">Activity Modes</span>
</div>
</div>
<div class="t-swimlanes-holder l-swimlanes-holder"
mct-scroll-y="scroll.y">
<mct-include key="'timeline-tabular-swimlane-cols-data'"
ng-repeat="swimlane in timelineController.swimlanes()"
ng-model="swimlane">
</mct-include>
</div>
</div>
</div>
</div>
<!-- HORZ SPLITTER -->
<mct-splitter></mct-splitter>
<!-- BOTTOM PANE RESOURCE LEGEND -->
<div class="split-pane-component abs l-timeline-pane t-pane-h l-pane-btm s-timeline-resource-legend l-timeline-resource-legend">
<div class="l-title s-title">{{ngModel.title}}Resource Graph Legend</div>
<div class="l-legend-items legend">
<mct-include key="'timeline-legend-item'"
ng-repeat="swimlane in timelineController.swimlanes()"
ng-model="swimlane"
ng-show="swimlane.graph()">
</mct-include>
</div>
</div>
</mct-split-pane>
<!-- MAIN VERTICAL SPLITTER -->
<mct-splitter></mct-splitter>
<!-- RIGHT PANE: GANTT AND RESOURCE PLOTS -->
<span ng-controller="TimelineZoomController as zoomController" class="abs">
<mct-split-pane anchor="bottom"
position="pane.y"
class="abs split-pane-component l-timeline-pane l-pane-r t-pane-v"
>
<!-- TOP PANE GANTT BARS -->
<div class="split-pane-component l-timeline-pane t-pane-h l-pane-top t-timeline-gantt l-timeline-gantt s-timeline-gantt"
>
<div class="l-hover-btns-holder s-hover-btns-holder t-btns-zoom">
<a class="t-btn l-btn s-btn s-icon-btn"
ng-click="zoomController.zoom(-1)"
ng-show="true"
title="Zoom in">
<span class="ui-symbol icon zoom-in">X</span>
</a>
<a class="t-btn l-btn s-btn s-icon-btn"
ng-click="zoomController.zoom(1)"
ng-show="true"
title="Zoom out">
<span class="ui-symbol icon zoom-out">Y</span>
</a>
</div>
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;"
mct-scroll-x="scroll.x">
<mct-include key="'timeline-ticks'"
parameters="{
fullWidth: zoomController.toPixels(zoomController.duration()),
start: scroll.x,
width: scroll.width,
step: zoomController.toPixels(zoomController.zoom()),
toMillis: zoomController.toMillis
}">
</mct-include>
</div>
<!-- TO-DO:
Make this control y-scroll of both .t-swimlanes-holder elements in TOP PANE TABULAR AREA
-->
<div class="t-swimlanes-holder l-swimlanes-holder"
mct-scroll-x="scroll.x"
mct-scroll-y="scroll.y">
<div class="l-width-control"
ng-style="{ width: timelineController.width(zoomController) + 'px' }">
<div class="t-swimlane s-swimlane l-swimlane"
ng-repeat="swimlane in timelineController.swimlanes()"
ng-class="{
exceeded: swimlane.exceeded(),
selected: selection.selected(swimlane),
'drop-into': swimlane.highlight(),
'drop-after': swimlane.highlightBottom()
}"
ng-click="selection.select(swimlane)"
mct-swimlane-drop="swimlane">
<mct-representation key="'gantt'"
mct-object="swimlane.domainObject"
parameters="{
scroll: scroll,
toPixels: zoomController.toPixels
}">
</mct-representation>
<span ng-if="selection.selected(swimlane)">
<span ng-repeat="handle in timelineController.handles()"
ng-style="handle.style(zoomController)"
style="position: absolute; top: 0px; bottom: 0px;"
class="handle"
ng-class="{ start: $index === 0, mid: $index === 1, end: $index > 1 }"
mct-drag-down="handle.begin()"
mct-drag="handle.drag(delta[0], zoomController); timelineController.refresh()"
mct-drag-up="handle.finish()">
</span>
</span>
</div>
</div>
</div>
</div>
<!-- HORZ SPLITTER -->
<mct-splitter></mct-splitter>
<!-- BOTTOM PANE RESOURCE GRAPHS AND RIGHT PANE HORIZONTAL SCROLL CONTROL -->
<div class="split-pane-component l-timeline-resource-graph l-timeline-pane t-pane-h l-pane-btm">
<div class="l-graphs-holder"
mct-resize="scroll.width = bounds.width">
<!-- TO-DO: Make this control y-scroll of .t-graph-labels-holder -->
<div class="t-graphs l-graphs">
<mct-include key="'timeline-resource-graphs'"
parameters="{
origin: zoomController.toMillis(scroll.x),
duration: zoomController.toMillis(scroll.width),
graphs: timelineController.graphs()
}">
</mct-include>
</div>
</div>
<!-- TO-DO: Make this control x-scroll of .t-timeline-gantt -->
<div mct-scroll-x="scroll.x"
class="t-pane-r-scroll-h-control l-scroll-control s-scroll-control">
<div class="l-width-control"
ng-style="{ width: timelineController.width(zoomController) + 'px' }">
</div>
</div>
</div>
</mct-split-pane>
</span>
</mct-split-pane>
</div>

View File

@ -0,0 +1,27 @@
<!--
Open MCT Web, Copyright (c) 2009-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT Web includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<ul ng-controller="ActivityModeValuesController as controller" class="cols cols-2-ff properties">
<li ng-repeat="(key, value) in cost" class="l-row s-row">
<span class="col col-100px s-title">{{controller.metadata(key).name}}</span>
<span class="col s-value">{{value}} {{controller.metadata(key).units}}</span>
</li>
</ul>

View File

@ -0,0 +1,32 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
/**
* Defines constant values for use in timeline view.
*/
define({
// Pixel width of start/end handles
HANDLE_WIDTH: 32,
// Pixel tolerance for snapping behavior
SNAP_WIDTH: 16
});

View File

@ -0,0 +1,78 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
'use strict';
// Conversion factors from time units to milliseconds
var SECONDS = 1000,
MINUTES = SECONDS * 60,
HOURS = MINUTES * 60,
DAYS = HOURS * 24;
/**
* Formatters for durations shown in a timeline view.
* @constructor
*/
function TimelineFormatter() {
// Format a numeric value to a string with some number of digits
function formatValue(value, digits) {
var v = value.toString(10);
// Pad with zeroes
while (v.length < digits) {
v = "0" + v;
}
return v;
}
// Format duration to string
function formatDuration(duration) {
var days = Math.floor(duration / DAYS),
hours = Math.floor(duration / HOURS) % 24,
minutes = Math.floor(duration / MINUTES) % 60,
seconds = Math.floor(duration / SECONDS) % 60,
millis = Math.floor(duration) % 1000;
return formatValue(days, 3) + " " +
formatValue(hours, 2) + ":" +
formatValue(minutes, 2) + ":" +
formatValue(seconds, 2) + "." +
formatValue(millis, 3);
}
return {
/**
* Format the provided duration.
* @param {number} duration duration, in milliseconds
* @returns {string} displayable representation of duration
*/
format: formatDuration
};
}
return TimelineFormatter;
}
);

View File

@ -0,0 +1,121 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
'use strict';
/**
* Describes the time span of an activity object.
* @param model the activity's object model
*/
function ActivityTimespan(model, mutation) {
// Get the start time for this timeline
function getStart() {
return model.start.timestamp;
}
// Get the end time for this timeline
function getEnd() {
return model.start.timestamp + model.duration.timestamp;
}
// Get the duration of this timeline
function getDuration() {
return model.duration.timestamp;
}
// Get the epoch used by this timeline
function getEpoch() {
return model.start.epoch; // Surface elapsed time
}
// Set the start time associated with this object
function setStart(value) {
var end = getEnd();
mutation.mutate(function (model) {
model.start.timestamp = Math.max(value, 0);
// Update duration to keep end time
model.duration.timestamp = Math.max(end - value, 0);
}, model.modified);
}
// Set the duration associated with this object
function setDuration(value) {
mutation.mutate(function (model) {
model.duration.timestamp = Math.max(value, 0);
}, model.modified);
}
// Set the end time associated with this object
function setEnd(value) {
var start = getStart();
mutation.mutate(function (model) {
model.duration.timestamp = Math.max(value - start, 0);
}, model.modified);
}
return {
/**
* Get the start time, in milliseconds relative to the epoch.
* @returns {number} the start time
*/
getStart: getStart,
/**
* Get the duration, in milliseconds.
* @returns {number} the duration
*/
getDuration: getDuration,
/**
* Get the end time, in milliseconds relative to the epoch.
* @returns {number} the end time
*/
getEnd: getEnd,
/**
* Set the start time, in milliseconds relative to the epoch.
* @param {number} the new value
*/
setStart: setStart,
/**
* Set the duration, in milliseconds.
* @param {number} the new value
*/
setDuration: setDuration,
/**
* Set the end time, in milliseconds relative to the epoch.
* @param {number} the new value
*/
setEnd: setEnd,
/**
* Get a string identifying the reference epoch used for
* start and end times.
* @returns {string} the epoch
*/
getEpoch: getEpoch
};
}
return ActivityTimespan;
}
);

View File

@ -0,0 +1,63 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./ActivityTimespan'],
function (ActivityTimespan) {
'use strict';
/**
* Implements the `timespan` capability for Activity objects.
*
* @constructor
* @param $q Angular's $q, for promise-handling
* @param {DomainObject} domainObject the Activity
*/
function ActivityTimespanCapability($q, domainObject) {
// Promise time span
function promiseTimeSpan() {
return $q.when(new ActivityTimespan(
domainObject.getModel(),
domainObject.getCapability('mutation')
));
}
return {
/**
* Get the time span (start, end, duration) of this activity.
* @returns {Promise.<ActivityTimespan>} the time span of
* this activity
*/
invoke: promiseTimeSpan
};
}
// Only applies to timeline objects
ActivityTimespanCapability.appliesTo = function (model) {
return model && (model.type === 'activity');
};
return ActivityTimespanCapability;
}
);

View File

@ -0,0 +1,52 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Provides data to populate resource graphs associated
* with activities in a timeline view.
* This is a placeholder until WTD-918.
* @constructor
*/
function ActivityUtilization() {
return {
getPointCount: function () {
return 0;
},
getDomainValue: function (index) {
return 0;
},
getRangeValue: function (index) {
return 0;
}
};
}
return ActivityUtilization;
}
);

View File

@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Exposes costs associated with a subsystem mode.
* @constructor
*/
function CostCapability(domainObject) {
var model = domainObject.getModel();
return {
/**
* Get a list of resource types which have associated
* costs for this object. Returned values are machine-readable
* keys, and should be paired with external metadata for
* presentation (see category of extension `resources`).
* @returns {string[]} resource types
*/
resources: function () {
return Object.keys(model.resources || {}).sort();
},
/**
* Get the cost associated with a resource of an identified
* type (typically, one of the types reported from a
* `resources` call.)
* @param {string} key the resource type
* @returns {number} the associated cost
*/
cost: function (key) {
return (model.resources || {})[key] || 0;
},
/**
* Get an object containing key-value pairs describing
* resource utilization as described by this object.
* Keys are resource types; values are levels of associated
* resource utilization.
* @returns {object} resource utilizations
*/
invoke: function () {
return model.resources || {};
}
};
}
// Only applies to subsystem modes.
CostCapability.appliesTo = function (model) {
return (model || {}).type === 'mode';
};
return CostCapability;
}
);

View File

@ -0,0 +1,155 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Provide points for a cumulative resource summary graph, using
* a provided instantaneous resource summary graph.
*
* @param {ResourceGraph} graph the resource graph
* @param {number} minimum the minimum allowable level
* @param {number} maximum the maximum allowable level
* @param {number} initial the initial state of the resource
* @param {number} rate the rate at which one unit of instantaneous
* utilization changes the available level in one unit
* of domain values (that is, per millisecond)
* @constructor
*/
function CumulativeGraph(graph, minimum, maximum, initial, rate) {
var values;
// Calculate the domain value at which a line starting at
// (domain, range) and proceeding with the specified slope
// will have the specified range value.
function intercept(domain, range, slope, value) {
// value = slope * (intercept - domain) + range
// value - range = slope * ...
// intercept - domain = (value - range) / slope
// intercept = domain + (value - range) / slope
return domain + (value - range) / slope;
}
// Initialize the data values
function initializeValues() {
var values = [],
slope = 0,
previous = 0,
i;
// Add a point (or points, if needed) reaching to the provided
// domain and/or range value
function addPoint(domain, range) {
var previous = values[values.length - 1],
delta = domain - previous.domain, // time delta
change = delta * slope * rate, // change
next = previous.range + change;
// Crop to minimum boundary...
if (next < minimum) {
values.push({
domain: intercept(
previous.domain,
previous.range,
slope * rate,
minimum
),
range: minimum
});
next = minimum;
}
// ...and maximum boundary
if (next > maximum) {
values.push({
domain: intercept(
previous.domain,
previous.range,
slope * rate,
maximum
),
range: maximum
});
next = maximum;
}
// Add the new data value
if (delta > 0) {
values.push({ domain: domain, range: next });
}
slope = range;
}
values.push({ domain: 0, range: initial });
for (i = 0; i < graph.getPointCount(); i += 1) {
addPoint(graph.getDomainValue(i), graph.getRangeValue(i));
}
return values;
}
function convertToPercent(point) {
point.range = 100 *
(point.range - minimum) / (maximum - minimum);
}
// Calculate cumulative values...
values = initializeValues();
// ...and convert to percentages.
values.forEach(convertToPercent);
return {
/**
* Get the total number of points in this graph.
* @returns {number} the total number of points
*/
getPointCount: function () {
return values.length;
},
/**
* Get the domain value (timestamp) for a point in this graph.
* @returns {number} the domain value
*/
getDomainValue: function (index) {
return values[index].domain;
},
/**
* Get the range value (utilization level) for a point in
* this graph.
* @returns {number} the range value
*/
getRangeValue: function (index) {
return values[index].range;
}
};
}
return CumulativeGraph;
}
);

View File

@ -0,0 +1,99 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./ResourceGraph', './CumulativeGraph'],
function (ResourceGraph, CumulativeGraph) {
'use strict';
/**
* Implements the `graph` capability for Timeline and
* Activity objects.
*
* @constructor
* @param {DomainObject} domainObject the Timeline or Activity
*/
function GraphCapability($q, domainObject) {
// Build graphs for this group of utilizations
function buildGraphs(utilizations) {
var utilizationMap = {},
result = {};
// Bucket utilizations by type
utilizations.forEach(function (u) {
var k = u.key;
utilizationMap[k] = utilizationMap[k] || [];
utilizationMap[k].push(u);
});
// ...then convert to graphs
Object.keys(utilizationMap).forEach(function (k) {
result[k] = new ResourceGraph(utilizationMap[k]);
});
// Add battery state of charge
if (domainObject.getModel().type === 'timeline' &&
result.power &&
domainObject.getModel().capacity > 0) {
result.battery = new CumulativeGraph(
result.power,
0,
domainObject.getModel().capacity, // Watts
domainObject.getModel().capacity,
1 / 3600000 // millis-to-hour (since units are watt-hours)
);
}
return result;
}
return {
/**
* Get resource graphs associated with this object.
* This is given as a promise for key-value pairs,
* where keys are resource types and values are graph
* objects.
* @returns {Promise} a promise for resource graphs
*/
invoke: function () {
return $q.when(
domainObject.useCapability('utilization') || []
).then(buildGraphs);
}
};
}
// Only applies to timeline objects
GraphCapability.appliesTo = function (model) {
return model &&
((model.type === 'timeline') ||
(model.type === 'activity'));
};
return GraphCapability;
}
);

View File

@ -0,0 +1,149 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
// Utility function to copy an array, sorted by a specific field
function sort(array, field) {
return array.slice().sort(function (a, b) {
return a[field] - b[field];
});
}
/**
* Provides data to populate resource graphs associated
* with timelines and activities.
* @param {Array} utilizations resource utilizations
* @constructor
*/
function ResourceGraph(utilizations) {
// Overview of algorithm here:
// * Goal: Have a list of time/value pairs which represents
// points along a stepped chart of resource utilization.
// Each change (stepping up or down) should have two points,
// at the bottom and top of the step respectively.
// * Step 1: Prepare two lists of utilizations sorted by start
// and end times. The "starts" will become step-ups, the
// "ends" will become step-downs.
// * Step 2: Initialize empty arrays for results, and a variable
// for the current utilization level.
// * Step 3: While there are still start or end times to add...
// * Step 3a: Determine whether the next change should be a
// step-up (start) or step-down (end) based on which of the
// next start/end times comes next (note that starts and ends
// are both sorted, so we look at the head of the array.)
// * Step 3b: Pull the next start or end (per previous decision)
// and convert it to a time-delta pair, negating if it's an
// end time (to step down or "un-step")
// * Step 3c: Add a point at the new time and the current
// running total (first point in the step, before the change)
// then increment the running total and add a new point
// (second point in the step, after the change)
// * Step 4: Filter out unnecessary points (if two activities
// run up against each other, there will be a zero-duration
// spike if we don't filter out the extra points from their
// start/end times.)
//
var starts = sort(utilizations, "start"),
ends = sort(utilizations, "end"),
values = [],
running = 0;
// If there are sequences of points with the same timestamp,
// allow only the first and last.
function filterPoint(value, index, values) {
// Allow the first or last point as a base case; aside from
// that, allow only points that have different timestamps
// from their predecessor or successor.
return (index === 0) || (index === values.length - 1) ||
(value.domain !== values[index - 1].domain) ||
(value.domain !== values[index + 1].domain);
}
// Add a step up or down (Step 3c above)
function addDelta(time, delta) {
values.push({ domain: time, range: running });
running += delta;
values.push({ domain: time, range: running });
}
// Add a start time (Step 3b above)
function addStart() {
var next = starts.shift();
addDelta(next.start, next.value);
}
// Add an end time (Step 3b above)
function addEnd() {
var next = ends.shift();
addDelta(next.end, -next.value);
}
// Decide whether next step should correspond to a start or
// an end. (Step 3c above)
function pickStart() {
return ends.length < 1 ||
(starts.length > 0 && starts[0].start <= ends[0].end);
}
// Build up start/end arrays (step 3 above)
while (starts.length > 0 || ends.length > 0) {
(pickStart() ? addStart : addEnd)();
}
// Filter out excess points
values = values.filter(filterPoint);
return {
/**
* Get the total number of points in this graph.
* @returns {number} the total number of points
*/
getPointCount: function () {
return values.length;
},
/**
* Get the domain value (timestamp) for a point in this graph.
* @returns {number} the domain value
*/
getDomainValue: function (index) {
return values[index].domain;
},
/**
* Get the range value (utilization level) for a point in
* this graph.
* @returns {number} the range value
*/
getRangeValue: function (index) {
return values[index].range;
}
};
}
return ResourceGraph;
}
);

View File

@ -0,0 +1,126 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
'use strict';
/**
* Describes the time span of a timeline object.
* @param model the timeline's object model
* @param {Timespan[]} time spans of contained activities
*/
function TimelineTimespan(model, mutation, timespans) {
// Get the start time for this timeline
function getStart() {
return model.start.timestamp;
}
// Get the end time for another time span
function getTimespanEnd(timespan) {
return timespan.getEnd();
}
// Wrapper for Math.max; used for max-finding of end time
function max(a, b) {
return Math.max(a, b);
}
// Get the end time for this timeline
function getEnd() {
return timespans.map(getTimespanEnd).reduce(max, getStart());
}
// Get the duration of this timeline
function getDuration() {
return getEnd() - getStart();
}
// Set the start time associated with this object
function setStart(value) {
mutation.mutate(function (model) {
model.start.timestamp = Math.max(value, 0);
}, model.modified);
}
// Set the duration associated with this object
function setDuration(value) {
// No-op; duration is implicit
}
// Set the end time associated with this object
function setEnd(value) {
// No-op; end time is implicit
}
// Get the epoch used by this timeline
function getEpoch() {
return model.start.epoch;
}
return {
/**
* Get the start time, in milliseconds relative to the epoch.
* @returns {number} the start time
*/
getStart: getStart,
/**
* Get the duration, in milliseconds.
* @returns {number} the duration
*/
getDuration: getDuration,
/**
* Get the end time, in milliseconds relative to the epoch.
* @returns {number} the end time
*/
getEnd: getEnd,
/**
* Set the start time, in milliseconds relative to the epoch.
* @param {number} the new value
*/
setStart: setStart,
/**
* Set the duration, in milliseconds. Timeline durations are
* implicit, so this is actually a no-op
* @param {number} the new value
*/
setDuration: setDuration,
/**
* Set the end time, in milliseconds. Timeline end times are
* implicit, so this is actually a no-op.
* @param {number} the new value
*/
setEnd: setEnd,
/**
* Get a string identifying the reference epoch used for
* start and end times.
* @returns {string} the epoch
*/
getEpoch: getEpoch
};
}
return TimelineTimespan;
}
);

View File

@ -0,0 +1,89 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./TimelineTimespan'],
function (TimelineTimespan) {
'use strict';
/**
* Implements the `timespan` capability for Timeline objects.
*
* @constructor
* @param $q Angular's $q, for promise-handling
* @param {DomainObject} domainObject the Timeline
*/
function TimelineTimespanCapability($q, domainObject) {
// Check if a capability is defin
// Look up a child object's time span
function lookupTimeSpan(childObject) {
return childObject.useCapability('timespan');
}
// Check if a child object exposes a time span
function hasTimeSpan(childObject) {
return childObject.hasCapability('timespan');
}
// Instantiate a time span bounding other time spans
function giveTimeSpan(timespans) {
return new TimelineTimespan(
domainObject.getModel(),
domainObject.getCapability('mutation'),
timespans
);
}
// Build a time span object that fits all children
function buildTimeSpan(childObjects) {
return $q.all(
childObjects.filter(hasTimeSpan).map(lookupTimeSpan)
).then(giveTimeSpan);
}
// Promise
function promiseTimeSpan() {
return domainObject.useCapability('composition')
.then(buildTimeSpan);
}
return {
/**
* Get the time span (start, end, duration) of this timeline.
* @returns {Promise.<TimelineTimespan>} the time span of
* this timeline
*/
invoke: promiseTimeSpan
};
}
// Only applies to timeline objects
TimelineTimespanCapability.appliesTo = function (model) {
return model && (model.type === 'timeline');
};
return TimelineTimespanCapability;
}
);

View File

@ -0,0 +1,52 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Provides data to populate resource graphs associated
* with timelines in a timeline view.
* This is a placeholder until WTD-918.
* @constructor
*/
function TimelineUtilization() {
return {
getPointCount: function () {
return 1000;
},
getDomainValue: function (index) {
return 60000 * index;
},
getRangeValue: function (index) {
return Math.sin(index) * (index % 10);
}
};
}
return TimelineUtilization;
}
);

View File

@ -0,0 +1,219 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Provide the resource utilization over time for a timeline
* or activity object. A utilization is presented as an object
* with four properties:
* * `key`: The resource being utilized.
* * `value`: The numeric utilization of that resource.
* * `start`: The start time of the resource's utilization.
* * `end`: The duration of this resource's utilization.
* * `epoch`: The epoch to which `start` is relative.
* @constructor
*/
function UtilizationCapability($q, domainObject) {
// Utility function for array reduction
function concatenate(a, b) {
return (a || []).concat(b || []);
}
// Check whether an element in an array looks unique (for below)
function unique(element, index, array) {
return (index === 0) || (array[index - 1] !== element);
}
// Utility function to ensure sorted array is all unique
function uniquify(array) {
return array.filter(unique);
}
// Utility function for sorting strings arrays
function sort(array) {
return array.sort();
}
// Combine into one big array
function flatten(arrayOfArrays) {
return arrayOfArrays.reduce(concatenate, []);
}
// Promise the objects contained by this timeline/activity
function promiseComposition() {
return $q.when(domainObject.useCapability('composition') || []);
}
// Promise all subsystem modes associated with this object
function promiseModes() {
var relationship = domainObject.getCapability('relationship'),
modes = relationship && relationship.getRelatedObjects('modes');
return $q.when(modes || []);
}
// Promise the utilization which results directly from this object
function promiseInternalUtilization() {
var utilizations = {};
// Record the cost of a given activity mode
function addUtilization(mode) {
var cost = mode.getCapability('cost');
if (cost) {
cost.resources().forEach(function (k) {
utilizations[k] = utilizations[k] || 0;
utilizations[k] += cost.cost(k);
});
}
}
// Record costs for these modes
function addUtilizations(modes) {
modes.forEach(addUtilization);
}
// Look up start/end times for this object
function lookupTimespan() {
return domainObject.useCapability('timespan');
}
// Provide the result
function giveResult(timespan) {
// Convert to utilization objects
return Object.keys(utilizations).sort().map(function (k) {
return {
key: k,
value: utilizations[k],
start: timespan.getStart(),
end: timespan.getEnd(),
epoch: timespan.getEpoch()
};
});
}
return promiseModes()
.then(addUtilizations)
.then(lookupTimespan)
.then(giveResult);
}
// Look up a specific object's resource utilization
function lookupUtilization(domainObject) {
return domainObject.useCapability('utilization');
}
// Look up a specific object's resource utilization keys
function lookupUtilizationResources(domainObject) {
var utilization = domainObject.getCapability('utilization');
return utilization && utilization.resources();
}
// Promise a consolidated list of resource utilizations
function mapUtilization(objects) {
return $q.all(objects.map(lookupUtilization))
.then(flatten);
}
// Promise a consolidated list of resource utilization keys
function mapUtilizationResources(objects) {
return $q.all(objects.map(lookupUtilizationResources))
.then(flatten);
}
// Promise utilization associated with contained objects
function promiseExternalUtilization() {
// Get the composition, then consolidate their utilizations
return promiseComposition().then(mapUtilization);
}
// Get resource keys for this mode
function getModeKeys(mode) {
var cost = mode.getCapability('cost');
return cost ? cost.resources() : [];
}
// Map the above (for use in below)
function mapModeKeys(modes) {
return modes.map(getModeKeys);
}
// Promise identifiers for resources associated with modes
function promiseInternalKeys() {
return promiseModes().then(mapModeKeys).then(flatten);
}
// Promise identifiers for resources associated with modes
function promiseExternalKeys() {
return promiseComposition().then(mapUtilizationResources);
}
// Promise identifiers for resources used
function promiseResourceKeys() {
return $q.all([
promiseInternalKeys(),
promiseExternalKeys()
]).then(flatten).then(sort).then(uniquify);
}
// Promise all utilization
function promiseAllUtilization() {
// Concatenate internal utilization (from activity modes)
// with external utilization (from subactivities)
return $q.all([
promiseInternalUtilization(),
promiseExternalUtilization()
]).then(flatten);
}
return {
/**
* Get the keys for resources associated with this object.
* @returns {Promise.<string[]>} a promise for resource identifiers
*/
resources: promiseResourceKeys,
/**
* Get the resource utilization associated with this
* object. Results are not sorted. This requires looking
* at contained objects, which in turn must happen
* asynchronously, so this returns a promise.
* @returns {Promise.<Array>} a promise for all resource
* utilizations
*/
invoke: promiseAllUtilization
};
}
// Only applies to timelines and activities
UtilizationCapability.appliesTo = function (model) {
return model &&
((model.type === 'timeline') ||
(model.type === 'activity'));
};
return UtilizationCapability;
}
);

View File

@ -0,0 +1,62 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Controller which support the Values view of Activity Modes.
* @constructor
* @param {Array} resources definitions for extensions of
* category `resources`
*/
function ActivityModeValuesController(resources) {
var metadata = {};
// Store metadata for a specific resource type
function storeMetadata(resource) {
var key = (resource || {}).key;
if (key) {
metadata[key] = resource;
}
}
// Populate the lookup table to resource metadata
resources.forEach(storeMetadata);
return {
/**
* Look up metadata associated with the specified
* resource type.
*/
metadata: function (key) {
return metadata[key];
}
};
}
return ActivityModeValuesController;
}
);

View File

@ -0,0 +1,149 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[
'./swimlane/TimelineSwimlanePopulator',
'./graph/TimelineGraphPopulator',
'./drag/TimelineDragPopulator'
],
function (
TimelineSwimlanePopulator,
TimelineGraphPopulator,
TimelineDragPopulator
) {
'use strict';
/**
* Controller for the Timeline view.
* @constructor
*/
function TimelineController($scope, $q, objectLoader, MINIMUM_DURATION) {
var swimlanePopulator = new TimelineSwimlanePopulator(
objectLoader,
$scope.configuration || {},
$scope.selection
),
graphPopulator = new TimelineGraphPopulator($q),
dragPopulator = new TimelineDragPopulator(objectLoader);
// Hash together all modification times. A sum is sufficient here,
// since modified timestamps should be non-decreasing.
function modificationSum() {
var sum = 0;
swimlanePopulator.get().forEach(function (swimlane) {
sum += swimlane.domainObject.getModel().modified || 0;
});
return sum;
}
// Reduce graph states to a watch-able number. A bitmask is
// sufficient here, since only ~30 graphed elements make sense
// (due to limits on recognizably unique line colors)
function graphMask() {
var mask = 0, bit = 1;
swimlanePopulator.get().forEach(function (swimlane) {
mask += swimlane.graph() ? 0 : bit;
bit *= 2;
});
return mask;
}
// Repopulate based on detected modification to in-view objects
function repopulateSwimlanes() {
swimlanePopulator.populate($scope.domainObject);
dragPopulator.populate($scope.domainObject);
graphPopulator.populate(swimlanePopulator.get());
}
// Repopulate graphs based on modification to swimlane graph state
function repopulateGraphs() {
graphPopulator.populate(swimlanePopulator.get());
}
// Get pixel width for right pane, using zoom controller
function width(zoomController) {
var start = swimlanePopulator.start(),
end = swimlanePopulator.end();
return zoomController.toPixels(zoomController.duration(
Math.max(end - start, MINIMUM_DURATION)
));
}
// Refresh resource graphs
function refresh() {
if (graphPopulator) {
graphPopulator.get().forEach(function (graph) {
graph.refresh();
});
}
}
// Recalculate swimlane state on changes
$scope.$watch("domainObject", swimlanePopulator.populate);
// Also recalculate whenever anything in view is modified
$scope.$watch(modificationSum, repopulateSwimlanes);
// Carry over changes in swimlane set to changes in graphs
$scope.$watch(graphMask, repopulateGraphs);
// Convey current selection to drag handle populator
$scope.$watch("selection.get()", dragPopulator.select);
// Provide initial scroll bar state, container for pane positions
$scope.scroll = { x: 0, y: 0 };
$scope.panes = {};
// Expose active set of swimlanes
return {
/**
* Get the width, in pixels, of the timeline area
* @returns {number} width, in pixels
*/
width: width,
/**
* Get the swimlanes which should currently be displayed.
* @returns {TimelineSwimlane[]} the swimlanes
*/
swimlanes: swimlanePopulator.get,
/**
* Get the resource graphs which should currently be displayed.
* @returns {TimelineGraph[]} the graphs
*/
graphs: graphPopulator.get,
/**
* Get drag handles for the current selection.
* @returns {TimelineDragHandle[]} the drag handles
*/
handles: dragPopulator.get,
/**
* Refresh resource graphs (during drag.)
*/
refresh: refresh
};
}
return TimelineController;
}
);

View File

@ -0,0 +1,93 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,moment*/
define(
[],
function () {
"use strict";
/**
* Controller for the `datetime` form control.
* This is a composite control; it includes multiple
* input fields but outputs a single timestamp (in
* milliseconds since start of 1970) to the ngModel.
*
* @constructor
*/
function DateTimeController($scope) {
// Update the data model
function updateModel(datetime) {
var days = parseInt(datetime.days, 10) || 0,
hour = parseInt(datetime.hours, 10) || 0,
min = parseInt(datetime.minutes, 10) || 0,
sec = parseInt(datetime.seconds, 10) || 0,
epoch = "SET", // Only permit SET, for now
timestamp;
// Build up timestamp
timestamp = days * 24;
timestamp = (hour + timestamp) * 60;
timestamp = (min + timestamp) * 60;
timestamp = (sec + timestamp) * 1000;
// Set in the model
$scope.ngModel[$scope.field] = {
timestamp: timestamp,
epoch: epoch
};
}
// Update the displayed state
function updateForm(modelState) {
var timestamp = (modelState || {}).timestamp || 0,
datetime = $scope.datetime;
timestamp = Math.floor(timestamp / 1000);
datetime.seconds = timestamp % 60;
timestamp = Math.floor(timestamp / 60);
datetime.minutes = timestamp % 60;
timestamp = Math.floor(timestamp / 60);
datetime.hours = timestamp % 24;
timestamp = Math.floor(timestamp / 24);
datetime.days = timestamp;
}
// Retrieve state from field, for watch
function getModelState() {
return $scope.ngModel[$scope.field];
}
// Update value whenever any field changes.
$scope.$watchCollection("datetime", updateModel);
$scope.$watchCollection(getModelState, updateForm);
// Initialize the scope
$scope.datetime = {};
updateForm(getModelState());
}
return DateTimeController;
}
);

View File

@ -0,0 +1,88 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Control for Gantt bars in a timeline view.
* Primarily reesponsible for supporting the positioning of Gantt
* bars; particularly, this ensures that the left and right edges
* never go to far off screen, because in some environments this
* will effect rendering performance without visible results.
* @constructor
* @param {number} MAXIMUM_OFFSCREEN the maximum number of pixels
* allowed to go off-screen (to either the left or the right)
*/
function TimelineGanttController(MAXIMUM_OFFSCREEN) {
// Pixel position for the CSS left property
function left(timespan, scroll, toPixels) {
return Math.max(
toPixels(timespan.getStart()),
scroll.x - MAXIMUM_OFFSCREEN
);
}
// Pixel value for the CSS width property
function width(timespan, scroll, toPixels) {
var x = left(timespan, scroll, toPixels),
right = Math.min(
toPixels(timespan.getEnd()),
scroll.x + scroll.width + MAXIMUM_OFFSCREEN
);
return right - x;
}
return {
/**
* Get the pixel position for the `left` style property
* of a Gantt bar for the specified timespan.
* @param {Timespan} timespan the timespan to be represented
* @param scroll an object containing an `x` and `width`
* property, representing the scroll position and
* visible width, respectively.
* @param {Function} toPixels a function to convert
* a timestamp to a pixel position
* @returns {number} the pixel position of the left edge
*/
left: left,
/**
* Get the pixel value for the `width` style property
* of a Gantt bar for the specified timespan.
* @param {Timespan} timespan the timespan to be represented
* @param scroll an object containing an `x` and `width`
* property, representing the scroll position and
* visible width, respectively.
* @param {Function} toPixels a function to convert
* a timestamp to a pixel position
* @returns {number} the pixel width of this Gantt bar
*/
width: width
};
}
return TimelineGanttController;
}
);

View File

@ -0,0 +1,97 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
'use strict';
/**
* Controller for the graph area of a timeline view.
* The set of graphs to show is provided by the timeline
* controller and communicated into the template via "parameters"
* in scope.
* @constructor
*/
function TimelineGraphController($scope, resources) {
var resourceMap = {},
labelCache = {};
// Add an element to the resource map
function addToResourceMap(resource) {
var key = resource.key;
if (key && !resourceMap[key]) {
resourceMap[key] = resource;
}
}
// Update the display bounds for all graphs to match
// scroll and/or width.
function updateGraphs(parameters) {
(parameters.graphs || []).forEach(function (graph) {
graph.setBounds(parameters.origin, parameters.duration);
});
}
// Add all resources to map for simpler lookup
resources.forEach(addToResourceMap);
// Update graphs as parameters change
$scope.$watchCollection("parameters", updateGraphs);
return {
/**
* Get a label object (suitable to pass into the
* `timeline-resource-graph-labels` template) for
* the specified graph.
* @param {TimelineGraph} the graph to label
* @returns {object} an object containing labels
*/
label: function (graph) {
var key = graph.key,
resource = resourceMap[key] || {},
name = resource.name || "",
units = resource.units,
min = graph.minimum() || 0,
max = graph.maximum() || 0,
label = labelCache[key] || {};
// Cache the label (this is passed into a template,
// so avoid excessive digest cycles)
labelCache[key] = label;
// Include units in title
label.title = name + (units ? (" (" + units + ")") : "");
// Provide low, middle, high data values
label.low = min.toFixed(3);
label.middle = ((min + max) / 2).toFixed(3);
label.high = max.toFixed(3);
return label;
}
};
}
return TimelineGraphController;
}
);

View File

@ -0,0 +1,53 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
["../TimelineFormatter"],
function (TimelineFormatter) {
"use strict";
var FORMATTER = new TimelineFormatter();
/**
* Provides tabular data for the Timeline's tabular view area.
*/
function TimelineTableController() {
function getNiceTime(millis) {
return FORMATTER.format(millis);
}
return {
/**
* Return human-readable time in the expected format,
* currently SET.
* @param {number} millis duration, in millisecond
* @return {string} human-readable duration
*/
niceTime: getNiceTime
};
}
return TimelineTableController;
}
);

View File

@ -0,0 +1,118 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
["../TimelineFormatter"],
function (TimelineFormatter) {
"use strict";
var FORMATTER = new TimelineFormatter();
/**
* Provides labels for the tick mark area of a timeline view.
* Since the tick mark regin is potentially extremeley large,
* only the subset of ticks which will actually be shown in
* view are provided.
* @constructor
*/
function TimelineTickController() {
var labels = [],
lastFirst,
lastStep,
lastCount,
lastStartMillis,
lastEndMillis;
// Actually recalculate the labels from scratch
function calculateLabels(first, count, step, toMillis) {
var result = [],
current;
// Create enough labels to fill the visible area
while (result.length < count) {
current = first + step * result.length;
result.push({
// Horizontal pixel position of this label
left: current,
// Text to display in this label
text: FORMATTER.format(toMillis(current))
});
}
return result;
}
// Get tick labels for this pixel span (recalculating if needed)
function getLabels(start, width, step, toMillis) {
// Calculate parameters for labels (first pixel position, last
// pixel position.) These are checked to detect changes.
var first = Math.floor(start / step) * step,
last = Math.ceil((start + width) / step) * step,
count = ((last - first) / step) + 1,
startMillis = toMillis(first),
endMillis = toMillis(last),
changed = (lastFirst !== first) ||
(lastCount !== count) ||
(lastStep !== step) ||
(lastStartMillis !== startMillis) ||
(lastEndMillis !== endMillis);
// This will be used in a template, so only recalculate on
// change.
if (changed) {
labels = calculateLabels(first, count, step, toMillis);
// Cache to avoid recomputing later
lastFirst = first;
lastCount = count;
lastStep = step;
lastStartMillis = startMillis;
lastEndMillis = endMillis;
}
return labels;
}
return {
/**
* Get labels for use in the visible region of a timeline's
* tick mark area. This will return the same array instance
* (without recalculating its contents) if called with the
* same parameters (and same apparent zoom state, as determined
* via `toMillis`), so it is safe to use in a template.
*
* @param {number} start left-most pixel position in view
* @param {number} width pixel width in view
* @param {number} step size, in pixels, of each major tick
* @param {Function} toMillis function to convert from pixel
* positions to milliseconds
* @returns {Array} an array of tick mark labels, suitable
* for use in the `timeline-ticks` template
*/
labels: getLabels
};
}
return TimelineTickController;
}
);

View File

@ -0,0 +1,130 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['../TimelineFormatter'],
function (TimelineFormatter) {
"use strict";
var FORMATTER = new TimelineFormatter();
/**
* Controls the pan-zoom state of a timeline view.
* @constructor
*/
function TimelineZoomController($scope, ZOOM_CONFIGURATION) {
// Prefer to start with the middle index
var zoomLevels = ZOOM_CONFIGURATION.levels || [ 1000 ],
zoomIndex = Math.floor(zoomLevels.length / 2),
tickWidth = ZOOM_CONFIGURATION.width || 200,
duration = 86400000; // Default duration in view
// Round a duration to a larger value, to ensure space for editing
function roundDuration(value) {
// Ensure there's always an extra day or so
var sz = zoomLevels[zoomLevels.length - 1];
value *= 1.25; // Add 25% padding to start
return Math.ceil(value / sz) * sz;
}
// Get/set zoom level
function setZoomLevel(level) {
if (!isNaN(level)) {
// Modify zoom level, keeping it in range
zoomIndex = Math.min(
Math.max(level, 0),
zoomLevels.length - 1
);
}
}
// Persist current zoom level
function storeZoom() {
var isEditMode = $scope.commit &&
$scope.domainObject &&
$scope.domainObject.hasCapability('editor');
if (isEditMode) {
$scope.configuration = $scope.configuration || {};
$scope.configuration.zoomLevel = zoomIndex;
$scope.commit();
}
}
$scope.$watch("configuration.zoomLevel", setZoomLevel);
return {
/**
* Increase or decrease the current zoom level by a given
* number of steps. Positive steps zoom in, negative steps
* zoom out.
* If called with no arguments, this returns the current
* zoom level, expressed as the number of milliseconds
* associated with a given tick mark.
* @param {number} steps how many steps to zoom in
* @returns {number} current zoom level (as the size of a
* major tick mark, in pixels)
*/
zoom: function (amount) {
// Update the zoom level if called with an argument
if (arguments.length > 0 && !isNaN(amount)) {
setZoomLevel(zoomIndex + amount);
storeZoom(zoomIndex);
}
return zoomLevels[zoomIndex];
},
/**
* Get the width, in pixels, of a specific time duration at
* the current zoom level.
* @returns {number} the number of pixels
*/
toPixels: function (millis) {
return tickWidth * millis / zoomLevels[zoomIndex];
},
/**
* Get the time duration, in milliseconds, occupied by the
* width (specified in pixels) at the current zoom level.
* @returns {number} the number of pixels
*/
toMillis: function (pixels) {
return (pixels / tickWidth) * zoomLevels[zoomIndex];
},
/**
* Get or set the current displayed duration. If used as a
* setter, this will typically be rounded up to ensure extra
* space is available at the right.
* @returns {number} duration, in milliseconds
*/
duration: function (value) {
var prior = duration;
if (arguments.length > 0) {
duration = roundDuration(value);
}
return duration;
}
};
}
return TimelineZoomController;
}
);

View File

@ -0,0 +1,76 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./TimelineStartHandle', './TimelineEndHandle', './TimelineMoveHandle'],
function (TimelineStartHandle, TimelineEndHandle, TimelineMoveHandle) {
"use strict";
var DEFAULT_HANDLES = [
TimelineStartHandle,
TimelineMoveHandle,
TimelineEndHandle
],
TIMELINE_HANDLES = [
TimelineStartHandle,
TimelineMoveHandle
];
/**
* Create a factory for drag handles for timelines/activities
* in a timeline view.
* @constructor
*/
function TimelineDragHandleFactory(dragHandler, snapHandler) {
return {
/**
* Create drag handles for this domain object.
* @param {DomainObject} domainObject the object to be
* manipulated by these gestures
* @returns {Array} array of drag handles
*/
handles: function (domainObject) {
var type = domainObject.getCapability('type'),
id = domainObject.getId();
// Instantiate a handle
function instantiate(Handle) {
return new Handle(
id,
dragHandler,
snapHandler
);
}
// Instantiate smaller set of handles for timelines
return (type && type.instanceOf('timeline') ?
TIMELINE_HANDLES : DEFAULT_HANDLES)
.map(instantiate);
}
};
}
return TimelineDragHandleFactory;
}
);

View File

@ -0,0 +1,258 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Handles business logic (mutation of objects, retrieval of start/end
* times) associated with drag gestures to manipulate start/end times
* of activities and timelines in a Timeline view.
* @constructor
* @param {DomainObject} domainObject the object being viewed
* @param {ObjectLoader} objectLoader service to assist in loading
* subtrees
*/
function TimelineDragHandler(domainObject, objectLoader) {
var timespans = {},
persists = {},
mutations = {},
compositions = {},
dirty = {};
// "Cast" a domainObject to an id, if necessary
function toId(value) {
return (typeof value !== 'string' && value.getId) ?
value.getId() : value;
}
// Get the timespan associated with this domain object
function populateCapabilityMaps(domainObject) {
var id = domainObject.getId(),
timespanPromise = domainObject.useCapability('timespan');
if (timespanPromise) {
timespanPromise.then(function (timespan) {
// Cache that timespan
timespans[id] = timespan;
// And its mutation capability
mutations[id] = domainObject.getCapability('mutation');
// Also cache the persistence capability for later
persists[id] = domainObject.getCapability('persistence');
// And the composition, for bulk moves
compositions[id] = domainObject.getModel().composition || [];
});
}
}
// Populate the id->timespan map
function populateTimespans(subgraph) {
populateCapabilityMaps(subgraph.domainObject);
subgraph.composition.forEach(populateTimespans);
}
// Persist changes for objects by id (when dragging ends)
function doPersist(id) {
var persistence = persists[id],
mutation = mutations[id];
if (mutation) {
// Mutate just to update the timestamp (since we
// explicitly don't do this during the drag to
// avoid firing a ton of refreshes.)
mutation.mutate(function () {});
}
if (persistence) {
// Persist the changes
persistence.persist();
}
}
// Use the object loader to get objects which have timespans
objectLoader.load(domainObject, 'timespan').then(populateTimespans);
return {
/**
* Get a list of identifiers for domain objects which have
* timespans that are managed here.
* @returns {string[]} ids for all objects which have managed
* timespans here
*/
ids: function () {
return Object.keys(timespans).sort();
},
/**
* Persist any changes to timespans that have been made through
* this handler.
*/
persist: function () {
// Persist every dirty object...
Object.keys(dirty).forEach(doPersist);
// Clear out the dirty list
dirty = {};
},
/**
* Get the start time for a specific domain object. The domain
* object may be specified by its identifier, or passed as a
* domain object instance. If a second, numeric argument is
* passed, this functions as a setter.
* @returns {number} the start time
* @param {string|DomainObject} id the domain object to modify
* @param {number} [value] the new value
*/
start: function (id, value) {
// Convert to domain object id, look up timespan
var timespan = timespans[toId(id)];
// Use as setter if argument is present
if ((typeof value === 'number') && timespan) {
// Set the start (ensuring that it's non-negative,
// and not after the end time.)
timespan.setStart(
Math.min(Math.max(value, 0), timespan.getEnd())
);
// Mark as dirty for subsequent persistence
dirty[toId(id)] = true;
}
// Return value from the timespan
return timespan && timespan.getStart();
},
/**
* Get the end time for a specific domain object. The domain
* object may be specified by its identifier, or passed as a
* domain object instance. If a second, numeric argument is
* passed, this functions as a setter.
* @returns {number} the end time
* @param {string|DomainObject} id the domain object to modify
* @param {number} [value] the new value
*/
end: function (id, value) {
// Convert to domain object id, look up timespan
var timespan = timespans[toId(id)];
// Use as setter if argument is present
if ((typeof value === 'number') && timespan) {
// Set the end (ensuring it doesn't preceed start)
timespan.setEnd(
Math.max(value, timespan.getStart())
);
// Mark as dirty for subsequent persistence
dirty[toId(id)] = true;
}
// Return value from the timespan
return timespan && timespan.getEnd();
},
/**
* Get the duration for a specific domain object. The domain
* object may be specified by its identifier, or passed as a
* domain object instance. If a second, numeric argument is
* passed, this functions as a setter.
* @returns {number} the duration
* @param {string|DomainObject} id the domain object to modify
* @param {number} [value] the new value
*/
duration: function (id, value) {
// Convert to domain object id, look up timespan
var timespan = timespans[toId(id)];
// Use as setter if argument is present
if ((typeof value === 'number') && timespan) {
// Set duration (ensure that it's non-negative)
timespan.setDuration(
Math.max(value, 0)
);
// Mark as dirty for subsequent persistence
dirty[toId(id)] = true;
}
// Return value from the timespan
return timespan && timespan.getDuration();
},
/**
* Move the start and end of this domain object by the
* specified delta. Contained objects will move as well.
* @param {string|DomainObject} id the domain object to modify
* @param {number} delta the amount by which to change
*/
move: function (id, delta) {
// Overview of algorithm used here:
// - Build up list of ids to actually move
// - Find the minimum start time
// - Change delta so it cannot move minimum past 0
// - Update start, then end time
var ids = {},
queue = [toId(id)],
minStart;
// Update start & end, in that order
function updateStartEnd(id) {
var timespan = timespans[id], start, end;
if (timespan) {
// Get start/end so we don't get fooled by our
// own adjustments
start = timespan.getStart();
end = timespan.getEnd();
// Update start, then end
timespan.setStart(start + delta);
timespan.setEnd(end + delta);
// Mark as dirty for subsequent persistence
dirty[toId(id)] = true;
}
}
// Build up set of ids
while (queue.length > 0) {
// Get the next id to consider
id = queue.shift();
// If we haven't already considered this...
if (!ids[id]) {
// Add it to the set
ids[id] = true;
// And queue up its composition
queue = queue.concat(compositions[id] || []);
}
}
// Find the minimum start time
minStart = Object.keys(ids).map(function (id) {
// Get the start time; default to +Inf if not
// found, since this will not survive a min
// test if any real timespans are present
return timespans[id] ?
timespans[id].getStart() :
Number.POSITIVE_INFINITY;
}).reduce(function (a, b) {
// Reduce with a minimum test
return Math.min(a, b);
}, Number.POSITIVE_INFINITY);
// Ensure delta doesn't exceed bounds
delta = Math.max(delta, -minStart);
// Update start/end times
if (delta !== 0) {
Object.keys(ids).forEach(updateStartEnd);
}
}
};
}
return TimelineDragHandler;
}
);

View File

@ -0,0 +1,97 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./TimelineDragHandler', './TimelineSnapHandler', './TimelineDragHandleFactory'],
function (TimelineDragHandler, TimelineSnapHandler, TimelineDragHandleFactory) {
"use strict";
/**
* Provides drag handles for the active selection in a timeline view.
* @constructor
*/
function TimelineDragPopulator(objectLoader) {
var handles = [],
factory,
selectedObject;
// Refresh active set of drag handles
function refreshHandles() {
handles = (factory && selectedObject) ?
factory.handles(selectedObject) :
[];
}
// Create a new factory for handles, based on root object in view
function populateForObject(domainObject) {
var dragHandler = domainObject && new TimelineDragHandler(
domainObject,
objectLoader
);
// Reinstantiate the factory
factory = dragHandler && new TimelineDragHandleFactory(
dragHandler,
new TimelineSnapHandler(dragHandler)
);
// If there's a selected object, restore the handles
refreshHandles();
}
// Change the current selection
function select(swimlane) {
// Cache selection to restore handles if other changes occur
selectedObject = swimlane && swimlane.domainObject;
// Provide handles for this selection, if it's defined
refreshHandles();
}
return {
/**
* Get the currently-applicable set of drag handles.
* @returns {Array} drag handles
*/
get: function () {
return handles;
},
/**
* Set the root object in view. Drag interactions consider
* the full graph for snapping behavior, so this is needed.
* @param {DomainObject} domainObject the timeline object
* being viewed
*/
populate: populateForObject,
/**
* Update selection state. Passing undefined means there
* is no selection.
* @param {TimelineSwimlane} swimlane the selected swimlane
*/
select: select
};
}
return TimelineDragPopulator;
}
);

View File

@ -0,0 +1,98 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['../../TimelineConstants'],
function (Constants) {
"use strict";
/**
* Handle for changing the end time of a timeline or
* activity in the Timeline view.
* @constructor
* @param {string} id identifier of the domain object
* @param {TimelineDragHandler} dragHandler the handler which
* will update object state
* @param {TimelineSnapHandler} snapHandler the handler which
* provides candidate snap-to locations.
*/
function TimelineEndHandle(id, dragHandler, snapHandler) {
var initialEnd;
// Get the snap-to location for a timestamp
function snap(timestamp, zoom) {
return snapHandler.snap(
timestamp,
zoom.toMillis(Constants.SNAP_WIDTH),
id
);
}
return {
/**
* Start dragging this handle.
*/
begin: function () {
// Cache the initial state
initialEnd = dragHandler.end(id);
},
/**
* Drag this handle.
* @param {number} delta pixel delta from start
* @param {TimelineZoomController} zoom provider of zoom state
*/
drag: function (delta, zoom) {
if (initialEnd !== undefined) {
// Update the state
dragHandler.end(
id,
snap(initialEnd + zoom.toMillis(delta), zoom)
);
}
},
/**
* Finish dragging this handle.
*/
finish: function () {
// Clear initial state
initialEnd = undefined;
// Persist changes
dragHandler.persist();
},
/**
* Get a style object (suitable for passing into `ng-style`)
* for this handle.
* @param {TimelineZoomController} zoom provider of zoom state
*/
style: function (zoom) {
return {
left: zoom.toPixels(dragHandler.end(id)) - Constants.HANDLE_WIDTH + 'px',
width: Constants.HANDLE_WIDTH + 'px'
};
}
};
}
return TimelineEndHandle;
}
);

View File

@ -0,0 +1,136 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['../../TimelineConstants'],
function (Constants) {
"use strict";
/**
* Handle for moving (by drag) a timeline or
* activity in the Timeline view.
* @constructor
* @param {string} id identifier of the domain object
* @param {TimelineDragHandler} dragHandler the handler which
* will update object state
* @param {TimelineSnapHandler} snapHandler the handler which
* provides candidate snap-to locations.
*/
function TimelineMoveHandle(id, dragHandler, snapHandler) {
var initialStart,
initialEnd;
// Get the snap-to location for a timestamp
function snap(timestamp, zoom) {
return snapHandler.snap(
timestamp,
zoom.toMillis(Constants.SNAP_WIDTH),
id
);
}
// Convert a pixel delta to a millisecond delta that will align
// with some useful snap location
function snapDelta(delta, zoom) {
var timeDelta = zoom.toMillis(delta),
desiredStart = initialStart + timeDelta,
desiredEnd = initialEnd + timeDelta,
snappedStart = snap(desiredStart, zoom),
snappedEnd = snap(desiredEnd, zoom),
diffStart = Math.abs(snappedStart - desiredStart),
diffEnd = Math.abs(snappedEnd - desiredEnd),
chooseEnd = false;
// First, check for case where both changed...
if ((diffStart > 0) && (diffEnd > 0)) {
// ...and choose the smallest change that snaps.
chooseEnd = diffEnd < diffStart;
} else {
// ...otherwise, snap toward the end if it changed.
chooseEnd = diffEnd > 0;
}
// Start is chosen if diffEnd didn't snap, or nothing snapped
// Our delta is relative to our initial state, but
// dragHandler.move is relative to current state, so whichever
// end we're snapping to, we need to compute a delta
// relative to the current state to get the desired result.
return chooseEnd ?
(snappedEnd - dragHandler.end(id)) :
(snappedStart - dragHandler.start(id));
}
return {
/**
* Start dragging this handle.
*/
begin: function () {
// Cache the initial state
initialStart = dragHandler.start(id);
initialEnd = dragHandler.end(id);
},
/**
* Drag this handle.
* @param {number} delta pixel delta from start
* @param {TimelineZoomController} zoom provider of zoom state
*/
drag: function (delta, zoom) {
if (initialStart !== undefined && initialEnd !== undefined) {
if (delta !== 0) {
dragHandler.move(id, snapDelta(delta, zoom));
}
}
},
/**
* Finish dragging this handle.
*/
finish: function () {
// Clear initial state
initialStart = undefined;
initialEnd = undefined;
// Persist changes
dragHandler.persist();
},
/**
* Get a style object (suitable for passing into `ng-style`)
* for this handle.
* @param {TimelineZoomController} zoom provider of zoom state
*/
style: function (zoom) {
return {
left: zoom.toPixels(dragHandler.start(id)) +
Constants.HANDLE_WIDTH +
'px',
width: zoom.toPixels(dragHandler.duration(id)) -
Constants.HANDLE_WIDTH * 2
+ 'px'
//cursor: initialStart === undefined ? 'grab' : 'grabbing'
};
}
};
}
return TimelineMoveHandle;
}
);

View File

@ -0,0 +1,106 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Snaps timestamps to match other timestamps within a
* certain tolerance, to support the snap-to-start-and-end
* behavior of drag interactions in a timeline view.
* @constructor
* @param {TimelineDragHandler} dragHandler the handler
* for drag interactions, which maintains start/end
* information for timelines in this view.
*/
function TimelineSnapHandler(dragHandler) {
// Snap to other end points
function snap(timestamp, tolerance, exclude) {
var result = timestamp,
closest = tolerance,
ids,
candidates;
// Filter an id for inclustion
function include(id) { return id !== exclude; }
// Evaluate a candidate timestamp as a snap-to location
function evaluate(candidate) {
var difference = Math.abs(candidate - timestamp);
// Is this closer than anything else we've found?
if (difference < closest) {
// ...then this is our new result
result = candidate;
// Track how close it was, for subsequent comparison.
closest = difference;
}
}
// Look up start time; for mapping below
function getStart(id) {
return dragHandler.start(id);
}
// Look up end time; for mapping below
function getEnd(id) {
return dragHandler.end(id);
}
// Get list of candidate ids
ids = dragHandler.ids().filter(include);
// Get candidate timestamps
candidates = ids.map(getStart).concat(ids.map(getEnd));
// ...and find the best one
candidates.forEach(evaluate);
// Closest candidate (or original timestamp) is our result
// now, so return it.
return result;
}
return {
/**
* Get a timestamp location that is near this
* timestamp (or simply return the provided
* timestamp if none are near enough, according
* to the specified tolerance.)
* Start/end times associated with the domain object
* with the specified identifier will be excluded
* from consideration (to avoid an undesired snap-to-self
* behavior.)
* @param {number} timestamp the timestamp to snap
* @param {number} tolerance the difference within which
* to snap
* @param {string} id the identifier to exclude
*/
snap: snap
};
}
return TimelineSnapHandler;
}
);

View File

@ -0,0 +1,98 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['../../TimelineConstants'],
function (Constants) {
"use strict";
/**
* Handle for changing the start time of a timeline or
* activity in the Timeline view.
* @constructor
* @param {string} id identifier of the domain object
* @param {TimelineDragHandler} dragHandler the handler which
* will update object state
* @param {TimelineSnapHandler} snapHandler the handler which
* provides candidate snap-to locations.
*/
function TimelineStartHandle(id, dragHandler, snapHandler) {
var initialStart;
// Get the snap-to location for a timestamp
function snap(timestamp, zoom) {
return snapHandler.snap(
timestamp,
zoom.toMillis(Constants.SNAP_WIDTH),
id
);
}
return {
/**
* Start dragging this handle.
*/
begin: function () {
// Cache the initial state
initialStart = dragHandler.start(id);
},
/**
* Drag this handle.
* @param {number} delta pixel delta from start
* @param {TimelineZoomController} zoom provider of zoom state
*/
drag: function (delta, zoom) {
if (initialStart !== undefined) {
// Update the state
dragHandler.start(
id,
snap(initialStart + zoom.toMillis(delta), zoom)
);
}
},
/**
* Finish dragging this handle.
*/
finish: function () {
// Clear initial state
initialStart = undefined;
// Persist changes
dragHandler.persist();
},
/**
* Get a style object (suitable for passing into `ng-style`)
* for this handle.
* @param {TimelineZoomController} zoom provider of zoom state
*/
style: function (zoom) {
return {
left: zoom.toPixels(dragHandler.start(id)) + 'px',
width: Constants.HANDLE_WIDTH + 'px'
};
}
};
}
return TimelineStartHandle;
}
);

View File

@ -0,0 +1,193 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
'use strict';
/**
* Provides data to populate a graph in a timeline view.
* @constructor
* @param {string} key the resource's identifying key
* @param {Object.<string,DomainObject>} domainObjects and object
* containing key-value pairs where keys are colors, and
* values are DomainObject instances to be drawn in that
* color
* @param {TimelineGraphRenderer} renderer a renderer which
* can be used to prepare Float32Array instances
*/
function TimelineGraph(key, domainObjects, renderer) {
var drawingObject = { origin: [0, 0], dimensions: [0, 0], modified: 0},
// lines for the drawing object, by swimlane index
lines = [],
// min/max seen for a given swimlane, by swimlane index
extrema = [],
// current minimum
min = 0,
// current maximum
max = 0,
// current displayed time span
duration = 1000,
// line colors to display
colors = Object.keys(domainObjects);
// Get minimum value, ensure there's some room
function minimum() {
return (min >= max) ? (max - 1) : min;
}
// Get maximum value, ensure there's some room
function maximum() {
return (min >= max) ? (min + 1) : max;
}
// Update minimum and maximum values
function updateMinMax() {
// Find the minimum among plot lines
min = extrema.map(function (ex) {
return ex.min;
}).reduce(function (a, b) {
return Math.min(a, b);
}, Number.POSITIVE_INFINITY);
// Do the same for the maximum
max = extrema.map(function (ex) {
return ex.max;
}).reduce(function (a, b) {
return Math.max(a, b);
}, Number.NEGATIVE_INFINITY);
// Ensure the infinities don't survive
min = min === Number.POSITIVE_INFINITY ? max : min;
min = min === Number.NEGATIVE_INFINITY ? 0 : min;
max = max === Number.NEGATIVE_INFINITY ? min : max;
}
// Change contents of the drawing object (to trigger redraw)
function updateDrawingObject() {
// Update drawing object to include non-empty lines
drawingObject.lines = lines.filter(function (line) {
return line.points > 1;
});
// Update drawing bounds to fit data
drawingObject.origin[1] = minimum();
drawingObject.dimensions[1] = maximum() - minimum();
}
// Update a specific line, by index
function updateLine(graph, index) {
var buffer = renderer.render(graph),
line = lines[index],
ex = extrema[index],
i;
// Track minimum/maximum; note we skip x values
for (i = 1; i < buffer.length; i += 2) {
ex.min = Math.min(buffer[i], ex.min);
ex.max = Math.max(buffer[i], ex.max);
}
// Update line in drawing object
line.buffer = buffer;
line.points = graph.getPointCount();
line.color = renderer.decode(colors[index]);
// Update the graph's total min/max
if (line.points > 0) {
updateMinMax();
}
// Update the drawing object (used to draw the graph)
updateDrawingObject();
}
// Request initialization for a line's contents
function populateLine(color, index) {
var domainObject = domainObjects[color],
graphPromise = domainObject.useCapability('graph');
if (graphPromise) {
graphPromise.then(function (g) {
if (g[key]) {
updateLine(g[key], index);
}
});
}
}
// Create empty lines
lines = colors.map(function () {
// Sentinel value to exclude these lines
return { points: 0 };
});
// Specify initial min/max state per-line
extrema = colors.map(function () {
return {
min: Number.POSITIVE_INFINITY,
max: Number.NEGATIVE_INFINITY
};
});
// Start creating lines for all swimlanes
colors.forEach(populateLine);
return {
/**
* Get the minimum resource value that appears in this graph.
* @returns {number} the minimum value
*/
minimum: minimum,
/**
* Get the maximum resource value that appears in this graph.
* @returns {number} the maximum value
*/
maximum: maximum,
/**
* Set the displayed origin and duration, in milliseconds.
* @param {number} [value] value to set, if setting
*/
setBounds: function (offset, duration) {
// We don't update in-place, because we need the change
// to trigger a watch in mct-chart.
drawingObject.origin = [ offset, drawingObject.origin[1] ];
drawingObject.dimensions = [ duration, drawingObject.dimensions[1] ];
},
/**
* Redraw lines in this graph.
*/
refresh: function () {
colors.forEach(populateLine);
},
// Expose key, drawing object directly for use in templates
key: key,
drawingObject: drawingObject
};
}
return TimelineGraph;
}
);

View File

@ -0,0 +1,157 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./TimelineGraph', './TimelineGraphRenderer'],
function (TimelineGraph, TimelineGraphRenderer) {
'use strict';
/**
* Responsible for determining which resource graphs
* to display (based on capabilities exposed by included
* domain objects) and allocating data to those different
* graphs.
* @constructor
*/
function TimelineGraphPopulator($q) {
var graphs = [],
cachedAssignments = {},
renderer = new TimelineGraphRenderer();
// Compare two domain objects
function idsMatch(objA, objB) {
return (objA && objA.getId && objA.getId()) ===
(objB && objB.getId && objB.getId());
}
// Compare two object sets for equality, to detect
// when graph updates are truly needed.
function deepEquals(objA, objB) {
var keysA, keysB;
// Check if all keys in both objects match
function keysMatch(keys) {
return keys.map(function (k) {
return deepEquals(objA[k], objB[k]);
}).reduce(function (a, b) {
return a && b;
}, true);
}
// First, check if they're matching domain objects
if (typeof (objA && objA.getId) === 'function') {
return idsMatch(objA, objB);
}
// Otherwise, assume key-value pairs
keysA = Object.keys(objA || {}).sort();
keysB = Object.keys(objB || {}).sort();
return (keysA.length === keysB.length) && keysMatch(keysA);
}
// Populate the graphs for these swimlanes
function populate(swimlanes) {
// Somewhere to store resource assignments
// (as key -> swimlane[])
var assignments = {};
// Look up resources for a domain object
function lookupResources(swimlane) {
var graphs = swimlane.domainObject.useCapability('graph');
function getKeys(obj) {
return Object.keys(obj);
}
return $q.when(graphs ? (graphs.then(getKeys)) : []);
}
// Add all graph assignments appropriate for this swimlane
function buildAssignments(swimlane) {
// Assign this swimlane to graphs for its resource keys
return lookupResources(swimlane).then(function (resources) {
resources.forEach(function (key) {
assignments[key] = assignments[key] || {};
assignments[key][swimlane.color()] =
swimlane.domainObject;
});
});
}
// Make a graph for this resource (after assigning)
function makeGraph(key) {
return new TimelineGraph(
key,
assignments[key],
renderer
);
}
// Used to filter down to swimlanes which need graphs
function needsGraph(swimlane) {
// Only show swimlanes with graphs & resources to graph
return swimlane.graph() &&
swimlane.domainObject.hasCapability('graph');
}
// Create graphs according to assignments that have been built
function createGraphs() {
// Only refresh graphs if our assignments actually changed
if (!deepEquals(cachedAssignments, assignments)) {
// Make new graphs
graphs = Object.keys(assignments).sort().map(makeGraph);
// Save resource->color->object assignments
cachedAssignments = assignments;
} else {
// Just refresh the existing graphs
graphs.forEach(function (graph) {
graph.refresh();
});
}
}
// Build up list of assignments, then create graphs
$q.all(swimlanes.filter(needsGraph).map(buildAssignments))
.then(createGraphs);
}
return {
/**
* Populate (or re-populate) the list of available resource
* graphs, based on the provided list of swimlanes (and their
* current state.)
* @param {TimelineSwimlane[]} swimlanes the swimlanes to use
*/
populate: populate,
/**
* Get the current list of displayable resource graphs.
* @returns {TimelineGraph[]} the resource graphs
*/
get: function () {
return graphs;
}
};
}
return TimelineGraphPopulator;
}
);

View File

@ -0,0 +1,83 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Float32Array*/
define(
[],
function () {
'use strict';
/**
* Responsible for preparing data for display by
* `mct-chart` in a timeline's resource graph.
* @constructor
*/
function TimelineGraphRenderer() {
return {
/**
* Render a resource utilization to a Float32Array,
* to be passed to WebGL for display.
* @param {ResourceGraph} graph the resource utilization
* @returns {Float32Array} the rendered buffer
*/
render: function (graph) {
var count = graph.getPointCount(),
buffer = new Float32Array(count * 2),
i;
// Populate the buffer
for (i = 0; i < count; i += 1) {
buffer[i * 2] = graph.getDomainValue(i);
buffer[i * 2 + 1] = graph.getRangeValue(i);
}
return buffer;
},
/**
* Convert an HTML color (in #-prefixed 6-digit hexadecimal)
* to an array of floating point values in a range of 0.0-1.0.
* An alpha element is included to facilitate display in an
* `mct-chart` (which uses WebGL.)
* @param {string} the color
* @returns {number[]} the same color, in floating-point format
*/
decode: function (color) {
// Check for bad input, default to black if needed
color = /^#[A-Fa-f0-9]{6}$/.test(color) ? color : "#000000";
// Pull out R, G, B hex values
return [
color.substring(1, 3),
color.substring(3, 5),
color.substring(5, 7)
].map(function (c) {
// Hex -> number
return parseInt(c, 16) / 255;
}).concat([1]); // Add the alpha channel
}
};
}
return TimelineGraphRenderer;
}
);

View File

@ -0,0 +1,122 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
var COLOR_OPTIONS = [
"#20b2aa",
"#9acd32",
"#ff8c00",
"#d2b48c",
"#40e0d0",
"#4169ff",
"#ffd700",
"#6a5acd",
"#ee82ee",
"#cc9966",
"#99cccc",
"#66cc33",
"#ffcc00",
"#ff6633",
"#cc66ff",
"#ff0066",
"#ffff00",
"#800080",
"#00868b",
"#008a00",
"#ff0000",
"#0000ff",
"#f5deb3",
"#bc8f8f",
"#4682b4",
"#ffafaf",
"#43cd80",
"#cdc1c5",
"#a0522d",
"#6495ed"
],
// Fall back to black, as "no more colors available"
FALLBACK_COLOR = "#000000";
/**
* Responsible for choosing unique colors for the resource
* graph listing of a timeline view. Supports TimelineController.
* @constructor
* @param colors an object to store color configuration into;
* typically, this should be a property from the view's
* configuration, but TimelineSwimlane manages this.
*/
function TimelineColorAssigner(colors) {
// Find an unused color
function freeColor() {
// Set of used colors
var set = {}, found;
// Build up a set of used colors
Object.keys(colors).forEach(function (id) {
set[colors[id]] = true;
});
// Find an unused color
COLOR_OPTIONS.forEach(function (c) {
found = (!set[c] && !found) ? c : found;
});
// Provide the color
return found || FALLBACK_COLOR;
}
return {
/**
* Get the current color assignment.
* @param {string} id the id to which the color is assigned
*/
get: function (id) {
return colors[id];
},
/**
* Assign a new color to this id. If no color is specified,
* an unused color will be chosen.
* @param {string} id the id to which the color is assigned
* @param {string} [color] the new color to assign
*/
assign: function (id, color) {
colors[id] = typeof color === 'string' ? color : freeColor();
},
/**
* Release the color assignment for this id. That id will
* no longer have a color associated with it, and its color
* will be free to use in subsequent calls.
* @param {string} id the id whose color should be released
*/
release: function (id) {
delete colors[id];
}
};
}
return TimelineColorAssigner;
}
);

View File

@ -0,0 +1,79 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Selection proxy for the Timeline view. Implements
* behavior associated with the Add button in the
* timeline's toolbar.
* @constructor
*/
function TimelineProxy(domainObject, selection) {
var actionMap = {};
// Populate available Create actions for this domain object
function populateActionMap(domainObject) {
var actionCapability = domainObject.getCapability('action'),
actions = actionCapability ?
actionCapability.getActions('create') : [];
actions.forEach(function (action) {
actionMap[action.getMetadata().type] = action;
});
}
// Populate available actions based on current selection
// (defaulting to object-in-view if there is none.)
function populateForSelection() {
var swimlane = selection && selection.get(),
selectedObject = swimlane && swimlane.domainObject;
populateActionMap(selectedObject || domainObject);
}
populateActionMap(domainObject);
return {
/**
* Add a domain object of the specified type.
* @param {string} type the type of domain object to add
*/
add: function (type) {
// Update list of create actions; this needs to reflect
// the current selection so that Save in defaults
// appropriately.
populateForSelection();
// Create an object of that type
if (actionMap[type]) {
return actionMap[type].perform();
}
}
};
}
return TimelineProxy;
}
);

View File

@ -0,0 +1,177 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Describes a swimlane in a timeline view. This will be
* used directly from timeline view.
*
* Only general properties of swimlanes are included here.
* Since swimlanes are also directly selected and exposed to the
* toolbar, the TimelineSwimlaneDecorator should also be used
* to add additional properties to specific swimlanes.
*
* @constructor
* @param {DomainObject} domainObject the represented object
* @param {TimelineColorAssigner} assigner color assignment handler
* @param configuration the view's configuration object
* @param {TimelineSwimlane} parent the parent swim lane (if any)
*/
function TimelineSwimlane(domainObject, assigner, configuration, parent, index) {
var id = domainObject.getId(),
highlight = false, // Drop highlight (middle)
highlightBottom = false, // Drop highlight (lower)
idPath = (parent ? parent.idPath : []).concat([domainObject.getId()]),
depth = parent ? (parent.depth + 1) : 0,
timespan,
path = (!parent || !parent.parent) ? "" : parent.path +
//(parent.path.length > 0 ? " / " : "") +
parent.domainObject.getModel().name +
" > ";
// Look up timespan for this object
domainObject.useCapability('timespan').then(function (t) {
timespan = t;
});
return {
/**
* Check if this swimlane is currently visible. (That is,
* check to see if its parents are expanded.)
* @returns {boolean} true if it is visible
*/
visible: function () {
return !parent || (parent.expanded && parent.visible());
},
/**
* Show the Edit Properties dialog.
*/
properties: function () {
return domainObject.getCapability("action").perform("properties");
},
/**
* Toggle inclusion of this swimlane's represented object in
* the resource graph area.
*/
toggleGraph: function () {
configuration.graph = configuration.graph || {};
configuration.graph[id] = !configuration.graph[id];
// Assign or release legend color
assigner[configuration.graph[id] ? 'assign' : 'release'](id);
},
/**
* Get (or set, if an argument is provided) the flag which
* determines if the object in this swimlane is included in
* the set of active resource graphs.
* @param {boolean} [value] the state to set (if setting)
* @returns {boolean} true if included; otherwise false
*/
graph: function (value) {
// Set if an argument was provided
if (arguments.length > 0) {
configuration.graph = configuration.graph || {};
configuration.graph[id] = !!value;
// Assign or release the legend color
assigner[value ? 'assign' : 'release'](id);
}
// Provide the current state
return (configuration.graph || {})[id];
},
/**
* Get (or set, if an argument is provided) the color
* associated with this swimlane when its contents are
* included in the set of active resource graphs.
* @param {string} [value] the color to set (if setting)
* @returns {string} the color for resource graphing
*/
color: function (value) {
// Set if an argument was provided
if (arguments.length > 0) {
// Defer to the color assigner
assigner.assign(id, value);
}
// Provide the current value
return assigner.get(id);
},
/**
* Get (or set, if an argument is provided) the drag
* highlight state for this swimlane. True means the body
* of the swimlane should be highlighted for drop into.
*/
highlight: function (value) {
// Set if an argument was provided
if (arguments.length > 0) {
highlight = value;
}
// Provide current value
return highlight;
},
/**
* Get (or set, if an argument is provided) the drag
* highlight state for this swimlane. True means the body
* of the swimlane should be highlighted for drop after.
*/
highlightBottom: function (value) {
// Set if an argument was provided
if (arguments.length > 0) {
highlightBottom = value;
}
// Provide current value
return highlightBottom;
},
/**
* Check if a swimlane exceeds the bounds of its parent.
* @returns {boolean} true if there is a bounds violation
*/
exceeded: function () {
var parentTimespan = parent && parent.timespan();
return timespan && parentTimespan &&
(timespan.getStart() < parentTimespan.getStart() ||
timespan.getEnd() > parentTimespan.getEnd());
},
/**
* Get the timespan associated with this swimlane
*/
timespan: function () {
return timespan;
},
// Expose domain object, expansion state, indentation depth
domainObject: domainObject,
expanded: true,
depth: depth,
path: path,
id: id,
idPath: idPath,
parent: parent,
index: index,
children: [] // Populated by populator
};
}
return TimelineSwimlane;
}
);

View File

@ -0,0 +1,114 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./TimelineSwimlaneDropHandler'],
function (TimelineSwimlaneDropHandler) {
"use strict";
var ACTIVITY_RELATIONSHIP = "modes";
/**
* Adds optional methods to TimelineSwimlanes, in order
* to conditionally make available options in the toolbar.
* @constructor
*/
function TimelineSwimlaneDecorator(swimlane, selection) {
var domainObject = swimlane && swimlane.domainObject,
model = (domainObject && domainObject.getModel()) || {},
mutator = domainObject && domainObject.getCapability('mutation'),
persister = domainObject && domainObject.getCapability('persistence'),
type = domainObject && domainObject.getCapability('type'),
dropHandler = new TimelineSwimlaneDropHandler(swimlane);
// Activity Modes dialog
function modes(value) {
// Can be used as a setter...
if (arguments.length > 0 && Array.isArray(value)) {
// Update the relationships
mutator.mutate(function (model) {
model.relationships = model.relationships || {};
model.relationships[ACTIVITY_RELATIONSHIP] = value;
}).then(persister.persist);
}
// ...otherwise, use as a getter
return (model.relationships || {})[ACTIVITY_RELATIONSHIP] || [];
}
// Activity Link dialog
function link(value) {
// Can be used as a setter...
if (arguments.length > 0 && (typeof value === 'string') &&
value !== model.link) {
// Update the link
mutator.mutate(function (model) {
model.link = value;
}).then(persister.persist);
}
return model.link;
}
// Fire the Remove action
function remove() {
return domainObject.getCapability("action").perform("remove");
}
// Select the current swimlane
function select() {
selection.select(swimlane);
}
// Check if the swimlane is selected
function selected() {
return selection.get() === swimlane;
}
// Activities should have the Activity Modes and Activity Link dialog
if (type && type.instanceOf("activity") && mutator && persister) {
swimlane.modes = modes;
swimlane.link = link;
}
// Everything but the top-level object should have Remove
if (swimlane.parent) {
swimlane.remove = remove;
}
// We're in edit mode, if a selection is available
if (selection) {
// Add shorthands to select, and check for selection
swimlane.select = select;
swimlane.selected = selected;
}
// Expose drop handlers (which needed a reference to the swimlane)
swimlane.allowDropIn = dropHandler.allowDropIn;
swimlane.allowDropAfter = dropHandler.allowDropAfter;
swimlane.drop = dropHandler.drop;
return swimlane;
}
return TimelineSwimlaneDecorator;
}
);

View File

@ -0,0 +1,207 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
/**
* Handles drop (from drag-and-drop) initiated changes to a swimlane.
* @constructor
*/
function TimelineSwimlaneDropHandler(swimlane) {
// Utility function; like $q.when, but synchronous (to reduce
// performance impact when wrapping synchronous values)
function asPromise(value) {
return (value && value.then) ? value : {
then: function (callback) {
return asPromise(callback(value));
}
};
}
// Check if we are in edit mode
function inEditMode() {
return swimlane.domainObject.hasCapability("editor");
}
// Boolean and (for reduce below)
function or(a, b) {
return a || b;
}
// Check if pathA entirely contains pathB
function pathContains(swimlane, id) {
// Check if id at a specific index matches (for map below)
function matches(pathId) {
return pathId === id;
}
// Path A contains Path B if it is longer, and all of
// B's ids match the ids in A.
return swimlane.idPath.map(matches).reduce(or, false);
}
// Check if a swimlane contains a child with the specified id
function contains(swimlane, id) {
// Check if a child swimlane has a matching domain object id
function matches(child) {
return child.domainObject.getId() === id;
}
// Find any one child id that matches this id
return swimlane.children.map(matches).reduce(or, false);
}
// Remove a domain object from its current location
function remove(domainObject) {
return domainObject &&
domainObject.getCapability('action').perform('remove');
}
// Initiate mutation of a domain object
function doMutate(domainObject, mutator) {
return asPromise(
domainObject.useCapability("mutation", mutator)
).then(function () {
// Persist the results of mutation
var persistence = domainObject.getCapability("persistence");
if (persistence) {
// Persist the changes
persistence.persist();
}
});
}
// Check if this swimlane is in a state where a drop-after will
// act as a drop-into-at-first position (expanded and non-empty)
function expandedForDropInto() {
return swimlane.expanded && swimlane.children.length > 0;
}
// Check if the swimlane is ready to accept a drop-into
// (instead of drop-after)
function isDropInto() {
return swimlane.highlight() || expandedForDropInto();
}
// Choose an index for insertion in a domain object's composition
function chooseTargetIndex(id, offset, composition) {
return Math.max(
Math.min(
(composition || []).indexOf(id) + offset,
(composition || []).length
),
0
);
}
// Insert an id into target's composition
function insert(id, target, indexOffset) {
var myId = swimlane.domainObject.getId();
return doMutate(target, function (model) {
model.composition.splice(
chooseTargetIndex(myId, indexOffset, model.composition),
0,
id
);
});
}
// Check if a compose action is allowed for the object in this
// swimlane (we handle the link differently to set the index,
// but check for the existence of the action to invole the
// relevant policies.)
function allowsCompose(swimlane, domainObject) {
var actionCapability =
swimlane.domainObject.getCapability('action');
return actionCapability && actionCapability.getActions({
key: 'compose',
selectedObject: domainObject
}).length > 0;
}
return {
/**
* Check if a drop-into should be allowed for this swimlane,
* for the provided domain object identifier.
* @param {string} id identifier for the domain object to be
* dropped
* @returns {boolean} true if this should be allowed
*/
allowDropIn: function (id, domainObject) {
return inEditMode() &&
!pathContains(swimlane, id) &&
!contains(swimlane, id) &&
allowsCompose(swimlane, domainObject);
},
/**
* Check if a drop-after should be allowed for this swimlane,
* for the provided domain object identifier.
* @param {string} id identifier for the domain object to be
* dropped
* @returns {boolean} true if this should be allowed
*/
allowDropAfter: function (id, domainObject) {
var target = expandedForDropInto() ?
swimlane : swimlane.parent;
return inEditMode() &&
target &&
!pathContains(target, id) &&
allowsCompose(target, domainObject);
},
/**
* Drop the provided domain object into a timeline. This is
* provided as a mandatory id, and an optional domain object
* instance; if the latter is provided, it will be removed
* from its parent before being added. (This is specifically
* to support moving Activity objects around within a Timeline.)
* @param {string} id the identifier for the domain object
* @param {DomainObject} [domainObject] the object itself
*/
drop: function (id, domainObject) {
// Get the desired drop object, and destination index
var dropInto = isDropInto(),
dropTarget = dropInto ?
swimlane.domainObject :
swimlane.parent.domainObject,
dropIndexOffset = (!dropInto) ? 1 :
(swimlane.expanded && swimlane.highlightBottom()) ?
Number.NEGATIVE_INFINITY :
Number.POSITIVE_INFINITY;
if (swimlane.highlight() || swimlane.highlightBottom()) {
// Remove the domain object from its original location...
return asPromise(remove(domainObject)).then(function () {
// ...then insert it at its new location.
insert(id, dropTarget, dropIndexOffset);
});
}
}
};
}
return TimelineSwimlaneDropHandler;
}
);

View File

@ -0,0 +1,185 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[
'./TimelineSwimlane',
'./TimelineSwimlaneDecorator',
'./TimelineColorAssigner',
'./TimelineProxy'
],
function (
TimelineSwimlane,
TimelineSwimlaneDecorator,
TimelineColorAssigner,
TimelineProxy
) {
'use strict';
/**
* Populates and maintains a list of swimlanes for a given
* timeline view.
* @constructor
*/
function TimelineSwimlanePopulator(objectLoader, configuration, selection) {
var swimlanes = [],
start = Number.POSITIVE_INFINITY,
end = Number.NEGATIVE_INFINITY,
colors = (configuration.colors || {}),
assigner = new TimelineColorAssigner(colors);
// Track extremes of start/end times
function trackStartEnd(timespan) {
if (timespan) {
start = Math.min(start, timespan.getStart());
end = Math.max(end, timespan.getEnd());
}
}
// Add domain object (and its subgraph) in as swimlanes
function populateSwimlanes(subgraph, parent, index) {
var domainObject = subgraph.domainObject,
swimlane;
// For the recursive step
function populate(childSubgraph, index) {
populateSwimlanes(childSubgraph, swimlane, index);
}
// Make sure we have a valid object instance...
if (domainObject) {
// Create the new swimlane
swimlane = new TimelineSwimlaneDecorator(new TimelineSwimlane(
domainObject,
assigner,
configuration,
parent,
index || 0
), selection);
// Track start & end times of this domain object
domainObject.useCapability('timespan').then(trackStartEnd);
// Add it to our list
swimlanes.push(swimlane);
// Fill in parent's children
((parent || {}).children || []).push(swimlane);
// Add in children
subgraph.composition.forEach(populate);
}
}
// Restore a selection
function reselect(path, candidates, depth) {
// Next ID on the path
var next = path[depth || 0];
// Ensure a default
depth = depth || 0;
// Search through this layer of candidates to see
// if they might contain our selection (based on id path)
candidates.forEach(function (swimlane) {
// Check if we're on the right path...
if (swimlane.id === next) {
// Do we still have ids to check?
if (depth < path.length - 1) {
// Yes, so recursively explore that path
reselect(path, swimlane.children, depth + 1);
} else {
// Nope, we found the object to select
selection.select(swimlane);
}
}
});
}
// Handle population of swimlanes
function recalculateSwimlanes(domainObject) {
function populate(subgraph) {
// Cache current selection state during refresh
var selected = selection && selection.get(),
selectedIdPath = selected && selected.idPath;
// Clear existing swimlanes
swimlanes = [];
// Build new set of swimlanes
populateSwimlanes(subgraph);
// Restore selection, if there was one
if (selectedIdPath && swimlanes.length > 0) {
reselect(selectedIdPath, [swimlanes[0]]);
}
}
// Repopulate swimlanes for this object
if (!domainObject) {
populate({});
} else {
objectLoader.load(domainObject, 'timespan').then(populate);
}
// Set the selection proxy as well (for the Add button)
if (selection) {
selection.proxy(
domainObject && new TimelineProxy(domainObject, selection)
);
}
}
// Ensure colors are exposed in configuration
configuration.colors = colors;
return {
/**
* Update list of swimlanes to match those reachable from this
* object.
* @param {DomainObject} the timeline being viewed
*/
populate: recalculateSwimlanes,
/**
* Get a list of swimlanes for this timeline view.
* @returns {TimelineSwimlane[]} current swimlanes
*/
get: function () {
return swimlanes;
},
/**
* Get the first timestamp in the set of swimlanes.
* @returns {number} first timestamp
*/
start: function () {
return start;
},
/**
* Get the last timestamp in the set of swimlanes.
* @returns {number} first timestamp
*/
end: function () {
return end;
}
};
}
return TimelineSwimlanePopulator;
}
);

View File

@ -0,0 +1,68 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./SwimlaneDragConstants'],
function (SwimlaneDragConstants) {
"use strict";
/**
* Defines the `mct-swimlane-drag` directive. When a drag is initiated
* form an element with this attribute, the swimlane being dragged
* (identified by the value of this attribute, as an Angular expression)
* will be exported to the `dndService` as part of the active drag-drop
* state.
* @param {DndService} dndService drag-and-drop service
*/
function MCTSwimlaneDrag(dndService) {
function link(scope, element, attrs) {
// Look up the swimlane from the provided expression
function swimlane() {
return scope.$eval(attrs.mctSwimlaneDrag);
}
// When drag starts, publish via dndService
element.on('dragstart', function () {
dndService.setData(
SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE,
swimlane()
);
});
// When drag ends, clear via dndService
element.on('dragend', function () {
dndService.removeData(
SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
);
});
}
return {
// Applies to attributes
restrict: "A",
// Link using above function
link: link
};
}
return MCTSwimlaneDrag;
}
);

View File

@ -0,0 +1,127 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./SwimlaneDragConstants'],
function (SwimlaneDragConstants) {
"use strict";
/**
* Defines the `mct-swimlane-drop` directive. When a drop occurs
* on an element with this attribute, the swimlane targeted by the drop
* (identified by the value of this attribute, as an Angular expression)
* will receive the dropped domain object (at which point it can handle
* the drop, typically by inserting/reordering.)
* @param {DndService} dndService drag-and-drop service
*/
function MCTSwimlaneDrop(dndService) {
// Handle dragover events
function dragOver(e, element, swimlane) {
var event = (e || {}).originalEvent || e,
height = element[0].offsetHeight,
rect = element[0].getBoundingClientRect(),
offset = event.pageY - rect.top,
dataTransfer = event.dataTransfer,
id = dndService.getData(
SwimlaneDragConstants.MCT_DRAG_TYPE
),
draggedObject = dndService.getData(
SwimlaneDragConstants.MCT_EXTENDED_DRAG_TYPE
);
if (id) {
// TODO: Vary this based on modifier keys
event.dataTransfer.dropEffect = 'move';
// Set the swimlane's drop highlight state; top 75% is
// for drop-into, bottom 25% is for drop-after.
swimlane.highlight(
offset < (height * 0.75) &&
swimlane.allowDropIn(id, draggedObject)
);
swimlane.highlightBottom(
offset >= (height * 0.75) &&
swimlane.allowDropAfter(id, draggedObject)
);
// Indicate that we will accept the drag
if (swimlane.highlight() || swimlane.highlightBottom()) {
event.preventDefault(); // Required in Chrome?
return false;
}
}
}
// Handle drop events
function drop(e, element, swimlane) {
var event = (e || {}).originalEvent || e,
dataTransfer = event.dataTransfer,
id = dataTransfer.getData(
SwimlaneDragConstants.MCT_DRAG_TYPE
),
draggedSwimlane = dndService.getData(
SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
);
if (id) {
// Delegate the drop to the swimlane itself
swimlane.drop(id, (draggedSwimlane || {}).domainObject);
}
// Clear the swimlane highlights
swimlane.highlight(false);
swimlane.highlightBottom(false);
}
function link(scope, element, attrs) {
// Lookup swimlane by evaluating this attribute
function swimlane() {
return scope.$eval(attrs.mctSwimlaneDrop);
}
// Handle dragover
element.on('dragover', function (e) {
dragOver(e, element, swimlane());
});
// Handle drops
element.on('drop', function (e) {
drop(e, element, swimlane());
});
// Clear highlights when drag leaves this swimlane
element.on('dragleave', function () {
swimlane().highlight(false);
swimlane().highlightBottom(false);
});
}
return {
// Applies to attributes
restrict: "A",
// Link using above function
link: link
};
}
return MCTSwimlaneDrop;
}
);

View File

@ -0,0 +1,41 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define({
/**
* The string identifier for the data type used for drag-and-drop
* composition of domain objects. (e.g. in event.dataTransfer.setData
* calls.)
*/
MCT_DRAG_TYPE: 'mct-domain-object-id',
/**
* The string identifier for the data type used for drag-and-drop
* composition of domain objects, by object instance (passed through
* the dndService)
*/
MCT_EXTENDED_DRAG_TYPE: 'mct-domain-object',
/**
* String identifier for swimlanes being dragged.
*/
TIMELINE_SWIMLANE_DRAG_TYPE: 'timeline-swimlane'
});

View File

@ -0,0 +1,135 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
'use strict';
/**
* The ObjectLoader is a utility service for loading subgraphs
* of the composition hierarchy, starting at a provided object,
* and optionally filtering out objects which fail to meet certain
* criteria.
* @constructor
*/
function ObjectLoader($q) {
// Build up an object containing id->object pairs
// for the subset of the graph that is relevant.
function loadSubGraph(domainObject, criterion) {
var result = { domainObject: domainObject, composition: [] },
visiting = {},
filter;
// Check object existence (for criterion-less filtering)
function exists(domainObject) {
return !!domainObject;
}
// Check for capability matching criterion
function hasCapability(domainObject) {
return domainObject && domainObject.hasCapability(criterion);
}
// For the recursive step...
function loadSubGraphFor(childObject) {
return loadSubGraph(childObject, filter);
}
// Store loaded subgraphs into the result
function storeSubgraphs(subgraphs) {
result.composition = subgraphs;
}
// Avoid infinite recursion
function notVisiting(domainObject) {
return !visiting[domainObject.getId()];
}
// Put the composition of this domain object into the result
function mapIntoResult(composition) {
return $q.all(
composition.filter(filter).filter(notVisiting)
.map(loadSubGraphFor)
).then(storeSubgraphs);
}
// Used to give the final result after promise chaining
function giveResult() {
// Stop suppressing recursive visitation
visiting[domainObject.getId()] = true;
// And return the expecting result value
return result;
}
// Load composition for
function loadComposition() {
// First, record that we're looking at this domain
// object to detect cycles and avoid an infinite loop
visiting[domainObject.getId()] = true;
// Look up the composition, store it to the graph structure
return domainObject.useCapability('composition')
.then(mapIntoResult)
.then(giveResult);
}
// Choose the filter function to use
filter = typeof criterion === 'function' ? criterion :
(typeof criterion === 'string' ? hasCapability :
exists);
// Load child hierarchy, then provide the flat list
return domainObject.hasCapability('composition') ?
loadComposition() : $q.when(result);
}
return {
/**
* Load domain objects contained in the subgraph of the
* composition hierarchy which starts at the specified
* domain object, optionally pruning out objects (and their
* subgraphs) which match a certain criterion.
* The result is given as a promise for an object containing
* key-value pairs, where keys are domain object identifiers
* and values are domain objects in the subgraph.
* The criterion may be omitted (in which case no pruning is
* done) or specified as a string, in which case it will be
* treated as the name of a required capability, or specified
* as a function, which should return a truthy/falsy value
* when called with a domain object to indicate whether or
* not it should be included in the result set.
*
* @param {DomainObject} domainObject the domain object to
* start from
* @param {string|Function} [criterion] the criterion used
* to prune domain objects
* @returns {Promise} a promise for loaded domain objects
*/
load: loadSubGraph
};
}
return ObjectLoader;
}
);

Some files were not shown because too many files have changed in this diff Show More