Merge branch 'master' into mct1221

This commit is contained in:
Alex M 2016-10-15 14:10:24 +03:00
commit 7dde924fcc
178 changed files with 9988 additions and 837 deletions

285
API.md Normal file
View File

@ -0,0 +1,285 @@
# Open MCT API
The Open MCT framework public api can be utilized by building the application
(`gulp install`) and then copying the file from `dist/main.js` to your
directory of choice.
Open MCT supports AMD, CommonJS, and loading via a script tag; it's easy to use
in your project. The [`openmct`]{@link module:openmct} module is exported
via AMD and CommonJS, and is also exposed as `openmct` in the global scope
if loaded via a script tag.
## Overview
Open MCT's goal is to allow you to browse, create, edit, and visualize all of
the domain knowledge you need on a daily basis.
To do this, the main building block provided by Open MCT is the _domain object_.
The temperature sensor on the starboard solar panel,
an overlay plot comparing the results of all temperature sensor,
the command dictionary for a spacecraft,
the individual commands in that dictionary, your "my documents" folder:
All of these things are domain objects.
Domain objects have Types, so a specific instrument temperature sensor is a
"Telemetry Point," and turning on a drill for a certain duration of time is
an "Activity". Types allow you to form an ontology of knowledge and provide
an abstraction for grouping, visualizing, and interpreting data.
And then we have Views. Views allow you to visualize domain objects. Views can
apply to specific domain objects; they may also apply to certain types of
domain objects, or they may apply to everything. Views are simply a method
of visualizing domain objects.
Regions allow you to specify what views are displayed for specific types of
domain objects in response to different user actions. For instance, you may
want to display a different view while editing, or you may want to update the
toolbar display when objects are selected. Regions allow you to map views to
specific user actions.
Domain objects can be mutated and persisted, developers can create custom
actions and apply them to domain objects, and many more things can be done.
For more information, read on!
## Running Open MCT
Once the [`openmct`](@link module:openmct) module has been loaded, you can
simply invoke [`start`]{@link module:openmct.MCT#start} to run Open MCT:
```
openmct.start();
```
Generally, however, you will want to configure Open MCT by adding plugins
before starting it. It is important to install plugins and configure Open MCT
_before_ calling [`start`]{@link module:openmct.MCT#start}; Open MCT is not
designed to be reconfigured once started.
## Configuring Open MCT
The [`openmct`]{@link module:openmct} module (more specifically, the
[`MCT`]{@link module:openmct.MCT} class, of which `openmct` is an instance)
exposes a variety of methods to allow the application to be configured,
extended, and customized before running.
Short examples follow; see the linked documentation for further details.
### Adding Domain Object Types
Custom types may be registered via
[`openmct.types`]{@link module:openmct.MCT#types}:
```
openmct.types.addType('my-type', new openmct.Type({
label: "My Type",
description: "This is a type that I added!"
});
```
### Adding Views
Custom views may be registered based on the region in the application
where they should appear:
* [`openmct.mainViews`]{@link module:openmct.MCT#mainViews} is a registry
of views of domain objects which should appear in the main viewing area.
* [`openmct.inspectors`]{@link module:openmct.MCT#inspectors} is a registry
of views of domain objects and/or active selections, which should appear in
the Inspector.
* [`openmct.toolbars`]{@link module:openmct.MCT#toolbars} is a registry
of views of domain objects and/or active selections, which should appear in
the toolbar area while editing.
* [`openmct.indicators`]{@link module:openmct.MCT#inspectors} is a registry
of views which should appear in the status area of the application.
Example:
```
openmct.mainViews.addProvider({
canView: function (domainObject) {
return domainObject.type === 'my-type';
},
view: function (domainObject) {
return new MyView(domainObject);
}
});
```
### Adding a Root-level Object
In many cases, you'd like a certain object (or a certain hierarchy of
objects) to be accessible from the top level of the application (the
tree on the left-hand side of Open MCT.) It is typical to expose a telemetry
dictionary as a hierarchy of telemetry-providing domain objects in this
fashion.
To do so, use the [`addRoot`]{@link module:openmct.ObjectAPI#addRoot} method
of the [object API]{@link module:openmct.ObjectAPI}:
```
openmct.objects.addRoot({
identifier: { key: "my-key", namespace: "my-namespace" }
name: "My Root-level Object",
type: "my-type"
});
```
You can also remove this root-level object via its identifier:
```
openmct.objects.removeRoot({ key: "my-key", namespace: "my-namespace" });
```
### Adding Composition Providers
The "composition" of a domain object is the list of objects it contains,
as shown (for example) in the tree for browsing. Open MCT provides a
default solution for composition, but there may be cases where you want
to provide the composition of a certain object (or type of object) dynamically.
For instance, you may want to populate a hierarchy under a custom root-level
object based on the contents of a telemetry dictionary.
To do this, you can add a new CompositionProvider:
```
openmct.composition.addProvider({
appliesTo: function (domainObject) {
return domainObject.type === 'my-type';
},
load: function (domainObject) {
return Promise.resolve(myDomainObjects);
}
});
```
### Adding Telemetry Providers
When connecting to a new telemetry source, you will want to register a new
[telemetry provider]{@link module:openmct.TelemetryAPI~TelemetryProvider}
with the [telemetry API]{@link module:openmct.TelemetryAPI#addProvider}:
```
openmct.telemetry.addProvider({
canProvideTelemetry: function (domainObject) {
return domainObject.type === 'my-type';
},
properties: function (domainObject) {
return [
{ key: 'value', name: "Temperature", units: "degC" },
{ key: 'time', name: "UTC" }
];
},
request: function (domainObject, options) {
var telemetryId = domainObject.myTelemetryId;
return myAdapter.request(telemetryId, options.start, options.end);
},
subscribe: function (domainObject, callback) {
var telemetryId = domainObject.myTelemetryId;
myAdapter.subscribe(telemetryId, callback);
return myAdapter.unsubscribe.bind(myAdapter, telemetryId, callback);
}
});
```
The implementations for `request` and `subscribe` can vary depending on the
nature of the endpoint which will provide telemetry. In the example above,
it is assumed that `myAdapter` contains the specific implementations
(HTTP requests, WebSocket connections, etc.) associated with some telemetry
source.
## Using Open MCT
When implementing new features, it is useful and sometimes necessary to
utilize functionality exposed by Open MCT.
### Retrieving Composition
To limit which objects are loaded at any given time, the composition of
a domain object must be requested asynchronously:
```
openmct.composition(myObject).load().then(function (childObjects) {
childObjects.forEach(doSomething);
});
```
### Support Common Gestures
Custom views may also want to support common gestures using the
[gesture API]{@link module:openmct.GestureAPI}. For instance, to make
a view (or part of a view) selectable:
```
openmct.gestures.selectable(myHtmlElement, myDomainObject);
```
### Working with Domain Objects
The [object API]{@link module:openmct.ObjectAPI} provides useful methods
for working with domain objects.
To make changes to a domain object, use the
[`mutate`]{@link module:openmct.ObjectAPI#mutate} method:
```
openmct.objects.mutate(myDomainObject, "name", "New name!");
```
Making modifications in this fashion allows other usages of the domain
object to remain up to date using the
[`observe`]{@link module:openmct.ObjectAPI#observe} method:
```
openmct.objects.observe(myDomainObject, "name", function (newName) {
myLabel.textContent = newName;
});
```
### Using Telemetry
Very often in Open MCT, you wish to work with telemetry data (for instance,
to display it in a custom visualization.)
### Synchronizing with the Time Conductor
Views which wish to remain synchronized with the state of Open MCT's
time conductor should utilize
[`openmct.conductor`]{@link module:openmct.TimeConductor}:
```
openmct.conductor.on('bounds', function (newBounds) {
requestTelemetry(newBounds.start, newBounds.end).then(displayTelemetry);
});
```
## Plugins
While you can register new features with Open MCT directly, it is generally
more useful to package these as a plugin. A plugin is a function that takes
[`openmct`]{@link module:openmct} as an argument, and performs configuration
upon `openmct` when invoked.
### Installing Plugins
To install plugins, use the [`install`]{@link module:openmct.MCT#install}
method:
```
openmct.install(myPlugin);
```
The plugin will be invoked to configure Open MCT before it is started.
### Writing Plugins
Plugins configure Open MCT, and should utilize the
[`openmct`]{@link module:openmct} module to do so, as summarized above in
"Configuring Open MCT" and "Using Open MCT" above.
### Distributing Plugins
Hosting or downloading plugins is outside of the scope of this documentation.
We recommend distributing plugins as UMD modules which export a single
function.

View File

@ -560,3 +560,132 @@ The above copyright notice and this permission notice shall be included in all c
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.
---
### Almond
* Link: https://github.com/requirejs/almond
* Version: 0.3.3
* Author: jQuery Foundation
* Description: Lightweight RequireJS replacement for builds
#### License
Copyright jQuery Foundation and other contributors, https://jquery.org/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/requirejs/almond
The following license applies to all parts of this software except as
documented below:
====
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.
====
Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code displayed within the prose of the
documentation.
CC0: http://creativecommons.org/publicdomain/zero/1.0/
====
Files located in the node_modules directory, and certain utilities used
to build or test the software in the test and dist directories, are
externally maintained libraries used by this software which have their own
licenses; we recommend you read them, as their terms may differ from the
terms above.
### Lodash
* Link: https://lodash.com
* Version: 3.10.1
* Author: Dojo Foundation
* Description: Utility functions
#### License
Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js, copyright 2009-2015 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
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.
### EventEmitter3
* Link: https://github.com/primus/eventemitter3
* Version: 1.2.0
* Author: Arnout Kazemier
* Description: Event-driven programming support
#### License
The MIT License (MIT)
Copyright (c) 2014 Arnout Kazemier
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.

View File

@ -10,9 +10,24 @@ Try Open MCT now with our [live demo](https://openmct-demo.herokuapp.com/).
![Demo](https://nasa.github.io/openmct/static/res/images/Open-MCT.Browse.Layout.Mars-Weather-1.jpg)
## New API
A new API is currently under development that will deprecate a lot of the documentation currently in the docs directory, however Open MCT will remain compatible with the currently documented API. An updated set of tutorials is being developed with the new API, and progress on this task can be followed in the [associated pull request](https://github.com/nasa/openmct/pull/999). Any code in this branch should be considered experimental, and we welcome any feedback.
Differences between the two APIs include a move away from a declarative system of JSON configuration files towards an imperative system based on function calls. Developers will be able to extend and build on Open MCT by making direct function calls to a public API. Open MCT is also being refactored to minimize the dependencies that using Open MCT imposes on developers, such as the current requirement to use Angular JS.
A simpler, [easier-to-use API](https://nasa.github.io/openmct/docs/api/)
has been added to Open MCT. Changes in this
API include a move away from a declarative system of JSON configuration files
towards an imperative system based on function calls. Developers will be able
to extend and build on Open MCT by making direct function calls to a public
API. Open MCT is also being refactored to minimize the dependencies that using
Open MCT imposes on developers, such as the current requirement to use
AngularJS.
This new API has not yet been heavily used and is likely to contain defects.
You can help by trying it out, and reporting any issues you encounter
using our GitHub issue tracker. Such issues may include bugs, suggestions,
missing documentation, or even just requests for help if you're having
trouble.
We want Open MCT to be as easy to use, install, run, and develop for as
possible, and your feedback will help us get there!
## Building and Running Open MCT Locally

View File

@ -19,6 +19,10 @@
"comma-separated-values": "^3.6.4",
"FileSaver.js": "^0.0.2",
"zepto": "^1.1.6",
"eventemitter3": "^1.2.0",
"lodash": "3.10.1",
"almond": "~0.3.2",
"d3": "~4.1.0",
"html2canvas": "^0.4.1"
}
}

View File

@ -24,7 +24,7 @@
# Script to build and deploy docs.
OUTPUT_DIRECTORY="target/docs"
OUTPUT_DIRECTORY="dist/docs"
# Docs, once built, are pushed to the private website repo
REPOSITORY_URL="git@github.com:nasa/openmct-website.git"
WEBSITE_DIRECTORY="website"

View File

@ -9,26 +9,29 @@
Open MCT provides functionality out of the box, but it's also a platform for
building rich mission operations applications based on modern web technology.
The platform is configured declaratively, and defines conventions for
building on the provided capabilities by creating modular 'bundles' that
extend the platform at a variety of extension points. The details of how to
The platform is configured by plugins which extend the platform at a variety
of extension points. The details of how to
extend the platform are provided in the following documentation.
## Sections
* The [Architecture Overview](architecture/) describes the concepts used
throughout Open MCT, and gives a high level overview of the platform's design.
* The [Developer's Guide](guide/) goes into more detail about how to use the
platform and the functionality that it provides.
* The [Tutorials](tutorials/) give examples of extending the platform to add
functionality,
and integrate with data sources.
* The [API](api/) document is generated from inline documentation
using [JSDoc](http://usejsdoc.org/), and describes the JavaScript objects and
functions that make up the software platform.
* Finally, the [Development Process](process/) document describes the
* The [Development Process](process/) document describes the
Open MCT software development cycle.
## Legacy Documentation
As we transition to a new API, the following documentation for the old API
(which is supported during the transtion) may be useful as well:
* The [Architecture Overview](architecture/) describes the concepts used
throughout Open MCT, and gives a high level overview of the platform's design.
* The [Developer's Guide](guide/) goes into more detail about how to use the
platform and the functionality that it provides.
* The [Tutorials](tutorials/) give examples of extending the platform to add
functionality, and integrate with data sources.

View File

@ -0,0 +1,48 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([
"./src/LocalTimeSystem",
"./src/LocalTimeFormat",
'legacyRegistry'
], function (
LocalTimeSystem,
LocalTimeFormat,
legacyRegistry
) {
legacyRegistry.register("example/localTimeSystem", {
"extensions": {
"formats": [
{
"key": "local-format",
"implementation": LocalTimeFormat
}
],
"timeSystems": [
{
"implementation": LocalTimeSystem,
"depends": ["$timeout"]
}
]
}
});
});

View File

@ -0,0 +1,43 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(['../../../platform/features/conductor-v2/conductor/src/timeSystems/LocalClock'], function (LocalClock) {
/**
* @implements TickSource
* @constructor
*/
function LADTickSource ($timeout, period) {
LocalClock.call(this, $timeout, period);
this.metadata = {
key: 'test-lad',
mode: 'lad',
cssclass: 'icon-clock',
label: 'Latest Available Data',
name: 'Latest available data',
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
};
}
LADTickSource.prototype = Object.create(LocalClock.prototype);
return LADTickSource;
});

View File

@ -0,0 +1,112 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([
'moment'
], function (
moment
) {
var DATE_FORMAT = "YYYY-MM-DD h:mm:ss.SSS a",
DATE_FORMATS = [
DATE_FORMAT,
"YYYY-MM-DD h:mm:ss a",
"YYYY-MM-DD h:mm a",
"YYYY-MM-DD"
];
/**
* @typedef Scale
* @property {number} min the minimum scale value, in ms
* @property {number} max the maximum scale value, in ms
*/
/**
* Formatter for UTC timestamps. Interprets numeric values as
* milliseconds since the start of 1970.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
function LocalTimeFormat() {
}
/**
* Returns an appropriate time format based on the provided value and
* the threshold required.
* @private
*/
function getScaledFormat (d) {
var m = moment.utc(d);
/**
* Uses logic from d3 Time-Scales, v3 of the API. See
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
*
* Licensed
*/
return [
[".SSS", function(m) { return m.milliseconds(); }],
[":ss", function(m) { return m.seconds(); }],
["hh:mma", function(m) { return m.minutes(); }],
["hha", function(m) { return m.hours(); }],
["ddd DD", function(m) {
return m.days() &&
m.date() != 1;
}],
["MMM DD", function(m) { return m.date() != 1; }],
["MMMM", function(m) {
return m.month();
}],
["YYYY", function() { return true; }]
].filter(function (row){
return row[1](m);
})[0][0];
};
/**
*
* @param value
* @param {Scale} [scale] Optionally provides context to the
* format request, allowing for scale-appropriate formatting.
* @returns {string} the formatted date
*/
LocalTimeFormat.prototype.format = function (value, scale) {
if (scale !== undefined){
var scaledFormat = getScaledFormat(value, scale);
if (scaledFormat) {
return moment.utc(value).format(scaledFormat);
}
}
return moment(value).format(DATE_FORMAT);
};
LocalTimeFormat.prototype.parse = function (text) {
return moment(text, DATE_FORMATS).valueOf();
};
LocalTimeFormat.prototype.validate = function (text) {
return moment(text, DATE_FORMATS).isValid();
};
return LocalTimeFormat;
});

View File

@ -0,0 +1,79 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([
'../../../platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem',
'../../../platform/features/conductor-v2/conductor/src/timeSystems/LocalClock',
'./LADTickSource'
], function (TimeSystem, LocalClock, LADTickSource) {
var THIRTY_MINUTES = 30 * 60 * 1000,
DEFAULT_PERIOD = 1000;
/**
* This time system supports UTC dates and provides a ticking clock source.
* @implements TimeSystem
* @constructor
*/
function LocalTimeSystem ($timeout) {
TimeSystem.call(this);
/**
* Some metadata, which will be used to identify the time system in
* the UI
* @type {{key: string, name: string, glyph: string}}
*/
this.metadata = {
'key': 'local',
'name': 'Local',
'glyph': '\u0043'
};
this.fmts = ['local-format'];
this.sources = [new LocalClock($timeout, DEFAULT_PERIOD), new LADTickSource($timeout, DEFAULT_PERIOD)];
}
LocalTimeSystem.prototype = Object.create(TimeSystem.prototype);
LocalTimeSystem.prototype.formats = function () {
return this.fmts;
};
LocalTimeSystem.prototype.deltaFormat = function () {
return 'duration';
};
LocalTimeSystem.prototype.tickSources = function () {
return this.sources;
};
LocalTimeSystem.prototype.defaults = function (key) {
var now = Math.ceil(Date.now() / 1000) * 1000;
return {
key: 'local-default',
name: 'Local 12 hour time system defaults',
deltas: {start: THIRTY_MINUTES, end: 0},
bounds: {start: now - THIRTY_MINUTES, end: now}
};
};
return LocalTimeSystem;
});

View File

@ -36,7 +36,7 @@ define([
legacyRegistry
) {
"use strict";
legacyRegistry.register("example/notifications", {
legacyRegistry.register("example/msl-adapter", {
"name" : "Mars Science Laboratory Data Adapter",
"extensions" : {
"types": [

View File

@ -21,41 +21,34 @@
*****************************************************************************/
/*global require,__dirname*/
var gulp = require('gulp'),
requirejsOptimize = require('gulp-requirejs-optimize'),
sourcemaps = require('gulp-sourcemaps'),
rename = require('gulp-rename'),
sass = require('gulp-sass'),
bourbon = require('node-bourbon'),
jshint = require('gulp-jshint'),
jscs = require('gulp-jscs'),
replace = require('gulp-replace-task'),
karma = require('karma'),
path = require('path'),
fs = require('fs'),
git = require('git-rev-sync'),
moment = require('moment'),
merge = require('merge-stream'),
project = require('./package.json'),
_ = require('lodash'),
paths = {
main: 'main.js',
main: 'openmct.js',
dist: 'dist',
assets: 'dist/assets',
reports: 'dist/reports',
scss: ['./platform/**/*.scss', './example/**/*.scss'],
scripts: [ 'main.js', 'platform/**/*.js', 'src/**/*.js' ],
assets: [
'./{example,platform}/**/*.{css,css.map,png,svg,ico,woff,eot,ttf}'
],
scripts: [ 'openmct.js', 'platform/**/*.js', 'src/**/*.js' ],
specs: [ 'platform/**/*Spec.js', 'src/**/*Spec.js' ],
static: [
'index.html',
'platform/**/*',
'example/**/*',
'bower_components/**/*'
]
},
options = {
requirejsOptimize: {
name: paths.main.replace(/\.js$/, ''),
name: 'bower_components/almond/almond.js',
include: paths.main.replace('.js', ''),
wrap: {
startFile: "src/start.frag",
endFile: "src/end.frag"
},
mainConfigFile: paths.main,
wrapShim: true
},
@ -64,7 +57,6 @@ var gulp = require('gulp'),
singleRun: true
},
sass: {
includePaths: bourbon.includePaths,
sourceComments: true
},
replace: {
@ -78,6 +70,8 @@ var gulp = require('gulp'),
};
gulp.task('scripts', function () {
var requirejsOptimize = require('gulp-requirejs-optimize');
var replace = require('gulp-replace-task');
return gulp.src(paths.main)
.pipe(sourcemaps.init())
.pipe(requirejsOptimize(options.requirejsOptimize))
@ -87,10 +81,16 @@ gulp.task('scripts', function () {
});
gulp.task('test', function (done) {
var karma = require('karma');
new karma.Server(options.karma, done).start();
});
gulp.task('stylesheets', function () {
var sass = require('gulp-sass');
var rename = require('gulp-rename');
var bourbon = require('node-bourbon');
options.sass.includePaths = bourbon.includePaths;
return gulp.src(paths.scss, {base: '.'})
.pipe(sourcemaps.init())
.pipe(sass(options.sass).on('error', sass.logError))
@ -104,6 +104,9 @@ gulp.task('stylesheets', function () {
});
gulp.task('lint', function () {
var jshint = require('gulp-jshint');
var merge = require('merge-stream');
var nonspecs = paths.specs.map(function (glob) {
return "!" + glob;
}),
@ -122,6 +125,8 @@ gulp.task('lint', function () {
});
gulp.task('checkstyle', function () {
var jscs = require('gulp-jscs');
return gulp.src(paths.scripts)
.pipe(jscs())
.pipe(jscs.reporter())
@ -129,18 +134,20 @@ gulp.task('checkstyle', function () {
});
gulp.task('fixstyle', function () {
var jscs = require('gulp-jscs');
return gulp.src(paths.scripts, { base: '.' })
.pipe(jscs({ fix: true }))
.pipe(gulp.dest('.'));
});
gulp.task('static', ['stylesheets'], function () {
return gulp.src(paths.static, { base: '.' })
gulp.task('assets', ['stylesheets'], function () {
return gulp.src(paths.assets)
.pipe(gulp.dest(paths.dist));
});
gulp.task('watch', function () {
gulp.watch(paths.scss, ['stylesheets']);
return gulp.watch(paths.scss, ['stylesheets', 'assets']);
});
gulp.task('serve', function () {
@ -148,9 +155,9 @@ gulp.task('serve', function () {
var app = require('./app.js');
});
gulp.task('develop', ['serve', 'stylesheets', 'watch']);
gulp.task('develop', ['serve', 'install', 'watch']);
gulp.task('install', [ 'static', 'scripts' ]);
gulp.task('install', [ 'assets', 'scripts' ]);
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);

View File

@ -28,12 +28,15 @@
<script src="bower_components/requirejs/require.js">
</script>
<script>
require(['main'], function (mct) {
require([
'./example/imagery/bundle',
'./example/eventGenerator/bundle',
'./example/generator/bundle'
], mct.run.bind(mct));
require(['openmct'], function (openmct) {
[
'example/imagery',
'example/eventGenerator',
'example/generator'
].forEach(
openmct.legacyRegistry.enable.bind(openmct.legacyRegistry)
);
openmct.start();
});
</script>
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
@ -47,7 +50,5 @@
<div class="l-splash-holder s-splash-holder">
<div class="l-splash s-splash"></div>
</div>
<div ng-view></div>
</body>
</html>

View File

@ -1,9 +1,9 @@
{
"source": {
"include": [
"platform/"
"src/"
],
"includePattern": "platform/.+\\.js$",
"includePattern": "src/.+\\.js$",
"excludePattern": ".+\\Spec\\.js$|lib/.+"
},
"plugins": [

View File

@ -37,9 +37,11 @@ module.exports = function(config) {
{pattern: 'bower_components/**/*.js', included: false},
{pattern: 'src/**/*.js', included: false},
{pattern: 'example/**/*.js', included: false},
{pattern: 'example/**/*.json', included: false},
{pattern: 'platform/**/*.js', included: false},
{pattern: 'warp/**/*.js', included: false},
{pattern: 'platform/**/*.html', included: false},
{pattern: 'src/**/*.html', included: false},
'test-main.js'
],

View File

@ -27,6 +27,7 @@ requirejs.config({
"angular": "bower_components/angular/angular.min",
"angular-route": "bower_components/angular-route/angular-route.min",
"csv": "bower_components/comma-separated-values/csv.min",
"EventEmitter": "bower_components/eventemitter3/index",
"es6-promise": "bower_components/es6-promise/es6-promise.min",
"html2canvas": "bower_components/html2canvas/build/html2canvas.min",
"moment": "bower_components/moment/moment",
@ -35,7 +36,9 @@ requirejs.config({
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
"zepto": "bower_components/zepto/zepto.min"
"zepto": "bower_components/zepto/zepto.min",
"lodash": "bower_components/lodash/lodash",
"d3": "bower_components/d3/d3.min"
},
"shim": {
"angular": {
@ -44,6 +47,9 @@ requirejs.config({
"angular-route": {
"deps": ["angular"]
},
"EventEmitter": {
"exports": "EventEmitter"
},
"html2canvas": {
"exports": "html2canvas"
},
@ -55,54 +61,28 @@ requirejs.config({
},
"zepto": {
"exports": "Zepto"
},
"lodash": {
"exports": "lodash"
},
"d3": {
"exports": "d3"
}
}
});
define([
'./platform/framework/src/Main',
'legacyRegistry',
'./src/defaultRegistry',
'./src/MCT'
], function (Main, defaultRegistry, MCT) {
var openmct = new MCT();
'./platform/framework/bundle',
'./platform/core/bundle',
'./platform/representation/bundle',
'./platform/commonUI/about/bundle',
'./platform/commonUI/browse/bundle',
'./platform/commonUI/edit/bundle',
'./platform/commonUI/dialog/bundle',
'./platform/commonUI/formats/bundle',
'./platform/commonUI/general/bundle',
'./platform/commonUI/inspect/bundle',
'./platform/commonUI/mobile/bundle',
'./platform/commonUI/themes/espresso/bundle',
'./platform/commonUI/notification/bundle',
'./platform/containment/bundle',
'./platform/execution/bundle',
'./platform/exporters/bundle',
'./platform/telemetry/bundle',
'./platform/features/clock/bundle',
'./platform/features/fixed/bundle',
'./platform/features/imagery/bundle',
'./platform/features/layout/bundle',
'./platform/features/pages/bundle',
'./platform/features/plot/bundle',
'./platform/features/timeline/bundle',
'./platform/features/table/bundle',
'./platform/forms/bundle',
'./platform/identity/bundle',
'./platform/persistence/aggregator/bundle',
'./platform/persistence/local/bundle',
'./platform/persistence/queue/bundle',
'./platform/policy/bundle',
'./platform/entanglement/bundle',
'./platform/search/bundle',
'./platform/status/bundle',
'./platform/commonUI/regions/bundle'
], function (Main, legacyRegistry) {
return {
legacyRegistry: legacyRegistry,
run: function () {
return new Main().run(legacyRegistry);
}
};
openmct.legacyRegistry = defaultRegistry;
openmct.on('start', function () {
return new Main().run(defaultRegistry);
});
return openmct;
});

View File

@ -48,8 +48,8 @@
"test": "karma start --single-run",
"jshint": "jshint platform example",
"watch": "karma start",
"jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api",
"otherdoc": "node docs/gendocs.js --in docs/src --out target/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
"otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
"docs": "npm run jsdoc ; npm run otherdoc",
"prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
},

View File

@ -41,6 +41,7 @@ define([
"text!./res/templates/items/items.html",
"text!./res/templates/browse/object-properties.html",
"text!./res/templates/browse/inspector-region.html",
"text!./res/templates/view-object.html",
'legacyRegistry'
], function (
BrowseController,
@ -63,6 +64,7 @@ define([
itemsTemplate,
objectPropertiesTemplate,
inspectorRegionTemplate,
viewObjectTemplate,
legacyRegistry
) {
@ -142,7 +144,7 @@ define([
"representations": [
{
"key": "view-object",
"templateUrl": "templates/view-object.html"
"template": viewObjectTemplate
},
{
"key": "browse-object",

View File

@ -43,7 +43,7 @@
</mct-representation>
</div>
</div>
<div class="holder l-flex-col flex-elem grows l-object-wrapper">
<div class="holder l-flex-col flex-elem grows l-object-wrapper l-controls-visible l-time-controller-visible">
<div class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
<!-- Toolbar and Save/Cancel buttons -->
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
@ -59,4 +59,5 @@
</mct-representation>
</div>
</div>
<mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include>
</div>

View File

@ -22,7 +22,8 @@
<span class='type-icon flex-elem {{type.getCssClass()}}'></span>
<span class="l-elem-wrapper l-flex-row flex-elem grows">
<span ng-if="parameters.mode" class='action flex-elem'>{{parameters.mode}}</span>
<span class='title-label flex-elem flex-can-shrink'>{{model.name}}</span>
<span class='title-label flex-elem holder flex-can-shrink'>{{model.name}}</span>
<span class='t-object-alert t-alert-unsynced flex-elem holder' title='This object is not currently displaying real-time data'></span>
<mct-representation
key="'menu-arrow'"
mct-object='domainObject'

View File

@ -200,7 +200,6 @@ define([
"name": "Remove",
"description": "Remove this object from its containing object.",
"depends": [
"$q",
"navigationService"
]
},
@ -245,7 +244,9 @@ define([
"key": "cancel",
"category": "conclude-editing",
"implementation": CancelAction,
"name": "Cancel",
// Because we use the name as label for edit buttons and mct-control buttons need
// the label to be set to undefined in order to not apply the labeled CSS rule.
"name": undefined,
"cssclass": "icon-x no-label",
"description": "Discard changes made to these objects.",
"depends": []

View File

@ -25,7 +25,7 @@
<mct-control key="'button'"
structure="{
text: saveActions[0].getMetadata().name,
click: saveActions[0].perform,
click: actionPerformer(saveActions[0]),
cssclass: 'major ' + saveActions[0].getMetadata().cssclass
}">
</mct-control>
@ -42,11 +42,12 @@
</span>
<span ng-repeat="currentAction in otherEditActions">
<a class='s-button {{currentAction.getMetadata().cssclass}}'
title='{{currentAction.getMetadata().name}}'
ng-click="currentAction.perform()"
ng-class="{ major: $index === 0 && saveActions.length === 0 }">
<span class="title-label">{{currentAction.getMetadata().name}}</span>
</a>
<mct-control key="'button'"
structure="{
text: currentAction.getMetadata().name,
click: actionPerformer(currentAction),
cssclass: currentAction.getMetadata().cssclass
}">
</mct-control>
</span>
</span>

View File

@ -66,4 +66,9 @@
</mct-representation>
</div><!--/ l-object-wrapper-inner -->
</div>
<mct-representation mct-object="domainObject"
key="'time-conductor'"
class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder">
</mct-representation>
</div>

View File

@ -40,19 +40,11 @@ define(
var self = this,
editAction = this.domainObject.getCapability('action').getActions("edit")[0];
// Persist changes to the domain object
function doPersist() {
var persistence =
self.domainObject.getCapability('persistence');
return persistence.persist();
}
// Link these objects
function doLink() {
var composition = self.domainObject &&
self.domainObject.getCapability('composition');
return composition && composition.add(self.selectedObject)
.then(doPersist);
return composition && composition.add(self.selectedObject);
}
if (editAction) {

View File

@ -50,12 +50,6 @@ define(
domainObject = this.domainObject,
dialogService = this.dialogService;
// Persist modifications to this domain object
function doPersist() {
var persistence = domainObject.getCapability('persistence');
return persistence && persistence.persist();
}
// Update the domain object model based on user input
function updateModel(userInput, dialog) {
return domainObject.useCapability('mutation', function (model) {
@ -73,11 +67,9 @@ define(
dialog.getFormStructure(),
dialog.getInitialFormValue()
).then(function (userInput) {
// Update the model, if user input was provided
return userInput && updateModel(userInput, dialog);
}).then(function (result) {
return result && doPersist();
});
// Update the model, if user input was provided
return userInput && updateModel(userInput, dialog);
});
}
return type && showDialog(type);
@ -94,9 +86,7 @@ define(
creatable = type && type.hasFeature('creation');
// Only allow creatable types to be edited
return domainObject &&
domainObject.hasCapability("persistence") &&
creatable;
return domainObject && creatable;
};
return PropertiesAction;

View File

@ -39,9 +39,8 @@ define(
* @constructor
* @implements {Action}
*/
function RemoveAction($q, navigationService, context) {
function RemoveAction(navigationService, context) {
this.domainObject = (context || {}).domainObject;
this.$q = $q;
this.navigationService = navigationService;
}
@ -51,8 +50,7 @@ define(
* fulfilled when the action has completed.
*/
RemoveAction.prototype.perform = function () {
var $q = this.$q,
navigationService = this.navigationService,
var navigationService = this.navigationService,
domainObject = this.domainObject;
/*
* Check whether an object ID matches the ID of the object being
@ -71,15 +69,6 @@ define(
model.composition = model.composition.filter(isNotObject);
}
/*
* Invoke persistence on a domain object. This will be called upon
* the removed object's parent (as its composition will have changed.)
*/
function doPersist(domainObj) {
var persistence = domainObj.getCapability('persistence');
return persistence && persistence.persist();
}
/*
* Checks current object and ascendants of current
* object with object being removed, if the current
@ -119,15 +108,10 @@ define(
// navigates to existing object up tree
checkObjectNavigation(object, parent);
return $q.when(
parent.useCapability('mutation', doMutate)
).then(function () {
return doPersist(parent);
});
return parent.useCapability('mutation', doMutate);
}
return $q.when(domainObject)
.then(removeFromContext);
return removeFromContext(domainObject);
};
// Object needs to have a parent for Remove to be applicable

View File

@ -81,6 +81,10 @@ define(
return this.persistenceCapability.getSpace();
};
TransactionalPersistenceCapability.prototype.persisted = function () {
return this.persistenceCapability.persisted();
};
return TransactionalPersistenceCapability;
}
);

View File

@ -61,6 +61,12 @@ define(
$scope.otherEditActions = $scope.action ?
$scope.action.getActions(OTHERS_ACTION_CONTEXT) :
[];
// Required because Angular does not allow 'bind'
// in expressions.
$scope.actionPerformer = function (action) {
return action.perform.bind(action);
};
}
// Update set of actions whenever the action capability

View File

@ -50,17 +50,13 @@ define(
this.listenHandle = undefined;
// Mutate and persist a new version of a domain object's model.
function doPersist(model) {
function doMutate(model) {
var domainObject = self.domainObject;
// First, mutate; then, persist.
return $q.when(domainObject.useCapability("mutation", function () {
return model;
})).then(function (result) {
// Only persist when mutation was successful
return result &&
domainObject.getCapability("persistence").persist();
});
}));
}
// Handle changes to model and/or view configuration
@ -80,14 +76,14 @@ define(
].join(" "));
// Update the configuration stored in the model, and persist.
if (domainObject && domainObject.hasCapability("persistence")) {
if (domainObject) {
// Configurations for specific views are stored by
// key in the "configuration" field of the model.
if (self.key && configuration) {
model.configuration = model.configuration || {};
model.configuration[self.key] = configuration;
}
doPersist(model);
doMutate(model);
}
}

View File

@ -0,0 +1,48 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
define(['./Transaction'], function (Transaction) {
/**
* A nested transaction is a transaction which takes place in the context
* of a larger parent transaction. It becomes part of the parent
* transaction when (and only when) committed.
* @param parent
* @constructor
* @extends {platform/commonUI/edit/services.Transaction}
* @memberof platform/commonUI/edit/services
*/
function NestedTransaction(parent) {
this.parent = parent;
Transaction.call(this, parent.$log);
}
NestedTransaction.prototype = Object.create(Transaction.prototype);
NestedTransaction.prototype.commit = function () {
this.parent.add(
Transaction.prototype.commit.bind(this),
Transaction.prototype.cancel.bind(this)
);
return Promise.resolve(true);
};
return NestedTransaction;
});

View File

@ -0,0 +1,96 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
define([], function () {
/**
* A Transaction represents a set of changes that are intended to
* be kept or discarded as a unit.
* @param $log Angular's `$log` service, for logging messages
* @constructor
* @memberof platform/commonUI/edit/services
*/
function Transaction($log) {
this.$log = $log;
this.callbacks = [];
}
/**
* Add a change to the current transaction, as expressed by functions
* to either keep or discard the change.
* @param {Function} commit called when the transaction is committed
* @param {Function} cancel called when the transaction is cancelled
* @returns {Function) a function which may be called to remove this
* pair of callbacks from the transaction
*/
Transaction.prototype.add = function (commit, cancel) {
var callback = { commit: commit, cancel: cancel };
this.callbacks.push(callback);
return function () {
this.callbacks = this.callbacks.filter(function (c) {
return c !== callback;
});
}.bind(this);
};
/**
* Get the number of changes in the current transaction.
* @returns {number} the size of the current transaction
*/
Transaction.prototype.size = function () {
return this.callbacks.length;
};
/**
* Keep all changes associated with this transaction.
* @method {platform/commonUI/edit/services.Transaction#commit}
* @returns {Promise} a promise which will resolve when all callbacks
* have been handled.
*/
/**
* Discard all changes associated with this transaction.
* @method {platform/commonUI/edit/services.Transaction#cancel}
* @returns {Promise} a promise which will resolve when all callbacks
* have been handled.
*/
['commit', 'cancel'].forEach(function (method) {
Transaction.prototype[method] = function () {
var promises = [];
var callback;
while (this.callbacks.length > 0) {
callback = this.callbacks.shift();
try {
promises.push(callback[method]());
} catch (e) {
this.$log
.error("Error trying to " + method + " transaction.");
}
}
return Promise.all(promises);
};
});
return Transaction;
});

View File

@ -21,8 +21,8 @@
*****************************************************************************/
/*global define*/
define(
[],
function () {
['./Transaction', './NestedTransaction'],
function (Transaction, NestedTransaction) {
/**
* Implements an application-wide transaction state. Once a
* transaction is started, calls to
@ -37,10 +37,7 @@ define(
function TransactionService($q, $log) {
this.$q = $q;
this.$log = $log;
this.transaction = false;
this.onCommits = [];
this.onCancels = [];
this.transactions = [];
}
/**
@ -50,18 +47,18 @@ define(
* #cancel} are called
*/
TransactionService.prototype.startTransaction = function () {
if (this.transaction) {
//Log error because this is a programming error if it occurs.
this.$log.error("Transaction already in progress");
}
this.transaction = true;
var transaction = this.isActive() ?
new NestedTransaction(this.transactions[0]) :
new Transaction(this.$log);
this.transactions.push(transaction);
};
/**
* @returns {boolean} If true, indicates that a transaction is in progress
*/
TransactionService.prototype.isActive = function () {
return this.transaction;
return this.transactions.length > 0;
};
/**
@ -72,24 +69,20 @@ define(
* @param onCancel A function to call on cancel
*/
TransactionService.prototype.addToTransaction = function (onCommit, onCancel) {
if (this.transaction) {
this.onCommits.push(onCommit);
if (onCancel) {
this.onCancels.push(onCancel);
}
if (this.isActive()) {
return this.activeTransaction().add(onCommit, onCancel);
} else {
//Log error because this is a programming error if it occurs.
this.$log.error("No transaction in progress");
}
};
return function () {
this.onCommits = this.onCommits.filter(function (callback) {
return callback !== onCommit;
});
this.onCancels = this.onCancels.filter(function (callback) {
return callback !== onCancel;
});
}.bind(this);
/**
* Get the transaction at the top of the stack.
* @private
*/
TransactionService.prototype.activeTransaction = function () {
return this.transactions[this.transactions.length - 1];
};
/**
@ -100,24 +93,8 @@ define(
* completed. Will reject if any commit operations fail
*/
TransactionService.prototype.commit = function () {
var self = this,
promises = [],
onCommit;
while (this.onCommits.length > 0) { // ...using a while in case some onCommit adds to transaction
onCommit = this.onCommits.pop();
try { // ...also don't want to fail mid-loop...
promises.push(onCommit());
} catch (e) {
this.$log.error("Error committing transaction.");
}
}
return this.$q.all(promises).then(function () {
self.transaction = false;
self.onCommits = [];
self.onCancels = [];
});
var transaction = this.transactions.pop();
return transaction ? transaction.commit() : Promise.reject();
};
/**
@ -129,28 +106,17 @@ define(
* @returns {*}
*/
TransactionService.prototype.cancel = function () {
var self = this,
results = [],
onCancel;
while (this.onCancels.length > 0) {
onCancel = this.onCancels.pop();
try {
results.push(onCancel());
} catch (error) {
this.$log.error("Error cancelling transaction.");
}
}
return this.$q.all(results).then(function () {
self.transaction = false;
self.onCommits = [];
self.onCancels = [];
});
var transaction = this.transactions.pop();
return transaction ? transaction.cancel() : Promise.reject();
};
/**
* Get the size (the number of commit/cancel callbacks) of
* the active transaction.
* @returns {number} size of the active transaction
*/
TransactionService.prototype.size = function () {
return this.onCommits.length;
return this.isActive() ? this.activeTransaction().size() : 0;
};
return TransactionService;

View File

@ -30,7 +30,6 @@ define(
mockParent,
mockContext,
mockComposition,
mockPersistence,
mockActionCapability,
mockEditAction,
mockType,
@ -68,7 +67,6 @@ define(
};
mockContext = jasmine.createSpyObj("context", ["getParent"]);
mockComposition = jasmine.createSpyObj("composition", ["invoke", "add"]);
mockPersistence = jasmine.createSpyObj("persistence", ["persist"]);
mockType = jasmine.createSpyObj("type", ["hasFeature", "getKey"]);
mockActionCapability = jasmine.createSpyObj("actionCapability", ["getActions"]);
mockEditAction = jasmine.createSpyObj("editAction", ["perform"]);
@ -84,7 +82,6 @@ define(
capabilities = {
composition: mockComposition,
persistence: mockPersistence,
action: mockActionCapability,
type: mockType
};
@ -107,11 +104,6 @@ define(
.toHaveBeenCalledWith(mockDomainObject);
});
it("persists changes afterward", function () {
action.perform();
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("enables edit mode for objects that have an edit action", function () {
mockActionCapability.getActions.andReturn([mockEditAction]);
action.perform();

View File

@ -43,7 +43,6 @@ define(
},
hasFeature: jasmine.createSpy('hasFeature')
},
persistence: jasmine.createSpyObj("persistence", ["persist"]),
mutation: jasmine.createSpy("mutation")
};
model = {};
@ -78,25 +77,18 @@ define(
action = new PropertiesAction(dialogService, context);
});
it("persists when an action is performed", function () {
action.perform();
expect(capabilities.persistence.persist)
.toHaveBeenCalled();
});
it("does not persist any changes upon cancel", function () {
input = undefined;
action.perform();
expect(capabilities.persistence.persist)
.not.toHaveBeenCalled();
});
it("mutates an object when performed", function () {
action.perform();
expect(capabilities.mutation).toHaveBeenCalled();
capabilities.mutation.mostRecentCall.args[0]({});
});
it("does not muate object upon cancel", function () {
input = undefined;
action.perform();
expect(capabilities.mutation).not.toHaveBeenCalled();
});
it("is only applicable when a domain object is in context", function () {
expect(PropertiesAction.appliesTo(context)).toBeTruthy();
expect(PropertiesAction.appliesTo({})).toBeFalsy();

View File

@ -37,7 +37,6 @@ define(
mockGrandchildContext,
mockRootContext,
mockMutation,
mockPersistence,
mockType,
actionContext,
model,
@ -53,8 +52,6 @@ define(
}
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
@ -88,7 +85,6 @@ define(
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
mockMutation = jasmine.createSpyObj("mutation", ["invoke"]);
mockPersistence = jasmine.createSpyObj("persistence", ["persist"]);
mockType = jasmine.createSpyObj("type", ["hasFeature"]);
mockNavigationService = jasmine.createSpyObj(
"navigationService",
@ -109,7 +105,6 @@ define(
capabilities = {
mutation: mockMutation,
persistence: mockPersistence,
type: mockType
};
model = {
@ -118,7 +113,7 @@ define(
actionContext = { domainObject: mockDomainObject };
action = new RemoveAction(mockQ, mockNavigationService, actionContext);
action = new RemoveAction(mockNavigationService, actionContext);
});
it("only applies to objects with parents", function () {
@ -154,9 +149,6 @@ define(
// Should have removed "test" - that was our
// mock domain object's id.
expect(result.composition).toEqual(["a", "b"]);
// Finally, should have persisted
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("removes parent of object currently navigated to", function () {

View File

@ -30,7 +30,6 @@ define(
mockScope,
testRepresentation,
mockDomainObject,
mockPersistence,
mockStatusCapability,
mockEditorCapability,
mockCapabilities,
@ -56,15 +55,12 @@ define(
"useCapability",
"hasCapability"
]);
mockPersistence =
jasmine.createSpyObj("persistence", ["persist"]);
mockStatusCapability =
jasmine.createSpyObj("statusCapability", ["listen"]);
mockEditorCapability =
jasmine.createSpyObj("editorCapability", ["isEditContextRoot"]);
mockCapabilities = {
'persistence': mockPersistence,
'status': mockStatusCapability,
'editor': mockEditorCapability
};
@ -96,7 +92,7 @@ define(
expect(representer.listenHandle).toHaveBeenCalled();
});
it("mutates and persists upon observed changes", function () {
it("mutates upon observed changes", function () {
mockScope.model = { someKey: "some value" };
mockScope.configuration = { someConfiguration: "something" };
@ -108,9 +104,6 @@ define(
jasmine.any(Function)
);
// ... and should have persisted the mutation
expect(mockPersistence.persist).toHaveBeenCalled();
// Finally, check that the provided mutation function
// includes both model and configuration
expect(

View File

@ -0,0 +1,78 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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,jasmine*/
define(["../../src/services/NestedTransaction"], function (NestedTransaction) {
var TRANSACTION_METHODS = ['add', 'commit', 'cancel', 'size'];
describe("A NestedTransaction", function () {
var mockTransaction,
nestedTransaction;
beforeEach(function () {
mockTransaction =
jasmine.createSpyObj('transaction', TRANSACTION_METHODS);
nestedTransaction = new NestedTransaction(mockTransaction);
});
it("exposes a Transaction's interface", function () {
TRANSACTION_METHODS.forEach(function (method) {
expect(nestedTransaction[method])
.toEqual(jasmine.any(Function));
});
});
describe("when callbacks are added", function () {
var mockCommit,
mockCancel,
remove;
beforeEach(function () {
mockCommit = jasmine.createSpy('commit');
mockCancel = jasmine.createSpy('cancel');
remove = nestedTransaction.add(mockCommit, mockCancel);
});
it("does not interact with its parent transaction", function () {
TRANSACTION_METHODS.forEach(function (method) {
expect(mockTransaction[method])
.not.toHaveBeenCalled();
});
});
describe("and the transaction is committed", function () {
beforeEach(function () {
nestedTransaction.commit();
});
it("adds to its parent transaction", function () {
expect(mockTransaction.add).toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Function)
);
});
});
});
});
});

View File

@ -57,8 +57,7 @@ define(
transactionService.startTransaction();
transactionService.addToTransaction(onCommit, onCancel);
expect(transactionService.onCommits.length).toBe(1);
expect(transactionService.onCancels.length).toBe(1);
expect(transactionService.size()).toBe(1);
});
it("size function returns size of commit and cancel queues", function () {
@ -85,7 +84,7 @@ define(
});
it("commit calls all queued commit functions", function () {
expect(transactionService.onCommits.length).toBe(3);
expect(transactionService.size()).toBe(3);
transactionService.commit();
onCommits.forEach(function (spy) {
expect(spy).toHaveBeenCalled();
@ -95,8 +94,8 @@ define(
it("commit resets active state and clears queues", function () {
transactionService.commit();
expect(transactionService.isActive()).toBe(false);
expect(transactionService.onCommits.length).toBe(0);
expect(transactionService.onCancels.length).toBe(0);
expect(transactionService.size()).toBe(0);
expect(transactionService.size()).toBe(0);
});
});
@ -116,7 +115,7 @@ define(
});
it("cancel calls all queued cancel functions", function () {
expect(transactionService.onCancels.length).toBe(3);
expect(transactionService.size()).toBe(3);
transactionService.cancel();
onCancels.forEach(function (spy) {
expect(spy).toHaveBeenCalled();
@ -126,8 +125,7 @@ define(
it("cancel resets active state and clears queues", function () {
transactionService.cancel();
expect(transactionService.isActive()).toBe(false);
expect(transactionService.onCommits.length).toBe(0);
expect(transactionService.onCancels.length).toBe(0);
expect(transactionService.size()).toBe(0);
});
});

View File

@ -0,0 +1,110 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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,jasmine*/
define(
["../../src/services/Transaction"],
function (Transaction) {
describe("A Transaction", function () {
var mockLog,
transaction;
beforeEach(function () {
mockLog = jasmine.createSpyObj(
'$log',
['warn', 'info', 'error', 'debug']
);
transaction = new Transaction(mockLog);
});
it("initially has a size of zero", function () {
expect(transaction.size()).toEqual(0);
});
describe("when callbacks are added", function () {
var mockCommit,
mockCancel,
remove;
beforeEach(function () {
mockCommit = jasmine.createSpy('commit');
mockCancel = jasmine.createSpy('cancel');
remove = transaction.add(mockCommit, mockCancel);
});
it("reports a new size", function () {
expect(transaction.size()).toEqual(1);
});
it("returns a function to remove those callbacks", function () {
expect(remove).toEqual(jasmine.any(Function));
remove();
expect(transaction.size()).toEqual(0);
});
describe("and the transaction is committed", function () {
beforeEach(function () {
transaction.commit();
});
it("triggers the commit callback", function () {
expect(mockCommit).toHaveBeenCalled();
});
it("does not trigger the cancel callback", function () {
expect(mockCancel).not.toHaveBeenCalled();
});
});
describe("and the transaction is cancelled", function () {
beforeEach(function () {
transaction.cancel();
});
it("triggers the cancel callback", function () {
expect(mockCancel).toHaveBeenCalled();
});
it("does not trigger the commit callback", function () {
expect(mockCommit).not.toHaveBeenCalled();
});
});
describe("and an exception is encountered during commit", function () {
beforeEach(function () {
mockCommit.andCallFake(function () {
throw new Error("test error");
});
transaction.commit();
});
it("logs an error", function () {
expect(mockLog.error).toHaveBeenCalled();
});
});
});
});
}
);

View File

@ -23,10 +23,12 @@
define([
"./src/FormatProvider",
"./src/UTCTimeFormat",
"./src/DurationFormat",
'legacyRegistry'
], function (
FormatProvider,
UTCTimeFormat,
DurationFormat,
legacyRegistry
) {
@ -48,6 +50,10 @@ define([
{
"key": "utc",
"implementation": UTCTimeFormat
},
{
"key": "duration",
"implementation": DurationFormat
}
],
"constants": [
@ -55,6 +61,17 @@ define([
"key": "DEFAULT_TIME_FORMAT",
"value": "utc"
}
],
"licenses": [
{
"name": "d3",
"version": "3.0.0",
"description": "Incorporates modified code from d3 Time Scales",
"author": "Mike Bostock",
"copyright": "Copyright 2010-2016 Mike Bostock. " +
"All rights reserved.",
"link": "https://github.com/d3/d3/blob/master/LICENSE"
}
]
}
});

View File

@ -0,0 +1,62 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([
'moment'
], function (
moment
) {
var DATE_FORMAT = "HH:mm:ss",
DATE_FORMATS = [
DATE_FORMAT
];
/**
* Formatter for duration. Uses moment to produce a date from a given
* value, but output is formatted to display only time. Can be used for
* specifying a time duration. For specifying duration, it's best to
* specify a date of January 1, 1970, as the ms offset will equal the
* duration represented by the time.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
function DurationFormat() {
}
DurationFormat.prototype.format = function (value) {
return moment.utc(value).format(DATE_FORMAT);
};
DurationFormat.prototype.parse = function (text) {
return moment.duration(text).asMilliseconds();
};
DurationFormat.prototype.validate = function (text) {
return moment.utc(text, DATE_FORMATS).isValid();
};
return DurationFormat;
});

View File

@ -58,6 +58,10 @@ define([
* @method format
* @memberof Format#
* @param {number} value the numeric value to format
* @param {number} [threshold] Optionally provides context to the
* format request, allowing for scale-appropriate formatting. This value
* should be the minimum unit to be represented by this format, in ms. For
* example, to display seconds, a threshold of 1 * 1000 should be provided.
* @returns {string} the text representation of the value
*/

View File

@ -34,6 +34,11 @@ define([
"YYYY-MM-DD"
];
/**
* @typedef Scale
* @property {number} min the minimum scale value, in ms
* @property {number} max the maximum scale value, in ms
*/
/**
* Formatter for UTC timestamps. Interprets numeric values as
@ -46,7 +51,64 @@ define([
function UTCTimeFormat() {
}
UTCTimeFormat.prototype.format = function (value) {
/**
* Returns an appropriate time format based on the provided value and
* the threshold required.
* @private
*/
function getScaledFormat(d) {
var momentified = moment.utc(d);
/**
* Uses logic from d3 Time-Scales, v3 of the API. See
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
*
* Licensed
*/
return [
[".SSS", function (m) {
return m.milliseconds();
}],
[":ss", function (m) {
return m.seconds();
}],
["HH:mm", function (m) {
return m.minutes();
}],
["HH", function (m) {
return m.hours();
}],
["ddd DD", function (m) {
return m.days() &&
m.date() !== 1;
}],
["MMM DD", function (m) {
return m.date() !== 1;
}],
["MMMM", function (m) {
return m.month();
}],
["YYYY", function () {
return true;
}]
].filter(function (row) {
return row[1](momentified);
})[0][0];
}
/**
*
* @param value
* @param {Scale} [scale] Optionally provides context to the
* format request, allowing for scale-appropriate formatting.
* @returns {string} the formatted date
*/
UTCTimeFormat.prototype.format = function (value, scale) {
if (scale !== undefined) {
var scaledFormat = getScaledFormat(value, scale);
if (scaledFormat) {
return moment.utc(value).format(scaledFormat);
}
}
return moment.utc(value).format(DATE_FORMAT) + "Z";
};

View File

@ -0,0 +1,62 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([
"./UTCTimeFormat",
"moment"
], function (
UTCTimeFormat,
moment
) {
describe("The UTCTimeFormat class", function () {
var format;
var scale;
beforeEach(function () {
format = new UTCTimeFormat();
scale = {min: 0, max: 0};
});
it("Provides an appropriately scaled time format based on the input" +
" time", function () {
var TWO_HUNDRED_MS = 200;
var THREE_SECONDS = 3000;
var FIVE_MINUTES = 5 * 60 * 1000;
var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000);
var TEN_HOURS = (10 * 60 * 60 * 1000);
var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD");
var APRIL = moment.utc("2016-04", "YYYY-MM");
var TWENTY_SIXTEEN = moment.utc("2016", "YYYY");
expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200");
expect(format.format(THREE_SECONDS, scale)).toBe(":03");
expect(format.format(FIVE_MINUTES, scale)).toBe("00:05");
expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20");
expect(format.format(TEN_HOURS, scale)).toBe("10");
expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03");
expect(format.format(APRIL, scale)).toBe("April");
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
});
});
});

View File

@ -48,6 +48,7 @@ define([
"./src/directives/MCTSplitPane",
"./src/directives/MCTSplitter",
"./src/directives/MCTTree",
"./src/filters/ReverseFilter",
"text!./res/templates/bottombar.html",
"text!./res/templates/controls/action-button.html",
"text!./res/templates/controls/input-filter.html",
@ -96,6 +97,7 @@ define([
MCTSplitPane,
MCTSplitter,
MCTTree,
ReverseFilter,
bottombarTemplate,
actionButtonTemplate,
inputFilterTemplate,
@ -146,7 +148,8 @@ define([
"depends": [
"stylesheets[]",
"$document",
"THEME"
"THEME",
"ASSETS_PATH"
]
},
{
@ -158,7 +161,7 @@ define([
],
"filters": [
{
"implementation": "filters/ReverseFilter.js",
"implementation": ReverseFilter,
"key": "reverse"
}
],
@ -405,6 +408,11 @@ define([
"key": "THEME",
"value": "unspecified",
"priority": "fallback"
},
{
"key": "ASSETS_PATH",
"value": ".",
"priority": "fallback"
}
],
"containers": [

View File

@ -0,0 +1,91 @@
@include keyframes(rotation) {
100% { @include transform(rotate(360deg)); }
}
@include keyframes(rotation-centered) {
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands) {
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands-sticky) {
0% {
@include transform(translate(-50%, -50%) rotate(0deg));
}
7% {
@include transform(translate(-50%, -50%) rotate(0deg));
}
8% {
@include transform(translate(-50%, -50%) rotate(30deg));
}
15% {
@include transform(translate(-50%, -50%) rotate(30deg));
}
16% {
@include transform(translate(-50%, -50%) rotate(60deg));
}
24% {
@include transform(translate(-50%, -50%) rotate(60deg));
}
25% {
@include transform(translate(-50%, -50%) rotate(90deg));
}
32% {
@include transform(translate(-50%, -50%) rotate(90deg));
}
33% {
@include transform(translate(-50%, -50%) rotate(120deg));
}
40% {
@include transform(translate(-50%, -50%) rotate(120deg));
}
41% {
@include transform(translate(-50%, -50%) rotate(150deg));
}
49% {
@include transform(translate(-50%, -50%) rotate(150deg));
}
50% {
@include transform(translate(-50%, -50%) rotate(180deg));
}
57% {
@include transform(translate(-50%, -50%) rotate(180deg));
}
58% {
@include transform(translate(-50%, -50%) rotate(210deg));
}
65% {
@include transform(translate(-50%, -50%) rotate(210deg));
}
66% {
@include transform(translate(-50%, -50%) rotate(240deg));
}
74% {
@include transform(translate(-50%, -50%) rotate(240deg));
}
75% {
@include transform(translate(-50%, -50%) rotate(270deg));
}
82% {
@include transform(translate(-50%, -50%) rotate(270deg));
}
83% {
@include transform(translate(-50%, -50%) rotate(300deg));
}
90% {
@include transform(translate(-50%, -50%) rotate(300deg));
}
91% {
@include transform(translate(-50%, -50%) rotate(330deg));
}
99% {
@include transform(translate(-50%, -50%) rotate(330deg));
}
100% {
@include transform(translate(-50%, -50%) rotate(360deg));
}
}

View File

@ -108,6 +108,9 @@
&.grows {
@include flex(1 1 auto);
}
&.contents-align-right {
text-align: right;
}
}
.flex-container {
// Apply to wrapping elements, mct-includes, etc.

View File

@ -49,7 +49,6 @@ $uePaneMiniTabFontSize: 8px;
$uePaneMiniTabCollapsedW: 18px;
$ueEditLeftPaneW: 75%;
$treeSearchInputBarH: 25px;
$ueTimeControlH: (33px, 18px, 20px);
/*************** Panes */
$ueBrowseLeftPaneTreeMinW: 150px;
$ueBrowseLeftPaneTreeMaxW: 35%;
@ -112,6 +111,7 @@ $bubbleMaxW: 300px;
$reqSymbolW: 15px;
$reqSymbolM: $interiorMargin * 2;
$reqSymbolFontSize: 0.75em;
$inputTextP: 3px 5px;
/*************** Wait Spinner Defaults */
$waitSpinnerD: 32px;
$waitSpinnerTreeD: 20px;

View File

@ -4,4 +4,3 @@
@include s-stale();
}
}

View File

@ -39,20 +39,20 @@
@include pulse($animName: pulse-subtle, $dur: 500ms, $opacity0: 0.7);
}
@mixin animTo($animName, $propName, $propValStart, $propValEnd, $dur: 500ms, $delay: 0) {
@mixin animTo($animName, $propName, $propValStart, $propValEnd, $dur: 500ms, $delay: 0, $dir: normal, $count: 1) {
@include keyframes($animName) {
from { #{propName}: $propValStart; }
to { #{$propName}: $propValEnd; }
}
@include animToParams($animName, $dur: 500ms, $delay: 0)
@include animToParams($animName, $dur: $dur, $delay: $delay, $dir: $dir, $count: $count)
}
@mixin animToParams($animName, $dur: 500ms, $delay: 0) {
@mixin animToParams($animName, $dur: 500ms, $delay: 0, $dir: normal, $count: 1) {
@include animation-name($animName);
@include animation-duration($dur);
@include animation-delay($delay);
@include animation-fill-mode(both);
@include animation-direction(normal);
@include animation-iteration-count(1);
@include animation-direction($dir);
@include animation-iteration-count($count);
@include animation-timing-function(ease-in-out);
}

View File

@ -82,7 +82,7 @@ input, textarea {
input[type="text"],
input[type="search"] {
vertical-align: baseline;
padding: 3px 5px;
padding: $inputTextP;
}
h1, h2, h3 {

View File

@ -30,6 +30,7 @@
.ui-symbol {
font-family: 'symbolsfont';
-webkit-font-smoothing: antialiased;
}
.ui-symbol.icon {
@ -70,10 +71,21 @@
line-height: inherit;
position: relative;
&.l-icon-link {
.t-item-icon-glyph {
&:after {
color: $colorIconLink;
content: $glyph-icon-link;
height: auto; width: auto;
position: absolute;
left: 0; top: 0; right: 0; bottom: 20%;
@include transform-origin(bottom left);
@include transform(scale(0.3));
z-index: 2;
}
/* .t-item-icon-glyph {
&:after {
color: $colorIconLink;
content: $glyph-icon-link;
content: '\e921'; //$glyph-icon-link;
height: auto; width: auto;
position: absolute;
left: 0; top: 0; right: 0; bottom: 20%;
@ -81,6 +93,6 @@
@include transform(scale(0.3));
z-index: 2;
}
}
}*/
}
}

View File

@ -22,6 +22,7 @@
@import "effects";
@import "global";
@import "glyphs";
@import "animations";
@import "archetypes";
@import "about";
@import "text";
@ -41,7 +42,6 @@
@import "controls/lists";
@import "controls/menus";
@import "controls/messages";
@import "controls/time-controller";
@import "mobile/controls/menus";
/********************************* FORMS */

View File

@ -185,21 +185,15 @@
}
@mixin sliderTrack($bg: $scrollbarTrackColorBg) {
//$b: 1px solid lighten($bg, 30%);
border-radius: 2px;
box-sizing: border-box;
@include boxIncised(0.7);
background-color: $bg;
//border-bottom: $b;
//border-right: $b;
}
@mixin controlGrippy($b, $direction: horizontal, $w: 1px, $style: dotted) {
//&:before {
//@include trans-prop-nice("border-color", 25ms);
content: '';
display: block;
//height: auto;
pointer-events: none;
position: absolute;
z-index: 2;
@ -274,16 +268,6 @@
text-shadow: rgba(black, $sVal) 0 3px 7px;
}
@function pullForward($c, $p: 20%) {
// For dark interfaces, lighter things come forward
@return lighten($c, $p);
}
@function pushBack($c, $p: 20%) {
// For dark interfaces, darker things move back
@return darken($c, $p);
}
@function percentToDecimal($p) {
@return $p / 100%;
}
@ -304,7 +288,6 @@
border-radius: $controlCr;
box-sizing: border-box;
color: $fg;
//display: inline-block;
}
@mixin btnBase($bg: $colorBtnBg, $bgHov: $colorBtnBgHov, $fg: $colorBtnFg, $fgHov: $colorBtnFgHov, $ic: $colorBtnIcon, $icHov: $colorBtnIconHov) {

View File

@ -296,8 +296,6 @@ input[type="search"] {
.title-label {
color: $colorObjHdrTxt;
@include ellipsize();
@include webkitProp(flex, '0 1 auto');
padding-right: 0.35em; // For context arrow. Done with em's so pad is relative to the scale of the text.
}
.context-available-w {
@ -308,6 +306,10 @@ input[type="search"] {
font-size: 0.7em;
@include flex(0 0 1);
}
.t-object-alert {
display: none;
}
}
/******************************************************** PROGRESS BAR */
@ -441,6 +443,63 @@ input[type="search"] {
}
}
@mixin sliderKnob() {
$h: 16px;
cursor: pointer;
width: floor($h/1.75);
height: $h;
margin-top: 1 + floor($h/2) * -1;
@include btnSubtle(pullForward($colorBtnBg, 10%));
//border-radius: 50% !important;
}
@mixin sliderKnobRound() {
$h: 12px;
cursor: pointer;
width: $h;
height: $h;
margin-top: 1 + floor($h/2) * -1;
@include btnSubtle(pullForward($colorBtnBg, 10%));
border-radius: 50% !important;
}
input[type="range"] {
// HTML5 range inputs
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
background: transparent; /* Otherwise white in Chrome */
&:focus {
outline: none; /* Removes the blue border. */
}
// Thumb
&::-webkit-slider-thumb {
-webkit-appearance: none;
@include sliderKnobRound();
}
&::-moz-range-thumb {
border: none;
@include sliderKnobRound();
}
&::-ms-thumb {
border: none;
@include sliderKnobRound();
}
// Track
&::-webkit-slider-runnable-track {
width: 100%;
height: 3px;
@include sliderTrack();
}
&::-moz-range-track {
width: 100%;
height: 3px;
@include sliderTrack();
}
}
/******************************************************** DATETIME PICKER */
.l-datetime-picker {
$r1H: 15px;

View File

@ -178,7 +178,7 @@
}
.pane {
box-sizing: border-box;
&.left {
&.menu-items {
border-right: 1px solid pullForward($colorMenuBg, 10%);
left: 0;
padding-right: $interiorMargin;
@ -194,38 +194,53 @@
}
}
}
&.right {
&.menu-item-description {
left: auto;
right: 0;
padding: $interiorMargin * 5;
width: $prw;
.desc-area {
&.icon {
color: $colorCreateMenuLgIcon;
font-size: 8em;
margin-bottom: $interiorMargin * 3;
position: relative;
text-align: center;
}
&.title {
color: $colorCreateMenuText;
font-size: 1.2em;
margin-bottom: $interiorMargin * 2;
}
&.description {
color: pushBack($colorCreateMenuText, 20%);
font-size: 0.8em;
line-height: 1.5em;
}
}
}
}
.menu-item-description {
.desc-area {
&.icon {
$h: 150px;
color: $colorCreateMenuLgIcon;
position: relative;
font-size: 8em;
left: 0;
height: $h;
line-height: $h;
margin-bottom: $interiorMargin * 5;
text-align: center;
}
&.title {
color: $colorCreateMenuText;
font-size: 1.2em;
margin-bottom: 0.5em;
}
&.description {
color: $colorCreateMenuText;
font-size: 0.8em;
line-height: 1.5em;
}
}
}
&.mini {
width: 400px;
height: 300px;
.pane {
&.menu-items {
font-size: 0.8em;
}
&.menu-item-description {
padding: $interiorMargin * 3;
.desc-area {
&.icon {
font-size: 4em;
}
&.title {
font-size: 1em;
}
}
}
}
}
}
.context-menu {
font-size: 0.80rem;
@ -262,3 +277,7 @@
right: 0;
width: auto;
}
.menus-up .menu {
bottom: $btnStdH; top: auto;
}

View File

@ -345,3 +345,29 @@ body.desktop .t-message-single {
body.desktop .t-message-list {
.message-contents .l-message { margin-right: $interiorMarginLg; }
}
// Alert elements in views
.s-unsynced {
$c: $colorPausedBg;
border: 1px solid $c;
@include animTo($animName: pulsePaused, $propName: border-color, $propValStart: rgba($c, 0.8), $propValEnd: rgba($c, 0.5), $dur: $animPausedPulseDur, $dir: alternate, $count: infinite);
}
.s-status-timeconductor-unsynced {
// Plot areas
.gl-plot .gl-plot-display-area {
@extend .s-unsynced;
}
// Object headers
.object-header {
.t-object-alert {
display: inline;
&.t-alert-unsynced {
@extend .icon-alert-triangle;
color: $colorPausedBg;
}
}
}
}

View File

@ -1,266 +0,0 @@
@mixin toiLineHovEffects() {
&:before,
&:after {
background-color: $timeControllerToiLineColorHov;
}
}
.l-time-controller {
$minW: 500px;
$knobHOffset: 0px;
$knobM: ($sliderKnobW + $knobHOffset) * -1;
$rangeValPad: $interiorMargin;
$rangeValOffset: $sliderKnobW + $interiorMargin;
$timeRangeSliderLROffset: 150px + ($sliderKnobW * 2);
$r1H: nth($ueTimeControlH,1); // Not currently used
$r2H: nth($ueTimeControlH,2);
$r3H: nth($ueTimeControlH,3);
min-width: $minW;
font-size: 0.8rem;
.l-time-range-inputs-holder,
.l-time-range-slider-holder,
.l-time-range-ticks-holder
{
box-sizing: border-box;
position: relative;
&:not(:first-child) {
margin-top: $interiorMargin;
}
}
.l-time-range-slider,
.l-time-range-ticks {
@include absPosDefault(0, visible);
left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
}
.l-time-range-inputs-holder {
border-top: 1px solid $colorInteriorBorder;
padding-top: $interiorMargin;
&.l-flex-row,
.l-flex-row {
@include align-items(center);
.flex-elem {
height: auto;
line-height: normal;
}
}
.type-icon {
font-size: 120%;
vertical-align: middle;
}
.l-time-range-input-w,
.l-time-range-inputs-elem {
margin-right: $interiorMargin;
.lbl {
color: $colorPlotLabelFg;
}
.ui-symbol.icon {
font-size: 11px;
}
}
.l-time-range-input-w {
// Wraps a datetime text input field
position: relative;
input[type="text"] {
width: 200px;
&.picker-icon {
padding-right: 20px;
}
}
.icon-calendar {
position: absolute;
right: 5px;
top: 5px;
}
}
}
.l-time-range-slider-holder {
height: $r2H;
.range-holder {
box-shadow: none;
background: none;
border: none;
.range {
.toi-line {
$myC: $timeControllerToiLineColor;
$myW: 8px;
@include transform(translateX(50%));
position: absolute;
top: 0; right: 0; bottom: 0px; left: auto;
width: $myW;
height: auto;
z-index: 2;
&:before {
// Vert line
background-color: $myC;
position: absolute;
content: "";
top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
width: 1px;
}
}
&:hover .toi-line {
@include toiLineHovEffects;
}
}
}
&:not(:active) {
.knob,
.range {
@include transition-property(left, right);
@include transition-duration(500ms);
@include transition-timing-function(ease-in-out);
}
}
}
.l-time-range-ticks-holder {
height: $r3H;
.l-time-range-ticks {
border-top: 1px solid $colorTick;
.tick {
background-color: $colorTick;
border:none;
height: 5px;
width: 1px;
margin-left: -1px;
position: absolute;
&:first-child {
margin-left: 0;
}
.l-time-range-tick-label {
@include webkitProp(transform, translateX(-50%));
color: $colorPlotLabelFg;
display: inline-block;
font-size: 0.7rem;
position: absolute;
top: 5px;
white-space: nowrap;
z-index: 2;
}
}
}
}
.knob {
z-index: 2;
&:before {
$mTB: 2px;
$grippyW: 3px;
$mLR: ($sliderKnobW - $grippyW)/2;
@include bgStripes($c: pullForward($sliderColorKnob, 20%), $a: 1, $bgsize: 4px, $angle: 0deg);
content: '';
display: block;
position: absolute;
top: $mTB; right: $mLR; bottom: $mTB; left: $mLR;
}
.range-value {
@include trans-prop-nice-fade(.25s);
font-size: 0.7rem;
position: absolute;
height: $r2H;
line-height: $r2H;
white-space: nowrap;
z-index: 1;
}
&:hover {
.range-value {
color: $sliderColorKnobHov;
}
}
&.knob-l {
margin-left: $knobM;
.range-value {
text-align: right;
right: $rangeValOffset;
}
}
&.knob-r {
margin-right: $knobM;
.range-value {
left: $rangeValOffset;
}
&:hover + .range-holder .range .toi-line {
@include toiLineHovEffects;
}
}
}
.l-time-domain-selector {
position: absolute;
right: 0px;
top: $interiorMargin;
}
}
.s-time-range-val {
border-radius: $controlCr;
background-color: $colorInputBg;
padding: 1px 1px 0 $interiorMargin;
}
/******************************************************************** MOBILE */
@include phoneandtablet {
.l-time-controller {
min-width: 0;
.l-time-range-slider-holder,
.l-time-range-ticks-holder {
display: none;
}
}
}
@include phone {
.l-time-controller {
.l-time-range-inputs-holder {
&.l-flex-row,
.l-flex-row {
@include align-items(flex-start);
}
.l-time-range-inputs-elem {
&.type-icon {
margin-top: 3px;
}
}
.t-inputs-w {
@include flex-direction(column);
.l-time-range-input-w:not(:first-child) {
&:not(:first-child) {
margin-top: $interiorMargin;
}
margin-right: 0;
}
.l-time-range-inputs-elem {
&.lbl { display: none; }
}
}
}
}
}
@include phonePortrait {
.l-time-controller {
.l-time-range-inputs-holder {
.t-inputs-w {
@include flex(1 1 auto);
padding-top: 25px; // Make room for the ever lovin' Time Domain Selector
.flex-elem {
@include flex(1 1 auto);
width: 100%;
}
input[type="text"] {
width: 100%;
}
}
}
}
.l-time-domain-selector {
right: auto;
left: 20px;
}
}

View File

@ -74,7 +74,7 @@
.s-image-main {
border: 1px solid transparent;
&.paused {
border-color: $colorPausedBg;
@extend .s-unsynced;
}
}

View File

@ -19,15 +19,6 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
@include keyframes(rotation) {
100% { @include transform(rotate(360deg)); }
}
@include keyframes(rotation-centered) {
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@mixin spinner($b: 5px, $c: $colorKey) {
@include transform-origin(center);
@include animation-name(rotation-centered);

View File

@ -128,7 +128,7 @@
line-height: $ueTopBarH;
}
.primary-pane {
.t-object.primary-pane {
// Need to lift up this pane to ensure that 'collapsed' panes don't block user interactions
z-index: 4;
}
@ -212,6 +212,8 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
.holder-object {
top: $bodyMargin;
bottom: $interiorMargin;
// Clip element that have min-widths
overflow: hidden;
}
.holder-inspector {
top: $bodyMargin;

View File

@ -23,6 +23,8 @@
<input type="text"
ng-model="textValue"
ng-blur="restoreTextValue(); ngBlur()"
ng-mouseup="ngMouseup()"
ng-disabled="ngDisabled"
ng-class="{
error: textInvalid ||
(structure.validate &&

View File

@ -37,8 +37,10 @@ define(
* @param {object[]} stylesheets stylesheet extension definitions
* @param $document Angular's jqLite-wrapped document element
* @param {string} activeTheme the theme in use
* @param {string} [assetPath] the directory relative to which
* stylesheets will be found
*/
function StyleSheetLoader(stylesheets, $document, activeTheme) {
function StyleSheetLoader(stylesheets, $document, activeTheme, assetPath) {
var head = $document.find('head'),
document = $document[0];
@ -47,6 +49,7 @@ define(
// Create a link element, and construct full path
var link = document.createElement('link'),
path = [
assetPath,
stylesheet.bundle.path,
stylesheet.bundle.resources,
stylesheet.stylesheetUrl
@ -68,6 +71,8 @@ define(
stylesheet.theme === activeTheme;
}
assetPath = assetPath || ".";
// Add all stylesheets from extensions
stylesheets.filter(matchesTheme).forEach(addStyleSheet);
}

View File

@ -69,7 +69,7 @@ define(
it("adjusts link locations", function () {
expect(mockElement.setAttribute)
.toHaveBeenCalledWith('href', "a/b/c/d.css");
.toHaveBeenCalledWith('href', "./a/b/c/d.css");
});
describe("for themed stylesheets", function () {
@ -95,12 +95,13 @@ define(
it("includes matching themes", function () {
expect(mockElement.setAttribute)
.toHaveBeenCalledWith('href', "a/b/c/themed.css");
.toHaveBeenCalledWith('href', "./a/b/c/themed.css");
});
it("excludes mismatching themes", function () {
expect(mockElement.setAttribute)
.not.toHaveBeenCalledWith('href', "a/b/c/bad-theme.css");
.not
.toHaveBeenCalledWith('href', "./a/b/c/bad-theme.css");
});
});

View File

@ -116,6 +116,7 @@ $colorProgressBarAmt: $colorKey;
$progressBarHOverlay: 15px;
$progressBarStripeW: 20px;
$shdwStatusIc: rgba(black, 0.4) 0 1px 2px;
$animPausedPulseDur: 500ms;
// Selects
$colorSelectBg: $colorBtnBg;

View File

@ -116,6 +116,7 @@ $colorProgressBarAmt: #0a0;
$progressBarHOverlay: 15px;
$progressBarStripeW: 20px;
$shdwStatusIc: rgba(white, 0.8) 0 0px 5px;
$animPausedPulseDur: 1s;
// Selects
$colorSelectBg: $colorBtnBg;

View File

@ -46,6 +46,7 @@ define([
"./src/capabilities/MutationCapability",
"./src/capabilities/DelegationCapability",
"./src/capabilities/InstantiationCapability",
"./src/runs/TransactingMutationListener",
"./src/services/Now",
"./src/services/Throttle",
"./src/services/Topic",
@ -78,6 +79,7 @@ define([
MutationCapability,
DelegationCapability,
InstantiationCapability,
TransactingMutationListener,
Now,
Throttle,
Topic,
@ -417,6 +419,12 @@ define([
}
}
],
"runs": [
{
"implementation": TransactingMutationListener,
"depends": ["topic", "transactionService"]
}
],
"constants": [
{
"key": "PERSISTENCE_SPACE",

View File

@ -49,8 +49,7 @@ define(
}
/**
* Add a domain object to the composition of the field.
* This mutates but does not persist the modified object.
* Add a domain object to the composition of this domain object.
*
* If no index is given, this is added to the end of the composition.
*

View File

@ -113,11 +113,16 @@ define(
domainObject = this.domainObject,
model = domainObject.getModel(),
modified = model.modified,
persisted = model.persisted,
persistenceService = this.persistenceService,
persistenceFn = model.persisted !== undefined ?
persistenceFn = persisted !== undefined ?
this.persistenceService.updateObject :
this.persistenceService.createObject;
if (persisted !== undefined && persisted === modified) {
return this.$q.when(true);
}
// Update persistence timestamp...
domainObject.useCapability("mutation", function (m) {
m.persisted = modified;
@ -178,6 +183,15 @@ define(
};
/**
* Check if this domain object has been persisted at some
* point.
* @returns {boolean} true if the object has been persisted
*/
PersistenceCapability.prototype.persisted = function () {
return this.domainObject.getModel().persisted !== undefined;
};
/**
* Get the key for this domain object in the given space.
*

View File

@ -0,0 +1,54 @@
/*****************************************************************************
* 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([], function () {
/**
* Listens for mutation on domain objects and triggers persistence when
* it occurs.
* @param {Topic} topic the `topic` service; used to listen for mutation
* @memberof platform/core
*/
function TransactingMutationListener(topic, transactionService) {
var mutationTopic = topic('mutation');
mutationTopic.listen(function (domainObject) {
var persistence = domainObject.getCapability('persistence');
var wasActive = transactionService.isActive();
if (persistence.persisted()) {
if (!wasActive) {
transactionService.startTransaction();
}
transactionService.addToTransaction(
persistence.persist.bind(persistence),
persistence.refresh.bind(persistence)
);
if (!wasActive) {
transactionService.commit();
}
}
});
}
return TransactingMutationListener;
});

View File

@ -0,0 +1,120 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
define(
["../../src/runs/TransactingMutationListener"],
function (TransactingMutationListener) {
describe("TransactingMutationListener", function () {
var mockTopic,
mockMutationTopic,
mockTransactionService,
mockDomainObject,
mockPersistence;
beforeEach(function () {
mockTopic = jasmine.createSpy('topic');
mockMutationTopic =
jasmine.createSpyObj('mutation', ['listen']);
mockTransactionService =
jasmine.createSpyObj('transactionService', [
'isActive',
'startTransaction',
'addToTransaction',
'commit'
]);
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getId', 'getCapability', 'getModel']
);
mockPersistence = jasmine.createSpyObj(
'persistence',
['persist', 'refresh', 'persisted']
);
mockTopic.andCallFake(function (t) {
return (t === 'mutation') && mockMutationTopic;
});
mockDomainObject.getCapability.andCallFake(function (c) {
return (c === 'persistence') && mockPersistence;
});
mockPersistence.persisted.andReturn(true);
return new TransactingMutationListener(
mockTopic,
mockTransactionService
);
});
it("listens for mutation", function () {
expect(mockMutationTopic.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
});
[false, true].forEach(function (isActive) {
var verb = isActive ? "is" : "isn't";
function onlyWhenInactive(expectation) {
return isActive ? expectation.not : expectation;
}
describe("when a transaction " + verb + " active", function () {
var innerVerb = isActive ? "does" : "doesn't";
beforeEach(function () {
mockTransactionService.isActive.andReturn(isActive);
});
describe("and mutation occurs", function () {
beforeEach(function () {
mockMutationTopic.listen.mostRecentCall
.args[0](mockDomainObject);
});
it(innerVerb + " start a new transaction", function () {
onlyWhenInactive(
expect(mockTransactionService.startTransaction)
).toHaveBeenCalled();
});
it("adds to the active transaction", function () {
expect(mockTransactionService.addToTransaction)
.toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Function)
);
});
it(innerVerb + " immediately commit", function () {
onlyWhenInactive(
expect(mockTransactionService.commit)
).toHaveBeenCalled();
});
});
});
});
});
}
);

View File

@ -76,17 +76,12 @@ define(
* completes.
*/
LocationCapability.prototype.setPrimaryLocation = function (location) {
var capability = this;
return this.domainObject.useCapability(
'mutation',
function (model) {
model.location = location;
}
).then(function () {
return capability.domainObject
.getCapability('persistence')
.persist();
});
);
};
/**

View File

@ -63,14 +63,7 @@ define(
);
}
return parentObject.getCapability('composition').add(object)
.then(function (objectInNewContext) {
return parentObject.getCapability('persistence')
.persist()
.then(function () {
return objectInNewContext;
});
});
return parentObject.getCapability('composition').add(object);
};
return LinkService;

View File

@ -33,7 +33,6 @@ define(
describe("instantiated with domain object", function () {
var locationCapability,
persistencePromise,
mutationPromise,
mockQ,
mockInjector,
@ -49,10 +48,6 @@ define(
return domainObjectFactory({id: 'root'});
}
},
persistence: jasmine.createSpyObj(
'persistenceCapability',
['persist']
),
mutation: jasmine.createSpyObj(
'mutationCapability',
['invoke']
@ -65,11 +60,6 @@ define(
mockObjectService =
jasmine.createSpyObj("objectService", ["getObjects"]);
persistencePromise = new ControlledPromise();
domainObject.capabilities.persistence.persist.andReturn(
persistencePromise
);
mutationPromise = new ControlledPromise();
domainObject.capabilities.mutation.invoke.andCallFake(
function (mutator) {
@ -103,22 +93,17 @@ define(
expect(locationCapability.isOriginal()).toBe(false);
});
it("can persist location", function () {
var persistResult = locationCapability
it("can mutate location", function () {
var result = locationCapability
.setPrimaryLocation('root'),
whenComplete = jasmine.createSpy('whenComplete');
persistResult.then(whenComplete);
result.then(whenComplete);
expect(domainObject.model.location).not.toBeDefined();
mutationPromise.resolve();
expect(domainObject.model.location).toBe('root');
expect(whenComplete).not.toHaveBeenCalled();
expect(domainObject.capabilities.persistence.persist)
.toHaveBeenCalled();
persistencePromise.resolve();
expect(whenComplete).toHaveBeenCalled();
});

View File

@ -139,20 +139,12 @@ define(
parentModel,
parentObject,
compositionPromise,
persistencePromise,
addPromise,
compositionCapability,
persistenceCapability;
compositionCapability;
beforeEach(function () {
compositionPromise = new ControlledPromise();
persistencePromise = new ControlledPromise();
addPromise = new ControlledPromise();
persistenceCapability = jasmine.createSpyObj(
'persistenceCapability',
['persist']
);
persistenceCapability.persist.andReturn(persistencePromise);
compositionCapability = jasmine.createSpyObj(
'compositionCapability',
['invoke', 'add']
@ -172,7 +164,6 @@ define(
return new ControlledPromise();
}
},
persistence: persistenceCapability,
composition: compositionCapability
}
});
@ -197,15 +188,6 @@ define(
.toHaveBeenCalledWith(object);
});
it("persists parent", function () {
linkService.perform(object, parentObject);
expect(addPromise.then).toHaveBeenCalled();
addPromise.resolve(linkedObject);
expect(parentObject.getCapability)
.toHaveBeenCalledWith('persistence');
expect(persistenceCapability.persist).toHaveBeenCalled();
});
it("returns object representing new link", function () {
var returnPromise, whenComplete;
returnPromise = linkService.perform(object, parentObject);
@ -213,7 +195,6 @@ define(
returnPromise.then(whenComplete);
addPromise.resolve(linkedObject);
persistencePromise.resolve();
compositionPromise.resolve([linkedObject]);
expect(whenComplete).toHaveBeenCalledWith(linkedObject);
});

View File

@ -42,11 +42,19 @@ define(
function addWorker(worker) {
var key = worker.key;
if (!workerUrls[key]) {
workerUrls[key] = [
worker.bundle.path,
worker.bundle.sources,
worker.scriptUrl
].join("/");
if (worker.scriptUrl) {
workerUrls[key] = [
worker.bundle.path,
worker.bundle.sources,
worker.scriptUrl
].join("/");
} else if (worker.scriptText) {
var blob = new Blob(
[worker.scriptText],
{type: 'application/javascript'}
);
workerUrls[key] = URL.createObjectURL(blob);
}
sharedWorkers[key] = worker.shared;
}
}

View File

@ -49,17 +49,11 @@ define(
var domainObject = this.domainObject,
now = this.now;
function doPersist() {
var persistence = domainObject.getCapability('persistence');
return persistence && persistence.persist();
}
function setTimestamp(model) {
model.timestamp = now();
}
return domainObject.useCapability('mutation', setTimestamp)
.then(doPersist);
return domainObject.useCapability('mutation', setTimestamp);
};
return AbstractStartTimerAction;

View File

@ -27,7 +27,6 @@ define(
describe("A timer's start/restart action", function () {
var mockNow,
mockDomainObject,
mockPersistence,
testModel,
action;
@ -45,14 +44,7 @@ define(
'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;
@ -67,18 +59,16 @@ define(
});
});
it("updates the model with a timestamp and persists", function () {
it("updates the model with a timestamp", function () {
mockNow.andReturn(12000);
action.perform();
expect(testModel.timestamp).toEqual(12000);
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("does not truncate milliseconds", function () {
mockNow.andReturn(42321);
action.perform();
expect(testModel.timestamp).toEqual(42321);
expect(mockPersistence.persist).toHaveBeenCalled();
});
});
}

View File

@ -27,7 +27,6 @@ define(
describe("A timer's restart action", function () {
var mockNow,
mockDomainObject,
mockPersistence,
testModel,
testContext,
action;
@ -46,14 +45,7 @@ define(
'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;
@ -70,11 +62,10 @@ define(
action = new RestartTimerAction(mockNow, testContext);
});
it("updates the model with a timestamp and persists", function () {
it("updates the model with a timestamp", 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 () {

View File

@ -27,7 +27,6 @@ define(
describe("A timer's start action", function () {
var mockNow,
mockDomainObject,
mockPersistence,
testModel,
testContext,
action;
@ -46,14 +45,7 @@ define(
'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;
@ -70,11 +62,10 @@ define(
action = new StartTimerAction(mockNow, testContext);
});
it("updates the model with a timestamp and persists", function () {
it("updates the model with a timestamp", 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 () {

View File

@ -0,0 +1,66 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([
"./src/ConductorTelemetryDecorator",
"./src/ConductorRepresenter",
"./src/ConductorService",
'legacyRegistry'
], function (
ConductorTelemetryDecorator,
ConductorRepresenter,
ConductorService,
legacyRegistry
) {
legacyRegistry.register("platform/features/conductor-v2/compatibility", {
"extensions": {
"services": [
{
"key": "conductorService",
"implementation": ConductorService,
"depends": [
"timeConductor"
]
}
],
"representers": [
{
"implementation": ConductorRepresenter,
"depends": [
"timeConductor"
]
}
],
"components": [
{
"type": "decorator",
"provides": "telemetryService",
"implementation": ConductorTelemetryDecorator,
"depends": [
"timeConductor"
]
}
]
}
});
});

View File

@ -0,0 +1,94 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
[],
function () {
/**
* Representer that provides a compatibility layer between the new
* time conductor and existing representations / views. Listens to
* the v2 time conductor API and generates v1 style events using the
* Angular event bus. This is transitional code code and will be
* removed.
*
* Deprecated immediately as this is temporary code
*
* @deprecated
* @constructor
*/
function ConductorRepresenter(
timeConductor,
scope,
element
) {
this.conductor = timeConductor;
this.scope = scope;
this.element = element;
this.boundsListener = this.boundsListener.bind(this);
this.timeSystemListener = this.timeSystemListener.bind(this);
this.followListener = this.followListener.bind(this);
}
ConductorRepresenter.prototype.boundsListener = function (bounds) {
this.scope.$broadcast('telemetry:display:bounds', {
start: bounds.start,
end: bounds.end,
domain: this.conductor.timeSystem().metadata.key
}, this.conductor.follow());
};
ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) {
var bounds = this.conductor.bounds();
this.scope.$broadcast('telemetry:display:bounds', {
start: bounds.start,
end: bounds.end,
domain: timeSystem.metadata.key
}, this.conductor.follow());
};
ConductorRepresenter.prototype.followListener = function () {
this.boundsListener(this.conductor.bounds());
};
// Handle a specific representation of a specific domain object
ConductorRepresenter.prototype.represent = function represent(representation) {
if (representation.key === 'browse-object') {
this.destroy();
this.conductor.on("bounds", this.boundsListener);
this.conductor.on("timeSystem", this.timeSystemListener);
this.conductor.on("follow", this.followListener);
}
};
ConductorRepresenter.prototype.destroy = function destroy() {
this.conductor.off("bounds", this.boundsListener);
this.conductor.off("timeSystem", this.timeSystemListener);
this.conductor.off("follow", this.followListener);
};
return ConductorRepresenter;
}
);

View File

@ -0,0 +1,72 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([
], function (
) {
function Conductor(timeConductorService) {
this.timeConductor = timeConductorService.conductor();
}
Conductor.prototype.displayStart = function () {
return this.timeConductor.bounds().start;
};
Conductor.prototype.displayEnd = function () {
return this.timeConductor.bounds().end;
};
Conductor.prototype.domainOptions = function () {
throw new Error([
'domainOptions not implemented in compatibility layer,',
' you must be using some crazy unknown code'
].join(''));
};
Conductor.prototype.domain = function () {
var system = this.timeConductor.timeSystem();
return {
key: system.metadata.key,
name: system.metadata.name,
format: system.formats()[0]
};
};
/**
* Small compatibility layer that implements old conductor service by
* wrapping new time conductor. This allows views that previously depended
* directly on the conductor service to continue to do so without
* modification.
*/
function ConductorService(timeConductor) {
this.tc = new Conductor(timeConductor);
}
ConductorService.prototype.getConductor = function () {
return this.tc;
};
return ConductorService;
});

View File

@ -0,0 +1,87 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
function () {
/**
* Decorates the `telemetryService` such that requests are
* mediated by the time conductor. This is a modified version of the
* decorator used in the old TimeConductor that integrates with the
* new TimeConductor API.
*
* @constructor
* @memberof platform/features/conductor
* @implements {TelemetryService}
* @param {platform/features/conductor.TimeConductor} conductor
* the service which exposes the global time conductor
* @param {TelemetryService} telemetryService the decorated service
*/
function ConductorTelemetryDecorator(timeConductor, telemetryService) {
this.conductor = timeConductor;
this.telemetryService = telemetryService;
this.amendRequests = ConductorTelemetryDecorator.prototype.amendRequests.bind(this);
}
function amendRequest(request, bounds, timeSystem) {
request = request || {};
request.start = bounds.start;
request.end = bounds.end;
request.domain = timeSystem.metadata.key;
return request;
}
ConductorTelemetryDecorator.prototype.amendRequests = function (requests) {
var bounds = this.conductor.bounds(),
timeSystem = this.conductor.timeSystem();
return (requests || []).map(function (request) {
return amendRequest(request, bounds, timeSystem);
});
};
ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) {
return this.telemetryService
.requestTelemetry(this.amendRequests(requests));
};
ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) {
var unsubscribeFunc = this.telemetryService.subscribe(callback, this.amendRequests(requests)),
conductor = this.conductor,
self = this;
function amendRequests() {
return self.amendRequests(requests);
}
conductor.on('bounds', amendRequests);
return function () {
unsubscribeFunc();
conductor.off('bounds', amendRequests);
};
};
return ConductorTelemetryDecorator;
}
);

View File

@ -0,0 +1,134 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([
"./src/ui/TimeConductorViewService",
"./src/ui/TimeConductorController",
"./src/TimeConductor",
"./src/ui/MctConductorAxis",
"./src/ui/NumberFormat",
"text!./res/templates/time-conductor.html",
"text!./res/templates/mode-selector/mode-selector.html",
"text!./res/templates/mode-selector/mode-menu.html",
'legacyRegistry'
], function (
TimeConductorViewService,
TimeConductorController,
TimeConductor,
MCTConductorAxis,
NumberFormat,
timeConductorTemplate,
modeSelectorTemplate,
modeMenuTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/conductor-v2/conductor", {
"extensions": {
"services": [
{
"key": "timeConductor",
"implementation": TimeConductor
},
{
"key": "timeConductorViewService",
"implementation": TimeConductorViewService,
"depends": [
"timeConductor",
"timeSystems[]"
]
}
],
"controllers": [
{
"key": "TimeConductorController",
"implementation": TimeConductorController,
"depends": [
"$scope",
"$window",
"timeConductor",
"timeConductorViewService",
"timeSystems[]"
]
}
],
"directives": [
{
"key": "mctConductorAxis",
"implementation": MCTConductorAxis,
"depends": [
"timeConductor",
"formatService"
]
}
],
"stylesheets": [
{
"stylesheetUrl": "css/time-conductor-espresso.css",
"theme": "espresso"
},
{
"stylesheetUrl": "css/time-conductor-snow.css",
"theme": "snow"
}
],
"templates": [
{
"key": "conductor",
"template": timeConductorTemplate
},
{
"key": "mode-menu",
"template": modeMenuTemplate
},
{
"key": "mode-selector",
"template": modeSelectorTemplate
}
],
"representations": [
{
"key": "time-conductor",
"template": timeConductorTemplate
}
],
"licenses": [
{
"name": "D3: Data-Driven Documents",
"version": "4.1.0",
"author": "Mike Bostock",
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
"website": "https://d3js.org/",
"copyright": "Copyright 2010-2016 Mike Bostock",
"license": "BSD-3-Clause",
"link": "https://github.com/d3/d3/blob/master/LICENSE"
}
],
"formats": [
{
"key": "number",
"implementation": NumberFormat
}
]
}
});
});

View File

@ -0,0 +1,3 @@
$ueTimeConductorH: (25px, 3px, 20px);
$timeCondInputTimeSysDefW: 165px; // Default width for datetime value inputs
$timeCondInputDeltaDefW: 60px; // Default width for delta value inputs, typically 00:00:00

View File

@ -0,0 +1,431 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
@mixin toiLineHovEffects() {
&:before,
&:after {
background-color: $timeControllerToiLineColorHov;
}
}
.l-time-conductor-holder {
border-top: 1px solid $colorInteriorBorder;
min-width: 500px;
padding-top: $interiorMargin;
}
.time-conductor-icon {
$c: $colorObjHdrIc;
$d: 18px;
height: $d !important;
width: $d;
position: relative;
&:before {
@extend .ui-symbol;
color: $c;
content: $glyph-icon-brackets;
font-size: $d;
line-height: normal;
display: block;
width: 100%;
height: 100%;
z-index: 1;
}
// Clock hands
div[class*="hand"] {
$handW: 2px;
$handH: $d * 0.4; //8px;
@include transform(translate(-50%, -50%));
@include animation-iteration-count(infinite);
@include animation-timing-function(linear);
position: absolute;
height: $handW;
width: $handW;
left: 50%;
top: 50%;
z-index: 2;
&:before {
background: $colorObjHdrIc;
content: '';
display: block;
position: absolute;
width: 100%;
bottom: -1px;
}
&.hand-little {
z-index: 2;
@include animation-duration(12s);
&:before {
height: ceil($handH * 0.7);
}
}
&.hand-big {
z-index: 1;
@include animation-duration(1s);
&:before {
height: $handH;
}
}
}
}
.l-time-conductor {
$knobHOffset: 0px;
$rangeValPad: $interiorMargin;
$rangeValOffset: $sliderKnobW + $interiorMargin;
$r1H: nth($ueTimeConductorH, 1);
$r2H: nth($ueTimeConductorH, 2);
$r3H: nth($ueTimeConductorH, 3);
position: relative;
> .l-row-elem {
// First order row elements
box-sizing: border-box;
width: 100%;
position: relative;
}
.mode-selector .s-menu-button,
.time-delta {
&:before {
@extend .ui-symbol;
}
}
.time-delta {
&:before {
color: $colorTimeCondKeyBg;
}
}
.l-time-conductor-inputs-holder,
.l-time-conductor-inputs-and-ticks,
.l-time-conductor-zoom-w {
font-size: 0.8rem;
}
.l-time-conductor-inputs-holder {
$ticksBlockerFadeW: 50px;
$iconCalendarW: 16px;
$wBgColor: $colorBodyBg;
height: $r1H;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
.l-time-range-w {
// Wraps a datetime text input field
height: 100%;
position: absolute;
.title {
display: inline-block;
margin-right: $interiorMarginSm;
}
&.start-w {
@include background-image(linear-gradient(270deg, transparent, $wBgColor $ticksBlockerFadeW));
padding-right: $ticksBlockerFadeW;
.title:before {
content: 'Start';
}
}
&.end-w {
@include background-image(linear-gradient(90deg, transparent, $wBgColor $ticksBlockerFadeW));
padding-left: $ticksBlockerFadeW;
right: 0;
.title:before {
content: 'End';
}
}
input[type="text"] {
@include trans-prop-nice(padding, 250ms);
}
.time-range-input input[type="text"] {
width: $timeCondInputTimeSysDefW;
}
.hrs-min-input input[type="text"] {
width: $timeCondInputDeltaDefW;
}
.icon-calendar {
margin-top: 4px;
}
}
}
.l-time-conductor-inputs-and-ticks {
$c: $colorTimeCondTicks; //$colorTick;
height: $r1H;
mct-conductor-axis {
display: block;
position: relative;
width: 100%;
}
.l-axis-holder {
height: $r1H;
position: relative;
width: 100%;
svg {
text-rendering: geometricPrecision;
width: 100%;
height: 100%;
> g {
font-size: 0.9em;
}
path {
// Line beneath ticks
display: none;
}
line {
// Tick marks
stroke: $c;
}
text {
// Tick labels
fill: $c;
}
}
}
}
.l-data-visualization {
background: $colorTimeCondDataVisBg;
height: $r2H;
}
.l-time-conductor-controls {
align-items: center;
margin-top: $interiorMargin;
.l-time-conductor-zoom-w {
@include justify-content(flex-end);
.time-conductor-zoom {
display: none; // TEMP per request from Andrew 8/1/16
height: $r3H;
min-width: 100px;
width: 20%;
}
.time-conductor-zoom-current-range {
display: none; // TEMP per request from Andrew 8/1/16
color: $colorTick;
}
}
}
// Real-time, latest modes
&.realtime-mode,
&.lad-mode {
.time-conductor-icon {
&:before { color: $colorTimeCondKeyBg; }
div[class*="hand"] {
@include animation-name(clock-hands);
&:before {
background: $colorTimeCondKeyBg;
}
}
}
.l-time-conductor-inputs-holder {
.l-time-range-input-w {
input[type="text"]:not(.error) {
background: transparent;
box-shadow: none;
border-radius: 0;
padding-left: 0;
padding-right: 0;
&:hover,
&:focus {
@include nice-input();
padding: $inputTextP;
}
}
.icon-calendar {
display: none;
}
&.start-date {
display: none;
}
&.end-date {
pointer-events: none;
input[type="text"] {
color: pullForward($colorTimeCondKeyBg, 5%);
margin-right: $interiorMargin;
tab-index: -1;
}
}
}
}
.l-data-visualization {
background: $colorTimeCondDataVisRtBg !important
}
.mode-selector .s-menu-button {
$fg: $colorTimeCondKeyFg;
@include btnSubtle($bg: $colorTimeCondKeyBg, $bgHov: pullForward($colorTimeCondKeyBg, $ltGamma), $fg: $colorTimeCondKeyFg);
&:before { color: $fg !important; };
color: $fg !important;
}
}
// Fixed mode
&.fixed-mode {
$i: $glyph-icon-calendar;
.time-conductor-icon div[class*="hand"] {
&.hand-little {
@include transform(rotate(120deg));
}
}
.mode-selector .s-menu-button:before {
content: $i;
}
}
// Realtime mode
&.realtime-mode {
$i: $glyph-icon-clock;
.time-conductor-icon div[class*="hand"] {
@include animation-name(clock-hands);
}
.time-delta:before {
content: $i;
}
.l-time-conductor-inputs-holder .l-time-range-w.end-w .title:before {
content: 'Now';
}
.mode-selector .s-menu-button:before {
content: $i;
}
}
// LAD mode
&.lad-mode {
$i: $glyph-icon-database;
.time-conductor-icon div[class*="hand"] {
@include animation-name(clock-hands-sticky);
&.hand-big {
@include animation-duration(5s);
}
&.hand-little {
@include animation-duration(60s);
}
}
.time-delta:before {
content: $i;
}
.l-time-conductor-inputs-holder .l-time-range-w.end-w .title:before {
content: 'LAD';
}
.mode-selector .s-menu-button:before {
content: $i;
}
}
}
/******************************************************************** MOBILE */
@include phoneandtablet {
.l-time-conductor-holder { min-width: 0 !important; }
.super-menu.mini {
width: 200px;
height: 100px;
.pane.menu-item-description {
display: none;
}
}
}
@include phone {
.l-time-conductor {
min-width: 0;
.l-time-conductor-inputs-and-ticks {
.l-time-conductor-inputs-holder {
.l-time-range-w {
background-image: none !important;
}
}
mct-conductor-axis {
display: none;
}
}
}
}
@include phonePortrait {
.l-time-conductor {
.l-data-visualization,
.l-time-conductor-zoom-w,
.time-delta {
display: none;
}
.l-time-conductor-inputs-and-ticks {
height: auto !important;
.l-time-conductor-inputs-holder {
position: relative;
height: auto !important;
.l-time-range-w {
background-image: none !important;
display: block;
height: auto !important;
padding: 0 !important;
position: relative;
text-align: left;
&:not(:first-child) {
margin-top: $interiorMargin;
}
}
}
}
// Fixed mode
&.fixed-mode {
.l-time-conductor-inputs-and-ticks {
.l-time-range-w {
.title {
width: 30px;
}
}
}
}
// Real-time, latest modes
&.realtime-mode,
&.lad-mode {
.l-time-conductor-inputs-and-ticks {
.l-time-range-w {
&.start-w {
display: none;
}
&.end-w {
margin-top: 0;
.end-date input[type="text"] {
margin: 0;
text-align: left;
}
}
}
}
}
}
}

View File

@ -0,0 +1,39 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
@import "bourbon";
@import "../../../../../commonUI/general/res/sass/constants";
@import "../../../../../commonUI/general/res/sass/mixins";
@import "../../../../../commonUI/general/res/sass/mobile/constants";
@import "../../../../../commonUI/general/res/sass/mobile/mixins";
@import "../../../../../commonUI/themes/espresso/res/sass/constants";
@import "../../../../../commonUI/themes/espresso/res/sass/mixins";
@import "../../../../../commonUI/general/res/sass/glyphs";
@import "../../../../../commonUI/general/res/sass/icons";
@import "constants";
// Thematic constants
$colorTimeCondTicks: pullForward($colorBodyBg, 30%);
$colorTimeCondKeyBg: #4e70dc;
$colorTimeCondKeyFg: #fff;
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 10%);
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 10%);
@import "time-conductor-base";

View File

@ -0,0 +1,39 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
@import "bourbon";
@import "../../../../../commonUI/general/res/sass/constants";
@import "../../../../../commonUI/general/res/sass/mixins";
@import "../../../../../commonUI/general/res/sass/mobile/constants";
@import "../../../../../commonUI/general/res/sass/mobile/mixins";
@import "../../../../../commonUI/themes/snow/res/sass/constants";
@import "../../../../../commonUI/themes/snow/res/sass/mixins";
@import "../../../../../commonUI/general/res/sass/glyphs";
@import "../../../../../commonUI/general/res/sass/icons";
@import "constants";
// Thematic constants
$colorTimeCondTicks: pullForward($colorBodyBg, 30%);
$colorTimeCondKeyBg: #6178dc;
$colorTimeCondKeyFg: #fff;
$colorTimeCondDataVisBg: pullForward($colorBodyBg, 10%);
$colorTimeCondDataVisRtBg: pushBack($colorTimeCondKeyBg, 30%);
@import "time-conductor-base";

View File

@ -0,0 +1,45 @@
<!--
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.
-->
<div class="contents">
<div class="pane left menu-items">
<ul>
<li ng-repeat="(key, metadata) in ngModel.options"
ng-click="ngModel.selectedKey=key">
<a ng-mouseover="ngModel.activeMetadata = metadata"
ng-mouseleave="ngModel.activeMetadata = undefined"
class="menu-item-a {{metadata.cssclass}}">
{{metadata.name}}
</a>
</li>
</ul>
</div>
<div class="pane right menu-item-description">
<div
class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssclass}}"></div>
<div class="desc-area title">
{{ngModel.activeMetadata.name}}
</div>
<div class="desc-area description">
{{ngModel.activeMetadata.description}}
</div>
</div>
</div>

View File

@ -0,0 +1,34 @@
<!--
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.
-->
<span ng-controller="ClickAwayController as modeController">
<div class="s-menu-button"
ng-click="modeController.toggle()">
<span class="title-label">{{ngModel.options[ngModel.selectedKey]
.label}}</span>
</div>
<div class="menu super-menu mini mode-selector-menu"
ng-show="modeController.isActive()">
<mct-include key="'mode-menu'"
ng-model="ngModel">
</mct-include>
</div>
</span>

View File

@ -0,0 +1,108 @@
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
<div ng-controller="TimeConductorController as tcController"
class="holder grows flex-elem l-flex-row l-time-conductor {{modeModel.selectedKey}}-mode {{timeSystemModel.selected.metadata.key}}-time-system">
<div class="flex-elem holder time-conductor-icon">
<div class="hand-little"></div>
<div class="hand-big"></div>
</div>
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
<!-- Holds inputs and ticks -->
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
<form class="l-time-conductor-inputs-holder"
ng-submit="tcController.updateBoundsFromForm(boundsModel)">
<span class="l-time-range-w start-w">
<span class="l-time-range-input-w start-date">
<span class="title"></span>
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.format,
validate: tcController.validation.validateStart
}"
ng-model="boundsModel"
ng-blur="tcController.updateBoundsFromForm(boundsModel)"
field="'start'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta start-delta"
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
-
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.deltaFormat,
validate: tcController.validation.validateStartDelta
}"
ng-model="boundsModel"
ng-blur="tcController.updateDeltasFromForm(boundsModel)"
field="'startDelta'"
class="hrs-min-input">
</mct-control>
</span>
</span>
<span class="l-time-range-w end-w">
<span class="l-time-range-input-w end-date"
ng-controller="ToggleController as t2">
<span class="title"></span>
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.format,
validate: tcController.validation.validateEnd
}"
ng-model="boundsModel"
ng-blur="tcController.updateBoundsFromForm(boundsModel)"
ng-disabled="modeModel.selectedKey !== 'fixed'"
field="'end'"
class="time-range-input">
</mct-control>
</span>
<span class="l-time-range-input-w time-delta end-delta"
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
+
<mct-control key="'datetime-field'"
structure="{
format: timeSystemModel.deltaFormat,
validate: tcController.validation.validateEndDelta
}"
ng-model="boundsModel"
ng-blur="tcController.updateDeltasFromForm(boundsModel)"
field="'endDelta'"
class="hrs-min-input">
</mct-control>
</span>
</span>
<input type="submit" class="hidden">
</form>
<mct-conductor-axis></mct-conductor-axis>
</div>
<!-- Holds data availability, time of interest -->
<div class="l-data-visualization l-row-elem flex-elem"></div>
<!-- Holds time system and session selectors, and zoom control -->
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
<mct-include
key="'mode-selector'"
ng-model="modeModel"
class="holder flex-elem menus-up mode-selector">
</mct-include>
<mct-control
key="'menu-button'"
class="holder flex-elem menus-up time-system"
structure="{
text: timeSystemModel.selected.metadata.name,
click: tcController.selectTimeSystemByKey,
options: timeSystemModel.options
}">
</mct-control>
<!-- Zoom control -->
<div class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder"></span>
<input class="time-conductor-zoom flex-elem" type="range" />
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,179 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(['EventEmitter'], function (EventEmitter) {
/**
* The public API for setting and querying time conductor state. The
* time conductor is the means by which the temporal bounds of a view
* are controlled. Time-sensitive views will typically respond to
* changes to bounds or other properties of the time conductor and
* update the data displayed based on the time conductor state.
*
* The TimeConductor extends the EventEmitter class. A number of events are
* fired when properties of the time conductor change, which are
* documented below.
* @constructor
*/
function TimeConductor() {
EventEmitter.call(this);
//The Time System
this.system = undefined;
//The Time Of Interest
this.toi = undefined;
this.boundsVal = {
start: undefined,
end: undefined
};
//Default to fixed mode
this.followMode = false;
}
TimeConductor.prototype = Object.create(EventEmitter.prototype);
/**
* Validate the given bounds. This can be used for pre-validation of
* bounds, for example by views validating user inputs.
* @param bounds The start and end time of the conductor.
* @returns {string | true} A validation error, or true if valid
*/
TimeConductor.prototype.validateBounds = function (bounds) {
if ((bounds.start === undefined) ||
(bounds.end === undefined) ||
isNaN(bounds.start) ||
isNaN(bounds.end)
) {
return "Start and end must be specified as integer values";
} else if (bounds.start > bounds.end) {
return "Specified start date exceeds end bound";
}
return true;
};
/**
* Get or set the follow mode of the time conductor. In follow mode the
* time conductor ticks, regularly updating the bounds from a timing
* source appropriate to the selected time system and mode of the time
* conductor.
* @fires TimeConductor#follow
* @param {boolean} followMode
* @returns {boolean}
*/
TimeConductor.prototype.follow = function (followMode) {
if (arguments.length > 0) {
this.followMode = followMode;
/**
* @event TimeConductor#follow The TimeConductor has toggled
* into or out of follow mode.
* @property {boolean} followMode true if follow mode is
* enabled, otherwise false.
*/
this.emit('follow', this.followMode);
}
return this.followMode;
};
/**
* @typedef {Object} TimeConductorBounds
* @property {number} start The start time displayed by the time conductor in ms since epoch. Epoch determined by current time system
* @property {number} end The end time displayed by the time conductor in ms since epoch.
*/
/**
* Get or set the start and end time of the time conductor. Basic validation
* of bounds is performed.
*
* @param {TimeConductorBounds} newBounds
* @throws {Error} Validation error
* @fires TimeConductor#bounds
* @returns {TimeConductorBounds}
*/
TimeConductor.prototype.bounds = function (newBounds) {
if (arguments.length > 0) {
var validationResult = this.validateBounds(newBounds);
if (validationResult !== true) {
throw new Error(validationResult);
}
//Create a copy to avoid direct mutation of conductor bounds
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
/**
* @event TimeConductor#bounds The start time, end time, or
* both have been updated
* @property {TimeConductorBounds} bounds
*/
this.emit('bounds', this.boundsVal);
}
//Return a copy to prevent direct mutation of time conductor bounds.
return JSON.parse(JSON.stringify(this.boundsVal));
};
/**
* Get or set the time system of the TimeConductor. Time systems determine
* units, epoch, and other aspects of time representation. When changing
* the time system in use, new valid bounds must also be provided.
* @param {TimeSystem} newTimeSystem
* @param {TimeConductorBounds} bounds
* @fires TimeConductor#timeSystem
* @returns {TimeSystem} The currently applied time system
*/
TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) {
if (arguments.length >= 2) {
this.system = newTimeSystem;
/**
* @event TimeConductor#timeSystem The time system used by the time
* conductor has changed. A change in Time System will always be
* followed by a bounds event specifying new query bounds
* @property {TimeSystem} The value of the currently applied
* Time System
* */
this.emit('timeSystem', this.system);
this.bounds(bounds);
} else if (arguments.length === 1) {
throw new Error('Must set bounds when changing time system');
}
return this.system;
};
/**
* Get or set the Time of Interest. The Time of Interest is the temporal
* focus of the current view. It can be manipulated by the user from the
* time conductor or from other views.
* @fires TimeConductor#timeOfInterest
* @param newTOI
* @returns {number} the current time of interest
*/
TimeConductor.prototype.timeOfInterest = function (newTOI) {
if (arguments.length > 0) {
this.toi = newTOI;
/**
* @event TimeConductor#timeOfInterest The Time of Interest has moved.
* @property {number} Current time of interest
*/
this.emit('timeOfInterest', this.toi);
}
return this.toi;
};
return TimeConductor;
});

View File

@ -0,0 +1,110 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(['./TimeConductor'], function (TimeConductor) {
describe("The Time Conductor", function () {
var tc,
timeSystem,
bounds,
eventListener,
toi,
follow;
beforeEach(function () {
tc = new TimeConductor();
timeSystem = {};
bounds = {start: 0, end: 0};
eventListener = jasmine.createSpy("eventListener");
toi = 111;
follow = true;
});
it("Supports setting and querying of time of interest and and follow mode", function () {
expect(tc.timeOfInterest()).not.toBe(toi);
tc.timeOfInterest(toi);
expect(tc.timeOfInterest()).toBe(toi);
expect(tc.follow()).not.toBe(follow);
tc.follow(follow);
expect(tc.follow()).toBe(follow);
});
it("Allows setting of valid bounds", function () {
bounds = {start: 0, end: 1};
expect(tc.bounds()).not.toEqual(bounds);
expect(tc.bounds.bind(tc, bounds)).not.toThrow();
expect(tc.bounds()).toEqual(bounds);
});
it("Disallows setting of invalid bounds", function () {
bounds = {start: 1, end: 0};
expect(tc.bounds()).not.toEqual(bounds);
expect(tc.bounds.bind(tc, bounds)).toThrow();
expect(tc.bounds()).not.toEqual(bounds);
bounds = {start: 1};
expect(tc.bounds()).not.toEqual(bounds);
expect(tc.bounds.bind(tc, bounds)).toThrow();
expect(tc.bounds()).not.toEqual(bounds);
});
it("Allows setting of time system with bounds", function () {
expect(tc.timeSystem()).not.toBe(timeSystem);
expect(tc.timeSystem.bind(tc, timeSystem, bounds)).not.toThrow();
expect(tc.timeSystem()).toBe(timeSystem);
});
it("Disallows setting of time system without bounds", function () {
expect(tc.timeSystem()).not.toBe(timeSystem);
expect(tc.timeSystem.bind(tc, timeSystem)).toThrow();
expect(tc.timeSystem()).not.toBe(timeSystem);
});
it("Emits an event when time system changes", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("timeSystem", eventListener);
tc.timeSystem(timeSystem, bounds);
expect(eventListener).toHaveBeenCalledWith(timeSystem);
});
it("Emits an event when time of interest changes", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("timeOfInterest", eventListener);
tc.timeOfInterest(toi);
expect(eventListener).toHaveBeenCalledWith(toi);
});
it("Emits an event when bounds change", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("bounds", eventListener);
tc.bounds(bounds);
expect(eventListener).toHaveBeenCalledWith(bounds);
});
it("Emits an event when follow mode changes", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("follow", eventListener);
tc.follow(follow);
expect(eventListener).toHaveBeenCalledWith(follow);
});
});
});

View File

@ -0,0 +1,89 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(['./TickSource'], function (TickSource) {
/**
* @implements TickSource
* @constructor
*/
function LocalClock($timeout, period) {
TickSource.call(this);
this.metadata = {
key: 'local',
mode: 'realtime',
cssclass: 'icon-clock',
label: 'Real-time',
name: 'Real-time Mode',
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
};
this.period = period;
this.$timeout = $timeout;
this.timeoutHandle = undefined;
}
LocalClock.prototype = Object.create(TickSource.prototype);
LocalClock.prototype.start = function () {
this.timeoutHandle = this.$timeout(this.tick.bind(this), this.period);
};
LocalClock.prototype.stop = function () {
if (this.timeoutHandle) {
this.$timeout.cancel(this.timeoutHandle);
}
};
LocalClock.prototype.tick = function () {
var now = Date.now();
this.listeners.forEach(function (listener) {
listener(now);
});
this.timeoutHandle = this.$timeout(this.tick.bind(this), this.period);
};
/**
* Register a listener for the local clock. When it ticks, the local
* clock will provide the current local system time
*
* @param listener
* @returns {function} a function for deregistering the provided listener
*/
LocalClock.prototype.listen = function (listener) {
var listeners = this.listeners;
listeners.push(listener);
if (listeners.length === 1) {
this.start();
}
return function () {
listeners.splice(listeners.indexOf(listener));
if (listeners.length === 0) {
this.stop();
}
}.bind(this);
};
return LocalClock;
});

View File

@ -0,0 +1,50 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(["./LocalClock"], function (LocalClock) {
describe("The LocalClock class", function () {
var clock,
mockTimeout,
timeoutHandle = {};
beforeEach(function () {
mockTimeout = jasmine.createSpy("timeout");
mockTimeout.andReturn(timeoutHandle);
mockTimeout.cancel = jasmine.createSpy("cancel");
clock = new LocalClock(mockTimeout, 0);
clock.start();
});
it("calls listeners on tick with current time", function () {
var mockListener = jasmine.createSpy("listener");
clock.listen(mockListener);
clock.tick();
expect(mockListener).toHaveBeenCalledWith(jasmine.any(Number));
});
it("stops ticking when stop is called", function () {
clock.stop();
expect(mockTimeout.cancel).toHaveBeenCalledWith(timeoutHandle);
});
});
});

View File

@ -0,0 +1,47 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([], function () {
/**
* A tick source is an event generator such as a timing signal, or
* indicator of data availability, which can be used to advance the Time
* Conductor. Usage is simple, a listener registers a callback which is
* invoked when this source 'ticks'.
*
* @interface
* @constructor
*/
function TickSource() {
this.listeners = [];
}
/**
* @param callback Function to be called when this tick source ticks.
* @returns an 'unlisten' function that will remove the callback from
* the registered listeners
*/
TickSource.prototype.listen = function (callback) {
throw new Error('Not implemented');
};
return TickSource;
});

View File

@ -0,0 +1,93 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define([], function () {
/**
* @interface
* @constructor
*/
function TimeSystem() {
/**
* @typedef TimeSystemMetadata
* @property {string} key
* @property {string} name
* @property {string} description
*
* @type {TimeSystemMetadata}
*/
this.metadata = undefined;
}
/**
* Time formats are defined as extensions. Time systems that implement
* this interface should provide an array of format keys supported by them.
*
* @returns {string[]} An array of time format keys
*/
TimeSystem.prototype.formats = function () {
throw new Error('Not implemented');
};
/**
* @typedef DeltaFormat
* @property {string} type the type of MctControl used to represent this
* field. Typically 'datetime-field' for UTC based dates, or 'textfield'
* otherwise
* @property {string} [format] An optional field specifying the
* Format to use for delta fields in this time system.
*/
/**
* Specifies a format for deltas in this time system.
*
* @returns {DeltaFormat} a delta format specifier
*/
TimeSystem.prototype.deltaFormat = function () {
throw new Error('Not implemented');
};
/**
* Returns the tick sources supported by this time system. Tick sources
* are event generators that can be used to advance the time conductor
* @returns {TickSource[]} The tick sources supported by this time system.
*/
TimeSystem.prototype.tickSources = function () {
throw new Error('Not implemented');
};
/**
*
* @returns {TimeSystemDefault[]} At least one set of default values for
* this time system.
*/
TimeSystem.prototype.defaults = function () {
throw new Error('Not implemented');
};
/**
* @return {boolean}
*/
TimeSystem.prototype.isUTCBased = function () {
return true;
};
return TimeSystem;
});

View File

@ -0,0 +1,146 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
[
"d3"
],
function (d3) {
var PADDING = 1;
/**
* The mct-conductor-axis renders a horizontal axis with regular
* labelled 'ticks'. It requires 'start' and 'end' integer values to
* be specified as attributes.
*/
function MCTConductorAxis(conductor, formatService) {
// Dependencies
this.d3 = d3;
this.conductor = conductor;
this.formatService = formatService;
// Runtime properties (set by 'link' function)
this.target = undefined;
this.xScale = undefined;
this.xAxis = undefined;
this.axisElement = undefined;
// Angular Directive interface
this.link = this.link.bind(this);
this.restrict = "E";
this.template =
"<div class=\"l-axis-holder\" mct-resize=\"resize()\"></div>";
this.priority = 1000;
//Bind all class functions to 'this'
Object.keys(MCTConductorAxis.prototype).filter(function (key) {
return typeof MCTConductorAxis.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = this[key].bind(this);
}.bind(this));
}
MCTConductorAxis.prototype.setScale = function () {
var width = this.target.offsetWidth;
var timeSystem = this.conductor.timeSystem();
var bounds = this.conductor.bounds();
if (timeSystem.isUTCBased()) {
this.xScale = this.xScale || this.d3.scaleUtc();
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
} else {
this.xScale = this.xScale || this.d3.scaleLinear();
this.xScale.domain([bounds.start, bounds.end]);
}
this.xScale.range([PADDING, width - PADDING * 2]);
this.axisElement.call(this.xAxis);
};
MCTConductorAxis.prototype.changeTimeSystem = function (timeSystem) {
var key = timeSystem.formats()[0];
if (key !== undefined) {
var format = this.formatService.getFormat(key);
var bounds = this.conductor.bounds();
if (timeSystem.isUTCBased()) {
this.xScale = this.d3.scaleUtc();
} else {
this.xScale = this.d3.scaleLinear();
}
this.xAxis.scale(this.xScale);
//Define a custom format function
this.xAxis.tickFormat(function (tickValue) {
// Normalize date representations to numbers
if (tickValue instanceof Date) {
tickValue = tickValue.getTime();
}
return format.format(tickValue, {
min: bounds.start,
max: bounds.end
});
});
this.axisElement.call(this.xAxis);
}
};
MCTConductorAxis.prototype.destroy = function () {
this.conductor.off('timeSystem', this.changeTimeSystem);
this.conductor.off('bounds', this.setScale);
};
MCTConductorAxis.prototype.link = function (scope, element) {
var conductor = this.conductor;
this.target = element[0].firstChild;
var height = this.target.offsetHeight;
var vis = this.d3.select(this.target)
.append('svg:svg')
.attr('width', '100%')
.attr('height', height);
this.xAxis = this.d3.axisTop();
// draw x axis with labels and move to the bottom of the chart area
this.axisElement = vis.append("g")
.attr("transform", "translate(0," + (height - PADDING) + ")");
scope.resize = this.setScale;
conductor.on('timeSystem', this.changeTimeSystem);
//On conductor bounds changes, redraw ticks
conductor.on('bounds', this.setScale);
scope.$on("$destroy", this.destroy);
if (conductor.timeSystem() !== undefined) {
this.changeTimeSystem(conductor.timeSystem());
this.setScale();
}
};
return function (conductor, formatService) {
return new MCTConductorAxis(conductor, formatService);
};
}
);

View File

@ -0,0 +1,146 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(['./MctConductorAxis'], function (MctConductorAxis) {
describe("The MctConductorAxis directive", function () {
var directive,
mockConductor,
mockFormatService,
mockScope,
mockElement,
mockTarget,
mockBounds,
d3;
beforeEach(function () {
mockScope = jasmine.createSpyObj("scope", [
"$on"
]);
//Add some HTML elements
mockTarget = {
offsetWidth: 0,
offsetHeight: 0
};
mockElement = {
firstChild: mockTarget
};
mockBounds = {
start: 100,
end: 200
};
mockConductor = jasmine.createSpyObj("conductor", [
"timeSystem",
"bounds",
"on",
"off"
]);
mockConductor.bounds.andReturn(mockBounds);
mockFormatService = jasmine.createSpyObj("formatService", [
"getFormat"
]);
var d3Functions = [
"scale",
"scaleUtc",
"scaleLinear",
"select",
"append",
"attr",
"axisTop",
"call",
"tickFormat",
"domain",
"range"
];
d3 = jasmine.createSpyObj("d3", d3Functions);
d3Functions.forEach(function (func) {
d3[func].andReturn(d3);
});
directive = new MctConductorAxis(mockConductor, mockFormatService);
directive.d3 = d3;
directive.link(mockScope, [mockElement]);
});
it("listens for changes to time system and bounds", function () {
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", directive.changeTimeSystem);
expect(mockConductor.on).toHaveBeenCalledWith("bounds", directive.setScale);
});
it("on scope destruction, deregisters listeners", function () {
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", directive.destroy);
directive.destroy();
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", directive.changeTimeSystem);
expect(mockConductor.off).toHaveBeenCalledWith("bounds", directive.setScale);
});
describe("when the time system changes", function () {
var mockTimeSystem;
var mockFormat;
beforeEach(function () {
mockTimeSystem = jasmine.createSpyObj("timeSystem", [
"formats",
"isUTCBased"
]);
mockFormat = jasmine.createSpyObj("format", [
"format"
]);
mockTimeSystem.formats.andReturn(["mockFormat"]);
mockFormatService.getFormat.andReturn(mockFormat);
});
it("uses a UTC scale for UTC time systems", function () {
mockTimeSystem.isUTCBased.andReturn(true);
directive.changeTimeSystem(mockTimeSystem);
expect(d3.scaleUtc).toHaveBeenCalled();
expect(d3.scaleLinear).not.toHaveBeenCalled();
});
it("uses a linear scale for non-UTC time systems", function () {
mockTimeSystem.isUTCBased.andReturn(false);
directive.changeTimeSystem(mockTimeSystem);
expect(d3.scaleLinear).toHaveBeenCalled();
expect(d3.scaleUtc).not.toHaveBeenCalled();
});
it("sets axis domain to time conductor bounds", function () {
mockTimeSystem.isUTCBased.andReturn(false);
mockConductor.timeSystem.andReturn(mockTimeSystem);
directive.setScale();
expect(d3.domain).toHaveBeenCalledWith([mockBounds.start, mockBounds.end]);
});
it("uses the format specified by the time system to format tick" +
" labels", function () {
directive.changeTimeSystem(mockTimeSystem);
expect(d3.tickFormat).toHaveBeenCalled();
d3.tickFormat.mostRecentCall.args[0]();
expect(mockFormat.format).toHaveBeenCalled();
});
});
});
});

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