Compare commits

...

78 Commits

Author SHA1 Message Date
Victor Woeltjen
580a4e52b5 Merge branch 'api-tutorials' into api-type-driven 2016-06-07 14:01:08 -07:00
Victor Woeltjen
9c4e17bfab [Tutorials] Add telemetry tutorial 2016-06-07 13:14:36 -07:00
Victor Woeltjen
d3e5d95d6b [Tutorials] Add example server 2016-06-07 13:00:38 -07:00
Victor Woeltjen
c70793ac2d [Tutorials] Add remainder of bargraph 2016-06-07 12:55:29 -07:00
Victor Woeltjen
a6ef1d3423 [Tutorials] Add Bar Graph tutorial 2016-06-07 12:49:38 -07:00
Victor Woeltjen
4ca2f51d5e [API] Use subclass style 2016-05-27 17:08:25 -07:00
Victor Woeltjen
86ac80ddbd [API] Persist mutations 2016-05-27 16:56:08 -07:00
Victor Woeltjen
0525ba6b0b [API] Check/uncheck todos 2016-05-27 16:55:10 -07:00
Victor Woeltjen
a79e958ffc [API] Show tasks from todo 2016-05-27 16:46:06 -07:00
Victor Woeltjen
03cb0ccb57 [API] Get View to render 2016-05-27 16:36:55 -07:00
Victor Woeltjen
7205faa6bb [API] Add adapter bundle 2016-05-27 16:27:47 -07:00
Victor Woeltjen
136f2ae785 [API] Add MCTView directive as an adapter 2016-05-27 16:19:20 -07:00
Victor Woeltjen
a07e2fb8e5 [API] Implement View 2016-05-27 16:08:43 -07:00
Victor Woeltjen
55b531bdeb [API] Sketch in view instantiation 2016-05-27 15:49:16 -07:00
Victor Woeltjen
7ece5897e8 [API] Begin adding View 2016-05-27 15:31:54 -07:00
Victor Woeltjen
a29c7a6eab [API] Deangularize todo templates 2016-05-27 13:46:30 -07:00
Victor Woeltjen
c4fec1af6a [API] Move type toward a newer API 2016-05-27 13:31:30 -07:00
Victor Woeltjen
a6996df3df [API] Begin moving out type 2016-05-27 13:17:16 -07:00
Victor Woeltjen
0c660238f2 [API] Add MCT class 2016-05-27 11:49:43 -07:00
Victor Woeltjen
b73b824e55 [API] Add EventEmitter dep 2016-05-27 11:45:59 -07:00
Victor Woeltjen
1954d98628 [Tutorials] Remove diff markings in TodoController 2016-05-27 11:30:53 -07:00
Victor Woeltjen
7aa034ce23 Add todo tutorial 2016-05-26 16:05:38 -07:00
Victor Woeltjen
385dc5d298 Begin adding tutorials 2016-05-26 15:36:09 -07:00
Andrew Henry
a88b4b31a1 Merge pull request #966 from nasa/open747
R&I open747: refined "unsaved changes" dialog message
2016-05-26 12:02:12 -07:00
Victor Woeltjen
04112956cf Merge pull request #964 from nasa/open913
R&I open913: various Timeline fixes
2016-05-26 11:47:06 -07:00
Charles Hacskaylo
f1113fda24 [Frontend] New message for unsaved changes warning
open #747
2016-05-26 09:04:40 -07:00
Charles Hacskaylo
33c208d8fe [Frontend] Timeline Gantt bar mods to allow small min-width
open #965
- CSS adjusted to handle min-width of 2px and better
approach to ellipsizing text;
- Angular ng-class added to hide icon and title if
width less than a value;
- Rounded corners on bars removed;
2016-05-25 20:52:36 -07:00
Charles Hacskaylo
c557fb6cd5 [Frontend] Cursor properties modified
open #768
2016-05-25 19:39:56 -07:00
Charles Hacskaylo
bde2bc7709 [Frontend] Bottom of holder divs adjusted
open #913
2016-05-25 19:28:28 -07:00
Andrew Henry
5fe759aa91 Merge pull request #955 from nasa/timeline-zoom-936
[Timeline] Improve zoom behaviors
2016-05-25 16:58:20 -07:00
Victor Woeltjen
a5b7badb95 [Timeline] Remove obsolete arguments
https://github.com/nasa/openmct/pull/955/files#r64668507
2016-05-25 16:08:28 -07:00
Andrew Henry
eefd4c8669 Merge pull request #949 from nasa/info-error-948
[Mobile] Remove usage of element.scope()
2016-05-25 15:15:53 -07:00
Victor Woeltjen
7c11f2db4f Merge pull request #961 from nasa/open959
[Style] Fixed style issues introduced by #954
2016-05-25 12:16:44 -07:00
Henry
7501f679f7 [Style] Fixed style issues introduced by #954 2016-05-25 12:10:39 -07:00
Victor Woeltjen
52e087d8f8 Merge pull request #954 from nasa/open628
[Edit Mode] #628 Remove edit related concerns from Create Action
2016-05-25 11:55:17 -07:00
Andrew Henry
c5cd495fce Merge pull request #952 from nasa/fix-build-142
[Code Style] Run code style checks on CircleCI
2016-05-25 11:49:54 -07:00
Pete Richards
37a417051d Merge remote-tracking branch 'origin/missing-time-conductor-957' 2016-05-25 11:43:57 -07:00
Victor Woeltjen
97d819739c Merge pull request #956 from nasa/fix-build-mct-popup
Resolve build conflict from #922
2016-05-25 11:41:14 -07:00
Victor Woeltjen
3935378b0c Revert "[Timeline] Test mct-representation ordering"
This reverts commit 2a4004fd5b.
2016-05-25 11:34:29 -07:00
Victor Woeltjen
952f95aa4c [Timeline] Update failing specs 2016-05-25 11:33:51 -07:00
Victor Woeltjen
0a75a5be1f [Timeline] Add minimal test case 2016-05-25 11:28:00 -07:00
Victor Woeltjen
70b593e28a [Timeline] Watch for configuration object
...to address #908 in a manner which does not cause #957
2016-05-25 11:22:22 -07:00
Victor Woeltjen
ed69a65f9b [Representation] Restore ordering in mct-representation
Revert "[Timeline] Change ordering in mct-representation"

This reverts commit 20ecf168f2.
These changes introduced a regression due to ordering
expected by time conductor, #957
2016-05-25 11:03:32 -07:00
Pete Richards
05b4f5401e Merge remote-tracking branch 'origin/open890' 2016-05-25 10:19:11 -07:00
Pete Richards
2ff0c7b06a [Test] Add spy method for addClass
Add spy method, fix a merge conflict that was improperly resolved
in https://github.com/nasa/openmct/pull/922
2016-05-25 10:11:34 -07:00
Pete Richards
2330f1d135 Merge remote-tracking branch 'origin/open907' 2016-05-25 10:06:55 -07:00
Pete Richards
ff92d3acab Merge remote-tracking branch 'origin/open889' into open922 2016-05-25 09:55:09 -07:00
Henry
32d7187db6 Relocated creation package to edit bundle 2016-05-24 17:08:12 -07:00
Victor Woeltjen
00534f8af7 [Timeline] Account for tick size
Account for tick size in duration reported by TimelineZoomController,
to avoid tick marks being cut off prematurely due to changes for
#936
2016-05-24 13:02:30 -07:00
Victor Woeltjen
3795570938 [Timeline] Rename shadowing variable 2016-05-24 12:45:25 -07:00
Victor Woeltjen
362248a02e [Timeline] Run gulp fixstyle 2016-05-24 12:37:10 -07:00
Victor Woeltjen
16d20eabd2 [Timeline] Simplify method 2016-05-24 12:33:47 -07:00
Victor Woeltjen
eb5566f041 [Timeline] Add tests for timeline zoom changes 2016-05-24 12:33:19 -07:00
Victor Woeltjen
757da1dff4 [Timeline] Remove obsolete test cases 2016-05-24 11:54:49 -07:00
Victor Woeltjen
85432af187 [Timeline] Don't store zoom configuration
https://github.com/nasa/openmct/issues/936#issuecomment-221343620
2016-05-24 11:53:27 -07:00
Henry
f0ab817e87 Added tests, and fixed failing ones 2016-05-24 10:53:04 -07:00
Henry
96af931c0b Modified EditActionPolicy to prevent editing of table views unless object is a table type 2016-05-23 16:48:31 -07:00
Victor Woeltjen
9a5209f7c2 [Timeline] Add zoom-to-fit button 2016-05-23 16:06:10 -07:00
Victor Woeltjen
0818a7cda0 [Timeline] Increase maximum zoom level
#936
2016-05-23 15:36:26 -07:00
Victor Woeltjen
f35947361c [Timeline] Remain centered during zoom
#936
2016-05-23 15:32:48 -07:00
Victor Woeltjen
9a8bcc0550 [Code Style] Specify lint, codestyle in CircleCI config
...to work around unexpected failure running test suite via gulp verify,
https://circleci.com/gh/nasa/openmct/1981
2016-05-23 15:10:05 -07:00
Henry
eff46b076c [New Edit Mode] #628 Removed duplicate logic from Create Action 2016-05-23 15:03:20 -07:00
Victor Woeltjen
ab64b682c3 [Code Style] Run checkstyle on CircleCI
Run the full gulp verify task for testing on CircleCI, to handle
unit tests as well as code style checks and linting (and other
verification steps that may be added in the future.)
2016-05-23 14:58:23 -07:00
Victor Woeltjen
1c007ea256 [Code Style] Remove trailing whitespace
...to fix build after changes for #142.
2016-05-23 14:55:04 -07:00
Henry
6c1412784b [Example] REMS heirarchy appear as links 2016-05-20 17:11:07 -07:00
Victor Woeltjen
6e7f4df5e3 [Mobile] Remove usage of element.scope()
Usage is unnecessary and is sensitive to initialization ordering of
representations, resulting in #948.
2016-05-20 16:13:51 -07:00
Henry
5eff4e45c9 [Tutorials] #907 Updated tutorials to use new bundle registration mechanism 2016-05-17 17:24:32 -07:00
Henry
70a13a75d5 Updating tutorials 2016-05-17 13:22:15 -07:00
Victor Woeltjen
69cc1086df Merge pull request #924 from nasa/open889b
[Popup] Add method to jqlite mocks
2016-05-16 10:14:52 -07:00
Victor Woeltjen
3d891073da [Popup] Add method to jqlite mocks
...to resolve build failure preventing merge,
https://github.com/nasa/openmct/pull/922#issuecomment-218588876
2016-05-11 14:25:23 -07:00
Charles Hacskaylo
73c2c01def [Frontend] Layout and styling of Time Conductor
open #889
open #298
Fixes for mobile;
Moved popup z-index def into sass;
Datetime picker compressed for
better fit in mobile phone;
Removed hours selector from datetime
picker to enable better fit in mobile;
2016-05-11 11:32:39 -07:00
Charles Hacskaylo
7c82e31b66 Merge branch 'master' into open889 2016-05-10 23:09:59 -07:00
Charles Hacskaylo
2829b8d495 [Frontend] Time Conductor mobile
open #889
- Major refactoring of mobile approach,
leveraging flex-box layout change;
2016-05-10 19:52:11 -07:00
Charles Hacskaylo
7ade873365 [Frontend] Flex; Tweaks to slider knobs
open #889
- Converted TC elements to use flex
layout instead of abs pos;
- Knob size increased;
- Knob grippies added;
2016-05-10 15:12:50 -07:00
Charles Hacskaylo
8788523c25 [Frontend] Tweaks to slider knobs
open #889
- Knob size increased;
- Knob grippies added;
2016-05-10 14:28:23 -07:00
Charles Hacskaylo
c301523156 [Frontend] Layout and positioning fixes for TC controls
open #889
IN PROGRESS
- Smaller font used on range value;
- More space allocated to left and right
for slider range values;
- Style tweaks to slider look;
- Layout and style of datetime inputs
fixed;
- Input error colors fixed, moved to
theme constants;
2016-05-09 17:56:50 -07:00
Charles Hacskaylo
2e8604e18d [Frontend] Layout and positioning fixes for TC controls
open #889
2016-05-09 10:38:18 -07:00
Charles Hacskaylo
cae85f3e30 [Frontend] Mods to slider .range-value elems
open #889
- Text smaller, line breaks;
- Height adjustments
- Increased with of slider area;
2016-05-09 10:16:54 -07:00
86 changed files with 2457 additions and 908 deletions

View File

@@ -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"
}
}

View File

@@ -17,6 +17,7 @@ deployment:
test:
post:
- gulp lint
- gulp checkstyle
general:
branches:

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

View File

@@ -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
View File

@@ -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;
});

View File

@@ -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,

View File

@@ -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();
});
});
}
);

View File

@@ -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
}
]
}
});

View File

@@ -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");
};

View File

@@ -22,7 +22,7 @@
define(
['../../../browse/src/creation/CreateWizard'],
['../creation/CreateWizard'],
function (CreateWizard) {
/**

View File

@@ -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);
}
};

View File

@@ -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
);
});
};

View File

@@ -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++;
}
});

View File

@@ -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
);
});

View 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();
});
});
});
});
}
);

View File

@@ -145,3 +145,8 @@
.flex-justify-end {
@include justify-content(flex-end);
}
/********************************************* POPUPS */
.t-popup {
z-index: 75;
}

View File

@@ -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%;

View File

@@ -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 {

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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">&#x43;</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">&#x43;</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>&nbsp;
</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"

View File

@@ -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);
});

View File

@@ -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,

View File

@@ -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();
}
};
}

View File

@@ -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();
});
});
}
);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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": [

View File

@@ -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
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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 ? {

View File

@@ -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()),

View File

@@ -97,6 +97,8 @@ define(
}
}
$scope.$watch("configuration", swimlanePopulator.configure);
// Recalculate swimlane state on changes
$scope.$watch("domainObject", swimlanePopulator.populate);

View File

@@ -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

View File

@@ -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();
}
};
}

View File

@@ -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,

View File

@@ -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));
});
});

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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
View File

@@ -0,0 +1,16 @@
define([
'legacyRegistry',
'./directives/MCTView'
], function (legacyRegistry, MCTView) {
legacyRegistry.register('src/adapter', {
"extensions": {
"directives": [
{
key: "mctView",
implementation: MCTView,
depends: ["newViews[]"]
}
]
}
});
});

View 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
View 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
View 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
View File

@@ -0,0 +1,12 @@
define([
'./Type',
'./View'
], function (
Type,
View
) {
return {
Type: Type,
View: View
};
});

View File

@@ -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
View 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"]);
});
}());

View 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"
}
]
}
]
}

View 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" ]
}
]
}
});
});

View 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>

View 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;
});

View 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" ]
}
]
}
});
});

View 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;
}
);

View 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;
}
);

View 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;
}
);

View 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;
}
);

View 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
View 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"
}
]
}
});
});

View 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>

View 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;
});

View 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
View 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
View 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;
};
});