diff --git a/API.md b/API.md new file mode 100644 index 00000000000..24083e1cf69 --- /dev/null +++ b/API.md @@ -0,0 +1,285 @@ +# 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 loading via a script tag; it's easy to use +in your project. The [`openmct`]{@link module:openmct} module is exported +via AMD and CommonJS, and is also exposed as `openmct` in the global scope +if loaded via a script tag. + +## 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 domain objects. 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! + +## Running Open MCT + +Once the [`openmct`](@link module:openmct) module has been loaded, you can +simply invoke [`start`]{@link module:openmct.MCT#start} to run Open MCT: + + +``` +openmct.start(); +``` + +Generally, however, you will want to configure Open MCT by adding plugins +before starting it. It is important to install plugins and configure Open MCT +_before_ calling [`start`]{@link module:openmct.MCT#start}; Open MCT is not +designed to be reconfigured once started. + +## Configuring Open MCT + +The [`openmct`]{@link module:openmct} module (more specifically, the +[`MCT`]{@link module:openmct.MCT} class, of which `openmct` is an instance) +exposes a variety of methods to allow the application to be configured, +extended, and customized before running. + +Short examples follow; see the linked documentation for further details. + +### Adding Domain Object Types + +Custom types may be registered via +[`openmct.types`]{@link module:openmct.MCT#types}: + +``` +openmct.types.addType('my-type', new openmct.Type({ + label: "My Type", + description: "This is a type that I added!" +}); +``` + +### Adding Views + +Custom views may be registered based on the region in the application +where they should appear: + +* [`openmct.mainViews`]{@link module:openmct.MCT#mainViews} is a registry + of views of domain objects which should appear in the main viewing area. +* [`openmct.inspectors`]{@link module:openmct.MCT#inspectors} is a registry + of views of domain objects and/or active selections, which should appear in + the Inspector. +* [`openmct.toolbars`]{@link module:openmct.MCT#toolbars} is a registry + of views of domain objects and/or active selections, which should appear in + the toolbar area while editing. +* [`openmct.indicators`]{@link module:openmct.MCT#inspectors} is a registry + of views which should appear in the status area of the application. + +Example: + +``` +openmct.mainViews.addProvider({ + canView: function (domainObject) { + return domainObject.type === 'my-type'; + }, + view: function (domainObject) { + return new MyView(domainObject); + } +}); +``` + +### Adding a Root-level Object + +In many cases, you'd like a certain object (or a certain hierarchy of +objects) to be accessible from the top level of the application (the +tree on the left-hand side of Open MCT.) It is typical to expose a telemetry +dictionary as a hierarchy of telemetry-providing domain objects in this +fashion. + +To do so, use the [`addRoot`]{@link module:openmct.ObjectAPI#addRoot} method +of the [object API]{@link module:openmct.ObjectAPI}: + +``` +openmct.objects.addRoot({ + identifier: { key: "my-key", namespace: "my-namespace" } + name: "My Root-level Object", + type: "my-type" +}); +``` + +You can also remove this root-level object via its identifier: + +``` +openmct.objects.removeRoot({ key: "my-key", namespace: "my-namespace" }); +``` + +### Adding Composition Providers + +The "composition" of a domain object is the list of objects it contains, +as shown (for example) in the tree for browsing. Open MCT provides a +default solution for composition, but there may be cases where you want +to provide the composition of a certain object (or type of object) dynamically. +For instance, you may want to populate a hierarchy under a custom root-level +object based on the contents of a telemetry dictionary. +To do this, you can add a new CompositionProvider: + +``` +openmct.composition.addProvider({ + appliesTo: function (domainObject) { + return domainObject.type === 'my-type'; + }, + load: function (domainObject) { + return Promise.resolve(myDomainObjects); + } +}); +``` + +### Adding Telemetry Providers + +When connecting to a new telemetry source, you will want to register a new +[telemetry provider]{@link module:openmct.TelemetryAPI~TelemetryProvider} +with the [telemetry API]{@link module:openmct.TelemetryAPI#addProvider}: + +``` +openmct.telemetry.addProvider({ + canProvideTelemetry: function (domainObject) { + return domainObject.type === 'my-type'; + }, + properties: function (domainObject) { + return [ + { key: 'value', name: "Temperature", units: "degC" }, + { key: 'time', name: "UTC" } + ]; + }, + request: function (domainObject, options) { + var telemetryId = domainObject.myTelemetryId; + return myAdapter.request(telemetryId, options.start, options.end); + }, + subscribe: function (domainObject, callback) { + var telemetryId = domainObject.myTelemetryId; + myAdapter.subscribe(telemetryId, callback); + return myAdapter.unsubscribe.bind(myAdapter, telemetryId, callback); + } +}); +``` + +The implementations for `request` and `subscribe` can vary depending on the +nature of the endpoint which will provide telemetry. In the example above, +it is assumed that `myAdapter` contains the specific implementations +(HTTP requests, WebSocket connections, etc.) associated with some telemetry +source. + +## Using Open MCT + +When implementing new features, it is useful and sometimes necessary to +utilize functionality exposed by Open MCT. + +### Retrieving Composition + +To limit which objects are loaded at any given time, the composition of +a domain object must be requested asynchronously: + +``` +openmct.composition(myObject).load().then(function (childObjects) { + childObjects.forEach(doSomething); +}); +``` + +### Support Common Gestures + +Custom views may also want to support common gestures using the +[gesture API]{@link module:openmct.GestureAPI}. For instance, to make +a view (or part of a view) selectable: + +``` +openmct.gestures.selectable(myHtmlElement, myDomainObject); +``` + +### Working with Domain Objects + +The [object API]{@link module:openmct.ObjectAPI} provides useful methods +for working with domain objects. + +To make changes to a domain object, use the +[`mutate`]{@link module:openmct.ObjectAPI#mutate} method: + +``` +openmct.objects.mutate(myDomainObject, "name", "New name!"); +``` + +Making modifications in this fashion allows other usages of the domain +object to remain up to date using the +[`observe`]{@link module:openmct.ObjectAPI#observe} method: + +``` +openmct.objects.observe(myDomainObject, "name", function (newName) { + myLabel.textContent = newName; +}); +``` + +### Using Telemetry + +Very often in Open MCT, you wish to work with telemetry data (for instance, +to display it in a custom visualization.) + + +### Synchronizing with the Time Conductor + +Views which wish to remain synchronized with the state of Open MCT's +time conductor should utilize +[`openmct.conductor`]{@link module:openmct.TimeConductor}: + +``` +openmct.conductor.on('bounds', function (newBounds) { + requestTelemetry(newBounds.start, newBounds.end).then(displayTelemetry); +}); +``` + +## Plugins + +While you can register new features with Open MCT directly, it is generally +more useful to package these as a plugin. A plugin is a function that takes +[`openmct`]{@link module:openmct} as an argument, and performs configuration +upon `openmct` when invoked. + +### Installing Plugins + +To install plugins, use the [`install`]{@link module:openmct.MCT#install} +method: + +``` +openmct.install(myPlugin); +``` + +The plugin will be invoked to configure Open MCT before it is started. + +### Writing Plugins + +Plugins configure Open MCT, and should utilize the +[`openmct`]{@link module:openmct} module to do so, as summarized above in +"Configuring Open MCT" and "Using Open MCT" above. + +### Distributing Plugins + +Hosting or downloading plugins is outside of the scope of this documentation. +We recommend distributing plugins as UMD modules which export a single +function. + diff --git a/LICENSES.md b/LICENSES.md index 483a6e40729..94a7fecf13a 100644 --- a/LICENSES.md +++ b/LICENSES.md @@ -560,3 +560,132 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--- + +### Almond + +* Link: https://github.com/requirejs/almond + +* Version: 0.3.3 + +* Author: jQuery Foundation + +* Description: Lightweight RequireJS replacement for builds + +#### License + +Copyright jQuery Foundation and other contributors, https://jquery.org/ + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/requirejs/almond + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules directory, and certain utilities used +to build or test the software in the test and dist directories, are +externally maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + + +### Lodash + +* Link: https://lodash.com + +* Version: 3.10.1 + +* Author: Dojo Foundation + +* Description: Utility functions + +#### License + +Copyright 2012-2015 The Dojo Foundation +Based on Underscore.js, copyright 2009-2015 Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +### EventEmitter3 + +* Link: https://github.com/primus/eventemitter3 + +* Version: 1.2.0 + +* Author: Arnout Kazemier + +* Description: Event-driven programming support + +#### License + +The MIT License (MIT) + +Copyright (c) 2014 Arnout Kazemier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index b9486dad3c8..8761314a0b4 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,24 @@ Try Open MCT now with our [live demo](https://openmct-demo.herokuapp.com/). ![Demo](https://nasa.github.io/openmct/static/res/images/Open-MCT.Browse.Layout.Mars-Weather-1.jpg) ## New API -A new API is currently under development that will deprecate a lot of the documentation currently in the docs directory, however Open MCT will remain compatible with the currently documented API. An updated set of tutorials is being developed with the new API, and progress on this task can be followed in the [associated pull request](https://github.com/nasa/openmct/pull/999). Any code in this branch should be considered experimental, and we welcome any feedback. -Differences between the two APIs include a move away from a declarative system of JSON configuration files towards an imperative system based on function calls. Developers will be able to extend and build on Open MCT by making direct function calls to a public API. Open MCT is also being refactored to minimize the dependencies that using Open MCT imposes on developers, such as the current requirement to use Angular JS. +A simpler, [easier-to-use API](https://nasa.github.io/openmct/docs/api/) +has been added to Open MCT. Changes in this +API include a move away from a declarative system of JSON configuration files +towards an imperative system based on function calls. Developers will be able +to extend and build on Open MCT by making direct function calls to a public +API. Open MCT is also being refactored to minimize the dependencies that using +Open MCT imposes on developers, such as the current requirement to use +AngularJS. + +This new API has not yet been heavily used and is likely to contain defects. +You can help by trying it out, and reporting any issues you encounter +using our GitHub issue tracker. Such issues may include bugs, suggestions, +missing documentation, or even just requests for help if you're having +trouble. + +We want Open MCT to be as easy to use, install, run, and develop for as +possible, and your feedback will help us get there! ## Building and Running Open MCT Locally diff --git a/bower.json b/bower.json index 641d5486dac..161ee041869 100644 --- a/bower.json +++ b/bower.json @@ -20,6 +20,8 @@ "FileSaver.js": "^0.0.2", "zepto": "^1.1.6", "eventemitter3": "^1.2.0", + "lodash": "3.10.1", + "almond": "~0.3.2", "d3": "~4.1.0", "html2canvas": "^0.4.1" } diff --git a/build-docs.sh b/build-docs.sh index 29841f921e4..22e87949bff 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -24,7 +24,7 @@ # Script to build and deploy docs. -OUTPUT_DIRECTORY="target/docs" +OUTPUT_DIRECTORY="dist/docs" # Docs, once built, are pushed to the private website repo REPOSITORY_URL="git@github.com:nasa/openmct-website.git" WEBSITE_DIRECTORY="website" diff --git a/docs/src/index.md b/docs/src/index.md index 3b4d7671060..1d523657f40 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -9,26 +9,29 @@ Open MCT provides functionality out of the box, but it's also a platform for building rich mission operations applications based on modern web technology. - The platform is configured declaratively, and defines conventions for - building on the provided capabilities by creating modular 'bundles' that - extend the platform at a variety of extension points. The details of how to + The platform is configured by plugins which extend the platform at a variety + of extension points. The details of how to extend the platform are provided in the following documentation. ## Sections - - * The [Architecture Overview](architecture/) describes the concepts used - throughout Open MCT, and gives a high level overview of the platform's design. - - * The [Developer's Guide](guide/) goes into more detail about how to use the - platform and the functionality that it provides. - - * The [Tutorials](tutorials/) give examples of extending the platform to add - functionality, - and integrate with data sources. * The [API](api/) document is generated from inline documentation using [JSDoc](http://usejsdoc.org/), and describes the JavaScript objects and functions that make up the software platform. - * Finally, the [Development Process](process/) document describes the + * The [Development Process](process/) document describes the Open MCT software development cycle. + +## Legacy Documentation + +As we transition to a new API, the following documentation for the old API +(which is supported during the transtion) may be useful as well: + + * The [Architecture Overview](architecture/) describes the concepts used + throughout Open MCT, and gives a high level overview of the platform's design. + + * The [Developer's Guide](guide/) goes into more detail about how to use the + platform and the functionality that it provides. + + * The [Tutorials](tutorials/) give examples of extending the platform to add + functionality, and integrate with data sources. diff --git a/example/msl/bundle.js b/example/msl/bundle.js index 76688680315..c40c237af03 100644 --- a/example/msl/bundle.js +++ b/example/msl/bundle.js @@ -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": [ diff --git a/gulpfile.js b/gulpfile.js index ff994c6a2c0..e9a5d0fcf00 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,41 +21,34 @@ *****************************************************************************/ /*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', + main: 'openmct.js', dist: 'dist', - assets: 'dist/assets', reports: 'dist/reports', scss: ['./platform/**/*.scss', './example/**/*.scss'], - scripts: [ 'main.js', 'platform/**/*.js', 'src/**/*.js' ], + assets: [ + './{example,platform}/**/*.{css,css.map,png,svg,ico,woff,eot,ttf}' + ], + scripts: [ 'openmct.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 }, @@ -64,7 +57,6 @@ var gulp = require('gulp'), singleRun: true }, sass: { - includePaths: bourbon.includePaths, sourceComments: true }, replace: { @@ -78,6 +70,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)) @@ -87,10 +81,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)) @@ -104,6 +104,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; }), @@ -122,6 +125,8 @@ gulp.task('lint', function () { }); gulp.task('checkstyle', function () { + var jscs = require('gulp-jscs'); + return gulp.src(paths.scripts) .pipe(jscs()) .pipe(jscs.reporter()) @@ -129,18 +134,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 () { @@ -148,9 +155,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' ]); diff --git a/index.html b/index.html index fcd37cfb0b3..aa5d79cfdc7 100644 --- a/index.html +++ b/index.html @@ -28,12 +28,15 @@ @@ -47,7 +50,5 @@
- -
diff --git a/jsdoc.json b/jsdoc.json index f913b650d14..ac485a5efaf 100644 --- a/jsdoc.json +++ b/jsdoc.json @@ -1,9 +1,9 @@ { "source": { "include": [ - "platform/" + "src/" ], - "includePattern": "platform/.+\\.js$", + "includePattern": "src/.+\\.js$", "excludePattern": ".+\\Spec\\.js$|lib/.+" }, "plugins": [ diff --git a/karma.conf.js b/karma.conf.js index e682ec6d86b..535c27b2a54 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -37,9 +37,11 @@ module.exports = function(config) { {pattern: 'bower_components/**/*.js', included: false}, {pattern: 'src/**/*.js', included: false}, {pattern: 'example/**/*.js', included: false}, + {pattern: 'example/**/*.json', included: false}, {pattern: 'platform/**/*.js', included: false}, {pattern: 'warp/**/*.js', included: false}, {pattern: 'platform/**/*.html', included: false}, + {pattern: 'src/**/*.html', included: false}, 'test-main.js' ], diff --git a/main.js b/openmct.js similarity index 64% rename from main.js rename to openmct.js index 76512d36f42..68151008cda 100644 --- a/main.js +++ b/openmct.js @@ -37,6 +37,7 @@ requirejs.config({ "text": "bower_components/text/text", "uuid": "bower_components/node-uuid/uuid", "zepto": "bower_components/zepto/zepto.min", + "lodash": "bower_components/lodash/lodash", "d3": "bower_components/d3/d3.min" }, "shim": { @@ -46,12 +47,12 @@ requirejs.config({ "angular-route": { "deps": ["angular"] }, - "html2canvas": { - "exports": "html2canvas" - }, "EventEmitter": { "exports": "EventEmitter" }, + "html2canvas": { + "exports": "html2canvas" + }, "moment-duration-format": { "deps": ["moment"] }, @@ -61,6 +62,9 @@ requirejs.config({ "zepto": { "exports": "Zepto" }, + "lodash": { + "exports": "lodash" + }, "d3": { "exports": "d3" } @@ -69,48 +73,16 @@ requirejs.config({ define([ './platform/framework/src/Main', - 'legacyRegistry', + './src/defaultRegistry', + './src/MCT' +], function (Main, defaultRegistry, MCT) { + var openmct = 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/fixed/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); - } - }; + openmct.legacyRegistry = defaultRegistry; + + openmct.on('start', function () { + return new Main().run(defaultRegistry); + }); + + return openmct; }); diff --git a/package.json b/package.json index e47a64e50e6..9ac04c51894 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,8 @@ "test": "karma start --single-run", "jshint": "jshint platform example", "watch": "karma start", - "jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api", - "otherdoc": "node docs/gendocs.js --in docs/src --out target/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'", + "jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api", + "otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'", "docs": "npm run jsdoc ; npm run otherdoc", "prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install" }, diff --git a/platform/commonUI/browse/bundle.js b/platform/commonUI/browse/bundle.js index 5ff13d789e4..b0aa7e42776 100644 --- a/platform/commonUI/browse/bundle.js +++ b/platform/commonUI/browse/bundle.js @@ -41,6 +41,7 @@ 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, @@ -63,6 +64,7 @@ define([ itemsTemplate, objectPropertiesTemplate, inspectorRegionTemplate, + viewObjectTemplate, legacyRegistry ) { @@ -142,7 +144,7 @@ define([ "representations": [ { "key": "view-object", - "templateUrl": "templates/view-object.html" + "template": viewObjectTemplate }, { "key": "browse-object", diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index 1094abb864e..d2e9a684ae6 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -200,7 +200,6 @@ define([ "name": "Remove", "description": "Remove this object from its containing object.", "depends": [ - "$q", "navigationService" ] }, diff --git a/platform/commonUI/edit/src/actions/EditAndComposeAction.js b/platform/commonUI/edit/src/actions/EditAndComposeAction.js index a6dc32b0d97..f1dc5e47d34 100644 --- a/platform/commonUI/edit/src/actions/EditAndComposeAction.js +++ b/platform/commonUI/edit/src/actions/EditAndComposeAction.js @@ -40,19 +40,11 @@ define( var self = this, editAction = this.domainObject.getCapability('action').getActions("edit")[0]; - // Persist changes to the domain object - function doPersist() { - var persistence = - self.domainObject.getCapability('persistence'); - return persistence.persist(); - } - // Link these objects function doLink() { var composition = self.domainObject && self.domainObject.getCapability('composition'); - return composition && composition.add(self.selectedObject) - .then(doPersist); + return composition && composition.add(self.selectedObject); } if (editAction) { diff --git a/platform/commonUI/edit/src/actions/PropertiesAction.js b/platform/commonUI/edit/src/actions/PropertiesAction.js index 43ebe215ada..e17e0053f18 100644 --- a/platform/commonUI/edit/src/actions/PropertiesAction.js +++ b/platform/commonUI/edit/src/actions/PropertiesAction.js @@ -50,12 +50,6 @@ define( domainObject = this.domainObject, dialogService = this.dialogService; - // Persist modifications to this domain object - function doPersist() { - var persistence = domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - // Update the domain object model based on user input function updateModel(userInput, dialog) { return domainObject.useCapability('mutation', function (model) { @@ -73,11 +67,9 @@ define( dialog.getFormStructure(), dialog.getInitialFormValue() ).then(function (userInput) { - // Update the model, if user input was provided - return userInput && updateModel(userInput, dialog); - }).then(function (result) { - return result && doPersist(); - }); + // Update the model, if user input was provided + return userInput && updateModel(userInput, dialog); + }); } return type && showDialog(type); @@ -94,9 +86,7 @@ define( creatable = type && type.hasFeature('creation'); // Only allow creatable types to be edited - return domainObject && - domainObject.hasCapability("persistence") && - creatable; + return domainObject && creatable; }; return PropertiesAction; diff --git a/platform/commonUI/edit/src/actions/RemoveAction.js b/platform/commonUI/edit/src/actions/RemoveAction.js index ab460f522db..604d4f0f0be 100644 --- a/platform/commonUI/edit/src/actions/RemoveAction.js +++ b/platform/commonUI/edit/src/actions/RemoveAction.js @@ -39,9 +39,8 @@ define( * @constructor * @implements {Action} */ - function RemoveAction($q, navigationService, context) { + function RemoveAction(navigationService, context) { this.domainObject = (context || {}).domainObject; - this.$q = $q; this.navigationService = navigationService; } @@ -51,8 +50,7 @@ define( * fulfilled when the action has completed. */ RemoveAction.prototype.perform = function () { - var $q = this.$q, - navigationService = this.navigationService, + var navigationService = this.navigationService, domainObject = this.domainObject; /* * Check whether an object ID matches the ID of the object being @@ -71,15 +69,6 @@ define( model.composition = model.composition.filter(isNotObject); } - /* - * Invoke persistence on a domain object. This will be called upon - * the removed object's parent (as its composition will have changed.) - */ - function doPersist(domainObj) { - var persistence = domainObj.getCapability('persistence'); - return persistence && persistence.persist(); - } - /* * Checks current object and ascendants of current * object with object being removed, if the current @@ -119,15 +108,10 @@ define( // navigates to existing object up tree checkObjectNavigation(object, parent); - return $q.when( - parent.useCapability('mutation', doMutate) - ).then(function () { - return doPersist(parent); - }); + return parent.useCapability('mutation', doMutate); } - return $q.when(domainObject) - .then(removeFromContext); + return removeFromContext(domainObject); }; // Object needs to have a parent for Remove to be applicable diff --git a/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js b/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js index bcda009c560..f9467326c0f 100644 --- a/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js +++ b/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js @@ -81,6 +81,10 @@ define( return this.persistenceCapability.getSpace(); }; + TransactionalPersistenceCapability.prototype.persisted = function () { + return this.persistenceCapability.persisted(); + }; + return TransactionalPersistenceCapability; } ); diff --git a/platform/commonUI/edit/src/representers/EditRepresenter.js b/platform/commonUI/edit/src/representers/EditRepresenter.js index 33613f8a173..81252977b11 100644 --- a/platform/commonUI/edit/src/representers/EditRepresenter.js +++ b/platform/commonUI/edit/src/representers/EditRepresenter.js @@ -50,17 +50,13 @@ define( this.listenHandle = undefined; // Mutate and persist a new version of a domain object's model. - function doPersist(model) { + function doMutate(model) { var domainObject = self.domainObject; // First, mutate; then, persist. return $q.when(domainObject.useCapability("mutation", function () { return model; - })).then(function (result) { - // Only persist when mutation was successful - return result && - domainObject.getCapability("persistence").persist(); - }); + })); } // Handle changes to model and/or view configuration @@ -80,14 +76,14 @@ define( ].join(" ")); // Update the configuration stored in the model, and persist. - if (domainObject && domainObject.hasCapability("persistence")) { + if (domainObject) { // Configurations for specific views are stored by // key in the "configuration" field of the model. if (self.key && configuration) { model.configuration = model.configuration || {}; model.configuration[self.key] = configuration; } - doPersist(model); + doMutate(model); } } diff --git a/platform/commonUI/edit/src/services/NestedTransaction.js b/platform/commonUI/edit/src/services/NestedTransaction.js new file mode 100644 index 00000000000..c7fcee4d5eb --- /dev/null +++ b/platform/commonUI/edit/src/services/NestedTransaction.js @@ -0,0 +1,48 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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(['./Transaction'], function (Transaction) { + /** + * A nested transaction is a transaction which takes place in the context + * of a larger parent transaction. It becomes part of the parent + * transaction when (and only when) committed. + * @param parent + * @constructor + * @extends {platform/commonUI/edit/services.Transaction} + * @memberof platform/commonUI/edit/services + */ + function NestedTransaction(parent) { + this.parent = parent; + Transaction.call(this, parent.$log); + } + + NestedTransaction.prototype = Object.create(Transaction.prototype); + + NestedTransaction.prototype.commit = function () { + this.parent.add( + Transaction.prototype.commit.bind(this), + Transaction.prototype.cancel.bind(this) + ); + return Promise.resolve(true); + }; + + return NestedTransaction; +}); diff --git a/platform/commonUI/edit/src/services/Transaction.js b/platform/commonUI/edit/src/services/Transaction.js new file mode 100644 index 00000000000..803536be4d7 --- /dev/null +++ b/platform/commonUI/edit/src/services/Transaction.js @@ -0,0 +1,96 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +define([], function () { + /** + * A Transaction represents a set of changes that are intended to + * be kept or discarded as a unit. + * @param $log Angular's `$log` service, for logging messages + * @constructor + * @memberof platform/commonUI/edit/services + */ + function Transaction($log) { + this.$log = $log; + this.callbacks = []; + } + + /** + * Add a change to the current transaction, as expressed by functions + * to either keep or discard the change. + * @param {Function} commit called when the transaction is committed + * @param {Function} cancel called when the transaction is cancelled + * @returns {Function) a function which may be called to remove this + * pair of callbacks from the transaction + */ + Transaction.prototype.add = function (commit, cancel) { + var callback = { commit: commit, cancel: cancel }; + this.callbacks.push(callback); + return function () { + this.callbacks = this.callbacks.filter(function (c) { + return c !== callback; + }); + }.bind(this); + }; + + /** + * Get the number of changes in the current transaction. + * @returns {number} the size of the current transaction + */ + Transaction.prototype.size = function () { + return this.callbacks.length; + }; + + /** + * Keep all changes associated with this transaction. + * @method {platform/commonUI/edit/services.Transaction#commit} + * @returns {Promise} a promise which will resolve when all callbacks + * have been handled. + */ + + /** + * Discard all changes associated with this transaction. + * @method {platform/commonUI/edit/services.Transaction#cancel} + * @returns {Promise} a promise which will resolve when all callbacks + * have been handled. + */ + + ['commit', 'cancel'].forEach(function (method) { + Transaction.prototype[method] = function () { + var promises = []; + var callback; + + while (this.callbacks.length > 0) { + callback = this.callbacks.shift(); + try { + promises.push(callback[method]()); + } catch (e) { + this.$log + .error("Error trying to " + method + " transaction."); + } + } + + return Promise.all(promises); + }; + }); + + + return Transaction; +}); diff --git a/platform/commonUI/edit/src/services/TransactionService.js b/platform/commonUI/edit/src/services/TransactionService.js index df0a051f0d2..3c234ca8829 100644 --- a/platform/commonUI/edit/src/services/TransactionService.js +++ b/platform/commonUI/edit/src/services/TransactionService.js @@ -21,8 +21,8 @@ *****************************************************************************/ /*global define*/ define( - [], - function () { + ['./Transaction', './NestedTransaction'], + function (Transaction, NestedTransaction) { /** * Implements an application-wide transaction state. Once a * transaction is started, calls to @@ -37,10 +37,7 @@ define( function TransactionService($q, $log) { this.$q = $q; this.$log = $log; - this.transaction = false; - - this.onCommits = []; - this.onCancels = []; + this.transactions = []; } /** @@ -50,18 +47,18 @@ define( * #cancel} are called */ TransactionService.prototype.startTransaction = function () { - if (this.transaction) { - //Log error because this is a programming error if it occurs. - this.$log.error("Transaction already in progress"); - } - this.transaction = true; + var transaction = this.isActive() ? + new NestedTransaction(this.transactions[0]) : + new Transaction(this.$log); + + this.transactions.push(transaction); }; /** * @returns {boolean} If true, indicates that a transaction is in progress */ TransactionService.prototype.isActive = function () { - return this.transaction; + return this.transactions.length > 0; }; /** @@ -72,24 +69,20 @@ define( * @param onCancel A function to call on cancel */ TransactionService.prototype.addToTransaction = function (onCommit, onCancel) { - if (this.transaction) { - this.onCommits.push(onCommit); - if (onCancel) { - this.onCancels.push(onCancel); - } + if (this.isActive()) { + return this.activeTransaction().add(onCommit, onCancel); } else { //Log error because this is a programming error if it occurs. this.$log.error("No transaction in progress"); } + }; - return function () { - this.onCommits = this.onCommits.filter(function (callback) { - return callback !== onCommit; - }); - this.onCancels = this.onCancels.filter(function (callback) { - return callback !== onCancel; - }); - }.bind(this); + /** + * Get the transaction at the top of the stack. + * @private + */ + TransactionService.prototype.activeTransaction = function () { + return this.transactions[this.transactions.length - 1]; }; /** @@ -100,24 +93,8 @@ define( * completed. Will reject if any commit operations fail */ TransactionService.prototype.commit = function () { - var self = this, - promises = [], - onCommit; - - while (this.onCommits.length > 0) { // ...using a while in case some onCommit adds to transaction - onCommit = this.onCommits.pop(); - try { // ...also don't want to fail mid-loop... - promises.push(onCommit()); - } catch (e) { - this.$log.error("Error committing transaction."); - } - } - return this.$q.all(promises).then(function () { - self.transaction = false; - - self.onCommits = []; - self.onCancels = []; - }); + var transaction = this.transactions.pop(); + return transaction ? transaction.commit() : Promise.reject(); }; /** @@ -129,28 +106,17 @@ define( * @returns {*} */ TransactionService.prototype.cancel = function () { - var self = this, - results = [], - onCancel; - - while (this.onCancels.length > 0) { - onCancel = this.onCancels.pop(); - try { - results.push(onCancel()); - } catch (error) { - this.$log.error("Error cancelling transaction."); - } - } - return this.$q.all(results).then(function () { - self.transaction = false; - - self.onCommits = []; - self.onCancels = []; - }); + var transaction = this.transactions.pop(); + return transaction ? transaction.cancel() : Promise.reject(); }; + /** + * Get the size (the number of commit/cancel callbacks) of + * the active transaction. + * @returns {number} size of the active transaction + */ TransactionService.prototype.size = function () { - return this.onCommits.length; + return this.isActive() ? this.activeTransaction().size() : 0; }; return TransactionService; diff --git a/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js b/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js index 25ec4567a64..0010a927354 100644 --- a/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js +++ b/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js @@ -30,7 +30,6 @@ define( mockParent, mockContext, mockComposition, - mockPersistence, mockActionCapability, mockEditAction, mockType, @@ -68,7 +67,6 @@ define( }; mockContext = jasmine.createSpyObj("context", ["getParent"]); mockComposition = jasmine.createSpyObj("composition", ["invoke", "add"]); - mockPersistence = jasmine.createSpyObj("persistence", ["persist"]); mockType = jasmine.createSpyObj("type", ["hasFeature", "getKey"]); mockActionCapability = jasmine.createSpyObj("actionCapability", ["getActions"]); mockEditAction = jasmine.createSpyObj("editAction", ["perform"]); @@ -84,7 +82,6 @@ define( capabilities = { composition: mockComposition, - persistence: mockPersistence, action: mockActionCapability, type: mockType }; @@ -107,11 +104,6 @@ define( .toHaveBeenCalledWith(mockDomainObject); }); - it("persists changes afterward", function () { - action.perform(); - expect(mockPersistence.persist).toHaveBeenCalled(); - }); - it("enables edit mode for objects that have an edit action", function () { mockActionCapability.getActions.andReturn([mockEditAction]); action.perform(); diff --git a/platform/commonUI/edit/test/actions/PropertiesActionSpec.js b/platform/commonUI/edit/test/actions/PropertiesActionSpec.js index 8123a506f4e..20e32ab0150 100644 --- a/platform/commonUI/edit/test/actions/PropertiesActionSpec.js +++ b/platform/commonUI/edit/test/actions/PropertiesActionSpec.js @@ -43,7 +43,6 @@ define( }, hasFeature: jasmine.createSpy('hasFeature') }, - persistence: jasmine.createSpyObj("persistence", ["persist"]), mutation: jasmine.createSpy("mutation") }; model = {}; @@ -78,23 +77,16 @@ define( action = new PropertiesAction(dialogService, context); }); - it("persists when an action is performed", function () { + it("mutates an object when performed", function () { action.perform(); - expect(capabilities.persistence.persist) - .toHaveBeenCalled(); + expect(capabilities.mutation).toHaveBeenCalled(); + capabilities.mutation.mostRecentCall.args[0]({}); }); - it("does not persist any changes upon cancel", function () { + it("does not muate object upon cancel", function () { input = undefined; action.perform(); - expect(capabilities.persistence.persist) - .not.toHaveBeenCalled(); - }); - - it("mutates an object when performed", function () { - action.perform(); - expect(capabilities.mutation).toHaveBeenCalled(); - capabilities.mutation.mostRecentCall.args[0]({}); + expect(capabilities.mutation).not.toHaveBeenCalled(); }); it("is only applicable when a domain object is in context", function () { diff --git a/platform/commonUI/edit/test/actions/RemoveActionSpec.js b/platform/commonUI/edit/test/actions/RemoveActionSpec.js index 7830ca02931..c5755b36366 100644 --- a/platform/commonUI/edit/test/actions/RemoveActionSpec.js +++ b/platform/commonUI/edit/test/actions/RemoveActionSpec.js @@ -37,7 +37,6 @@ define( mockGrandchildContext, mockRootContext, mockMutation, - mockPersistence, mockType, actionContext, model, @@ -53,8 +52,6 @@ define( } beforeEach(function () { - - mockDomainObject = jasmine.createSpyObj( "domainObject", ["getId", "getCapability"] @@ -88,7 +85,6 @@ define( mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]); mockRootContext = jasmine.createSpyObj("context", ["getParent"]); mockMutation = jasmine.createSpyObj("mutation", ["invoke"]); - mockPersistence = jasmine.createSpyObj("persistence", ["persist"]); mockType = jasmine.createSpyObj("type", ["hasFeature"]); mockNavigationService = jasmine.createSpyObj( "navigationService", @@ -109,7 +105,6 @@ define( capabilities = { mutation: mockMutation, - persistence: mockPersistence, type: mockType }; model = { @@ -118,7 +113,7 @@ define( actionContext = { domainObject: mockDomainObject }; - action = new RemoveAction(mockQ, mockNavigationService, actionContext); + action = new RemoveAction(mockNavigationService, actionContext); }); it("only applies to objects with parents", function () { @@ -154,9 +149,6 @@ define( // Should have removed "test" - that was our // mock domain object's id. expect(result.composition).toEqual(["a", "b"]); - - // Finally, should have persisted - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("removes parent of object currently navigated to", function () { diff --git a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js index 7af3740fd65..cb766f95a7e 100644 --- a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js +++ b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js @@ -30,7 +30,6 @@ define( mockScope, testRepresentation, mockDomainObject, - mockPersistence, mockStatusCapability, mockEditorCapability, mockCapabilities, @@ -56,15 +55,12 @@ define( "useCapability", "hasCapability" ]); - mockPersistence = - jasmine.createSpyObj("persistence", ["persist"]); mockStatusCapability = jasmine.createSpyObj("statusCapability", ["listen"]); mockEditorCapability = jasmine.createSpyObj("editorCapability", ["isEditContextRoot"]); mockCapabilities = { - 'persistence': mockPersistence, 'status': mockStatusCapability, 'editor': mockEditorCapability }; @@ -96,7 +92,7 @@ define( expect(representer.listenHandle).toHaveBeenCalled(); }); - it("mutates and persists upon observed changes", function () { + it("mutates upon observed changes", function () { mockScope.model = { someKey: "some value" }; mockScope.configuration = { someConfiguration: "something" }; @@ -108,9 +104,6 @@ define( jasmine.any(Function) ); - // ... and should have persisted the mutation - expect(mockPersistence.persist).toHaveBeenCalled(); - // Finally, check that the provided mutation function // includes both model and configuration expect( diff --git a/platform/commonUI/edit/test/services/NestedTransactionSpec.js b/platform/commonUI/edit/test/services/NestedTransactionSpec.js new file mode 100644 index 00000000000..df82c12a78e --- /dev/null +++ b/platform/commonUI/edit/test/services/NestedTransactionSpec.js @@ -0,0 +1,78 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine*/ + +define(["../../src/services/NestedTransaction"], function (NestedTransaction) { + var TRANSACTION_METHODS = ['add', 'commit', 'cancel', 'size']; + + describe("A NestedTransaction", function () { + var mockTransaction, + nestedTransaction; + + beforeEach(function () { + mockTransaction = + jasmine.createSpyObj('transaction', TRANSACTION_METHODS); + nestedTransaction = new NestedTransaction(mockTransaction); + }); + + it("exposes a Transaction's interface", function () { + TRANSACTION_METHODS.forEach(function (method) { + expect(nestedTransaction[method]) + .toEqual(jasmine.any(Function)); + }); + }); + + describe("when callbacks are added", function () { + var mockCommit, + mockCancel, + remove; + + beforeEach(function () { + mockCommit = jasmine.createSpy('commit'); + mockCancel = jasmine.createSpy('cancel'); + remove = nestedTransaction.add(mockCommit, mockCancel); + }); + + it("does not interact with its parent transaction", function () { + TRANSACTION_METHODS.forEach(function (method) { + expect(mockTransaction[method]) + .not.toHaveBeenCalled(); + }); + }); + + describe("and the transaction is committed", function () { + beforeEach(function () { + nestedTransaction.commit(); + }); + + it("adds to its parent transaction", function () { + expect(mockTransaction.add).toHaveBeenCalledWith( + jasmine.any(Function), + jasmine.any(Function) + ); + }); + }); + }); + }); +}); + + diff --git a/platform/commonUI/edit/test/services/TransactionServiceSpec.js b/platform/commonUI/edit/test/services/TransactionServiceSpec.js index 8c4d635a6fc..f05fb9df3d4 100644 --- a/platform/commonUI/edit/test/services/TransactionServiceSpec.js +++ b/platform/commonUI/edit/test/services/TransactionServiceSpec.js @@ -57,8 +57,7 @@ define( transactionService.startTransaction(); transactionService.addToTransaction(onCommit, onCancel); - expect(transactionService.onCommits.length).toBe(1); - expect(transactionService.onCancels.length).toBe(1); + expect(transactionService.size()).toBe(1); }); it("size function returns size of commit and cancel queues", function () { @@ -85,7 +84,7 @@ define( }); it("commit calls all queued commit functions", function () { - expect(transactionService.onCommits.length).toBe(3); + expect(transactionService.size()).toBe(3); transactionService.commit(); onCommits.forEach(function (spy) { expect(spy).toHaveBeenCalled(); @@ -95,8 +94,8 @@ define( it("commit resets active state and clears queues", function () { transactionService.commit(); expect(transactionService.isActive()).toBe(false); - expect(transactionService.onCommits.length).toBe(0); - expect(transactionService.onCancels.length).toBe(0); + expect(transactionService.size()).toBe(0); + expect(transactionService.size()).toBe(0); }); }); @@ -116,7 +115,7 @@ define( }); it("cancel calls all queued cancel functions", function () { - expect(transactionService.onCancels.length).toBe(3); + expect(transactionService.size()).toBe(3); transactionService.cancel(); onCancels.forEach(function (spy) { expect(spy).toHaveBeenCalled(); @@ -126,8 +125,7 @@ define( it("cancel resets active state and clears queues", function () { transactionService.cancel(); expect(transactionService.isActive()).toBe(false); - expect(transactionService.onCommits.length).toBe(0); - expect(transactionService.onCancels.length).toBe(0); + expect(transactionService.size()).toBe(0); }); }); diff --git a/platform/commonUI/edit/test/services/TransactionSpec.js b/platform/commonUI/edit/test/services/TransactionSpec.js new file mode 100644 index 00000000000..3b555f3d059 --- /dev/null +++ b/platform/commonUI/edit/test/services/TransactionSpec.js @@ -0,0 +1,110 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,jasmine*/ + +define( + ["../../src/services/Transaction"], + function (Transaction) { + + describe("A Transaction", function () { + var mockLog, + transaction; + + beforeEach(function () { + mockLog = jasmine.createSpyObj( + '$log', + ['warn', 'info', 'error', 'debug'] + ); + transaction = new Transaction(mockLog); + }); + + it("initially has a size of zero", function () { + expect(transaction.size()).toEqual(0); + }); + + describe("when callbacks are added", function () { + var mockCommit, + mockCancel, + remove; + + beforeEach(function () { + mockCommit = jasmine.createSpy('commit'); + mockCancel = jasmine.createSpy('cancel'); + remove = transaction.add(mockCommit, mockCancel); + }); + + it("reports a new size", function () { + expect(transaction.size()).toEqual(1); + }); + + it("returns a function to remove those callbacks", function () { + expect(remove).toEqual(jasmine.any(Function)); + remove(); + expect(transaction.size()).toEqual(0); + }); + + describe("and the transaction is committed", function () { + beforeEach(function () { + transaction.commit(); + }); + + it("triggers the commit callback", function () { + expect(mockCommit).toHaveBeenCalled(); + }); + + it("does not trigger the cancel callback", function () { + expect(mockCancel).not.toHaveBeenCalled(); + }); + }); + + describe("and the transaction is cancelled", function () { + beforeEach(function () { + transaction.cancel(); + }); + + it("triggers the cancel callback", function () { + expect(mockCancel).toHaveBeenCalled(); + }); + + it("does not trigger the commit callback", function () { + expect(mockCommit).not.toHaveBeenCalled(); + }); + }); + + describe("and an exception is encountered during commit", function () { + beforeEach(function () { + mockCommit.andCallFake(function () { + throw new Error("test error"); + }); + transaction.commit(); + }); + + it("logs an error", function () { + expect(mockLog.error).toHaveBeenCalled(); + }); + }); + }); + + }); + } +); + diff --git a/platform/commonUI/general/bundle.js b/platform/commonUI/general/bundle.js index 15ea3076a62..66d9b3de951 100644 --- a/platform/commonUI/general/bundle.js +++ b/platform/commonUI/general/bundle.js @@ -48,6 +48,7 @@ define([ "./src/directives/MCTSplitPane", "./src/directives/MCTSplitter", "./src/directives/MCTTree", + "./src/filters/ReverseFilter", "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" } ], @@ -405,6 +408,11 @@ define([ "key": "THEME", "value": "unspecified", "priority": "fallback" + }, + { + "key": "ASSETS_PATH", + "value": ".", + "priority": "fallback" } ], "containers": [ diff --git a/platform/commonUI/general/res/sass/controls/_buttons.scss b/platform/commonUI/general/res/sass/controls/_buttons.scss index 17e03941dfc..fe2c6d660c0 100644 --- a/platform/commonUI/general/res/sass/controls/_buttons.scss +++ b/platform/commonUI/general/res/sass/controls/_buttons.scss @@ -104,9 +104,17 @@ body.desktop .mini-tab-icon { } } +@mixin btnSetButtonFirst() { + @include border-left-radius($controlCr); + margin-left: 0; +} + +@mixin btnSetButtonLast() { + @include border-right-radius($controlCr); +} + .l-btn-set { // Buttons that have a very tight conceptual grouping - no internal space between them. - // Structure: .btn-set > mct-representation class=first|last > .s-button font-size: 0; // Remove space between s-button elements due to white space in markup .s-button { @@ -114,20 +122,16 @@ body.desktop .mini-tab-icon { margin-left: 1px; } - .first { - .s-button, - &.s-button { - @include border-left-radius($controlCr); - margin-left: 0; - } + > .s-button { + // Styles for .s-button as immediate descendants in .l-btn-set + &:first-child { @include btnSetButtonFirst(); } + &:last-child { @include btnSetButtonLast(); } } - .last { - .s-button, - &.s-button { - @include border-right-radius($controlCr); - } - } + // Must use following due to DOM structure of action buttons, + // which have structure like .l-btn-set > mct-representation class=first|last > .s-button + .first > .s-button { @include btnSetButtonFirst(); } + .last > .s-button { @include btnSetButtonLast(); } } .paused { diff --git a/platform/commonUI/general/src/StyleSheetLoader.js b/platform/commonUI/general/src/StyleSheetLoader.js index 13c5075a8bc..7f5cabe7c1e 100644 --- a/platform/commonUI/general/src/StyleSheetLoader.js +++ b/platform/commonUI/general/src/StyleSheetLoader.js @@ -37,8 +37,10 @@ define( * @param {object[]} stylesheets stylesheet extension definitions * @param $document Angular's jqLite-wrapped document element * @param {string} activeTheme the theme in use + * @param {string} [assetPath] the directory relative to which + * stylesheets will be found */ - function StyleSheetLoader(stylesheets, $document, activeTheme) { + function StyleSheetLoader(stylesheets, $document, activeTheme, assetPath) { var head = $document.find('head'), document = $document[0]; @@ -47,6 +49,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 @@ -68,6 +71,8 @@ define( stylesheet.theme === activeTheme; } + assetPath = assetPath || "."; + // Add all stylesheets from extensions stylesheets.filter(matchesTheme).forEach(addStyleSheet); } diff --git a/platform/commonUI/general/test/StyleSheetLoaderSpec.js b/platform/commonUI/general/test/StyleSheetLoaderSpec.js index 272ad61bf97..4dfb20e82d5 100644 --- a/platform/commonUI/general/test/StyleSheetLoaderSpec.js +++ b/platform/commonUI/general/test/StyleSheetLoaderSpec.js @@ -69,7 +69,7 @@ define( it("adjusts link locations", function () { expect(mockElement.setAttribute) - .toHaveBeenCalledWith('href', "a/b/c/d.css"); + .toHaveBeenCalledWith('href', "./a/b/c/d.css"); }); describe("for themed stylesheets", function () { @@ -95,12 +95,13 @@ define( it("includes matching themes", function () { expect(mockElement.setAttribute) - .toHaveBeenCalledWith('href', "a/b/c/themed.css"); + .toHaveBeenCalledWith('href', "./a/b/c/themed.css"); }); it("excludes mismatching themes", function () { expect(mockElement.setAttribute) - .not.toHaveBeenCalledWith('href', "a/b/c/bad-theme.css"); + .not + .toHaveBeenCalledWith('href', "./a/b/c/bad-theme.css"); }); }); diff --git a/platform/core/bundle.js b/platform/core/bundle.js index 4b9e2b9adbb..26a49e16d91 100644 --- a/platform/core/bundle.js +++ b/platform/core/bundle.js @@ -46,6 +46,7 @@ define([ "./src/capabilities/MutationCapability", "./src/capabilities/DelegationCapability", "./src/capabilities/InstantiationCapability", + "./src/runs/TransactingMutationListener", "./src/services/Now", "./src/services/Throttle", "./src/services/Topic", @@ -78,6 +79,7 @@ define([ MutationCapability, DelegationCapability, InstantiationCapability, + TransactingMutationListener, Now, Throttle, Topic, @@ -417,6 +419,12 @@ define([ } } ], + "runs": [ + { + "implementation": TransactingMutationListener, + "depends": ["topic", "transactionService"] + } + ], "constants": [ { "key": "PERSISTENCE_SPACE", diff --git a/platform/core/src/capabilities/CompositionCapability.js b/platform/core/src/capabilities/CompositionCapability.js index f7c2fb1a3ca..3f5769005fa 100644 --- a/platform/core/src/capabilities/CompositionCapability.js +++ b/platform/core/src/capabilities/CompositionCapability.js @@ -49,8 +49,7 @@ define( } /** - * Add a domain object to the composition of the field. - * This mutates but does not persist the modified object. + * Add a domain object to the composition of this domain object. * * If no index is given, this is added to the end of the composition. * diff --git a/platform/core/src/capabilities/PersistenceCapability.js b/platform/core/src/capabilities/PersistenceCapability.js index 275080368b2..54fa537c649 100644 --- a/platform/core/src/capabilities/PersistenceCapability.js +++ b/platform/core/src/capabilities/PersistenceCapability.js @@ -113,11 +113,16 @@ define( domainObject = this.domainObject, model = domainObject.getModel(), modified = model.modified, + persisted = model.persisted, persistenceService = this.persistenceService, - persistenceFn = model.persisted !== undefined ? + persistenceFn = persisted !== undefined ? this.persistenceService.updateObject : this.persistenceService.createObject; + if (persisted !== undefined && persisted === modified) { + return this.$q.when(true); + } + // Update persistence timestamp... domainObject.useCapability("mutation", function (m) { m.persisted = modified; @@ -178,6 +183,15 @@ define( }; + /** + * Check if this domain object has been persisted at some + * point. + * @returns {boolean} true if the object has been persisted + */ + PersistenceCapability.prototype.persisted = function () { + return this.domainObject.getModel().persisted !== undefined; + }; + /** * Get the key for this domain object in the given space. * diff --git a/platform/core/src/runs/TransactingMutationListener.js b/platform/core/src/runs/TransactingMutationListener.js new file mode 100644 index 00000000000..c534c6fae2e --- /dev/null +++ b/platform/core/src/runs/TransactingMutationListener.js @@ -0,0 +1,54 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define([], function () { + /** + * Listens for mutation on domain objects and triggers persistence when + * it occurs. + * @param {Topic} topic the `topic` service; used to listen for mutation + * @memberof platform/core + */ + function TransactingMutationListener(topic, transactionService) { + var mutationTopic = topic('mutation'); + mutationTopic.listen(function (domainObject) { + var persistence = domainObject.getCapability('persistence'); + var wasActive = transactionService.isActive(); + if (persistence.persisted()) { + if (!wasActive) { + transactionService.startTransaction(); + } + + transactionService.addToTransaction( + persistence.persist.bind(persistence), + persistence.refresh.bind(persistence) + ); + + if (!wasActive) { + transactionService.commit(); + } + } + }); + } + + return TransactingMutationListener; +}); diff --git a/platform/core/test/runs/TransactingMutationListenerSpec.js b/platform/core/test/runs/TransactingMutationListenerSpec.js new file mode 100644 index 00000000000..c371d25db95 --- /dev/null +++ b/platform/core/test/runs/TransactingMutationListenerSpec.js @@ -0,0 +1,120 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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( + ["../../src/runs/TransactingMutationListener"], + function (TransactingMutationListener) { + + describe("TransactingMutationListener", function () { + var mockTopic, + mockMutationTopic, + mockTransactionService, + mockDomainObject, + mockPersistence; + + beforeEach(function () { + mockTopic = jasmine.createSpy('topic'); + mockMutationTopic = + jasmine.createSpyObj('mutation', ['listen']); + mockTransactionService = + jasmine.createSpyObj('transactionService', [ + 'isActive', + 'startTransaction', + 'addToTransaction', + 'commit' + ]); + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getId', 'getCapability', 'getModel'] + ); + mockPersistence = jasmine.createSpyObj( + 'persistence', + ['persist', 'refresh', 'persisted'] + ); + + mockTopic.andCallFake(function (t) { + return (t === 'mutation') && mockMutationTopic; + }); + + mockDomainObject.getCapability.andCallFake(function (c) { + return (c === 'persistence') && mockPersistence; + }); + + mockPersistence.persisted.andReturn(true); + + return new TransactingMutationListener( + mockTopic, + mockTransactionService + ); + }); + + it("listens for mutation", function () { + expect(mockMutationTopic.listen) + .toHaveBeenCalledWith(jasmine.any(Function)); + }); + + [false, true].forEach(function (isActive) { + var verb = isActive ? "is" : "isn't"; + + function onlyWhenInactive(expectation) { + return isActive ? expectation.not : expectation; + } + + describe("when a transaction " + verb + " active", function () { + var innerVerb = isActive ? "does" : "doesn't"; + + beforeEach(function () { + mockTransactionService.isActive.andReturn(isActive); + }); + + describe("and mutation occurs", function () { + beforeEach(function () { + mockMutationTopic.listen.mostRecentCall + .args[0](mockDomainObject); + }); + + + it(innerVerb + " start a new transaction", function () { + onlyWhenInactive( + expect(mockTransactionService.startTransaction) + ).toHaveBeenCalled(); + }); + + it("adds to the active transaction", function () { + expect(mockTransactionService.addToTransaction) + .toHaveBeenCalledWith( + jasmine.any(Function), + jasmine.any(Function) + ); + }); + + it(innerVerb + " immediately commit", function () { + onlyWhenInactive( + expect(mockTransactionService.commit) + ).toHaveBeenCalled(); + }); + }); + }); + }); + }); + } +); diff --git a/platform/entanglement/src/capabilities/LocationCapability.js b/platform/entanglement/src/capabilities/LocationCapability.js index d1fe61e4c40..0e1c171d89e 100644 --- a/platform/entanglement/src/capabilities/LocationCapability.js +++ b/platform/entanglement/src/capabilities/LocationCapability.js @@ -76,17 +76,12 @@ define( * completes. */ LocationCapability.prototype.setPrimaryLocation = function (location) { - var capability = this; return this.domainObject.useCapability( 'mutation', function (model) { model.location = location; } - ).then(function () { - return capability.domainObject - .getCapability('persistence') - .persist(); - }); + ); }; /** diff --git a/platform/entanglement/src/services/LinkService.js b/platform/entanglement/src/services/LinkService.js index d94e362841f..74c985f3128 100644 --- a/platform/entanglement/src/services/LinkService.js +++ b/platform/entanglement/src/services/LinkService.js @@ -63,14 +63,7 @@ define( ); } - return parentObject.getCapability('composition').add(object) - .then(function (objectInNewContext) { - return parentObject.getCapability('persistence') - .persist() - .then(function () { - return objectInNewContext; - }); - }); + return parentObject.getCapability('composition').add(object); }; return LinkService; diff --git a/platform/entanglement/test/capabilities/LocationCapabilitySpec.js b/platform/entanglement/test/capabilities/LocationCapabilitySpec.js index f9d4aa8fc0e..4ff2acd1fdc 100644 --- a/platform/entanglement/test/capabilities/LocationCapabilitySpec.js +++ b/platform/entanglement/test/capabilities/LocationCapabilitySpec.js @@ -33,7 +33,6 @@ define( describe("instantiated with domain object", function () { var locationCapability, - persistencePromise, mutationPromise, mockQ, mockInjector, @@ -49,10 +48,6 @@ define( return domainObjectFactory({id: 'root'}); } }, - persistence: jasmine.createSpyObj( - 'persistenceCapability', - ['persist'] - ), mutation: jasmine.createSpyObj( 'mutationCapability', ['invoke'] @@ -65,11 +60,6 @@ define( mockObjectService = jasmine.createSpyObj("objectService", ["getObjects"]); - persistencePromise = new ControlledPromise(); - domainObject.capabilities.persistence.persist.andReturn( - persistencePromise - ); - mutationPromise = new ControlledPromise(); domainObject.capabilities.mutation.invoke.andCallFake( function (mutator) { @@ -103,22 +93,17 @@ define( expect(locationCapability.isOriginal()).toBe(false); }); - it("can persist location", function () { - var persistResult = locationCapability + it("can mutate location", function () { + var result = locationCapability .setPrimaryLocation('root'), whenComplete = jasmine.createSpy('whenComplete'); - persistResult.then(whenComplete); + result.then(whenComplete); expect(domainObject.model.location).not.toBeDefined(); mutationPromise.resolve(); expect(domainObject.model.location).toBe('root'); - expect(whenComplete).not.toHaveBeenCalled(); - expect(domainObject.capabilities.persistence.persist) - .toHaveBeenCalled(); - - persistencePromise.resolve(); expect(whenComplete).toHaveBeenCalled(); }); diff --git a/platform/entanglement/test/services/LinkServiceSpec.js b/platform/entanglement/test/services/LinkServiceSpec.js index 632ab9bb8be..16d82e66015 100644 --- a/platform/entanglement/test/services/LinkServiceSpec.js +++ b/platform/entanglement/test/services/LinkServiceSpec.js @@ -139,20 +139,12 @@ define( parentModel, parentObject, compositionPromise, - persistencePromise, addPromise, - compositionCapability, - persistenceCapability; + compositionCapability; beforeEach(function () { compositionPromise = new ControlledPromise(); - persistencePromise = new ControlledPromise(); addPromise = new ControlledPromise(); - persistenceCapability = jasmine.createSpyObj( - 'persistenceCapability', - ['persist'] - ); - persistenceCapability.persist.andReturn(persistencePromise); compositionCapability = jasmine.createSpyObj( 'compositionCapability', ['invoke', 'add'] @@ -172,7 +164,6 @@ define( return new ControlledPromise(); } }, - persistence: persistenceCapability, composition: compositionCapability } }); @@ -197,15 +188,6 @@ define( .toHaveBeenCalledWith(object); }); - it("persists parent", function () { - linkService.perform(object, parentObject); - expect(addPromise.then).toHaveBeenCalled(); - addPromise.resolve(linkedObject); - expect(parentObject.getCapability) - .toHaveBeenCalledWith('persistence'); - expect(persistenceCapability.persist).toHaveBeenCalled(); - }); - it("returns object representing new link", function () { var returnPromise, whenComplete; returnPromise = linkService.perform(object, parentObject); @@ -213,7 +195,6 @@ define( returnPromise.then(whenComplete); addPromise.resolve(linkedObject); - persistencePromise.resolve(); compositionPromise.resolve([linkedObject]); expect(whenComplete).toHaveBeenCalledWith(linkedObject); }); diff --git a/platform/execution/src/WorkerService.js b/platform/execution/src/WorkerService.js index d809a568f2d..87efd6f7da5 100644 --- a/platform/execution/src/WorkerService.js +++ b/platform/execution/src/WorkerService.js @@ -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; } } diff --git a/platform/features/clock/src/actions/AbstractStartTimerAction.js b/platform/features/clock/src/actions/AbstractStartTimerAction.js index 50154dfc31f..dc55f446f42 100644 --- a/platform/features/clock/src/actions/AbstractStartTimerAction.js +++ b/platform/features/clock/src/actions/AbstractStartTimerAction.js @@ -49,17 +49,11 @@ define( var domainObject = this.domainObject, now = this.now; - function doPersist() { - var persistence = domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - function setTimestamp(model) { model.timestamp = now(); } - return domainObject.useCapability('mutation', setTimestamp) - .then(doPersist); + return domainObject.useCapability('mutation', setTimestamp); }; return AbstractStartTimerAction; diff --git a/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js b/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js index a7c6150c875..6478d07877f 100644 --- a/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js +++ b/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js @@ -27,7 +27,6 @@ define( describe("A timer's start/restart action", function () { var mockNow, mockDomainObject, - mockPersistence, testModel, action; @@ -45,14 +44,7 @@ define( 'domainObject', ['getCapability', 'useCapability'] ); - mockPersistence = jasmine.createSpyObj( - 'persistence', - ['persist'] - ); - mockDomainObject.getCapability.andCallFake(function (c) { - return (c === 'persistence') && mockPersistence; - }); mockDomainObject.useCapability.andCallFake(function (c, v) { if (c === 'mutation') { testModel = v(testModel) || testModel; @@ -67,18 +59,16 @@ define( }); }); - it("updates the model with a timestamp and persists", function () { + it("updates the model with a timestamp", function () { mockNow.andReturn(12000); action.perform(); expect(testModel.timestamp).toEqual(12000); - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("does not truncate milliseconds", function () { mockNow.andReturn(42321); action.perform(); expect(testModel.timestamp).toEqual(42321); - expect(mockPersistence.persist).toHaveBeenCalled(); }); }); } diff --git a/platform/features/clock/test/actions/RestartTimerActionSpec.js b/platform/features/clock/test/actions/RestartTimerActionSpec.js index af6e8a05481..c0d4ded90d5 100644 --- a/platform/features/clock/test/actions/RestartTimerActionSpec.js +++ b/platform/features/clock/test/actions/RestartTimerActionSpec.js @@ -27,7 +27,6 @@ define( describe("A timer's restart action", function () { var mockNow, mockDomainObject, - mockPersistence, testModel, testContext, action; @@ -46,14 +45,7 @@ define( 'domainObject', ['getCapability', 'useCapability', 'getModel'] ); - mockPersistence = jasmine.createSpyObj( - 'persistence', - ['persist'] - ); - mockDomainObject.getCapability.andCallFake(function (c) { - return (c === 'persistence') && mockPersistence; - }); mockDomainObject.useCapability.andCallFake(function (c, v) { if (c === 'mutation') { testModel = v(testModel) || testModel; @@ -70,11 +62,10 @@ define( action = new RestartTimerAction(mockNow, testContext); }); - it("updates the model with a timestamp and persists", function () { + it("updates the model with a timestamp", function () { mockNow.andReturn(12000); action.perform(); expect(testModel.timestamp).toEqual(12000); - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("applies only to timers with a target time", function () { diff --git a/platform/features/clock/test/actions/StartTimerActionSpec.js b/platform/features/clock/test/actions/StartTimerActionSpec.js index 58d66e5cb0a..588f776d707 100644 --- a/platform/features/clock/test/actions/StartTimerActionSpec.js +++ b/platform/features/clock/test/actions/StartTimerActionSpec.js @@ -27,7 +27,6 @@ define( describe("A timer's start action", function () { var mockNow, mockDomainObject, - mockPersistence, testModel, testContext, action; @@ -46,14 +45,7 @@ define( 'domainObject', ['getCapability', 'useCapability', 'getModel'] ); - mockPersistence = jasmine.createSpyObj( - 'persistence', - ['persist'] - ); - mockDomainObject.getCapability.andCallFake(function (c) { - return (c === 'persistence') && mockPersistence; - }); mockDomainObject.useCapability.andCallFake(function (c, v) { if (c === 'mutation') { testModel = v(testModel) || testModel; @@ -70,11 +62,10 @@ define( action = new StartTimerAction(mockNow, testContext); }); - it("updates the model with a timestamp and persists", function () { + it("updates the model with a timestamp", function () { mockNow.andReturn(12000); action.perform(); expect(testModel.timestamp).toEqual(12000); - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("applies only to timers without a target time", function () { diff --git a/platform/features/conductor-v2/compatibility/bundle.js b/platform/features/conductor-v2/compatibility/bundle.js index b91ab7454c7..d09b23ab5b9 100644 --- a/platform/features/conductor-v2/compatibility/bundle.js +++ b/platform/features/conductor-v2/compatibility/bundle.js @@ -39,7 +39,7 @@ define([ "key": "conductorService", "implementation": ConductorService, "depends": [ - "timeConductor" + "openmct" ] } ], @@ -47,7 +47,7 @@ define([ { "implementation": ConductorRepresenter, "depends": [ - "timeConductor" + "openmct" ] } ], @@ -57,7 +57,7 @@ define([ "provides": "telemetryService", "implementation": ConductorTelemetryDecorator, "depends": [ - "timeConductor" + "openmct" ] } ] diff --git a/platform/features/conductor-v2/compatibility/src/ConductorRepresenter.js b/platform/features/conductor-v2/compatibility/src/ConductorRepresenter.js index 7013906ef96..e267c9d2f40 100644 --- a/platform/features/conductor-v2/compatibility/src/ConductorRepresenter.js +++ b/platform/features/conductor-v2/compatibility/src/ConductorRepresenter.js @@ -37,11 +37,11 @@ define( * @constructor */ function ConductorRepresenter( - timeConductor, + openmct, scope, element ) { - this.conductor = timeConductor; + this.conductor = openmct.conductor; this.scope = scope; this.element = element; diff --git a/platform/features/conductor-v2/compatibility/src/ConductorService.js b/platform/features/conductor-v2/compatibility/src/ConductorService.js index df2c119793a..1c13b4ada4e 100644 --- a/platform/features/conductor-v2/compatibility/src/ConductorService.js +++ b/platform/features/conductor-v2/compatibility/src/ConductorService.js @@ -60,8 +60,8 @@ define([ * directly on the conductor service to continue to do so without * modification. */ - function ConductorService(timeConductor) { - this.tc = new Conductor(timeConductor); + function ConductorService(openmct) { + this.tc = new Conductor(openmct.conductor); } ConductorService.prototype.getConductor = function () { diff --git a/platform/features/conductor-v2/compatibility/src/ConductorTelemetryDecorator.js b/platform/features/conductor-v2/compatibility/src/ConductorTelemetryDecorator.js index 10d8c4b8c12..137da592684 100644 --- a/platform/features/conductor-v2/compatibility/src/ConductorTelemetryDecorator.js +++ b/platform/features/conductor-v2/compatibility/src/ConductorTelemetryDecorator.js @@ -36,8 +36,8 @@ define( * the service which exposes the global time conductor * @param {TelemetryService} telemetryService the decorated service */ - function ConductorTelemetryDecorator(timeConductor, telemetryService) { - this.conductor = timeConductor; + function ConductorTelemetryDecorator(openmct, telemetryService) { + this.conductor = openmct.conductor; this.telemetryService = telemetryService; this.amendRequests = ConductorTelemetryDecorator.prototype.amendRequests.bind(this); diff --git a/platform/features/conductor-v2/conductor/bundle.js b/platform/features/conductor-v2/conductor/bundle.js index c15423f9c73..a3f2150e46a 100644 --- a/platform/features/conductor-v2/conductor/bundle.js +++ b/platform/features/conductor-v2/conductor/bundle.js @@ -23,7 +23,6 @@ define([ "./src/ui/TimeConductorViewService", "./src/ui/TimeConductorController", - "./src/TimeConductor", "./src/ui/ConductorAxisController", "./src/ui/ConductorTOIController", "./src/ui/TimeOfInterestController", @@ -37,7 +36,6 @@ define([ ], function ( TimeConductorViewService, TimeConductorController, - TimeConductor, ConductorAxisController, ConductorTOIController, TimeOfInterestController, @@ -53,15 +51,11 @@ define([ legacyRegistry.register("platform/features/conductor-v2/conductor", { "extensions": { "services": [ - { - "key": "timeConductor", - "implementation": TimeConductor - }, { "key": "timeConductorViewService", "implementation": TimeConductorViewService, "depends": [ - "timeConductor", + "openmct", "timeSystems[]" ] } @@ -73,7 +67,7 @@ define([ "depends": [ "$scope", "$window", - "timeConductor", + "openmct", "timeConductorViewService", "timeSystems[]", "formatService" @@ -83,7 +77,7 @@ define([ "key": "ConductorAxisController", "implementation": ConductorAxisController, "depends": [ - "timeConductor", + "openmct", "formatService", "timeConductorViewService" ] @@ -93,7 +87,7 @@ define([ "implementation": ConductorTOIController, "depends": [ "$scope", - "timeConductor", + "openmct", "timeConductorViewService", "formatService" ] @@ -103,7 +97,7 @@ define([ "implementation": TimeOfInterestController, "depends": [ "$scope", - "timeConductor", + "openmct", "formatService" ] } @@ -113,7 +107,7 @@ define([ "key": "mctConductorAxis", "implementation": MCTConductorAxis, "depends": [ - "timeConductor", + "openmct", "formatService" ] } diff --git a/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js b/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js index f70ba8f223a..75c654107b2 100644 --- a/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js +++ b/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js @@ -32,11 +32,11 @@ define( * labelled 'ticks'. It requires 'start' and 'end' integer values to * be specified as attributes. */ - function ConductorAxisController(conductor, formatService, conductorViewService) { + function ConductorAxisController(openmct, formatService, conductorViewService) { // Dependencies this.d3 = d3; this.formatService = formatService; - this.conductor = conductor; + this.conductor = openmct.conductor; this.conductorViewService = conductorViewService; // Runtime properties (set by 'link' function) @@ -47,8 +47,8 @@ define( this.initialized = false; this.msPerPixel = undefined; - this.bounds = conductor.bounds(); - this.timeSystem = conductor.timeSystem(); + this.bounds = this.conductor.bounds(); + this.timeSystem = this.conductor.timeSystem(); //Bind all class functions to 'this' Object.keys(ConductorAxisController.prototype).filter(function (key) { diff --git a/platform/features/conductor-v2/conductor/src/ui/ConductorTOIController.js b/platform/features/conductor-v2/conductor/src/ui/ConductorTOIController.js index cb26c9e0d41..b897f832d6c 100644 --- a/platform/features/conductor-v2/conductor/src/ui/ConductorTOIController.js +++ b/platform/features/conductor-v2/conductor/src/ui/ConductorTOIController.js @@ -29,8 +29,8 @@ define( * labelled 'ticks'. It requires 'start' and 'end' integer values to * be specified as attributes. */ - function ConductorTOIController($scope, conductor, conductorViewService) { - this.conductor = conductor; + function ConductorTOIController($scope, openmct, conductorViewService) { + this.conductor = openmct.conductor; this.conductorViewService = conductorViewService; //Bind all class functions to 'this' diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js index 6c57e1b6953..376a4e72fa7 100644 --- a/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js +++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js @@ -26,7 +26,7 @@ define( ], function (TimeConductorValidation) { - function TimeConductorController($scope, $window, timeConductor, conductorViewService, timeSystems, formatService) { + function TimeConductorController($scope, $window, openmct, conductorViewService, timeSystems, formatService) { var self = this; @@ -40,7 +40,7 @@ define( this.$scope = $scope; this.$window = $window; this.conductorViewService = conductorViewService; - this.conductor = timeConductor; + this.conductor = openmct.conductor; this.modes = conductorViewService.availableModes(); this.validation = new TimeConductorValidation(this.conductor); this.formatService = formatService; diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js index db970006f30..891c09c1fe1 100644 --- a/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js +++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js @@ -36,7 +36,7 @@ define( * @param timeSystems * @constructor */ - function TimeConductorViewService(conductor, timeSystems) { + function TimeConductorViewService(openmct, timeSystems) { EventEmitter.call(this); @@ -44,7 +44,7 @@ define( return timeSystemConstructor(); }); - this.conductor = conductor; + this.conductor = openmct.conductor; this.currentMode = undefined; /** diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js index 5477eadeeef..e23b4add820 100644 --- a/platform/features/plot/bundle.js +++ b/platform/features/plot/bundle.js @@ -78,7 +78,7 @@ define([ "telemetryHandler", "throttle", "PLOT_FIXED_DURATION", - "timeConductor" + "openmct" ] }, { diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index 5ca2721bced..c2f80151aff 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -29,12 +29,12 @@ class="abs holder holder-plot has-control-bar">
- PNG - JPG diff --git a/platform/features/table/bundle.js b/platform/features/table/bundle.js index 1babbda8c7a..02b78f847ff 100644 --- a/platform/features/table/bundle.js +++ b/platform/features/table/bundle.js @@ -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 ) { /** @@ -109,12 +115,12 @@ define([ { "key": "HistoricalTableController", "implementation": HistoricalTableController, - "depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout", "timeConductor", "timeConductor"] + "depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout", "openmct"] }, { "key": "RealtimeTableController", "implementation": RealtimeTableController, - "depends": ["$scope", "telemetryHandler", "telemetryFormatter", "timeConductor"] + "depends": ["$scope", "telemetryHandler", "telemetryFormatter", "openmct"] }, { "key": "TableOptionsController", @@ -127,8 +133,8 @@ define([ { "name": "Historical Table", "key": "table", + "template": historicalTableTemplate, "cssclass": "icon-tabular", - "templateUrl": "templates/historical-table.html", "needs": [ "telemetry" ], @@ -139,7 +145,7 @@ define([ "name": "Real-time Table", "key": "rt-table", "cssclass": "icon-tabular-realtime", - "templateUrl": "templates/rt-table.html", + "template": rtTableTemplate, "needs": [ "telemetry" ], @@ -157,7 +163,7 @@ define([ "representations": [ { "key": "table-options-edit", - "templateUrl": "templates/table-options-edit.html" + "template": tableOptionsEditTemplate } ], "stylesheets": [ diff --git a/platform/features/timeline/src/controllers/drag/TimelineDragHandler.js b/platform/features/timeline/src/controllers/drag/TimelineDragHandler.js index 745c3cc5cff..d7bb73c6ec6 100644 --- a/platform/features/timeline/src/controllers/drag/TimelineDragHandler.js +++ b/platform/features/timeline/src/controllers/drag/TimelineDragHandler.js @@ -35,7 +35,6 @@ define( */ function TimelineDragHandler(domainObject, objectLoader) { var timespans = {}, - persists = {}, mutations = {}, compositions = {}, dirty = {}; @@ -56,8 +55,6 @@ define( timespans[id] = timespan; // And its mutation capability mutations[id] = object.getCapability('mutation'); - // Also cache the persistence capability for later - persists[id] = object.getCapability('persistence'); // And the composition, for bulk moves compositions[id] = object.getModel().composition || []; }); @@ -71,19 +68,14 @@ define( } // Persist changes for objects by id (when dragging ends) - function doPersist(id) { - var persistence = persists[id], - mutation = mutations[id]; + function finalMutate(id) { + var mutation = mutations[id]; if (mutation) { // Mutate just to update the timestamp (since we // explicitly don't do this during the drag to // avoid firing a ton of refreshes.) mutation.mutate(function () {}); } - if (persistence) { - // Persist the changes - persistence.persist(); - } } // Use the object loader to get objects which have timespans @@ -105,7 +97,7 @@ define( */ persist: function () { // Persist every dirty object... - Object.keys(dirty).forEach(doPersist); + Object.keys(dirty).forEach(finalMutate); // Clear out the dirty list dirty = {}; }, diff --git a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDecorator.js b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDecorator.js index b082d4e1c0a..bafd5cd71c5 100644 --- a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDecorator.js +++ b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDecorator.js @@ -35,7 +35,6 @@ define( var domainObject = swimlane && swimlane.domainObject, model = (domainObject && domainObject.getModel()) || {}, mutator = domainObject && domainObject.getCapability('mutation'), - persister = domainObject && domainObject.getCapability('persistence'), type = domainObject && domainObject.getCapability('type'), dropHandler = new TimelineSwimlaneDropHandler(swimlane); @@ -48,7 +47,7 @@ define( mutator.mutate(function (m) { m.relationships = m.relationships || {}; m.relationships[ACTIVITY_RELATIONSHIP] = value; - }).then(persister.persist); + }); } } // ...otherwise, use as a getter @@ -63,7 +62,7 @@ define( // Update the link mutator.mutate(function (m) { m.link = value; - }).then(persister.persist); + }); } return model.link; } @@ -84,7 +83,7 @@ define( } // Activities should have the Activity Modes and Activity Link dialog - if (type && type.instanceOf("activity") && mutator && persister) { + if (type && type.instanceOf("activity") && mutator) { swimlane.modes = modes; swimlane.link = link; } diff --git a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js index affa6318632..8aa642f6fbf 100644 --- a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js +++ b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js @@ -29,16 +29,6 @@ define( * @constructor */ function TimelineSwimlaneDropHandler(swimlane) { - // Utility function; like $q.when, but synchronous (to reduce - // performance impact when wrapping synchronous values) - function asPromise(value) { - return (value && value.then) ? value : { - then: function (callback) { - return asPromise(callback(value)); - } - }; - } - // Check if we are in edit mode (also check parents) function inEditMode() { return swimlane.domainObject.hasCapability('editor') && @@ -75,16 +65,7 @@ define( // Initiate mutation of a domain object function doMutate(domainObject, mutator) { - return asPromise( - domainObject.useCapability("mutation", mutator) - ).then(function () { - // Persist the results of mutation - var persistence = domainObject.getCapability("persistence"); - if (persistence) { - // Persist the changes - persistence.persist(); - } - }); + return domainObject.useCapability("mutation", mutator); } // Check if this swimlane is in a state where a drop-after will diff --git a/platform/features/timeline/test/controllers/drag/TimelineDragHandlerSpec.js b/platform/features/timeline/test/controllers/drag/TimelineDragHandlerSpec.js index 7ea79059717..03bb50504ab 100644 --- a/platform/features/timeline/test/controllers/drag/TimelineDragHandlerSpec.js +++ b/platform/features/timeline/test/controllers/drag/TimelineDragHandlerSpec.js @@ -32,7 +32,6 @@ define( mockDomainObjects, mockTimespans, mockMutations, - mockPersists, mockCallback, handler; @@ -66,7 +65,6 @@ define( mockDomainObj.useCapability.andReturn(asPromise(mockTimespans[id])); mockDomainObj.getCapability.andCallFake(function (c) { return { - persistence: mockPersists[id], mutation: mockMutations[id] }[c]; }); @@ -76,17 +74,12 @@ define( beforeEach(function () { mockTimespans = {}; - mockPersists = {}; mockMutations = {}; ['a', 'b', 'c', 'd', 'e', 'f'].forEach(function (id, index) { mockTimespans[id] = jasmine.createSpyObj( 'timespan-' + id, ['getStart', 'getEnd', 'getDuration', 'setStart', 'setEnd', 'setDuration'] ); - mockPersists[id] = jasmine.createSpyObj( - 'persistence-' + id, - ['persist'] - ); mockMutations[id] = jasmine.createSpyObj( 'mutation-' + id, ['mutate'] @@ -209,20 +202,6 @@ define( expect(mockTimespans.c.setStart).toHaveBeenCalledWith(1000); }); - it("persists mutated objects", function () { - handler.start('a', 20); - handler.end('b', 50); - handler.duration('c', 30); - handler.persist(); - expect(mockPersists.a.persist).toHaveBeenCalled(); - expect(mockPersists.b.persist).toHaveBeenCalled(); - expect(mockPersists.c.persist).toHaveBeenCalled(); - expect(mockPersists.d.persist).not.toHaveBeenCalled(); - expect(mockPersists.e.persist).not.toHaveBeenCalled(); - expect(mockPersists.f.persist).not.toHaveBeenCalled(); - }); - - }); } ); diff --git a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDecoratorSpec.js b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDecoratorSpec.js index fb251c22cc4..2d777656ee8 100644 --- a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDecoratorSpec.js +++ b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDecoratorSpec.js @@ -50,10 +50,6 @@ define( 'mutation', ['mutate'] ); - mockCapabilities.persistence = jasmine.createSpyObj( - 'persistence', - ['persist'] - ); mockCapabilities.type = jasmine.createSpyObj( 'type', ['instanceOf'] @@ -115,11 +111,6 @@ define( .toHaveBeenCalledWith(jasmine.any(Function)); mockCapabilities.mutation.mutate.mostRecentCall.args[0](testModel); expect(testModel.relationships.modes).toEqual(['abc', 'xyz']); - - // Verify that persistence is called when promise resolves - expect(mockCapabilities.persistence.persist).not.toHaveBeenCalled(); - mockPromise.then.mostRecentCall.args[0](); - expect(mockCapabilities.persistence.persist).toHaveBeenCalled(); }); it("mutates modes when used as a setter", function () { @@ -128,11 +119,6 @@ define( .toHaveBeenCalledWith(jasmine.any(Function)); mockCapabilities.mutation.mutate.mostRecentCall.args[0](testModel); expect(testModel.link).toEqual("http://www.noaa.gov"); - - // Verify that persistence is called when promise resolves - expect(mockCapabilities.persistence.persist).not.toHaveBeenCalled(); - mockPromise.then.mostRecentCall.args[0](); - expect(mockCapabilities.persistence.persist).toHaveBeenCalled(); }); it("does not mutate modes when unchanged", function () { diff --git a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js index 6e763851394..1ea986ae169 100644 --- a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js +++ b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js @@ -29,7 +29,6 @@ define( mockOtherObject, mockActionCapability, mockEditorCapability, - mockPersistence, mockContext, mockAction, handler; @@ -76,7 +75,6 @@ define( ["getId", "getCapability", "useCapability", "hasCapability"] ); mockActionCapability = jasmine.createSpyObj("action", ["perform", "getActions"]); - mockPersistence = jasmine.createSpyObj("persistence", ["persist"]); mockContext = jasmine.createSpyObj('context', ['getParent']); mockActionCapability.getActions.andReturn([mockAction]); @@ -89,14 +87,12 @@ define( mockSwimlane.domainObject.getCapability.andCallFake(function (c) { return { action: mockActionCapability, - persistence: mockPersistence, editor: mockEditorCapability }[c]; }); mockSwimlane.parent.domainObject.getCapability.andCallFake(function (c) { return { action: mockActionCapability, - persistence: mockPersistence, editor: mockEditorCapability }[c]; }); @@ -162,8 +158,6 @@ define( mockSwimlane.domainObject.useCapability.mostRecentCall .args[1](testModel); expect(testModel.composition).toEqual(['c', 'd']); - // Finally, should also have persisted - expect(mockPersistence.persist).toHaveBeenCalled(); }); it("inserts after as a peer when highlighted at the bottom", function () { diff --git a/platform/representation/src/gestures/GestureProvider.js b/platform/representation/src/gestures/GestureProvider.js index 5e170f68ac1..051dafd5a7b 100644 --- a/platform/representation/src/gestures/GestureProvider.js +++ b/platform/representation/src/gestures/GestureProvider.js @@ -72,7 +72,7 @@ define( // Assemble all gestures into a map, for easy look up gestures.forEach(function (gesture) { - gestureMap[gesture.key] = gesture; + gestureMap[gesture.key] = gestureMap[gesture.key] || gesture; }); this.gestureMap = gestureMap; diff --git a/platform/search/bundle.js b/platform/search/bundle.js index 61a266d0497..7f8a64ab048 100644 --- a/platform/search/bundle.js +++ b/platform/search/bundle.js @@ -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 } ] } diff --git a/src/BundleRegistry.js b/src/BundleRegistry.js index ccc1f04c602..9089d0ee9a2 100644 --- a/src/BundleRegistry.js +++ b/src/BundleRegistry.js @@ -24,10 +24,28 @@ 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); + } + delete this.bundles[path]; }; BundleRegistry.prototype.contains = function (path) { @@ -42,8 +60,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; diff --git a/src/BundleRegistrySpec.js b/src/BundleRegistrySpec.js index 2dfa238d88c..b301db3d9c5 100644 --- a/src/BundleRegistrySpec.js +++ b/src/BundleRegistrySpec.js @@ -51,6 +51,7 @@ define(['./BundleRegistry'], function (BundleRegistry) { beforeEach(function () { testBundleDef = { someKey: "some value" }; bundleRegistry.register(testPath, testBundleDef); + bundleRegistry.enable(testPath); }); it("lists registered bundles", function () { diff --git a/src/MCT.js b/src/MCT.js new file mode 100644 index 00000000000..c1b328b736a --- /dev/null +++ b/src/MCT.js @@ -0,0 +1,275 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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', + 'legacyRegistry', + 'uuid', + './api/api', + 'text!./adapter/templates/edit-object-replacement.html', + './selection/Selection', + './api/objects/object-utils', + './ui/ViewRegistry' +], function ( + EventEmitter, + legacyRegistry, + uuid, + api, + editObjectTemplate, + Selection, + objectUtils, + ViewRegistry +) { + /** + * Open MCT is an extensible web application for building mission + * control user interfaces. This module is itself an instance of + * [MCT]{@link module:openmct.MCT}, which provides an interface for + * configuring and executing the application. + * + * @exports openmct + */ + + /** + * The Open MCT application. This may be configured by installing plugins + * or registering extensions before the application is started. + * @class MCT + * @memberof module:openmct + * @augments {EventEmitter} + */ + function MCT() { + EventEmitter.call(this); + this.legacyBundle = { extensions: { + services: [ + { + key: "openmct", + implementation: function () { + return this; + }.bind(this) + } + ] + } }; + + /** + * Tracks current selection state of the application. + * @private + */ + this.selection = new Selection(); + + /** + * MCT's time conductor, which may be used to synchronize view contents + * for telemetry- or time-based views. + * @type {module:openmct.TimeConductor} + * @memberof module:openmct.MCT# + * @name conductor + */ + this.conductor = new api.TimeConductor(); + + /** + * An interface for interacting with the composition of domain objects. + * The composition of a domain object is the list of other domain + * objects it "contains" (for instance, that should be displayed + * beneath it in the tree.) + * + * `composition` may be called as a function, in which case it acts + * as [`composition.get`]{@link module:openmct.CompositionAPI#get}. + * + * @type {module:openmct.CompositionAPI} + * @memberof module:openmct.MCT# + * @name composition + */ + this.composition = new api.CompositionAPI(); + + /** + * Registry for views of domain objects which should appear in the + * main viewing area. + * + * @type {module:openmct.ViewRegistry} + * @memberof module:openmct.MCT# + * @name mainViews + */ + this.mainViews = new ViewRegistry(); + + /** + * Registry for views which should appear in the Inspector area. + * These views will be chosen based on selection state, so + * providers should be prepared to test arbitrary objects for + * viewability. + * + * @type {module:openmct.ViewRegistry} + * @memberof module:openmct.MCT# + * @name inspectors + */ + this.inspectors = new ViewRegistry(); + + /** + * Registry for views which should appear in Edit Properties + * dialogs, and similar user interface elements used for + * modifying domain objects external to its regular views. + * + * @type {module:openmct.ViewRegistry} + * @memberof module:openmct.MCT# + * @name propertyEditors + */ + this.propertyEditors = new ViewRegistry(); + + /** + * Registry for views which should appear in the status indicator area. + * @type {module:openmct.ViewRegistry} + * @memberof module:openmct.MCT# + * @name indicators + */ + this.indicators = new ViewRegistry(); + + /** + * Registry for views which should appear in the toolbar area while + * editing. + * + * These views will be chosen based on selection state, so + * providers should be prepared to test arbitrary objects for + * viewability. + * + * @type {module:openmct.ViewRegistry} + * @memberof module:openmct.MCT# + * @name toolbars + */ + this.toolbars = new ViewRegistry(); + + /** + * Registry for domain object types which may exist within this + * instance of Open MCT. + * + * @type {module:openmct.TypeRegistry} + * @memberof module:openmct.MCT# + * @name types + */ + this.types = new api.TypeRegistry(); + + /** + * Utilities for attaching common behaviors to views. + * + * @type {module:openmct.GestureAPI} + * @memberof module:openmct.MCT# + * @name gestures + */ + this.gestures = new api.GestureAPI(); + + /** + * An interface for interacting with domain objects and the domain + * object hierarchy. + * + * @type {module:openmct.ObjectAPI} + * @memberof module:openmct.MCT# + * @name objects + */ + this.objects = new api.ObjectAPI(); + + /** + * An interface for retrieving and interpreting telemetry data associated + * with a domain object. + * + * @type {module:openmct.TelemetryAPI} + * @memberof module:openmct.MCT# + * @name telemetry + */ + this.telemetry = new api.TelemetryAPI(); + + this.TimeConductor = this.conductor; // compatibility for prototype + 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. + * @memberof module:openmct.MCT# + * @method setAssetPath + */ + MCT.prototype.setAssetPath = function (path) { + this.legacyExtension('constants', { + key: "ASSETS_PATH", + value: path + }); + }; + + /** + * Start running Open MCT. This should be called only after any plugins + * have been installed. + * @fires module:openmct.MCT~start + * @memberof module:openmct.MCT# + * @method start + * @param {HTMLElement} [domElement] the DOM element in which to run + * MCT; if undefined, MCT will be run in the body of the document + */ + MCT.prototype.start = function (domElement) { + if (!domElement) { + domElement = document.body; + } + + var appDiv = document.createElement('div'); + appDiv.setAttribute('ng-view', ''); + appDiv.className = 'user-environ'; + domElement.appendChild(appDiv); + + 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'); + /** + * Fired by [MCT]{@link module:openmct.MCT} when the application + * is started. + * @event start + * @memberof module:openmct.MCT~ + */ + this.emit('start'); + }; + + + /** + * Install a plugin in MCT. + * + * @param {Function} plugin a plugin install function which will be + * invoked with the mct instance. + * @memberof module:openmct.MCT# + */ + MCT.prototype.install = function (plugin) { + plugin(this); + }; + + return MCT; +}); diff --git a/src/adapter/actions/ActionDialogDecorator.js b/src/adapter/actions/ActionDialogDecorator.js new file mode 100644 index 00000000000..d80e72741ae --- /dev/null +++ b/src/adapter/actions/ActionDialogDecorator.js @@ -0,0 +1,57 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + '../../api/objects/object-utils' +], function (objectUtils) { + function ActionDialogDecorator(mct, actionService) { + this.mct = mct; + this.actionService = actionService; + } + + ActionDialogDecorator.prototype.getActions = function (context) { + var mct = this.mct; + + return this.actionService.getActions(context).map(function (action) { + if (action.dialogService) { + var domainObject = objectUtils.toNewFormat( + context.domainObject.getModel(), + objectUtils.parseKeyString(context.domainObject.getId()) + ); + var providers = mct.propertyEditors.get(domainObject); + + if (providers.length > 0) { + action.dialogService = Object.create(action.dialogService); + action.dialogService.getUserInput = function (form, value) { + return new mct.Dialog( + providers[0].view(context.domainObject), + form.title + ).show(); + }; + } + } + return action; + }); + }; + + return ActionDialogDecorator; +}); diff --git a/src/adapter/bundle.js b/src/adapter/bundle.js new file mode 100644 index 00000000000..0e2a4cf3040 --- /dev/null +++ b/src/adapter/bundle.js @@ -0,0 +1,127 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + 'legacyRegistry', + './actions/ActionDialogDecorator', + './directives/MCTView', + './services/Instantiate', + './capabilities/APICapabilityDecorator', + './policies/AdapterCompositionPolicy', + './runs/AlternateCompositionInitializer' +], function ( + legacyRegistry, + ActionDialogDecorator, + MCTView, + Instantiate, + APICapabilityDecorator, + AdapterCompositionPolicy, + AlternateCompositionInitializer +) { + legacyRegistry.register('src/adapter', { + "extensions": { + "directives": [ + { + key: "mctView", + implementation: MCTView, + depends: [ + "newViews[]", + "openmct" + ] + } + ], + 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: ["openmct"] + } + ], + policies: [ + { + category: "composition", + implementation: AdapterCompositionPolicy, + depends: ["openmct"] + } + ], + runs: [ + { + implementation: AlternateCompositionInitializer, + depends: ["openmct"] + } + ], + licenses: [ + { + "name": "almond", + "version": "0.3.3", + "description": "Lightweight RequireJS replacement for builds", + "author": "jQuery Foundation", + "website": "https://github.com/requirejs/almond", + "copyright": "Copyright jQuery Foundation and other contributors, https://jquery.org/", + "license": "license-mit", + "link": "https://github.com/requirejs/almond/blob/master/LICENSE" + }, + { + "name": "lodash", + "version": "3.10.1", + "description": "Utility functions", + "author": "Dojo Foundation", + "website": "https://lodash.com", + "copyright": "Copyright 2012-2015 The Dojo Foundation", + "license": "license-mit", + "link": "https://raw.githubusercontent.com/lodash/lodash/3.10.1/LICENSE" + }, + { + "name": "EventEmitter3", + "version": "1.2.0", + "description": "Event-driven programming support", + "author": "Arnout Kazemier", + "website": "https://github.com/primus/eventemitter3", + "copyright": "Copyright (c) 2014 Arnout Kazemier", + "license": "license-mit", + "link": "https://github.com/primus/eventemitter3/blob/1.2.0/LICENSE" + } + ] + } + }); +}); diff --git a/src/adapter/capabilities/APICapabilityDecorator.js b/src/adapter/capabilities/APICapabilityDecorator.js new file mode 100644 index 00000000000..01bd6a32efa --- /dev/null +++ b/src/adapter/capabilities/APICapabilityDecorator.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + './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; + +}); diff --git a/src/adapter/capabilities/AlternateCompositionCapability.js b/src/adapter/capabilities/AlternateCompositionCapability.js new file mode 100644 index 00000000000..20cceb9d10e --- /dev/null +++ b/src/adapter/capabilities/AlternateCompositionCapability.js @@ -0,0 +1,107 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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' +], function (objectUtils) { + function AlternateCompositionCapability($injector, domainObject) { + this.domainObject = domainObject; + this.getDependencies = function () { + this.instantiate = $injector.get("instantiate"); + this.contextualize = $injector.get("contextualize"); + this.getDependencies = undefined; + this.openmct = $injector.get("openmct"); + }.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() + ); + + if (this.getDependencies) { + this.getDependencies(); + } + + var collection = this.openmct.composition.get(newFormatDO); + return collection.load() + .then(function (children) { + collection.destroy(); + return children.map(this.contextualizeChild, this); + }.bind(this)); + }; + + AlternateCompositionCapability.appliesTo = function (model) { + // Will get replaced by a runs exception to properly + // bind to running openmct instance + return false; + }; + + return AlternateCompositionCapability; + } +); diff --git a/src/adapter/capabilities/synchronizeMutationCapability.js b/src/adapter/capabilities/synchronizeMutationCapability.js new file mode 100644 index 00000000000..e2aa14a96a8 --- /dev/null +++ b/src/adapter/capabilities/synchronizeMutationCapability.js @@ -0,0 +1,49 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([ + +], function ( + +) { + + /** + * 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; +}); diff --git a/src/adapter/directives/MCTView.js b/src/adapter/directives/MCTView.js new file mode 100644 index 00000000000..21306d0bf5d --- /dev/null +++ b/src/adapter/directives/MCTView.js @@ -0,0 +1,87 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + '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; +}); diff --git a/src/adapter/directives/Region.js b/src/adapter/directives/Region.js new file mode 100644 index 00000000000..dcd3305acd3 --- /dev/null +++ b/src/adapter/directives/Region.js @@ -0,0 +1,45 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([], function () { + function 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; +}); diff --git a/src/adapter/policies/AdapterCompositionPolicy.js b/src/adapter/policies/AdapterCompositionPolicy.js new file mode 100644 index 00000000000..865b08cb59b --- /dev/null +++ b/src/adapter/policies/AdapterCompositionPolicy.js @@ -0,0 +1,42 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([], function () { + function AdapterCompositionPolicy(openmct) { + this.openmct = openmct; + } + + AdapterCompositionPolicy.prototype.allow = function ( + containerType, + childType + ) { + var containerObject = containerType.getInitialModel(); + var childObject = childType.getInitialModel(); + + return this.openmct.composition.checkPolicy( + containerObject, + childObject + ); + }; + + return AdapterCompositionPolicy; +}); diff --git a/src/adapter/runs/AlternateCompositionInitializer.js b/src/adapter/runs/AlternateCompositionInitializer.js new file mode 100644 index 00000000000..c9169ab55b2 --- /dev/null +++ b/src/adapter/runs/AlternateCompositionInitializer.js @@ -0,0 +1,36 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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([ + '../capabilities/AlternateCompositionCapability' +], function (AlternateCompositionCapability) { + // Present to work around the need for openmct to be used + // from AlternateCompositionCapability.appliesTo, even though it + // cannot be injected. + function AlternateCompositionInitializer(openmct) { + AlternateCompositionCapability.appliesTo = function (model) { + return !model.composition && !!openmct.composition.get(model); + }; + } + + return AlternateCompositionInitializer; +}); diff --git a/src/adapter/services/Instantiate.js b/src/adapter/services/Instantiate.js new file mode 100644 index 00000000000..3b4c1907050 --- /dev/null +++ b/src/adapter/services/Instantiate.js @@ -0,0 +1,49 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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; + } +); diff --git a/src/adapter/templates/edit-object-replacement.html b/src/adapter/templates/edit-object-replacement.html new file mode 100644 index 00000000000..f8fc33ca07e --- /dev/null +++ b/src/adapter/templates/edit-object-replacement.html @@ -0,0 +1,46 @@ +
+
+
+ + + +
+
+ + + + + +
+
+
+
+ +
+ + + + +
+ + +
+
+
diff --git a/platform/features/conductor-v2/conductor/src/TimeConductor.js b/src/api/TimeConductor.js similarity index 75% rename from platform/features/conductor-v2/conductor/src/TimeConductor.js rename to src/api/TimeConductor.js index f5940831002..ed5c2285518 100644 --- a/platform/features/conductor-v2/conductor/src/TimeConductor.js +++ b/src/api/TimeConductor.js @@ -1,9 +1,9 @@ /***************************************************************************** - * Open MCT Web, Copyright (c) 2014-2015, United States Government + * Open MCT, Copyright (c) 2014-2016, 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 + * Open MCT 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. @@ -14,7 +14,7 @@ * License for the specific language governing permissions and limitations * under the License. * - * Open MCT Web includes source code licensed under additional open source + * Open MCT 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. @@ -32,7 +32,8 @@ define(['EventEmitter'], function (EventEmitter) { * The TimeConductor extends the EventEmitter class. A number of events are * fired when properties of the time conductor change, which are * documented below. - * @constructor + * @interface + * @memberof module:openmct */ function TimeConductor() { EventEmitter.call(this); @@ -58,6 +59,8 @@ define(['EventEmitter'], function (EventEmitter) { * 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 + * @memberof module:openmct.TimeConductor# + * @method validateBounds */ TimeConductor.prototype.validateBounds = function (bounds) { if ((bounds.start === undefined) || @@ -72,23 +75,32 @@ define(['EventEmitter'], function (EventEmitter) { 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 + * @fires module:openmct.TimeConductor~follow * @param {boolean} followMode * @returns {boolean} + * @memberof module:openmct.TimeConductor# + * @method follow */ 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. + * The TimeConductor has toggled into or out of follow mode. + * @event follow + * @memberof module:openmct.TimeConductor~ * @property {boolean} followMode true if follow mode is - * enabled, otherwise false. + * enabled, otherwise false. */ this.emit('follow', this.followMode); } @@ -99,15 +111,19 @@ define(['EventEmitter'], function (EventEmitter) { * @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. + * @memberof module:openmct.TimeConductor~ */ + /** * Get or set the start and end time of the time conductor. Basic validation * of bounds is performed. * - * @param {TimeConductorBounds} newBounds + * @param {module:openmct.TimeConductorBounds~TimeConductorBounds} newBounds * @throws {Error} Validation error - * @fires TimeConductor#bounds - * @returns {TimeConductorBounds} + * @fires module:openmct.TimeConductor~bounds + * @returns {module:openmct.TimeConductorBounds~TimeConductorBounds} + * @memberof module:openmct.TimeConductor# + * @method bounds */ TimeConductor.prototype.bounds = function (newBounds) { if (arguments.length > 0) { @@ -118,8 +134,9 @@ define(['EventEmitter'], function (EventEmitter) { //Create a copy to avoid direct mutation of conductor bounds this.boundsVal = JSON.parse(JSON.stringify(newBounds)); /** - * @event TimeConductor#bounds The start time, end time, or - * both have been updated + * The start time, end time, or both have been updated. + * @event bounds + * @memberof module:openmct.TimeConductor~ * @property {TimeConductorBounds} bounds */ this.emit('bounds', this.boundsVal); @@ -139,17 +156,21 @@ define(['EventEmitter'], function (EventEmitter) { * 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 + * @param {module:openmct.TimeConductor~TimeConductorBounds} bounds + * @fires module:openmct.TimeConductor~timeSystem * @returns {TimeSystem} The currently applied time system + * @memberof module:openmct.TimeConductor# + * @method timeSystem */ TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) { if (arguments.length >= 2) { this.system = newTimeSystem; /** - * @event TimeConductor#timeSystem The time system used by the time + * 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 + * followed by a bounds event specifying new query bounds. + * + * @event module:openmct.TimeConductor~timeSystem * @property {TimeSystem} The value of the currently applied * Time System * */ @@ -164,21 +185,22 @@ define(['EventEmitter'], function (EventEmitter) { /** * 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. The time of interest can + * time conductor or from other views.The time of interest can * effectively be unset by assigning a value of 'undefined'. - * @fires TimeConductor#timeOfInterest - * @param {number | undefined} newTOI A new time of interest, represented - * as a - * number that is valid in the current time system. - * @returns {number | undefined} the current time of interest + * @fires module:openmct.TimeConductor~timeOfInterest + * @param newTOI + * @returns {number} the current time of interest + * @memberof module:openmct.TimeConductor# + * @method timeOfInterest */ TimeConductor.prototype.timeOfInterest = function (newTOI) { if (arguments.length > 0) { this.toi = newTOI; /** - * @event TimeConductor#timeOfInterest The Time of Interest has - * changed. - * @property {number | undefined} Current time of interest + * The Time of Interest has moved. + * @event timeOfInterest + * @memberof module:openmct.TimeConductor~ + * @property {number} Current time of interest */ this.emit('timeOfInterest', this.toi); } diff --git a/platform/features/conductor-v2/conductor/src/TimeConductorSpec.js b/src/api/TimeConductorSpec.js similarity index 88% rename from platform/features/conductor-v2/conductor/src/TimeConductorSpec.js rename to src/api/TimeConductorSpec.js index 7701f5e9b4e..536667a6480 100644 --- a/platform/features/conductor-v2/conductor/src/TimeConductorSpec.js +++ b/src/api/TimeConductorSpec.js @@ -1,9 +1,9 @@ /***************************************************************************** - * Open MCT Web, Copyright (c) 2014-2015, United States Government + * Open MCT, Copyright (c) 2014-2016, 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 + * Open MCT 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. @@ -14,7 +14,7 @@ * License for the specific language governing permissions and limitations * under the License. * - * Open MCT Web includes source code licensed under additional open source + * Open MCT 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. @@ -50,21 +50,21 @@ define(['./TimeConductor'], function (TimeConductor) { it("Allows setting of valid bounds", function () { bounds = {start: 0, end: 1}; - expect(tc.bounds()).not.toEqual(bounds); + expect(tc.bounds()).not.toBe(bounds); expect(tc.bounds.bind(tc, bounds)).not.toThrow(); - expect(tc.bounds()).toEqual(bounds); + expect(tc.bounds()).toBe(bounds); }); it("Disallows setting of invalid bounds", function () { bounds = {start: 1, end: 0}; - expect(tc.bounds()).not.toEqual(bounds); + expect(tc.bounds()).not.toBe(bounds); expect(tc.bounds.bind(tc, bounds)).toThrow(); - expect(tc.bounds()).not.toEqual(bounds); + expect(tc.bounds()).not.toBe(bounds); bounds = {start: 1}; - expect(tc.bounds()).not.toEqual(bounds); + expect(tc.bounds()).not.toBe(bounds); expect(tc.bounds.bind(tc, bounds)).toThrow(); - expect(tc.bounds()).not.toEqual(bounds); + expect(tc.bounds()).not.toBe(bounds); }); it("Allows setting of time system with bounds", function () { diff --git a/src/api/Type.js b/src/api/Type.js new file mode 100644 index 00000000000..717e417b218 --- /dev/null +++ b/src/api/Type.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define(function () { + /** + * @typedef TypeDefinition + * @memberof module:openmct.Type~ + * @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) + */ + + /** + * A Type describes a kind of domain object that may appear or be + * created within Open MCT. + * + * @param {module:opemct.Type~TypeDefinition} definition + * @class Type + * @memberof module:openmct + */ + 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 + * @memberof module:openmct.Type# + * @method check + */ + Type.prototype.check = function (domainObject) { + // Depends on assignment from MCT. + return domainObject.type === this.key; + }; + + return Type; +}); diff --git a/src/api/api.js b/src/api/api.js new file mode 100644 index 00000000000..f68460578b3 --- /dev/null +++ b/src/api/api.js @@ -0,0 +1,52 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + './Type', + './TimeConductor', + './objects/ObjectAPI', + './composition/CompositionAPI', + './types/TypeRegistry', + './ui/Dialog', + './ui/GestureAPI', + './telemetry/TelemetryAPI' +], function ( + Type, + TimeConductor, + ObjectAPI, + CompositionAPI, + TypeRegistry, + Dialog, + GestureAPI, + TelemetryAPI +) { + return { + Type: Type, + TimeConductor: TimeConductor, + ObjectAPI: ObjectAPI, + CompositionAPI: CompositionAPI, + Dialog: Dialog, + TypeRegistry: TypeRegistry, + GestureAPI: GestureAPI, + TelemetryAPI: TelemetryAPI + }; +}); diff --git a/src/api/composition/CompositionAPI.js b/src/api/composition/CompositionAPI.js new file mode 100644 index 00000000000..31073bd956d --- /dev/null +++ b/src/api/composition/CompositionAPI.js @@ -0,0 +1,136 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + 'lodash', + 'EventEmitter', + './DefaultCompositionProvider', + './CompositionCollection' +], function ( + _, + EventEmitter, + DefaultCompositionProvider, + CompositionCollection +) { + /** + * An interface for interacting with the composition of domain objects. + * The composition of a domain object is the list of other domain objects + * it "contains" (for instance, that should be displayed beneath it + * in the tree.) + * + * @interface CompositionAPI + * @returns {module:openmct.CompositionCollection} + * @memberof module:openmct + */ + function CompositionAPI() { + this.registry = []; + this.policies = []; + this.addProvider(new DefaultCompositionProvider()); + } + + /** + * Add a composition provider. + * + * Plugins can add new composition providers to change the loading + * behavior for certain domain objects. + * + * @method addProvider + * @param {module:openmct.CompositionProvider} provider the provider to add + * @memberof module:openmct.CompositionAPI# + */ + CompositionAPI.prototype.addProvider = function (provider) { + this.registry.unshift(provider); + }; + + /** + * Retrieve the composition (if any) of this domain object. + * + * @method get + * @returns {module:openmct.CompositionCollection} + * @memberof module:openmct.CompositionAPI# + */ + CompositionAPI.prototype.get = function (domainObject) { + var provider = _.find(this.registry, function (p) { + return p.appliesTo(domainObject); + }); + + if (!provider) { + return; + } + + return new CompositionCollection(domainObject, provider); + }; + + /** + * A composition policy is a function which either allows or disallows + * placing one object in another's composition. + * + * Open MCT's policy model requires consensus, so any one policy may + * reject composition by returning false. As such, policies should + * generally be written to return true in the default case. + * + * @callback CompositionPolicy + * @memberof module:openmct.CompositionAPI~ + * @param {module:openmct.DomainObject} containingObject the object which + * would act as a container + * @param {module:openmct.DomainObject} containedObject the object which + * would be contained + * @returns {boolean} false if this composition should be disallowed + */ + + /** + * Add a composition policy. Composition policies may disallow domain + * objects from containing other domain objects. + * + * @method addPolicy + * @param {module:openmct.CompositionAPI~CompositionPolicy} policy + * the policy to add + * @memberof module:openmct.CompositionAPI# + */ + CompositionAPI.prototype.addPolicy = function (policy) { + this.policies.push(policy); + }; + + /** + * Check whether or not a domain object is allowed to contain another + * domain object. + * + * @private + * @method checkPolicy + * @param {module:openmct.DomainObject} containingObject the object which + * would act as a container + * @param {module:openmct.DomainObject} containedObject the object which + * would be contained + * @returns {boolean} false if this composition should be disallowed + + * @param {module:openmct.CompositionAPI~CompositionPolicy} policy + * the policy to add + * @memberof module:openmct.CompositionAPI# + */ + CompositionAPI.prototype.checkPolicy = function (container, containee) { + return this.policies.every(function (policy) { + return policy(container, containee); + }); + }; + + return CompositionAPI; +}); diff --git a/src/api/composition/CompositionCollection.js b/src/api/composition/CompositionCollection.js new file mode 100644 index 00000000000..1d0d0669d5e --- /dev/null +++ b/src/api/composition/CompositionCollection.js @@ -0,0 +1,231 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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', + 'lodash', + '../objects/object-utils' +], function ( + EventEmitter, + _, + objectUtils +) { + + + /** + * A CompositionCollection represents the list of domain objects contained + * by another domain object. It provides methods for loading this + * list asynchronously, and for modifying this list. + * + * @interface CompositionCollection + * @param {module:openmct.DomainObject} domainObject the domain object + * whose composition will be contained + * @param {module:openmct.CompositionProvider} provider the provider + * to use to retrieve other domain objects + * @param {module:openmct.CompositionAPI} api the composition API, for + * policy checks + * @memberof module:openmct + * @augments EventEmitter + */ + function CompositionCollection(domainObject, provider, api) { + EventEmitter.call(this); + this.domainObject = domainObject; + this.provider = provider; + this.api = api; + 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); + }; + + /** + * Get the index of a domain object within this composition. If the + * domain object is not contained here, -1 will be returned. + * + * A call to [load]{@link module:openmct.CompositionCollection#load} + * must have resolved before using this method. + * + * @param {module:openmct.DomainObject} child the domain object for which + * an index should be retrieved + * @returns {number} the index of that domain object + * @memberof module:openmct.CompositionCollection# + * @name indexOf + */ + CompositionCollection.prototype.indexOf = function (child) { + return _.findIndex(this.loadedChildren, function (other) { + return objectUtils.equals(child, other); + }); + }; + + /** + * Get the index of a domain object within this composition. + * + * A call to [load]{@link module:openmct.CompositionCollection#load} + * must have resolved before using this method. + * + * @param {module:openmct.DomainObject} child the domain object for which + * containment should be checked + * @returns {boolean} true if the domain object is contained here + * @memberof module:openmct.CompositionCollection# + * @name contains + */ + CompositionCollection.prototype.contains = function (child) { + return this.indexOf(child) !== -1; + }; + + /** + * Check if a domain object can be added to this composition. + * + * @param {module:openmct.DomainObject} child the domain object to add + * @memberof module:openmct.CompositionCollection# + * @name canContain + */ + CompositionCollection.prototype.canContain = function (domainObject) { + return this.api.checkPolicy(this.domainObject, domainObject); + }; + + /** + * Add a domain object to this composition. + * + * A call to [load]{@link module:openmct.CompositionCollection#load} + * must have resolved before using this method. + * + * @param {module:openmct.DomainObject} child the domain object to add + * @param {boolean} skipMutate true if the underlying provider should + * not be updated + * @memberof module:openmct.CompositionCollection# + * @name add + */ + CompositionCollection.prototype.add = function (child, skipMutate) { + if (!this.loadedChildren) { + 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.loadedChildren.push(child); + this.emit('add', child); + if (!skipMutate) { + // add after we have added. + this.provider.add(this.domainObject, child); + } + }; + + /** + * Load the domain objects in this composition. + * + * @returns {Promise.>} a promise for + * the domain objects in this composition + * @memberof {module:openmct.CompositionCollection#} + * @name load + */ + CompositionCollection.prototype.load = function () { + return this.provider.load(this.domainObject) + .then(function (children) { + this.loadedChildren = []; + children.map(function (c) { + this.add(c, true); + }, this); + this.emit('load'); + return this.loadedChildren.slice(); + }.bind(this)); + }; + + /** + * Remove a domain object from this composition. + * + * A call to [load]{@link module:openmct.CompositionCollection#load} + * must have resolved before using this method. + * + * @param {module:openmct.DomainObject} child the domain object to remove + * @param {boolean} skipMutate true if the underlying provider should + * not be updated + * @memberof module:openmct.CompositionCollection# + * @name remove + */ + 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.loadedChildren.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); + } + }; + + /** + * Stop using this composition collection. This will release any resources + * associated with this collection. + * @name destroy + * @memberof module:openmct.CompositionCollection# + */ + 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; +}); diff --git a/src/api/composition/DefaultCompositionProvider.js b/src/api/composition/DefaultCompositionProvider.js new file mode 100644 index 00000000000..df048379a5f --- /dev/null +++ b/src/api/composition/DefaultCompositionProvider.js @@ -0,0 +1,150 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + 'lodash', + 'EventEmitter', + '../objects/ObjectAPI', + '../objects/object-utils' +], function ( + _, + EventEmitter, + ObjectAPI, + objectUtils +) { + /** + * A CompositionProvider provides the underlying implementation of + * composition-related behavior for certain types of domain object. + * + * @interface CompositionProvider + * @memberof module:openmct + * @augments EventEmitter + */ + + function makeEventName(domainObject, event) { + return event + ':' + objectUtils.makeKeyString(domainObject.key); + } + + function DefaultCompositionProvider() { + EventEmitter.call(this); + } + + DefaultCompositionProvider.prototype = + Object.create(EventEmitter.prototype); + + /** + * Check if this provider should be used to load composition for a + * particular domain object. + * @param {module:openmct.DomainObject} domainObject the domain object + * to check + * @returns {boolean} true if this provider can provide + * composition for a given domain object + * @memberof module:openmct.CompositionProvider# + * @method appliesTo + */ + DefaultCompositionProvider.prototype.appliesTo = function (domainObject) { + return !!domainObject.composition; + }; + + /** + * Load any domain objects contained in the composition of this domain + * object. + * @param {module:openmct.DomainObjcet} domainObject the domain object + * for which to load composition + * @returns {Promise.>} a promise for + * the domain objects in this composition + * @memberof module:openmct.CompositionProvider# + * @method load + */ + 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 + ); + }; + + /** + * Remove a domain object from another domain object's composition. + * + * This method is optional; if not present, adding to a domain object's + * composition using this provider will be disallowed. + * + * @param {module:openmct.DomainObject} domainObject the domain object + * which should have its composition modified + * @param {module:openmct.DomainObject} child the domain object to remove + * @memberof module:openmct.CompositionProvider# + * @method remove + */ + 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); + }; + + /** + * Add a domain object to another domain object's composition. + * + * This method is optional; if not present, adding to a domain object's + * composition using this provider will be disallowed. + * + * @param {module:openmct.DomainObject} domainObject the domain object + * which should have its composition modified + * @param {module:openmct.DomainObject} child the domain object to add + * @memberof module:openmct.CompositionProvider# + * @method add + */ + 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; +}); diff --git a/src/api/objects/LegacyObjectAPIInterceptor.js b/src/api/objects/LegacyObjectAPIInterceptor.js new file mode 100644 index 00000000000..88758710e4c --- /dev/null +++ b/src/api/objects/LegacyObjectAPIInterceptor.js @@ -0,0 +1,128 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + './object-utils', + './objectEventEmitter' +], function ( + utils, + 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 handleLegacyMutation; + + 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); + + 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; + + 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(openmct, ROOTS, instantiate, topic, objectService) { + this.getObjects = function (keys) { + var results = {}, + promises = keys.map(function (keyString) { + var key = utils.parseKeyString(keyString); + return openmct.objects.get(key) + .then(function (object) { + object = utils.toOldFormat(object); + results[keyString] = instantiate(object, keyString); + }); + }); + + return Promise.all(promises) + .then(function () { + return results; + }); + }; + + openmct.objects.supersecretSetFallbackProvider( + new ObjectServiceProvider(objectService, instantiate, topic) + ); + + ROOTS.forEach(function (r) { + openmct.objects.addRoot(utils.parseKeyString(r.id)); + }); + + return this; + } + + return LegacyObjectAPIInterceptor; +}); diff --git a/src/api/objects/MutableObject.js b/src/api/objects/MutableObject.js new file mode 100644 index 00000000000..003bc2c5b90 --- /dev/null +++ b/src/api/objects/MutableObject.js @@ -0,0 +1,90 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + 'lodash', + './objectEventEmitter' +], function ( + _, + objectEventEmitter +) { + var ANY_OBJECT_EVENT = "mutation"; + + /** + * The MutableObject wraps a DomainObject and provides getters and + * setters for + * @param eventEmitter + * @param object + * @interface MutableObject + */ + 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(); + }); + }; + + /** + * Observe changes to this domain object. + * @param {string} path the property to observe + * @param {Function} callback a callback to invoke when new values for + * this property are observed + * @method on + * @memberof module:openmct.MutableObject# + */ + 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)); + }; + + /** + * Modify this domain object. + * @param {string} path the property to modify + * @param {*} value the new value for this property + * @method set + * @memberof module:openmct.MutableObject# + */ + 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; +}); diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js new file mode 100644 index 00000000000..eb67c7adbdb --- /dev/null +++ b/src/api/objects/ObjectAPI.js @@ -0,0 +1,234 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + 'lodash', + './object-utils', + './MutableObject' +], function ( + _, + utils, + MutableObject +) { + + + /** + * Utilities for loading, saving, and manipulating domain objects. + * @interface ObjectAPI + * @memberof module:openmct + * @implements {module:openmct.ObjectProvider} + */ + + function ObjectAPI() { + this.providers = {}; + this.rootRegistry = []; + this.rootProvider = { + 'get': function () { + return Promise.resolve({ + name: 'The root object', + type: 'root', + composition: this.rootRegistry + }); + }.bind(this) + }; + } + + ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) { + this.fallbackProvider = p; + }; + + // Retrieve the provider for a given key. + ObjectAPI.prototype.getProvider = function (key) { + if (key.identifier === 'ROOT') { + return this.rootProvider; + } + return this.providers[key.namespace] || this.fallbackProvider; + }; + + + /** + * Register a new object provider for a particular namespace. + * + * @param {string} namespace the namespace for which to provide objects + * @param {module:openmct.ObjectProvider} provider the provider which + * will handle loading domain objects from this namespace + * @memberof {module:openmct.ObjectAPI#} + * @name addProvider + */ + ObjectAPI.prototype.addProvider = function (namespace, provider) { + this.providers[namespace] = provider; + }; + + /** + * Provides the ability to read, write, and delete domain objects. + * + * When registering a new object provider, all methods on this interface + * are optional. + * + * @interface ObjectProvider + * @memberof module:openmct + */ + + /** + * Save this domain object in its current state. + * + * @method save + * @memberof module:openmct.ObjectProvider# + * @param {module:openmct.DomainObject} domainObject the domain object to + * save + * @returns {Promise} a promise which will resolve when the domain object + * has been saved, or be rejected if it cannot be saved + */ + + /** + * Delete this domain object. + * + * @method delete + * @memberof module:openmct.ObjectProvider# + * @param {module:openmct.DomainObject} domainObject the domain object to + * delete + * @returns {Promise} a promise which will resolve when the domain object + * has been deleted, or be rejected if it cannot be deleted + */ + + /** + * Get a domain object. + * + * @method get + * @memberof module:openmct.ObjectProvider# + * @param {string} key the key for the domain object to load + * @returns {Promise} a promise which will resolve when the domain object + * has been saved, or be rejected if it cannot be saved + */ + + [ + 'save', + 'delete', + 'get' + ].forEach(function (method) { + ObjectAPI.prototype[method] = function () { + var key = arguments[0], + provider = this.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); + }; + }); + + /** + * Add a root-level object. + * @param {module:openmct.DomainObject} domainObject the root-level object + * to add. + * @method addRoot + * @memberof module:openmct.ObjectAPI# + */ + ObjectAPI.prototype.addRoot = function (key) { + this.rootRegistry.unshift(key); + }; + + /** + * Remove a root-level object. + * @param {module:openmct.ObjectAPI~Identifier} id the identifier of the + * root-level object to remove. + * @method removeRoot + * @memberof module:openmct.ObjectAPI# + */ + ObjectAPI.prototype.removeRoot = function (key) { + this.rootRegistry = this.rootRegistry.filter(function (k) { + return ( + k.identifier !== key.identifier || + k.namespace !== key.namespace + ); + }); + }; + + /** + * Modify a domain object. + * @param {module:openmct.DomainObject} object the object to mutate + * @param {string} path the property to modify + * @param {*} value the new value for this property + * @method mutate + * @memberof module:openmct.ObjectAPI# + */ + ObjectAPI.prototype.mutate = function (domainObject, path, value) { + return new MutableObject(domainObject).set(path, value); + }; + + /** + * Observe changes to a domain object. + * @param {module:openmct.DomainObject} object the object to observe + * @param {string} path the property to observe + * @param {Function} callback a callback to invoke when new values for + * this property are observed + * @method observe + * @memberof module:openmct.ObjectAPI# + */ + ObjectAPI.prototype.observe = function (domainObject, path, callback) { + var mutableObject = new MutableObject(domainObject); + mutableObject.on(path, callback); + return mutableObject.stopListening.bind(mutableObject); + }; + + /** + * Uniquely identifies a domain object. + * + * @typedef Identifier + * @memberof module:openmct.ObjectAPI~ + * @property {string} namespace the namespace to/from which this domain + * object should be loaded/stored. + * @property {string} key a unique identifier for the domain object + * within that namespace + */ + + /** + * A domain object is an entity of relevance to a user's workflow, that + * should appear as a distinct and meaningful object within the user + * interface. Examples of domain objects are folders, telemetry sensors, + * and so forth. + * + * A few common properties are defined for domain objects. Beyond these, + * individual types of domain objects may add more as they see fit. + * + * @property {module:openmct.ObjectAPI~Identifier} identifier a key/namespace pair which + * uniquely identifies this domain object + * @property {string} type the type of domain object + * @property {string} name the human-readable name for this domain object + * @property {string} [creator] the user name of the creator of this domain + * object + * @property {number} [modified] the time, in milliseconds since the UNIX + * epoch, at which this domain object was last modified + * @property {module:openmct.ObjectAPI~Identifier[]} [composition] if + * present, this will be used by the default composition provider + * to load domain objects + * @typedef DomainObject + * @memberof module:openmct + */ + + return ObjectAPI; +}); diff --git a/src/api/objects/bundle.js b/src/api/objects/bundle.js new file mode 100644 index 00000000000..83a865b84ed --- /dev/null +++ b/src/api/objects/bundle.js @@ -0,0 +1,51 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define([ + './LegacyObjectAPIInterceptor', + 'legacyRegistry' +], function ( + LegacyObjectAPIInterceptor, + legacyRegistry +) { + legacyRegistry.register('src/api/objects', { + name: 'Object API', + description: 'The public Objects API', + extensions: { + components: [ + { + provides: "objectService", + type: "decorator", + priority: "mandatory", + implementation: LegacyObjectAPIInterceptor, + depends: [ + "openmct", + "roots[]", + "instantiate", + "topic" + ] + } + ] + } + }); +}); diff --git a/src/api/objects/object-utils.js b/src/api/objects/object-utils.js new file mode 100644 index 00000000000..f749a8110b4 --- /dev/null +++ b/src/api/objects/object-utils.js @@ -0,0 +1,109 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([ + +], function ( + +) { + + // take a key string and turn it into a key object + // 'scratch:root' ==> {namespace: 'scratch', identifier: 'root'} + var parseKeyString = function (key) { + if (typeof key === 'object') { + return key; + } + var namespace = '', + identifier = key; + for (var i = 0, escaped = false; i < key.length; i++) { + if (escaped) { + escaped = false; + namespace += key[i]; + } else { + if (key[i] === "\\") { + escaped = true; + } else if (key[i] === ":") { + // namespace = key.slice(0, i); + identifier = key.slice(i + 1); + break; + } + namespace += key[i]; + } + } + + if (key === namespace) { + namespace = ''; + } + + return { + namespace: namespace, + identifier: identifier + }; + }; + + // take a key and turn it into a key string + // {namespace: 'scratch', identifier: 'root'} ==> 'scratch:root' + var makeKeyString = function (key) { + if (typeof key === 'string') { + return key; + } + if (!key.namespace) { + return key.identifier; + } + return [ + key.namespace.replace(':', '\\:'), + key.identifier.replace(':', '\\:') + ].join(':'); + }; + + // Converts composition to use key strings instead of keys + var toOldFormat = function (model) { + model = JSON.parse(JSON.stringify(model)); + delete model.key; + if (model.composition) { + model.composition = model.composition.map(makeKeyString); + } + return model; + }; + + // converts composition to use keys instead of key strings + var toNewFormat = function (model, key) { + model = JSON.parse(JSON.stringify(model)); + model.key = key; + if (model.composition) { + model.composition = model.composition.map(parseKeyString); + } + return model; + }; + + var equals = function (a, b) { + return makeKeyString(a.key) === makeKeyString(b.key); + }; + + return { + toOldFormat: toOldFormat, + toNewFormat: toNewFormat, + makeKeyString: makeKeyString, + parseKeyString: parseKeyString, + equals: equals + }; +}); diff --git a/src/api/objects/objectEventEmitter.js b/src/api/objects/objectEventEmitter.js new file mode 100644 index 00000000000..8f8bf5d3258 --- /dev/null +++ b/src/api/objects/objectEventEmitter.js @@ -0,0 +1,32 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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 +) { + /** + * Provides a singleton event bus for sharing between objects. + */ + return new EventEmitter(); +}); diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js new file mode 100644 index 00000000000..6e310b1aa8f --- /dev/null +++ b/src/api/telemetry/TelemetryAPI.js @@ -0,0 +1,319 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + 'lodash', + 'EventEmitter' +], function ( + _, + EventEmitter +) { + /** + * A LimitEvaluator may be used to detect when telemetry values + * have exceeded nominal conditions. + * + * @interface LimitEvaluator + * @memberof module:openmct.TelemetryAPI~ + */ + + /** + * Check for any limit violations associated with a telemetry datum. + * @method evaluate + * @param {*} datum the telemetry datum to evaluate + * @param {TelemetryProperty} the property to check for limit violations + * @memberof module:openmct.TelemetryAPI~LimitEvaluator + * @returns {module:openmct.TelemetryAPI~LimitViolation} metadata about + * the limit violation, or undefined if a value is within limits + */ + + /** + * A violation of limits defined for a telemetry property. + * @typedef LimitViolation + * @memberof {module:openmct.TelemetryAPI~} + * @property {string} cssclass the class (or space-separated classes) to + * apply to display elements for values which violate this limit + * @property {string} name the human-readable name for the limit violation + */ + + /** + * A TelemetryFormatter converts telemetry values for purposes of + * display as text. + * + * @interface TelemetryFormatter + * @memberof module:openmct.TelemetryAPI~ + */ + + /** + * Retrieve the 'key' from the datum and format it accordingly to + * telemetry metadata in domain object. + * + * @method format + * @memberof module:openmct.TelemetryAPI~TelemetryFormatter# + */ + + + + + // format map is a placeholder until we figure out format service. + var FORMAT_MAP = { + generic: function (range) { + return function (datum) { + return datum[range.key]; + }; + }, + enum: function (range) { + var enumMap = _.indexBy(range.enumerations, 'value'); + return function (datum) { + try { + return enumMap[datum[range.valueKey]].text; + } catch (e) { + return datum[range.valueKey]; + } + }; + } + }; + + FORMAT_MAP.number = + FORMAT_MAP.float = + FORMAT_MAP.integer = + FORMAT_MAP.ascii = + FORMAT_MAP.generic; + + /** + * Describes a property which would be found in a datum of telemetry + * associated with a particular domain object. + * + * @typedef TelemetryProperty + * @memberof module:openmct.TelemetryAPI~ + * @property {string} key the name of the property in the datum which + * contains this telemetry value + * @property {string} name the human-readable name for this property + * @property {string} [units] the units associated with this property + * @property {boolean} [temporal] true if this property is a timestamp, or + * may be otherwise used to order telemetry in a time-like + * fashion; default is false + * @property {boolean} [numeric] true if the values for this property + * can be interpreted plainly as numbers; default is true + * @property {boolean} [enumerated] true if this property may have only + * certain specific values; default is false + * @property {string} [values] for enumerated states, an ordered list + * of possible values + */ + + /** + * Describes and bounds requests for telemetry data. + * + * @typedef TelemetryRequest + * @memberof module:openmct.TelemetryAPI~ + * @property {string} sort the key of the property to sort by. This may + * be prefixed with a "+" or a "-" sign to sort in ascending + * or descending order respectively. If no prefix is present, + * ascending order will be used. + * @property {*} start the lower bound for values of the sorting property + * @property {*} end the upper bound for values of the sorting property + * @property {string[]} strategies symbolic identifiers for strategies + * (such as `minmax`) which may be recognized by providers; + * these will be tried in order until an appropriate provider + * is found + */ + + /** + * Provides telemetry data. To connect to new data sources, new + * TelemetryProvider implementations should be + * [registered]{@link module:openmct.TelemetryAPI#addProvider}. + * + * @interface TelemetryProvider + * @memberof module:openmct.TelemetryAPI~ + */ + + + + /** + * An interface for retrieving telemetry data associated with a domain + * object. + * + * @interface TelemetryAPI + * @augments module:openmct.TelemetryAPI~TelemetryProvider + * @memberof module:openmct + */ + function TelemetryAPI() { + this.providersByStrategy = {}; + this.defaultProviders = []; + } + + /** + * Check if this provider can supply telemetry data associated with + * this domain object. + * + * @method canProvideTelemetry + * @param {module:openmct.DomainObject} domainObject the object for + * which telemetry would be provided + * @returns {boolean} true if telemetry can be provided + * @memberof module:openmct.TelemetryAPI~TelemetryProvider# + */ + TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) { + return this.defaultProviders.some(function (provider) { + return provider.canProvideTelemetry(domainObject); + }); + }; + + /** + * Register a telemetry provider with the telemetry service. This + * allows you to connect alternative telemetry sources. + * @method addProvider + * @memberof module:openmct.TelemetryAPI# + * @param {module:openmct.TelemetryAPI~TelemetryProvider} provider the new + * telemetry provider + * @param {string} [strategy] the request strategy supported by + * this provider. If omitted, this will be used as a + * default provider (when no strategy is requested or no + * matching strategy is found.) + */ + TelemetryAPI.prototype.addProvider = function (provider, strategy) { + if (!strategy) { + this.defaultProviders.push(provider); + } else { + this.providersByStrategy[strategy] = + this.providersByStrategy[strategy] || []; + this.providersByStrategy[strategy].push(provider); + } + }; + + /** + * @private + */ + TelemetryAPI.prototype.findProvider = function (domainObject, strategy) { + function supportsDomainObject(provider) { + return provider.canProvideTelemetry(domainObject); + } + + if (strategy) { + var eligibleProviders = + (this.providersByStrategy[strategy] || []) + .filter(supportsDomainObject); + if (eligibleProviders.length > 0) { + return eligibleProviders[0]; + } + } + + return this.defaultProviders.filter(supportsDomainObject)[0]; + }; + + /** + * Request historical telemetry for a domain object. + * The `options` argument allows you to specify filters + * (start, end, etc.), sort order, and strategies for retrieving + * telemetry (aggregation, latest available, etc.). + * + * @method request + * @memberof module:openmct.TelemetryAPI~TelemetryProvider# + * @param {module:openmct.DomainObject} domainObject the object + * which has associated telemetry + * @param {module:openmct.TelemetryAPI~TelemetryRequest} options + * options for this historical request + * @returns {Promise.} a promise for an array of + * telemetry data + */ + TelemetryAPI.prototype.request = function (domainObject, options) { + var provider = this.findProvider(domainObject, options.strategy); + return provider ? + provider.request(domainObject, options) : + Promise.reject([]); + }; + + /** + * Subscribe to realtime telemetry for a specific domain object. + * The callback will be called whenever data is received from a + * realtime provider. + * + * @method subscribe + * @memberof module:openmct.TelemetryAPI~TelemetryProvider# + * @param {module:openmct.DomainObject} domainObject the object + * which has associated telemetry + * @param {Function} callback the callback to invoke with new data, as + * it becomes available + * @param {module:openmct.TelemetryAPI~TelemetryRequest} options + * options for this request + * @returns {Function} a function which may be called to terminate + * the subscription + */ + + /** + * Get a list of all telemetry properties defined for this + * domain object. + * + * @param {module:openmct.DomainObject} domainObject the domain + * object for which to request telemetry + * @returns {module:openmct.TelemetryAPI~TelemetryProperty[]} + * telemetry metadata + * @method properties + * @memberof module:openmct.TelemetryAPI~TelemetryProvider# + */ + + /** + * Telemetry formatters help you format telemetry values for + * display. Under the covers, they use telemetry metadata to + * interpret your telemetry data, and then they use the format API + * to format that data for display. + * + * This method is optional. + * If a provider does not implement this method, it is presumed + * that all telemetry associated with this domain object can + * be formatted correctly by string coercion. + * + * @param {module:openmct.DomainObject} domainObject the domain + * object for which to format telemetry + * @returns {module:openmct.TelemetryAPI~TelemetryFormatter} + * @method formatter + * @memberof module:openmct.TelemetryAPI~TelemetryProvider# + */ + + /** + * Get a limit evaluator for this domain object. + * Limit Evaluators help you evaluate limit and alarm status of individual telemetry datums for display purposes without having to interact directly with the Limit API. + * + * This method is optional. + * If a provider does not implement this method, it is presumed + * that no limits are defined for this domain object's telemetry. + * + * @param {module:openmct.DomainObject} domainObject the domain + * object for which to evaluate limits + * @returns {module:openmct.TelemetryAPI~LimitEvaluator} + * @method limitEvaluator + * @memberof module:openmct.TelemetryAPI~TelemetryProvider# + */ + _.forEach({ + subscribe: undefined, + properties: [], + formatter: undefined, + limitEvaluator: undefined + }, function (defaultValue, method) { + TelemetryAPI.prototype[method] = function (domainObject) { + var provider = this.findProvider(domainObject); + return provider ? + provider[method].apply(provider, arguments) : + defaultValue; + }; + }); + + return TelemetryAPI; +}); diff --git a/src/api/telemetry/bundle.js b/src/api/telemetry/bundle.js new file mode 100644 index 00000000000..a389ebf804f --- /dev/null +++ b/src/api/telemetry/bundle.js @@ -0,0 +1,45 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + './TelemetryAPI', + 'legacyRegistry' +], function ( + TelemetryAPI, + legacyRegistry +) { + legacyRegistry.register('api/telemetry-api', { + name: 'Telemetry API', + description: 'The public Telemetry API', + extensions: { + runs: [ + { + key: "TelemetryAPI", + implementation: TelemetryAPI, + depends: [ + 'formatService' + ] + } + ] + } + }); +}); diff --git a/src/api/types/TypeRegistry.js b/src/api/types/TypeRegistry.js new file mode 100644 index 00000000000..e01bea4a9d8 --- /dev/null +++ b/src/api/types/TypeRegistry.js @@ -0,0 +1,51 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([], function () { + + /** + * A TypeRegistry maintains the definitions for different types + * that domain objects may have. + * @interface TypeRegistry + * @memberof module:openmct + */ + function TypeRegistry() { + this.types = {}; + } + + /** + * Register a new type of view. + * + * @param {string} typeKey a string identifier for this type + * @param {module:openmct.Type} type the type to add + * @method addProvider + * @memberof module:openmct.TypeRegistry# + */ + TypeRegistry.prototype.addType = function (typeKey, type) { + this.types[typeKey] = type; + }; + + + return TypeRegistry; +}); + + diff --git a/src/api/ui/Dialog.js b/src/api/ui/Dialog.js new file mode 100644 index 00000000000..63408f31abe --- /dev/null +++ b/src/api/ui/Dialog.js @@ -0,0 +1,107 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) { + + /** + * A dialog may be displayed to show blocking content to users. + * @param {module:openmct.View} view the view to show in the dialog + * @param {string} [title] the title for this dialog + * @constructor + * @memberof module:openmct + */ + function Dialog(view, title) { + this.view = view; + this.title = title; + this.showing = false; + this.enabledState = true; + } + + /** + * Display this dialog. + * @returns {Promise} a promise that will be resolved if the user + * chooses "OK", an rejected if the user chooses "cancel" + * @method show + * @memberof module:openmct.Dialog# + */ + Dialog.prototype.show = function () { + if (this.showing) { + throw new Error("Dialog already showing."); + } + + var $body = $('body'); + var $dialog = $(dialogTemplate); + var $contents = $dialog.find('.contents .editor'); + var $close = $dialog.find('.close'); + + var $ok = $dialog.find('.ok'); + var $cancel = $dialog.find('.cancel'); + + if (this.title) { + $dialog.find('.title').text(this.title); + } + + $body.append($dialog); + this.view.show($contents[0]); + this.$dialog = $dialog; + this.$ok = $ok; + this.showing = true; + + [$ok, $cancel, $close].forEach(function ($button) { + $button.on('click', this.hide.bind(this)); + }.bind(this)); + + return new Promise(function (resolve, reject) { + $ok.on('click', resolve); + $cancel.on('click', reject); + $close.on('click', reject); + }); + }; + + Dialog.prototype.hide = function () { + if (!this.showing) { + return; + } + this.$dialog.remove(); + this.view.destroy(); + this.showing = false; + }; + + /** + * Get or set the "enabled" state of the OK button for this dialog. + * @param {boolean} [state] true to enable, false to disable + * @returns {boolean} true if enabled, false if disabled + * @method enabled + * @memberof module:openmct.Dialog# + */ + Dialog.prototype.enabled = function (state) { + if (state !== undefined) { + this.enabledState = state; + if (this.showing) { + this.$ok.toggleClass('disabled', !state); + } + } + return this.enabledState; + }; + + return Dialog; +}); diff --git a/src/api/ui/GestureAPI.js b/src/api/ui/GestureAPI.js new file mode 100644 index 00000000000..211318673af --- /dev/null +++ b/src/api/ui/GestureAPI.js @@ -0,0 +1,68 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([], function () { + /** + * Allows support for common user actions to be attached to views. + * @interface GestureAPI + * @memberof module:openmct + */ + function GestureAPI(selectGesture, contextMenuGesture) { + this.selectGesture = selectGesture; + this.contextMenuGesture = contextMenuGesture; + } + + /** + * Designate an HTML element as selectable, and associated with a + * particular object. + * + * @param {HTMLElement} htmlElement the element to make selectable + * @param {*} item the object which should become selected when this + * element is clicked. + * @returns {Function} a function to remove selectability from this + * HTML element. + * @method selectable + * @memberof module:openmct.GestureAPI# + */ + GestureAPI.prototype.selectable = function (htmlElement, item) { + return this.selectGesture.apply(htmlElement, item); + }; + + + /** + * Designate an HTML element as having a context menu associated with + * the provided item. + * + * @private + * @param {HTMLElement} htmlElement the element to make selectable + * @param {*} item the object for which a context menu should appear + * @returns {Function} a function to remove this geture from this + * HTML element. + * @method selectable + * @memberof module:openmct.GestureAPI# + */ + GestureAPI.prototype.contextMenu = function (htmlElement, item) { + return this.contextMenuGesture.apply(htmlElement, item); + }; + + return GestureAPI; +}); diff --git a/src/api/ui/dialog.html b/src/api/ui/dialog.html new file mode 100644 index 00000000000..83181c5c780 --- /dev/null +++ b/src/api/ui/dialog.html @@ -0,0 +1,21 @@ +
+
+
+ x +
+
+
+
+
+
+
+
+ OK + Cancel +
+
+
+
+ + + diff --git a/src/defaultRegistry.js b/src/defaultRegistry.js new file mode 100644 index 00000000000..a4070313dcf --- /dev/null +++ b/src/defaultRegistry.js @@ -0,0 +1,141 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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([ + 'legacyRegistry', + + '../src/adapter/bundle', + '../src/api/objects/bundle', + + '../example/builtins/bundle', + '../example/composite/bundle', + '../example/eventGenerator/bundle', + '../example/export/bundle', + '../example/extensions/bundle', + '../example/forms/bundle', + '../example/generator/bundle', + '../example/identity/bundle', + '../example/imagery/bundle', + '../example/mobile/bundle', + '../example/msl/bundle', + '../example/notifications/bundle', + '../example/persistence/bundle', + '../example/plotOptions/bundle', + '../example/policy/bundle', + '../example/profiling/bundle', + '../example/scratchpad/bundle', + '../example/taxonomy/bundle', + '../example/worker/bundle', + '../example/localTimeSystem/bundle', + + '../platform/commonUI/about/bundle', + '../platform/commonUI/browse/bundle', + '../platform/commonUI/dialog/bundle', + '../platform/commonUI/edit/bundle', + '../platform/commonUI/formats/bundle', + '../platform/commonUI/general/bundle', + '../platform/commonUI/inspect/bundle', + '../platform/commonUI/mobile/bundle', + '../platform/commonUI/notification/bundle', + '../platform/commonUI/regions/bundle', + '../platform/commonUI/themes/espresso/bundle', + '../platform/commonUI/themes/snow/bundle', + '../platform/containment/bundle', + '../platform/core/bundle', + '../platform/entanglement/bundle', + '../platform/execution/bundle', + '../platform/exporters/bundle', + '../platform/features/clock/bundle', + '../platform/features/conductor/bundle', + '../platform/features/fixed/bundle', + '../platform/features/conductor-v2/conductor/bundle', + '../platform/features/conductor-v2/compatibility/bundle', + '../platform/features/conductor-v2/utcTimeSystem/bundle', + '../platform/features/imagery/bundle', + '../platform/features/layout/bundle', + '../platform/features/pages/bundle', + '../platform/features/plot/bundle', + '../platform/features/static-markup/bundle', + '../platform/features/table/bundle', + '../platform/features/timeline/bundle', + '../platform/forms/bundle', + '../platform/framework/bundle', + '../platform/framework/src/load/Bundle', + '../platform/identity/bundle', + '../platform/persistence/aggregator/bundle', + '../platform/persistence/couch/bundle', + '../platform/persistence/elastic/bundle', + '../platform/persistence/local/bundle', + '../platform/persistence/queue/bundle', + '../platform/policy/bundle', + '../platform/representation/bundle', + '../platform/search/bundle', + '../platform/status/bundle', + '../platform/telemetry/bundle' +], function (legacyRegistry) { + + var DEFAULTS = [ + 'src/adapter', + 'src/api/objects', + 'platform/framework', + 'platform/core', + 'platform/representation', + 'platform/commonUI/about', + 'platform/commonUI/browse', + 'platform/commonUI/edit', + 'platform/commonUI/dialog', + 'platform/commonUI/formats', + 'platform/commonUI/general', + 'platform/commonUI/inspect', + 'platform/commonUI/mobile', + 'platform/commonUI/themes/espresso', + 'platform/commonUI/notification', + 'platform/containment', + 'platform/execution', + 'platform/exporters', + 'platform/telemetry', + 'platform/features/clock', + 'platform/features/fixed', + 'platform/features/imagery', + 'platform/features/layout', + 'platform/features/pages', + 'platform/features/plot', + 'platform/features/timeline', + 'platform/features/table', + 'platform/forms', + 'platform/identity', + 'platform/persistence/aggregator', + 'platform/persistence/local', + 'platform/persistence/queue', + 'platform/policy', + 'platform/entanglement', + 'platform/search', + 'platform/status', + 'platform/commonUI/regions' + ]; + + DEFAULTS.forEach(function (bundlePath) { + legacyRegistry.enable(bundlePath); + }); + + return legacyRegistry; +}); diff --git a/src/end.frag b/src/end.frag new file mode 100644 index 00000000000..9746cce6b89 --- /dev/null +++ b/src/end.frag @@ -0,0 +1,2 @@ + return require('openmct'); +})); diff --git a/src/selection/ContextManager.js b/src/selection/ContextManager.js new file mode 100644 index 00000000000..1bd524dcde2 --- /dev/null +++ b/src/selection/ContextManager.js @@ -0,0 +1,78 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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(['zepto'], function ($) { + /** + * @typedef Context + * @property {*} item + * @property {HTMLElement} element + * @property {Context} parent the containing context (may be undefined) + * @memberof module:openmct + */ + + + function ContextManager() { + this.counter = 0; + this.contexts = {}; + } + + ContextManager.prototype.nextId = function () { + this.counter += 1; + return "context-" + this.counter; + }; + + ContextManager.prototype.context = function (item, htmlElement) { + var $element = $(htmlElement); + var id = $element.attr('data-context') || this.nextId(); + + $element.attr('data-context', id); + + if (this.contexts[id] && this.contexts[id].item !== item) { + this.release(htmlElement); + } + + if (!this.contexts[id]) { + var $parent = $element.closest('[data-context]'); + var parentId = $parent.attr('data-context'); + var parentContext = parentId ? this.contexts[parentId] : undefined; + this.contexts[id] = { + item: item, + element: htmlElement, + parent: parentContext + }; + } + + return this.contexts[id]; + }; + + ContextManager.prototype.release = function (htmlElement) { + var $element = $(htmlElement); + var id = $element.attr('data-context'); + + if (id) { + delete this.contexts[id]; + $element.removeAttr('data-context'); + } + }; + + return ContextManager; +}); diff --git a/src/selection/HoverGesture.js b/src/selection/HoverGesture.js new file mode 100644 index 00000000000..5c1c7ae2ad9 --- /dev/null +++ b/src/selection/HoverGesture.js @@ -0,0 +1,58 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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(['zepto'], function ($) { + function HoverGesture(hoverManager) { + this.hoverManager = hoverManager; + } + + HoverGesture.prototype.apply = function (htmlElement) { + var $element = $(htmlElement); + var hoverManager = this.hoverManager; + + function update() { + $(hoverManager.all()).removeClass('hovering'); + $(hoverManager.top()).addClass('hovering'); + } + + function enter() { + hoverManager.add(htmlElement); + update(); + } + + function leave() { + hoverManager.remove(htmlElement); + update(); + } + + $element.on('mouseenter', enter); + $element.on('mouseleave', leave); + + return function () { + leave(); + $element.off('mouseenter', enter); + $element.off('mouseleave', leave); + }.bind(this); + }; + + return HoverGesture; +}); diff --git a/src/selection/SelectGesture.js b/src/selection/SelectGesture.js new file mode 100644 index 00000000000..aaea15a4841 --- /dev/null +++ b/src/selection/SelectGesture.js @@ -0,0 +1,60 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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(['zepto'], function ($) { + function SelectGesture(selection, contextManager) { + this.selection = selection; + this.contextManager = contextManager; + } + + SelectGesture.prototype.apply = function (htmlElement, item) { + var $element = $(htmlElement); + var contextManager = this.contextManager; + var selection = this.selection; + var path = contextManager.path(item, htmlElement); + + function select() { + selection.add(path); + } + + function change() { + var selected = selection.primary(); + $element.toggleClass( + 'selected', + selected && path.matches(selected) + ); + } + + $element.addClass('selectable'); + $element.on('click', select); + selection.on('change', change); + change(); // Initialize + + return function () { + contextManager.release(htmlElement); + $element.off('click', select); + selection.off('change', change); + }; + }; + + return SelectGesture; +}); diff --git a/src/selection/Selection.js b/src/selection/Selection.js new file mode 100644 index 00000000000..e6e3c19287a --- /dev/null +++ b/src/selection/Selection.js @@ -0,0 +1,69 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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) { + + /** + * Manages selection state for Open MCT + * @private + */ + function Selection() { + EventEmitter.call(this); + this.selected = []; + } + + Selection.prototype = Object.create(EventEmitter.prototype); + + Selection.prototype.add = function (context) { + this.clear(); // Only allow single select as initial simplification + this.selected.push(context); + this.emit('change'); + }; + + Selection.prototype.remove = function (path) { + this.selected = this.selected.filter(function (otherPath) { + return !path.matches(otherPath); + }); + this.emit('change'); + }; + + Selection.prototype.contains = function (path) { + return this.selected.some(function (otherPath) { + return path.matches(otherPath); + }); + }; + + Selection.prototype.clear = function () { + this.selected = []; + this.emit('change'); + }; + + Selection.prototype.primary = function () { + return this.selected[this.selected.length - 1]; + }; + + Selection.prototype.all = function () { + return this.selected; + }; + + return Selection; +}); diff --git a/src/start.frag b/src/start.frag new file mode 100644 index 00000000000..5cd7cf2114a --- /dev/null +++ b/src/start.frag @@ -0,0 +1,39 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 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. + *****************************************************************************/ + +/** + * Open MCT https://nasa.github.io/openmct/ + * Version @@version + * Built @@timestamp + * Revision @@revision + * Branch @@branch + */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.MCT = factory(); + } +}(this, function() { diff --git a/src/ui/ViewRegistry.js b/src/ui/ViewRegistry.js new file mode 100644 index 00000000000..67bd494f4d6 --- /dev/null +++ b/src/ui/ViewRegistry.js @@ -0,0 +1,152 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([], function () { + /** + * A ViewRegistry maintains the definitions for different kinds of views + * that may occur in different places in the user interface. + * @interface ViewRegistry + * @memberof module:openmct + */ + function ViewRegistry() { + this.providers = []; + } + + + /** + * @private for platform-internal use + * @param {*} item the object to be viewed + * @returns {module:openmct.ViewProvider[]} any providers + * which can provide views of this object + */ + ViewRegistry.prototype.get = function (item) { + return this.providers.filter(function (provider) { + return provider.canView(item); + }); + }; + + /** + * Register a new type of view. + * + * @param {module:openmct.ViewProvider} provider the provider for this view + * @method addProvider + * @memberof module:openmct.ViewRegistry# + */ + ViewRegistry.prototype.addProvider = function (provider) { + this.providers.push(provider); + }; + + /** + * A View is used to provide displayable content, and to react to + * associated life cycle events. + * + * @name View + * @interface + * @memberof module:openmct + */ + + /** + * 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 + * @method show + * @memberof module:openmct.View# + */ + + /** + * 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. + * + * @method destroy + * @memberof module:openmct.View# + */ + + /** + * Exposes types of views in Open MCT. + * + * @interface ViewProvider + * @memberof module:openmct + */ + + /** + * Check if this provider can supply views for a domain object. + * + * When called by Open MCT, this may include additional arguments + * which are on the path to the object to be viewed; for instance, + * when viewing "A Folder" within "My Items", this method will be + * invoked with "A Folder" (as a domain object) as the first argument, + * and "My Items" as the second argument. + * + * @method canView + * @memberof module:openmct.ViewProvider# + * @param {module:openmct.DomainObject} domainObject the domain object + * to be viewed + * @returns {boolean} true if this domain object can be viewed using + * this provider + */ + + /** + * Provide a view of this object. + * + * When called by Open MCT, this may include additional arguments + * which are on the path to the object to be viewed; for instance, + * when viewing "A Folder" within "My Items", this method will be + * invoked with "A Folder" (as a domain object) as the first argument, + * and "My Items" as the second argument. + * + * @method view + * @memberof module:openmct.ViewProvider# + * @param {*} object the object to be viewed + * @returns {module:openmct.View} a view of this domain object + */ + + /** + * Get metadata associated with this view provider. This may be used + * to populate the user interface with options associated with this + * view provider. + * + * @method metadata + * @memberof module:openmct.ViewProvider# + * @returns {module:openmct.ViewProvider~ViewMetadata} view metadata + */ + + /** + * @typedef ViewMetadata + * @memberof module:openmct.ViewProvider~ + * @property {string} name the human-readable name of this view + * @property {string} key a machine-readable name for this view + * @property {string} [description] a longer-form description (typically + * a single sentence or short paragraph) of this kind of view + * @property {string} cssclass the CSS class to apply to labels for this + * view (to add icons, for instance) + */ + + return ViewRegistry; + +}); diff --git a/test-main.js b/test-main.js index 3ec070f999b..b300560588f 100644 --- a/test-main.js +++ b/test-main.js @@ -63,6 +63,7 @@ requirejs.config({ "text": "bower_components/text/text", "uuid": "bower_components/node-uuid/uuid", "zepto": "bower_components/zepto/zepto.min", + "lodash": "bower_components/lodash/lodash", "d3": "bower_components/d3/d3.min" }, @@ -85,6 +86,9 @@ requirejs.config({ "zepto": { "exports": "Zepto" }, + "lodash": { + "exports": "lodash" + }, "d3": { "exports": "d3" }