mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
15 Commits
save-style
...
api-tutori
Author | SHA1 | Date | |
---|---|---|---|
d475d767d5 | |||
a63e053399 | |||
5de7a96ccc | |||
09a833f524 | |||
9c4e17bfab | |||
d3e5d95d6b | |||
c70793ac2d | |||
a6ef1d3423 | |||
c4fec1af6a | |||
a6996df3df | |||
0c660238f2 | |||
b73b824e55 | |||
1954d98628 | |||
7aa034ce23 | |||
385dc5d298 |
@ -18,6 +18,8 @@
|
|||||||
"node-uuid": "^1.4.7",
|
"node-uuid": "^1.4.7",
|
||||||
"comma-separated-values": "^3.6.4",
|
"comma-separated-values": "^3.6.4",
|
||||||
"FileSaver.js": "^0.0.2",
|
"FileSaver.js": "^0.0.2",
|
||||||
"zepto": "^1.1.6"
|
"zepto": "^1.1.6",
|
||||||
|
"eventemitter3": "^1.2.0",
|
||||||
|
"lodash": "3.10.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
index.html
11
index.html
@ -31,10 +31,17 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
require(['main'], function (mct) {
|
require(['main'], function (mct) {
|
||||||
require([
|
require([
|
||||||
|
'./tutorials/grootprovider/groots',
|
||||||
|
'./tutorials/todo/todo',
|
||||||
|
'./tutorials/todo/bundle',
|
||||||
'./example/imagery/bundle',
|
'./example/imagery/bundle',
|
||||||
'./example/eventGenerator/bundle',
|
'./example/eventGenerator/bundle',
|
||||||
'./example/generator/bundle'
|
'./example/generator/bundle',
|
||||||
], mct.run.bind(mct));
|
], function (grootify, todoPlugin) {
|
||||||
|
grootify(mct);
|
||||||
|
todoPlugin(mct);
|
||||||
|
mct.start();
|
||||||
|
})
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
||||||
|
25
main.js
25
main.js
@ -28,13 +28,15 @@ requirejs.config({
|
|||||||
"angular-route": "bower_components/angular-route/angular-route.min",
|
"angular-route": "bower_components/angular-route/angular-route.min",
|
||||||
"csv": "bower_components/comma-separated-values/csv.min",
|
"csv": "bower_components/comma-separated-values/csv.min",
|
||||||
"es6-promise": "bower_components/es6-promise/promise.min",
|
"es6-promise": "bower_components/es6-promise/promise.min",
|
||||||
|
"EventEmitter": "bower_components/eventemitter3/index",
|
||||||
"moment": "bower_components/moment/moment",
|
"moment": "bower_components/moment/moment",
|
||||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||||
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||||
"text": "bower_components/text/text",
|
"text": "bower_components/text/text",
|
||||||
"uuid": "bower_components/node-uuid/uuid",
|
"uuid": "bower_components/node-uuid/uuid",
|
||||||
"zepto": "bower_components/zepto/zepto.min"
|
"zepto": "bower_components/zepto/zepto.min",
|
||||||
|
"lodash": "bower_components/lodash/lodash"
|
||||||
},
|
},
|
||||||
"shim": {
|
"shim": {
|
||||||
"angular": {
|
"angular": {
|
||||||
@ -43,6 +45,9 @@ requirejs.config({
|
|||||||
"angular-route": {
|
"angular-route": {
|
||||||
"deps": ["angular"]
|
"deps": ["angular"]
|
||||||
},
|
},
|
||||||
|
"EventEmitter": {
|
||||||
|
"exports": "EventEmitter"
|
||||||
|
},
|
||||||
"moment-duration-format": {
|
"moment-duration-format": {
|
||||||
"deps": ["moment"]
|
"deps": ["moment"]
|
||||||
},
|
},
|
||||||
@ -58,6 +63,7 @@ requirejs.config({
|
|||||||
define([
|
define([
|
||||||
'./platform/framework/src/Main',
|
'./platform/framework/src/Main',
|
||||||
'legacyRegistry',
|
'legacyRegistry',
|
||||||
|
'./src/MCT',
|
||||||
|
|
||||||
'./platform/framework/bundle',
|
'./platform/framework/bundle',
|
||||||
'./platform/core/bundle',
|
'./platform/core/bundle',
|
||||||
@ -93,11 +99,14 @@ define([
|
|||||||
'./platform/search/bundle',
|
'./platform/search/bundle',
|
||||||
'./platform/status/bundle',
|
'./platform/status/bundle',
|
||||||
'./platform/commonUI/regions/bundle'
|
'./platform/commonUI/regions/bundle'
|
||||||
], function (Main, legacyRegistry) {
|
], function (Main, legacyRegistry, MCT) {
|
||||||
return {
|
var mct = new MCT();
|
||||||
legacyRegistry: legacyRegistry,
|
|
||||||
run: function () {
|
mct.legacyRegistry = legacyRegistry;
|
||||||
return new Main().run(legacyRegistry);
|
mct.run = mct.start;
|
||||||
}
|
mct.on('start', function () {
|
||||||
};
|
return new Main().run(legacyRegistry);
|
||||||
|
});
|
||||||
|
|
||||||
|
return mct;
|
||||||
});
|
});
|
||||||
|
36
src/MCT.js
Normal file
36
src/MCT.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
define([
|
||||||
|
'EventEmitter',
|
||||||
|
'legacyRegistry',
|
||||||
|
'./api/api',
|
||||||
|
'./api/objects/bundle'
|
||||||
|
], function (
|
||||||
|
EventEmitter,
|
||||||
|
legacyRegistry,
|
||||||
|
api
|
||||||
|
) {
|
||||||
|
function MCT() {
|
||||||
|
EventEmitter.call(this);
|
||||||
|
this.legacyBundle = { extensions: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
|
Object.keys(api).forEach(function (k) {
|
||||||
|
MCT.prototype[k] = api[k];
|
||||||
|
});
|
||||||
|
|
||||||
|
MCT.prototype.type = function (key, type) {
|
||||||
|
var legacyDef = type.toLegacyDefinition();
|
||||||
|
legacyDef.key = key;
|
||||||
|
this.legacyBundle.extensions.types =
|
||||||
|
this.legacyBundle.extensions.types || [];
|
||||||
|
this.legacyBundle.extensions.types.push(legacyDef);
|
||||||
|
};
|
||||||
|
|
||||||
|
MCT.prototype.start = function () {
|
||||||
|
legacyRegistry.register('adapter', this.legacyBundle);
|
||||||
|
this.emit('start');
|
||||||
|
};
|
||||||
|
|
||||||
|
return MCT;
|
||||||
|
});
|
43
src/api/Type.js
Normal file
43
src/api/Type.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
if (this.definition.initialize) {
|
||||||
|
def.model = {};
|
||||||
|
this.definition.initialize(def.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.definition.creatable) {
|
||||||
|
def.features = ['creation'];
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Type;
|
||||||
|
});
|
12
src/api/api.js
Normal file
12
src/api/api.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
define([
|
||||||
|
'./Type',
|
||||||
|
'./objects/ObjectAPI'
|
||||||
|
], function (
|
||||||
|
Type,
|
||||||
|
ObjectAPI
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
Type: Type,
|
||||||
|
Objects: ObjectAPI
|
||||||
|
};
|
||||||
|
});
|
70
src/api/objects/LegacyObjectAPIInterceptor.js
Normal file
70
src/api/objects/LegacyObjectAPIInterceptor.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
define([
|
||||||
|
'./object-utils',
|
||||||
|
'./ObjectAPI'
|
||||||
|
], function (
|
||||||
|
utils,
|
||||||
|
ObjectAPI
|
||||||
|
) {
|
||||||
|
function ObjectServiceProvider(objectService, instantiate) {
|
||||||
|
this.objectService = objectService;
|
||||||
|
this.instantiate = instantiate;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectServiceProvider.prototype.save = function (object) {
|
||||||
|
var key = object.key,
|
||||||
|
keyString = utils.makeKeyString(key),
|
||||||
|
newObject = this.instantiate(utils.toOldFormat(object), keyString);
|
||||||
|
|
||||||
|
return object.getCapability('persistence')
|
||||||
|
.persist()
|
||||||
|
.then(function () {
|
||||||
|
return utils.toNewFormat(object, key);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ObjectServiceProvider.prototype.delete = function (object) {
|
||||||
|
// TODO!
|
||||||
|
};
|
||||||
|
|
||||||
|
ObjectServiceProvider.prototype.get = function (key) {
|
||||||
|
var keyString = utils.makeKeyString(key);
|
||||||
|
return this.objectService.getObjects([keyString])
|
||||||
|
.then(function (results) {
|
||||||
|
var model = JSON.parse(JSON.stringify(results[keyString].getModel()));
|
||||||
|
return utils.toNewFormat(model, key);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Injects new object API as a decorator so that it hijacks all requests.
|
||||||
|
// Object providers implemented on new API should just work, old API should just work, many things may break.
|
||||||
|
function LegacyObjectAPIInterceptor(ROOTS, instantiate, objectService) {
|
||||||
|
this.getObjects = function (keys) {
|
||||||
|
var results = {},
|
||||||
|
promises = keys.map(function (keyString) {
|
||||||
|
var key = utils.parseKeyString(keyString);
|
||||||
|
return ObjectAPI.get(key)
|
||||||
|
.then(function (object) {
|
||||||
|
object = utils.toOldFormat(object)
|
||||||
|
results[keyString] = instantiate(object, keyString);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(function () {
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ObjectAPI._supersecretSetFallbackProvider(
|
||||||
|
new ObjectServiceProvider(objectService, instantiate)
|
||||||
|
);
|
||||||
|
|
||||||
|
ROOTS.forEach(function (r) {
|
||||||
|
ObjectAPI.addRoot(utils.parseKeyString(r.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LegacyObjectAPIInterceptor;
|
||||||
|
});
|
92
src/api/objects/ObjectAPI.js
Normal file
92
src/api/objects/ObjectAPI.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
define([
|
||||||
|
'lodash',
|
||||||
|
'./object-utils'
|
||||||
|
], function (
|
||||||
|
_,
|
||||||
|
utils
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Object API. Intercepts the existing object API while also exposing
|
||||||
|
A new Object API.
|
||||||
|
|
||||||
|
MCT.objects.get('mine')
|
||||||
|
.then(function (root) {
|
||||||
|
console.log(root);
|
||||||
|
MCT.objects.getComposition(root)
|
||||||
|
.then(function (composition) {
|
||||||
|
console.log(composition)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Objects = {},
|
||||||
|
ROOT_REGISTRY = [],
|
||||||
|
PROVIDER_REGISTRY = {},
|
||||||
|
FALLBACK_PROVIDER;
|
||||||
|
|
||||||
|
Objects._supersecretSetFallbackProvider = function (p) {
|
||||||
|
FALLBACK_PROVIDER = p;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Root provider is hardcoded in; can't be skipped.
|
||||||
|
var RootProvider = {
|
||||||
|
'get': function () {
|
||||||
|
return Promise.resolve({
|
||||||
|
name: 'The root object',
|
||||||
|
type: 'root',
|
||||||
|
composition: ROOT_REGISTRY
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Retrieve the provider for a given key.
|
||||||
|
function getProvider(key) {
|
||||||
|
if (key.identifier === 'ROOT') {
|
||||||
|
return RootProvider;
|
||||||
|
}
|
||||||
|
return PROVIDER_REGISTRY[key.namespace] || FALLBACK_PROVIDER;
|
||||||
|
};
|
||||||
|
|
||||||
|
Objects.addProvider = function (namespace, provider) {
|
||||||
|
PROVIDER_REGISTRY[namespace] = provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
[
|
||||||
|
'save',
|
||||||
|
'delete',
|
||||||
|
'get'
|
||||||
|
].forEach(function (method) {
|
||||||
|
Objects[method] = function () {
|
||||||
|
var key = arguments[0],
|
||||||
|
provider = getProvider(key);
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
throw new Error('No Provider Matched');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider[method]) {
|
||||||
|
throw new Error('Provider does not support [' + method + '].');
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider[method].apply(provider, arguments);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
Objects.addRoot = function (key) {
|
||||||
|
ROOT_REGISTRY.unshift(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
Objects.removeRoot = function (key) {
|
||||||
|
ROOT_REGISTRY = ROOT_REGISTRY.filter(function (k) {
|
||||||
|
return (
|
||||||
|
k.identifier !== key.identifier ||
|
||||||
|
k.namespace !== key.namespace
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Objects;
|
||||||
|
});
|
100
src/api/objects/README.md
Normal file
100
src/api/objects/README.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Object API - Overview
|
||||||
|
|
||||||
|
The object API provides methods for fetching domain objects.
|
||||||
|
|
||||||
|
# Keys
|
||||||
|
Keys are a composite identifier that is used to create and persist objects. Ex:
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
namespace: 'elastic',
|
||||||
|
identifier: 'myIdentifier'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In old MCT days, we called this an "id", and we encoded it in a single string.
|
||||||
|
The above key would encode into the identifier, `elastic:myIdentifier`.
|
||||||
|
|
||||||
|
When interacting with the API you will be dealing with key objects.
|
||||||
|
|
||||||
|
# Configuring the Object API
|
||||||
|
|
||||||
|
The following methods should be used before calling run. They allow you to
|
||||||
|
configure the persistence space of MCT.
|
||||||
|
|
||||||
|
* `MCT.objects.addRoot(key)` -- add a "ROOT" to Open MCT by specifying it's
|
||||||
|
key.
|
||||||
|
* `MCT.objects.removeRoot(key)` -- Remove a "ROOT" from Open MCT by key.
|
||||||
|
* `MCT.objects.addProvider(namespace, provider)` -- register an object provider
|
||||||
|
for a specific namespace. See below for documentation on the provider
|
||||||
|
interface.
|
||||||
|
|
||||||
|
# Using the object API
|
||||||
|
|
||||||
|
The object API provides methods for getting, saving, and deleting objects.
|
||||||
|
|
||||||
|
* MCT.objects.get(key) -> returns promise for an object
|
||||||
|
* MCT.objects.save(object) -> returns promise that is resolved when object
|
||||||
|
has been saved
|
||||||
|
* MCT.objects.delete(object) -> returns promise that is resolved when object has
|
||||||
|
been deleted
|
||||||
|
|
||||||
|
## Configuration Example: Adding a groot
|
||||||
|
|
||||||
|
The following example adds a new root object for groot and populates it with
|
||||||
|
some pieces of groot.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
var ROOT_KEY = {
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'groot'
|
||||||
|
};
|
||||||
|
|
||||||
|
var GROOT_ROOT = {
|
||||||
|
name: 'I am groot',
|
||||||
|
type: 'folder',
|
||||||
|
composition: [
|
||||||
|
{
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'arms'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'legs'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'torso'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var GrootProvider = {
|
||||||
|
get: function (key) {
|
||||||
|
if (key.identifier === 'groot') {
|
||||||
|
return Promise.resolve(GROOT_ROOT);
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
name: 'Groot\'s ' + key.identifier
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mct.Objects.addRoot(ROOT_KEY);
|
||||||
|
|
||||||
|
mct.Objects.addProvider('groot', GrootProvider);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Making a custom provider:
|
||||||
|
|
||||||
|
All methods on the provider interface are optional, so you do not need
|
||||||
|
to modify them.
|
||||||
|
|
||||||
|
* `provider.get(key)` -> promise for a domain object.
|
||||||
|
* `provider.save(domainObject)` -> returns promise that is fulfilled when object
|
||||||
|
has been saved.
|
||||||
|
* `provider.delete(domainObject)` -> returns promise that is fulfilled when
|
||||||
|
object has been deleted.
|
||||||
|
|
||||||
|
|
49
src/api/objects/bundle.js
Normal file
49
src/api/objects/bundle.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'./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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
67
src/api/objects/object-utils.js
Normal file
67
src/api/objects/object-utils.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
define([
|
||||||
|
|
||||||
|
], function (
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
|
// take a key string and turn it into a key object
|
||||||
|
// 'scratch:root' ==> {namespace: 'scratch', identifier: 'root'}
|
||||||
|
var parseKeyString = function (key) {
|
||||||
|
if (typeof key === 'object') {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
var namespace = '',
|
||||||
|
identifier = key;
|
||||||
|
for (var i = 0, escaped = false, len=key.length; i < len; i++) {
|
||||||
|
if (key[i] === ":" && !escaped) {
|
||||||
|
namespace = key.slice(0, i);
|
||||||
|
identifier = key.slice(i + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
namespace: namespace,
|
||||||
|
identifier: identifier
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// take a key and turn it into a key string
|
||||||
|
// {namespace: 'scratch', identifier: 'root'} ==> 'scratch:root'
|
||||||
|
var makeKeyString = function (key) {
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
if (!key.namespace) {
|
||||||
|
return key.identifier;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
key.namespace.replace(':', '\\:'),
|
||||||
|
key.identifier.replace(':', '\\:')
|
||||||
|
].join(':');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Converts composition to use key strings instead of keys
|
||||||
|
var toOldFormat = function (model) {
|
||||||
|
delete model.key;
|
||||||
|
if (model.composition) {
|
||||||
|
model.composition = model.composition.map(makeKeyString);
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
};
|
||||||
|
|
||||||
|
// converts composition to use keys instead of key strings
|
||||||
|
var toNewFormat = function (model, key) {
|
||||||
|
model.key = key;
|
||||||
|
if (model.composition) {
|
||||||
|
model.composition = model.composition.map(parseKeyString);
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
toOldFormat: toOldFormat,
|
||||||
|
toNewFormat: toNewFormat,
|
||||||
|
makeKeyString: makeKeyString,
|
||||||
|
parseKeyString: parseKeyString
|
||||||
|
};
|
||||||
|
});
|
@ -48,6 +48,7 @@ requirejs.config({
|
|||||||
"angular-route": "bower_components/angular-route/angular-route.min",
|
"angular-route": "bower_components/angular-route/angular-route.min",
|
||||||
"csv": "bower_components/comma-separated-values/csv.min",
|
"csv": "bower_components/comma-separated-values/csv.min",
|
||||||
"es6-promise": "bower_components/es6-promise/promise.min",
|
"es6-promise": "bower_components/es6-promise/promise.min",
|
||||||
|
"EventEmitter": "bower_components/eventemitter3/index",
|
||||||
"moment": "bower_components/moment/moment",
|
"moment": "bower_components/moment/moment",
|
||||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||||
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||||
@ -64,6 +65,9 @@ requirejs.config({
|
|||||||
"angular-route": {
|
"angular-route": {
|
||||||
"deps": [ "angular" ]
|
"deps": [ "angular" ]
|
||||||
},
|
},
|
||||||
|
"EventEmitter": {
|
||||||
|
"exports": "EventEmitter"
|
||||||
|
},
|
||||||
"moment-duration-format": {
|
"moment-duration-format": {
|
||||||
"deps": [ "moment" ]
|
"deps": [ "moment" ]
|
||||||
},
|
},
|
||||||
|
127
tutorial-server/app.js
Normal file
127
tutorial-server/app.js
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*global require,process,console*/
|
||||||
|
|
||||||
|
var CONFIG = {
|
||||||
|
port: 8081,
|
||||||
|
dictionary: "dictionary.json",
|
||||||
|
interval: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var WebSocketServer = require('ws').Server,
|
||||||
|
fs = require('fs'),
|
||||||
|
wss = new WebSocketServer({ port: CONFIG.port }),
|
||||||
|
dictionary = JSON.parse(fs.readFileSync(CONFIG.dictionary, "utf8")),
|
||||||
|
spacecraft = {
|
||||||
|
"prop.fuel": 77,
|
||||||
|
"prop.thrusters": "OFF",
|
||||||
|
"comms.recd": 0,
|
||||||
|
"comms.sent": 0,
|
||||||
|
"pwr.temp": 245,
|
||||||
|
"pwr.c": 8.15,
|
||||||
|
"pwr.v": 30
|
||||||
|
},
|
||||||
|
histories = {},
|
||||||
|
listeners = [];
|
||||||
|
|
||||||
|
function updateSpacecraft() {
|
||||||
|
spacecraft["prop.fuel"] = Math.max(
|
||||||
|
0,
|
||||||
|
spacecraft["prop.fuel"] -
|
||||||
|
(spacecraft["prop.thrusters"] === "ON" ? 0.5 : 0)
|
||||||
|
);
|
||||||
|
spacecraft["pwr.temp"] = spacecraft["pwr.temp"] * 0.985
|
||||||
|
+ Math.random() * 0.25 + Math.sin(Date.now());
|
||||||
|
spacecraft["pwr.c"] = spacecraft["pwr.c"] * 0.985;
|
||||||
|
spacecraft["pwr.v"] = 30 + Math.pow(Math.random(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTelemetry() {
|
||||||
|
var timestamp = Date.now(), sent = 0;
|
||||||
|
Object.keys(spacecraft).forEach(function (id) {
|
||||||
|
var state = { timestamp: timestamp, value: spacecraft[id] };
|
||||||
|
histories[id] = histories[id] || []; // Initialize
|
||||||
|
histories[id].push(state);
|
||||||
|
spacecraft["comms.sent"] += JSON.stringify(state).length;
|
||||||
|
});
|
||||||
|
listeners.forEach(function (listener) {
|
||||||
|
listener();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
updateSpacecraft();
|
||||||
|
generateTelemetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConnection(ws) {
|
||||||
|
var subscriptions = {}, // Active subscriptions for this connection
|
||||||
|
handlers = { // Handlers for specific requests
|
||||||
|
dictionary: function () {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: "dictionary",
|
||||||
|
value: dictionary
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
subscribe: function (id) {
|
||||||
|
subscriptions[id] = true;
|
||||||
|
},
|
||||||
|
unsubscribe: function (id) {
|
||||||
|
delete subscriptions[id];
|
||||||
|
},
|
||||||
|
history: function (id) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: "history",
|
||||||
|
id: id,
|
||||||
|
value: histories[id]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function notifySubscribers() {
|
||||||
|
Object.keys(subscriptions).forEach(function (id) {
|
||||||
|
var history = histories[id];
|
||||||
|
if (history) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: "data",
|
||||||
|
id: id,
|
||||||
|
value: history[history.length - 1]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for requests
|
||||||
|
ws.on('message', function (message) {
|
||||||
|
var parts = message.split(' '),
|
||||||
|
handler = handlers[parts[0]];
|
||||||
|
if (handler) {
|
||||||
|
handler.apply(handlers, parts.slice(1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop sending telemetry updates for this connection when closed
|
||||||
|
ws.on('close', function () {
|
||||||
|
listeners = listeners.filter(function (listener) {
|
||||||
|
return listener !== notifySubscribers;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notify subscribers when telemetry is updated
|
||||||
|
listeners.push(notifySubscribers);
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
setInterval(update, CONFIG.interval);
|
||||||
|
|
||||||
|
wss.on('connection', handleConnection);
|
||||||
|
|
||||||
|
console.log("Example spacecraft running on port ");
|
||||||
|
console.log("Press Enter to toggle thruster state.");
|
||||||
|
process.stdin.on('data', function (data) {
|
||||||
|
spacecraft['prop.thrusters'] =
|
||||||
|
(spacecraft['prop.thrusters'] === "OFF") ? "ON" : "OFF";
|
||||||
|
console.log("Thrusters " + spacecraft["prop.thrusters"]);
|
||||||
|
});
|
||||||
|
}());
|
66
tutorial-server/dictionary.json
Normal file
66
tutorial-server/dictionary.json
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"name": "Example Spacecraft",
|
||||||
|
"identifier": "sc",
|
||||||
|
"subsystems": [
|
||||||
|
{
|
||||||
|
"name": "Propulsion",
|
||||||
|
"identifier": "prop",
|
||||||
|
"measurements": [
|
||||||
|
{
|
||||||
|
"name": "Fuel",
|
||||||
|
"identifier": "prop.fuel",
|
||||||
|
"units": "kilograms",
|
||||||
|
"type": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Thrusters",
|
||||||
|
"identifier": "prop.thrusters",
|
||||||
|
"units": "None",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Communications",
|
||||||
|
"identifier": "comms",
|
||||||
|
"measurements": [
|
||||||
|
{
|
||||||
|
"name": "Received",
|
||||||
|
"identifier": "comms.recd",
|
||||||
|
"units": "bytes",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sent",
|
||||||
|
"identifier": "comms.sent",
|
||||||
|
"units": "bytes",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Power",
|
||||||
|
"identifier": "pwr",
|
||||||
|
"measurements": [
|
||||||
|
{
|
||||||
|
"name": "Generator Temperature",
|
||||||
|
"identifier": "pwr.temp",
|
||||||
|
"units": "\u0080C",
|
||||||
|
"type": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generator Current",
|
||||||
|
"identifier": "pwr.c",
|
||||||
|
"units": "A",
|
||||||
|
"type": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generator Voltage",
|
||||||
|
"identifier": "pwr.v",
|
||||||
|
"units": "V",
|
||||||
|
"type": "float"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
66
tutorials/bargraph/bundle.js
Normal file
66
tutorials/bargraph/bundle.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
define([
|
||||||
|
'legacyRegistry',
|
||||||
|
'./src/controllers/BarGraphController'
|
||||||
|
], function (
|
||||||
|
legacyRegistry,
|
||||||
|
BarGraphController
|
||||||
|
) {
|
||||||
|
legacyRegistry.register("tutorials/bargraph", {
|
||||||
|
"name": "Bar Graph",
|
||||||
|
"description": "Provides the Bar Graph view of telemetry elements.",
|
||||||
|
"extensions": {
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"name": "Bar Graph",
|
||||||
|
"key": "example.bargraph",
|
||||||
|
"glyph": "H",
|
||||||
|
"templateUrl": "templates/bargraph.html",
|
||||||
|
"needs": [ "telemetry" ],
|
||||||
|
"delegation": true,
|
||||||
|
"editable": true,
|
||||||
|
"toolbar": {
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "Low",
|
||||||
|
"property": "low",
|
||||||
|
"required": true,
|
||||||
|
"control": "textfield",
|
||||||
|
"size": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Middle",
|
||||||
|
"property": "middle",
|
||||||
|
"required": true,
|
||||||
|
"control": "textfield",
|
||||||
|
"size": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "High",
|
||||||
|
"property": "high",
|
||||||
|
"required": true,
|
||||||
|
"control": "textfield",
|
||||||
|
"size": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stylesheets": [
|
||||||
|
{
|
||||||
|
"stylesheetUrl": "css/bargraph.css"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "BarGraphController",
|
||||||
|
"implementation": BarGraphController,
|
||||||
|
"depends": [ "$scope", "telemetryHandler" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
35
tutorials/bargraph/res/templates/bargraph.html
Normal file
35
tutorials/bargraph/res/templates/bargraph.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<div class="example-bargraph" ng-controller="BarGraphController">
|
||||||
|
<div class="example-tick-labels">
|
||||||
|
<div ng-repeat="value in [low, middle, high] track by $index"
|
||||||
|
class="example-tick-label"
|
||||||
|
style="bottom: {{ toPercent(value) }}%">
|
||||||
|
{{value}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="example-graph-area">
|
||||||
|
<div ng-repeat="telemetryObject in telemetryObjects"
|
||||||
|
style="left: {{barWidth * $index}}%; width: {{barWidth}}%"
|
||||||
|
class="example-bar-holder">
|
||||||
|
<div class="example-bar"
|
||||||
|
ng-style="{
|
||||||
|
bottom: getBottom(telemetryObject) + '%',
|
||||||
|
top: getTop(telemetryObject) + '%'
|
||||||
|
}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="bottom: {{ toPercent(middle) }}%"
|
||||||
|
class="example-graph-tick">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="example-bar-labels">
|
||||||
|
<div ng-repeat="telemetryObject in telemetryObjects"
|
||||||
|
style="left: {{barWidth * $index}}%; width: {{barWidth}}%"
|
||||||
|
class="example-bar-holder example-label">
|
||||||
|
<mct-representation key="'label'"
|
||||||
|
mct-object="telemetryObject">
|
||||||
|
</mct-representation>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
75
tutorials/bargraph/src/controllers/BarGraphController.js
Normal file
75
tutorials/bargraph/src/controllers/BarGraphController.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
define(function () {
|
||||||
|
function BarGraphController($scope, telemetryHandler) {
|
||||||
|
var handle;
|
||||||
|
|
||||||
|
// Expose configuration constants directly in scope
|
||||||
|
function exposeConfiguration() {
|
||||||
|
$scope.low = $scope.configuration.low;
|
||||||
|
$scope.middle = $scope.configuration.middle;
|
||||||
|
$scope.high = $scope.configuration.high;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate a default value in the configuration
|
||||||
|
function setDefault(key, value) {
|
||||||
|
if ($scope.configuration[key] === undefined) {
|
||||||
|
$scope.configuration[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter-setter for configuration properties (for view proxy)
|
||||||
|
function getterSetter(property) {
|
||||||
|
return function (value) {
|
||||||
|
value = parseFloat(value);
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
$scope.configuration[property] = value;
|
||||||
|
exposeConfiguration();
|
||||||
|
}
|
||||||
|
return $scope.configuration[property];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add min/max defaults
|
||||||
|
setDefault('low', -1);
|
||||||
|
setDefault('middle', 0);
|
||||||
|
setDefault('high', 1);
|
||||||
|
exposeConfiguration($scope.configuration);
|
||||||
|
|
||||||
|
// Expose view configuration options
|
||||||
|
if ($scope.selection) {
|
||||||
|
$scope.selection.proxy({
|
||||||
|
low: getterSetter('low'),
|
||||||
|
middle: getterSetter('middle'),
|
||||||
|
high: getterSetter('high')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert value to a percent between 0-100
|
||||||
|
$scope.toPercent = function (value) {
|
||||||
|
var pct = 100 * (value - $scope.low) /
|
||||||
|
($scope.high - $scope.low);
|
||||||
|
return Math.min(100, Math.max(0, pct));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get bottom and top (as percentages) for current value
|
||||||
|
$scope.getBottom = function (telemetryObject) {
|
||||||
|
var value = handle.getRangeValue(telemetryObject);
|
||||||
|
return $scope.toPercent(Math.min($scope.middle, value));
|
||||||
|
};
|
||||||
|
$scope.getTop = function (telemetryObject) {
|
||||||
|
var value = handle.getRangeValue(telemetryObject);
|
||||||
|
return 100 - $scope.toPercent(Math.max($scope.middle, value));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the telemetryHandler to get telemetry objects here
|
||||||
|
handle = telemetryHandler.handle($scope.domainObject, function () {
|
||||||
|
$scope.telemetryObjects = handle.getTelemetryObjects();
|
||||||
|
$scope.barWidth =
|
||||||
|
100 / Math.max(($scope.telemetryObjects).length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Release subscriptions when scope is destroyed
|
||||||
|
$scope.$on('$destroy', handle.unsubscribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BarGraphController;
|
||||||
|
});
|
44
tutorials/grootprovider/groots.js
Normal file
44
tutorials/grootprovider/groots.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
define(function () {
|
||||||
|
return function grootPlugin(mct) {
|
||||||
|
var ROOT_KEY = {
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'groot'
|
||||||
|
};
|
||||||
|
|
||||||
|
var GROOT_ROOT = {
|
||||||
|
name: 'I am groot',
|
||||||
|
type: 'folder',
|
||||||
|
composition: [
|
||||||
|
{
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'arms'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'legs'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'torso'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var GrootProvider = {
|
||||||
|
get: function (key) {
|
||||||
|
if (key.identifier === 'groot') {
|
||||||
|
return Promise.resolve(GROOT_ROOT);
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
name: 'Groot\'s ' + key.identifier
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mct.Objects.addRoot(ROOT_KEY);
|
||||||
|
|
||||||
|
mct.Objects.addProvider('groot', GrootProvider);
|
||||||
|
|
||||||
|
return mct;
|
||||||
|
};
|
||||||
|
});
|
90
tutorials/telemetry/bundle.js
Normal file
90
tutorials/telemetry/bundle.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
define([
|
||||||
|
'legacyRegistry',
|
||||||
|
'./src/ExampleTelemetryServerAdapter',
|
||||||
|
'./src/ExampleTelemetryInitializer',
|
||||||
|
'./src/ExampleTelemetryModelProvider'
|
||||||
|
], function (
|
||||||
|
legacyRegistry,
|
||||||
|
ExampleTelemetryServerAdapter,
|
||||||
|
ExampleTelemetryInitializer,
|
||||||
|
ExampleTelemetryModelProvider
|
||||||
|
) {
|
||||||
|
legacyRegistry.register("tutorials/telemetry", {
|
||||||
|
"name": "Example Telemetry Adapter",
|
||||||
|
"extensions": {
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "Spacecraft",
|
||||||
|
"key": "example.spacecraft",
|
||||||
|
"glyph": "o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Subsystem",
|
||||||
|
"key": "example.subsystem",
|
||||||
|
"glyph": "o",
|
||||||
|
"model": { "composition": [] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Measurement",
|
||||||
|
"key": "example.measurement",
|
||||||
|
"glyph": "T",
|
||||||
|
"model": { "telemetry": {} },
|
||||||
|
"telemetry": {
|
||||||
|
"source": "example.source",
|
||||||
|
"domains": [
|
||||||
|
{
|
||||||
|
"name": "Time",
|
||||||
|
"key": "timestamp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"roots": [
|
||||||
|
{
|
||||||
|
"id": "example:sc",
|
||||||
|
"priority": "preferred",
|
||||||
|
"model": {
|
||||||
|
"type": "example.spacecraft",
|
||||||
|
"name": "My Spacecraft",
|
||||||
|
"composition": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"key": "example.adapter",
|
||||||
|
"implementation": "ExampleTelemetryServerAdapter.js",
|
||||||
|
"depends": [ "$q", "EXAMPLE_WS_URL" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"key": "EXAMPLE_WS_URL",
|
||||||
|
"priority": "fallback",
|
||||||
|
"value": "ws://localhost:8081"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"implementation": "ExampleTelemetryInitializer.js",
|
||||||
|
"depends": [ "example.adapter", "objectService" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"provides": "modelService",
|
||||||
|
"type": "provider",
|
||||||
|
"implementation": "ExampleTelemetryModelProvider.js",
|
||||||
|
"depends": [ "example.adapter", "$q" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"provides": "telemetryService",
|
||||||
|
"type": "provider",
|
||||||
|
"implementation": "ExampleTelemetryProvider.js",
|
||||||
|
"depends": [ "example.adapter", "$q" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
47
tutorials/telemetry/src/ExampleTelemetryInitializer.js
Normal file
47
tutorials/telemetry/src/ExampleTelemetryInitializer.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
define(
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var TAXONOMY_ID = "example:sc",
|
||||||
|
PREFIX = "example_tlm:";
|
||||||
|
|
||||||
|
function ExampleTelemetryInitializer(adapter, objectService) {
|
||||||
|
// Generate a domain object identifier for a dictionary element
|
||||||
|
function makeId(element) {
|
||||||
|
return PREFIX + element.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the dictionary is available, add all subsystems
|
||||||
|
// to the composition of My Spacecraft
|
||||||
|
function initializeTaxonomy(dictionary) {
|
||||||
|
// Get the top-level container for dictionary objects
|
||||||
|
// from a group of domain objects.
|
||||||
|
function getTaxonomyObject(domainObjects) {
|
||||||
|
return domainObjects[TAXONOMY_ID];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate
|
||||||
|
function populateModel(taxonomyObject) {
|
||||||
|
return taxonomyObject.useCapability(
|
||||||
|
"mutation",
|
||||||
|
function (model) {
|
||||||
|
model.name =
|
||||||
|
dictionary.name;
|
||||||
|
model.composition =
|
||||||
|
dictionary.subsystems.map(makeId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up My Spacecraft, and populate it accordingly.
|
||||||
|
objectService.getObjects([TAXONOMY_ID])
|
||||||
|
.then(getTaxonomyObject)
|
||||||
|
.then(populateModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.dictionary().then(initializeTaxonomy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetryInitializer;
|
||||||
|
}
|
||||||
|
);
|
78
tutorials/telemetry/src/ExampleTelemetryModelProvider.js
Normal file
78
tutorials/telemetry/src/ExampleTelemetryModelProvider.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
define(
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var PREFIX = "example_tlm:",
|
||||||
|
FORMAT_MAPPINGS = {
|
||||||
|
float: "number",
|
||||||
|
integer: "number",
|
||||||
|
string: "string"
|
||||||
|
};
|
||||||
|
|
||||||
|
function ExampleTelemetryModelProvider(adapter, $q) {
|
||||||
|
var modelPromise, empty = $q.when({});
|
||||||
|
|
||||||
|
// Check if this model is in our dictionary (by prefix)
|
||||||
|
function isRelevant(id) {
|
||||||
|
return id.indexOf(PREFIX) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a domain object identifier by adding a prefix
|
||||||
|
function makeId(element) {
|
||||||
|
return PREFIX + element.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create domain object models from this dictionary
|
||||||
|
function buildTaxonomy(dictionary) {
|
||||||
|
var models = {};
|
||||||
|
|
||||||
|
// Create & store a domain object model for a measurement
|
||||||
|
function addMeasurement(measurement) {
|
||||||
|
var format = FORMAT_MAPPINGS[measurement.type];
|
||||||
|
models[makeId(measurement)] = {
|
||||||
|
type: "example.measurement",
|
||||||
|
name: measurement.name,
|
||||||
|
telemetry: {
|
||||||
|
key: measurement.identifier,
|
||||||
|
ranges: [{
|
||||||
|
key: "value",
|
||||||
|
name: "Value",
|
||||||
|
units: measurement.units,
|
||||||
|
format: format
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create & store a domain object model for a subsystem
|
||||||
|
function addSubsystem(subsystem) {
|
||||||
|
var measurements =
|
||||||
|
(subsystem.measurements || []);
|
||||||
|
models[makeId(subsystem)] = {
|
||||||
|
type: "example.subsystem",
|
||||||
|
name: subsystem.name,
|
||||||
|
composition: measurements.map(makeId)
|
||||||
|
};
|
||||||
|
measurements.forEach(addMeasurement);
|
||||||
|
}
|
||||||
|
|
||||||
|
(dictionary.subsystems || []).forEach(addSubsystem);
|
||||||
|
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin generating models once the dictionary is available
|
||||||
|
modelPromise = adapter.dictionary().then(buildTaxonomy);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getModels: function (ids) {
|
||||||
|
// Return models for the dictionary only when they
|
||||||
|
// are relevant to the request.
|
||||||
|
return ids.some(isRelevant) ? modelPromise : empty;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetryModelProvider;
|
||||||
|
}
|
||||||
|
);
|
80
tutorials/telemetry/src/ExampleTelemetryProvider.js
Normal file
80
tutorials/telemetry/src/ExampleTelemetryProvider.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
define(
|
||||||
|
['./ExampleTelemetrySeries'],
|
||||||
|
function (ExampleTelemetrySeries) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var SOURCE = "example.source";
|
||||||
|
|
||||||
|
function ExampleTelemetryProvider(adapter, $q) {
|
||||||
|
var subscribers = {};
|
||||||
|
|
||||||
|
// Used to filter out requests for telemetry
|
||||||
|
// from some other source
|
||||||
|
function matchesSource(request) {
|
||||||
|
return (request.source === SOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for data, notify subscribers
|
||||||
|
adapter.listen(function (message) {
|
||||||
|
var packaged = {};
|
||||||
|
packaged[SOURCE] = {};
|
||||||
|
packaged[SOURCE][message.id] =
|
||||||
|
new ExampleTelemetrySeries([message.value]);
|
||||||
|
(subscribers[message.id] || []).forEach(function (cb) {
|
||||||
|
cb(packaged);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
requestTelemetry: function (requests) {
|
||||||
|
var packaged = {},
|
||||||
|
relevantReqs = requests.filter(matchesSource);
|
||||||
|
|
||||||
|
// Package historical telemetry that has been received
|
||||||
|
function addToPackage(history) {
|
||||||
|
packaged[SOURCE][history.id] =
|
||||||
|
new ExampleTelemetrySeries(history.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve telemetry for a specific measurement
|
||||||
|
function handleRequest(request) {
|
||||||
|
var key = request.key;
|
||||||
|
return adapter.history(key).then(addToPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
packaged[SOURCE] = {};
|
||||||
|
return $q.all(relevantReqs.map(handleRequest))
|
||||||
|
.then(function () { return packaged; });
|
||||||
|
},
|
||||||
|
subscribe: function (callback, requests) {
|
||||||
|
var keys = requests.filter(matchesSource)
|
||||||
|
.map(function (req) { return req.key; });
|
||||||
|
|
||||||
|
function notCallback(cb) {
|
||||||
|
return cb !== callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsubscribe(key) {
|
||||||
|
subscribers[key] =
|
||||||
|
(subscribers[key] || []).filter(notCallback);
|
||||||
|
if (subscribers[key].length < 1) {
|
||||||
|
adapter.unsubscribe(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.forEach(function (key) {
|
||||||
|
subscribers[key] = subscribers[key] || [];
|
||||||
|
adapter.subscribe(key);
|
||||||
|
subscribers[key].push(callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
keys.forEach(unsubscribe);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetryProvider;
|
||||||
|
}
|
||||||
|
);
|
23
tutorials/telemetry/src/ExampleTelemetrySeries.js
Normal file
23
tutorials/telemetry/src/ExampleTelemetrySeries.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function ExampleTelemetrySeries(data) {
|
||||||
|
return {
|
||||||
|
getPointCount: function () {
|
||||||
|
return data.length;
|
||||||
|
},
|
||||||
|
getDomainValue: function (index) {
|
||||||
|
return (data[index] || {}).timestamp;
|
||||||
|
},
|
||||||
|
getRangeValue: function (index) {
|
||||||
|
return (data[index] || {}).value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetrySeries;
|
||||||
|
}
|
||||||
|
);
|
60
tutorials/telemetry/src/ExampleTelemetryServerAdapter.js
Normal file
60
tutorials/telemetry/src/ExampleTelemetryServerAdapter.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function ExampleTelemetryServerAdapter($q, wsUrl) {
|
||||||
|
var ws = new WebSocket(wsUrl),
|
||||||
|
histories = {},
|
||||||
|
listeners = [],
|
||||||
|
dictionary = $q.defer();
|
||||||
|
|
||||||
|
// Handle an incoming message from the server
|
||||||
|
ws.onmessage = function (event) {
|
||||||
|
var message = JSON.parse(event.data);
|
||||||
|
|
||||||
|
switch (message.type) {
|
||||||
|
case "dictionary":
|
||||||
|
dictionary.resolve(message.value);
|
||||||
|
break;
|
||||||
|
case "history":
|
||||||
|
histories[message.id].resolve(message);
|
||||||
|
delete histories[message.id];
|
||||||
|
break;
|
||||||
|
case "data":
|
||||||
|
listeners.forEach(function (listener) {
|
||||||
|
listener(message);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Request dictionary once connection is established
|
||||||
|
ws.onopen = function () {
|
||||||
|
ws.send("dictionary");
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
dictionary: function () {
|
||||||
|
return dictionary.promise;
|
||||||
|
},
|
||||||
|
history: function (id) {
|
||||||
|
histories[id] = histories[id] || $q.defer();
|
||||||
|
ws.send("history " + id);
|
||||||
|
return histories[id].promise;
|
||||||
|
},
|
||||||
|
subscribe: function (id) {
|
||||||
|
ws.send("subscribe " + id);
|
||||||
|
},
|
||||||
|
unsubscribe: function (id) {
|
||||||
|
ws.send("unsubscribe " + id);
|
||||||
|
},
|
||||||
|
listen: function (callback) {
|
||||||
|
listeners.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetryServerAdapter;
|
||||||
|
}
|
||||||
|
);
|
59
tutorials/todo/bundle.js
Normal file
59
tutorials/todo/bundle.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
define([
|
||||||
|
'legacyRegistry',
|
||||||
|
'./src/controllers/TodoController'
|
||||||
|
], function (
|
||||||
|
legacyRegistry,
|
||||||
|
TodoController
|
||||||
|
) {
|
||||||
|
legacyRegistry.register("tutorials/todo", {
|
||||||
|
"name": "To-do Plugin",
|
||||||
|
"description": "Allows creating and editing to-do lists.",
|
||||||
|
"extensions": {
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"key": "example.todo",
|
||||||
|
"type": "example.todo",
|
||||||
|
"glyph": "2",
|
||||||
|
"name": "List",
|
||||||
|
"templateUrl": "templates/todo.html",
|
||||||
|
"editable": true,
|
||||||
|
"toolbar": {
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"text": "Add Task",
|
||||||
|
"glyph": "+",
|
||||||
|
"method": "addTask",
|
||||||
|
"control": "button"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"glyph": "Z",
|
||||||
|
"method": "removeTask",
|
||||||
|
"control": "button"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "TodoController",
|
||||||
|
"implementation": TodoController,
|
||||||
|
"depends": [ "$scope", "dialogService" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stylesheets": [
|
||||||
|
{
|
||||||
|
"stylesheetUrl": "css/todo.css"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
29
tutorials/todo/res/templates/todo.html
Normal file
29
tutorials/todo/res/templates/todo.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<div ng-controller="TodoController" class="example-todo">
|
||||||
|
<div class="example-button-group">
|
||||||
|
<a ng-class="{ selected: checkVisibility(true) }"
|
||||||
|
ng-click="setVisibility(true)">All</a>
|
||||||
|
<a ng-class="{ selected: checkVisibility(false, false) }"
|
||||||
|
ng-click="setVisibility(false, false)">Incomplete</a>
|
||||||
|
<a ng-class="{ selected: checkVisibility(false, true) }"
|
||||||
|
ng-click="setVisibility(false, true)">Complete</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="task in model.tasks"
|
||||||
|
ng-class="{ 'example-task-completed': task.completed }"
|
||||||
|
ng-if="showTask(task)">
|
||||||
|
|
||||||
|
<input type="checkbox"
|
||||||
|
ng-checked="task.completed"
|
||||||
|
ng-click="toggleCompletion($index)">
|
||||||
|
<span ng-click="selectTask($index)"
|
||||||
|
ng-class="{ selected: isSelected($index) }"
|
||||||
|
class="example-task-description">
|
||||||
|
{{task.description}}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div ng-if="model.tasks.length < 1" class="example-message">
|
||||||
|
There are no tasks to show.
|
||||||
|
</div>
|
||||||
|
</div>
|
97
tutorials/todo/src/controllers/TodoController.js
Normal file
97
tutorials/todo/src/controllers/TodoController.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
define(function () {
|
||||||
|
// Form to display when adding new tasks
|
||||||
|
var NEW_TASK_FORM = {
|
||||||
|
name: "Add a Task",
|
||||||
|
sections: [{
|
||||||
|
rows: [{
|
||||||
|
name: 'Description',
|
||||||
|
key: 'description',
|
||||||
|
control: 'textfield',
|
||||||
|
required: true
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
function TodoController($scope, dialogService) {
|
||||||
|
var showAll = true,
|
||||||
|
showCompleted;
|
||||||
|
|
||||||
|
// Persist changes made to a domain object's model
|
||||||
|
function persist() {
|
||||||
|
var persistence =
|
||||||
|
$scope.domainObject.getCapability('persistence');
|
||||||
|
return persistence && persistence.persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a task
|
||||||
|
function removeTaskAtIndex(taskIndex) {
|
||||||
|
$scope.domainObject.useCapability('mutation', function (model) {
|
||||||
|
model.tasks.splice(taskIndex, 1);
|
||||||
|
});
|
||||||
|
persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a task
|
||||||
|
function addNewTask(task) {
|
||||||
|
$scope.domainObject.useCapability('mutation', function (model) {
|
||||||
|
model.tasks.push(task);
|
||||||
|
});
|
||||||
|
persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change which tasks are visible
|
||||||
|
$scope.setVisibility = function (all, completed) {
|
||||||
|
showAll = all;
|
||||||
|
showCompleted = completed;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if current visibility settings match
|
||||||
|
$scope.checkVisibility = function (all, completed) {
|
||||||
|
return showAll ? all : (completed === showCompleted);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle the completion state of a task
|
||||||
|
$scope.toggleCompletion = function (taskIndex) {
|
||||||
|
$scope.domainObject.useCapability('mutation', function (model) {
|
||||||
|
var task = model.tasks[taskIndex];
|
||||||
|
task.completed = !task.completed;
|
||||||
|
});
|
||||||
|
persist();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check whether a task should be visible
|
||||||
|
$scope.showTask = function (task) {
|
||||||
|
return showAll || (showCompleted === !!(task.completed));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle selection state in edit mode
|
||||||
|
if ($scope.selection) {
|
||||||
|
// Expose the ability to select tasks
|
||||||
|
$scope.selectTask = function (taskIndex) {
|
||||||
|
$scope.selection.select({
|
||||||
|
removeTask: function () {
|
||||||
|
removeTaskAtIndex(taskIndex);
|
||||||
|
$scope.selection.deselect();
|
||||||
|
},
|
||||||
|
taskIndex: taskIndex
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Expose a check for current selection state
|
||||||
|
$scope.isSelected = function (taskIndex) {
|
||||||
|
return ($scope.selection.get() || {}).taskIndex ===
|
||||||
|
taskIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Expose a view-level selection proxy
|
||||||
|
$scope.selection.proxy({
|
||||||
|
addTask: function () {
|
||||||
|
dialogService.getUserInput(NEW_TASK_FORM, {})
|
||||||
|
.then(addNewTask);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TodoController;
|
||||||
|
});
|
19
tutorials/todo/todo.js
Normal file
19
tutorials/todo/todo.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
define(function () {
|
||||||
|
return function todoPlugin(mct) {
|
||||||
|
var todoType = new mct.Type({
|
||||||
|
metadata: {
|
||||||
|
label: "To-Do List",
|
||||||
|
glyph: "2",
|
||||||
|
description: "A list of things that need to be done."
|
||||||
|
},
|
||||||
|
initialize: function (model) {
|
||||||
|
model.tasks = [];
|
||||||
|
},
|
||||||
|
creatable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
mct.type('example.todo', todoType);
|
||||||
|
|
||||||
|
return mct;
|
||||||
|
};
|
||||||
|
});
|
Reference in New Issue
Block a user