mirror of
https://github.com/nasa/openmct.git
synced 2024-12-29 17:38:53 +00:00
Merge remote-tracking branch 'github/master' into open250
This commit is contained in:
commit
76e15f2963
35
LICENSES.md
35
LICENSES.md
@ -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
|
||||
|
@ -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
9
docs/footer.html
Normal 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>
|
@ -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
7
docs/header.html
Normal file
@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet"
|
||||
href="http://jasonm23.github.io/markdown-css-themes/avenir-white.css">
|
||||
</head>
|
||||
<body>
|
||||
|
@ -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.
|
||||
|
@ -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},
|
||||
|
@ -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
|
||||
*/
|
||||
|
||||
|
@ -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": [
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
@ -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;
|
||||
|
198
platform/entanglement/src/services/CopyTask.js
Normal file
198
platform/entanglement/src/services/CopyTask.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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
|
||||
);
|
||||
});
|
||||
|
@ -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);
|
||||
|
178
platform/features/clock/bundle.json
Normal file
178
platform/features/clock/bundle.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
482
platform/features/clock/lib/moment-duration-format.js
Normal file
482
platform/features/clock/lib/moment-duration-format.js
Normal 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);
|
34
platform/features/clock/res/templates/clock.html
Normal file
34
platform/features/clock/res/templates/clock.html
Normal 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>
|
42
platform/features/clock/res/templates/timer.html
Normal file
42
platform/features/clock/res/templates/timer.html
Normal 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>
|
@ -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;
|
||||
}
|
||||
);
|
54
platform/features/clock/src/actions/RestartTimerAction.js
Normal file
54
platform/features/clock/src/actions/RestartTimerAction.js
Normal 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;
|
||||
|
||||
}
|
||||
);
|
55
platform/features/clock/src/actions/StartTimerAction.js
Normal file
55
platform/features/clock/src/actions/StartTimerAction.js
Normal 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;
|
||||
|
||||
}
|
||||
);
|
100
platform/features/clock/src/controllers/ClockController.js
Normal file
100
platform/features/clock/src/controllers/ClockController.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
167
platform/features/clock/src/controllers/TimerController.js
Normal file
167
platform/features/clock/src/controllers/TimerController.js
Normal 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;
|
||||
}
|
||||
);
|
81
platform/features/clock/src/controllers/TimerFormatter.js
Normal file
81
platform/features/clock/src/controllers/TimerFormatter.js
Normal 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;
|
||||
}
|
||||
);
|
59
platform/features/clock/src/indicators/ClockIndicator.js
Normal file
59
platform/features/clock/src/indicators/ClockIndicator.js
Normal 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;
|
||||
}
|
||||
);
|
89
platform/features/clock/src/services/TickerService.js
Normal file
89
platform/features/clock/src/services/TickerService.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
97
platform/features/clock/test/actions/StartTimerActionSpec.js
Normal file
97
platform/features/clock/test/actions/StartTimerActionSpec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
104
platform/features/clock/test/controllers/ClockControllerSpec.js
Normal file
104
platform/features/clock/test/controllers/ClockControllerSpec.js
Normal 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();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
199
platform/features/clock/test/controllers/TimerControllerSpec.js
Normal file
199
platform/features/clock/test/controllers/TimerControllerSpec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
117
platform/features/clock/test/controllers/TimerFormatterSpec.js
Normal file
117
platform/features/clock/test/controllers/TimerFormatterSpec.js
Normal 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");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -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));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
64
platform/features/clock/test/services/TickerServiceSpec.js
Normal file
64
platform/features/clock/test/services/TickerServiceSpec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
11
platform/features/clock/test/suite.json
Normal file
11
platform/features/clock/test/suite.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
"actions/AbstractStartTimerAction",
|
||||
"actions/RestartTimerAction",
|
||||
"actions/StartTimerAction",
|
||||
"controllers/ClockController",
|
||||
"controllers/RefreshingController",
|
||||
"controllers/TimerController",
|
||||
"controllers/TimerFormatter",
|
||||
"indicators/ClockIndicator",
|
||||
"services/TickerService"
|
||||
]
|
@ -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) {
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
70
platform/features/timeline/README.md
Normal file
70
platform/features/timeline/README.md
Normal 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)
|
||||
}
|
||||
}
|
||||
```
|
372
platform/features/timeline/bundle.json
Normal file
372
platform/features/timeline/bundle.json
Normal 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": "%"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
39
platform/features/timeline/res/templates/activity-gantt.html
Normal file
39
platform/features/timeline/res/templates/activity-gantt.html
Normal 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>
|
@ -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>
|
34
platform/features/timeline/res/templates/legend-item.html
Normal file
34
platform/features/timeline/res/templates/legend-item.html
Normal 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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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()">
|
||||
é
|
||||
</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()}}"
|
||||
>
|
||||
è
|
||||
</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>
|
39
platform/features/timeline/res/templates/ticks.html
Normal file
39
platform/features/timeline/res/templates/ticks.html
Normal 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>
|
218
platform/features/timeline/res/templates/timeline.html
Normal file
218
platform/features/timeline/res/templates/timeline.html
Normal 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">é</span>
|
||||
<span class="l-col l-col-icon l-col-link ui-symbol">è</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>
|
27
platform/features/timeline/res/templates/values.html
Normal file
27
platform/features/timeline/res/templates/values.html
Normal 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>
|
32
platform/features/timeline/src/TimelineConstants.js
Normal file
32
platform/features/timeline/src/TimelineConstants.js
Normal 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
|
||||
});
|
78
platform/features/timeline/src/TimelineFormatter.js
Normal file
78
platform/features/timeline/src/TimelineFormatter.js
Normal 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;
|
||||
}
|
||||
);
|
121
platform/features/timeline/src/capabilities/ActivityTimespan.js
Normal file
121
platform/features/timeline/src/capabilities/ActivityTimespan.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
155
platform/features/timeline/src/capabilities/CumulativeGraph.js
Normal file
155
platform/features/timeline/src/capabilities/CumulativeGraph.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
|
||||
}
|
||||
);
|
149
platform/features/timeline/src/capabilities/ResourceGraph.js
Normal file
149
platform/features/timeline/src/capabilities/ResourceGraph.js
Normal 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;
|
||||
}
|
||||
|
||||
);
|
126
platform/features/timeline/src/capabilities/TimelineTimespan.js
Normal file
126
platform/features/timeline/src/capabilities/TimelineTimespan.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
149
platform/features/timeline/src/controllers/TimelineController.js
Normal file
149
platform/features/timeline/src/controllers/TimelineController.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
|
||||
}
|
||||
);
|
@ -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;
|
||||
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
@ -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;
|
||||
}
|
||||
);
|
68
platform/features/timeline/src/directives/MCTSwimlaneDrag.js
Normal file
68
platform/features/timeline/src/directives/MCTSwimlaneDrag.js
Normal 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;
|
||||
}
|
||||
);
|
127
platform/features/timeline/src/directives/MCTSwimlaneDrop.js
Normal file
127
platform/features/timeline/src/directives/MCTSwimlaneDrop.js
Normal 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;
|
||||
}
|
||||
);
|
@ -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'
|
||||
});
|
135
platform/features/timeline/src/services/ObjectLoader.js
Normal file
135
platform/features/timeline/src/services/ObjectLoader.js
Normal 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
Loading…
Reference in New Issue
Block a user