Compare commits
78 Commits
tc-api-tak
...
api-type-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
580a4e52b5 | ||
|
|
9c4e17bfab | ||
|
|
d3e5d95d6b | ||
|
|
c70793ac2d | ||
|
|
a6ef1d3423 | ||
|
|
4ca2f51d5e | ||
|
|
86ac80ddbd | ||
|
|
0525ba6b0b | ||
|
|
a79e958ffc | ||
|
|
03cb0ccb57 | ||
|
|
7205faa6bb | ||
|
|
136f2ae785 | ||
|
|
a07e2fb8e5 | ||
|
|
55b531bdeb | ||
|
|
7ece5897e8 | ||
|
|
a29c7a6eab | ||
|
|
c4fec1af6a | ||
|
|
a6996df3df | ||
|
|
0c660238f2 | ||
|
|
b73b824e55 | ||
|
|
1954d98628 | ||
|
|
7aa034ce23 | ||
|
|
385dc5d298 | ||
|
|
a88b4b31a1 | ||
|
|
04112956cf | ||
|
|
f1113fda24 | ||
|
|
33c208d8fe | ||
|
|
c557fb6cd5 | ||
|
|
bde2bc7709 | ||
|
|
5fe759aa91 | ||
|
|
a5b7badb95 | ||
|
|
eefd4c8669 | ||
|
|
7c11f2db4f | ||
|
|
7501f679f7 | ||
|
|
52e087d8f8 | ||
|
|
c5cd495fce | ||
|
|
37a417051d | ||
|
|
97d819739c | ||
|
|
3935378b0c | ||
|
|
952f95aa4c | ||
|
|
0a75a5be1f | ||
|
|
70b593e28a | ||
|
|
ed69a65f9b | ||
|
|
05b4f5401e | ||
|
|
2ff0c7b06a | ||
|
|
2330f1d135 | ||
|
|
ff92d3acab | ||
|
|
32d7187db6 | ||
|
|
00534f8af7 | ||
|
|
3795570938 | ||
|
|
362248a02e | ||
|
|
16d20eabd2 | ||
|
|
eb5566f041 | ||
|
|
757da1dff4 | ||
|
|
85432af187 | ||
|
|
f0ab817e87 | ||
|
|
96af931c0b | ||
|
|
9a5209f7c2 | ||
|
|
0818a7cda0 | ||
|
|
f35947361c | ||
|
|
9a8bcc0550 | ||
|
|
eff46b076c | ||
|
|
ab64b682c3 | ||
|
|
1c007ea256 | ||
|
|
6c1412784b | ||
|
|
6e7f4df5e3 | ||
|
|
5eff4e45c9 | ||
|
|
70a13a75d5 | ||
|
|
69cc1086df | ||
|
|
3d891073da | ||
|
|
73c2c01def | ||
|
|
7c82e31b66 | ||
|
|
2829b8d495 | ||
|
|
7ade873365 | ||
|
|
8788523c25 | ||
|
|
c301523156 | ||
|
|
2e8604e18d | ||
|
|
cae85f3e30 |
@@ -18,6 +18,7 @@
|
||||
"node-uuid": "^1.4.7",
|
||||
"comma-separated-values": "^3.6.4",
|
||||
"FileSaver.js": "^0.0.2",
|
||||
"zepto": "^1.1.6"
|
||||
"zepto": "^1.1.6",
|
||||
"eventemitter3": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ deployment:
|
||||
test:
|
||||
post:
|
||||
- gulp lint
|
||||
- gulp checkstyle
|
||||
|
||||
general:
|
||||
branches:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,11 +45,12 @@ define(
|
||||
function buildTaxonomy(dictionary){
|
||||
var models = {};
|
||||
|
||||
function addMeasurement(measurement){
|
||||
function addMeasurement(measurement, parent){
|
||||
var format = FORMAT_MAPPINGS[measurement.type];
|
||||
models[makeId(measurement)] = {
|
||||
type: "msl.measurement",
|
||||
name: measurement.name,
|
||||
location: parent,
|
||||
telemetry: {
|
||||
key: measurement.identifier,
|
||||
ranges: [{
|
||||
@@ -62,17 +63,24 @@ define(
|
||||
};
|
||||
}
|
||||
|
||||
function addInstrument(subsystem) {
|
||||
var measurements = (subsystem.measurements || []);
|
||||
models[makeId(subsystem)] = {
|
||||
function addInstrument(subsystem, spacecraftId) {
|
||||
var measurements = (subsystem.measurements || []),
|
||||
instrumentId = makeId(subsystem);
|
||||
|
||||
models[instrumentId] = {
|
||||
type: "msl.instrument",
|
||||
name: subsystem.name,
|
||||
location: spacecraftId,
|
||||
composition: measurements.map(makeId)
|
||||
};
|
||||
measurements.forEach(addMeasurement);
|
||||
measurements.forEach(function(measurement) {
|
||||
addMeasurement(measurement, instrumentId);
|
||||
});
|
||||
}
|
||||
|
||||
(dictionary.instruments || []).forEach(addInstrument);
|
||||
(dictionary.instruments || []).forEach(function(instrument) {
|
||||
addInstrument(instrument, "msl:curiosity");
|
||||
});
|
||||
return models;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,15 @@
|
||||
<script type="text/javascript">
|
||||
require(['main'], function (mct) {
|
||||
require([
|
||||
'./tutorials/todo/todo',
|
||||
'./tutorials/todo/bundle',
|
||||
'./example/imagery/bundle',
|
||||
'./example/eventGenerator/bundle',
|
||||
'./example/generator/bundle'
|
||||
], mct.run.bind(mct));
|
||||
], function (todoPlugin) {
|
||||
todoPlugin(mct);
|
||||
mct.start();
|
||||
})
|
||||
});
|
||||
</script>
|
||||
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
||||
|
||||
24
main.js
24
main.js
@@ -28,6 +28,7 @@ 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",
|
||||
@@ -43,6 +44,9 @@ requirejs.config({
|
||||
"angular-route": {
|
||||
"deps": ["angular"]
|
||||
},
|
||||
"EventEmitter": {
|
||||
"exports": "EventEmitter"
|
||||
},
|
||||
"moment-duration-format": {
|
||||
"deps": ["moment"]
|
||||
},
|
||||
@@ -58,6 +62,9 @@ requirejs.config({
|
||||
define([
|
||||
'./platform/framework/src/Main',
|
||||
'legacyRegistry',
|
||||
'./src/MCT',
|
||||
|
||||
'./src/adapter/bundle',
|
||||
|
||||
'./platform/framework/bundle',
|
||||
'./platform/core/bundle',
|
||||
@@ -93,11 +100,14 @@ define([
|
||||
'./platform/search/bundle',
|
||||
'./platform/status/bundle',
|
||||
'./platform/commonUI/regions/bundle'
|
||||
], function (Main, legacyRegistry) {
|
||||
return {
|
||||
legacyRegistry: legacyRegistry,
|
||||
run: function () {
|
||||
return new Main().run(legacyRegistry);
|
||||
}
|
||||
};
|
||||
], function (Main, legacyRegistry, MCT) {
|
||||
var mct = new MCT();
|
||||
|
||||
mct.legacyRegistry = legacyRegistry;
|
||||
mct.run = mct.start;
|
||||
mct.on('start', function () {
|
||||
return new Main().run(legacyRegistry);
|
||||
});
|
||||
|
||||
return mct;
|
||||
});
|
||||
|
||||
@@ -24,23 +24,14 @@ 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",
|
||||
@@ -53,23 +44,14 @@ define([
|
||||
BrowseController,
|
||||
PaneController,
|
||||
BrowseObjectController,
|
||||
CreateMenuController,
|
||||
LocatorController,
|
||||
MenuArrowController,
|
||||
NavigationService,
|
||||
CreationPolicy,
|
||||
NavigateAction,
|
||||
NewTabAction,
|
||||
FullscreenAction,
|
||||
CreateActionProvider,
|
||||
AddActionProvider,
|
||||
CreationService,
|
||||
WindowTitler,
|
||||
browseTemplate,
|
||||
locatorTemplate,
|
||||
browseObjectTemplate,
|
||||
createButtonTemplate,
|
||||
createMenuTemplate,
|
||||
gridItemTemplate,
|
||||
objectHeaderTemplate,
|
||||
menuArrowTemplate,
|
||||
@@ -136,22 +118,6 @@ define([
|
||||
"$route"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CreateMenuController",
|
||||
"implementation": CreateMenuController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "LocatorController",
|
||||
"implementation": LocatorController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$timeout",
|
||||
"objectService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "MenuArrowController",
|
||||
"implementation": MenuArrowController,
|
||||
@@ -160,12 +126,6 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"key": "locator",
|
||||
"template": locatorTemplate
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
{
|
||||
"key": "view-object",
|
||||
@@ -181,17 +141,6 @@ define([
|
||||
"view"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "create-button",
|
||||
"template": createButtonTemplate
|
||||
},
|
||||
{
|
||||
"key": "create-menu",
|
||||
"template": createMenuTemplate,
|
||||
"uses": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "grid-item",
|
||||
"template": gridItemTemplate,
|
||||
@@ -244,12 +193,6 @@ define([
|
||||
"implementation": NavigationService
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"implementation": CreationPolicy,
|
||||
"category": "creation"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"key": "navigate",
|
||||
@@ -302,42 +245,6 @@ 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,
|
||||
|
||||
@@ -1,130 +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,
|
||||
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,6 +43,15 @@ 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",
|
||||
@@ -72,6 +81,15 @@ define([
|
||||
EditorCapability,
|
||||
TransactionCapabilityDecorator,
|
||||
TransactionService,
|
||||
CreateMenuController,
|
||||
LocatorController,
|
||||
CreationPolicy,
|
||||
CreateActionProvider,
|
||||
AddActionProvider,
|
||||
CreationService,
|
||||
locatorTemplate,
|
||||
createButtonTemplate,
|
||||
createMenuTemplate,
|
||||
libraryTemplate,
|
||||
editObjectTemplate,
|
||||
editActionButtonsTemplate,
|
||||
@@ -112,6 +130,22 @@ define([
|
||||
"$location",
|
||||
"policyService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CreateMenuController",
|
||||
"implementation": CreateMenuController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "LocatorController",
|
||||
"implementation": LocatorController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$timeout",
|
||||
"objectService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
@@ -219,10 +253,13 @@ define([
|
||||
},
|
||||
{
|
||||
"category": "navigation",
|
||||
"message": "There are unsaved changes.",
|
||||
"message": "Continuing will cause the loss of any unsaved changes.",
|
||||
"implementation": EditNavigationPolicy
|
||||
},
|
||||
{
|
||||
"implementation": CreationPolicy,
|
||||
"category": "creation"
|
||||
}
|
||||
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
@@ -261,6 +298,17 @@ define([
|
||||
{
|
||||
"key": "topbar-edit",
|
||||
"template": topbarEditTemplate
|
||||
},
|
||||
{
|
||||
"key": "create-button",
|
||||
"template": createButtonTemplate
|
||||
},
|
||||
{
|
||||
"key": "create-menu",
|
||||
"template": createMenuTemplate,
|
||||
"uses": [
|
||||
"action"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
@@ -282,7 +330,40 @@ 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": [
|
||||
{
|
||||
@@ -316,6 +397,12 @@ define([
|
||||
"transactionService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"key": "locator",
|
||||
"template": locatorTemplate
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -74,6 +74,12 @@ 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(
|
||||
['../../../browse/src/creation/CreateWizard'],
|
||||
['../creation/CreateWizard'],
|
||||
function (CreateWizard) {
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,11 +43,8 @@ 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, $q, navigationService) {
|
||||
function CreateAction(type, parent, context) {
|
||||
this.metadata = {
|
||||
key: 'create',
|
||||
glyph: type.getGlyph(),
|
||||
@@ -56,24 +53,8 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,26 +63,31 @@ define(
|
||||
*/
|
||||
CreateAction.prototype.perform = function () {
|
||||
var newModel = this.type.getInitialModel(),
|
||||
parentObject = this.navigationService.getNavigation(),
|
||||
editorCapability,
|
||||
newObject;
|
||||
newObject,
|
||||
editAction,
|
||||
editorCapability;
|
||||
|
||||
function onSave() {
|
||||
return editorCapability.save();
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
return editorCapability.cancel();
|
||||
}
|
||||
|
||||
newModel.type = this.type.getKey();
|
||||
newModel.location = parentObject.getId();
|
||||
newObject = parentObject.useCapability('instantiation', newModel);
|
||||
newModel.location = this.parent.getId();
|
||||
newObject = this.parent.useCapability('instantiation', newModel);
|
||||
editorCapability = newObject.hasCapability('editor') && newObject.getCapability("editor");
|
||||
|
||||
editorCapability = newObject.getCapability("editor");
|
||||
|
||||
if (countEditableViews(newObject) > 0 && newObject.hasCapability('composition')) {
|
||||
this.navigationService.setNavigation(newObject);
|
||||
return newObject.getCapability("action").perform("edit");
|
||||
} else {
|
||||
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.edit();
|
||||
return newObject.useCapability("action").perform("save").then(function () {
|
||||
return editorCapability.save();
|
||||
}, function () {
|
||||
return editorCapability.cancel();
|
||||
});
|
||||
return newObject.getCapability("action").perform("save").then(onSave, onCancel);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,10 +44,8 @@ define(
|
||||
* introduced in this bundle), responsible for handling actual
|
||||
* object creation.
|
||||
*/
|
||||
function CreateActionProvider($q, typeService, navigationService, policyService) {
|
||||
function CreateActionProvider(typeService, policyService) {
|
||||
this.typeService = typeService;
|
||||
this.navigationService = navigationService;
|
||||
this.$q = $q;
|
||||
this.policyService = policyService;
|
||||
}
|
||||
|
||||
@@ -72,9 +70,7 @@ define(
|
||||
return new CreateAction(
|
||||
type,
|
||||
destination,
|
||||
context,
|
||||
self.$q,
|
||||
self.navigationService
|
||||
context
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -56,7 +56,10 @@ 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 === 'plot' && type.getKey() === 'telemetry.panel') ||
|
||||
(view.key === 'table' && type.getKey() === 'table') ||
|
||||
(view.key === 'rt-table' && type.getKey() === 'rttable')
|
||||
) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,13 +29,10 @@ define(
|
||||
|
||||
describe("The create action provider", function () {
|
||||
var mockTypeService,
|
||||
mockDialogService,
|
||||
mockNavigationService,
|
||||
mockPolicyService,
|
||||
mockCreationPolicy,
|
||||
mockPolicyMap = {},
|
||||
mockTypes,
|
||||
mockQ,
|
||||
provider;
|
||||
|
||||
function createMockType(name) {
|
||||
@@ -61,14 +58,6 @@ define(
|
||||
"typeService",
|
||||
["listTypes"]
|
||||
);
|
||||
mockDialogService = jasmine.createSpyObj(
|
||||
"dialogService",
|
||||
["getUserInput"]
|
||||
);
|
||||
mockNavigationService = jasmine.createSpyObj(
|
||||
"navigationService",
|
||||
["setNavigation"]
|
||||
);
|
||||
mockPolicyService = jasmine.createSpyObj(
|
||||
"policyService",
|
||||
["allow"]
|
||||
@@ -91,9 +80,7 @@ define(
|
||||
mockTypeService.listTypes.andReturn(mockTypes);
|
||||
|
||||
provider = new CreateActionProvider(
|
||||
mockQ,
|
||||
mockTypeService,
|
||||
mockNavigationService,
|
||||
mockPolicyService
|
||||
);
|
||||
});
|
||||
190
platform/commonUI/edit/test/creation/CreateActionSpec.js
Normal file
190
platform/commonUI/edit/test/creation/CreateActionSpec.js
Normal file
@@ -0,0 +1,190 @@
|
||||
/*****************************************************************************
|
||||
* 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -145,3 +145,8 @@
|
||||
.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, 20px, 20px);
|
||||
$ueTimeControlH: (33px, 18px, 20px);
|
||||
// Panes
|
||||
$ueBrowseLeftPaneTreeMinW: 150px;
|
||||
$ueBrowseLeftPaneTreeMaxW: 35%;
|
||||
|
||||
@@ -63,9 +63,10 @@ input, textarea {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
input[type="text"],
|
||||
input[type="search"] {
|
||||
vertical-align: baseline;
|
||||
padding: 3px 5px !important;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
|
||||
@@ -139,6 +139,17 @@
|
||||
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%,
|
||||
@@ -322,13 +333,13 @@
|
||||
color: $fg;
|
||||
outline: none;
|
||||
&.error {
|
||||
background: rgba(red, 0.5);
|
||||
background-color: $colorFormFieldErrorBg;
|
||||
color: $colorFormFieldErrorFg;
|
||||
}
|
||||
}
|
||||
|
||||
@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($sliderKnobW);
|
||||
@include border-left-radius($sliderKnobR);
|
||||
cursor: w-resize;
|
||||
}
|
||||
.knob-r {
|
||||
@include border-right-radius($sliderKnobW);
|
||||
@include border-right-radius($sliderKnobR);
|
||||
cursor: e-resize;
|
||||
}
|
||||
.range {
|
||||
@@ -426,7 +426,6 @@ input[type="search"] {
|
||||
@include user-select(none);
|
||||
font-size: 0.8rem;
|
||||
padding: $interiorMarginLg !important;
|
||||
width: 230px;
|
||||
.l-month-year-pager {
|
||||
$pagerW: 20px;
|
||||
height: $r1H;
|
||||
@@ -518,6 +517,19 @@ 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,25 +10,24 @@
|
||||
$knobHOffset: 0px;
|
||||
$knobM: ($sliderKnobW + $knobHOffset) * -1;
|
||||
$rangeValPad: $interiorMargin;
|
||||
$rangeValOffset: $sliderKnobW;
|
||||
$timeRangeSliderLROffset: 130px + $sliderKnobW + $rangeValOffset;
|
||||
$r1H: nth($ueTimeControlH,1);
|
||||
$rangeValOffset: $sliderKnobW + $interiorMargin;
|
||||
$timeRangeSliderLROffset: 150px + ($sliderKnobW * 2);
|
||||
$r1H: nth($ueTimeControlH,1); // Not currently used
|
||||
$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;
|
||||
top: auto;
|
||||
position: relative;
|
||||
&:not(:first-child) {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
.l-time-range-slider,
|
||||
.l-time-range-ticks {
|
||||
@@ -37,14 +36,21 @@
|
||||
}
|
||||
|
||||
.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,
|
||||
.l-time-range-input-w,
|
||||
.l-time-range-inputs-elem {
|
||||
margin-right: $interiorMargin;
|
||||
.lbl {
|
||||
@@ -52,13 +58,27 @@
|
||||
}
|
||||
.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; bottom: $r3H + ($interiorMarginSm * 1);
|
||||
height: $r2H;
|
||||
.range-holder {
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
@@ -73,24 +93,13 @@
|
||||
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: 2px;
|
||||
}
|
||||
&:after {
|
||||
// Circle element
|
||||
border-radius: $myW;
|
||||
@include transform(translateY(-50%));
|
||||
top: 50%; right: 0; bottom: auto; left: 0;
|
||||
width: auto;
|
||||
height: $myW;
|
||||
width: 1px;
|
||||
}
|
||||
}
|
||||
&:hover .toi-line {
|
||||
@@ -126,9 +135,9 @@
|
||||
@include webkitProp(transform, translateX(-50%));
|
||||
color: $colorPlotLabelFg;
|
||||
display: inline-block;
|
||||
font-size: 0.9em;
|
||||
font-size: 0.7rem;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
top: 5px;
|
||||
white-space: nowrap;
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -138,16 +147,29 @@
|
||||
|
||||
.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);
|
||||
padding: 0 $rangeValOffset;
|
||||
font-size: 0.7rem;
|
||||
position: absolute;
|
||||
height: $r2H;
|
||||
line-height: $r2H;
|
||||
white-space: nowrap;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
}
|
||||
&:hover .range-value {
|
||||
color: $sliderColorKnobHov;
|
||||
&:hover {
|
||||
.range-value {
|
||||
color: $sliderColorKnobHov;
|
||||
}
|
||||
}
|
||||
&.knob-l {
|
||||
margin-left: $knobM;
|
||||
@@ -170,7 +192,7 @@
|
||||
.l-time-domain-selector {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 46px;
|
||||
top: $interiorMargin;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -181,174 +203,64 @@
|
||||
padding: 1px 1px 0 $interiorMargin;
|
||||
}
|
||||
|
||||
/******************************************************************** MOBILE */
|
||||
|
||||
@include phoneandtablet {
|
||||
.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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.l-time-controller {
|
||||
min-width: 0;
|
||||
.l-time-range-slider-holder,
|
||||
.l-time-range-ticks-holder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include phone {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.l-time-controller {
|
||||
.l-time-range-inputs-holder {
|
||||
&.l-flex-row,
|
||||
.l-flex-row {
|
||||
@include align-items(flex-start);
|
||||
}
|
||||
.l-time-range-inputs-elem {
|
||||
&.type-icon {
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
.t-inputs-w {
|
||||
@include flex-direction(column);
|
||||
.l-time-range-input-w:not(:first-child) {
|
||||
&:not(:first-child) {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
margin-right: 0;
|
||||
}
|
||||
.l-time-range-inputs-elem {
|
||||
&.lbl { display: none; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@include 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
|
||||
.holder.holder-treeview-elements {
|
||||
top: $bodyMargin;
|
||||
right: 0;
|
||||
bottom: $bodyMargin;
|
||||
bottom: $interiorMargin;
|
||||
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: $bodyMargin;
|
||||
bottom: $interiorMargin;
|
||||
}
|
||||
.holder-inspector {
|
||||
top: $bodyMargin;
|
||||
bottom: $bodyMargin;
|
||||
bottom: $interiorMargin;
|
||||
left: $bodyMargin;
|
||||
right: $bodyMargin;
|
||||
}
|
||||
.holder-elements {
|
||||
top: 0;
|
||||
bottom: $bodyMargin;
|
||||
bottom: $interiorMargin;
|
||||
left: $bodyMargin;
|
||||
right: $bodyMargin;
|
||||
}
|
||||
|
||||
@@ -19,18 +19,17 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span class="s-btn"
|
||||
ng-controller="DateTimeFieldController">
|
||||
<span ng-controller="DateTimeFieldController">
|
||||
<input type="text"
|
||||
ng-model="textValue"
|
||||
ng-blur="restoreTextValue(); ngBlur()"
|
||||
ng-class="{
|
||||
error: textInvalid ||
|
||||
(structure.validate &&
|
||||
!structure.validate(ngModel[field]))
|
||||
!structure.validate(ngModel[field])),
|
||||
'picker-icon': structure.format === 'utc' || !structure.format
|
||||
}">
|
||||
</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>
|
||||
@@ -38,8 +37,7 @@
|
||||
<div mct-click-elsewhere="picker.active = false">
|
||||
<mct-control key="'datetime-picker'"
|
||||
ng-model="pickerModel"
|
||||
field="'value'"
|
||||
options="{ hours: true }">
|
||||
field="'value'">
|
||||
</mct-control>
|
||||
</div>
|
||||
</mct-popup>
|
||||
|
||||
@@ -19,42 +19,43 @@
|
||||
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">
|
||||
<form class="l-time-range-inputs-holder"
|
||||
<div ng-controller="TimeRangeController as trCtrl" class="l-flex-col">
|
||||
<form class="l-time-range-inputs-holder l-flex-row flex-elem"
|
||||
ng-submit="trCtrl.updateBoundsFromForm()">
|
||||
<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 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>
|
||||
|
||||
<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">
|
||||
<div class="l-time-range-slider-holder flex-elem">
|
||||
<div class="l-time-range-slider">
|
||||
<div class="slider"
|
||||
mct-resize="spanWidth = bounds.width">
|
||||
@@ -85,7 +86,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="l-time-range-ticks-holder">
|
||||
<div class="l-time-range-ticks-holder flex-elem">
|
||||
<div class="l-time-range-ticks">
|
||||
<div
|
||||
ng-repeat="tick in ticks track by $index"
|
||||
|
||||
@@ -49,10 +49,7 @@ define(
|
||||
position = [rect.left, rect.top],
|
||||
popup = popupService.display(div, position);
|
||||
|
||||
// TODO: Handle in CSS;
|
||||
// https://github.com/nasa/openmctweb/issues/298
|
||||
div.css('z-index', 75);
|
||||
|
||||
div.addClass('t-popup');
|
||||
transclude(function (clone) {
|
||||
div.append(clone);
|
||||
});
|
||||
|
||||
@@ -24,7 +24,15 @@ define(
|
||||
["../../src/directives/MCTPopup"],
|
||||
function (MCTPopup) {
|
||||
|
||||
var JQLITE_METHODS = ["on", "off", "find", "parent", "css", "append"];
|
||||
var JQLITE_METHODS = [
|
||||
"on",
|
||||
"off",
|
||||
"find",
|
||||
"parent",
|
||||
"css",
|
||||
"addClass",
|
||||
"append"
|
||||
];
|
||||
|
||||
describe("The mct-popup directive", function () {
|
||||
var mockCompile,
|
||||
|
||||
@@ -38,7 +38,6 @@ define(
|
||||
function InfoGestureButton($document, agentService, infoService, element, domainObject) {
|
||||
var dismissBubble,
|
||||
touchPosition,
|
||||
scopeOff,
|
||||
body = $document.find('body');
|
||||
|
||||
function trackPosition(event) {
|
||||
@@ -94,10 +93,6 @@ 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.
|
||||
@@ -109,7 +104,6 @@ define(
|
||||
hideBubble();
|
||||
// ...and detach listeners
|
||||
element.off('click', showBubble);
|
||||
scopeOff();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,6 +137,11 @@ define(
|
||||
);
|
||||
});
|
||||
|
||||
// https://github.com/nasa/openmct/issues/948
|
||||
it("does not try to access scope", function () {
|
||||
expect(mockElement.scope).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -32,11 +32,12 @@ $sliderColorBase: $colorKey;
|
||||
$sliderColorRangeHolder: rgba(black, 0.1);
|
||||
$sliderColorRange: rgba($sliderColorBase, 0.3);
|
||||
$sliderColorRangeHov: rgba($sliderColorBase, 0.5);
|
||||
$sliderColorKnob: rgba($sliderColorBase, 0.6);
|
||||
$sliderColorKnobHov: $sliderColorBase;
|
||||
$sliderColorKnob: $sliderColorBase;
|
||||
$sliderColorKnobHov: pullForward($sliderColorKnob, $ltGamma);
|
||||
$sliderColorRangeValHovBg: rgba($sliderColorBase, 0.1);
|
||||
$sliderColorRangeValHovFg: $colorKeyFg;
|
||||
$sliderKnobW: nth($ueTimeControlH,2)/2;
|
||||
$sliderKnobW: 15px;
|
||||
$sliderKnobR: 2px;
|
||||
$timeControllerToiLineColor: #00c2ff;
|
||||
$timeControllerToiLineColorHov: #fff;
|
||||
|
||||
@@ -69,8 +70,10 @@ $colorCreateMenuText: $colorMenuFg;
|
||||
$colorCheck: $colorKey;
|
||||
$colorFormRequired: $colorAlt1;
|
||||
$colorFormValid: #33cc33;
|
||||
$colorFormError: #cc0000;
|
||||
$colorFormError: #990000;
|
||||
$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,11 +32,12 @@ $sliderColorBase: $colorKey;
|
||||
$sliderColorRangeHolder: rgba(black, 0.07);
|
||||
$sliderColorRange: rgba($sliderColorBase, 0.2);
|
||||
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
|
||||
$sliderColorKnob: rgba($sliderColorBase, 0.5);
|
||||
$sliderColorKnob: pushBack($sliderColorBase, 20%);
|
||||
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
|
||||
$sliderColorRangeValHovBg: $sliderColorRange; //rgba($sliderColorBase, 0.1);
|
||||
$sliderColorRangeValHovBg: $sliderColorRange;
|
||||
$sliderColorRangeValHovFg: $colorBodyFg;
|
||||
$sliderKnobW: nth($ueTimeControlH,2)/2;
|
||||
$sliderKnobW: 15px;
|
||||
$sliderKnobR: 2px;
|
||||
$timeControllerToiLineColor: $colorBodyFg;
|
||||
$timeControllerToiLineColorHov: #0052b5;
|
||||
|
||||
@@ -69,8 +70,10 @@ $colorCreateMenuText: $colorBodyFg;
|
||||
$colorCheck: $colorKey;
|
||||
$colorFormRequired: $colorKey;
|
||||
$colorFormValid: #33cc33;
|
||||
$colorFormError: #cc0000;
|
||||
$colorFormError: #990000;
|
||||
$colorFormInvalid: #ff2200;
|
||||
$colorFormFieldErrorBg: $colorFormError;
|
||||
$colorFormFieldErrorFg: rgba(#fff, 0.6);
|
||||
$colorFormLines: rgba(#000, 0.1);
|
||||
$colorFormSectionHeader: rgba(#000, 0.05);
|
||||
$colorInputBg: $colorGenBg;
|
||||
|
||||
@@ -133,7 +133,7 @@ define([
|
||||
"telemetry"
|
||||
],
|
||||
"delegation": true,
|
||||
"editable": true
|
||||
"editable": false
|
||||
},
|
||||
{
|
||||
"name": "Real-time Table",
|
||||
@@ -144,7 +144,7 @@ define([
|
||||
"telemetry"
|
||||
],
|
||||
"delegation": true,
|
||||
"editable": true
|
||||
"editable": false
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
|
||||
@@ -127,7 +127,16 @@ define([
|
||||
14400000,
|
||||
28800000,
|
||||
43200000,
|
||||
86400000
|
||||
86400000,
|
||||
86400000 * 2,
|
||||
86400000 * 5,
|
||||
86400000 * 10,
|
||||
86400000 * 20,
|
||||
86400000 * 30,
|
||||
86400000 * 60,
|
||||
86400000 * 120,
|
||||
86400000 * 240,
|
||||
86400000 * 365
|
||||
],
|
||||
"width": 200
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
.l-timeline-gantt {
|
||||
min-width: 2px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: $timelineSwimlaneGanttVM; bottom: $timelineSwimlaneGanttVM;
|
||||
|
||||
.bar {
|
||||
@include ellipsize();
|
||||
height: $activityBarH;
|
||||
line-height: $activityBarH + 2;
|
||||
line-height: $activityBarH;
|
||||
padding: 0 $interiorMargin;
|
||||
|
||||
span {
|
||||
display: inline;
|
||||
$iconW: 20px;
|
||||
@include absPosDefault();
|
||||
display: block;
|
||||
&.s-activity-type {
|
||||
right: auto; width: $iconW;
|
||||
text-align: center;
|
||||
&.timeline {
|
||||
&:before {
|
||||
content:"S";
|
||||
@@ -23,7 +29,9 @@
|
||||
}
|
||||
}
|
||||
&.s-title {
|
||||
text-shadow: rgba(black, 0.1) 0 1px 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
left: $iconW;
|
||||
}
|
||||
&.duration {
|
||||
left: auto;
|
||||
@@ -52,6 +60,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&.sm .bar span {
|
||||
// Hide icon and label if width is too small
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-mode .s-timeline-gantt,
|
||||
@@ -59,7 +71,7 @@
|
||||
.handle {
|
||||
cursor: col-resize;
|
||||
&.mid {
|
||||
cursor: move;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,20 +32,10 @@
|
||||
}
|
||||
|
||||
.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,6 +52,9 @@
|
||||
// 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
|
||||
@@ -67,6 +70,7 @@
|
||||
&.l-timeline-gantt {
|
||||
.l-swimlanes-holder {
|
||||
@include scrollV(scroll);
|
||||
bottom: $scrollbarTrackSize;
|
||||
}
|
||||
}
|
||||
&.l-timeline-resource-legend {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
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,6 +103,13 @@
|
||||
<!-- 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"
|
||||
@@ -121,7 +128,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: zoomController.toPixels(zoomController.duration()),
|
||||
fullWidth: timelineController.width(zoomController),
|
||||
start: scroll.x,
|
||||
width: scroll.width,
|
||||
step: zoomController.toPixels(zoomController.zoom()),
|
||||
|
||||
@@ -97,6 +97,8 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch("configuration", swimlanePopulator.configure);
|
||||
|
||||
// Recalculate swimlane state on changes
|
||||
$scope.$watch("domainObject", swimlanePopulator.populate);
|
||||
|
||||
|
||||
@@ -32,16 +32,26 @@ 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 sz = zoomLevels[zoomLevels.length - 1];
|
||||
var tickCount = bounds.width / tickWidth,
|
||||
sz = zoomLevels[zoomLevels.length - 1] * tickCount;
|
||||
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)) {
|
||||
@@ -53,20 +63,27 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch("configuration.zoomLevel", setZoomLevel);
|
||||
$scope.$watch("scroll", function (scroll) {
|
||||
bounds = scroll;
|
||||
});
|
||||
$scope.$watch("domainObject", initializeZoom);
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -83,27 +100,29 @@ 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);
|
||||
storeZoom(zoomIndex);
|
||||
bounds.x = this.toPixels(center) - bounds.width / 2;
|
||||
}
|
||||
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: function (millis) {
|
||||
return tickWidth * millis / zoomLevels[zoomIndex];
|
||||
},
|
||||
toPixels: toPixels,
|
||||
/**
|
||||
* Get the time duration, in milliseconds, occupied by the
|
||||
* width (specified in pixels) at the current zoom level.
|
||||
* @returns {number} the number of pixels
|
||||
*/
|
||||
toMillis: function (pixels) {
|
||||
return (pixels / tickWidth) * zoomLevels[zoomIndex];
|
||||
},
|
||||
toMillis: toMillis,
|
||||
/**
|
||||
* Get or set the current displayed duration. If used as a
|
||||
* setter, this will typically be rounded up to ensure extra
|
||||
|
||||
@@ -43,8 +43,7 @@ define(
|
||||
var swimlanes = [],
|
||||
start = Number.POSITIVE_INFINITY,
|
||||
end = Number.NEGATIVE_INFINITY,
|
||||
colors = (configuration.colors || {}),
|
||||
assigner = new TimelineColorAssigner(colors),
|
||||
assigner,
|
||||
lastDomainObject;
|
||||
|
||||
// Track extremes of start/end times
|
||||
@@ -152,8 +151,15 @@ define(
|
||||
recalculateSwimlanes(lastDomainObject);
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
var colors = (configuration.colors || {});
|
||||
assigner = new TimelineColorAssigner(colors);
|
||||
configuration.colors = colors;
|
||||
recalculateSwimlanes(lastDomainObject);
|
||||
}
|
||||
|
||||
// Ensure colors are exposed in configuration
|
||||
configuration.colors = colors;
|
||||
initialize();
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -188,6 +194,15 @@ 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,6 +68,14 @@ 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;
|
||||
@@ -141,9 +149,15 @@ 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,
|
||||
strWatchCall;
|
||||
var fnWatchCall;
|
||||
|
||||
// Find the $watch that was given a function
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
@@ -151,16 +165,11 @@ 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
|
||||
expect(strWatchCall.args[0]).toEqual('domainObject');
|
||||
// Initially populate
|
||||
strWatchCall.args[1](mockDomainObject);
|
||||
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
// There should be to swimlanes
|
||||
expect(controller.swimlanes().length).toEqual(2);
|
||||
|
||||
@@ -182,23 +191,23 @@ define(
|
||||
// order of $watch calls in TimelineController.
|
||||
|
||||
// Initially populate
|
||||
mockScope.$watch.calls[0].args[1](mockDomainObject);
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
|
||||
// Verify precondition - no graphs
|
||||
expect(controller.graphs().length).toEqual(0);
|
||||
|
||||
// Execute the watch function for graph state
|
||||
tmp = mockScope.$watch.calls[2].args[0]();
|
||||
tmp = mockScope.$watch.calls[3].args[0]();
|
||||
|
||||
// Change graph state
|
||||
testConfiguration.graph = { a: true, b: true };
|
||||
|
||||
// Verify that this would have triggered a watch
|
||||
expect(mockScope.$watch.calls[2].args[0]())
|
||||
expect(mockScope.$watch.calls[3].args[0]())
|
||||
.not.toEqual(tmp);
|
||||
|
||||
// Run the function the watch would have triggered
|
||||
mockScope.$watch.calls[2].args[1]();
|
||||
mockScope.$watch.calls[3].args[1]();
|
||||
|
||||
// Should have some graphs now
|
||||
expect(controller.graphs().length).toEqual(2);
|
||||
@@ -211,7 +220,7 @@ define(
|
||||
mockZoom.duration.andReturn(12345);
|
||||
|
||||
// Initially populate
|
||||
mockScope.$watch.calls[0].args[1](mockDomainObject);
|
||||
fireWatch('domainObject', mockDomainObject);
|
||||
|
||||
expect(controller.width(mockZoom)).toEqual(54321);
|
||||
// Verify interactions; we took zoom's duration for our start/end,
|
||||
|
||||
@@ -32,11 +32,7 @@ define(
|
||||
|
||||
beforeEach(function () {
|
||||
testConfiguration = {
|
||||
levels: [
|
||||
1000,
|
||||
2000,
|
||||
3500
|
||||
],
|
||||
levels: [1000, 2000, 3500],
|
||||
width: 12321
|
||||
};
|
||||
mockScope = jasmine.createSpyObj("$scope", ['$watch']);
|
||||
@@ -74,32 +70,61 @@ define(
|
||||
expect(controller.zoom()).toEqual(3500);
|
||||
});
|
||||
|
||||
it("does not normally persist zoom changes", function () {
|
||||
controller.zoom(1);
|
||||
expect(mockScope.commit).not.toHaveBeenCalled();
|
||||
it("observes scroll bounds", function () {
|
||||
expect(mockScope.$watch)
|
||||
.toHaveBeenCalledWith("scroll", jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("persists zoom changes in Edit mode", function () {
|
||||
mockScope.domainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['hasCapability', 'getCapability']
|
||||
);
|
||||
mockScope.domainObject.hasCapability.andCallFake(function (c) {
|
||||
return c === 'editor';
|
||||
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]]);
|
||||
});
|
||||
});
|
||||
mockScope.domainObject.getCapability.andCallFake(function (c) {
|
||||
if (c === 'editor') {
|
||||
return {
|
||||
inEditContext: function () {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
controller.zoom(1);
|
||||
expect(mockScope.commit).toHaveBeenCalled();
|
||||
expect(mockScope.configuration.zoomLevel)
|
||||
.toEqual(jasmine.any(Number));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -177,6 +177,10 @@ 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();
|
||||
|
||||
@@ -222,10 +226,6 @@ 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,21 +194,6 @@ 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";
|
||||
|
||||
|
||||
70
src/MCT.js
Normal file
70
src/MCT.js
Normal file
@@ -0,0 +1,70 @@
|
||||
define([
|
||||
'EventEmitter',
|
||||
'legacyRegistry',
|
||||
'./api/api'
|
||||
], 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.MCT = MCT;
|
||||
|
||||
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);
|
||||
|
||||
var viewFactory = type.view(this.regions.main);
|
||||
if (viewFactory) {
|
||||
var viewKey = key + "." + this.regions.main;
|
||||
|
||||
this.legacyBundle.extensions.views =
|
||||
this.legacyBundle.extensions.views || [];
|
||||
this.legacyBundle.extensions.views.push({
|
||||
name: "A view",
|
||||
key: "adapted-view",
|
||||
template: '<mct-view key="\'' +
|
||||
viewKey +
|
||||
'\'" ' +
|
||||
'mct-object="domainObject">' +
|
||||
'</mct-view>'
|
||||
});
|
||||
|
||||
this.legacyBundle.extensions.newViews =
|
||||
this.legacyBundle.extensions.newViews || [];
|
||||
this.legacyBundle.extensions.newViews.push({
|
||||
factory: viewFactory,
|
||||
key: viewKey
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
MCT.prototype.start = function () {
|
||||
legacyRegistry.register('adapter', this.legacyBundle);
|
||||
this.emit('start');
|
||||
};
|
||||
|
||||
MCT.prototype.regions = {
|
||||
main: "MAIN"
|
||||
};
|
||||
|
||||
MCT.prototype.verbs = {
|
||||
mutate: function (domainObject, mutator) {
|
||||
return domainObject.useCapability('mutation', mutator)
|
||||
.then(function () {
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
return persistence.persist();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return MCT;
|
||||
});
|
||||
16
src/adapter/bundle.js
Normal file
16
src/adapter/bundle.js
Normal file
@@ -0,0 +1,16 @@
|
||||
define([
|
||||
'legacyRegistry',
|
||||
'./directives/MCTView'
|
||||
], function (legacyRegistry, MCTView) {
|
||||
legacyRegistry.register('src/adapter', {
|
||||
"extensions": {
|
||||
"directives": [
|
||||
{
|
||||
key: "mctView",
|
||||
implementation: MCTView,
|
||||
depends: ["newViews[]"]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
51
src/adapter/directives/MCTView.js
Normal file
51
src/adapter/directives/MCTView.js
Normal file
@@ -0,0 +1,51 @@
|
||||
define(['angular'], function (angular) {
|
||||
function MCTView(newViews) {
|
||||
var factories = {};
|
||||
|
||||
newViews.forEach(function (newView) {
|
||||
factories[newView.key] = newView.factory;
|
||||
});
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function (scope, element, attrs) {
|
||||
var key = undefined;
|
||||
var mctObject = undefined;
|
||||
|
||||
function maybeShow() {
|
||||
if (!factories[key]) {
|
||||
return;
|
||||
}
|
||||
if (!mctObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
var view = factories[key](mctObject);
|
||||
var elements = view.elements();
|
||||
element.empty();
|
||||
element.append(elements);
|
||||
}
|
||||
|
||||
function setKey(k) {
|
||||
key = k;
|
||||
maybeShow();
|
||||
}
|
||||
|
||||
function setObject(obj) {
|
||||
mctObject = obj;
|
||||
maybeShow();
|
||||
}
|
||||
|
||||
scope.$watch('key', setKey);
|
||||
scope.$watch('mctObject', setObject);
|
||||
|
||||
},
|
||||
scope: {
|
||||
key: "=",
|
||||
mctObject: "="
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return MCTView;
|
||||
});
|
||||
51
src/api/Type.js
Normal file
51
src/api/Type.js
Normal file
@@ -0,0 +1,51 @@
|
||||
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;
|
||||
this.views = {};
|
||||
}
|
||||
|
||||
Type.prototype.view = function (region, factory) {
|
||||
if (arguments.length > 1) {
|
||||
this.views[region] = factory;
|
||||
}
|
||||
return this.views[region];
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
});
|
||||
21
src/api/View.js
Normal file
21
src/api/View.js
Normal file
@@ -0,0 +1,21 @@
|
||||
define(['EventEmitter'], function (EventEmitter) {
|
||||
function View() {
|
||||
EventEmitter.call(this);
|
||||
}
|
||||
|
||||
View.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
['elements', 'model'].forEach(function (method) {
|
||||
View.prototype[method] = function (value) {
|
||||
this.viewState =
|
||||
this.viewState || { elements: [], model: undefined };
|
||||
if (arguments.length > 0) {
|
||||
this.viewState[method] = value;
|
||||
this.emit(method, value);
|
||||
}
|
||||
return this.viewState[method];
|
||||
}
|
||||
});
|
||||
|
||||
return View;
|
||||
});
|
||||
12
src/api/api.js
Normal file
12
src/api/api.js
Normal file
@@ -0,0 +1,12 @@
|
||||
define([
|
||||
'./Type',
|
||||
'./View'
|
||||
], function (
|
||||
Type,
|
||||
View
|
||||
) {
|
||||
return {
|
||||
Type: Type,
|
||||
View: View
|
||||
};
|
||||
});
|
||||
@@ -48,6 +48,7 @@ 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",
|
||||
@@ -64,6 +65,9 @@ requirejs.config({
|
||||
"angular-route": {
|
||||
"deps": [ "angular" ]
|
||||
},
|
||||
"EventEmitter": {
|
||||
"exports": "EventEmitter"
|
||||
},
|
||||
"moment-duration-format": {
|
||||
"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;
|
||||
});
|
||||
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;
|
||||
}
|
||||
);
|
||||
19
tutorials/todo/bundle.js
Normal file
19
tutorials/todo/bundle.js
Normal file
@@ -0,0 +1,19 @@
|
||||
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": {
|
||||
"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;
|
||||
});
|
||||
5
tutorials/todo/todo-task.html
Normal file
5
tutorials/todo/todo-task.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<li>
|
||||
<input type="checkbox" class="example-task-checked">
|
||||
<span class="example-task-description">
|
||||
</span>
|
||||
</li>
|
||||
14
tutorials/todo/todo.html
Normal file
14
tutorials/todo/todo.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<div class="example-todo">
|
||||
<div class="example-button-group">
|
||||
<a class="example-todo-button-all">All</a>
|
||||
<a class="example-todo-button-incomplete">Incomplete</a>
|
||||
<a class="example-todo-button-complete">Complete</a>
|
||||
</div>
|
||||
|
||||
<ul class="example-todo-task-list">
|
||||
</ul>
|
||||
|
||||
<div class="example-message">
|
||||
There are no tasks to show.
|
||||
</div>
|
||||
</div>
|
||||
108
tutorials/todo/todo.js
Normal file
108
tutorials/todo/todo.js
Normal file
@@ -0,0 +1,108 @@
|
||||
define([
|
||||
"text!./todo.html",
|
||||
"text!./todo-task.html",
|
||||
"zepto"
|
||||
], function (todoTemplate, taskTemplate, $) {
|
||||
/**
|
||||
* @param {mct.MCT} mct
|
||||
*/
|
||||
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 = [
|
||||
{ description: "This is a task." }
|
||||
];
|
||||
},
|
||||
creatable: true
|
||||
});
|
||||
|
||||
function TodoView(domainObject) {
|
||||
mct.View.apply(this);
|
||||
this.filterValue = "all";
|
||||
this.elements($(todoTemplate));
|
||||
|
||||
var $els = $(this.elements());
|
||||
this.$buttons = {
|
||||
all: $els.find('.example-todo-button-all'),
|
||||
incomplete: $els.find('.example-todo-button-incomplete'),
|
||||
complete: $els.find('.example-todo-button-complete')
|
||||
};
|
||||
|
||||
this.initialize();
|
||||
this.on('model', this.render.bind(this));
|
||||
this.model(domainObject);
|
||||
}
|
||||
|
||||
TodoView.prototype = Object.create(mct.View.prototype);
|
||||
|
||||
TodoView.prototype.setFilter = function (value) {
|
||||
this.filterValue = value;
|
||||
this.render();
|
||||
};
|
||||
|
||||
TodoView.prototype.initialize = function () {
|
||||
Object.keys(this.$buttons).forEach(function (k) {
|
||||
this.$buttons[k].on('click', this.setFilter.bind(this, k));
|
||||
}, this);
|
||||
};
|
||||
|
||||
TodoView.prototype.render = function () {
|
||||
var $els = $(this.elements());
|
||||
var domainObject = this.model();
|
||||
var tasks = domainObject.getModel().tasks;
|
||||
var $message = $els.find('.example-message');
|
||||
var $list = $els.find('.example-todo-task-list');
|
||||
var $buttons = this.$buttons;
|
||||
var filters = {
|
||||
all: function () {
|
||||
return true;
|
||||
},
|
||||
incomplete: function (task) {
|
||||
return !task.completed;
|
||||
},
|
||||
complete: function (task) {
|
||||
return task.completed;
|
||||
}
|
||||
};
|
||||
var filterValue = this.filterValue;
|
||||
|
||||
Object.keys($buttons).forEach(function (k) {
|
||||
$buttons[k].toggleClass('selected', filterValue === k);
|
||||
});
|
||||
tasks = tasks.filter(filters[filterValue]);
|
||||
|
||||
$list.empty();
|
||||
tasks.forEach(function (task, index) {
|
||||
var $taskEls = $(taskTemplate);
|
||||
var $checkbox = $taskEls.find('.example-task-checked');
|
||||
$checkbox.prop('checked', task.completed);
|
||||
$taskEls.find('.example-task-description')
|
||||
.text(task.description);
|
||||
|
||||
$checkbox.on('change', function () {
|
||||
var checked = !!$checkbox.prop('checked');
|
||||
mct.verbs.mutate(domainObject, function (model) {
|
||||
model.tasks[index].completed = checked;
|
||||
});
|
||||
});
|
||||
|
||||
$list.append($taskEls);
|
||||
});
|
||||
|
||||
$message.toggle(tasks.length < 1);
|
||||
};
|
||||
|
||||
todoType.view(mct.regions.main, function (domainObject) {
|
||||
return new TodoView(domainObject);
|
||||
});
|
||||
|
||||
mct.type('example.todo', todoType);
|
||||
|
||||
return mct;
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user