Compare commits

..

145 Commits

Author SHA1 Message Date
Victor Woeltjen
b4dc50295c Merge pull request #1131 from nasa/open1094
Resolve synchronization issues with MutableObject
2016-08-25 13:26:50 -07:00
Victor Woeltjen
382dde300a Merge remote-tracking branch 'origin/api-tutorials' into open1094
Conflicts:
	index.html
	tutorials/todo/todo.js
2016-08-25 13:25:54 -07:00
Victor Woeltjen
02aa08a3ef Merge pull request #1121 from nasa/api-containment
[API] Containment
2016-08-25 13:19:14 -07:00
Victor Woeltjen
c95d9c7956 Merge pull request #1107 from nasa/api-type-forms
[API] Handle forms with a "properties" region
2016-08-25 13:17:26 -07:00
Andrew Henry
b1b8df4d87 Use MutationCapability 2016-08-23 13:57:12 +01:00
Andrew Henry
bd3c6665fb Added bridge between old and new event models 2016-08-22 16:14:01 +01:00
Pete Richards
10e90519c0 Tidy todo views, remove unnecessary code 2016-08-22 16:10:45 +01:00
Pete Richards
d341a8be97 Selection changes include new selection 2016-08-22 16:10:45 +01:00
Andrew Henry
8c439d8109 Adding compatibility between old and new style mutation events 2016-08-22 07:59:17 +01:00
Henry
9c88b7ce1d Removed setters from MutableObject and fixed non-working todo list tutorial
Refactoring MutableObject

Fixed non-working todo example
2016-08-19 13:39:23 -07:00
Victor Woeltjen
2463e4d59f [API] Update Dialog API usage 2016-08-12 12:54:39 -07:00
Victor Woeltjen
d73c505bea [API] Fix typo, add missing this 2016-08-12 11:17:00 -07:00
Victor Woeltjen
831ecc59d9 [API] Wire in canContain via policy 2016-08-12 10:24:59 -07:00
Victor Woeltjen
1de26d3c5d [API] Throw error on containment violation 2016-08-12 09:30:02 -07:00
Victor Woeltjen
11409ce509 [API] Add containment methods 2016-08-12 09:27:46 -07:00
Victor Woeltjen
93872ce074 [API] Expose Dialog as constructor
...and use it that way from todo plugin
2016-08-11 16:04:26 -07:00
Victor Woeltjen
8861644f2d [API] Adjust Dialog API
...to allow OK button to be enabled/disabled.
2016-08-11 16:02:04 -07:00
Victor Woeltjen
d4948f771b Merge branch 'api-todo-update' into api-type-forms 2016-08-11 15:31:11 -07:00
Victor Woeltjen
8295a0bed1 [API] Update todo tutorial
...to expect new domain object API (instead of explicitly
wrapping it.)
2016-08-11 15:29:46 -07:00
Victor Woeltjen
0656a298da [API] Remove test usage of properties region 2016-08-04 14:00:48 -07:00
Victor Woeltjen
fe2ce91d50 [API] Show a custom view in dialog 2016-07-28 16:16:23 -07:00
Victor Woeltjen
14f30b2489 [API] Restrict dialog overrides
...to those domain objects which have some view for the
properties region registered.
2016-07-28 16:05:02 -07:00
Victor Woeltjen
62d90a8114 [API] Show dialog via mct 2016-07-28 15:57:15 -07:00
Victor Woeltjen
87682607a5 [API] Rename dependency in adapter layer 2016-07-28 15:53:07 -07:00
Victor Woeltjen
7bf265b478 [API] Move mct service up 2016-07-28 15:52:52 -07:00
Victor Woeltjen
1d31fe8d02 [API] Override dialogService in actions
An ugly hack to allow dialogs to be shown for Save As
and Edit Properties, without requiring form generation.
This will permit views to be shown instead in certain
cases, https://github.com/nasa/openmct/pull/999#issuecomment-236045158
2016-07-28 15:48:30 -07:00
Victor Woeltjen
bfdbc71e40 [API] Define a properties region 2016-07-28 15:34:03 -07:00
Pete Richards
1147f3aa47 tutorials: support arbitrary hosting directory (#1097)
* Include all bundles in artifact

change bundle registry such that all bundles are immediately registered,
but must be specifically enabled.  A default registry class enables bundles
that make sense for demonstration purposes.

Added methods to the registry to allow enabling and disabling of bundles
without having to load additional files.

* support alternate asset/worker paths

Change the gulp glob for assets to copy over a more minimal
set of files-- only css, fonts, and images.  Results in a
smaller distributable archive.

Update stylesheet loader to use a constant for the assets path.

This can be customized at run time via MCT.setAssetPath() to
allow MCT to be hosted in various locations.

Update worker loader to support loading workers from blobs to
support packaging as standalone file.

* Load templates via requirejs

* [gulp] lazy-require where reasonable

Require things right before starting tasks to reduce gulp start up time.

* document setAssetPath
2016-07-25 14:38:44 -07:00
Victor Woeltjen
719f9f45e8 [API] Add documentation for selection state (#1096) 2016-07-22 14:09:31 -07:00
Victor Woeltjen
95ef70a24c [API] Use selection state from toolbar (#1070)
* [API] Allow selection

* [API] Keep in sync using model

* [API] Add selection as EventEmitter

* [API] Use selection from ToDo tutorial

* [API] Restore selection functionality
2016-07-22 13:56:45 -07:00
Pete Richards
d5aa998b4c [API] Draft Composition API (#1068)
* [Objects] util for equality checking

Add a method for checking object equality, useful for other services.

* [Composition] Draft Composition API

Draft composition API.  Composition collections provide an observable
for watching and mutating the composition of an object.

Composition providers implement the loading and modification of composition.

The default composition provider uses the composition attribute of
domain objects, while allowing other providers to implement their
own loading and mutation behavior.

* add todo about event listener bindings

* [Type] Add form property for defining form fields

* [tutorial] Add Composition tutorial

* provider doesn't have to implement events, load returns array of children

* use new composition in old api

* correct key name

* Override instantiate to provide model ids

Override instantiate in public API adapter to prevent making changes to
platform code.  Instantiate now passes the id of the domain object with the
model so that capabilities can convert to a new-style domain object and use
that to detect functionality.

* Implement mutation capability with decorator

Implementation mutation capability override with decorator to adapter code
outside of platform.  Capability override ensures that models are kept in
sync even though they are no longer shared objects.

* override composition cleanly

Override composition capability without making changes inside platform.

* cleanup after temporary collections

* remove unused try/catch
2016-07-22 13:53:03 -07:00
Pete Richards
7890fcae69 tutorial consistency . (#1079)
* [API] use new-style objects consistently

* rewrite todo tutorial in test-api.html

* [API] Add API doc, update object API

* [Tutorials] Rename tutorials, remove old

* Fix Links

* updates

* initial

* hope this works

* Object Utils always return new objects instead of mutating existing objects

* keep domain object model in-sync when listening

Keep the domain object model in sync with the latest version when
listening for mutation events.

* Remove old-style plugins

* Update views to use new API

* Tidy Code

* Update API Docs

* Add Plugin API and Example
2016-07-21 14:39:02 -07:00
Victor Woeltjen
18843cee48 [API] Change approach to applies-to checking (#1072)
* [API] Allow selection

* [API] Keep in sync using model

* [API] Add selection as EventEmitter

* [API] Use selection from ToDo tutorial

* [API] Add appliesTo-style method

* [API] Remove destroy method, simplify show

* [View] Return a no-op

* [API] Use new applies-to checking

* [API] Rename TodoView to TodoRenderer

* [API] Rewire views

* [API] Wire up so that things work

* [API] Begin adding container

...to attempt to give views something to listen to for destroy-like
events

* [API] Begin using regions...

* [API] Begin working through Region stuff

* [API] Revise Region API

...for similarity with Marionette,
https://github.com/nasa/openmct/pull/1072#issuecomment-230902986

* [API] Begin separating View, ViewDefinition

* [API] Finish separating View/ViewDefinition

* [API] Update MCTView

...to reflect updates to Region/View/ViewDefinition APIs

* [API] Simplify View API

...merging closely-related populate/show methods, and restoring
compatibility with todo tutorial

* [API] Wire in from todo tutorial plugin

* [API] Switch back to region constants

* [API] Update method signature, add JSDoc

* [API] Update variable name

* [API] Remove unnecessary separate regions file

* [API] Relocate Region; not external api

* [API] Revert changes to api.js

...as these ended up becoming entirely superficial
2016-07-20 13:46:03 -07:00
Andrew Henry
1879c122c7 Mutation API (#1074)
* [API] Allow selection

* [API] Keep in sync using model

* [API] Add selection as EventEmitter

* [API] Use selection from ToDo tutorial

* Object events prototype

* Added examples

* Transitional API

* Modified todo list code to work with new setters

* [API] Removed emitting of events on container when property changes value to remove ambiguity. Listeners must be listening to the same path used in the setter to catch changes
2016-07-07 14:30:45 -07:00
Pete Richards
d7ddb96c4e [API] UMD Packaging (#1078)
* [Bundle] load filter with requirejs

* [Build] Use almond, wrap in UMD

Use almond for built version of application and wrap in UMD so that
it supports requirejs, commonjs, and basic browser loading.

* [Main] Can choose where to load app

MCT.run allows you to specify a dom element to load application
within.  If element is not specified, will use body.

* [MCT] set class on injected div

Set class on injected div so extra markup is not required.

* [Build] Re-enable optimize

* Add minimal bootstrap example
2016-07-07 14:25:23 -07:00
Andrew Henry
b55668426d Merge pull request #1062 from nasa/tc-redux
[Time Conductor] V2 Public API
2016-07-01 10:22:16 -07:00
Henry
5b656faa9d Added tests 2016-07-01 10:22:44 -07:00
Pete Richards
8d2c489fa9 [TimeConductor] Set bounds on timeSystem Change
Always set bounds on timeSystem change as not having valid bounds would
put views in inconsistent states.
2016-07-01 10:22:44 -07:00
Henry
4366b0870d [Time Conductor] API redesign. Initial commit of V2 public API. Addresses #933 2016-07-01 10:22:44 -07:00
Victor Woeltjen
47a543beb7 Merge pull request #1067 from nasa/api-css
[API] Remove stylesheet from example
2016-07-01 10:17:36 -07:00
Victor Woeltjen
06f87c1472 Merge pull request #1029 from nasa/api-toolbar-add-only
[API Prototype] Add toolbar
2016-07-01 10:13:29 -07:00
Victor Woeltjen
c9c41cdcc8 Merge remote-tracking branch 'origin/api-tutorials' into api-toolbar-add-only
Conflicts:
	src/MCT.js
2016-07-01 10:10:33 -07:00
Victor Woeltjen
14a56ea17e Merge pull request #1028 from nasa/api-view
[API Prototype] Support imperative view registration
2016-07-01 10:09:29 -07:00
Victor Woeltjen
b2e7db71cc Merge remote-tracking branch 'origin/api-tutorials' into api-view
Conflicts:
	src/MCT.js
	src/api/api.js
2016-07-01 10:08:05 -07:00
Victor Woeltjen
d51e6bfd92 Merge pull request #1030 from nasa/api-tutorial/objects
Api tutorial/objects
2016-07-01 10:03:42 -07:00
Pete Richards
d475d767d5 add grootprovider 2016-06-17 17:05:05 -07:00
Pete Richards
a63e053399 [ObjectAPI] Draft new Object API
Rought prototype of new object API.
2016-06-17 16:59:35 -07:00
Victor Woeltjen
370b515c23 [API] Synchronize view to model 2016-06-17 14:21:37 -07:00
Victor Woeltjen
4a50f325cb [API] Allow tasks to be added 2016-06-17 14:18:51 -07:00
Victor Woeltjen
dbe6a4efc1 [API] Title dialog 2016-06-17 14:05:00 -07:00
Victor Woeltjen
13920d8802 [API] Resolve/reject from dialog 2016-06-17 14:00:45 -07:00
Victor Woeltjen
b6a8c514aa [API] Show dialog from toolbar 2016-06-17 13:51:15 -07:00
Victor Woeltjen
e4a4704baa [API] Listen to add/remove buttons 2016-06-17 13:41:59 -07:00
Victor Woeltjen
be0029e59a [API] Get todo toolbar to look right 2016-06-17 13:38:54 -07:00
Victor Woeltjen
9cb273ef0a [API] Get registered toolbar to appear 2016-06-17 13:30:08 -07:00
Victor Woeltjen
eec9b1cf4c [API] Support distinct region registration 2016-06-17 13:10:24 -07:00
Victor Woeltjen
1f96e84542 [API] Override template to allow toolbar injection 2016-06-17 12:16:46 -07:00
Victor Woeltjen
c289a27305 [API] Begin adding toolbar 2016-06-17 11:43:49 -07:00
Victor Woeltjen
c944080790 [API] Remove stylesheet from example
No need to provide custom API for this.
2016-06-17 11:27:04 -07:00
Victor Woeltjen
96316de6e4 [API] Update view API 2016-06-17 11:16:08 -07:00
Victor Woeltjen
2240a87ddc [API] Move view off of type 2016-06-17 10:53:56 -07:00
Victor Woeltjen
d891affe48 [API] Move view off of type 2016-06-17 10:21:00 -07:00
Victor Woeltjen
21a618d1ce Merge branch 'api-type-proto' into api-view 2016-06-17 10:19:44 -07:00
Victor Woeltjen
5de7a96ccc Merge pull request #1010 from nasa/api-type-proto
[API Prototype] Type registration
2016-06-17 10:18:42 -07:00
Victor Woeltjen
09a833f524 Merge branch 'api-tutorials' into api-type-proto 2016-06-10 13:28:09 -07:00
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
Victor Woeltjen
5b6ea600ba Merge pull request #930 from nasa/open633
[New Edit Mode] #633 Remove Editing workflow concerns from FixedController, LayoutController
2016-05-20 16:10:46 -07:00
Henry
fd9d766913 Defer resolution of scope in DropGesture 2016-05-20 15:15:18 -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
127 changed files with 5173 additions and 984 deletions

114
API.md Normal file
View File

@@ -0,0 +1,114 @@
# Open MCT API
The Open MCT framework public api can be utilized by building the application (`gulp install`) and then copying the file from `dist/main.js` to your directory
of choice.
Open MCT supports AMD, CommonJS, and standard browser loading; it's easy to use
in your project.
## Overview
Open MCT's goal is to allow you to browse, create, edit, and visualize all of the domain knowledge you need on a daily basis.
To do this, the main building block provided by Open MCT is the domain object-- the temperature sensor on the starboard solar panel, an overlay plot comparing the results of all temperature sensor, the command dictionary for a spacecraft, the individual commands in that dictionary, your "my documents" folder: all of these things are domain objects.
Domain objects have Types-- so a specific instrument temperature sensor is a "Telemetry Point," and turning on a drill for a certain duration of time is an "Activity". Types allow you to form an ontology of knowledge and provide an abstraction for grouping, visualizing, and interpreting data.
And then we have Views. Views allow you to visualize a domain object. Views can apply to specific domain objects; they may also apply to certain types of domain objects, or they may apply to everything. Views are simply a method of visualizing domain objects.
Regions allow you to specify what views are displayed for specific types of domain objects in response to different user actions-- for instance, you may want to display a different view while editing, or you may want to update the toolbar display when objects are selected. Regions allow you to map views to specific user actions.
Domain objects can be mutated and persisted, developers can create custom actions and apply them to domain objects, and many more things can be done. For more information, read on.
## The API
### `MCT.Type(options)`
Status: First Draft
Returns a `typeInstance`. `options` is an object supporting the following properties:
* `metadata`: `object` defining metadata used in displaying the object; has the following properties:
* `label`: `string`, the human-readible name of the type. used in menus and inspector.
* `glyph`: `string`, the name of the icon to display for this type, used in labels.
* `description`: `string`, a human readible description of the object and what it is for.
* `initialize`: `function` which initializes new instances of this type. it is called with an object, should add any default properties to that object.
* `creatable`: `boolean`, if true, this object will be visible in the create menu.
* `form`: `Array` an array of form fields, as defined... somewhere! Generates a property sheet that is visible while editing this object.
### `MCT.type(typeKey, typeInstance)`
Status: First Draft
Register a `typeInstance` with a given Type `key` (a `string`). There can only be one `typeInstance` registered per type `key`. typeInstances must be registered before they can be utilized.
### `MCT.Objects`
Status: First Draft
Allows you to register object providers, which allows you to integrate domain objects from various different sources. Also implements methods for mutation and persistence of objects. See [Object API](src/api/objects/README.md) for more details.
### `MCT.Composition`
Status: First Draft
Objects can contain other objects, and the Composition API allows you to fetch the composition of any given domain object, or implement custom methods for defining composition as necessary.
### `MCT.view(region, definition)`
Status: First Draft
Register a view factory for a specific region. View factories receive an instance of a domain object and return a `View` for that object, or return undefined if they do not know how to generate a view for that object.
* `ViewDefinition`: an object with the following properties:
* `canView(domainObject)`: should return truthy if the view is valid for a given domain object, falsy if it is not capable of generating a view for that object.
* `view(domainObject)`: should instantate and return a `View` for the given object.
* `metadata()`: a function that returns metadata about this view. Optional.
* `View`: an object containing a number of lifecycle methods:
* `view.show(container)`: instantiate a view (a set of dom elements) and attach it to the container.
* `view.destroy(container)`: remove any listeners and expect your dom elements to be destroyed.
For a basic introduction to views & types, check out these tutorials:
* [custom-view](custom-view.html) -- Implementing a custom view with vanilla javascript.
* [custom-view-react](custom-view-react.html) -- Implementing a custom view with React.
### `MCT.conductor`
Status: First Draft
The time conductor is an API that facilitates time synchronization across multiple components. Components that would like to be "time aware" may attach listeners to the time conductor API to allow them to remain synchronized with other components. For more information ont he time conductor API, please look at the API draft here: https://github.com/nasa/openmct/blob/66220b89ca568075f107505ba414de9457dc0427/platform/features/conductor-redux/src/README.md
### `MCT.selection`
Status: First Draft
Tracks the application's selection state (which elements of a view has a user selected?)
One or more JavaScript objects may be selected at any given time. User code is responsible for any necessary type-checking.
The following methods are exposed from this object:
* `select(value)`: Add `value` to the current selection.
* `deselect(value)`: Remove `value` from the current selection.
* `selected()`: Get array of all selected objects.
* `clear()`: Deselect all selected objects.
MCT.selection is an EventEmitter; a `change` event is emitted whenever the selection changes.
### `MCT.systems`
Status: Not Implemented, Needs to be ported from old system.
A registry for different time system definitions. Based upon the previous time format system which utilized the "formats" extension category.
### `MCT.run([container])`
Status: Stable Draft
Run the MCT application, loading the application into the `container`, a DOM element. If a container is not specified, the application is injected into the body of the page.
### `MCT.install(plugin)`
Status: Stable Draft
Install a plugin in MCT. Must be called before calling `run`. Plugins are functions which are invoked with the `MCT` instance as their first argument, and are expected to use the MCT public API to add functionality.
For an example of writing a plugin, check out [plugin-example.html](plugin-example.html)
### `MCT.setAssetPath(path)`
Sets the path (absolute or relative) at which the Open MCT static files are being hosted. The default value is '.'.
Note that this API is transitional and will be removed in a future version.

View File

@@ -18,6 +18,9 @@
"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",
"lodash": "3.10.1",
"almond": "~0.3.2"
}
}

View File

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

65
composition-test.html Normal file
View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Implementing a composition provider</title>
<script src="dist/main.js"></script>
</head>
<body>
<script>
var widgetParts = ['foo', 'bar', 'baz', 'bing', 'frobnak']
function fabricateName() {
return [
widgetParts[Math.floor(Math.random() * widgetParts.length)],
widgetParts[Math.floor(Math.random() * widgetParts.length)],
Math.floor(Math.random() * 1000)
].join('_');
}
MCT.type('example.widget-factory', new MCT.Type({
metadata: {
label: "Widget Factory",
glyph: "s",
description: "A factory for making widgets"
},
initialize: function (object) {
object.widgetCount = 5;
object.composition = [];
},
creatable: true,
form: [
{
name: "Widget Count",
control: "textfield",
key: "widgetCount",
property: "widgetCount",
required: true
}
]
}));
MCT.Composition.addProvider({
appliesTo: function (domainObject) {
return domainObject.type === 'example.widget-factory';
},
load: function (domainObject) {
var widgets = [];
while (widgets.length < domainObject.widgetCount) {
widgets.push({
name: fabricateName(),
key: {
namespace: 'widget-factory',
identifier: '' + widgets.length
}
});
}
return Promise.resolve(widgets);
}
});
MCT.run();
</script>
</body>
</html>

144
custom-view-react.html Normal file
View File

@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Implementing a Custom Type and View </title>
<script src="dist/main.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.1/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
</head>
<body>
<script type="text/babel">
// First, we're going to create the Todo List type, so that users can create
// todo lists.
MCT.type('example.todo', new MCT.Type({
metadata: {
label: "To-Do List",
glyph: "2",
description: "A list of things that need to be done."
},
initialize: function (object) {
object.tasks = [
{ description: "This is a task." }
];
},
creatable: true
}));
/*
Refresh the page, and you should be able to create new Todo Lists.
unfortunately, when you navigate to a Todo list, you see a blank page. let's
fix that by adding a main view for that todo list.
If you're wondering why this is commented out, well, it's because we'll
write a new version later.
*/
var Task = React.createClass({
render: function() {
return (
<li>
<input type="checkbox"
checked={this.props.checked}/>
<span>{this.props.description}</span>
</li>
);
}
});
var TaskList = React.createClass({
render: function () {
var taskNodes = this.props.tasks.map(function(task) {
return (
<Task checked={task.checked}
description={task.description}/>
);
});
return (
<ul>
{taskNodes}
</ul>
);
}
});
MCT.view(MCT.regions.main, {
canView: function (domainObject) {
return domainObject.type === 'example.todo';
},
view: function (domainObject) {
var mutableObject = MCT.Objects.getMutable(domainObject);
return {
show: function (container) {
ReactDOM.render(
<TaskList tasks={domainObject.tasks}/>,
container
);
mutableObject.on('tasks', function (tasks) {
ReactDOM.render(
<TaskList tasks={tasks}/>,
container
);
});
}
};
}
});
/*
Refresh the page and you should see a todo list with checkboxes! Now let's
Allow you to add tasks by mutating the object. We'll add a toolbar view to
do this.
*/
var TaskToolbar = React.createClass({
render: function () {
return (
<button onClick={this.props.addTask}>Add Task</button>
);
}
});
MCT.view(MCT.regions.toolbar, {
canView: function (domainObject) {
return domainObject.type === 'example.todo';
},
view: function (domainObject) {
var mutableObject = MCT.Objects.getMutable(domainObject);
function addTask(event) {
var description = prompt('Task description');
var tasks = mutableObject.get('tasks');
tasks.push({
description: description,
complete: false
});
mutableObject.set('tasks', tasks);
}
return {
show: function (container) {
ReactDOM.render(
<TaskToolbar addTask={addTask}/>,
container
);
},
}
}
});
/*
Refresh the page, edit the todo list, and you'll have a button that allows
you to add tasks! Unfortunately, new tasks don't show in the list. Why?
Well, if your view should update on mutation, you need to set up the correct
listeners. Let's update the TodoView we made earlier:
*/
MCT.run();
</script>
</body>
</html>

160
custom-view.html Normal file
View File

@@ -0,0 +1,160 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Implementing a Custom Type and View </title>
<script src="dist/main.js"></script>
</head>
<body>
<script>
// First, we're going to create the Todo List type, so that users can create
// todo lists.
MCT.type('example.todo', new MCT.Type({
metadata: {
label: "To-Do List",
glyph: "2",
description: "A list of things that need to be done."
},
initialize: function (object) {
object.tasks = [
{ description: "This is a task." }
];
},
creatable: true
}));
/*
Refresh the page, and you should be able to create new Todo Lists.
unfortunately, when you navigate to a Todo list, you see a blank page. let's
fix that by adding a main view for that todo list.
If you're wondering why this is commented out, well, it's because we'll
write a new version later.
*/
// MCT.view(MCT.regions.main, {
// canView: function (domainObject) {
// return domainObject.type === 'example.todo';
// },
// view: function (domainObject) {
// function renderTask(task) {
// return [
// '<li>',
// '<input type="checkbox"' + (task.complete ? ' checked="true"' : '') + '>',
// '<span>' + task.description + '</span>',
// '</li>'
// ].join('');
// };
//
// function renderTaskList() {
// return [
// '<ul>',
// domainObject.tasks.map(renderTask).join(''),
// '</ul>'
// ].join('');
// };
//
// return {
// show: function (container) {
// container.innerHTML = renderTaskList();
// }
// };
// }
// });
/*
Refresh the page and you should see a todo list with checkboxes! Now let's
Allow you to add tasks by mutating the object. We'll add a toolbar view to
do this.
*/
MCT.view(MCT.regions.toolbar, {
canView: function (domainObject) {
return domainObject.type === 'example.todo';
},
view: function (domainObject) {
var mutableObject = MCT.Objects.getMutable(domainObject);
function addTask(event) {
var description = prompt('Task description');
var tasks = mutableObject.get('tasks');
tasks.push({
description: description,
complete: false
});
mutableObject.set('tasks', tasks);
}
return {
show: function (container) {
container.addEventListener('click', addTask);
container.innerHTML = '<button>Add Task</button>';
},
destroy: function (container) {
container.removeEventListener('click', addTask);
}
}
}
});
/*
Refresh the page, edit the todo list, and you'll have a button that allows
you to add tasks! Unfortunately, new tasks don't show in the list. Why?
Well, if your view should update on mutation, you need to set up the correct
listeners. Let's update the TodoView we made earlier:
*/
MCT.view(MCT.regions.main, {
canView: function(domainObject) {
return domainObject.type === 'example.todo'
},
view: function (domainObject) {
var mutableObject = MCT.Objects.getMutable(domainObject);
function renderTask(task) {
return [
'<li>',
'<input type="checkbox"' + (task.complete ? ' checked="true"' : '') + '>',
'<span>' + task.description + '</span>',
'</li>'
].join('');
}
function renderTaskList(tasks) {
return [
'<ul>',
tasks.map(renderTask).join(''),
'</ul>'
].join('');
}
function onCheckboxChange(event) {
var checkbox = event.target;
var taskEl = checkbox.parentNode;
var taskList = taskEl.parentNode;
var taskIndex = [].slice.apply(taskList.children).indexOf(taskEl);
mutableObject.set('tasks[' + taskIndex + '].complete', checkbox.checked);
}
return {
show: function (container) {
container.innerHTML = renderTaskList(domainObject.tasks);
mutableObject.on('tasks', function (tasks) {
container.innerHTML = renderTaskList(tasks);
});
container.addEventListener('change', onCheckboxChange);
},
destroy: function () {
mutableObject.stopListening();
}
};
}
});
MCT.run();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

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

View File

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

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

View File

@@ -31,10 +31,14 @@
<script type="text/javascript">
require(['main'], function (mct) {
require([
'./tutorials/todo/todo',
'./example/imagery/bundle',
'./example/eventGenerator/bundle',
'./example/generator/bundle'
], mct.run.bind(mct));
], function (todoPlugin) {
mct.install(todoPlugin);
mct.run();
})
});
</script>
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
@@ -48,7 +52,5 @@
<div class="l-splash-holder s-splash-holder">
<div class="l-splash s-splash"></div>
</div>
<div ng-view></div>
</body>
</html>

68
main.js
View File

@@ -28,13 +28,15 @@ requirejs.config({
"angular-route": "bower_components/angular-route/angular-route.min",
"csv": "bower_components/comma-separated-values/csv.min",
"es6-promise": "bower_components/es6-promise/promise.min",
"EventEmitter": "bower_components/eventemitter3/index",
"moment": "bower_components/moment/moment",
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
"zepto": "bower_components/zepto/zepto.min"
"zepto": "bower_components/zepto/zepto.min",
"lodash": "bower_components/lodash/lodash"
},
"shim": {
"angular": {
@@ -43,6 +45,9 @@ requirejs.config({
"angular-route": {
"deps": ["angular"]
},
"EventEmitter": {
"exports": "EventEmitter"
},
"moment-duration-format": {
"deps": ["moment"]
},
@@ -51,53 +56,32 @@ requirejs.config({
},
"zepto": {
"exports": "Zepto"
},
"lodash": {
"exports": "lodash"
}
}
});
define([
'./platform/framework/src/Main',
'legacyRegistry',
'./src/defaultRegistry',
'./src/MCT'
], function (Main, defaultRegistry, MCT) {
var mct = new MCT();
'./platform/framework/bundle',
'./platform/core/bundle',
'./platform/representation/bundle',
'./platform/commonUI/about/bundle',
'./platform/commonUI/browse/bundle',
'./platform/commonUI/edit/bundle',
'./platform/commonUI/dialog/bundle',
'./platform/commonUI/formats/bundle',
'./platform/commonUI/general/bundle',
'./platform/commonUI/inspect/bundle',
'./platform/commonUI/mobile/bundle',
'./platform/commonUI/themes/espresso/bundle',
'./platform/commonUI/notification/bundle',
'./platform/containment/bundle',
'./platform/execution/bundle',
'./platform/exporters/bundle',
'./platform/telemetry/bundle',
'./platform/features/clock/bundle',
'./platform/features/imagery/bundle',
'./platform/features/layout/bundle',
'./platform/features/pages/bundle',
'./platform/features/plot/bundle',
'./platform/features/timeline/bundle',
'./platform/features/table/bundle',
'./platform/forms/bundle',
'./platform/identity/bundle',
'./platform/persistence/aggregator/bundle',
'./platform/persistence/local/bundle',
'./platform/persistence/queue/bundle',
'./platform/policy/bundle',
'./platform/entanglement/bundle',
'./platform/search/bundle',
'./platform/status/bundle',
'./platform/commonUI/regions/bundle'
], function (Main, legacyRegistry) {
return {
legacyRegistry: legacyRegistry,
run: function () {
return new Main().run(legacyRegistry);
}
mct.legacyRegistry = defaultRegistry;
mct.run = function (domElement) {
if (!domElement) { domElement = document.body; }
var appDiv = document.createElement('div');
appDiv.setAttribute('ng-view', '');
appDiv.className = 'user-environ';
domElement.appendChild(appDiv);
mct.start();
};
mct.on('start', function () {
return new Main().run(defaultRegistry);
});
return mct;
});

64
object-provider.html Normal file
View File

@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Groot Tutorial</title>
<script src="dist/main.js"></script>
</head>
<body>
<script>
// First, we need a source of objects, so we're going to define a few
// objects that we wish to expose. The first object is a folder which
// contains the other objects.
var GROOT_ROOT = {
name: 'I am groot',
type: 'folder',
composition: [
{
namespace: 'groot',
identifier: 'arms'
},
{
namespace: 'groot',
identifier: 'legs'
},
{
namespace: 'groot',
identifier: 'torso'
}
]
};
// Now, we will define an object provider. This will allow us to fetch the
// GROOT_ROOT object, plus any other objects in the `groot` namespace.
// For more info, see the Object API documentation.
MCT.Objects.addProvider('groot', {
// we'll only define a get function, objects from this provider will
// not be mutable.
get: function (key) {
if (key.identifier === 'groot') {
return Promise.resolve(GROOT_ROOT);
}
return Promise.resolve({
name: 'Groot\'s ' + key.identifier
});
}
});
// Finally, we need to add a "ROOT." This is an identifier for an object
// that Open MCT will load at run time and show at the top-level of the
// navigation tree.
MCT.Objects.addRoot({
namespace: 'groot',
identifier: 'groot'
});
MCT.run();
</script>
</body>
</html>

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",
@@ -48,28 +39,20 @@ define([
"text!./res/templates/items/items.html",
"text!./res/templates/browse/object-properties.html",
"text!./res/templates/browse/inspector-region.html",
"text!./res/templates/view-object.html",
'legacyRegistry'
], function (
BrowseController,
PaneController,
BrowseObjectController,
CreateMenuController,
LocatorController,
MenuArrowController,
NavigationService,
CreationPolicy,
NavigateAction,
NewTabAction,
FullscreenAction,
CreateActionProvider,
AddActionProvider,
CreationService,
WindowTitler,
browseTemplate,
locatorTemplate,
browseObjectTemplate,
createButtonTemplate,
createMenuTemplate,
gridItemTemplate,
objectHeaderTemplate,
menuArrowTemplate,
@@ -77,6 +60,7 @@ define([
itemsTemplate,
objectPropertiesTemplate,
inspectorRegionTemplate,
viewObjectTemplate,
legacyRegistry
) {
@@ -136,22 +120,6 @@ define([
"$route"
]
},
{
"key": "CreateMenuController",
"implementation": CreateMenuController,
"depends": [
"$scope"
]
},
{
"key": "LocatorController",
"implementation": LocatorController,
"depends": [
"$scope",
"$timeout",
"objectService"
]
},
{
"key": "MenuArrowController",
"implementation": MenuArrowController,
@@ -160,16 +128,10 @@ define([
]
}
],
"controls": [
{
"key": "locator",
"template": locatorTemplate
}
],
"representations": [
{
"key": "view-object",
"templateUrl": "templates/view-object.html"
"template": viewObjectTemplate
},
{
"key": "browse-object",
@@ -181,17 +143,6 @@ define([
"view"
]
},
{
"key": "create-button",
"template": createButtonTemplate
},
{
"key": "create-menu",
"template": createMenuTemplate,
"uses": [
"action"
]
},
{
"key": "grid-item",
"template": gridItemTemplate,
@@ -244,12 +195,6 @@ define([
"implementation": NavigationService
}
],
"policies": [
{
"implementation": CreationPolicy,
"category": "creation"
}
],
"actions": [
{
"key": "navigate",
@@ -302,42 +247,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

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

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

@@ -38,7 +38,7 @@ define(
* @param $document Angular's jqLite-wrapped document element
* @param {string} activeTheme the theme in use
*/
function StyleSheetLoader(stylesheets, $document, activeTheme) {
function StyleSheetLoader(stylesheets, $document, activeTheme, assetPath) {
var head = $document.find('head'),
document = $document[0];
@@ -47,6 +47,7 @@ define(
// Create a link element, and construct full path
var link = document.createElement('link'),
path = [
assetPath,
stylesheet.bundle.path,
stylesheet.bundle.resources,
stylesheet.stylesheetUrl

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

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

View File

@@ -27,6 +27,9 @@ define([
"./src/controllers/TableOptionsController",
'../../commonUI/regions/src/Region',
'../../commonUI/browse/src/InspectorRegion',
"text!./res/templates/table-options-edit.html",
"text!./res/templates/rt-table.html",
"text!./res/templates/historical-table.html",
"legacyRegistry"
], function (
MCTTable,
@@ -35,6 +38,9 @@ define([
TableOptionsController,
Region,
InspectorRegion,
tableOptionsEditTemplate,
rtTableTemplate,
historicalTableTemplate,
legacyRegistry
) {
/**
@@ -128,23 +134,23 @@ define([
"name": "Historical Table",
"key": "table",
"glyph": "\ue604",
"templateUrl": "templates/historical-table.html",
"template": historicalTableTemplate,
"needs": [
"telemetry"
],
"delegation": true,
"editable": true
"editable": false
},
{
"name": "Real-time Table",
"key": "rt-table",
"glyph": "\ue620",
"templateUrl": "templates/rt-table.html",
"template": rtTableTemplate,
"needs": [
"telemetry"
],
"delegation": true,
"editable": true
"editable": false
}
],
"directives": [
@@ -157,7 +163,7 @@ define([
"representations": [
{
"key": "table-options-edit",
"templateUrl": "templates/table-options-edit.html"
"template": tableOptionsEditTemplate
}
],
"stylesheets": [

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

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

View File

@@ -28,6 +28,7 @@ define([
"text!./res/templates/search-item.html",
"text!./res/templates/search.html",
"text!./res/templates/search-menu.html",
"text!./src/services/GenericSearchWorker.js",
'legacyRegistry'
], function (
SearchController,
@@ -37,6 +38,7 @@ define([
searchItemTemplate,
searchTemplate,
searchMenuTemplate,
searchWorkerText,
legacyRegistry
) {
@@ -114,7 +116,7 @@ define([
"workers": [
{
"key": "genericSearchWorker",
"scriptUrl": "services/GenericSearchWorker.js"
"scriptText": searchWorkerText
}
]
}

50
plugin-example.html Normal file
View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Implementing a basic plugin</title>
<script src="dist/main.js"></script>
</head>
<body>
<script>
function WebPlugin(websites) {
this.websites = websites;
var ROOTS = websites.reduce(function (rootMap, website) {
rootMap[website] = {
namespace: website,
identifier: 'page'
};
return rootMap;
}, {});
function installPlugin(MCT) {
Object.keys(ROOTS).forEach(function (rootUrl) {
MCT.Objects.addRoot(ROOTS[rootUrl]);
MCT.Objects.addProvider(rootUrl, {
get: function () {
return Promise.resolve({
type: 'example.page',
url: rootUrl,
name: rootUrl
});
}
});
});
}
return installPlugin;
}
var myWebPlugin = WebPlugin([
'http://www.wikipedia.org/',
'http://nasa.github.io/openmct'
]);
MCT.install(myWebPlugin);
MCT.run();
</script>
</body>
</html>

View File

@@ -24,10 +24,27 @@ define(function () {
function BundleRegistry() {
this.bundles = {};
this.knownBundles = {};
}
BundleRegistry.prototype.register = function (path, definition) {
this.bundles[path] = definition;
if (this.knownBundles.hasOwnProperty(path)) {
throw new Error('Cannot register bundle with duplicate path', path);
}
this.knownBundles[path] = definition;
};
BundleRegistry.prototype.enable = function (path) {
if (!this.knownBundles[path]) {
throw new Error('Unknown bundle ' + path);
}
this.bundles[path] = this.knownBundles[path];
};
BundleRegistry.prototype.disable = function (path) {
if (!this.bundles[path]) {
throw new Error('Tried to disable inactive bundle ' + path);
}
};
BundleRegistry.prototype.contains = function (path) {
@@ -42,8 +59,14 @@ define(function () {
return Object.keys(this.bundles);
};
BundleRegistry.prototype.remove = function (path) {
BundleRegistry.prototype.remove = BundleRegistry.prototype.disable;
BundleRegistry.prototype.delete = function (path) {
if (!this.knownBundles[path]) {
throw new Error('Cannot remove Unknown Bundle ' + path);
}
delete this.bundles[path];
delete this.knownBundles[path];
};
return BundleRegistry;

151
src/MCT.js Normal file
View File

@@ -0,0 +1,151 @@
define([
'EventEmitter',
'legacyRegistry',
'uuid',
'./api/api',
'text!./adapter/templates/edit-object-replacement.html',
'./Selection',
'./api/objects/object-utils'
], function (
EventEmitter,
legacyRegistry,
uuid,
api,
editObjectTemplate,
Selection,
objectUtils
) {
function MCT() {
EventEmitter.call(this);
this.legacyBundle = { extensions: {
services: [
{
key: "mct",
implementation: function () {
return this;
}.bind(this)
}
]
} };
this.selection = new Selection();
this.on('navigation', this.selection.clear.bind(this.selection));
}
MCT.prototype = Object.create(EventEmitter.prototype);
Object.keys(api).forEach(function (k) {
MCT.prototype[k] = api[k];
});
MCT.prototype.MCT = MCT;
MCT.prototype.legacyExtension = function (category, extension) {
this.legacyBundle.extensions[category] =
this.legacyBundle.extensions[category] || [];
this.legacyBundle.extensions[category].push(extension);
};
/**
* Set path to where assets are hosted. This should be the path to main.js.
*/
MCT.prototype.setAssetPath = function (path) {
this.legacyExtension('constants', {
key: "ASSETS_PATH",
value: path
});
};
/**
* Register a new type of view.
*
* @param region the region identifier (see mct.regions)
* @param {ViewDefinition} definition the definition for this view
*/
MCT.prototype.view = function (region, definition) {
var viewKey = region + uuid();
var adaptedViewKey = "adapted-view-" + region;
this.legacyExtension(
region === this.regions.main ? 'views' : 'representations',
{
name: "A view",
key: adaptedViewKey,
editable: true,
template: '<mct-view region="\'' +
region +
'\'" ' +
'key="\'' +
viewKey +
'\'" ' +
'mct-object="domainObject">' +
'</mct-view>'
}
);
this.legacyExtension('policies', {
category: "view",
implementation: function Policy() {
this.allow = function (view, domainObject) {
if (view.key === adaptedViewKey) {
var model = domainObject.getModel();
var newDO = objectUtils.toNewFormat(model);
return definition.canView(newDO);
}
return true;
};
}
});
this.legacyExtension('newViews', {
factory: definition,
region: region,
key: viewKey
});
};
MCT.prototype.type = function (key, type) {
var legacyDef = type.toLegacyDefinition();
legacyDef.key = key;
type.key = key;
this.legacyExtension('types', legacyDef);
this.legacyExtension('representations', {
key: "edit-object",
priority: "preferred",
template: editObjectTemplate,
type: key
});
};
MCT.prototype.start = function () {
this.legacyExtension('runs', {
depends: ['navigationService'],
implementation: function (navigationService) {
navigationService
.addListener(this.emit.bind(this, 'navigation'));
}.bind(this)
});
legacyRegistry.register('adapter', this.legacyBundle);
legacyRegistry.enable('adapter');
this.emit('start');
};
/**
* Install a plugin in MCT.
*
* @param `Function` plugin -- a plugin install function which will be
* invoked with the mct instance.
*/
MCT.prototype.install = function (plugin) {
plugin(this);
};
MCT.prototype.regions = {
main: "MAIN",
properties: "PROPERTIES",
toolbar: "TOOLBAR"
};
return MCT;
});

32
src/Selection.js Normal file
View File

@@ -0,0 +1,32 @@
define(['EventEmitter'], function (EventEmitter) {
function Selection() {
EventEmitter.call(this);
this.selectedValues = [];
}
Selection.prototype = Object.create(EventEmitter.prototype);
Selection.prototype.select = function (value) {
this.selectedValues.push(value);
this.emit('change', this.selectedValues);
return this.deselect.bind(this, value);
};
Selection.prototype.deselect = function (value) {
this.selectedValues = this.selectedValues.filter(function (v) {
return v !== value;
});
this.emit('change', this.selectedValues);
};
Selection.prototype.selected = function () {
return this.selectedValues;
};
Selection.prototype.clear = function () {
this.selectedValues = [];
this.emit('change', this.selectedValues);
};
return Selection;
});

View File

@@ -0,0 +1,44 @@
define([
'../../api/objects/object-utils'
], function (objectUtils) {
function ActionDialogDecorator(mct, newViews, actionService) {
this.actionService = actionService;
this.mct = mct;
this.definitions = newViews.filter(function (newView) {
return newView.region === mct.regions.properties;
}).map(function (newView) {
return newView.factory;
});
}
ActionDialogDecorator.prototype.getActions = function (context) {
var mct = this.mct;
var definitions = this.definitions;
return this.actionService.getActions(context).map(function (action) {
if (action.dialogService) {
var domainObject = objectUtils.toNewFormat(
context.domainObject.getModel(),
objectUtils.parseKeyString(context.domainObject.getId())
);
definitions = definitions.filter(function (definition) {
return definition.canView(domainObject);
});
if (definitions.length > 0) {
action.dialogService = Object.create(action.dialogService);
action.dialogService.getUserInput = function (form, value) {
return new mct.Dialog(
definitions[0].view(context.domainObject),
form.title
).show();
};
}
}
return action;
});
};
return ActionDialogDecorator;
});

65
src/adapter/bundle.js Normal file
View File

@@ -0,0 +1,65 @@
define([
'legacyRegistry',
'./actions/ActionDialogDecorator',
'./directives/MCTView',
'./services/Instantiate',
'./capabilities/APICapabilityDecorator',
'./policies/AdapterCompositionPolicy'
], function (
legacyRegistry,
ActionDialogDecorator,
MCTView,
Instantiate,
APICapabilityDecorator,
AdapterCompositionPolicy
) {
legacyRegistry.register('src/adapter', {
"extensions": {
"directives": [
{
key: "mctView",
implementation: MCTView,
depends: [
"newViews[]",
"mct"
]
}
],
services: [
{
key: "instantiate",
priority: "mandatory",
implementation: Instantiate,
depends: [
"capabilityService",
"identifierService",
"cacheService"
]
}
],
components: [
{
type: "decorator",
provides: "capabilityService",
implementation: APICapabilityDecorator,
depends: [
"$injector"
]
},
{
type: "decorator",
provides: "actionService",
implementation: ActionDialogDecorator,
depends: [ "mct", "newViews[]" ]
}
],
policies: [
{
category: "composition",
implementation: AdapterCompositionPolicy,
depends: [ "mct" ]
}
]
}
});
});

View File

@@ -0,0 +1,37 @@
define([
'./synchronizeMutationCapability',
'./AlternateCompositionCapability'
], function (
synchronizeMutationCapability,
AlternateCompositionCapability
) {
/**
* Overrides certain capabilities to keep consistency between old API
* and new API.
*/
function APICapabilityDecorator($injector, capabilityService) {
this.$injector = $injector;
this.capabilityService = capabilityService;
}
APICapabilityDecorator.prototype.getCapabilities = function (
model
) {
var capabilities = this.capabilityService.getCapabilities(model);
if (capabilities.mutation) {
capabilities.mutation =
synchronizeMutationCapability(capabilities.mutation);
}
if (AlternateCompositionCapability.appliesTo(model)) {
capabilities.composition = function (domainObject) {
return new AlternateCompositionCapability(this.$injector, domainObject)
}.bind(this);
}
return capabilities;
};
return APICapabilityDecorator;
});

View File

@@ -0,0 +1,102 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* Module defining AlternateCompositionCapability. Created by vwoeltje on 11/7/14.
*/
define([
'../../api/objects/object-utils',
'../../api/composition/CompositionAPI'
], function (objectUtils, CompositionAPI) {
function AlternateCompositionCapability($injector, domainObject) {
this.domainObject = domainObject;
this.getDependencies = function () {
this.instantiate = $injector.get("instantiate");
this.contextualize = $injector.get("contextualize");
this.getDependencies = undefined;
}.bind(this);
}
AlternateCompositionCapability.prototype.add = function (child, index) {
if (typeof index !== 'undefined') {
// At first glance I don't see a location in the existing
// codebase where add is called with an index. Won't support.
throw new Error(
'Composition Capability does not support adding at index'
);
}
function addChildToComposition(model) {
var existingIndex = model.composition.indexOf(child.getId());
if (existingIndex === -1) {
model.composition.push(child.getId())
}
}
return this.domainObject.useCapability(
'mutation',
addChildToComposition
)
.then(this.invoke.bind(this))
.then(function (children) {
return children.filter(function (c) {
return c.getId() === child.getId();
})[0];
});
};
AlternateCompositionCapability.prototype.contextualizeChild = function (
child
) {
if (this.getDependencies) {
this.getDependencies();
}
var keyString = objectUtils.makeKeyString(child.key);
var oldModel = objectUtils.toOldFormat(child);
var newDO = this.instantiate(oldModel, keyString);
return this.contextualize(newDO, this.domainObject);
};
AlternateCompositionCapability.prototype.invoke = function () {
var newFormatDO = objectUtils.toNewFormat(
this.domainObject.getModel(),
this.domainObject.getId()
);
var collection = CompositionAPI(newFormatDO);
return collection.load()
.then(function (children) {
collection.destroy();
return children.map(this.contextualizeChild, this);
}.bind(this));
};
AlternateCompositionCapability.appliesTo = function (model) {
return !!CompositionAPI(objectUtils.toNewFormat(model, model.id));
};
return AlternateCompositionCapability;
}
);

View File

@@ -0,0 +1,27 @@
define([
], function (
) {
/**
* Wraps the mutation capability and synchronizes the mutation
*/
function synchronizeMutationCapability(mutationConstructor) {
return function makeCapability(domainObject) {
var capability = mutationConstructor(domainObject);
var oldListen = capability.listen.bind(capability);
capability.listen = function (listener) {
return oldListen(function (newModel) {
capability.domainObject.model =
JSON.parse(JSON.stringify(newModel));
listener(newModel);
});
};
return capability;
}
};
return synchronizeMutationCapability;
});

View File

@@ -0,0 +1,65 @@
define([
'angular',
'./Region',
'../../api/objects/object-utils'
], function (
angular,
Region,
objectUtils
) {
function MCTView(newViews, PublicAPI) {
var definitions = {};
newViews.forEach(function (newView) {
definitions[newView.region] = definitions[newView.region] || {};
definitions[newView.region][newView.key] = newView.factory;
});
return {
restrict: 'E',
link: function (scope, element, attrs) {
var key, mctObject, regionId, region;
function maybeShow() {
if (!definitions[regionId] || !definitions[regionId][key] || !mctObject) {
return;
}
region.show(definitions[regionId][key].view(mctObject));
}
function setKey(k) {
key = k;
maybeShow();
}
function setObject(obj) {
mctObject = undefined;
PublicAPI.Objects.get(objectUtils.parseKeyString(obj.getId()))
.then(function (mobj) {
mctObject = mobj;
maybeShow();
});
}
function setRegionId(r) {
regionId = r;
maybeShow();
}
region = new Region(element[0]);
scope.$watch('key', setKey);
scope.$watch('region', setRegionId);
scope.$watch('mctObject', setObject);
},
scope: {
key: "=",
region: "=",
mctObject: "="
}
};
}
return MCTView;
});

View File

@@ -0,0 +1,23 @@
define([], function () {
function Region(element) {
this.activeView = undefined;
this.element = element;
}
Region.prototype.clear = function () {
if (this.activeView) {
this.activeView.destroy();
this.activeView = undefined;
}
};
Region.prototype.show = function (view) {
this.clear();
this.activeView = view;
if (this.activeView) {
this.activeView.show(this.element);
}
};
return Region;
});

View File

@@ -0,0 +1,26 @@
define([], function () {
function AdapterCompositionPolicy(mct) {
this.mct = mct;
}
AdapterCompositionPolicy.prototype.allow = function (
containerType,
childType
) {
var containerObject = containerType.getInitialModel();
var childObject = childType.getInitialModel();
containerObject.type = containerType.getKey();
childObject.type = childType.getKey();
var composition = this.mct.Composition(containerObject);
if (composition) {
return composition.canContain(childObject);
}
return true;
};
return AdapterCompositionPolicy;
});

View File

@@ -0,0 +1,49 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['../../../platform/core/src/objects/DomainObjectImpl'],
function (DomainObjectImpl) {
/**
* Overrides platform version of instantiate, passes Id with model such
* that capability detection can utilize new format domain objects.
*/
function Instantiate(
capabilityService,
identifierService,
cacheService
) {
return function (model, id) {
id = id || identifierService.generate();
var old_id = model.id;
model.id = id;
var capabilities = capabilityService.getCapabilities(model);
model.id = old_id;
cacheService.put(id, model);
return new DomainObjectImpl(id, model, capabilities);
};
}
return Instantiate;
}
);

View File

@@ -0,0 +1,46 @@
<div class="abs l-flex-col" ng-controller="EditObjectController as EditObjectController">
<div mct-before-unload="EditObjectController.getUnloadWarning()"
class="holder flex-elem l-flex-row object-browse-bar ">
<div class="items-select left flex-elem l-flex-row grows">
<mct-representation key="'back-arrow'"
mct-object="domainObject"
class="flex-elem l-back"></mct-representation>
<mct-representation key="'object-header'"
mct-object="domainObject"
class="l-flex-row flex-elem grows object-header">
</mct-representation>
</div>
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<mct-representation key="'switcher'"
mct-object="domainObject"
ng-model="representation">
</mct-representation>
<!-- Temporarily, on mobile, the action buttons are hidden-->
<mct-representation key="'action-group'"
mct-object="domainObject"
parameters="{ category: 'view-control' }"
class="mobile-hide">
</mct-representation>
</div>
</div>
<div class="holder l-flex-col flex-elem grows l-object-wrapper">
<div class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
<!-- Toolbar and Save/Cancel buttons -->
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
<mct-representation key="'adapted-view-TOOLBAR'"
mct-object="domainObject"
class="flex-elem grows">
</mct-representation>
<mct-representation key="'edit-action-buttons'"
mct-object="domainObject"
class='flex-elem conclude-editing'>
</mct-representation>
</div>
<mct-representation key="representation.selected.key"
mct-object="representation.selected.key && domainObject"
class="abs flex-elem grows object-holder-main scroll"
toolbar="toolbar">
</mct-representation>
</div><!--/ l-object-wrapper-inner -->
</div>
</div>

183
src/api/TimeConductor.js Normal file
View File

@@ -0,0 +1,183 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['EventEmitter'], function (EventEmitter) {
/**
* The public API for setting and querying time conductor state. The
* time conductor is the means by which the temporal bounds of a view
* are controlled. Time-sensitive views will typically respond to
* changes to bounds or other properties of the time conductor and
* update the data displayed based on the time conductor state.
*
* The TimeConductor extends the EventEmitter class. A number of events are
* fired when properties of the time conductor change, which are
* documented below.
* @constructor
*/
function TimeConductor() {
EventEmitter.call(this);
//The Time System
this.system = undefined;
//The Time Of Interest
this.toi = undefined;
this.boundsVal = {
start: undefined,
end: undefined
};
//Default to fixed mode
this.followMode = false;
}
TimeConductor.prototype = Object.create(EventEmitter.prototype);
/**
* Validate the given bounds. This can be used for pre-validation of
* bounds, for example by views validating user inputs.
* @param bounds The start and end time of the conductor.
* @returns {string | true} A validation error, or true if valid
*/
TimeConductor.prototype.validateBounds = function (bounds) {
if ((bounds.start === undefined) ||
(bounds.end === undefined) ||
isNaN(bounds.start) ||
isNaN(bounds.end)
) {
return "Start and end must be specified as integer values";
} else if (bounds.start > bounds.end) {
return "Specified start date exceeds end bound";
}
return true;
};
function throwOnError(validationResult) {
if (validationResult !== true) {
throw new Error(validationResult);
}
}
/**
* Get or set the follow mode of the time conductor. In follow mode the
* time conductor ticks, regularly updating the bounds from a timing
* source appropriate to the selected time system and mode of the time
* conductor.
* @fires TimeConductor#follow
* @param {boolean} followMode
* @returns {boolean}
*/
TimeConductor.prototype.follow = function (followMode) {
if (arguments.length > 0) {
this.followMode = followMode;
/**
* @event TimeConductor#follow The TimeConductor has toggled
* into or out of follow mode.
* @property {boolean} followMode true if follow mode is
* enabled, otherwise false.
*/
this.emit('follow', this.followMode);
}
return this.followMode;
};
/**
* @typedef {Object} TimeConductorBounds
* @property {number} start The start time displayed by the time conductor in ms since epoch. Epoch determined by current time system
* @property {number} end The end time displayed by the time conductor in ms since epoch.
*/
/**
* Get or set the start and end time of the time conductor. Basic validation
* of bounds is performed.
*
* @param {TimeConductorBounds} newBounds
* @throws {Error} Validation error
* @fires TimeConductor#bounds
* @returns {TimeConductorBounds}
*/
TimeConductor.prototype.bounds = function (newBounds) {
if (arguments.length > 0) {
throwOnError(this.validateBounds(newBounds));
this.boundsVal = newBounds;
/**
* @event TimeConductor#bounds The start time, end time, or
* both have been updated
* @property {TimeConductorBounds} bounds
*/
this.emit('bounds', this.boundsVal);
}
return this.boundsVal;
};
/**
* Get or set the time system of the TimeConductor. Time systems determine
* units, epoch, and other aspects of time representation. When changing
* the time system in use, new valid bounds must also be provided.
* @param {TimeSystem} newTimeSystem
* @param {TimeConductorBounds} bounds
* @fires TimeConductor#timeSystem
* @returns {TimeSystem} The currently applied time system
*/
TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) {
if (arguments.length >= 2) {
this.system = newTimeSystem;
/**
* @event TimeConductor#timeSystem The time system used by the time
* conductor has changed. A change in Time System will always be
* followed by a bounds event specifying new query bounds
* @property {TimeSystem} The value of the currently applied
* Time System
* */
this.emit('timeSystem', this.system);
// Do something with bounds here. Try and convert between
// time systems? Or just set defaults when time system changes?
// eg.
this.bounds(bounds);
} else if (arguments.length === 1) {
throw new Error('Must set bounds when changing time system');
}
return this.system;
};
/**
* Get or set the Time of Interest. The Time of Interest is the temporal
* focus of the current view. It can be manipulated by the user from the
* time conductor or from other views.
* @fires TimeConductor#timeOfInterest
* @param newTOI
* @returns {number} the current time of interest
*/
TimeConductor.prototype.timeOfInterest = function (newTOI) {
if (arguments.length > 0) {
this.toi = newTOI;
/**
* @event TimeConductor#timeOfInterest The Time of Interest has moved.
* @property {number} Current time of interest
*/
this.emit('timeOfInterest', this.toi);
}
return this.toi;
};
return TimeConductor;
});

View File

@@ -0,0 +1,110 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./TimeConductor'], function (TimeConductor) {
describe("The Time Conductor", function () {
var tc,
timeSystem,
bounds,
eventListener,
toi,
follow;
beforeEach(function () {
tc = new TimeConductor();
timeSystem = {};
bounds = {start: 0, end: 0};
eventListener = jasmine.createSpy("eventListener");
toi = 111;
follow = true;
});
it("Supports setting and querying of time of interest and and follow mode", function () {
expect(tc.timeOfInterest()).not.toBe(toi);
tc.timeOfInterest(toi);
expect(tc.timeOfInterest()).toBe(toi);
expect(tc.follow()).not.toBe(follow);
tc.follow(follow);
expect(tc.follow()).toBe(follow);
});
it("Allows setting of valid bounds", function () {
bounds = {start: 0, end: 1};
expect(tc.bounds()).not.toBe(bounds);
expect(tc.bounds.bind(tc, bounds)).not.toThrow();
expect(tc.bounds()).toBe(bounds);
});
it("Disallows setting of invalid bounds", function () {
bounds = {start: 1, end: 0};
expect(tc.bounds()).not.toBe(bounds);
expect(tc.bounds.bind(tc, bounds)).toThrow();
expect(tc.bounds()).not.toBe(bounds);
bounds = {start: 1};
expect(tc.bounds()).not.toBe(bounds);
expect(tc.bounds.bind(tc, bounds)).toThrow();
expect(tc.bounds()).not.toBe(bounds);
});
it("Allows setting of time system with bounds", function () {
expect(tc.timeSystem()).not.toBe(timeSystem);
expect(tc.timeSystem.bind(tc, timeSystem, bounds)).not.toThrow();
expect(tc.timeSystem()).toBe(timeSystem);
});
it("Disallows setting of time system without bounds", function () {
expect(tc.timeSystem()).not.toBe(timeSystem);
expect(tc.timeSystem.bind(tc, timeSystem)).toThrow();
expect(tc.timeSystem()).not.toBe(timeSystem);
});
it("Emits an event when time system changes", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("timeSystem", eventListener);
tc.timeSystem(timeSystem, bounds);
expect(eventListener).toHaveBeenCalledWith(timeSystem);
});
it("Emits an event when time of interest changes", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("timeOfInterest", eventListener);
tc.timeOfInterest(toi);
expect(eventListener).toHaveBeenCalledWith(toi);
});
it("Emits an event when bounds change", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("bounds", eventListener);
tc.bounds(bounds);
expect(eventListener).toHaveBeenCalledWith(bounds);
});
it("Emits an event when follow mode changes", function () {
expect(eventListener).not.toHaveBeenCalled();
tc.on("follow", eventListener);
tc.follow(follow);
expect(eventListener).toHaveBeenCalledWith(follow);
});
});
});

54
src/api/Type.js Normal file
View File

@@ -0,0 +1,54 @@
define(function () {
/**
* @typedef TypeDefinition
* @property {Metadata} metadata displayable metadata about this type
* @property {function (object)} [initialize] a function which initializes
* the model for new domain objects of this type
* @property {boolean} [creatable] true if users should be allowed to
* create this type (default: false)
*/
/**
*
* @param {TypeDefinition} definition
* @constructor
*/
function Type(definition) {
this.definition = definition;
}
/**
* Check if a domain object is an instance of this type.
* @param domainObject
* @returns {boolean} true if the domain object is of this type
*/
Type.prototype.check = function (domainObject) {
// Depends on assignment from MCT.
return domainObject.type === this.key;
};
/**
* Get a definition for this type that can be registered using the
* legacy bundle format.
* @private
*/
Type.prototype.toLegacyDefinition = function () {
var def = {};
def.name = this.definition.metadata.label;
def.glyph = this.definition.metadata.glyph;
def.description = this.definition.metadata.description;
def.properties = this.definition.form;
if (this.definition.initialize) {
def.model = {};
this.definition.initialize(def.model);
}
if (this.definition.creatable) {
def.features = ['creation'];
}
return def;
};
return Type;
});

38
src/api/View.js Normal file
View File

@@ -0,0 +1,38 @@
define([], function () {
/**
* A View is used to provide displayable content, and to react to
* associated life cycle events.
*
* @interface
*/
function View() {
}
/**
* Populate the supplied DOM element with the contents of this view.
*
* View implementations should use this method to attach any
* listeners or acquire other resources that are necessary to keep
* the contents of this view up-to-date.
*
* @param {HTMLElement} container the DOM element to populate
*/
View.prototype.show = function (container) {
};
/**
* Release any resources associated with this view.
*
* View implementations should use this method to detach any
* listeners or release other resources that are no longer necessary
* once a view is no longer used.
*/
View.prototype.destroy = function () {
};
return View;
});

44
src/api/ViewDefinition.js Normal file
View File

@@ -0,0 +1,44 @@
define(function () {
/**
* Defines a kind of view.
* @interface
*/
function ViewDefinition() {
}
/**
* Get metadata about this view, as may be used in the user interface
* to present options for this view.
* @param {*} object the object to be shown in this view
* @returns {mct.ViewMetadata} metadata about this view
*/
ViewDefinition.prototype.metadata = function (object) {
};
/**
* Instantiate a new view of this object. Callers of this method are
* responsible for calling `canView` before instantiating views in this
* manner.
*
* @param {*} object the object to be shown in this view
* @returns {mct.View} a view of this object
*/
ViewDefinition.prototype.view = function (object) {
};
/**
* Check if this view is capable of showing this object. Users of
* views should use this method before calling `show`.
*
* Subclasses should override this method to control the applicability
* of this view to other objects.
*
* @param {*} object the object to be shown in this view
* @returns {boolean} true if this view is applicable to this object
*/
ViewDefinition.prototype.canView = function (object) {
};
return ViewDefinition;
});

24
src/api/api.js Normal file
View File

@@ -0,0 +1,24 @@
define([
'./Type',
'./TimeConductor',
'./View',
'./objects/ObjectAPI',
'./composition/CompositionAPI',
'./ui/Dialog'
], function (
Type,
TimeConductor,
View,
ObjectAPI,
CompositionAPI,
Dialog
) {
return {
Type: Type,
TimeConductor: new TimeConductor(),
View: View,
Objects: ObjectAPI,
Composition: CompositionAPI,
Dialog: Dialog
};
});

View File

@@ -0,0 +1,39 @@
define([
'lodash',
'EventEmitter',
'./DefaultCompositionProvider',
'./CompositionCollection'
], function (
_,
EventEmitter,
DefaultCompositionProvider,
CompositionCollection
) {
var PROVIDER_REGISTRY = [];
function getProvider (object) {
return _.find(PROVIDER_REGISTRY, function (p) {
return p.appliesTo(object);
});
};
function composition(object) {
var provider = getProvider(object);
if (!provider) {
return;
}
return new CompositionCollection(object, provider);
};
composition.addProvider = function (provider) {
PROVIDER_REGISTRY.unshift(provider);
};
composition.addProvider(new DefaultCompositionProvider());
return composition;
});

View File

@@ -0,0 +1,122 @@
define([
'EventEmitter',
'lodash',
'../objects/object-utils'
], function (
EventEmitter,
_,
objectUtils
) {
function CompositionCollection(domainObject, provider) {
EventEmitter.call(this);
this.domainObject = domainObject;
this.provider = provider;
if (this.provider.on) {
this.provider.on(
this.domainObject,
'add',
this.onProviderAdd,
this
);
this.provider.on(
this.domainObject,
'remove',
this.onProviderRemove,
this
);
}
};
CompositionCollection.prototype = Object.create(EventEmitter.prototype);
CompositionCollection.prototype.onProviderAdd = function (child) {
this.add(child, true);
};
CompositionCollection.prototype.onProviderRemove = function (child) {
this.remove(child, true);
};
CompositionCollection.prototype.indexOf = function (child) {
return _.findIndex(this._children, function (other) {
return objectUtils.equals(child, other);
});
};
CompositionCollection.prototype.contains = function (child) {
return this.indexOf(child) !== -1;
};
CompositionCollection.prototype.add = function (child, skipMutate) {
if (!this._children) {
throw new Error("Must load composition before you can add!");
}
if (!this.canContain(child)) {
throw new Error("This object cannot contain that object.");
}
if (this.contains(child)) {
if (skipMutate) {
return; // don't add twice, don't error.
}
throw new Error("Unable to add child: already in composition");
}
this._children.push(child);
this.emit('add', child);
if (!skipMutate) {
// add after we have added.
this.provider.add(this.domainObject, child);
}
};
CompositionCollection.prototype.load = function () {
return this.provider.load(this.domainObject)
.then(function (children) {
this._children = [];
children.map(function (c) {
this.add(c, true);
}, this);
this.emit('load');
return this._children.slice();
}.bind(this));
};
CompositionCollection.prototype.remove = function (child, skipMutate) {
if (!this.contains(child)) {
if (skipMutate) {
return;
}
throw new Error("Unable to remove child: not found in composition");
}
var index = this.indexOf(child);
var removed = this._children.splice(index, 1)[0];
this.emit('remove', index, child);
if (!skipMutate) {
// trigger removal after we have internally removed it.
this.provider.remove(this.domainObject, removed);
}
};
CompositionCollection.prototype.canContain = function (domainObject) {
return this.provider.canContain(this.domainObject, domainObject);
};
CompositionCollection.prototype.destroy = function () {
if (this.provider.off) {
this.provider.off(
this.domainObject,
'add',
this.onProviderAdd,
this
);
this.provider.off(
this.domainObject,
'remove',
this.onProviderRemove,
this
);
}
};
return CompositionCollection
});

View File

@@ -0,0 +1,80 @@
define([
'lodash',
'EventEmitter',
'../objects/ObjectAPI',
'../objects/object-utils'
], function (
_,
EventEmitter,
ObjectAPI,
objectUtils
) {
function makeEventName(domainObject, event) {
return event + ':' + objectUtils.makeKeyString(domainObject.key);
}
function DefaultCompositionProvider() {
EventEmitter.call(this);
}
DefaultCompositionProvider.prototype =
Object.create(EventEmitter.prototype);
DefaultCompositionProvider.prototype.appliesTo = function (domainObject) {
return !!domainObject.composition;
};
DefaultCompositionProvider.prototype.load = function (domainObject) {
return Promise.all(domainObject.composition.map(ObjectAPI.get));
};
DefaultCompositionProvider.prototype.on = function (
domainObject,
event,
listener,
context
) {
// these can likely be passed through to the mutation service instead
// of using an eventemitter.
this.addListener(
makeEventName(domainObject, event),
listener,
context
);
};
DefaultCompositionProvider.prototype.off = function (
domainObject,
event,
listener,
context
) {
// these can likely be passed through to the mutation service instead
// of using an eventemitter.
this.removeListener(
makeEventName(domainObject, event),
listener,
context
);
};
DefaultCompositionProvider.prototype.canContain = function (domainObject, child) {
return true;
};
DefaultCompositionProvider.prototype.remove = function (domainObject, child) {
// TODO: this needs to be synchronized via mutation
var index = domainObject.composition.indexOf(child);
domainObject.composition.splice(index, 1);
this.emit(makeEventName(domainObject, 'remove'), child);
};
DefaultCompositionProvider.prototype.add = function (domainObject, child) {
// TODO: this needs to be synchronized via mutation
domainObject.composition.push(child.key);
this.emit(makeEventName(domainObject, 'add'), child);
};
return DefaultCompositionProvider;
});

View File

@@ -0,0 +1,37 @@
# Composition API - Overview
The composition API is straightforward:
MCT.composition(object) -- returns a `CompositionCollection` if the object has
composition, returns undefined if it doesn't.
## CompositionCollection
Has three events:
* `load`: when the collection has completed loading.
* `add`: when a new object has been added to the collection.
* `remove` when an object has been removed from the collection.
Has three methods:
`Collection.load()` -- returns a promise that is fulfilled when the composition
has loaded.
`Collection.add(object)` -- add a domain object to the composition.
`Collection.remove(object)` -- remove the object from the composition.
## Composition providers
composition providers are anything that meets the following interface:
* `provider.appliesTo(domainObject)` -> return true if this provider can provide
composition for a given domain object.
* `provider.add(domainObject, childObject)` -> adds object
* `provider.remove(domainObject, childObject)` -> immediately removes objects
* `provider.load(domainObject)` -> returns promise for array of children
TODO: need to figure out how to make the provider event listeners invisible to the provider.
There is a default composition provider which handles loading composition for
any object with a `composition` property. If you want specialized composition
loading behavior, implement your own composition provider and register it with
`MCT.composition.addProvider(myProvider)`

View File

@@ -0,0 +1,109 @@
define([
'./object-utils',
'./ObjectAPI',
'./objectEventEmitter'
], function (
utils,
ObjectAPI,
objectEventEmitter
) {
function ObjectServiceProvider(objectService, instantiate, topic) {
this.objectService = objectService;
this.instantiate = instantiate;
this.generalTopic = topic('mutation');
this.bridgeEventBuses();
}
/**
* Bridges old and new style mutation events to provide compatibility between the two APIs
* @private
*/
ObjectServiceProvider.prototype.bridgeEventBuses = function () {
var removeGeneralTopicListener;
var handleMutation = function (newStyleObject) {
var keyString = utils.makeKeyString(newStyleObject.key);
var oldStyleObject = this.instantiate(utils.toOldFormat(newStyleObject), keyString);
// Don't trigger self
removeGeneralTopicListener();
oldStyleObject.getCapability('mutation').mutate(function () {
return utils.toOldFormat(newStyleObject);
});
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
}.bind(this);
var handleLegacyMutation = function (legacyObject){
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
//Don't trigger self
objectEventEmitter.off('mutation', handleMutation);
objectEventEmitter.emit(newStyleObject.key.identifier + ":*", newStyleObject);
objectEventEmitter.on('mutation', handleMutation);
}.bind(this);
objectEventEmitter.on('mutation', handleMutation);
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
};
ObjectServiceProvider.prototype.save = function (object) {
var key = object.key,
keyString = utils.makeKeyString(key),
newObject = this.instantiate(utils.toOldFormat(object), keyString);
return object.getCapability('persistence')
.persist()
.then(function () {
return utils.toNewFormat(object, key);
});
};
ObjectServiceProvider.prototype.delete = function (object) {
// TODO!
};
ObjectServiceProvider.prototype.get = function (key) {
var keyString = utils.makeKeyString(key);
return this.objectService.getObjects([keyString])
.then(function (results) {
var model = results[keyString].getModel();
return utils.toNewFormat(model, key);
});
};
// Injects new object API as a decorator so that it hijacks all requests.
// Object providers implemented on new API should just work, old API should just work, many things may break.
function LegacyObjectAPIInterceptor(ROOTS, instantiate, topic, objectService) {
this.getObjects = function (keys) {
var results = {},
promises = keys.map(function (keyString) {
var key = utils.parseKeyString(keyString);
return ObjectAPI.get(key)
.then(function (object) {
object = utils.toOldFormat(object)
results[keyString] = instantiate(object, keyString);
});
});
return Promise.all(promises)
.then(function () {
return results;
});
};
ObjectAPI._supersecretSetFallbackProvider(
new ObjectServiceProvider(objectService, instantiate, topic)
);
ROOTS.forEach(function (r) {
ObjectAPI.addRoot(utils.parseKeyString(r.id));
});
return this;
}
return LegacyObjectAPIInterceptor;
});

View File

@@ -0,0 +1,54 @@
define([
'lodash',
'./objectEventEmitter'
], function (
_,
objectEventEmitter
) {
var ANY_OBJECT_EVENT = "mutation";
/**
* The MutableObject wraps a DomainObject and provides getters and
* setters for
* @param eventEmitter
* @param object
* @constructor
*/
function MutableObject(object) {
this.object = object;
this.unlisteners = [];
}
function qualifiedEventName(object, eventName) {
return [object.key.identifier, eventName].join(':');
}
MutableObject.prototype.stopListening = function () {
this.unlisteners.forEach(function (unlisten) {
unlisten();
})
};
MutableObject.prototype.on = function(path, callback) {
var fullPath = qualifiedEventName(this.object, path);
objectEventEmitter.on(fullPath, callback);
this.unlisteners.push(objectEventEmitter.off.bind(objectEventEmitter, fullPath, callback));
};
MutableObject.prototype.set = function (path, value) {
_.set(this.object, path, value);
_.set(this.object, 'modified', Date.now());
//Emit event specific to property
objectEventEmitter.emit(qualifiedEventName(this.object, path), value);
//Emit wildcare event
objectEventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
//Emit a general "any object" event
objectEventEmitter.emit(ANY_OBJECT_EVENT, this.object);
};
return MutableObject;
});

View File

@@ -0,0 +1,110 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./MutableObject'], function (MutableObject) {
describe('Mutable Domain Objects', function () {
var domainObject,
mutableObject,
eventEmitter,
arrayProperty,
objectProperty,
identifier;
beforeEach(function () {
identifier = 'objectId';
arrayProperty = [
'First array element',
'Second array element',
'Third array element'
];
objectProperty = {
prop1: 'val1',
prop2: 'val2',
prop3: {
propA: 'valA',
propB: 'valB',
propC: []
}
};
domainObject = {
key: {
identifier: identifier
},
stringProperty: 'stringValue',
objectProperty: objectProperty,
arrayProperty: arrayProperty
};
eventEmitter = jasmine.createSpyObj('eventEmitter', [
'emit'
]);
mutableObject = new MutableObject(eventEmitter, domainObject);
});
it('Supports getting and setting of object properties', function () {
expect(domainObject.stringProperty).toEqual('stringValue');
mutableObject.set('stringProperty', 'updated');
expect(domainObject.stringProperty).toEqual('updated');
var newArrayProperty = [];
expect(domainObject.arrayProperty).toEqual(arrayProperty);
mutableObject.set('arrayProperty', newArrayProperty);
expect(domainObject.arrayProperty).toEqual(newArrayProperty);
var newObjectProperty = [];
expect(domainObject.objectProperty).toEqual(objectProperty);
mutableObject.set('objectProperty', newObjectProperty);
expect(domainObject.objectProperty).toEqual(newObjectProperty);
});
it('Supports getting and setting of nested properties', function () {
expect(domainObject.objectProperty).toEqual(objectProperty);
expect(domainObject.objectProperty.prop1).toEqual(objectProperty.prop1);
expect(domainObject.objectProperty.prop3.propA).toEqual(objectProperty.prop3.propA);
mutableObject.set('objectProperty.prop1', 'new-prop-1');
expect(domainObject.objectProperty.prop1).toEqual('new-prop-1');
mutableObject.set('objectProperty.prop3.propA', 'new-prop-A');
expect(domainObject.objectProperty.prop3.propA).toEqual('new-prop-A');
mutableObject.set('arrayProperty.1', 'New Second Array Element');
expect(arrayProperty[1]).toEqual('New Second Array Element');
});
it('Fires events when properties change', function () {
var newString = 'updated'
mutableObject.set('stringProperty', newString);
expect(eventEmitter.emit).toHaveBeenCalledWith([identifier, 'stringProperty'].join(':'), newString);
var newArray = [];
mutableObject.set('arrayProperty', newArray);
expect(eventEmitter.emit).toHaveBeenCalledWith([identifier, 'arrayProperty'].join(':'), newArray);
});
it('Fires wildcard event when any property changes', function () {
var newString = 'updated'
mutableObject.set('objectProperty.prop3.propA', newString);
expect(eventEmitter.emit).toHaveBeenCalledWith([identifier, '*'].join(':'), domainObject);
});
});
});

View File

@@ -0,0 +1,87 @@
define([
'lodash',
'./object-utils',
'./MutableObject'
], function (
_,
utils,
MutableObject
) {
/**
Object API. Intercepts the existing object API while also exposing
A new Object API.
*/
var Objects = {},
ROOT_REGISTRY = [],
PROVIDER_REGISTRY = {},
FALLBACK_PROVIDER;
Objects._supersecretSetFallbackProvider = function (p) {
FALLBACK_PROVIDER = p;
};
// Root provider is hardcoded in; can't be skipped.
var RootProvider = {
'get': function () {
return Promise.resolve({
name: 'The root object',
type: 'root',
composition: ROOT_REGISTRY
});
}
};
// Retrieve the provider for a given key.
function getProvider(key) {
if (key.identifier === 'ROOT') {
return RootProvider;
}
return PROVIDER_REGISTRY[key.namespace] || FALLBACK_PROVIDER;
}
Objects.addProvider = function (namespace, provider) {
PROVIDER_REGISTRY[namespace] = provider;
};
[
'save',
'delete',
'get'
].forEach(function (method) {
Objects[method] = function () {
var key = arguments[0],
provider = getProvider(key);
if (!provider) {
throw new Error('No Provider Matched');
}
if (!provider[method]) {
throw new Error('Provider does not support [' + method + '].');
}
return provider[method].apply(provider, arguments);
};
});
Objects.addRoot = function (key) {
ROOT_REGISTRY.unshift(key);
};
Objects.removeRoot = function (key) {
ROOT_REGISTRY = ROOT_REGISTRY.filter(function (k) {
return (
k.identifier !== key.identifier ||
k.namespace !== key.namespace
);
});
};
Objects.getMutable = function (object) {
return new MutableObject(object);
};
return Objects;
});

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