mirror of
https://github.com/nasa/openmct.git
synced 2025-06-26 11:09:22 +00:00
Compare commits
2 Commits
api-tutori
...
tc-api-tak
Author | SHA1 | Date | |
---|---|---|---|
86b3da1e86 | |||
a910b86109 |
114
API.md
114
API.md
@ -1,114 +0,0 @@
|
||||
# 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 standard browser loading; it's easy to use
|
||||
in your project.
|
||||
|
||||
## 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 a domain object. 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.
|
||||
|
||||
## The API
|
||||
|
||||
### `MCT.Type(options)`
|
||||
Status: First Draft
|
||||
|
||||
Returns a `typeInstance`. `options` is an object supporting the following properties:
|
||||
|
||||
* `metadata`: `object` defining metadata used in displaying the object; has the following properties:
|
||||
* `label`: `string`, the human-readible name of the type. used in menus and inspector.
|
||||
* `glyph`: `string`, the name of the icon to display for this type, used in labels.
|
||||
* `description`: `string`, a human readible description of the object and what it is for.
|
||||
* `initialize`: `function` which initializes new instances of this type. it is called with an object, should add any default properties to that object.
|
||||
* `creatable`: `boolean`, if true, this object will be visible in the create menu.
|
||||
* `form`: `Array` an array of form fields, as defined... somewhere! Generates a property sheet that is visible while editing this object.
|
||||
|
||||
### `MCT.type(typeKey, typeInstance)`
|
||||
Status: First Draft
|
||||
|
||||
Register a `typeInstance` with a given Type `key` (a `string`). There can only be one `typeInstance` registered per type `key`. typeInstances must be registered before they can be utilized.
|
||||
|
||||
### `MCT.Objects`
|
||||
Status: First Draft
|
||||
|
||||
Allows you to register object providers, which allows you to integrate domain objects from various different sources. Also implements methods for mutation and persistence of objects. See [Object API](src/api/objects/README.md) for more details.
|
||||
|
||||
### `MCT.Composition`
|
||||
Status: First Draft
|
||||
|
||||
Objects can contain other objects, and the Composition API allows you to fetch the composition of any given domain object, or implement custom methods for defining composition as necessary.
|
||||
|
||||
### `MCT.view(region, definition)`
|
||||
Status: First Draft
|
||||
|
||||
Register a view factory for a specific region. View factories receive an instance of a domain object and return a `View` for that object, or return undefined if they do not know how to generate a view for that object.
|
||||
|
||||
* `ViewDefinition`: an object with the following properties:
|
||||
* `canView(domainObject)`: should return truthy if the view is valid for a given domain object, falsy if it is not capable of generating a view for that object.
|
||||
* `view(domainObject)`: should instantate and return a `View` for the given object.
|
||||
* `metadata()`: a function that returns metadata about this view. Optional.
|
||||
* `View`: an object containing a number of lifecycle methods:
|
||||
* `view.show(container)`: instantiate a view (a set of dom elements) and attach it to the container.
|
||||
* `view.destroy(container)`: remove any listeners and expect your dom elements to be destroyed.
|
||||
|
||||
For a basic introduction to views & types, check out these tutorials:
|
||||
|
||||
* [custom-view](custom-view.html) -- Implementing a custom view with vanilla javascript.
|
||||
* [custom-view-react](custom-view-react.html) -- Implementing a custom view with React.
|
||||
|
||||
### `MCT.conductor`
|
||||
Status: First Draft
|
||||
|
||||
The time conductor is an API that facilitates time synchronization across multiple components. Components that would like to be "time aware" may attach listeners to the time conductor API to allow them to remain synchronized with other components. For more information ont he time conductor API, please look at the API draft here: https://github.com/nasa/openmct/blob/66220b89ca568075f107505ba414de9457dc0427/platform/features/conductor-redux/src/README.md
|
||||
|
||||
### `MCT.selection`
|
||||
Status: First Draft
|
||||
|
||||
Tracks the application's selection state (which elements of a view has a user selected?)
|
||||
|
||||
One or more JavaScript objects may be selected at any given time. User code is responsible for any necessary type-checking.
|
||||
|
||||
The following methods are exposed from this object:
|
||||
|
||||
* `select(value)`: Add `value` to the current selection.
|
||||
* `deselect(value)`: Remove `value` from the current selection.
|
||||
* `selected()`: Get array of all selected objects.
|
||||
* `clear()`: Deselect all selected objects.
|
||||
|
||||
MCT.selection is an EventEmitter; a `change` event is emitted whenever the selection changes.
|
||||
|
||||
### `MCT.systems`
|
||||
Status: Not Implemented, Needs to be ported from old system.
|
||||
|
||||
A registry for different time system definitions. Based upon the previous time format system which utilized the "formats" extension category.
|
||||
|
||||
### `MCT.run([container])`
|
||||
Status: Stable Draft
|
||||
|
||||
Run the MCT application, loading the application into the `container`, a DOM element. If a container is not specified, the application is injected into the body of the page.
|
||||
|
||||
### `MCT.install(plugin)`
|
||||
Status: Stable Draft
|
||||
|
||||
Install a plugin in MCT. Must be called before calling `run`. Plugins are functions which are invoked with the `MCT` instance as their first argument, and are expected to use the MCT public API to add functionality.
|
||||
|
||||
For an example of writing a plugin, check out [plugin-example.html](plugin-example.html)
|
||||
|
||||
### `MCT.setAssetPath(path)`
|
||||
|
||||
Sets the path (absolute or relative) at which the Open MCT static files are being hosted. The default value is '.'.
|
||||
|
||||
Note that this API is transitional and will be removed in a future version.
|
||||
|
@ -18,9 +18,6 @@
|
||||
"node-uuid": "^1.4.7",
|
||||
"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"
|
||||
"zepto": "^1.1.6"
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ deployment:
|
||||
test:
|
||||
post:
|
||||
- gulp lint
|
||||
- gulp checkstyle
|
||||
|
||||
general:
|
||||
branches:
|
||||
|
@ -1,65 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Implementing a composition provider</title>
|
||||
<script src="dist/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
var widgetParts = ['foo', 'bar', 'baz', 'bing', 'frobnak']
|
||||
|
||||
function fabricateName() {
|
||||
return [
|
||||
widgetParts[Math.floor(Math.random() * widgetParts.length)],
|
||||
widgetParts[Math.floor(Math.random() * widgetParts.length)],
|
||||
Math.floor(Math.random() * 1000)
|
||||
].join('_');
|
||||
}
|
||||
|
||||
MCT.type('example.widget-factory', new MCT.Type({
|
||||
metadata: {
|
||||
label: "Widget Factory",
|
||||
glyph: "s",
|
||||
description: "A factory for making widgets"
|
||||
},
|
||||
initialize: function (object) {
|
||||
object.widgetCount = 5;
|
||||
object.composition = [];
|
||||
},
|
||||
creatable: true,
|
||||
form: [
|
||||
{
|
||||
name: "Widget Count",
|
||||
control: "textfield",
|
||||
key: "widgetCount",
|
||||
property: "widgetCount",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
MCT.Composition.addProvider({
|
||||
appliesTo: function (domainObject) {
|
||||
return domainObject.type === 'example.widget-factory';
|
||||
},
|
||||
load: function (domainObject) {
|
||||
var widgets = [];
|
||||
while (widgets.length < domainObject.widgetCount) {
|
||||
widgets.push({
|
||||
name: fabricateName(),
|
||||
key: {
|
||||
namespace: 'widget-factory',
|
||||
identifier: '' + widgets.length
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(widgets);
|
||||
}
|
||||
});
|
||||
|
||||
MCT.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,144 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Implementing a Custom Type and View </title>
|
||||
<script src="dist/main.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.1/react.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.1/react-dom.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/babel">
|
||||
|
||||
// First, we're going to create the Todo List type, so that users can create
|
||||
// todo lists.
|
||||
|
||||
MCT.type('example.todo', new MCT.Type({
|
||||
metadata: {
|
||||
label: "To-Do List",
|
||||
glyph: "2",
|
||||
description: "A list of things that need to be done."
|
||||
},
|
||||
initialize: function (object) {
|
||||
object.tasks = [
|
||||
{ description: "This is a task." }
|
||||
];
|
||||
},
|
||||
creatable: true
|
||||
}));
|
||||
|
||||
/*
|
||||
Refresh the page, and you should be able to create new Todo Lists.
|
||||
unfortunately, when you navigate to a Todo list, you see a blank page. let's
|
||||
fix that by adding a main view for that todo list.
|
||||
|
||||
If you're wondering why this is commented out, well, it's because we'll
|
||||
write a new version later.
|
||||
*/
|
||||
|
||||
var Task = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<li>
|
||||
<input type="checkbox"
|
||||
checked={this.props.checked}/>
|
||||
<span>{this.props.description}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TaskList = React.createClass({
|
||||
render: function () {
|
||||
var taskNodes = this.props.tasks.map(function(task) {
|
||||
return (
|
||||
<Task checked={task.checked}
|
||||
description={task.description}/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<ul>
|
||||
{taskNodes}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
MCT.view(MCT.regions.main, {
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'example.todo';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
var mutableObject = MCT.Objects.getMutable(domainObject);
|
||||
|
||||
return {
|
||||
show: function (container) {
|
||||
ReactDOM.render(
|
||||
<TaskList tasks={domainObject.tasks}/>,
|
||||
container
|
||||
);
|
||||
mutableObject.on('tasks', function (tasks) {
|
||||
ReactDOM.render(
|
||||
<TaskList tasks={tasks}/>,
|
||||
container
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
Refresh the page and you should see a todo list with checkboxes! Now let's
|
||||
Allow you to add tasks by mutating the object. We'll add a toolbar view to
|
||||
do this.
|
||||
*/
|
||||
|
||||
var TaskToolbar = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<button onClick={this.props.addTask}>Add Task</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
MCT.view(MCT.regions.toolbar, {
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'example.todo';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
var mutableObject = MCT.Objects.getMutable(domainObject);
|
||||
|
||||
function addTask(event) {
|
||||
var description = prompt('Task description');
|
||||
var tasks = mutableObject.get('tasks');
|
||||
tasks.push({
|
||||
description: description,
|
||||
complete: false
|
||||
});
|
||||
mutableObject.set('tasks', tasks);
|
||||
}
|
||||
|
||||
return {
|
||||
show: function (container) {
|
||||
ReactDOM.render(
|
||||
<TaskToolbar addTask={addTask}/>,
|
||||
container
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
Refresh the page, edit the todo list, and you'll have a button that allows
|
||||
you to add tasks! Unfortunately, new tasks don't show in the list. Why?
|
||||
Well, if your view should update on mutation, you need to set up the correct
|
||||
listeners. Let's update the TodoView we made earlier:
|
||||
*/
|
||||
|
||||
MCT.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
160
custom-view.html
160
custom-view.html
@ -1,160 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Implementing a Custom Type and View </title>
|
||||
<script src="dist/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
// First, we're going to create the Todo List type, so that users can create
|
||||
// todo lists.
|
||||
|
||||
MCT.type('example.todo', new MCT.Type({
|
||||
metadata: {
|
||||
label: "To-Do List",
|
||||
glyph: "2",
|
||||
description: "A list of things that need to be done."
|
||||
},
|
||||
initialize: function (object) {
|
||||
object.tasks = [
|
||||
{ description: "This is a task." }
|
||||
];
|
||||
},
|
||||
creatable: true
|
||||
}));
|
||||
|
||||
/*
|
||||
Refresh the page, and you should be able to create new Todo Lists.
|
||||
unfortunately, when you navigate to a Todo list, you see a blank page. let's
|
||||
fix that by adding a main view for that todo list.
|
||||
|
||||
If you're wondering why this is commented out, well, it's because we'll
|
||||
write a new version later.
|
||||
*/
|
||||
|
||||
// MCT.view(MCT.regions.main, {
|
||||
// canView: function (domainObject) {
|
||||
// return domainObject.type === 'example.todo';
|
||||
// },
|
||||
// view: function (domainObject) {
|
||||
// function renderTask(task) {
|
||||
// return [
|
||||
// '<li>',
|
||||
// '<input type="checkbox"' + (task.complete ? ' checked="true"' : '') + '>',
|
||||
// '<span>' + task.description + '</span>',
|
||||
// '</li>'
|
||||
// ].join('');
|
||||
// };
|
||||
//
|
||||
// function renderTaskList() {
|
||||
// return [
|
||||
// '<ul>',
|
||||
// domainObject.tasks.map(renderTask).join(''),
|
||||
// '</ul>'
|
||||
// ].join('');
|
||||
// };
|
||||
//
|
||||
// return {
|
||||
// show: function (container) {
|
||||
// container.innerHTML = renderTaskList();
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
|
||||
/*
|
||||
Refresh the page and you should see a todo list with checkboxes! Now let's
|
||||
Allow you to add tasks by mutating the object. We'll add a toolbar view to
|
||||
do this.
|
||||
*/
|
||||
|
||||
MCT.view(MCT.regions.toolbar, {
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'example.todo';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
var mutableObject = MCT.Objects.getMutable(domainObject);
|
||||
|
||||
function addTask(event) {
|
||||
var description = prompt('Task description');
|
||||
var tasks = mutableObject.get('tasks');
|
||||
tasks.push({
|
||||
description: description,
|
||||
complete: false
|
||||
});
|
||||
mutableObject.set('tasks', tasks);
|
||||
}
|
||||
|
||||
return {
|
||||
show: function (container) {
|
||||
container.addEventListener('click', addTask);
|
||||
container.innerHTML = '<button>Add Task</button>';
|
||||
},
|
||||
destroy: function (container) {
|
||||
container.removeEventListener('click', addTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
Refresh the page, edit the todo list, and you'll have a button that allows
|
||||
you to add tasks! Unfortunately, new tasks don't show in the list. Why?
|
||||
Well, if your view should update on mutation, you need to set up the correct
|
||||
listeners. Let's update the TodoView we made earlier:
|
||||
*/
|
||||
|
||||
MCT.view(MCT.regions.main, {
|
||||
canView: function(domainObject) {
|
||||
return domainObject.type === 'example.todo'
|
||||
},
|
||||
view: function (domainObject) {
|
||||
|
||||
var mutableObject = MCT.Objects.getMutable(domainObject);
|
||||
|
||||
function renderTask(task) {
|
||||
return [
|
||||
'<li>',
|
||||
'<input type="checkbox"' + (task.complete ? ' checked="true"' : '') + '>',
|
||||
'<span>' + task.description + '</span>',
|
||||
'</li>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderTaskList(tasks) {
|
||||
return [
|
||||
'<ul>',
|
||||
tasks.map(renderTask).join(''),
|
||||
'</ul>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
function onCheckboxChange(event) {
|
||||
var checkbox = event.target;
|
||||
var taskEl = checkbox.parentNode;
|
||||
var taskList = taskEl.parentNode;
|
||||
var taskIndex = [].slice.apply(taskList.children).indexOf(taskEl);
|
||||
mutableObject.set('tasks[' + taskIndex + '].complete', checkbox.checked);
|
||||
}
|
||||
|
||||
return {
|
||||
show: function (container) {
|
||||
container.innerHTML = renderTaskList(domainObject.tasks);
|
||||
mutableObject.on('tasks', function (tasks) {
|
||||
container.innerHTML = renderTaskList(tasks);
|
||||
});
|
||||
container.addEventListener('change', onCheckboxChange);
|
||||
},
|
||||
destroy: function () {
|
||||
mutableObject.stopListening();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
MCT.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
@ -36,7 +36,7 @@ define([
|
||||
legacyRegistry
|
||||
) {
|
||||
"use strict";
|
||||
legacyRegistry.register("example/msl-adapter", {
|
||||
legacyRegistry.register("example/notifications", {
|
||||
"name" : "Mars Science Laboratory Data Adapter",
|
||||
"extensions" : {
|
||||
"types": [
|
||||
|
@ -45,12 +45,11 @@ define(
|
||||
function buildTaxonomy(dictionary){
|
||||
var models = {};
|
||||
|
||||
function addMeasurement(measurement, parent){
|
||||
function addMeasurement(measurement){
|
||||
var format = FORMAT_MAPPINGS[measurement.type];
|
||||
models[makeId(measurement)] = {
|
||||
type: "msl.measurement",
|
||||
name: measurement.name,
|
||||
location: parent,
|
||||
telemetry: {
|
||||
key: measurement.identifier,
|
||||
ranges: [{
|
||||
@ -63,24 +62,17 @@ define(
|
||||
};
|
||||
}
|
||||
|
||||
function addInstrument(subsystem, spacecraftId) {
|
||||
var measurements = (subsystem.measurements || []),
|
||||
instrumentId = makeId(subsystem);
|
||||
|
||||
models[instrumentId] = {
|
||||
function addInstrument(subsystem) {
|
||||
var measurements = (subsystem.measurements || []);
|
||||
models[makeId(subsystem)] = {
|
||||
type: "msl.instrument",
|
||||
name: subsystem.name,
|
||||
location: spacecraftId,
|
||||
composition: measurements.map(makeId)
|
||||
};
|
||||
measurements.forEach(function(measurement) {
|
||||
addMeasurement(measurement, instrumentId);
|
||||
});
|
||||
measurements.forEach(addMeasurement);
|
||||
}
|
||||
|
||||
(dictionary.instruments || []).forEach(function(instrument) {
|
||||
addInstrument(instrument, "msl:curiosity");
|
||||
});
|
||||
(dictionary.instruments || []).forEach(addInstrument);
|
||||
return models;
|
||||
}
|
||||
|
||||
|
53
gulpfile.js
53
gulpfile.js
@ -21,33 +21,40 @@
|
||||
*****************************************************************************/
|
||||
|
||||
/*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',
|
||||
dist: 'dist',
|
||||
assets: 'dist/assets',
|
||||
scss: ['./platform/**/*.scss', './example/**/*.scss'],
|
||||
assets: [
|
||||
'./{example,platform}/**/*.{css,css.map,png,svg,ico,woff,eot,ttf}'
|
||||
],
|
||||
scripts: [ 'main.js', 'platform/**/*.js', 'src/**/*.js' ],
|
||||
specs: [ 'platform/**/*Spec.js', 'src/**/*Spec.js' ],
|
||||
static: [
|
||||
'index.html',
|
||||
'platform/**/*',
|
||||
'example/**/*',
|
||||
'bower_components/**/*'
|
||||
]
|
||||
},
|
||||
options = {
|
||||
requirejsOptimize: {
|
||||
name: 'bower_components/almond/almond.js',
|
||||
include: paths.main.replace('.js', ''),
|
||||
wrap: {
|
||||
startFile: "src/start.frag",
|
||||
endFile: "src/end.frag"
|
||||
},
|
||||
name: paths.main.replace(/\.js$/, ''),
|
||||
mainConfigFile: paths.main,
|
||||
wrapShim: true
|
||||
},
|
||||
@ -56,6 +63,7 @@ var gulp = require('gulp'),
|
||||
singleRun: true
|
||||
},
|
||||
sass: {
|
||||
includePaths: bourbon.includePaths,
|
||||
sourceComments: true
|
||||
},
|
||||
replace: {
|
||||
@ -69,8 +77,6 @@ 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))
|
||||
@ -80,16 +86,10 @@ 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))
|
||||
@ -103,9 +103,6 @@ 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;
|
||||
}),
|
||||
@ -120,8 +117,6 @@ gulp.task('lint', function () {
|
||||
});
|
||||
|
||||
gulp.task('checkstyle', function () {
|
||||
var jscs = require('gulp-jscs');
|
||||
|
||||
return gulp.src(paths.scripts)
|
||||
.pipe(jscs())
|
||||
.pipe(jscs.reporter())
|
||||
@ -129,20 +124,18 @@ 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('assets', ['stylesheets'], function () {
|
||||
return gulp.src(paths.assets)
|
||||
gulp.task('static', ['stylesheets'], function () {
|
||||
return gulp.src(paths.static, { base: '.' })
|
||||
.pipe(gulp.dest(paths.dist));
|
||||
});
|
||||
|
||||
gulp.task('watch', function () {
|
||||
return gulp.watch(paths.scss, ['stylesheets', 'assets']);
|
||||
gulp.watch(paths.scss, ['stylesheets']);
|
||||
});
|
||||
|
||||
gulp.task('serve', function () {
|
||||
@ -150,9 +143,9 @@ gulp.task('serve', function () {
|
||||
var app = require('./app.js');
|
||||
});
|
||||
|
||||
gulp.task('develop', ['serve', 'install', 'watch']);
|
||||
gulp.task('develop', ['serve', 'stylesheets', 'watch']);
|
||||
|
||||
gulp.task('install', [ 'assets', 'scripts' ]);
|
||||
gulp.task('install', [ 'static', 'scripts' ]);
|
||||
|
||||
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);
|
||||
|
||||
|
@ -31,14 +31,10 @@
|
||||
<script type="text/javascript">
|
||||
require(['main'], function (mct) {
|
||||
require([
|
||||
'./tutorials/todo/todo',
|
||||
'./example/imagery/bundle',
|
||||
'./example/eventGenerator/bundle',
|
||||
'./example/generator/bundle'
|
||||
], function (todoPlugin) {
|
||||
mct.install(todoPlugin);
|
||||
mct.run();
|
||||
})
|
||||
], mct.run.bind(mct));
|
||||
});
|
||||
</script>
|
||||
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
||||
@ -52,5 +48,7 @@
|
||||
<div class="l-splash-holder s-splash-holder">
|
||||
<div class="l-splash s-splash"></div>
|
||||
</div>
|
||||
|
||||
<div ng-view></div>
|
||||
</body>
|
||||
</html>
|
||||
|
68
main.js
68
main.js
@ -28,15 +28,13 @@ requirejs.config({
|
||||
"angular-route": "bower_components/angular-route/angular-route.min",
|
||||
"csv": "bower_components/comma-separated-values/csv.min",
|
||||
"es6-promise": "bower_components/es6-promise/promise.min",
|
||||
"EventEmitter": "bower_components/eventemitter3/index",
|
||||
"moment": "bower_components/moment/moment",
|
||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||
"text": "bower_components/text/text",
|
||||
"uuid": "bower_components/node-uuid/uuid",
|
||||
"zepto": "bower_components/zepto/zepto.min",
|
||||
"lodash": "bower_components/lodash/lodash"
|
||||
"zepto": "bower_components/zepto/zepto.min"
|
||||
},
|
||||
"shim": {
|
||||
"angular": {
|
||||
@ -45,9 +43,6 @@ requirejs.config({
|
||||
"angular-route": {
|
||||
"deps": ["angular"]
|
||||
},
|
||||
"EventEmitter": {
|
||||
"exports": "EventEmitter"
|
||||
},
|
||||
"moment-duration-format": {
|
||||
"deps": ["moment"]
|
||||
},
|
||||
@ -56,32 +51,53 @@ requirejs.config({
|
||||
},
|
||||
"zepto": {
|
||||
"exports": "Zepto"
|
||||
},
|
||||
"lodash": {
|
||||
"exports": "lodash"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
define([
|
||||
'./platform/framework/src/Main',
|
||||
'./src/defaultRegistry',
|
||||
'./src/MCT'
|
||||
], function (Main, defaultRegistry, MCT) {
|
||||
var mct = new MCT();
|
||||
'legacyRegistry',
|
||||
|
||||
mct.legacyRegistry = defaultRegistry;
|
||||
mct.run = function (domElement) {
|
||||
if (!domElement) { domElement = document.body; }
|
||||
var appDiv = document.createElement('div');
|
||||
appDiv.setAttribute('ng-view', '');
|
||||
appDiv.className = 'user-environ';
|
||||
domElement.appendChild(appDiv);
|
||||
mct.start();
|
||||
'./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/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);
|
||||
}
|
||||
};
|
||||
mct.on('start', function () {
|
||||
return new Main().run(defaultRegistry);
|
||||
});
|
||||
|
||||
return mct;
|
||||
});
|
||||
|
@ -1,64 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Groot Tutorial</title>
|
||||
<script src="dist/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
// First, we need a source of objects, so we're going to define a few
|
||||
// objects that we wish to expose. The first object is a folder which
|
||||
// contains the other objects.
|
||||
|
||||
|
||||
var GROOT_ROOT = {
|
||||
name: 'I am groot',
|
||||
type: 'folder',
|
||||
composition: [
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'arms'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'legs'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'torso'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Now, we will define an object provider. This will allow us to fetch the
|
||||
// GROOT_ROOT object, plus any other objects in the `groot` namespace.
|
||||
// For more info, see the Object API documentation.
|
||||
|
||||
MCT.Objects.addProvider('groot', {
|
||||
// we'll only define a get function, objects from this provider will
|
||||
// not be mutable.
|
||||
get: function (key) {
|
||||
if (key.identifier === 'groot') {
|
||||
return Promise.resolve(GROOT_ROOT);
|
||||
}
|
||||
return Promise.resolve({
|
||||
name: 'Groot\'s ' + key.identifier
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Finally, we need to add a "ROOT." This is an identifier for an object
|
||||
// that Open MCT will load at run time and show at the top-level of the
|
||||
// navigation tree.
|
||||
|
||||
MCT.Objects.addRoot({
|
||||
namespace: 'groot',
|
||||
identifier: 'groot'
|
||||
});
|
||||
|
||||
MCT.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -24,14 +24,23 @@ define([
|
||||
"./src/BrowseController",
|
||||
"./src/PaneController",
|
||||
"./src/BrowseObjectController",
|
||||
"./src/creation/CreateMenuController",
|
||||
"./src/creation/LocatorController",
|
||||
"./src/MenuArrowController",
|
||||
"./src/navigation/NavigationService",
|
||||
"./src/creation/CreationPolicy",
|
||||
"./src/navigation/NavigateAction",
|
||||
"./src/windowing/NewTabAction",
|
||||
"./src/windowing/FullscreenAction",
|
||||
"./src/creation/CreateActionProvider",
|
||||
"./src/creation/AddActionProvider",
|
||||
"./src/creation/CreationService",
|
||||
"./src/windowing/WindowTitler",
|
||||
"text!./res/templates/browse.html",
|
||||
"text!./res/templates/create/locator.html",
|
||||
"text!./res/templates/browse-object.html",
|
||||
"text!./res/templates/create/create-button.html",
|
||||
"text!./res/templates/create/create-menu.html",
|
||||
"text!./res/templates/items/grid-item.html",
|
||||
"text!./res/templates/browse/object-header.html",
|
||||
"text!./res/templates/menu-arrow.html",
|
||||
@ -39,20 +48,28 @@ 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,
|
||||
PaneController,
|
||||
BrowseObjectController,
|
||||
CreateMenuController,
|
||||
LocatorController,
|
||||
MenuArrowController,
|
||||
NavigationService,
|
||||
CreationPolicy,
|
||||
NavigateAction,
|
||||
NewTabAction,
|
||||
FullscreenAction,
|
||||
CreateActionProvider,
|
||||
AddActionProvider,
|
||||
CreationService,
|
||||
WindowTitler,
|
||||
browseTemplate,
|
||||
locatorTemplate,
|
||||
browseObjectTemplate,
|
||||
createButtonTemplate,
|
||||
createMenuTemplate,
|
||||
gridItemTemplate,
|
||||
objectHeaderTemplate,
|
||||
menuArrowTemplate,
|
||||
@ -60,7 +77,6 @@ define([
|
||||
itemsTemplate,
|
||||
objectPropertiesTemplate,
|
||||
inspectorRegionTemplate,
|
||||
viewObjectTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@ -120,6 +136,22 @@ define([
|
||||
"$route"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CreateMenuController",
|
||||
"implementation": CreateMenuController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "LocatorController",
|
||||
"implementation": LocatorController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$timeout",
|
||||
"objectService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "MenuArrowController",
|
||||
"implementation": MenuArrowController,
|
||||
@ -128,10 +160,16 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"key": "locator",
|
||||
"template": locatorTemplate
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
{
|
||||
"key": "view-object",
|
||||
"template": viewObjectTemplate
|
||||
"templateUrl": "templates/view-object.html"
|
||||
},
|
||||
{
|
||||
"key": "browse-object",
|
||||
@ -143,6 +181,17 @@ define([
|
||||
"view"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "create-button",
|
||||
"template": createButtonTemplate
|
||||
},
|
||||
{
|
||||
"key": "create-menu",
|
||||
"template": createMenuTemplate,
|
||||
"uses": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "grid-item",
|
||||
"template": gridItemTemplate,
|
||||
@ -195,6 +244,12 @@ define([
|
||||
"implementation": NavigationService
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"implementation": CreationPolicy,
|
||||
"category": "creation"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"key": "navigate",
|
||||
@ -247,6 +302,42 @@ define([
|
||||
"editable": false
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"key": "CreateActionProvider",
|
||||
"provides": "actionService",
|
||||
"type": "provider",
|
||||
"implementation": CreateActionProvider,
|
||||
"depends": [
|
||||
"$q",
|
||||
"typeService",
|
||||
"navigationService",
|
||||
"policyService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "AddActionProvider",
|
||||
"provides": "actionService",
|
||||
"type": "provider",
|
||||
"implementation": AddActionProvider,
|
||||
"depends": [
|
||||
"$q",
|
||||
"typeService",
|
||||
"dialogService",
|
||||
"policyService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CreationService",
|
||||
"provides": "creationService",
|
||||
"type": "provider",
|
||||
"implementation": CreationService,
|
||||
"depends": [
|
||||
"$q",
|
||||
"$log"
|
||||
]
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
{
|
||||
"implementation": WindowTitler,
|
||||
|
@ -43,8 +43,11 @@ define(
|
||||
* override this)
|
||||
* @param {ActionContext} context the context in which the
|
||||
* action is being performed
|
||||
* @param {NavigationService} navigationService the navigation service,
|
||||
* which handles changes in navigation. It allows the object
|
||||
* being browsed/edited to be set.
|
||||
*/
|
||||
function CreateAction(type, parent, context) {
|
||||
function CreateAction(type, parent, context, $q, navigationService) {
|
||||
this.metadata = {
|
||||
key: 'create',
|
||||
glyph: type.getGlyph(),
|
||||
@ -53,8 +56,24 @@ define(
|
||||
description: type.getDescription(),
|
||||
context: context
|
||||
};
|
||||
|
||||
this.type = type;
|
||||
this.parent = parent;
|
||||
this.navigationService = navigationService;
|
||||
this.$q = $q;
|
||||
}
|
||||
|
||||
// Get a count of views which are not flagged as non-editable.
|
||||
function countEditableViews(domainObject) {
|
||||
var views = domainObject && domainObject.useCapability('view'),
|
||||
count = 0;
|
||||
|
||||
// A view is editable unless explicitly flagged as not
|
||||
(views || []).forEach(function (view) {
|
||||
count += (view.editable !== false) ? 1 : 0;
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,31 +82,26 @@ define(
|
||||
*/
|
||||
CreateAction.prototype.perform = function () {
|
||||
var newModel = this.type.getInitialModel(),
|
||||
newObject,
|
||||
editAction,
|
||||
editorCapability;
|
||||
|
||||
function onSave() {
|
||||
return editorCapability.save();
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
return editorCapability.cancel();
|
||||
}
|
||||
parentObject = this.navigationService.getNavigation(),
|
||||
editorCapability,
|
||||
newObject;
|
||||
|
||||
newModel.type = this.type.getKey();
|
||||
newModel.location = this.parent.getId();
|
||||
newObject = this.parent.useCapability('instantiation', newModel);
|
||||
editorCapability = newObject.hasCapability('editor') && newObject.getCapability("editor");
|
||||
newModel.location = parentObject.getId();
|
||||
newObject = parentObject.useCapability('instantiation', newModel);
|
||||
|
||||
editAction = newObject.getCapability("action").getActions("edit")[0];
|
||||
//If an edit action is available, perform it
|
||||
if (editAction) {
|
||||
return editAction.perform();
|
||||
} else if (editorCapability) {
|
||||
//otherwise, use the save action
|
||||
editorCapability = newObject.getCapability("editor");
|
||||
|
||||
if (countEditableViews(newObject) > 0 && newObject.hasCapability('composition')) {
|
||||
this.navigationService.setNavigation(newObject);
|
||||
return newObject.getCapability("action").perform("edit");
|
||||
} else {
|
||||
editorCapability.edit();
|
||||
return newObject.getCapability("action").perform("save").then(onSave, onCancel);
|
||||
return newObject.useCapability("action").perform("save").then(function () {
|
||||
return editorCapability.save();
|
||||
}, function () {
|
||||
return editorCapability.cancel();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -44,8 +44,10 @@ define(
|
||||
* introduced in this bundle), responsible for handling actual
|
||||
* object creation.
|
||||
*/
|
||||
function CreateActionProvider(typeService, policyService) {
|
||||
function CreateActionProvider($q, typeService, navigationService, policyService) {
|
||||
this.typeService = typeService;
|
||||
this.navigationService = navigationService;
|
||||
this.$q = $q;
|
||||
this.policyService = policyService;
|
||||
}
|
||||
|
||||
@ -70,7 +72,9 @@ define(
|
||||
return new CreateAction(
|
||||
type,
|
||||
destination,
|
||||
context
|
||||
context,
|
||||
self.$q,
|
||||
self.navigationService
|
||||
);
|
||||
});
|
||||
};
|
@ -29,10 +29,13 @@ define(
|
||||
|
||||
describe("The create action provider", function () {
|
||||
var mockTypeService,
|
||||
mockDialogService,
|
||||
mockNavigationService,
|
||||
mockPolicyService,
|
||||
mockCreationPolicy,
|
||||
mockPolicyMap = {},
|
||||
mockTypes,
|
||||
mockQ,
|
||||
provider;
|
||||
|
||||
function createMockType(name) {
|
||||
@ -58,6 +61,14 @@ define(
|
||||
"typeService",
|
||||
["listTypes"]
|
||||
);
|
||||
mockDialogService = jasmine.createSpyObj(
|
||||
"dialogService",
|
||||
["getUserInput"]
|
||||
);
|
||||
mockNavigationService = jasmine.createSpyObj(
|
||||
"navigationService",
|
||||
["setNavigation"]
|
||||
);
|
||||
mockPolicyService = jasmine.createSpyObj(
|
||||
"policyService",
|
||||
["allow"]
|
||||
@ -80,7 +91,9 @@ define(
|
||||
mockTypeService.listTypes.andReturn(mockTypes);
|
||||
|
||||
provider = new CreateActionProvider(
|
||||
mockQ,
|
||||
mockTypeService,
|
||||
mockNavigationService,
|
||||
mockPolicyService
|
||||
);
|
||||
});
|
130
platform/commonUI/browse/test/creation/CreateActionSpec.js
Normal file
130
platform/commonUI/browse/test/creation/CreateActionSpec.js
Normal file
@ -0,0 +1,130 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* MCTRepresentationSpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
define(
|
||||
["../../src/creation/CreateAction"],
|
||||
function (CreateAction) {
|
||||
|
||||
describe("The create action", function () {
|
||||
var mockType,
|
||||
mockParent,
|
||||
mockContext,
|
||||
mockDialogService,
|
||||
mockCreationService,
|
||||
action;
|
||||
|
||||
function mockPromise(value) {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockType = jasmine.createSpyObj(
|
||||
"type",
|
||||
[
|
||||
"getKey",
|
||||
"getGlyph",
|
||||
"getName",
|
||||
"getDescription",
|
||||
"getProperties",
|
||||
"getInitialModel"
|
||||
]
|
||||
);
|
||||
mockParent = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[
|
||||
"getId",
|
||||
"getModel",
|
||||
"getCapability"
|
||||
]
|
||||
);
|
||||
mockContext = {
|
||||
domainObject: mockParent
|
||||
};
|
||||
mockDialogService = jasmine.createSpyObj(
|
||||
"dialogService",
|
||||
["getUserInput"]
|
||||
);
|
||||
mockCreationService = jasmine.createSpyObj(
|
||||
"creationService",
|
||||
["createObject"]
|
||||
);
|
||||
|
||||
mockType.getKey.andReturn("test");
|
||||
mockType.getGlyph.andReturn("T");
|
||||
mockType.getDescription.andReturn("a test type");
|
||||
mockType.getName.andReturn("Test");
|
||||
mockType.getProperties.andReturn([]);
|
||||
mockType.getInitialModel.andReturn({});
|
||||
|
||||
mockDialogService.getUserInput.andReturn(mockPromise({}));
|
||||
|
||||
action = new CreateAction(
|
||||
mockType,
|
||||
mockParent,
|
||||
mockContext,
|
||||
mockDialogService,
|
||||
mockCreationService
|
||||
);
|
||||
});
|
||||
|
||||
it("exposes type-appropriate metadata", function () {
|
||||
var metadata = action.getMetadata();
|
||||
|
||||
expect(metadata.name).toEqual("Test");
|
||||
expect(metadata.description).toEqual("a test type");
|
||||
expect(metadata.glyph).toEqual("T");
|
||||
});
|
||||
|
||||
//TODO: Disabled for NEM Beta
|
||||
xit("invokes the creation service when performed", function () {
|
||||
action.perform();
|
||||
expect(mockCreationService.createObject).toHaveBeenCalledWith(
|
||||
{ type: "test" },
|
||||
mockParent
|
||||
);
|
||||
});
|
||||
|
||||
//TODO: Disabled for NEM Beta
|
||||
xit("does not create an object if the user cancels", function () {
|
||||
mockDialogService.getUserInput.andReturn({
|
||||
then: function (callback, fail) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
|
||||
action.perform();
|
||||
|
||||
expect(mockCreationService.createObject)
|
||||
.not.toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -43,15 +43,6 @@ define([
|
||||
"./src/capabilities/EditorCapability",
|
||||
"./src/capabilities/TransactionCapabilityDecorator",
|
||||
"./src/services/TransactionService",
|
||||
"./src/creation/CreateMenuController",
|
||||
"./src/creation/LocatorController",
|
||||
"./src/creation/CreationPolicy",
|
||||
"./src/creation/CreateActionProvider",
|
||||
"./src/creation/AddActionProvider",
|
||||
"./src/creation/CreationService",
|
||||
"text!./res/templates/create/locator.html",
|
||||
"text!./res/templates/create/create-button.html",
|
||||
"text!./res/templates/create/create-menu.html",
|
||||
"text!./res/templates/library.html",
|
||||
"text!./res/templates/edit-object.html",
|
||||
"text!./res/templates/edit-action-buttons.html",
|
||||
@ -81,15 +72,6 @@ define([
|
||||
EditorCapability,
|
||||
TransactionCapabilityDecorator,
|
||||
TransactionService,
|
||||
CreateMenuController,
|
||||
LocatorController,
|
||||
CreationPolicy,
|
||||
CreateActionProvider,
|
||||
AddActionProvider,
|
||||
CreationService,
|
||||
locatorTemplate,
|
||||
createButtonTemplate,
|
||||
createMenuTemplate,
|
||||
libraryTemplate,
|
||||
editObjectTemplate,
|
||||
editActionButtonsTemplate,
|
||||
@ -130,22 +112,6 @@ define([
|
||||
"$location",
|
||||
"policyService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CreateMenuController",
|
||||
"implementation": CreateMenuController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "LocatorController",
|
||||
"implementation": LocatorController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$timeout",
|
||||
"objectService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
@ -253,13 +219,10 @@ define([
|
||||
},
|
||||
{
|
||||
"category": "navigation",
|
||||
"message": "Continuing will cause the loss of any unsaved changes.",
|
||||
"message": "There are unsaved changes.",
|
||||
"implementation": EditNavigationPolicy
|
||||
},
|
||||
{
|
||||
"implementation": CreationPolicy,
|
||||
"category": "creation"
|
||||
}
|
||||
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
@ -298,17 +261,6 @@ define([
|
||||
{
|
||||
"key": "topbar-edit",
|
||||
"template": topbarEditTemplate
|
||||
},
|
||||
{
|
||||
"key": "create-button",
|
||||
"template": createButtonTemplate
|
||||
},
|
||||
{
|
||||
"key": "create-menu",
|
||||
"template": createMenuTemplate,
|
||||
"uses": [
|
||||
"action"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
@ -330,40 +282,7 @@ define([
|
||||
"$q",
|
||||
"$log"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CreateActionProvider",
|
||||
"provides": "actionService",
|
||||
"type": "provider",
|
||||
"implementation": CreateActionProvider,
|
||||
"depends": [
|
||||
"typeService",
|
||||
"policyService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "AddActionProvider",
|
||||
"provides": "actionService",
|
||||
"type": "provider",
|
||||
"implementation": AddActionProvider,
|
||||
"depends": [
|
||||
"$q",
|
||||
"typeService",
|
||||
"dialogService",
|
||||
"policyService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CreationService",
|
||||
"provides": "creationService",
|
||||
"type": "provider",
|
||||
"implementation": CreationService,
|
||||
"depends": [
|
||||
"$q",
|
||||
"$log"
|
||||
]
|
||||
}
|
||||
|
||||
],
|
||||
"representers": [
|
||||
{
|
||||
@ -397,12 +316,6 @@ define([
|
||||
"transactionService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"key": "locator",
|
||||
"template": locatorTemplate
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
@ -74,12 +74,6 @@ define(
|
||||
self.domainObject.getCapability('editor').cancel();
|
||||
self.navigationService.removeListener(cancelEditing);
|
||||
}
|
||||
//If this is not the currently navigated object, then navigate
|
||||
// to it.
|
||||
if (this.navigationService.getNavigation() !== this.domainObject) {
|
||||
this.navigationService.setNavigation(this.domainObject);
|
||||
}
|
||||
|
||||
this.navigationService.addListener(cancelEditing);
|
||||
this.domainObject.useCapability("editor");
|
||||
};
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
|
||||
define(
|
||||
['../creation/CreateWizard'],
|
||||
['../../../browse/src/creation/CreateWizard'],
|
||||
function (CreateWizard) {
|
||||
|
||||
/**
|
||||
|
@ -56,10 +56,7 @@ define(
|
||||
// A view is editable unless explicitly flagged as not
|
||||
(views || []).forEach(function (view) {
|
||||
if (view.editable === true ||
|
||||
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
|
||||
(view.key === 'table' && type.getKey() === 'table') ||
|
||||
(view.key === 'rt-table' && type.getKey() === 'rttable')
|
||||
) {
|
||||
(view.key === 'plot' && type.getKey() === 'telemetry.panel')) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
@ -1,190 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* MCTRepresentationSpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
define(
|
||||
["../../src/creation/CreateAction"],
|
||||
function (CreateAction) {
|
||||
|
||||
describe("The create action", function () {
|
||||
var mockType,
|
||||
mockParent,
|
||||
mockContext,
|
||||
mockDomainObject,
|
||||
capabilities = {},
|
||||
mockEditAction,
|
||||
mockSaveAction,
|
||||
action;
|
||||
|
||||
function mockPromise(value) {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockType = jasmine.createSpyObj(
|
||||
"type",
|
||||
[
|
||||
"getKey",
|
||||
"getGlyph",
|
||||
"getName",
|
||||
"getDescription",
|
||||
"getProperties",
|
||||
"getInitialModel"
|
||||
]
|
||||
);
|
||||
mockParent = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[
|
||||
"getId",
|
||||
"getModel",
|
||||
"getCapability",
|
||||
"useCapability"
|
||||
]
|
||||
);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[
|
||||
"getId",
|
||||
"getModel",
|
||||
"getCapability",
|
||||
"hasCapability",
|
||||
"useCapability"
|
||||
]
|
||||
);
|
||||
mockDomainObject.hasCapability.andCallFake(function (name) {
|
||||
return !!capabilities[name];
|
||||
});
|
||||
mockDomainObject.getCapability.andCallFake(function (name) {
|
||||
return capabilities[name];
|
||||
});
|
||||
mockSaveAction = jasmine.createSpyObj(
|
||||
"saveAction",
|
||||
[
|
||||
"perform"
|
||||
]
|
||||
);
|
||||
|
||||
capabilities.action = jasmine.createSpyObj(
|
||||
"actionCapability",
|
||||
[
|
||||
"getActions",
|
||||
"perform"
|
||||
]
|
||||
);
|
||||
|
||||
capabilities.editor = jasmine.createSpyObj(
|
||||
"editorCapability",
|
||||
[
|
||||
"edit",
|
||||
"save",
|
||||
"cancel"
|
||||
]
|
||||
);
|
||||
|
||||
mockEditAction = jasmine.createSpyObj(
|
||||
"editAction",
|
||||
[
|
||||
"perform"
|
||||
]
|
||||
);
|
||||
|
||||
mockContext = {
|
||||
domainObject: mockParent
|
||||
};
|
||||
mockParent.useCapability.andReturn(mockDomainObject);
|
||||
|
||||
mockType.getKey.andReturn("test");
|
||||
mockType.getGlyph.andReturn("T");
|
||||
mockType.getDescription.andReturn("a test type");
|
||||
mockType.getName.andReturn("Test");
|
||||
mockType.getProperties.andReturn([]);
|
||||
mockType.getInitialModel.andReturn({});
|
||||
|
||||
action = new CreateAction(
|
||||
mockType,
|
||||
mockParent,
|
||||
mockContext
|
||||
);
|
||||
});
|
||||
|
||||
it("exposes type-appropriate metadata", function () {
|
||||
var metadata = action.getMetadata();
|
||||
|
||||
expect(metadata.name).toEqual("Test");
|
||||
expect(metadata.description).toEqual("a test type");
|
||||
expect(metadata.glyph).toEqual("T");
|
||||
});
|
||||
|
||||
describe("the perform function", function () {
|
||||
beforeEach(function () {
|
||||
capabilities.action.getActions.andReturn([mockEditAction]);
|
||||
});
|
||||
|
||||
it("uses the instantiation capability when performed", function () {
|
||||
action.perform();
|
||||
expect(mockParent.useCapability).toHaveBeenCalledWith("instantiation", jasmine.any(Object));
|
||||
});
|
||||
|
||||
it("uses the edit action if available", function () {
|
||||
action.perform();
|
||||
expect(mockEditAction.perform).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses the save action if object does not have an edit action" +
|
||||
" available", function () {
|
||||
capabilities.action.getActions.andReturn([]);
|
||||
capabilities.action.perform.andReturn(mockPromise(undefined));
|
||||
action.perform();
|
||||
expect(capabilities.action.perform).toHaveBeenCalledWith("save");
|
||||
});
|
||||
|
||||
describe("uses to editor capability", function () {
|
||||
var promise = jasmine.createSpyObj("promise", ["then"]);
|
||||
beforeEach(function () {
|
||||
capabilities.action.getActions.andReturn([]);
|
||||
capabilities.action.perform.andReturn(promise);
|
||||
});
|
||||
|
||||
it("to save the edit if user saves dialog", function () {
|
||||
action.perform();
|
||||
expect(promise.then).toHaveBeenCalled();
|
||||
promise.then.mostRecentCall.args[0]();
|
||||
expect(capabilities.editor.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("to cancel the edit if user cancels dialog", function () {
|
||||
action.perform();
|
||||
promise.then.mostRecentCall.args[1]();
|
||||
expect(capabilities.editor.cancel).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -48,7 +48,6 @@ define([
|
||||
"./src/directives/MCTSplitPane",
|
||||
"./src/directives/MCTSplitter",
|
||||
"./src/directives/MCTTree",
|
||||
"./src/filters/ReverseFilter.js",
|
||||
"text!./res/templates/bottombar.html",
|
||||
"text!./res/templates/controls/action-button.html",
|
||||
"text!./res/templates/controls/input-filter.html",
|
||||
@ -97,7 +96,6 @@ define([
|
||||
MCTSplitPane,
|
||||
MCTSplitter,
|
||||
MCTTree,
|
||||
ReverseFilter,
|
||||
bottombarTemplate,
|
||||
actionButtonTemplate,
|
||||
inputFilterTemplate,
|
||||
@ -148,8 +146,7 @@ define([
|
||||
"depends": [
|
||||
"stylesheets[]",
|
||||
"$document",
|
||||
"THEME",
|
||||
"ASSETS_PATH"
|
||||
"THEME"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -161,7 +158,7 @@ define([
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"implementation": ReverseFilter,
|
||||
"implementation": "filters/ReverseFilter.js",
|
||||
"key": "reverse"
|
||||
}
|
||||
],
|
||||
@ -407,11 +404,6 @@ define([
|
||||
"key": "THEME",
|
||||
"value": "unspecified",
|
||||
"priority": "fallback"
|
||||
},
|
||||
{
|
||||
"key": "ASSETS_PATH",
|
||||
"value": ".",
|
||||
"priority": "fallback"
|
||||
}
|
||||
],
|
||||
"containers": [
|
||||
|
@ -145,8 +145,3 @@
|
||||
.flex-justify-end {
|
||||
@include justify-content(flex-end);
|
||||
}
|
||||
|
||||
/********************************************* POPUPS */
|
||||
.t-popup {
|
||||
z-index: 75;
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ $uePaneMiniTabW: 10px;
|
||||
$uePaneMiniTabCollapsedW: 11px;
|
||||
$ueEditLeftPaneW: 75%;
|
||||
$treeSearchInputBarH: 25px;
|
||||
$ueTimeControlH: (33px, 18px, 20px);
|
||||
$ueTimeControlH: (33px, 20px, 20px);
|
||||
// Panes
|
||||
$ueBrowseLeftPaneTreeMinW: 150px;
|
||||
$ueBrowseLeftPaneTreeMaxW: 35%;
|
||||
|
@ -63,10 +63,9 @@ input, textarea {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="search"] {
|
||||
input[type="text"] {
|
||||
vertical-align: baseline;
|
||||
padding: 3px 5px;
|
||||
padding: 3px 5px !important;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
|
@ -139,17 +139,6 @@
|
||||
background-size: $d $d;
|
||||
}
|
||||
|
||||
@mixin bgStripes($c: yellow, $a: 0.1, $bgsize: 5px, $angle: 90deg) {
|
||||
@include background-image(linear-gradient($angle,
|
||||
rgba($c, $a) 25%, transparent 25%,
|
||||
transparent 50%, rgba($c, $a) 50%,
|
||||
rgba($c, $a) 75%, transparent 75%,
|
||||
transparent 100%
|
||||
));
|
||||
background-repeat: repeat;
|
||||
background-size: $bgsize $bgsize;
|
||||
}
|
||||
|
||||
@mixin bgVertStripes($c: yellow, $a: 0.1, $d: 40px) {
|
||||
@include background-image(linear-gradient(-90deg,
|
||||
rgba($c, $a) 0%, rgba($c, $a) 50%,
|
||||
@ -333,13 +322,13 @@
|
||||
color: $fg;
|
||||
outline: none;
|
||||
&.error {
|
||||
background-color: $colorFormFieldErrorBg;
|
||||
color: $colorFormFieldErrorFg;
|
||||
background: rgba(red, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg) {
|
||||
@include input-base($bg, $fg);
|
||||
padding: 0 $interiorMarginSm;
|
||||
}
|
||||
|
||||
@mixin contextArrow() {
|
||||
|
@ -29,7 +29,7 @@
|
||||
.accordion-head {
|
||||
$op: 0.2;
|
||||
border-radius: $basicCr * 0.75;
|
||||
box-sizing: border-box;
|
||||
box-sizing: "border-box";
|
||||
background: rgba($colorBodyFg, $op);
|
||||
cursor: pointer;
|
||||
font-size: 0.75em;
|
||||
@ -396,11 +396,11 @@ input[type="search"] {
|
||||
left: auto;
|
||||
}
|
||||
.knob-l {
|
||||
@include border-left-radius($sliderKnobR);
|
||||
@include border-left-radius($sliderKnobW);
|
||||
cursor: w-resize;
|
||||
}
|
||||
.knob-r {
|
||||
@include border-right-radius($sliderKnobR);
|
||||
@include border-right-radius($sliderKnobW);
|
||||
cursor: e-resize;
|
||||
}
|
||||
.range {
|
||||
@ -426,6 +426,7 @@ input[type="search"] {
|
||||
@include user-select(none);
|
||||
font-size: 0.8rem;
|
||||
padding: $interiorMarginLg !important;
|
||||
width: 230px;
|
||||
.l-month-year-pager {
|
||||
$pagerW: 20px;
|
||||
height: $r1H;
|
||||
@ -517,19 +518,6 @@ input[type="search"] {
|
||||
}
|
||||
}
|
||||
|
||||
@include phone {
|
||||
.l-datetime-picker {
|
||||
padding: $interiorMargin !important;
|
||||
}
|
||||
.l-calendar {
|
||||
ul.l-cal-row {
|
||||
li {
|
||||
padding: 2px $interiorMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** TEXTAREA */
|
||||
textarea {
|
||||
@include nice-textarea($colorInputBg, $colorInputFg);
|
||||
|
@ -10,24 +10,25 @@
|
||||
$knobHOffset: 0px;
|
||||
$knobM: ($sliderKnobW + $knobHOffset) * -1;
|
||||
$rangeValPad: $interiorMargin;
|
||||
$rangeValOffset: $sliderKnobW + $interiorMargin;
|
||||
$timeRangeSliderLROffset: 150px + ($sliderKnobW * 2);
|
||||
$r1H: nth($ueTimeControlH,1); // Not currently used
|
||||
$rangeValOffset: $sliderKnobW;
|
||||
$timeRangeSliderLROffset: 130px + $sliderKnobW + $rangeValOffset;
|
||||
$r1H: nth($ueTimeControlH,1);
|
||||
$r2H: nth($ueTimeControlH,2);
|
||||
$r3H: nth($ueTimeControlH,3);
|
||||
|
||||
display: block;
|
||||
height: $r1H + $r2H + $r3H + ($interiorMargin * 2);
|
||||
min-width: $minW;
|
||||
font-size: 0.8rem;
|
||||
|
||||
|
||||
.l-time-range-inputs-holder,
|
||||
.l-time-range-slider-holder,
|
||||
.l-time-range-ticks-holder
|
||||
{
|
||||
@include absPosDefault(0, visible);
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
&:not(:first-child) {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
top: auto;
|
||||
}
|
||||
.l-time-range-slider,
|
||||
.l-time-range-ticks {
|
||||
@ -36,21 +37,14 @@
|
||||
}
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
height: $r1H; bottom: $r2H + $r3H + ($interiorMarginSm * 2);
|
||||
padding-top: $interiorMargin;
|
||||
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-input,
|
||||
.l-time-range-inputs-elem {
|
||||
margin-right: $interiorMargin;
|
||||
.lbl {
|
||||
@ -58,27 +52,13 @@
|
||||
}
|
||||
.ui-symbol.icon {
|
||||
font-size: 11px;
|
||||
width: 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;
|
||||
height: $r2H; bottom: $r3H + ($interiorMarginSm * 1);
|
||||
.range-holder {
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
@ -93,13 +73,24 @@
|
||||
width: $myW;
|
||||
height: auto;
|
||||
z-index: 2;
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: $myC;
|
||||
content: "";
|
||||
position: absolute;
|
||||
}
|
||||
&:before {
|
||||
// Vert line
|
||||
background-color: $myC;
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
|
||||
width: 1px;
|
||||
width: 2px;
|
||||
}
|
||||
&:after {
|
||||
// Circle element
|
||||
border-radius: $myW;
|
||||
@include transform(translateY(-50%));
|
||||
top: 50%; right: 0; bottom: auto; left: 0;
|
||||
width: auto;
|
||||
height: $myW;
|
||||
}
|
||||
}
|
||||
&:hover .toi-line {
|
||||
@ -135,9 +126,9 @@
|
||||
@include webkitProp(transform, translateX(-50%));
|
||||
color: $colorPlotLabelFg;
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.9em;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
top: 8px;
|
||||
white-space: nowrap;
|
||||
z-index: 2;
|
||||
}
|
||||
@ -147,29 +138,16 @@
|
||||
|
||||
.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;
|
||||
padding: 0 $rangeValOffset;
|
||||
position: absolute;
|
||||
height: $r2H;
|
||||
line-height: $r2H;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
&:hover {
|
||||
.range-value {
|
||||
color: $sliderColorKnobHov;
|
||||
}
|
||||
&:hover .range-value {
|
||||
color: $sliderColorKnobHov;
|
||||
}
|
||||
&.knob-l {
|
||||
margin-left: $knobM;
|
||||
@ -192,7 +170,7 @@
|
||||
.l-time-domain-selector {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: $interiorMargin;
|
||||
bottom: 46px;
|
||||
}
|
||||
|
||||
}
|
||||
@ -203,64 +181,174 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
.l-time-controller, .l-time-range-inputs-holder {
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.l-time-controller {
|
||||
|
||||
.l-time-domain-selector {
|
||||
select {
|
||||
height: 25px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.l-time-range-slider-holder, .l-time-range-ticks-holder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.time-range-start, .time-range-end, {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
.l-time-range-input {
|
||||
display: block;
|
||||
.s-btn {
|
||||
padding-right: 18px;
|
||||
white-space: nowrap;
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.l-time-range-inputs-elem {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.l-time-controller {
|
||||
height: 48px;
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
bottom: 24px;
|
||||
}
|
||||
|
||||
.l-time-domain-selector {
|
||||
width: 33%;
|
||||
bottom: -9px;
|
||||
}
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
.l-time-range-input {
|
||||
margin-bottom: 5px;
|
||||
.s-btn {
|
||||
width: 66%;
|
||||
}
|
||||
}
|
||||
.l-time-range-inputs-elem {
|
||||
&.ui-symbol {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.lbl {
|
||||
width: 33%;
|
||||
right: 0px;
|
||||
top: 5px;
|
||||
display: block;
|
||||
height: 25px;
|
||||
margin: 0;
|
||||
line-height: 25px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@include tablet {
|
||||
.l-time-controller {
|
||||
height: 17px;
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
bottom: -7px;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.l-time-domain-selector {
|
||||
width: 23%;
|
||||
right: -4px;
|
||||
bottom: -10px;
|
||||
}
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
.l-time-range-input {
|
||||
float: left;
|
||||
.s-btn {
|
||||
width: 100%;
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include tabletLandscape {
|
||||
.l-time-controller {
|
||||
height: 17px;
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
bottom: -7px;
|
||||
}
|
||||
|
||||
.l-time-domain-selector {
|
||||
width: 23%;
|
||||
right: auto;
|
||||
bottom: -10px;
|
||||
left: 391px;
|
||||
}
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
.l-time-range-inputs-elem {
|
||||
&.ui-symbol, &.lbl {
|
||||
display: block;
|
||||
float: left;
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pane-tree-hidden .l-time-controller {
|
||||
.l-time-domain-selector {
|
||||
left: 667px;
|
||||
}
|
||||
.l-time-range-inputs-holder {
|
||||
padding-left: 277px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@include tabletPortrait {
|
||||
.l-time-controller {
|
||||
height: 17px;
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
bottom: -7px;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.l-time-domain-selector {
|
||||
width: 23%;
|
||||
right: -4px;
|
||||
bottom: -10px;
|
||||
}
|
||||
|
||||
.l-time-range-inputs-holder {
|
||||
.l-time-range-input {
|
||||
width: 38%;
|
||||
float: left;
|
||||
}
|
||||
.l-time-range-inputs-elem {
|
||||
&.ui-symbol, &.lbl {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
|
||||
.holder.holder-treeview-elements {
|
||||
top: $bodyMargin;
|
||||
right: 0;
|
||||
bottom: $interiorMargin;
|
||||
bottom: $bodyMargin;
|
||||
left: $bodyMargin;
|
||||
.create-btn-holder {
|
||||
&.s-status-editing {
|
||||
@ -215,17 +215,17 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
|
||||
left: 0;
|
||||
.holder-object {
|
||||
top: $bodyMargin;
|
||||
bottom: $interiorMargin;
|
||||
bottom: $bodyMargin;
|
||||
}
|
||||
.holder-inspector {
|
||||
top: $bodyMargin;
|
||||
bottom: $interiorMargin;
|
||||
bottom: $bodyMargin;
|
||||
left: $bodyMargin;
|
||||
right: $bodyMargin;
|
||||
}
|
||||
.holder-elements {
|
||||
top: 0;
|
||||
bottom: $interiorMargin;
|
||||
bottom: $bodyMargin;
|
||||
left: $bodyMargin;
|
||||
right: $bodyMargin;
|
||||
}
|
||||
|
@ -19,17 +19,18 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span ng-controller="DateTimeFieldController">
|
||||
<span class="s-btn"
|
||||
ng-controller="DateTimeFieldController">
|
||||
<input type="text"
|
||||
ng-model="textValue"
|
||||
ng-blur="restoreTextValue(); ngBlur()"
|
||||
ng-class="{
|
||||
error: textInvalid ||
|
||||
(structure.validate &&
|
||||
!structure.validate(ngModel[field])),
|
||||
'picker-icon': structure.format === 'utc' || !structure.format
|
||||
!structure.validate(ngModel[field]))
|
||||
}">
|
||||
</input><a class="ui-symbol icon icon-calendar"
|
||||
</input>
|
||||
<a class="ui-symbol icon icon-calendar"
|
||||
ng-if="structure.format === 'utc' || !structure.format"
|
||||
ng-click="picker.active = !picker.active">
|
||||
</a>
|
||||
@ -37,7 +38,8 @@
|
||||
<div mct-click-elsewhere="picker.active = false">
|
||||
<mct-control key="'datetime-picker'"
|
||||
ng-model="pickerModel"
|
||||
field="'value'">
|
||||
field="'value'"
|
||||
options="{ hours: true }">
|
||||
</mct-control>
|
||||
</div>
|
||||
</mct-popup>
|
||||
|
@ -19,43 +19,42 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div ng-controller="TimeRangeController as trCtrl" class="l-flex-col">
|
||||
<form class="l-time-range-inputs-holder l-flex-row flex-elem"
|
||||
<div ng-controller="TimeRangeController as trCtrl">
|
||||
<form class="l-time-range-inputs-holder"
|
||||
ng-submit="trCtrl.updateBoundsFromForm()">
|
||||
<span class="l-time-range-inputs-elem ui-symbol type-icon flex-elem">C</span>
|
||||
<span class="l-time-range-inputs-elem t-inputs-w l-flex-row flex-elem">
|
||||
<span class="l-time-range-input-w flex-elem">
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: parameters.format,
|
||||
validate: trCtrl.validateStart
|
||||
}"
|
||||
ng-model="formModel"
|
||||
ng-blur="trCtrl.updateBoundsFromForm()"
|
||||
field="'start'"
|
||||
class="time-range-start">
|
||||
</mct-control>
|
||||
</span>
|
||||
|
||||
<span class="l-time-range-inputs-elem lbl flex-elem">to</span>
|
||||
|
||||
<span class="l-time-range-input-w flex-elem" ng-controller="ToggleController as t2">
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: parameters.format,
|
||||
validate: trCtrl.validateEnd
|
||||
}"
|
||||
ng-model="formModel"
|
||||
ng-blur="trCtrl.updateBoundsFromForm()"
|
||||
field="'end'"
|
||||
class="time-range-end">
|
||||
</mct-control>
|
||||
</span>
|
||||
<span class="l-time-range-inputs-elem ui-symbol type-icon">C</span>
|
||||
<span class="l-time-range-input">
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: parameters.format,
|
||||
validate: trCtrl.validateStart
|
||||
}"
|
||||
ng-model="formModel"
|
||||
ng-blur="trCtrl.updateBoundsFromForm()"
|
||||
field="'start'"
|
||||
class="time-range-start">
|
||||
</mct-control>
|
||||
</span>
|
||||
|
||||
<span class="l-time-range-inputs-elem lbl">to</span>
|
||||
|
||||
<span class="l-time-range-input" ng-controller="ToggleController as t2">
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: parameters.format,
|
||||
validate: trCtrl.validateEnd
|
||||
}"
|
||||
ng-model="formModel"
|
||||
ng-blur="trCtrl.updateBoundsFromForm()"
|
||||
field="'end'"
|
||||
class="time-range-end">
|
||||
</mct-control>
|
||||
</span>
|
||||
|
||||
<input type="submit" class="hidden">
|
||||
</form>
|
||||
|
||||
<div class="l-time-range-slider-holder flex-elem">
|
||||
<div class="l-time-range-slider-holder">
|
||||
<div class="l-time-range-slider">
|
||||
<div class="slider"
|
||||
mct-resize="spanWidth = bounds.width">
|
||||
@ -86,7 +85,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="l-time-range-ticks-holder flex-elem">
|
||||
<div class="l-time-range-ticks-holder">
|
||||
<div class="l-time-range-ticks">
|
||||
<div
|
||||
ng-repeat="tick in ticks track by $index"
|
||||
|
@ -38,7 +38,7 @@ define(
|
||||
* @param $document Angular's jqLite-wrapped document element
|
||||
* @param {string} activeTheme the theme in use
|
||||
*/
|
||||
function StyleSheetLoader(stylesheets, $document, activeTheme, assetPath) {
|
||||
function StyleSheetLoader(stylesheets, $document, activeTheme) {
|
||||
var head = $document.find('head'),
|
||||
document = $document[0];
|
||||
|
||||
@ -47,7 +47,6 @@ define(
|
||||
// Create a link element, and construct full path
|
||||
var link = document.createElement('link'),
|
||||
path = [
|
||||
assetPath,
|
||||
stylesheet.bundle.path,
|
||||
stylesheet.bundle.resources,
|
||||
stylesheet.stylesheetUrl
|
||||
|
@ -49,7 +49,10 @@ define(
|
||||
position = [rect.left, rect.top],
|
||||
popup = popupService.display(div, position);
|
||||
|
||||
div.addClass('t-popup');
|
||||
// TODO: Handle in CSS;
|
||||
// https://github.com/nasa/openmctweb/issues/298
|
||||
div.css('z-index', 75);
|
||||
|
||||
transclude(function (clone) {
|
||||
div.append(clone);
|
||||
});
|
||||
|
@ -24,15 +24,7 @@ define(
|
||||
["../../src/directives/MCTPopup"],
|
||||
function (MCTPopup) {
|
||||
|
||||
var JQLITE_METHODS = [
|
||||
"on",
|
||||
"off",
|
||||
"find",
|
||||
"parent",
|
||||
"css",
|
||||
"addClass",
|
||||
"append"
|
||||
];
|
||||
var JQLITE_METHODS = ["on", "off", "find", "parent", "css", "append"];
|
||||
|
||||
describe("The mct-popup directive", function () {
|
||||
var mockCompile,
|
||||
|
@ -38,6 +38,7 @@ define(
|
||||
function InfoGestureButton($document, agentService, infoService, element, domainObject) {
|
||||
var dismissBubble,
|
||||
touchPosition,
|
||||
scopeOff,
|
||||
body = $document.find('body');
|
||||
|
||||
function trackPosition(event) {
|
||||
@ -93,6 +94,10 @@ define(
|
||||
element.on('click', showBubble);
|
||||
}
|
||||
|
||||
// Also make sure we dismiss bubble if representation is destroyed
|
||||
// before the mouse actually leaves it
|
||||
scopeOff = element.scope().$on('$destroy', hideBubble);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Detach any event handlers associated with this gesture.
|
||||
@ -104,6 +109,7 @@ define(
|
||||
hideBubble();
|
||||
// ...and detach listeners
|
||||
element.off('click', showBubble);
|
||||
scopeOff();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -137,11 +137,6 @@ define(
|
||||
);
|
||||
});
|
||||
|
||||
// https://github.com/nasa/openmct/issues/948
|
||||
it("does not try to access scope", function () {
|
||||
expect(mockElement.scope).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -32,12 +32,11 @@ $sliderColorBase: $colorKey;
|
||||
$sliderColorRangeHolder: rgba(black, 0.1);
|
||||
$sliderColorRange: rgba($sliderColorBase, 0.3);
|
||||
$sliderColorRangeHov: rgba($sliderColorBase, 0.5);
|
||||
$sliderColorKnob: $sliderColorBase;
|
||||
$sliderColorKnobHov: pullForward($sliderColorKnob, $ltGamma);
|
||||
$sliderColorKnob: rgba($sliderColorBase, 0.6);
|
||||
$sliderColorKnobHov: $sliderColorBase;
|
||||
$sliderColorRangeValHovBg: rgba($sliderColorBase, 0.1);
|
||||
$sliderColorRangeValHovFg: $colorKeyFg;
|
||||
$sliderKnobW: 15px;
|
||||
$sliderKnobR: 2px;
|
||||
$sliderKnobW: nth($ueTimeControlH,2)/2;
|
||||
$timeControllerToiLineColor: #00c2ff;
|
||||
$timeControllerToiLineColorHov: #fff;
|
||||
|
||||
@ -70,10 +69,8 @@ $colorCreateMenuText: $colorMenuFg;
|
||||
$colorCheck: $colorKey;
|
||||
$colorFormRequired: $colorAlt1;
|
||||
$colorFormValid: #33cc33;
|
||||
$colorFormError: #990000;
|
||||
$colorFormError: #cc0000;
|
||||
$colorFormInvalid: #ff3300;
|
||||
$colorFormFieldErrorBg: $colorFormError;
|
||||
$colorFormFieldErrorFg: rgba(#fff, 0.6);
|
||||
$colorFormLines: rgba(#fff, 0.1);
|
||||
$colorFormSectionHeader: rgba(#fff, 0.1);
|
||||
$colorInputBg: rgba(#000, 0.1);
|
||||
|
@ -32,12 +32,11 @@ $sliderColorBase: $colorKey;
|
||||
$sliderColorRangeHolder: rgba(black, 0.07);
|
||||
$sliderColorRange: rgba($sliderColorBase, 0.2);
|
||||
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
|
||||
$sliderColorKnob: pushBack($sliderColorBase, 20%);
|
||||
$sliderColorKnob: rgba($sliderColorBase, 0.5);
|
||||
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
|
||||
$sliderColorRangeValHovBg: $sliderColorRange;
|
||||
$sliderColorRangeValHovBg: $sliderColorRange; //rgba($sliderColorBase, 0.1);
|
||||
$sliderColorRangeValHovFg: $colorBodyFg;
|
||||
$sliderKnobW: 15px;
|
||||
$sliderKnobR: 2px;
|
||||
$sliderKnobW: nth($ueTimeControlH,2)/2;
|
||||
$timeControllerToiLineColor: $colorBodyFg;
|
||||
$timeControllerToiLineColorHov: #0052b5;
|
||||
|
||||
@ -70,10 +69,8 @@ $colorCreateMenuText: $colorBodyFg;
|
||||
$colorCheck: $colorKey;
|
||||
$colorFormRequired: $colorKey;
|
||||
$colorFormValid: #33cc33;
|
||||
$colorFormError: #990000;
|
||||
$colorFormError: #cc0000;
|
||||
$colorFormInvalid: #ff2200;
|
||||
$colorFormFieldErrorBg: $colorFormError;
|
||||
$colorFormFieldErrorFg: rgba(#fff, 0.6);
|
||||
$colorFormLines: rgba(#000, 0.1);
|
||||
$colorFormSectionHeader: rgba(#000, 0.05);
|
||||
$colorInputBg: $colorGenBg;
|
||||
|
@ -42,19 +42,11 @@ define(
|
||||
function addWorker(worker) {
|
||||
var key = worker.key;
|
||||
if (!workerUrls[key]) {
|
||||
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);
|
||||
}
|
||||
workerUrls[key] = [
|
||||
worker.bundle.path,
|
||||
worker.bundle.sources,
|
||||
worker.scriptUrl
|
||||
].join("/");
|
||||
sharedWorkers[key] = worker.shared;
|
||||
}
|
||||
}
|
||||
|
150
platform/features/conductor-redux/src/README.md
Normal file
150
platform/features/conductor-redux/src/README.md
Normal file
@ -0,0 +1,150 @@
|
||||
## Notes
|
||||
API is notional for now, based on use-cases identified below. Possible the
|
||||
use cases are not sufficient, so please include in comments
|
||||
any other use cases you'd like to see.
|
||||
|
||||
Plan now is to start building out test suite for the use cases identified below
|
||||
in order to get the API functional. Need to discuss how UI aspects of timeline will be implemented.
|
||||
Propose in place refactoring of existing timeline rather than starting again.
|
||||
|
||||
Some caveats / open questions
|
||||
* I don't understand the use case shown on page 52 of UI sketches. It shows RT/FT, with deltas,
|
||||
with inner interval unlocked. Not sure what result would be, has inner end switched to fixed?
|
||||
Also example on page 55 in real-time where inner end < now. Is there a use case for this? Semantically, it's saying
|
||||
show me real time, but stale data. Why would a user want this? My feeling is that if the inner
|
||||
OR outer ends are moved behind NOW in real-time mode then you drop into historical mode.
|
||||
* For the API itself, have ignored question of how it's namespaced / exposed.
|
||||
Examples assume global namespace and availability from window object.
|
||||
For now API implemented as standard standard Require JS AMDs. Could attach
|
||||
to window from bundle.js. Perhaps attaching to window not best approach though...
|
||||
* Have not included validation (eg. start time < end time) or any other
|
||||
business logic such as what happens when outer interval gets dragged
|
||||
within range of inner interval. Focus is on teasing out the public API
|
||||
right now.
|
||||
* Time systems are vague right now also, I don't know how they're going
|
||||
to work or whether any API has yet been specified.
|
||||
* Not clear on the differences between real-time and follow-time as it
|
||||
concerns the time conductor? For now the API has an end bounds mode
|
||||
of FOLLOW which automatically tracks current time, and a start time mode
|
||||
of RELATIVE. I can envision a real-time plot that is not in follow time mode,
|
||||
but not sure what implication is for time conductor itself and how it
|
||||
differs from an historical plot?
|
||||
* Should the time conductor be responsible for choosing time system / domain? Currently
|
||||
it is.
|
||||
|
||||
## Use Cases
|
||||
1. Historical session is loaded and system sets time bounds on conductor
|
||||
2. Real-time session is loaded, setting custom start and end deltas
|
||||
3. User changes time of interest
|
||||
4. Plot controller listens for change to TOI
|
||||
5. Plot Controller updated on tick
|
||||
6. Plot Controller updated when user changes bounds (eg to reset plot zoom)
|
||||
7. Conductor controller needs to update bounds and mode on TC when user changes bounds
|
||||
|
||||
### Additional possible use-cases
|
||||
1. Telemetry adapter wants to indicate presence of data at a particular time
|
||||
2. Time conductor controller wants to paint map of data availability.
|
||||
|
||||
These use-cases could be features of the TimeConductor, but perhaps makes
|
||||
sense to make knowledge of data availability the sole preserve of telemetry
|
||||
adapters, not TimeConductor itself. Adapters will be ultimately responsible
|
||||
for providing these data so doesn't make much sense to duplicate elsewhere.
|
||||
The TimeConductorController - which knows tick interval on scale (which
|
||||
TimeConductor API does not) - could simply request data availability from
|
||||
telemetry API and paint it into the Time Conductor UI
|
||||
|
||||
## Example implementations of use cases
|
||||
### 1. Real time session is loaded (outside of TC) and system sets time bounds on conductor
|
||||
``` javascript
|
||||
function loadSession(telemetryMetadata) {
|
||||
var tc = MCT.conductor;
|
||||
tc.timeSystem(session.timeSystem());
|
||||
|
||||
//Set start and end modes to fixed date
|
||||
tc.mode(new FixedMode());
|
||||
|
||||
//Set both inner and outer bounds
|
||||
tc.bounds({start: session.start(), end: session.end()});
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Real-time session is loaded (outside of TC), setting custom start and end deltas
|
||||
``` javascript
|
||||
function loadSession(session) {
|
||||
var tc = MCT.conductor;
|
||||
var FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||
|
||||
// Could have a central ticking source somewhere, or connect to a
|
||||
// remote ticking source. Should not need to be done manually with
|
||||
// each session load. Actually not quite sure what to do with tick
|
||||
// sources yet.
|
||||
|
||||
var tickSource = new LocalClock();
|
||||
tickSource.attach(); // Start ticking
|
||||
|
||||
var mode = new RealtimeMode({
|
||||
startDelta: FIFTEEN_MINUTES,
|
||||
endDelta: 0 // End delta offset is from "Now" in the time system
|
||||
});
|
||||
|
||||
tc.timeSystem(session.timeSystem());
|
||||
|
||||
// Set mode to realtime, specifying a tick source
|
||||
tc.mode(mode);
|
||||
|
||||
//No need to set bounds manually, will be established by mode and the deltas specified
|
||||
}
|
||||
```
|
||||
|
||||
### 3. User changes time of interest
|
||||
```javascript
|
||||
//Somewhere in the TimeConductorController...
|
||||
function changeTOI(newTime) {
|
||||
MCT.conductor.timeOfInterest(newTime);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Plot controller listens for change to TOI
|
||||
```javascript
|
||||
// toi is attribute of Time Conductor object. Add a listener to the time
|
||||
// conductor to be alerted to changes in value
|
||||
|
||||
// Time conductor is an event emitter, listen to timeOfInterest event
|
||||
MCT.conductor.on("timeOfInterest", function (timeOfInterest) {
|
||||
plot.setTimeOfInterest(timeOfInterest);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Plot Controller updated on tick
|
||||
``` javascript
|
||||
MCT.conductor.on("bounds", function (bounds) {
|
||||
plotUpdater.setDomainBounds(bounds.start, bounds.end);
|
||||
});
|
||||
```
|
||||
|
||||
### 6. Plot Controller updated when user changes bounds (eg to reset plot zoom)
|
||||
``` javascript
|
||||
MCT.conductor.on("refresh", function (conductor) {
|
||||
plot.setBounds(conductor.bounds());
|
||||
//Also need to reset tick labels. if time system has changed.
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 7. Conductor controller needs to update bounds and mode on TC when user changes bounds
|
||||
```javascript
|
||||
var tc = MCT.conductor;
|
||||
|
||||
function dragStartHandle(finalPos){
|
||||
var bounds = tc.bounds();
|
||||
bounds.start = positionToTime(finalPos)
|
||||
tc.bounds(bounds);
|
||||
}
|
||||
|
||||
function dragEndHandle(finalPos){
|
||||
var bounds = tc.bounds();
|
||||
bounds.end = positionToTime(finalPos);
|
||||
tc.bounds(bounds);
|
||||
}
|
||||
|
||||
```
|
148
platform/features/conductor-redux/src/TimeConductor.js
Normal file
148
platform/features/conductor-redux/src/TimeConductor.js
Normal file
@ -0,0 +1,148 @@
|
||||
/*****************************************************************************
|
||||
* 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",
|
||||
"./UTCTimeSystem",
|
||||
"./modes/RelativeMode",
|
||||
"./modes/FixedMode"
|
||||
], function (EventEmitter, UTCTimeSystem, RelativeMode, FixedMode) {
|
||||
|
||||
/**
|
||||
* A class for setting and querying time conductor state.
|
||||
*
|
||||
* @event TimeConductor:refresh The time conductor has changed, and its values should be re-queried
|
||||
* @event TimeConductor:bounds The start time, end time, or both have been updated
|
||||
* @event TimeConductor:timeOfInterest The Time of Interest has moved.
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductor() {
|
||||
EventEmitter.call(this);
|
||||
|
||||
//The Time System
|
||||
this.system = new UTCTimeSystem();
|
||||
//The Time Of Interest
|
||||
this.toi = undefined;
|
||||
|
||||
this.bounds = {
|
||||
start: undefined,
|
||||
end: undefined
|
||||
};
|
||||
|
||||
//Default to fixed mode
|
||||
this.modeVal = new FixedMode();
|
||||
}
|
||||
|
||||
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 ||
|
||||
!bounds.end ||
|
||||
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;
|
||||
};
|
||||
|
||||
function throwOnError(validationResult) {
|
||||
if (validationResult !== true) {
|
||||
throw validationResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode of the time conductor.
|
||||
* @param {FixedMode | RealtimeMode} newMode
|
||||
* @fires TimeConductor#refresh
|
||||
* @returns {FixedMode | RealtimeMode}
|
||||
*/
|
||||
TimeConductor.prototype.mode = function (newMode) {
|
||||
if (arguments.length > 0) {
|
||||
this.modeVal = newMode;
|
||||
this.emit('refresh', this);
|
||||
newMode.initialize();
|
||||
}
|
||||
return this.modeVal;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
/**
|
||||
* Set the start and end time of the time conductor. Basic validation of bounds is performed.
|
||||
*
|
||||
* @param {TimeConductorBounds} newBounds
|
||||
* @throws {string} Validation error
|
||||
* @fires TimeConductor#bounds
|
||||
* @returns {TimeConductorBounds}
|
||||
*/
|
||||
TimeConductor.prototype.bounds = function (newBounds) {
|
||||
if (arguments.length > 0) {
|
||||
throwOnError(this.validateBounds(newBounds));
|
||||
this.bounds = newBounds;
|
||||
this.emit('bounds', this.bounds);
|
||||
}
|
||||
return this.bounds;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the time system of the TimeConductor. Time systems determine units, epoch, and other aspects of time representation.
|
||||
* @param newTimeSystem
|
||||
* @fires TimeConductor#refresh
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
*/
|
||||
TimeConductor.prototype.timeSystem = function (newTimeSystem) {
|
||||
if (arguments.length > 0) {
|
||||
this.system = newTimeSystem;
|
||||
this.emit('refresh', this);
|
||||
}
|
||||
return this.system;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param newTOI
|
||||
* @returns {*}
|
||||
*/
|
||||
TimeConductor.prototype.timeOfInterest = function (newTOI) {
|
||||
if (arguments.length > 0) {
|
||||
this.toi = newTOI;
|
||||
this.emit('toi');
|
||||
}
|
||||
return this.toi;
|
||||
};
|
||||
|
||||
return TimeConductor;
|
||||
});
|
69
platform/features/conductor-redux/src/TimeConductorBounds.js
Normal file
69
platform/features/conductor-redux/src/TimeConductorBounds.js
Normal file
@ -0,0 +1,69 @@
|
||||
/*****************************************************************************
|
||||
* 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 TimeConductorBounds(conductor) {
|
||||
this.listeners = [];
|
||||
this.start = new TimeConductorLimit(this);
|
||||
this.end = new TimeConductorLimit(this);
|
||||
this.conductor = conductor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeConductorBounds.prototype.notify = function (eventType) {
|
||||
eventType = eventType || this.conductor.EventTypes.EITHER;
|
||||
|
||||
this.listeners.forEach(function (element){
|
||||
if (element.eventType & eventType){
|
||||
element.listener(this);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Listen for changes to the bounds
|
||||
* @param listener a callback function to be called when the bounds change. The bounds object will be passed into
|
||||
* the function (ie. 'this')
|
||||
* @param eventType{TimeConductorBounds.EventType} The event type to listen to, ie. system, user, or Both. If not provied, will default to both.
|
||||
* @returns {Function} an 'unlisten' function
|
||||
*/
|
||||
TimeConductorBounds.prototype.listen = function (listener, eventType) {
|
||||
var self = this,
|
||||
wrappedListener = {
|
||||
listener: listener,
|
||||
eventType: eventType
|
||||
};
|
||||
this.listeners.push(wrappedListener);
|
||||
return function () {
|
||||
self.listeners = self.listeners.filter(function (element){
|
||||
return element !== wrappedListener;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return TimeConductorBounds;
|
||||
});
|
71
platform/features/conductor-redux/src/TimeConductorLimit.js
Normal file
71
platform/features/conductor-redux/src/TimeConductorLimit.js
Normal file
@ -0,0 +1,71 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
|
||||
/**
|
||||
* Defines a limit object for a time conductor bounds, ie. start or end values. Holds time and delta values.
|
||||
*
|
||||
* TODO: Calculation of time from delta. Should probably be done from the 'tick' function at a higher level,
|
||||
* which has start and end values in scope to do calculations.
|
||||
* @param listener
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductorLimit(listener) {
|
||||
this.deltaVal = undefined;
|
||||
this.timeVal = undefined;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the start value for the bounds. If newVal is provided, will set the start value. May only set delta in
|
||||
* RELATIVE mode.
|
||||
* @param {Number} [newVal] a time in ms. A negative value describes a time in the past, positive in the future. A
|
||||
* start time cannot have a positive delta offset, but an end time can.
|
||||
* @param {TimeConductor.EventTypes} [eventType=TimeConductor.EventTypes.EITHER] The type of event (User, System, or
|
||||
* Either)
|
||||
* @returns {Number} the start date (in milliseconds since some epoch, depending on time system)
|
||||
*/
|
||||
TimeConductorLimit.prototype.delta = function (newVal, eventType) {
|
||||
if (arguments.length > 0) {
|
||||
this.deltaVal = newVal;
|
||||
this.listener.notify(eventType);
|
||||
}
|
||||
return this.deltaVal;
|
||||
};
|
||||
/**
|
||||
* Get or set the end value for the bounds. If newVal is provided, will set the end value. May only set time in FIXED
|
||||
* mode
|
||||
* @param {Number} [newVal] A time in ms relative to time system epoch.
|
||||
* @param {TimeConductor.EventTypes} [eventType=TimeConductor.EventTypes.EITHER] The type of event (User, System, or Either)
|
||||
* @returns {Number} the end date (in milliseconds since some epoch, depending on time system)
|
||||
*/
|
||||
TimeConductorLimit.prototype.time = function (newVal, eventType) {
|
||||
if (arguments.length > 0) {
|
||||
this.timeVal = newVal;
|
||||
this.listener.notify(eventType);
|
||||
}
|
||||
return this.timeVal;
|
||||
};
|
||||
|
||||
return TimeConductorLimit;
|
||||
});
|
@ -20,30 +20,10 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['../../../platform/core/src/objects/DomainObjectImpl'],
|
||||
function (DomainObjectImpl) {
|
||||
define([
|
||||
], function () {
|
||||
function FixedMode(options) {
|
||||
|
||||
/**
|
||||
* Overrides platform version of instantiate, passes Id with model such
|
||||
* that capability detection can utilize new format domain objects.
|
||||
*/
|
||||
function Instantiate(
|
||||
capabilityService,
|
||||
identifierService,
|
||||
cacheService
|
||||
) {
|
||||
return function (model, id) {
|
||||
id = id || identifierService.generate();
|
||||
var old_id = model.id;
|
||||
model.id = id;
|
||||
var capabilities = capabilityService.getCapabilities(model);
|
||||
model.id = old_id;
|
||||
cacheService.put(id, model);
|
||||
return new DomainObjectImpl(id, model, capabilities);
|
||||
};
|
||||
}
|
||||
|
||||
return Instantiate;
|
||||
}
|
||||
);
|
||||
return FixedMode;
|
||||
});
|
71
platform/features/conductor-redux/src/modes/RealtimeMode.js
Normal file
71
platform/features/conductor-redux/src/modes/RealtimeMode.js
Normal file
@ -0,0 +1,71 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
|
||||
/**
|
||||
* Class representing the real-time mode of the Time Conductor. In this mode,
|
||||
* the bounds are updated automatically based on a timing source.
|
||||
*
|
||||
* @param options
|
||||
* @constructor
|
||||
*/
|
||||
function RealtimeMode(options) {
|
||||
this.startDelta = options.startDelta;
|
||||
this.endDelta = options.endDelta;
|
||||
this.tickSource = options.tickSource;
|
||||
this.system = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.prototype.initialize = function (conductor) {
|
||||
var self = this;
|
||||
/**
|
||||
* Deltas can be specified for start and end. An end delta will mean
|
||||
* that the end bound is always in the future by 'endDelta' units
|
||||
*/
|
||||
this.startDelta = this.startDelta || conductor.timeSystem().DEFAULT_DELTA;
|
||||
this.endDelta = this.endDelta || 0;
|
||||
|
||||
function setBounds() {
|
||||
var now = conductor.timeSystem().now();
|
||||
conductor.bounds({
|
||||
start: now - self.startDelta,
|
||||
end: now + self.endDelta
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If a tick source is specified, listen for ticks
|
||||
*/
|
||||
if (this.tickSource) {
|
||||
this.tickSource.on("tick", setBounds);
|
||||
}
|
||||
//Set initial bounds
|
||||
setBounds();
|
||||
};
|
||||
|
||||
return RealtimeMode;
|
||||
});
|
@ -19,32 +19,32 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
'./LegacyObjectAPIInterceptor',
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
LegacyObjectAPIInterceptor,
|
||||
legacyRegistry
|
||||
) {
|
||||
legacyRegistry.register('src/api/objects', {
|
||||
name: 'Object API',
|
||||
description: 'The public Objects API',
|
||||
extensions: {
|
||||
components: [
|
||||
{
|
||||
provides: "objectService",
|
||||
type: "decorator",
|
||||
priority: "mandatory",
|
||||
implementation: LegacyObjectAPIInterceptor,
|
||||
depends: [
|
||||
"roots[]",
|
||||
"instantiate",
|
||||
"topic"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
"./TimingSource"
|
||||
], function (TimingSource) {
|
||||
|
||||
/**
|
||||
* A timing source that 'ticks' when new data is available
|
||||
* @implements TimingSource
|
||||
* @constructor
|
||||
*/
|
||||
function DataAvailabilityTicker(){
|
||||
TimingSource.call(this);
|
||||
}
|
||||
|
||||
DataAvailabilityTicker.prototype = Object.create(TimingSource.prototype);
|
||||
|
||||
/**
|
||||
* Registers an event listener to listen for data availability at telemetry source
|
||||
*/
|
||||
DataAvailabilityTicker.prototype.attach = function () {};
|
||||
|
||||
/**
|
||||
* Unregisters event listeners, seasing tick events.
|
||||
*/
|
||||
DataAvailabilityTicker.prototype.detach = function () {};
|
||||
|
||||
DataAvailabilityTicker.prototype.attached = function () {}
|
||||
|
||||
});
|
74
platform/features/conductor-redux/src/timing/LocalClock.js
Normal file
74
platform/features/conductor-redux/src/timing/LocalClock.js
Normal file
@ -0,0 +1,74 @@
|
||||
/*****************************************************************************
|
||||
* 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([
|
||||
"./TimingSource"
|
||||
], function (TimingSource) {
|
||||
|
||||
var ONE_SECOND = 1 * 1000;
|
||||
|
||||
/**
|
||||
* A clock that ticks at the given interval (given in ms).
|
||||
*
|
||||
* @implements TimingSource
|
||||
* @constructor
|
||||
*/
|
||||
function LocalClock(interval){
|
||||
TimingSource.call(this);
|
||||
|
||||
this.interval = interval;
|
||||
this.intervalHandle = undefined;
|
||||
}
|
||||
|
||||
LocalClock.prototype = Object.create(TimingSource.prototype);
|
||||
|
||||
/**
|
||||
* Start the clock ticking. Ticks can be listened to by registering
|
||||
* listeners of the "tick" event
|
||||
*/
|
||||
LocalClock.prototype.attach = function () {
|
||||
function tick() {
|
||||
this.emit("tick");
|
||||
}
|
||||
this.stop();
|
||||
|
||||
this.intervalHandle = setInterval(this.bind(this), this.interval || ONE_SECOND);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop the currently running clock. "tick" events will no longer be emitted
|
||||
*/
|
||||
LocalClock.prototype.detach = function () {
|
||||
if (this.intervalHandle) {
|
||||
clearInterval(this.intervalHandle);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {boolean} true if the clock is currently running
|
||||
*/
|
||||
LocalClock.prototype.attached = function () {
|
||||
return !!this.intervalHandle;
|
||||
}
|
||||
|
||||
|
||||
});
|
56
platform/features/conductor-redux/src/timing/TimingSource.js
Normal file
56
platform/features/conductor-redux/src/timing/TimingSource.js
Normal file
@ -0,0 +1,56 @@
|
||||
/*****************************************************************************
|
||||
* 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) {
|
||||
|
||||
/**
|
||||
* An interface defining a timing source. A timing source is a local or remote source of 'tick' events.
|
||||
* Could be used to tick when new data is received from a data source.
|
||||
* @interface
|
||||
* @constructor
|
||||
*/
|
||||
function TimingSource(){
|
||||
EventEmitter.call(this);
|
||||
}
|
||||
|
||||
TimingSource.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
/**
|
||||
* Attach to the timing source. If it's a local clock, will start a local timing loop. If remote, will connect to
|
||||
* remote source. If event driven (eg. based on data availability) will attach an event listener to telemetry source.
|
||||
*/
|
||||
TimingSource.prototype.attach = function () {};
|
||||
|
||||
/**
|
||||
* Detach from the timing source
|
||||
*/
|
||||
TimingSource.prototype.detach = function () {};
|
||||
|
||||
/**
|
||||
* @returns {boolean} true if current attached to timing source
|
||||
*/
|
||||
TimingSource.prototype.attached = function () {}
|
||||
|
||||
|
||||
});
|
@ -27,9 +27,6 @@ define([
|
||||
"./src/controllers/TableOptionsController",
|
||||
'../../commonUI/regions/src/Region',
|
||||
'../../commonUI/browse/src/InspectorRegion',
|
||||
"text!./res/templates/table-options-edit.html",
|
||||
"text!./res/templates/rt-table.html",
|
||||
"text!./res/templates/historical-table.html",
|
||||
"legacyRegistry"
|
||||
], function (
|
||||
MCTTable,
|
||||
@ -38,9 +35,6 @@ define([
|
||||
TableOptionsController,
|
||||
Region,
|
||||
InspectorRegion,
|
||||
tableOptionsEditTemplate,
|
||||
rtTableTemplate,
|
||||
historicalTableTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
/**
|
||||
@ -134,23 +128,23 @@ define([
|
||||
"name": "Historical Table",
|
||||
"key": "table",
|
||||
"glyph": "\ue604",
|
||||
"template": historicalTableTemplate,
|
||||
"templateUrl": "templates/historical-table.html",
|
||||
"needs": [
|
||||
"telemetry"
|
||||
],
|
||||
"delegation": true,
|
||||
"editable": false
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"name": "Real-time Table",
|
||||
"key": "rt-table",
|
||||
"glyph": "\ue620",
|
||||
"template": rtTableTemplate,
|
||||
"templateUrl": "templates/rt-table.html",
|
||||
"needs": [
|
||||
"telemetry"
|
||||
],
|
||||
"delegation": true,
|
||||
"editable": false
|
||||
"editable": true
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
@ -163,7 +157,7 @@ define([
|
||||
"representations": [
|
||||
{
|
||||
"key": "table-options-edit",
|
||||
"template": tableOptionsEditTemplate
|
||||
"templateUrl": "templates/table-options-edit.html"
|
||||
}
|
||||
],
|
||||
"stylesheets": [
|
||||
|
@ -127,16 +127,7 @@ define([
|
||||
14400000,
|
||||
28800000,
|
||||
43200000,
|
||||
86400000,
|
||||
86400000 * 2,
|
||||
86400000 * 5,
|
||||
86400000 * 10,
|
||||
86400000 * 20,
|
||||
86400000 * 30,
|
||||
86400000 * 60,
|
||||
86400000 * 120,
|
||||
86400000 * 240,
|
||||
86400000 * 365
|
||||
86400000
|
||||
],
|
||||
"width": 200
|
||||
}
|
||||
|
@ -1,22 +1,16 @@
|
||||
.l-timeline-gantt {
|
||||
min-width: 2px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: $timelineSwimlaneGanttVM; bottom: $timelineSwimlaneGanttVM;
|
||||
|
||||
.bar {
|
||||
@include ellipsize();
|
||||
height: $activityBarH;
|
||||
line-height: $activityBarH;
|
||||
line-height: $activityBarH + 2;
|
||||
padding: 0 $interiorMargin;
|
||||
|
||||
span {
|
||||
$iconW: 20px;
|
||||
@include absPosDefault();
|
||||
display: block;
|
||||
display: inline;
|
||||
&.s-activity-type {
|
||||
right: auto; width: $iconW;
|
||||
text-align: center;
|
||||
&.timeline {
|
||||
&:before {
|
||||
content:"S";
|
||||
@ -29,9 +23,7 @@
|
||||
}
|
||||
}
|
||||
&.s-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
left: $iconW;
|
||||
text-shadow: rgba(black, 0.1) 0 1px 2px;
|
||||
}
|
||||
&.duration {
|
||||
left: auto;
|
||||
@ -60,10 +52,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&.sm .bar span {
|
||||
// Hide icon and label if width is too small
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-mode .s-timeline-gantt,
|
||||
@ -71,7 +59,7 @@
|
||||
.handle {
|
||||
cursor: col-resize;
|
||||
&.mid {
|
||||
cursor: ew-resize;
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
}
|
@ -32,10 +32,20 @@
|
||||
}
|
||||
|
||||
.s-timeline-gantt {
|
||||
$br: $controlCr;
|
||||
.bar {
|
||||
color: $colorGanttBarFg;
|
||||
@include activityBg($colorGanttBarBg);
|
||||
border-radius: $br;
|
||||
box-shadow: $shdwGanttBar;
|
||||
&.expanded {
|
||||
@include border-top-radius($br);
|
||||
@include border-bottom-radius(0);
|
||||
}
|
||||
&.leaf {
|
||||
@include border-top-radius(0);
|
||||
@include border-bottom-radius($br);
|
||||
}
|
||||
.s-toggle {
|
||||
color: $colorGanttToggle;
|
||||
}
|
||||
|
@ -52,9 +52,6 @@
|
||||
// Tree area with item title
|
||||
right: auto; // Set this to auto and uncomment width below when additional tabular columns are added
|
||||
width: $timelineTabularTitleW;
|
||||
.l-swimlanes-holder {
|
||||
bottom: $scrollbarTrackSize;
|
||||
}
|
||||
}
|
||||
&.l-tabular-r {
|
||||
// Start, end, duration, activity modes columns
|
||||
@ -70,7 +67,6 @@
|
||||
&.l-timeline-gantt {
|
||||
.l-swimlanes-holder {
|
||||
@include scrollV(scroll);
|
||||
bottom: $scrollbarTrackSize;
|
||||
}
|
||||
}
|
||||
&.l-timeline-resource-legend {
|
||||
|
@ -20,7 +20,6 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="t-timeline-gantt l-timeline-gantt s-timeline-gantt"
|
||||
ng-class="{ sm: gantt.width(timespan, parameters.scroll, parameters.toPixels) < 25 }"
|
||||
title="{{model.name}}"
|
||||
ng-controller="TimelineGanttController as gantt"
|
||||
ng-style="timespan ? {
|
||||
|
@ -103,13 +103,6 @@
|
||||
<!-- TOP PANE GANTT BARS -->
|
||||
<div class="split-pane-component l-timeline-pane t-pane-h l-pane-top t-timeline-gantt l-timeline-gantt s-timeline-gantt">
|
||||
<div class="l-hover-btns-holder s-hover-btns-holder t-btns-zoom">
|
||||
<a class="t-btn l-btn s-btn"
|
||||
ng-click="zoomController.fit()"
|
||||
ng-show="true"
|
||||
title="Zoom to fit">
|
||||
<span class="ui-symbol icon zoom-in">I</span>
|
||||
</a>
|
||||
|
||||
<a class="t-btn l-btn s-btn"
|
||||
ng-click="zoomController.zoom(-1)"
|
||||
ng-show="true"
|
||||
@ -128,7 +121,7 @@
|
||||
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;" mct-scroll-x="scroll.x">
|
||||
<mct-include key="'timeline-ticks'"
|
||||
parameters="{
|
||||
fullWidth: timelineController.width(zoomController),
|
||||
fullWidth: zoomController.toPixels(zoomController.duration()),
|
||||
start: scroll.x,
|
||||
width: scroll.width,
|
||||
step: zoomController.toPixels(zoomController.zoom()),
|
||||
|
@ -97,8 +97,6 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch("configuration", swimlanePopulator.configure);
|
||||
|
||||
// Recalculate swimlane state on changes
|
||||
$scope.$watch("domainObject", swimlanePopulator.populate);
|
||||
|
||||
|
@ -32,26 +32,16 @@ define(
|
||||
var zoomLevels = ZOOM_CONFIGURATION.levels || [1000],
|
||||
zoomIndex = Math.floor(zoomLevels.length / 2),
|
||||
tickWidth = ZOOM_CONFIGURATION.width || 200,
|
||||
bounds = { x: 0, width: tickWidth },
|
||||
duration = 86400000; // Default duration in view
|
||||
|
||||
// Round a duration to a larger value, to ensure space for editing
|
||||
function roundDuration(value) {
|
||||
// Ensure there's always an extra day or so
|
||||
var tickCount = bounds.width / tickWidth,
|
||||
sz = zoomLevels[zoomLevels.length - 1] * tickCount;
|
||||
var sz = zoomLevels[zoomLevels.length - 1];
|
||||
value *= 1.25; // Add 25% padding to start
|
||||
return Math.ceil(value / sz) * sz;
|
||||
}
|
||||
|
||||
function toMillis(pixels) {
|
||||
return (pixels / tickWidth) * zoomLevels[zoomIndex];
|
||||
}
|
||||
|
||||
function toPixels(millis) {
|
||||
return tickWidth * millis / zoomLevels[zoomIndex];
|
||||
}
|
||||
|
||||
// Get/set zoom level
|
||||
function setZoomLevel(level) {
|
||||
if (!isNaN(level)) {
|
||||
@ -63,27 +53,20 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
function initializeZoomFromTimespan(timespan) {
|
||||
var timelineDuration = timespan.getDuration();
|
||||
zoomIndex = 0;
|
||||
while (toMillis(bounds.width) < timelineDuration &&
|
||||
zoomIndex < zoomLevels.length - 1) {
|
||||
zoomIndex += 1;
|
||||
}
|
||||
bounds.x = toPixels(timespan.getStart());
|
||||
}
|
||||
|
||||
function initializeZoom() {
|
||||
if ($scope.domainObject) {
|
||||
$scope.domainObject.useCapability('timespan')
|
||||
.then(initializeZoomFromTimespan);
|
||||
// Persist current zoom level
|
||||
function storeZoom() {
|
||||
var isEditMode = $scope.commit &&
|
||||
$scope.domainObject &&
|
||||
$scope.domainObject.hasCapability('editor') &&
|
||||
$scope.domainObject.getCapability('editor').inEditContext();
|
||||
if (isEditMode) {
|
||||
$scope.configuration = $scope.configuration || {};
|
||||
$scope.configuration.zoomLevel = zoomIndex;
|
||||
$scope.commit();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch("scroll", function (scroll) {
|
||||
bounds = scroll;
|
||||
});
|
||||
$scope.$watch("domainObject", initializeZoom);
|
||||
$scope.$watch("configuration.zoomLevel", setZoomLevel);
|
||||
|
||||
return {
|
||||
/**
|
||||
@ -100,29 +83,27 @@ define(
|
||||
zoom: function (amount) {
|
||||
// Update the zoom level if called with an argument
|
||||
if (arguments.length > 0 && !isNaN(amount)) {
|
||||
var center = this.toMillis(bounds.x + bounds.width / 2);
|
||||
setZoomLevel(zoomIndex + amount);
|
||||
bounds.x = this.toPixels(center) - bounds.width / 2;
|
||||
storeZoom(zoomIndex);
|
||||
}
|
||||
return zoomLevels[zoomIndex];
|
||||
},
|
||||
/**
|
||||
* Set the zoom level to fit the bounds of the timeline
|
||||
* being viewed.
|
||||
*/
|
||||
fit: initializeZoom,
|
||||
/**
|
||||
* Get the width, in pixels, of a specific time duration at
|
||||
* the current zoom level.
|
||||
* @returns {number} the number of pixels
|
||||
*/
|
||||
toPixels: toPixels,
|
||||
toPixels: function (millis) {
|
||||
return tickWidth * millis / zoomLevels[zoomIndex];
|
||||
},
|
||||
/**
|
||||
* Get the time duration, in milliseconds, occupied by the
|
||||
* width (specified in pixels) at the current zoom level.
|
||||
* @returns {number} the number of pixels
|
||||
*/
|
||||
toMillis: toMillis,
|
||||
toMillis: function (pixels) {
|
||||
return (pixels / tickWidth) * zoomLevels[zoomIndex];
|
||||
},
|
||||
/**
|
||||
* Get or set the current displayed duration. If used as a
|
||||
* setter, this will typically be rounded up to ensure extra
|
||||
|
@ -43,7 +43,8 @@ define(
|
||||
var swimlanes = [],
|
||||
start = Number.POSITIVE_INFINITY,
|
||||
end = Number.NEGATIVE_INFINITY,
|
||||
assigner,
|
||||
colors = (configuration.colors || {}),
|
||||
assigner = new TimelineColorAssigner(colors),
|
||||
lastDomainObject;
|
||||
|
||||
// Track extremes of start/end times
|
||||
@ -151,15 +152,8 @@ define(
|
||||
recalculateSwimlanes(lastDomainObject);
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
var colors = (configuration.colors || {});
|
||||
assigner = new TimelineColorAssigner(colors);
|
||||
configuration.colors = colors;
|
||||
recalculateSwimlanes(lastDomainObject);
|
||||
}
|
||||
|
||||
// Ensure colors are exposed in configuration
|
||||
initialize();
|
||||
configuration.colors = colors;
|
||||
|
||||
return {
|
||||
/**
|
||||
@ -194,15 +188,6 @@ define(
|
||||
*/
|
||||
end: function () {
|
||||
return end;
|
||||
},
|
||||
/**
|
||||
* Pass a new configuration object (to retrieve and store
|
||||
* swimlane configuration)
|
||||
* @param newConfig
|
||||
*/
|
||||
configure: function (newConfig) {
|
||||
configuration = newConfig;
|
||||
initialize();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -68,14 +68,6 @@ define(
|
||||
};
|
||||
}
|
||||
|
||||
function fireWatch(expr, value) {
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === expr) {
|
||||
call.args[1](value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
beforeEach(function () {
|
||||
var mockA, mockB, mockUtilization, mockPromise, mockGraph, testCapabilities;
|
||||
@ -149,15 +141,9 @@ define(
|
||||
expect(mockScope.scroll.y).toEqual(0);
|
||||
});
|
||||
|
||||
it("watches for a configuration object", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"configuration",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("repopulates when modifications are made", function () {
|
||||
var fnWatchCall;
|
||||
var fnWatchCall,
|
||||
strWatchCall;
|
||||
|
||||
// Find the $watch that was given a function
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
@ -165,11 +151,16 @@ define(
|
||||
// white-box: we know the first call is
|
||||
// the one we're looking for
|
||||
fnWatchCall = fnWatchCall || call;
|
||||
} else if (typeof call.args[0] === 'string') {
|
||||
strWatchCall = strWatchCall || call;
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure string watch was for domainObject
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
expect(strWatchCall.args[0]).toEqual('domainObject');
|
||||
// Initially populate
|
||||
strWatchCall.args[1](mockDomainObject);
|
||||
|
||||
// There should be to swimlanes
|
||||
expect(controller.swimlanes().length).toEqual(2);
|
||||
|
||||
@ -191,23 +182,23 @@ define(
|
||||
// order of $watch calls in TimelineController.
|
||||
|
||||
// Initially populate
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
mockScope.$watch.calls[0].args[1](mockDomainObject);
|
||||
|
||||
// Verify precondition - no graphs
|
||||
expect(controller.graphs().length).toEqual(0);
|
||||
|
||||
// Execute the watch function for graph state
|
||||
tmp = mockScope.$watch.calls[3].args[0]();
|
||||
tmp = mockScope.$watch.calls[2].args[0]();
|
||||
|
||||
// Change graph state
|
||||
testConfiguration.graph = { a: true, b: true };
|
||||
|
||||
// Verify that this would have triggered a watch
|
||||
expect(mockScope.$watch.calls[3].args[0]())
|
||||
expect(mockScope.$watch.calls[2].args[0]())
|
||||
.not.toEqual(tmp);
|
||||
|
||||
// Run the function the watch would have triggered
|
||||
mockScope.$watch.calls[3].args[1]();
|
||||
mockScope.$watch.calls[2].args[1]();
|
||||
|
||||
// Should have some graphs now
|
||||
expect(controller.graphs().length).toEqual(2);
|
||||
@ -220,7 +211,7 @@ define(
|
||||
mockZoom.duration.andReturn(12345);
|
||||
|
||||
// Initially populate
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
mockScope.$watch.calls[0].args[1](mockDomainObject);
|
||||
|
||||
expect(controller.width(mockZoom)).toEqual(54321);
|
||||
// Verify interactions; we took zoom's duration for our start/end,
|
||||
|
@ -32,7 +32,11 @@ define(
|
||||
|
||||
beforeEach(function () {
|
||||
testConfiguration = {
|
||||
levels: [1000, 2000, 3500],
|
||||
levels: [
|
||||
1000,
|
||||
2000,
|
||||
3500
|
||||
],
|
||||
width: 12321
|
||||
};
|
||||
mockScope = jasmine.createSpyObj("$scope", ['$watch']);
|
||||
@ -70,61 +74,32 @@ define(
|
||||
expect(controller.zoom()).toEqual(3500);
|
||||
});
|
||||
|
||||
it("observes scroll bounds", function () {
|
||||
expect(mockScope.$watch)
|
||||
.toHaveBeenCalledWith("scroll", jasmine.any(Function));
|
||||
it("does not normally persist zoom changes", function () {
|
||||
controller.zoom(1);
|
||||
expect(mockScope.commit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when watches have fired", function () {
|
||||
var mockDomainObject,
|
||||
mockPromise,
|
||||
mockTimespan,
|
||||
testStart,
|
||||
testEnd;
|
||||
|
||||
beforeEach(function () {
|
||||
testStart = 3000;
|
||||
testEnd = 5500;
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
||||
'getId',
|
||||
'getModel',
|
||||
'getCapability',
|
||||
'useCapability'
|
||||
]);
|
||||
mockPromise = jasmine.createSpyObj('promise', ['then']);
|
||||
mockTimespan = jasmine.createSpyObj('timespan', [
|
||||
'getStart',
|
||||
'getEnd',
|
||||
'getDuration'
|
||||
]);
|
||||
|
||||
mockDomainObject.useCapability.andCallFake(function (c) {
|
||||
return c === 'timespan' && mockPromise;
|
||||
});
|
||||
mockPromise.then.andCallFake(function (callback) {
|
||||
callback(mockTimespan);
|
||||
});
|
||||
mockTimespan.getStart.andReturn(testStart);
|
||||
mockTimespan.getEnd.andReturn(testEnd);
|
||||
mockTimespan.getDuration.andReturn(testEnd - testStart);
|
||||
|
||||
mockScope.scroll = { x: 0, width: 20000 };
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
call.args[1](mockScope[call.args[0]]);
|
||||
});
|
||||
it("persists zoom changes in Edit mode", function () {
|
||||
mockScope.domainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['hasCapability', 'getCapability']
|
||||
);
|
||||
mockScope.domainObject.hasCapability.andCallFake(function (c) {
|
||||
return c === 'editor';
|
||||
});
|
||||
|
||||
it("zooms to fit the timeline", function () {
|
||||
var x1 = mockScope.scroll.x,
|
||||
x2 = mockScope.scroll.x + mockScope.scroll.width;
|
||||
expect(Math.round(controller.toMillis(x1)))
|
||||
.toEqual(testStart);
|
||||
expect(Math.round(controller.toMillis(x2)))
|
||||
.toBeGreaterThan(testEnd);
|
||||
mockScope.domainObject.getCapability.andCallFake(function (c) {
|
||||
if (c === 'editor') {
|
||||
return {
|
||||
inEditContext: function () {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
controller.zoom(1);
|
||||
expect(mockScope.commit).toHaveBeenCalled();
|
||||
expect(mockScope.configuration.zoomLevel)
|
||||
.toEqual(jasmine.any(Number));
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -177,10 +177,6 @@ define(
|
||||
// representation to store local variables into.
|
||||
$scope.representation = {};
|
||||
|
||||
// Change templates (passing in undefined to clear
|
||||
// if we don't have enough info to show a template.)
|
||||
changeTemplate(canRepresent ? representation : undefined);
|
||||
|
||||
// Any existing representers are no longer valid; release them.
|
||||
destroyRepresenters();
|
||||
|
||||
@ -226,6 +222,10 @@ define(
|
||||
// next change object/key pair changes
|
||||
toClear = uses.concat(['model']);
|
||||
}
|
||||
|
||||
// Change templates (passing in undefined to clear
|
||||
// if we don't have enough info to show a template.)
|
||||
changeTemplate(canRepresent ? representation : undefined);
|
||||
}
|
||||
|
||||
// Update the representation when the key changes (e.g. if a
|
||||
|
@ -46,7 +46,7 @@ define(
|
||||
// Find the relevant scope...
|
||||
var rect,
|
||||
scope = element.scope && element.scope();
|
||||
|
||||
|
||||
if (scope && scope.$broadcast) {
|
||||
// Get the representation's bounds, to convert
|
||||
// drop position
|
||||
|
@ -194,6 +194,21 @@ define(
|
||||
.toHaveBeenCalledWith(testViews[1]);
|
||||
});
|
||||
|
||||
it("exposes configuration before changing templates", function () {
|
||||
var observedConfiguration;
|
||||
|
||||
mockChangeTemplate.andCallFake(function () {
|
||||
observedConfiguration = mockScope.configuration;
|
||||
});
|
||||
|
||||
mockScope.key = "xyz";
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
fireWatch('key', mockScope.key);
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
|
||||
expect(observedConfiguration).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not load templates until there is an object", function () {
|
||||
mockScope.key = "xyz";
|
||||
|
||||
|
@ -28,7 +28,6 @@ define([
|
||||
"text!./res/templates/search-item.html",
|
||||
"text!./res/templates/search.html",
|
||||
"text!./res/templates/search-menu.html",
|
||||
"text!./src/services/GenericSearchWorker.js",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
SearchController,
|
||||
@ -38,7 +37,6 @@ define([
|
||||
searchItemTemplate,
|
||||
searchTemplate,
|
||||
searchMenuTemplate,
|
||||
searchWorkerText,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@ -116,7 +114,7 @@ define([
|
||||
"workers": [
|
||||
{
|
||||
"key": "genericSearchWorker",
|
||||
"scriptText": searchWorkerText
|
||||
"scriptUrl": "services/GenericSearchWorker.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Implementing a basic plugin</title>
|
||||
<script src="dist/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
function WebPlugin(websites) {
|
||||
this.websites = websites;
|
||||
|
||||
var ROOTS = websites.reduce(function (rootMap, website) {
|
||||
rootMap[website] = {
|
||||
namespace: website,
|
||||
identifier: 'page'
|
||||
};
|
||||
return rootMap;
|
||||
}, {});
|
||||
|
||||
function installPlugin(MCT) {
|
||||
Object.keys(ROOTS).forEach(function (rootUrl) {
|
||||
MCT.Objects.addRoot(ROOTS[rootUrl]);
|
||||
MCT.Objects.addProvider(rootUrl, {
|
||||
get: function () {
|
||||
return Promise.resolve({
|
||||
type: 'example.page',
|
||||
url: rootUrl,
|
||||
name: rootUrl
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return installPlugin;
|
||||
}
|
||||
|
||||
var myWebPlugin = WebPlugin([
|
||||
'http://www.wikipedia.org/',
|
||||
'http://nasa.github.io/openmct'
|
||||
]);
|
||||
|
||||
MCT.install(myWebPlugin);
|
||||
|
||||
MCT.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -24,27 +24,10 @@ define(function () {
|
||||
|
||||
function BundleRegistry() {
|
||||
this.bundles = {};
|
||||
this.knownBundles = {};
|
||||
}
|
||||
|
||||
BundleRegistry.prototype.register = function (path, definition) {
|
||||
if (this.knownBundles.hasOwnProperty(path)) {
|
||||
throw new Error('Cannot register bundle with duplicate path', path);
|
||||
}
|
||||
this.knownBundles[path] = definition;
|
||||
};
|
||||
|
||||
BundleRegistry.prototype.enable = function (path) {
|
||||
if (!this.knownBundles[path]) {
|
||||
throw new Error('Unknown bundle ' + path);
|
||||
}
|
||||
this.bundles[path] = this.knownBundles[path];
|
||||
};
|
||||
|
||||
BundleRegistry.prototype.disable = function (path) {
|
||||
if (!this.bundles[path]) {
|
||||
throw new Error('Tried to disable inactive bundle ' + path);
|
||||
}
|
||||
this.bundles[path] = definition;
|
||||
};
|
||||
|
||||
BundleRegistry.prototype.contains = function (path) {
|
||||
@ -59,14 +42,8 @@ define(function () {
|
||||
return Object.keys(this.bundles);
|
||||
};
|
||||
|
||||
BundleRegistry.prototype.remove = BundleRegistry.prototype.disable;
|
||||
|
||||
BundleRegistry.prototype.delete = function (path) {
|
||||
if (!this.knownBundles[path]) {
|
||||
throw new Error('Cannot remove Unknown Bundle ' + path);
|
||||
}
|
||||
BundleRegistry.prototype.remove = function (path) {
|
||||
delete this.bundles[path];
|
||||
delete this.knownBundles[path];
|
||||
};
|
||||
|
||||
return BundleRegistry;
|
||||
|
151
src/MCT.js
151
src/MCT.js
@ -1,151 +0,0 @@
|
||||
define([
|
||||
'EventEmitter',
|
||||
'legacyRegistry',
|
||||
'uuid',
|
||||
'./api/api',
|
||||
'text!./adapter/templates/edit-object-replacement.html',
|
||||
'./Selection',
|
||||
'./api/objects/object-utils'
|
||||
], function (
|
||||
EventEmitter,
|
||||
legacyRegistry,
|
||||
uuid,
|
||||
api,
|
||||
editObjectTemplate,
|
||||
Selection,
|
||||
objectUtils
|
||||
) {
|
||||
function MCT() {
|
||||
EventEmitter.call(this);
|
||||
this.legacyBundle = { extensions: {
|
||||
services: [
|
||||
{
|
||||
key: "mct",
|
||||
implementation: function () {
|
||||
return this;
|
||||
}.bind(this)
|
||||
}
|
||||
]
|
||||
} };
|
||||
|
||||
this.selection = new Selection();
|
||||
this.on('navigation', this.selection.clear.bind(this.selection));
|
||||
}
|
||||
|
||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
Object.keys(api).forEach(function (k) {
|
||||
MCT.prototype[k] = api[k];
|
||||
});
|
||||
MCT.prototype.MCT = MCT;
|
||||
|
||||
MCT.prototype.legacyExtension = function (category, extension) {
|
||||
this.legacyBundle.extensions[category] =
|
||||
this.legacyBundle.extensions[category] || [];
|
||||
this.legacyBundle.extensions[category].push(extension);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set path to where assets are hosted. This should be the path to main.js.
|
||||
*/
|
||||
MCT.prototype.setAssetPath = function (path) {
|
||||
this.legacyExtension('constants', {
|
||||
key: "ASSETS_PATH",
|
||||
value: path
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a new type of view.
|
||||
*
|
||||
* @param region the region identifier (see mct.regions)
|
||||
* @param {ViewDefinition} definition the definition for this view
|
||||
*/
|
||||
MCT.prototype.view = function (region, definition) {
|
||||
var viewKey = region + uuid();
|
||||
var adaptedViewKey = "adapted-view-" + region;
|
||||
|
||||
this.legacyExtension(
|
||||
region === this.regions.main ? 'views' : 'representations',
|
||||
{
|
||||
name: "A view",
|
||||
key: adaptedViewKey,
|
||||
editable: true,
|
||||
template: '<mct-view region="\'' +
|
||||
region +
|
||||
'\'" ' +
|
||||
'key="\'' +
|
||||
viewKey +
|
||||
'\'" ' +
|
||||
'mct-object="domainObject">' +
|
||||
'</mct-view>'
|
||||
}
|
||||
);
|
||||
|
||||
this.legacyExtension('policies', {
|
||||
category: "view",
|
||||
implementation: function Policy() {
|
||||
this.allow = function (view, domainObject) {
|
||||
if (view.key === adaptedViewKey) {
|
||||
var model = domainObject.getModel();
|
||||
var newDO = objectUtils.toNewFormat(model);
|
||||
return definition.canView(newDO);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
this.legacyExtension('newViews', {
|
||||
factory: definition,
|
||||
region: region,
|
||||
key: viewKey
|
||||
});
|
||||
};
|
||||
|
||||
MCT.prototype.type = function (key, type) {
|
||||
var legacyDef = type.toLegacyDefinition();
|
||||
legacyDef.key = key;
|
||||
type.key = key;
|
||||
|
||||
this.legacyExtension('types', legacyDef);
|
||||
this.legacyExtension('representations', {
|
||||
key: "edit-object",
|
||||
priority: "preferred",
|
||||
template: editObjectTemplate,
|
||||
type: key
|
||||
});
|
||||
};
|
||||
|
||||
MCT.prototype.start = function () {
|
||||
this.legacyExtension('runs', {
|
||||
depends: ['navigationService'],
|
||||
implementation: function (navigationService) {
|
||||
navigationService
|
||||
.addListener(this.emit.bind(this, 'navigation'));
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
legacyRegistry.register('adapter', this.legacyBundle);
|
||||
legacyRegistry.enable('adapter');
|
||||
this.emit('start');
|
||||
};
|
||||
|
||||
/**
|
||||
* Install a plugin in MCT.
|
||||
*
|
||||
* @param `Function` plugin -- a plugin install function which will be
|
||||
* invoked with the mct instance.
|
||||
*/
|
||||
MCT.prototype.install = function (plugin) {
|
||||
plugin(this);
|
||||
};
|
||||
|
||||
MCT.prototype.regions = {
|
||||
main: "MAIN",
|
||||
properties: "PROPERTIES",
|
||||
toolbar: "TOOLBAR"
|
||||
};
|
||||
|
||||
return MCT;
|
||||
});
|
@ -1,32 +0,0 @@
|
||||
define(['EventEmitter'], function (EventEmitter) {
|
||||
function Selection() {
|
||||
EventEmitter.call(this);
|
||||
this.selectedValues = [];
|
||||
}
|
||||
|
||||
Selection.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
Selection.prototype.select = function (value) {
|
||||
this.selectedValues.push(value);
|
||||
this.emit('change', this.selectedValues);
|
||||
return this.deselect.bind(this, value);
|
||||
};
|
||||
|
||||
Selection.prototype.deselect = function (value) {
|
||||
this.selectedValues = this.selectedValues.filter(function (v) {
|
||||
return v !== value;
|
||||
});
|
||||
this.emit('change', this.selectedValues);
|
||||
};
|
||||
|
||||
Selection.prototype.selected = function () {
|
||||
return this.selectedValues;
|
||||
};
|
||||
|
||||
Selection.prototype.clear = function () {
|
||||
this.selectedValues = [];
|
||||
this.emit('change', this.selectedValues);
|
||||
};
|
||||
|
||||
return Selection;
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
define([
|
||||
'../../api/objects/object-utils'
|
||||
], function (objectUtils) {
|
||||
function ActionDialogDecorator(mct, newViews, actionService) {
|
||||
this.actionService = actionService;
|
||||
this.mct = mct;
|
||||
this.definitions = newViews.filter(function (newView) {
|
||||
return newView.region === mct.regions.properties;
|
||||
}).map(function (newView) {
|
||||
return newView.factory;
|
||||
});
|
||||
}
|
||||
|
||||
ActionDialogDecorator.prototype.getActions = function (context) {
|
||||
var mct = this.mct;
|
||||
var definitions = this.definitions;
|
||||
|
||||
return this.actionService.getActions(context).map(function (action) {
|
||||
if (action.dialogService) {
|
||||
var domainObject = objectUtils.toNewFormat(
|
||||
context.domainObject.getModel(),
|
||||
objectUtils.parseKeyString(context.domainObject.getId())
|
||||
);
|
||||
|
||||
definitions = definitions.filter(function (definition) {
|
||||
return definition.canView(domainObject);
|
||||
});
|
||||
|
||||
if (definitions.length > 0) {
|
||||
action.dialogService = Object.create(action.dialogService);
|
||||
action.dialogService.getUserInput = function (form, value) {
|
||||
return new mct.Dialog(
|
||||
definitions[0].view(context.domainObject),
|
||||
form.title
|
||||
).show();
|
||||
};
|
||||
}
|
||||
}
|
||||
return action;
|
||||
});
|
||||
};
|
||||
|
||||
return ActionDialogDecorator;
|
||||
});
|
@ -1,65 +0,0 @@
|
||||
define([
|
||||
'legacyRegistry',
|
||||
'./actions/ActionDialogDecorator',
|
||||
'./directives/MCTView',
|
||||
'./services/Instantiate',
|
||||
'./capabilities/APICapabilityDecorator',
|
||||
'./policies/AdapterCompositionPolicy'
|
||||
], function (
|
||||
legacyRegistry,
|
||||
ActionDialogDecorator,
|
||||
MCTView,
|
||||
Instantiate,
|
||||
APICapabilityDecorator,
|
||||
AdapterCompositionPolicy
|
||||
) {
|
||||
legacyRegistry.register('src/adapter', {
|
||||
"extensions": {
|
||||
"directives": [
|
||||
{
|
||||
key: "mctView",
|
||||
implementation: MCTView,
|
||||
depends: [
|
||||
"newViews[]",
|
||||
"mct"
|
||||
]
|
||||
}
|
||||
],
|
||||
services: [
|
||||
{
|
||||
key: "instantiate",
|
||||
priority: "mandatory",
|
||||
implementation: Instantiate,
|
||||
depends: [
|
||||
"capabilityService",
|
||||
"identifierService",
|
||||
"cacheService"
|
||||
]
|
||||
}
|
||||
],
|
||||
components: [
|
||||
{
|
||||
type: "decorator",
|
||||
provides: "capabilityService",
|
||||
implementation: APICapabilityDecorator,
|
||||
depends: [
|
||||
"$injector"
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "decorator",
|
||||
provides: "actionService",
|
||||
implementation: ActionDialogDecorator,
|
||||
depends: [ "mct", "newViews[]" ]
|
||||
}
|
||||
],
|
||||
policies: [
|
||||
{
|
||||
category: "composition",
|
||||
implementation: AdapterCompositionPolicy,
|
||||
depends: [ "mct" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
define([
|
||||
'./synchronizeMutationCapability',
|
||||
'./AlternateCompositionCapability'
|
||||
], function (
|
||||
synchronizeMutationCapability,
|
||||
AlternateCompositionCapability
|
||||
) {
|
||||
|
||||
/**
|
||||
* Overrides certain capabilities to keep consistency between old API
|
||||
* and new API.
|
||||
*/
|
||||
function APICapabilityDecorator($injector, capabilityService) {
|
||||
this.$injector = $injector;
|
||||
this.capabilityService = capabilityService;
|
||||
}
|
||||
|
||||
APICapabilityDecorator.prototype.getCapabilities = function (
|
||||
model
|
||||
) {
|
||||
var capabilities = this.capabilityService.getCapabilities(model);
|
||||
if (capabilities.mutation) {
|
||||
capabilities.mutation =
|
||||
synchronizeMutationCapability(capabilities.mutation);
|
||||
}
|
||||
if (AlternateCompositionCapability.appliesTo(model)) {
|
||||
capabilities.composition = function (domainObject) {
|
||||
return new AlternateCompositionCapability(this.$injector, domainObject)
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
return capabilities;
|
||||
};
|
||||
|
||||
return APICapabilityDecorator;
|
||||
|
||||
});
|
@ -1,102 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining AlternateCompositionCapability. Created by vwoeltje on 11/7/14.
|
||||
*/
|
||||
define([
|
||||
'../../api/objects/object-utils',
|
||||
'../../api/composition/CompositionAPI'
|
||||
], function (objectUtils, CompositionAPI) {
|
||||
|
||||
function AlternateCompositionCapability($injector, domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
|
||||
this.getDependencies = function () {
|
||||
this.instantiate = $injector.get("instantiate");
|
||||
this.contextualize = $injector.get("contextualize");
|
||||
this.getDependencies = undefined;
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
AlternateCompositionCapability.prototype.add = function (child, index) {
|
||||
if (typeof index !== 'undefined') {
|
||||
// At first glance I don't see a location in the existing
|
||||
// codebase where add is called with an index. Won't support.
|
||||
throw new Error(
|
||||
'Composition Capability does not support adding at index'
|
||||
);
|
||||
}
|
||||
|
||||
function addChildToComposition(model) {
|
||||
var existingIndex = model.composition.indexOf(child.getId());
|
||||
if (existingIndex === -1) {
|
||||
model.composition.push(child.getId())
|
||||
}
|
||||
}
|
||||
|
||||
return this.domainObject.useCapability(
|
||||
'mutation',
|
||||
addChildToComposition
|
||||
)
|
||||
.then(this.invoke.bind(this))
|
||||
.then(function (children) {
|
||||
return children.filter(function (c) {
|
||||
return c.getId() === child.getId();
|
||||
})[0];
|
||||
});
|
||||
};
|
||||
|
||||
AlternateCompositionCapability.prototype.contextualizeChild = function (
|
||||
child
|
||||
) {
|
||||
if (this.getDependencies) {
|
||||
this.getDependencies();
|
||||
}
|
||||
|
||||
var keyString = objectUtils.makeKeyString(child.key);
|
||||
var oldModel = objectUtils.toOldFormat(child);
|
||||
var newDO = this.instantiate(oldModel, keyString);
|
||||
return this.contextualize(newDO, this.domainObject);
|
||||
|
||||
};
|
||||
|
||||
AlternateCompositionCapability.prototype.invoke = function () {
|
||||
var newFormatDO = objectUtils.toNewFormat(
|
||||
this.domainObject.getModel(),
|
||||
this.domainObject.getId()
|
||||
);
|
||||
var collection = CompositionAPI(newFormatDO);
|
||||
return collection.load()
|
||||
.then(function (children) {
|
||||
collection.destroy();
|
||||
return children.map(this.contextualizeChild, this);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
AlternateCompositionCapability.appliesTo = function (model) {
|
||||
return !!CompositionAPI(objectUtils.toNewFormat(model, model.id));
|
||||
};
|
||||
|
||||
return AlternateCompositionCapability;
|
||||
}
|
||||
);
|
@ -1,27 +0,0 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
/**
|
||||
* Wraps the mutation capability and synchronizes the mutation
|
||||
*/
|
||||
function synchronizeMutationCapability(mutationConstructor) {
|
||||
|
||||
return function makeCapability(domainObject) {
|
||||
var capability = mutationConstructor(domainObject);
|
||||
var oldListen = capability.listen.bind(capability);
|
||||
capability.listen = function (listener) {
|
||||
return oldListen(function (newModel) {
|
||||
capability.domainObject.model =
|
||||
JSON.parse(JSON.stringify(newModel));
|
||||
listener(newModel);
|
||||
});
|
||||
};
|
||||
return capability;
|
||||
}
|
||||
};
|
||||
|
||||
return synchronizeMutationCapability;
|
||||
});
|
@ -1,65 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'./Region',
|
||||
'../../api/objects/object-utils'
|
||||
], function (
|
||||
angular,
|
||||
Region,
|
||||
objectUtils
|
||||
) {
|
||||
function MCTView(newViews, PublicAPI) {
|
||||
var definitions = {};
|
||||
|
||||
newViews.forEach(function (newView) {
|
||||
definitions[newView.region] = definitions[newView.region] || {};
|
||||
definitions[newView.region][newView.key] = newView.factory;
|
||||
});
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function (scope, element, attrs) {
|
||||
var key, mctObject, regionId, region;
|
||||
|
||||
function maybeShow() {
|
||||
if (!definitions[regionId] || !definitions[regionId][key] || !mctObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
region.show(definitions[regionId][key].view(mctObject));
|
||||
}
|
||||
|
||||
function setKey(k) {
|
||||
key = k;
|
||||
maybeShow();
|
||||
}
|
||||
|
||||
function setObject(obj) {
|
||||
mctObject = undefined;
|
||||
PublicAPI.Objects.get(objectUtils.parseKeyString(obj.getId()))
|
||||
.then(function (mobj) {
|
||||
mctObject = mobj;
|
||||
maybeShow();
|
||||
});
|
||||
}
|
||||
|
||||
function setRegionId(r) {
|
||||
regionId = r;
|
||||
maybeShow();
|
||||
}
|
||||
|
||||
region = new Region(element[0]);
|
||||
|
||||
scope.$watch('key', setKey);
|
||||
scope.$watch('region', setRegionId);
|
||||
scope.$watch('mctObject', setObject);
|
||||
},
|
||||
scope: {
|
||||
key: "=",
|
||||
region: "=",
|
||||
mctObject: "="
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return MCTView;
|
||||
});
|
@ -1,23 +0,0 @@
|
||||
define([], function () {
|
||||
function Region(element) {
|
||||
this.activeView = undefined;
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
Region.prototype.clear = function () {
|
||||
if (this.activeView) {
|
||||
this.activeView.destroy();
|
||||
this.activeView = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
Region.prototype.show = function (view) {
|
||||
this.clear();
|
||||
this.activeView = view;
|
||||
if (this.activeView) {
|
||||
this.activeView.show(this.element);
|
||||
}
|
||||
};
|
||||
|
||||
return Region;
|
||||
});
|
@ -1,26 +0,0 @@
|
||||
define([], function () {
|
||||
function AdapterCompositionPolicy(mct) {
|
||||
this.mct = mct;
|
||||
}
|
||||
|
||||
AdapterCompositionPolicy.prototype.allow = function (
|
||||
containerType,
|
||||
childType
|
||||
) {
|
||||
var containerObject = containerType.getInitialModel();
|
||||
var childObject = childType.getInitialModel();
|
||||
|
||||
containerObject.type = containerType.getKey();
|
||||
childObject.type = childType.getKey();
|
||||
|
||||
var composition = this.mct.Composition(containerObject);
|
||||
|
||||
if (composition) {
|
||||
return composition.canContain(childObject);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return AdapterCompositionPolicy;
|
||||
});
|
@ -1,46 +0,0 @@
|
||||
<div class="abs l-flex-col" ng-controller="EditObjectController as EditObjectController">
|
||||
<div mct-before-unload="EditObjectController.getUnloadWarning()"
|
||||
class="holder flex-elem l-flex-row object-browse-bar ">
|
||||
<div class="items-select left flex-elem l-flex-row grows">
|
||||
<mct-representation key="'back-arrow'"
|
||||
mct-object="domainObject"
|
||||
class="flex-elem l-back"></mct-representation>
|
||||
<mct-representation key="'object-header'"
|
||||
mct-object="domainObject"
|
||||
class="l-flex-row flex-elem grows object-header">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||
<mct-representation key="'switcher'"
|
||||
mct-object="domainObject"
|
||||
ng-model="representation">
|
||||
</mct-representation>
|
||||
<!-- Temporarily, on mobile, the action buttons are hidden-->
|
||||
<mct-representation key="'action-group'"
|
||||
mct-object="domainObject"
|
||||
parameters="{ category: 'view-control' }"
|
||||
class="mobile-hide">
|
||||
</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-inner">
|
||||
<!-- Toolbar and Save/Cancel buttons -->
|
||||
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
||||
<mct-representation key="'adapted-view-TOOLBAR'"
|
||||
mct-object="domainObject"
|
||||
class="flex-elem grows">
|
||||
</mct-representation>
|
||||
<mct-representation key="'edit-action-buttons'"
|
||||
mct-object="domainObject"
|
||||
class='flex-elem conclude-editing'>
|
||||
</mct-representation>
|
||||
</div>
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="abs flex-elem grows object-holder-main scroll"
|
||||
toolbar="toolbar">
|
||||
</mct-representation>
|
||||
</div><!--/ l-object-wrapper-inner -->
|
||||
</div>
|
||||
</div>
|
@ -1,183 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
};
|
||||
|
||||
function throwOnError(validationResult) {
|
||||
if (validationResult !== true) {
|
||||
throw new Error(validationResult);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
throwOnError(this.validateBounds(newBounds));
|
||||
this.boundsVal = newBounds;
|
||||
/**
|
||||
* @event TimeConductor#bounds The start time, end time, or
|
||||
* both have been updated
|
||||
* @property {TimeConductorBounds} bounds
|
||||
*/
|
||||
this.emit('bounds', this.boundsVal);
|
||||
}
|
||||
return 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);
|
||||
// Do something with bounds here. Try and convert between
|
||||
// time systems? Or just set defaults when time system changes?
|
||||
// eg.
|
||||
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;
|
||||
});
|
@ -1,110 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* 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.toBe(bounds);
|
||||
expect(tc.bounds.bind(tc, bounds)).not.toThrow();
|
||||
expect(tc.bounds()).toBe(bounds);
|
||||
});
|
||||
|
||||
it("Disallows setting of invalid bounds", function () {
|
||||
bounds = {start: 1, end: 0};
|
||||
expect(tc.bounds()).not.toBe(bounds);
|
||||
expect(tc.bounds.bind(tc, bounds)).toThrow();
|
||||
expect(tc.bounds()).not.toBe(bounds);
|
||||
|
||||
bounds = {start: 1};
|
||||
expect(tc.bounds()).not.toBe(bounds);
|
||||
expect(tc.bounds.bind(tc, bounds)).toThrow();
|
||||
expect(tc.bounds()).not.toBe(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);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,54 +0,0 @@
|
||||
define(function () {
|
||||
/**
|
||||
* @typedef TypeDefinition
|
||||
* @property {Metadata} metadata displayable metadata about this type
|
||||
* @property {function (object)} [initialize] a function which initializes
|
||||
* the model for new domain objects of this type
|
||||
* @property {boolean} [creatable] true if users should be allowed to
|
||||
* create this type (default: false)
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {TypeDefinition} definition
|
||||
* @constructor
|
||||
*/
|
||||
function Type(definition) {
|
||||
this.definition = definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a domain object is an instance of this type.
|
||||
* @param domainObject
|
||||
* @returns {boolean} true if the domain object is of this type
|
||||
*/
|
||||
Type.prototype.check = function (domainObject) {
|
||||
// Depends on assignment from MCT.
|
||||
return domainObject.type === this.key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a definition for this type that can be registered using the
|
||||
* legacy bundle format.
|
||||
* @private
|
||||
*/
|
||||
Type.prototype.toLegacyDefinition = function () {
|
||||
var def = {};
|
||||
def.name = this.definition.metadata.label;
|
||||
def.glyph = this.definition.metadata.glyph;
|
||||
def.description = this.definition.metadata.description;
|
||||
def.properties = this.definition.form;
|
||||
|
||||
if (this.definition.initialize) {
|
||||
def.model = {};
|
||||
this.definition.initialize(def.model);
|
||||
}
|
||||
|
||||
if (this.definition.creatable) {
|
||||
def.features = ['creation'];
|
||||
}
|
||||
return def;
|
||||
};
|
||||
|
||||
return Type;
|
||||
});
|
@ -1,38 +0,0 @@
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* A View is used to provide displayable content, and to react to
|
||||
* associated life cycle events.
|
||||
*
|
||||
* @interface
|
||||
*/
|
||||
function View() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the supplied DOM element with the contents of this view.
|
||||
*
|
||||
* View implementations should use this method to attach any
|
||||
* listeners or acquire other resources that are necessary to keep
|
||||
* the contents of this view up-to-date.
|
||||
*
|
||||
* @param {HTMLElement} container the DOM element to populate
|
||||
*/
|
||||
View.prototype.show = function (container) {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Release any resources associated with this view.
|
||||
*
|
||||
* View implementations should use this method to detach any
|
||||
* listeners or release other resources that are no longer necessary
|
||||
* once a view is no longer used.
|
||||
*/
|
||||
View.prototype.destroy = function () {
|
||||
|
||||
};
|
||||
|
||||
return View;
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
define(function () {
|
||||
|
||||
/**
|
||||
* Defines a kind of view.
|
||||
* @interface
|
||||
*/
|
||||
function ViewDefinition() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata about this view, as may be used in the user interface
|
||||
* to present options for this view.
|
||||
* @param {*} object the object to be shown in this view
|
||||
* @returns {mct.ViewMetadata} metadata about this view
|
||||
*/
|
||||
ViewDefinition.prototype.metadata = function (object) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate a new view of this object. Callers of this method are
|
||||
* responsible for calling `canView` before instantiating views in this
|
||||
* manner.
|
||||
*
|
||||
* @param {*} object the object to be shown in this view
|
||||
* @returns {mct.View} a view of this object
|
||||
*/
|
||||
ViewDefinition.prototype.view = function (object) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this view is capable of showing this object. Users of
|
||||
* views should use this method before calling `show`.
|
||||
*
|
||||
* Subclasses should override this method to control the applicability
|
||||
* of this view to other objects.
|
||||
*
|
||||
* @param {*} object the object to be shown in this view
|
||||
* @returns {boolean} true if this view is applicable to this object
|
||||
*/
|
||||
ViewDefinition.prototype.canView = function (object) {
|
||||
};
|
||||
|
||||
return ViewDefinition;
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user