Compare commits
	
		
			1 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					6868bfd4e1 | 
							
								
								
									
										2
									
								
								Procfile
									
									
									
									
									
								
							
							
						
						@@ -1 +1 @@
 | 
			
		||||
web: node app.js --port $PORT --include example/localstorage
 | 
			
		||||
web: node app.js --port $PORT
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -103,6 +103,21 @@ This build will:
 | 
			
		||||
 | 
			
		||||
Run as `mvn clean install`.
 | 
			
		||||
 | 
			
		||||
### Building Documentation
 | 
			
		||||
 | 
			
		||||
Open MCT Web's documentation is generated by an
 | 
			
		||||
[npm](https://www.npmjs.com/)-based build:
 | 
			
		||||
 | 
			
		||||
* `npm install` _(only needs to run once)_
 | 
			
		||||
* `npm run docs`
 | 
			
		||||
 | 
			
		||||
Documentation will be generated in `target/docs`. Note that diagram
 | 
			
		||||
generation is dependent on having [Cairo](http://cairographics.org/download/)
 | 
			
		||||
installed; see
 | 
			
		||||
[node-canvas](https://github.com/Automattic/node-canvas#installation)'s
 | 
			
		||||
documentation for help with installation.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Glossary
 | 
			
		||||
 | 
			
		||||
Certain terms are used throughout Open MCT Web with consistent meanings
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,8 @@
 | 
			
		||||
var CONSTANTS = {
 | 
			
		||||
        DIAGRAM_WIDTH: 800,
 | 
			
		||||
        DIAGRAM_HEIGHT: 500
 | 
			
		||||
    };
 | 
			
		||||
    },
 | 
			
		||||
    TOC_HEAD = "# Table of Contents";
 | 
			
		||||
 | 
			
		||||
GLOBAL.window = GLOBAL.window ||  GLOBAL; // nomnoml expects window to be defined
 | 
			
		||||
(function () {
 | 
			
		||||
@@ -44,6 +45,7 @@ GLOBAL.window = GLOBAL.window ||  GLOBAL; // nomnoml expects window to be define
 | 
			
		||||
        split = require("split"),
 | 
			
		||||
        stream = require("stream"),
 | 
			
		||||
        nomnoml = require('nomnoml'),
 | 
			
		||||
        toc = require("markdown-toc"),
 | 
			
		||||
        Canvas = require('canvas'),
 | 
			
		||||
        options = require("minimist")(process.argv.slice(2));
 | 
			
		||||
 | 
			
		||||
@@ -110,6 +112,9 @@ GLOBAL.window = GLOBAL.window ||  GLOBAL; // nomnoml expects window to be define
 | 
			
		||||
            done();
 | 
			
		||||
        };
 | 
			
		||||
        transform._flush = function (done) {
 | 
			
		||||
            // Prepend table of contents
 | 
			
		||||
            markdown =
 | 
			
		||||
                [ TOC_HEAD, toc(markdown).content, "", markdown ].join("\n");
 | 
			
		||||
            this.push("<html><body>\n");
 | 
			
		||||
            this.push(marked(markdown));
 | 
			
		||||
            this.push("\n</body></html>\n");
 | 
			
		||||
@@ -133,8 +138,8 @@ GLOBAL.window = GLOBAL.window ||  GLOBAL; // nomnoml expects window to be define
 | 
			
		||||
        customRenderer.link = function (href, title, text) {
 | 
			
		||||
            // ...but only if they look like relative paths
 | 
			
		||||
            return (href || "").indexOf(":") === -1 && href[0] !== "/" ?
 | 
			
		||||
                renderer.link(href.replace(/\.md/, ".html"), title, text) :
 | 
			
		||||
                renderer.link.apply(renderer, arguments);
 | 
			
		||||
                    renderer.link(href.replace(/\.md/, ".html"), title, text) :
 | 
			
		||||
                    renderer.link.apply(renderer, arguments);
 | 
			
		||||
        };
 | 
			
		||||
        return customRenderer;
 | 
			
		||||
    }
 | 
			
		||||
@@ -179,13 +184,17 @@ GLOBAL.window = GLOBAL.window ||  GLOBAL; // nomnoml expects window to be define
 | 
			
		||||
    glob(options['in'] + "/**/*.@(html|css|png)", {}, function (err, files) {
 | 
			
		||||
        files.forEach(function (file) {
 | 
			
		||||
            var destination = file.replace(options['in'], options.out),
 | 
			
		||||
                destPath = path.dirname(destination);
 | 
			
		||||
 | 
			
		||||
                destPath = path.dirname(destination),
 | 
			
		||||
                streamOptions = {};
 | 
			
		||||
            if (file.match(/png$/)){
 | 
			
		||||
                streamOptions.encoding = 'binary';
 | 
			
		||||
            } else {
 | 
			
		||||
                streamOptions.encoding = 'utf8';
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            mkdirp(destPath, function (err) {
 | 
			
		||||
                fs.createReadStream(file, { encoding: 'utf8' })
 | 
			
		||||
                    .pipe(fs.createWriteStream(destination, {
 | 
			
		||||
                        encoding: 'utf8'
 | 
			
		||||
                    }));
 | 
			
		||||
                fs.createReadStream(file, streamOptions)
 | 
			
		||||
                    .pipe(fs.createWriteStream(destination, streamOptions));
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -35,16 +35,26 @@ in __any of these tiers__.
 | 
			
		||||
* _DOM_: The rendered HTML document, composed from HTML templates which
 | 
			
		||||
  have been processed by AngularJS and will be updated by AngularJS
 | 
			
		||||
  to reflect changes from the presentation layer. User interactions
 | 
			
		||||
  are initiated from here and invoke behavior in the presentation layer.
 | 
			
		||||
  are initiated from here and invoke behavior in the presentation layer. HTML 
 | 
			
		||||
  templates are written in Angular’s template syntax; see the [Angular documentation on templates](https://docs.angularjs.org/guide/templates). 
 | 
			
		||||
  These describe the page as actually seen by the user. Conceptually, 
 | 
			
		||||
  stylesheets (controlling the lookandfeel of the rendered templates) belong 
 | 
			
		||||
  in this grouping as well. 
 | 
			
		||||
* [_Presentation layer_](#presentation-layer): The presentation layer
 | 
			
		||||
  is responsible for updating (and providing information to update)
 | 
			
		||||
  the displayed state of the application. The presentation layer consists
 | 
			
		||||
  primarily of _controllers_ and _directives_. The presentation layer is
 | 
			
		||||
  concerned with inspecting the information model and preparing it for
 | 
			
		||||
  display.
 | 
			
		||||
* [_Information model_](#information-model): The information model
 | 
			
		||||
  describes the state and behavior of the objects with which the user
 | 
			
		||||
  interacts.
 | 
			
		||||
* [_Information model_](#information-model): Provides a common (within Open MCT 
 | 
			
		||||
  Web) set of interfaces for dealing with “things”  domain objects  within the 
 | 
			
		||||
  system. Userfacing concerns in a Open MCT Web application are expressed as 
 | 
			
		||||
  domain objects; examples include folders (used to organize other domain 
 | 
			
		||||
  objects), layouts (used to build displays), or telemetry points (used as 
 | 
			
		||||
  handles for streams of remote measurements.) These domain objects expose a 
 | 
			
		||||
  common set of interfaces to allow reusable user interfaces to be built in the 
 | 
			
		||||
  presentation and template tiers; the specifics of these behaviors are then 
 | 
			
		||||
  mapped to interactions with underlying services. 
 | 
			
		||||
* [_Service infrastructure_](#service-infrastructure): The service
 | 
			
		||||
  infrastructure is responsible for providing the underlying general
 | 
			
		||||
  functionality needed to support the information model. This includes
 | 
			
		||||
@@ -52,7 +62,9 @@ in __any of these tiers__.
 | 
			
		||||
  back-end.
 | 
			
		||||
* _Back-end_: The back-end is out of the scope of Open MCT Web, except
 | 
			
		||||
  for the interfaces which are utilized by adapters participating in the
 | 
			
		||||
  service infrastructure.
 | 
			
		||||
  service infrastructure. Includes the underlying persistence stores, telemetry 
 | 
			
		||||
  streams, and so forth which the Open MCT Web client is being used to interact 
 | 
			
		||||
  with.
 | 
			
		||||
 | 
			
		||||
## Application Start-up
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,9 @@
 | 
			
		||||
    Sections:
 | 
			
		||||
    <ul>
 | 
			
		||||
        <li><a href="api/">API</a></li>
 | 
			
		||||
        <li><a href="guide/">Developer Guide</a></li>
 | 
			
		||||
        <li><a href="architecture/">Architecture Overview</a></li>
 | 
			
		||||
        <li><a href="guide/">Developer Guide</a></li>
 | 
			
		||||
        <li><a href="tutorials/">Tutorials</a></li>
 | 
			
		||||
    </ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/add-task.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/bar-plot-2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 39 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/bar-plot-3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 42 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/bar-plot-4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 30 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/bar-plot.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 34 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/chrome.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 140 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/remove-task.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 11 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/telemetry-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 16 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/telemetry-2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 39 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/telemetry-3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 51 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/todo-edit.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/todo-list.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/todo-restyled.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/todo-selection.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 31 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/src/tutorials/images/todo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 43 KiB  | 
							
								
								
									
										3055
									
								
								docs/src/tutorials/index.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -34,6 +34,10 @@
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "time",
 | 
			
		||||
                            "name": "Time"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "yesterday",
 | 
			
		||||
                            "name": "Yesterday"
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    "ranges": [
 | 
			
		||||
@@ -61,4 +65,4 @@
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,8 @@
 | 
			
		||||
 * Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14.
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    ["./SinewaveTelemetry"],
 | 
			
		||||
    function (SinewaveTelemetry) {
 | 
			
		||||
    ["./SinewaveTelemetrySeries"],
 | 
			
		||||
    function (SinewaveTelemetrySeries) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -45,7 +45,7 @@ define(
 | 
			
		||||
            function generateData(request) {
 | 
			
		||||
                return {
 | 
			
		||||
                    key: request.key,
 | 
			
		||||
                    telemetry: new SinewaveTelemetry(request)
 | 
			
		||||
                    telemetry: new SinewaveTelemetrySeries(request)
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -112,4 +112,4 @@ define(
 | 
			
		||||
 | 
			
		||||
        return SinewaveTelemetryProvider;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -29,35 +29,47 @@ define(
 | 
			
		||||
    function () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var firstObservedTime = Date.now();
 | 
			
		||||
        var ONE_DAY = 60 * 60 * 24,
 | 
			
		||||
            firstObservedTime = Math.floor(Date.now() / 1000) - ONE_DAY;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function SinewaveTelemetry(request) {
 | 
			
		||||
            var latestObservedTime = Date.now(),
 | 
			
		||||
                count = Math.floor((latestObservedTime - firstObservedTime) / 1000),
 | 
			
		||||
                period = request.period || 30,
 | 
			
		||||
                generatorData = {};
 | 
			
		||||
        function SinewaveTelemetrySeries(request) {
 | 
			
		||||
            var timeOffset = (request.domain === 'yesterday') ? ONE_DAY : 0,
 | 
			
		||||
                latestTime = Math.floor(Date.now() / 1000) - timeOffset,
 | 
			
		||||
                firstTime = firstObservedTime - timeOffset,
 | 
			
		||||
                endTime = (request.end !== undefined) ?
 | 
			
		||||
                        Math.floor(request.end / 1000) : latestTime,
 | 
			
		||||
                count = Math.min(endTime, latestTime) - firstTime,
 | 
			
		||||
                period = +request.period || 30,
 | 
			
		||||
                generatorData = {},
 | 
			
		||||
                requestStart = (request.start === undefined) ? firstTime :
 | 
			
		||||
                        Math.max(Math.floor(request.start / 1000), firstTime),
 | 
			
		||||
                offset = requestStart - firstTime;
 | 
			
		||||
 | 
			
		||||
            if (request.size !== undefined) {
 | 
			
		||||
                offset = Math.max(offset, count - request.size);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            generatorData.getPointCount = function () {
 | 
			
		||||
                return count;
 | 
			
		||||
                return count - offset;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            generatorData.getDomainValue = function (i, domain) {
 | 
			
		||||
                return i * 1000 +
 | 
			
		||||
                        (domain !== 'delta' ? firstObservedTime : 0);
 | 
			
		||||
                return (i + offset) * 1000 + firstTime * 1000 -
 | 
			
		||||
                    (domain === 'yesterday' ? ONE_DAY : 0);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            generatorData.getRangeValue = function (i, range) {
 | 
			
		||||
                range = range || "sin";
 | 
			
		||||
                return Math[range](i * Math.PI * 2 / period);
 | 
			
		||||
                return Math[range]((i + offset) * Math.PI * 2 / period);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            return generatorData;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return SinewaveTelemetry;
 | 
			
		||||
        return SinewaveTelemetrySeries;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
);
 | 
			
		||||
@@ -4,7 +4,11 @@
 | 
			
		||||
            {
 | 
			
		||||
                "implementation": "WatchIndicator.js",
 | 
			
		||||
                "depends": ["$interval", "$rootScope"]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "implementation": "DigestIndicator.js",
 | 
			
		||||
                "depends": ["$interval", "$rootScope"]
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								example/profiling/src/DigestIndicator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,77 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Displays the number of digests that have occurred since the
 | 
			
		||||
         * indicator was first instantiated.
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param $interval Angular's $interval
 | 
			
		||||
         * @implements {Indicator}
 | 
			
		||||
         */
 | 
			
		||||
        function DigestIndicator($interval, $rootScope) {
 | 
			
		||||
            var digests = 0,
 | 
			
		||||
                displayed = 0,
 | 
			
		||||
                start = Date.now();
 | 
			
		||||
 | 
			
		||||
            function update() {
 | 
			
		||||
                var secs = (Date.now() - start) / 1000;
 | 
			
		||||
                displayed = Math.round(digests / secs);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function increment() {
 | 
			
		||||
                digests += 1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $rootScope.$watch(increment);
 | 
			
		||||
 | 
			
		||||
            // Update state every second
 | 
			
		||||
            $interval(update, 1000);
 | 
			
		||||
 | 
			
		||||
            // Provide initial state, too
 | 
			
		||||
            update();
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                getGlyph: function () {
 | 
			
		||||
                    return ".";
 | 
			
		||||
                },
 | 
			
		||||
                getGlyphClass: function () {
 | 
			
		||||
                    return undefined;
 | 
			
		||||
                },
 | 
			
		||||
                getText: function () {
 | 
			
		||||
                    return displayed + " digests/sec";
 | 
			
		||||
                },
 | 
			
		||||
                getDescription: function () {
 | 
			
		||||
                    return "";
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return DigestIndicator;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -22,7 +22,8 @@
 | 
			
		||||
    "split": "^1.0.0",
 | 
			
		||||
    "mkdirp": "^0.5.1",
 | 
			
		||||
    "nomnoml": "^0.0.3",
 | 
			
		||||
    "canvas": "^1.2.7"
 | 
			
		||||
    "canvas": "^1.2.7",
 | 
			
		||||
    "markdown-toc": "^0.11.7"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "node app.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
    "configuration": {
 | 
			
		||||
        "paths": {
 | 
			
		||||
            "uuid": "uuid"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "extensions": {
 | 
			
		||||
        "routes": [
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -42,9 +42,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class='object-holder abs vscroll'>
 | 
			
		||||
        <mct-representation key="representation.selected.key"
 | 
			
		||||
                            mct-object="representation.selected.key && domainObject">
 | 
			
		||||
        </mct-representation>
 | 
			
		||||
    </div>
 | 
			
		||||
    <mct-representation key="representation.selected.key"
 | 
			
		||||
                        mct-object="representation.selected.key && domainObject"
 | 
			
		||||
                        class="abs object-holder">
 | 
			
		||||
    </mct-representation>
 | 
			
		||||
</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,9 @@
 | 
			
		||||
        <mct-split-pane class='contents abs' anchor='left'>
 | 
			
		||||
            <div class='split-pane-component treeview pane left'>
 | 
			
		||||
                <div class="holder abs l-mobile">
 | 
			
		||||
                    <mct-representation key="'create-button'" mct-object="navigatedObject">
 | 
			
		||||
                    <mct-representation key="'create-button'"
 | 
			
		||||
                                        mct-object="navigatedObject"
 | 
			
		||||
                                        mct-device="desktop">
 | 
			
		||||
                    </mct-representation>
 | 
			
		||||
                    <div class='holder search-holder abs'
 | 
			
		||||
                         ng-class="{active: treeModel.search}">
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div class="menu-element wrapper" ng-controller="ClickAwayController as createController">
 | 
			
		||||
    <div class="s-menu major create-btn" ng-click="createController.toggle()">
 | 
			
		||||
    <div class="s-menu-btn major create-btn" ng-click="createController.toggle()">
 | 
			
		||||
		<span class="ui-symbol icon type-icon">+</span>
 | 
			
		||||
		<span class="title-label">Create</span>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
 * Module defining CreateService. Created by vwoeltje on 11/10/14.
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    ["../../lib/uuid"],
 | 
			
		||||
    ["uuid"],
 | 
			
		||||
    function (uuid) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,12 +30,11 @@
 | 
			
		||||
                         structure="toolbar.structure"
 | 
			
		||||
                         ng-model="toolbar.state">
 | 
			
		||||
            </mct-toolbar>
 | 
			
		||||
            <div class='holder abs object-holder work-area'>
 | 
			
		||||
                <mct-representation key="representation.selected.key"
 | 
			
		||||
                                    toolbar="toolbar"
 | 
			
		||||
                                    mct-object="representation.selected.key && domainObject">
 | 
			
		||||
                </mct-representation>
 | 
			
		||||
            </div>
 | 
			
		||||
            <mct-representation key="representation.selected.key"
 | 
			
		||||
                                toolbar="toolbar"
 | 
			
		||||
                                mct-object="representation.selected.key && domainObject"
 | 
			
		||||
                                class="holder abs object-holder work-area">
 | 
			
		||||
            </mct-representation>
 | 
			
		||||
        </div>
 | 
			
		||||
        <mct-splitter></mct-splitter>
 | 
			
		||||
        <div
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,11 @@
 | 
			
		||||
                "key": "urlService",
 | 
			
		||||
                "implementation": "services/UrlService.js",
 | 
			
		||||
                "depends": [ "$location" ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "popupService",
 | 
			
		||||
                "implementation": "services/PopupService.js",
 | 
			
		||||
                "depends": [ "$document", "$window" ]
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "runs": [
 | 
			
		||||
@@ -53,6 +58,16 @@
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "controllers": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "TimeRangeController",
 | 
			
		||||
                "implementation": "controllers/TimeRangeController.js",
 | 
			
		||||
                "depends": [ "$scope", "now" ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "DateTimePickerController",
 | 
			
		||||
                "implementation": "controllers/DateTimePickerController.js",
 | 
			
		||||
                "depends": [ "$scope", "now" ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "TreeNodeController",
 | 
			
		||||
                "implementation": "controllers/TreeNodeController.js",
 | 
			
		||||
@@ -118,11 +133,21 @@
 | 
			
		||||
                "implementation": "directives/MCTDrag.js",
 | 
			
		||||
                "depends": [ "$document" ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "mctClickElsewhere",
 | 
			
		||||
                "implementation": "directives/MCTClickElsewhere.js",
 | 
			
		||||
                "depends": [ "$document" ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "mctResize",
 | 
			
		||||
                "implementation": "directives/MCTResize.js",
 | 
			
		||||
                "depends": [ "$timeout" ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "mctPopup",
 | 
			
		||||
                "implementation": "directives/MCTPopup.js",
 | 
			
		||||
                "depends": [ "$compile", "popupService" ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "mctScrollX",
 | 
			
		||||
                "implementation": "directives/MCTScroll.js",
 | 
			
		||||
@@ -226,6 +251,10 @@
 | 
			
		||||
            {
 | 
			
		||||
                "key": "selector",
 | 
			
		||||
                "templateUrl": "templates/controls/selector.html"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "datetime-picker",
 | 
			
		||||
                "templateUrl": "templates/controls/datetime-picker.html"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "licenses": [
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ $ueEditToolBarH: 25px;
 | 
			
		||||
$ueBrowseLeftPaneW: 25%;
 | 
			
		||||
$ueEditLeftPaneW: 75%;
 | 
			
		||||
$treeSearchInputBarH: 25px;
 | 
			
		||||
$ueTimeControlH: (33px, 20px, 20px);
 | 
			
		||||
// Overlay
 | 
			
		||||
$ovrTopBarH: 45px;
 | 
			
		||||
$ovrFooterH: 24px;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,11 +40,11 @@
 | 
			
		||||
 | 
			
		||||
/************************** HTML ENTITIES */
 | 
			
		||||
a {
 | 
			
		||||
	color: #ccc;
 | 
			
		||||
	color: $colorA;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	text-decoration: none;
 | 
			
		||||
	&:hover {
 | 
			
		||||
		color: #fff;
 | 
			
		||||
		color: $colorAHov;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -125,6 +125,14 @@ mct-container {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.scrolling {
 | 
			
		||||
	overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vscroll {
 | 
			
		||||
	overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.no-margin {
 | 
			
		||||
	margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,9 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ui-symbol {
 | 
			
		||||
	&.type-icon {
 | 
			
		||||
		color: $colorObjHdrIc;
 | 
			
		||||
	}
 | 
			
		||||
	&.icon {
 | 
			
		||||
		color: $colorKey;
 | 
			
		||||
		&.alert {
 | 
			
		||||
@@ -41,6 +44,9 @@
 | 
			
		||||
			font-size: 1.65em;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	&.icon-calendar:after {
 | 
			
		||||
		content: "\e605";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bar .ui-symbol {
 | 
			
		||||
@@ -52,7 +58,7 @@
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.s-menu .invoke-menu,
 | 
			
		||||
.s-menu-btn .invoke-menu,
 | 
			
		||||
.icon.major .invoke-menu {
 | 
			
		||||
	margin-left: $interiorMarginSm;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -364,9 +364,10 @@
 | 
			
		||||
    /* This doesn't work on an element inside an element with absolute positioning that has height: auto */
 | 
			
		||||
    //position: relative;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    -webkit-transform: translateY(-50%);
 | 
			
		||||
    -ms-transform: translateY(-50%);
 | 
			
		||||
    transform: translateY(-50%);
 | 
			
		||||
	@include webkitProp(transform, translateY(-50%));
 | 
			
		||||
    //-webkit-transform: translateY(-50%);
 | 
			
		||||
    //-ms-transform: translateY(-50%);
 | 
			
		||||
    //transform: translateY(-50%);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin verticalCenterBlock($holderH, $itemH) {
 | 
			
		||||
@@ -391,22 +392,8 @@
 | 
			
		||||
    overflow-y: $showBar;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin wait-spinner($b: 5px, $c: $colorAlt1) {
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    -webkit-animation: rotation .6s infinite linear;
 | 
			
		||||
    -moz-animation: rotation .6s infinite linear;
 | 
			
		||||
    -o-animation: rotation .6s infinite linear;
 | 
			
		||||
    animation: rotation .6s infinite linear;
 | 
			
		||||
    border-color: rgba($c, 0.25);
 | 
			
		||||
    border-top-color: rgba($c, 1.0);
 | 
			
		||||
    border-style: solid;
 | 
			
		||||
    border-width: $b;
 | 
			
		||||
    border-radius: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin test($c: #ffcc00, $a: 0.2) {
 | 
			
		||||
    background-color: rgba($c, $a);
 | 
			
		||||
    background-color: rgba($c, $a) !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin tmpBorder($c: #ffcc00, $a: 0.75) {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,6 @@
 | 
			
		||||
	&.fixed {
 | 
			
		||||
		font-size: 0.8em;
 | 
			
		||||
	}
 | 
			
		||||
	&.scrolling {
 | 
			
		||||
		overflow: auto;
 | 
			
		||||
	}
 | 
			
		||||
	.controls,
 | 
			
		||||
	label,
 | 
			
		||||
	.inline-block {
 | 
			
		||||
 
 | 
			
		||||
@@ -177,7 +177,7 @@ label.checkbox.custom {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.s-menu label.checkbox.custom {
 | 
			
		||||
.s-menu-btn label.checkbox.custom {
 | 
			
		||||
	margin-left: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -349,49 +349,155 @@ label.checkbox.custom {
 | 
			
		||||
 | 
			
		||||
.slider {
 | 
			
		||||
	$knobH: 100%; //14px;
 | 
			
		||||
	$knobW: 12px;
 | 
			
		||||
	$slotH: 50%;
 | 
			
		||||
	.slot {
 | 
			
		||||
		//		@include border-radius($basicCr * .75);
 | 
			
		||||
		@include sliderTrack();
 | 
			
		||||
		height: $slotH;
 | 
			
		||||
		//@include sliderTrack();
 | 
			
		||||
		width: auto;
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		top: ($knobH - $slotH) / 2;
 | 
			
		||||
		top: 0;
 | 
			
		||||
		right: 0;
 | 
			
		||||
		bottom: auto;
 | 
			
		||||
		bottom: 0;
 | 
			
		||||
		left: 0;
 | 
			
		||||
	}
 | 
			
		||||
	.knob {
 | 
			
		||||
		@include btnSubtle();
 | 
			
		||||
		@include controlGrippy(rgba(black, 0.3), vertical, 1px, solid);
 | 
			
		||||
		cursor: ew-resize;
 | 
			
		||||
		//@include btnSubtle();
 | 
			
		||||
		//@include controlGrippy(rgba(black, 0.3), vertical, 1px, solid);
 | 
			
		||||
		@include trans-prop-nice-fade(.25s);
 | 
			
		||||
		background-color: $sliderColorKnob;
 | 
			
		||||
		&:hover {
 | 
			
		||||
			background-color: $sliderColorKnobHov;
 | 
			
		||||
		}
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		height: $knobH;
 | 
			
		||||
		width: $knobW;
 | 
			
		||||
		width: $sliderKnobW;
 | 
			
		||||
		top: 0;
 | 
			
		||||
		auto: 0;
 | 
			
		||||
		bottom: auto;
 | 
			
		||||
		left: auto;
 | 
			
		||||
		&:before {
 | 
			
		||||
			top: 1px;
 | 
			
		||||
			bottom: 3px;
 | 
			
		||||
			left: ($knobW / 2) - 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	.knob-l {
 | 
			
		||||
		@include border-left-radius($sliderKnobW);
 | 
			
		||||
		cursor: w-resize;
 | 
			
		||||
	}
 | 
			
		||||
	.knob-r {
 | 
			
		||||
		@include border-right-radius($sliderKnobW);
 | 
			
		||||
		cursor: e-resize;
 | 
			
		||||
	}
 | 
			
		||||
	.range {
 | 
			
		||||
		background: rgba($colorKey, 0.6);
 | 
			
		||||
		@include trans-prop-nice-fade(.25s);
 | 
			
		||||
		background-color: $sliderColorRange;
 | 
			
		||||
		cursor: ew-resize;
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		top: 0;
 | 
			
		||||
		top: 0; //$tbOffset;
 | 
			
		||||
		right: auto;
 | 
			
		||||
		bottom: 0;
 | 
			
		||||
		left: auto;
 | 
			
		||||
		height: auto;
 | 
			
		||||
		width: auto;
 | 
			
		||||
		&:hover {
 | 
			
		||||
			background: rgba($colorKey, 0.7);
 | 
			
		||||
			background-color: $sliderColorRangeHov;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/******************************************************** DATETIME PICKER */
 | 
			
		||||
.l-datetime-picker {
 | 
			
		||||
	$r1H: 15px;
 | 
			
		||||
	@include user-select(none);
 | 
			
		||||
	font-size: 0.8rem;
 | 
			
		||||
	padding: $interiorMarginLg !important;
 | 
			
		||||
	width: 230px;
 | 
			
		||||
	.l-month-year-pager {
 | 
			
		||||
		$pagerW: 20px;
 | 
			
		||||
		//@include test();
 | 
			
		||||
		//font-size: 0.8rem;
 | 
			
		||||
		height: $r1H;
 | 
			
		||||
		margin-bottom: $interiorMargin;
 | 
			
		||||
		position: relative;
 | 
			
		||||
		.pager,
 | 
			
		||||
		.val {
 | 
			
		||||
			//@include test(red);
 | 
			
		||||
			@extend .abs;
 | 
			
		||||
		}
 | 
			
		||||
		.pager {
 | 
			
		||||
			width: $pagerW;
 | 
			
		||||
			@extend .ui-symbol;
 | 
			
		||||
			&.prev {
 | 
			
		||||
				right: auto;
 | 
			
		||||
				&:before {
 | 
			
		||||
					content: "\3c";
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			&.next {
 | 
			
		||||
				left: auto;
 | 
			
		||||
				text-align: right;
 | 
			
		||||
				&:before {
 | 
			
		||||
					content: "\3e";
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		.val {
 | 
			
		||||
			text-align: center;
 | 
			
		||||
			left: $pagerW + $interiorMargin;
 | 
			
		||||
			right: $pagerW + $interiorMargin;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	.l-calendar,
 | 
			
		||||
	.l-time-selects {
 | 
			
		||||
		border-top: 1px solid $colorInteriorBorder
 | 
			
		||||
	}
 | 
			
		||||
	.l-time-selects {
 | 
			
		||||
		line-height: $formInputH;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/******************************************************** CALENDAR */
 | 
			
		||||
.l-calendar {
 | 
			
		||||
	$colorMuted: pushBack($colorMenuFg, 30%);
 | 
			
		||||
	ul.l-cal-row {
 | 
			
		||||
		@include display-flex;
 | 
			
		||||
		@include flex-flow(row nowrap);
 | 
			
		||||
		margin-top: 1px;
 | 
			
		||||
		&:first-child {
 | 
			
		||||
			margin-top: 0;
 | 
			
		||||
		}
 | 
			
		||||
		li {
 | 
			
		||||
			@include flex(1 0);
 | 
			
		||||
			//@include test();
 | 
			
		||||
			margin-left: 1px;
 | 
			
		||||
			padding: $interiorMargin;
 | 
			
		||||
			text-align: center;
 | 
			
		||||
			&:first-child {
 | 
			
		||||
				margin-left: 0;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		&.l-header li {
 | 
			
		||||
			color: $colorMuted;
 | 
			
		||||
		}
 | 
			
		||||
		&.l-body li {
 | 
			
		||||
			@include trans-prop-nice(background-color, .25s);
 | 
			
		||||
			cursor: pointer;
 | 
			
		||||
			&.in-month {
 | 
			
		||||
				background-color: $colorCalCellInMonthBg;
 | 
			
		||||
			}
 | 
			
		||||
			.sub {
 | 
			
		||||
				color: $colorMuted;
 | 
			
		||||
				font-size: 0.8em;
 | 
			
		||||
			}
 | 
			
		||||
			&.selected {
 | 
			
		||||
				background: $colorCalCellSelectedBg;
 | 
			
		||||
				color: $colorCalCellSelectedFg;
 | 
			
		||||
				.sub {
 | 
			
		||||
					color: inherit;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			&:hover {
 | 
			
		||||
				background-color: $colorCalCellHovBg;
 | 
			
		||||
				color: $colorCalCellHovFg;
 | 
			
		||||
				.sub {
 | 
			
		||||
					color: inherit;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
/******************************************************** MENU BUTTONS */
 | 
			
		||||
.s-menu {
 | 
			
		||||
.s-menu-btn {
 | 
			
		||||
	// Formerly .btn-menu
 | 
			
		||||
	@extend .s-btn;
 | 
			
		||||
	span.l-click-area {
 | 
			
		||||
@@ -62,186 +62,192 @@
 | 
			
		||||
 | 
			
		||||
/******************************************************** MENUS THEMSELVES */
 | 
			
		||||
.menu-element {
 | 
			
		||||
	$bg: $colorMenuBg;
 | 
			
		||||
	$fg: $colorMenuFg;
 | 
			
		||||
	$ic: $colorMenuIc;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	.menu {
 | 
			
		||||
		@include border-radius($basicCr);
 | 
			
		||||
		@include containerSubtle($bg, $fg);
 | 
			
		||||
		@include boxShdw($shdwMenu);
 | 
			
		||||
		@include txtShdw($shdwMenuText);
 | 
			
		||||
		display: block; // set to block via jQuery
 | 
			
		||||
		padding: $interiorMarginSm 0;
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		z-index: 10;
 | 
			
		||||
		ul {
 | 
			
		||||
			@include menuUlReset();
 | 
			
		||||
			li {
 | 
			
		||||
				@include box-sizing(border-box);
 | 
			
		||||
				border-top: 1px solid lighten($bg, 20%);
 | 
			
		||||
				color: pullForward($bg, 60%);
 | 
			
		||||
				line-height: $menuLineH;
 | 
			
		||||
				padding: $interiorMarginSm $interiorMargin * 2 $interiorMarginSm ($interiorMargin * 2) + $treeTypeIconW;
 | 
			
		||||
				position: relative;
 | 
			
		||||
				white-space: nowrap;
 | 
			
		||||
				&:first-child {
 | 
			
		||||
					border: none;
 | 
			
		||||
				}
 | 
			
		||||
				&:hover {
 | 
			
		||||
					background: $colorMenuHovBg;
 | 
			
		||||
					color: $colorMenuHovFg;
 | 
			
		||||
					.icon {
 | 
			
		||||
						color: $colorMenuHovIc;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				.type-icon {
 | 
			
		||||
					left: $interiorMargin * 2;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	.menu,
 | 
			
		||||
	.context-menu,
 | 
			
		||||
	.super-menu {
 | 
			
		||||
		pointer-events: auto;
 | 
			
		||||
		ul li {
 | 
			
		||||
			//padding-left: 25px;
 | 
			
		||||
			a {
 | 
			
		||||
				color: $fg;
 | 
			
		||||
			}
 | 
			
		||||
			.icon {
 | 
			
		||||
				color: $ic;
 | 
			
		||||
			}
 | 
			
		||||
			.type-icon {
 | 
			
		||||
				left: $interiorMargin;
 | 
			
		||||
			}
 | 
			
		||||
			&:hover .icon {
 | 
			
		||||
				//color: lighten($ic, 5%);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
.s-menu {
 | 
			
		||||
	@include border-radius($basicCr);
 | 
			
		||||
	@include containerSubtle($colorMenuBg, $colorMenuFg);
 | 
			
		||||
	@include boxShdw($shdwMenu);
 | 
			
		||||
	@include txtShdw($shdwMenuText);
 | 
			
		||||
	padding: $interiorMarginSm 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	.checkbox-menu {
 | 
			
		||||
		// Used in search dropdown in tree
 | 
			
		||||
		@extend .context-menu;
 | 
			
		||||
		ul li {
 | 
			
		||||
			padding-left: 50px;
 | 
			
		||||
			.checkbox {
 | 
			
		||||
				$d: 0.7rem;
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				left: $interiorMargin;
 | 
			
		||||
				top: ($menuLineH - $d) / 1.5;
 | 
			
		||||
				em {
 | 
			
		||||
					height: $d;
 | 
			
		||||
					width: $d;
 | 
			
		||||
					&:before {
 | 
			
		||||
						font-size: 7px !important;// $d/2;
 | 
			
		||||
						height: $d;
 | 
			
		||||
						width: $d;
 | 
			
		||||
						line-height: $d;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			.type-icon {
 | 
			
		||||
				left: 25px;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.super-menu {
 | 
			
		||||
		$w: 500px;
 | 
			
		||||
		$h: $w - 20;
 | 
			
		||||
		$plw: 50%;
 | 
			
		||||
		$prw: 50%;
 | 
			
		||||
		display: block;
 | 
			
		||||
		width: $w;
 | 
			
		||||
		height: $h;
 | 
			
		||||
		.contents {
 | 
			
		||||
			@include absPosDefault($interiorMargin);
 | 
			
		||||
		}
 | 
			
		||||
		.pane {
 | 
			
		||||
.menu {
 | 
			
		||||
	@extend .s-menu;
 | 
			
		||||
	display: block;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	z-index: 10;
 | 
			
		||||
	ul {
 | 
			
		||||
		@include menuUlReset();
 | 
			
		||||
		li {
 | 
			
		||||
			@include box-sizing(border-box);
 | 
			
		||||
			&.left {
 | 
			
		||||
				//@include test();
 | 
			
		||||
				border-right: 1px solid pullForward($colorMenuBg, 10%);
 | 
			
		||||
				left: 0;
 | 
			
		||||
				padding-right: $interiorMargin;
 | 
			
		||||
				right: auto;
 | 
			
		||||
				width: $plw;
 | 
			
		||||
				overflow-x: hidden;
 | 
			
		||||
				overflow-y: auto;
 | 
			
		||||
				ul {
 | 
			
		||||
					li {
 | 
			
		||||
						@include border-radius($controlCr);
 | 
			
		||||
						padding-left: 30px;
 | 
			
		||||
						border-top: none;
 | 
			
		||||
					}
 | 
			
		||||
			border-top: 1px solid lighten($colorMenuBg, 20%);
 | 
			
		||||
			color: pullForward($colorMenuBg, 60%);
 | 
			
		||||
			line-height: $menuLineH;
 | 
			
		||||
			padding: $interiorMarginSm $interiorMargin * 2 $interiorMarginSm ($interiorMargin * 2) + $treeTypeIconW;
 | 
			
		||||
			position: relative;
 | 
			
		||||
			white-space: nowrap;
 | 
			
		||||
			&:first-child {
 | 
			
		||||
				border: none;
 | 
			
		||||
			}
 | 
			
		||||
			&:hover {
 | 
			
		||||
				background: $colorMenuHovBg;
 | 
			
		||||
				color: $colorMenuHovFg;
 | 
			
		||||
				.icon {
 | 
			
		||||
					color: $colorMenuHovIc;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			&.right {
 | 
			
		||||
				//@include test(red);
 | 
			
		||||
				left: auto;
 | 
			
		||||
				right: 0;
 | 
			
		||||
				padding: $interiorMargin * 5;
 | 
			
		||||
				width: $prw;
 | 
			
		||||
			.type-icon {
 | 
			
		||||
				left: $interiorMargin * 2;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		.menu-item-description {
 | 
			
		||||
			.desc-area {
 | 
			
		||||
				&.icon {
 | 
			
		||||
					$h: 150px;
 | 
			
		||||
					color: $colorCreateMenuLgIcon;
 | 
			
		||||
					position: relative;
 | 
			
		||||
					font-size: 8em;
 | 
			
		||||
					left: 0;
 | 
			
		||||
					height: $h;
 | 
			
		||||
					line-height: $h;
 | 
			
		||||
					margin-bottom: $interiorMargin * 5;
 | 
			
		||||
					text-align: center;
 | 
			
		||||
				}
 | 
			
		||||
				&.title {
 | 
			
		||||
					color: $colorCreateMenuText;
 | 
			
		||||
					font-size: 1.2em;
 | 
			
		||||
					margin-bottom: 0.5em;
 | 
			
		||||
				}
 | 
			
		||||
				&.description {
 | 
			
		||||
					//color: lighten($bg, 30%);
 | 
			
		||||
					color: $colorCreateMenuText;
 | 
			
		||||
					font-size: 0.8em;
 | 
			
		||||
					line-height: 1.5em;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	.context-menu {
 | 
			
		||||
		font-size: 0.80rem;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.context-menu-holder {
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
.menu,
 | 
			
		||||
.context-menu,
 | 
			
		||||
.super-menu {
 | 
			
		||||
	pointer-events: auto;
 | 
			
		||||
	ul li {
 | 
			
		||||
		//padding-left: 25px;
 | 
			
		||||
		a {
 | 
			
		||||
			color: $colorMenuFg;
 | 
			
		||||
		}
 | 
			
		||||
		.icon {
 | 
			
		||||
			color: $colorMenuIc;
 | 
			
		||||
		}
 | 
			
		||||
		.type-icon {
 | 
			
		||||
			left: $interiorMargin;
 | 
			
		||||
		}
 | 
			
		||||
		&:hover .icon {
 | 
			
		||||
			//color: lighten($colorMenuIc, 5%);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.checkbox-menu {
 | 
			
		||||
	// Used in search dropdown in tree
 | 
			
		||||
	@extend .context-menu;
 | 
			
		||||
	ul li {
 | 
			
		||||
		padding-left: 50px;
 | 
			
		||||
		.checkbox {
 | 
			
		||||
			$d: 0.7rem;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			left: $interiorMargin;
 | 
			
		||||
			top: ($menuLineH - $d) / 1.5;
 | 
			
		||||
			em {
 | 
			
		||||
				height: $d;
 | 
			
		||||
				width: $d;
 | 
			
		||||
				&:before {
 | 
			
		||||
					font-size: 7px !important;// $d/2;
 | 
			
		||||
					height: $d;
 | 
			
		||||
					width: $d;
 | 
			
		||||
					line-height: $d;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		.type-icon {
 | 
			
		||||
			left: 25px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.super-menu {
 | 
			
		||||
	$w: 500px;
 | 
			
		||||
	$h: $w - 20;
 | 
			
		||||
	$plw: 50%;
 | 
			
		||||
	$prw: 50%;
 | 
			
		||||
	display: block;
 | 
			
		||||
	width: $w;
 | 
			
		||||
	height: $h;
 | 
			
		||||
	.contents {
 | 
			
		||||
		@include absPosDefault($interiorMargin);
 | 
			
		||||
	}
 | 
			
		||||
	.pane {
 | 
			
		||||
		@include box-sizing(border-box);
 | 
			
		||||
		&.left {
 | 
			
		||||
			//@include test();
 | 
			
		||||
			border-right: 1px solid pullForward($colorMenuBg, 10%);
 | 
			
		||||
			left: 0;
 | 
			
		||||
			padding-right: $interiorMargin;
 | 
			
		||||
			right: auto;
 | 
			
		||||
			width: $plw;
 | 
			
		||||
			overflow-x: hidden;
 | 
			
		||||
			overflow-y: auto;
 | 
			
		||||
			ul {
 | 
			
		||||
				li {
 | 
			
		||||
					@include border-radius($controlCr);
 | 
			
		||||
					padding-left: 30px;
 | 
			
		||||
					border-top: none;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		&.right {
 | 
			
		||||
			//@include test(red);
 | 
			
		||||
			left: auto;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			padding: $interiorMargin * 5;
 | 
			
		||||
			width: $prw;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	.menu-item-description {
 | 
			
		||||
		.desc-area {
 | 
			
		||||
			&.icon {
 | 
			
		||||
				$h: 150px;
 | 
			
		||||
				color: $colorCreateMenuLgIcon;
 | 
			
		||||
				position: relative;
 | 
			
		||||
				font-size: 8em;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				height: $h;
 | 
			
		||||
				line-height: $h;
 | 
			
		||||
				margin-bottom: $interiorMargin * 5;
 | 
			
		||||
				text-align: center;
 | 
			
		||||
			}
 | 
			
		||||
			&.title {
 | 
			
		||||
				color: $colorCreateMenuText;
 | 
			
		||||
				font-size: 1.2em;
 | 
			
		||||
				margin-bottom: 0.5em;
 | 
			
		||||
			}
 | 
			
		||||
			&.description {
 | 
			
		||||
				//color: lighten($colorMenuBg, 30%);
 | 
			
		||||
				color: $colorCreateMenuText;
 | 
			
		||||
				font-size: 0.8em;
 | 
			
		||||
				line-height: 1.5em;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
.context-menu {
 | 
			
		||||
	font-size: 0.80rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.context-menu-holder,
 | 
			
		||||
.menu-holder {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	height: 200px;
 | 
			
		||||
	width: 170px;
 | 
			
		||||
	z-index: 70;
 | 
			
		||||
	.context-menu-wrapper {
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		height: 100%;
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		.context-menu {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	&.go-left .context-menu {
 | 
			
		||||
	&.go-left .context-menu,
 | 
			
		||||
	&.go-left .menu {
 | 
			
		||||
		right: 0;
 | 
			
		||||
	}
 | 
			
		||||
	&.go-up .context-menu {
 | 
			
		||||
	&.go-up .context-menu,
 | 
			
		||||
	&.go-up .menu {
 | 
			
		||||
		bottom: 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.context-menu-holder {
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
	height: 200px;
 | 
			
		||||
	width: 170px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-bar.right .menu,
 | 
			
		||||
.menus-to-left .menu {
 | 
			
		||||
	left: auto;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,72 +1,155 @@
 | 
			
		||||
.l-time-controller {
 | 
			
		||||
	$inputTxtW: 90px;
 | 
			
		||||
	$knobW: 9px;
 | 
			
		||||
	$r1H: 20px;
 | 
			
		||||
	$r2H: 30px;
 | 
			
		||||
	$r3H: 10px;
 | 
			
		||||
@mixin toiLineHovEffects() {
 | 
			
		||||
	//@include pulse(.25s);
 | 
			
		||||
	&:before,
 | 
			
		||||
	&:after {
 | 
			
		||||
		background-color: $timeControllerToiLineColorHov;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	position: relative;
 | 
			
		||||
	margin: $interiorMarginLg 0;
 | 
			
		||||
	min-width: 400px;
 | 
			
		||||
.l-time-controller-visible {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mct-include.l-time-controller {
 | 
			
		||||
	$minW: 500px;
 | 
			
		||||
	$knobHOffset: 0px;
 | 
			
		||||
	$knobM: ($sliderKnobW + $knobHOffset) * -1;
 | 
			
		||||
	$rangeValPad: $interiorMargin;
 | 
			
		||||
	$rangeValOffset: $sliderKnobW;
 | 
			
		||||
	//$knobCr: $sliderKnobW;
 | 
			
		||||
	$timeRangeSliderLROffset: 130px + $sliderKnobW + $rangeValOffset;
 | 
			
		||||
	$r1H: nth($ueTimeControlH,1);
 | 
			
		||||
	$r2H: nth($ueTimeControlH,2);
 | 
			
		||||
	$r3H: nth($ueTimeControlH,3);
 | 
			
		||||
 | 
			
		||||
    @include absPosDefault();
 | 
			
		||||
    //@include test();
 | 
			
		||||
    display: block;
 | 
			
		||||
    top: auto;
 | 
			
		||||
	height: $r1H + $r2H + $r3H + ($interiorMargin * 2);
 | 
			
		||||
    min-width: $minW;
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
 | 
			
		||||
	.l-time-range-inputs-holder,
 | 
			
		||||
	.l-time-range-slider {
 | 
			
		||||
		font-size: 0.8em;
 | 
			
		||||
		//font-size: 0.8em;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.l-time-range-inputs-holder,
 | 
			
		||||
	.l-time-range-slider-holder,
 | 
			
		||||
	.l-time-range-ticks-holder
 | 
			
		||||
	{
 | 
			
		||||
		margin-bottom: $interiorMargin;
 | 
			
		||||
		position: relative;
 | 
			
		||||
		//@include test();
 | 
			
		||||
		@include absPosDefault(0, visible);
 | 
			
		||||
		@include box-sizing(border-box);
 | 
			
		||||
		top: auto;
 | 
			
		||||
	}
 | 
			
		||||
	.l-time-range-slider,
 | 
			
		||||
	.l-time-range-ticks {
 | 
			
		||||
		//@include test(red, 0.1);
 | 
			
		||||
		@include absPosDefault(0, visible);
 | 
			
		||||
		left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.l-time-range-inputs-holder {
 | 
			
		||||
		height: $r1H;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.l-time-range-slider,
 | 
			
		||||
	.l-time-range-ticks {
 | 
			
		||||
		left: $inputTxtW; right: $inputTxtW;
 | 
			
		||||
 | 
			
		||||
		//@include test(red);
 | 
			
		||||
		height: $r1H; bottom: $r2H + $r3H + ($interiorMarginSm * 2);
 | 
			
		||||
		padding-top: $interiorMargin;
 | 
			
		||||
		border-top: 1px solid $colorInteriorBorder;
 | 
			
		||||
		.type-icon {
 | 
			
		||||
			font-size: 120%;
 | 
			
		||||
			vertical-align: middle;
 | 
			
		||||
		}
 | 
			
		||||
		.l-time-range-input,
 | 
			
		||||
		.l-time-range-inputs-elem {
 | 
			
		||||
			margin-right: $interiorMargin;
 | 
			
		||||
			.lbl {
 | 
			
		||||
				color: $colorPlotLabelFg;
 | 
			
		||||
			}
 | 
			
		||||
			.ui-symbol.icon {
 | 
			
		||||
				font-size: 11px;
 | 
			
		||||
				width: 11px;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.l-time-range-slider-holder {
 | 
			
		||||
		height: $r2H;
 | 
			
		||||
		//@include test(green);
 | 
			
		||||
		height: $r2H; bottom: $r3H + ($interiorMarginSm * 1);
 | 
			
		||||
		.range-holder {
 | 
			
		||||
			@include box-shadow(none);
 | 
			
		||||
			background: none;
 | 
			
		||||
			border: none;
 | 
			
		||||
			height: 75%;
 | 
			
		||||
			.range {
 | 
			
		||||
				.toi-line {
 | 
			
		||||
					$myC: $timeControllerToiLineColor;
 | 
			
		||||
					$myW: 8px;
 | 
			
		||||
					@include transform(translateX(50%));
 | 
			
		||||
					position: absolute;
 | 
			
		||||
					//@include test();
 | 
			
		||||
					top: 0; right: 0; bottom: 0px; left: auto;
 | 
			
		||||
					width: $myW;
 | 
			
		||||
					height: auto;
 | 
			
		||||
					z-index: 2;
 | 
			
		||||
					&:before,
 | 
			
		||||
					&:after {
 | 
			
		||||
						background-color: $myC;
 | 
			
		||||
						content: "";
 | 
			
		||||
						position: absolute;
 | 
			
		||||
					}
 | 
			
		||||
					&:before {
 | 
			
		||||
						// Vert line
 | 
			
		||||
						top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
 | 
			
		||||
						width: 2px;
 | 
			
		||||
						//top: 0; right: 3px; bottom: 0; left: 3px;
 | 
			
		||||
					}
 | 
			
		||||
					&:after {
 | 
			
		||||
						// Circle element
 | 
			
		||||
						@include border-radius($myW);
 | 
			
		||||
						@include transform(translateY(-50%));
 | 
			
		||||
						top: 50%; right: 0; bottom: auto; left: 0;
 | 
			
		||||
						width: auto;
 | 
			
		||||
						height: $myW;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				&:hover .toi-line {
 | 
			
		||||
					@include toiLineHovEffects;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		&:not(:active) {
 | 
			
		||||
			//@include test(#ff00cc);
 | 
			
		||||
			.knob,
 | 
			
		||||
			.range {
 | 
			
		||||
				@include transition-property(left, right);
 | 
			
		||||
				@include transition-duration(500ms);
 | 
			
		||||
				@include transition-timing-function(ease-in-out);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.l-time-range-ticks-holder {
 | 
			
		||||
		height: $r3H;
 | 
			
		||||
		.l-time-range-ticks {
 | 
			
		||||
			border-top: 1px solid $colorInteriorBorder;
 | 
			
		||||
			border-top: 1px solid $colorTick;
 | 
			
		||||
			.tick {
 | 
			
		||||
				background-color: $colorInteriorBorder;
 | 
			
		||||
				background-color: $colorTick;
 | 
			
		||||
				border:none;
 | 
			
		||||
				height: 5px;
 | 
			
		||||
				width: 1px;
 | 
			
		||||
				margin-left: -1px;
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				&:first-child {
 | 
			
		||||
					margin-left: 0;
 | 
			
		||||
				}
 | 
			
		||||
				.l-time-range-tick-label {
 | 
			
		||||
					color: lighten($colorInteriorBorder, 20%);
 | 
			
		||||
					font-size: 0.7em;
 | 
			
		||||
					@include webkitProp(transform, translateX(-50%));
 | 
			
		||||
					color: $colorPlotLabelFg;
 | 
			
		||||
					display: inline-block;
 | 
			
		||||
					font-size: 0.9em;
 | 
			
		||||
					position: absolute;
 | 
			
		||||
					margin-left: -0.5 * $tickLblW;
 | 
			
		||||
					text-align: center;
 | 
			
		||||
					top: $r3H;
 | 
			
		||||
					width: $tickLblW;
 | 
			
		||||
					top: 8px;
 | 
			
		||||
					white-space: nowrap;
 | 
			
		||||
					z-index: 2;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -74,31 +157,47 @@
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.knob {
 | 
			
		||||
		width: $knobW;
 | 
			
		||||
		z-index: 2;
 | 
			
		||||
		.range-value {
 | 
			
		||||
			$w: 75px;
 | 
			
		||||
			//@include test();
 | 
			
		||||
			//@include test($sliderColorRange);
 | 
			
		||||
			@include trans-prop-nice-fade(.25s);
 | 
			
		||||
			padding: 0 $rangeValOffset;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: 50%;
 | 
			
		||||
			margin-top: -7px; // Label is 13px high
 | 
			
		||||
			height: $r2H;
 | 
			
		||||
			line-height: $r2H;
 | 
			
		||||
			white-space: nowrap;
 | 
			
		||||
			width: $w;
 | 
			
		||||
		}
 | 
			
		||||
		&:hover .range-value {
 | 
			
		||||
			color: $colorKey;
 | 
			
		||||
			color: $sliderColorKnobHov;
 | 
			
		||||
		}
 | 
			
		||||
		&.knob-l {
 | 
			
		||||
			margin-left: $knobW / -2;
 | 
			
		||||
			//@include border-bottom-left-radius($knobCr); // MOVED TO _CONTROLS.SCSS
 | 
			
		||||
			margin-left: $knobM;
 | 
			
		||||
			.range-value {
 | 
			
		||||
				text-align: right;
 | 
			
		||||
				right: $knobW + $interiorMargin;
 | 
			
		||||
				right: $rangeValOffset;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		&.knob-r {
 | 
			
		||||
			margin-right: $knobW / -2;
 | 
			
		||||
			//@include border-bottom-right-radius($knobCr);
 | 
			
		||||
			margin-right: $knobM;
 | 
			
		||||
			.range-value {
 | 
			
		||||
				left: $knobW + $interiorMargin;
 | 
			
		||||
				left: $rangeValOffset;
 | 
			
		||||
			}
 | 
			
		||||
			&:hover + .range-holder .range .toi-line {
 | 
			
		||||
				@include toiLineHovEffects;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//.slot.range-holder {
 | 
			
		||||
//	background-color: $sliderColorRangeHolder;
 | 
			
		||||
//}
 | 
			
		||||
 | 
			
		||||
.s-time-range-val {
 | 
			
		||||
	//@include test();
 | 
			
		||||
	@include border-radius($controlCr);
 | 
			
		||||
	background-color: $colorInputBg;
 | 
			
		||||
	padding: 1px 1px 0 $interiorMargin;
 | 
			
		||||
}
 | 
			
		||||
@@ -19,39 +19,44 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
@mixin complexFieldHolder($myW) {
 | 
			
		||||
	width: $myW + $interiorMargin;
 | 
			
		||||
	input[type="text"] {
 | 
			
		||||
		width: $myW;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.complex.datetime {
 | 
			
		||||
	span {
 | 
			
		||||
		display: inline-block;
 | 
			
		||||
		margin-right: $interiorMargin;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	.field-hints,
 | 
			
		||||
	.fields {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	.field-hints {
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	.fields {
 | 
			
		||||
		margin-top: $interiorMarginSm 0;
 | 
			
		||||
		padding: $interiorMarginSm 0;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	.date {
 | 
			
		||||
		$myW: 80px;
 | 
			
		||||
		width: $myW + $interiorMargin;
 | 
			
		||||
		input {
 | 
			
		||||
			width: $myW;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		@include complexFieldHolder(80px);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.time.md {
 | 
			
		||||
		@include complexFieldHolder(60px);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	.time.sm {
 | 
			
		||||
		$myW: 40px;
 | 
			
		||||
		width: $myW + $interiorMargin;
 | 
			
		||||
		input {
 | 
			
		||||
			width: $myW;
 | 
			
		||||
		}
 | 
			
		||||
		@include complexFieldHolder(40px);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -21,10 +21,13 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
.select {
 | 
			
		||||
	@include btnSubtle($colorSelectBg);
 | 
			
		||||
	margin: 0 0 2px 2px; // Needed to avoid dropshadow from being clipped by parent containers
 | 
			
		||||
    @if $shdwBtns != none {
 | 
			
		||||
	    margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
 | 
			
		||||
    }
 | 
			
		||||
	padding: 0 $interiorMargin;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
	position: relative;
 | 
			
		||||
    line-height: $formInputH;
 | 
			
		||||
	select {
 | 
			
		||||
		@include appearance(none);
 | 
			
		||||
		@include box-sizing(border-box);
 | 
			
		||||
@@ -40,11 +43,8 @@
 | 
			
		||||
	}
 | 
			
		||||
	&:after {
 | 
			
		||||
		@include contextArrow();
 | 
			
		||||
		pointer-events: none;
 | 
			
		||||
		color: rgba($colorSelectFg, percentToDecimal($contrastInvokeMenuPercent));
 | 
			
		||||
		//content:"v";
 | 
			
		||||
		//display: block;
 | 
			
		||||
		//font-family: 'symbolsfont';
 | 
			
		||||
		//pointer-events: none;
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		right: $interiorMargin; top: 0;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,24 +19,45 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
@-webkit-keyframes rotation {
 | 
			
		||||
	from {-webkit-transform: rotate(0deg);}
 | 
			
		||||
	to {-webkit-transform: rotate(359deg);}
 | 
			
		||||
@include keyframes(rotation) {
 | 
			
		||||
    0%   { transform: rotate(0deg); }
 | 
			
		||||
    100% { transform: rotate(359deg); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@-moz-keyframes rotation {
 | 
			
		||||
	from {-moz-transform: rotate(0deg);}
 | 
			
		||||
	to {-moz-transform: rotate(359deg);}
 | 
			
		||||
@mixin wait-spinner2($b: 5px, $c: $colorAlt1) {
 | 
			
		||||
    @include keyframes(rotateCentered) {
 | 
			
		||||
         0%   { transform: translateX(-50%) translateY(-50%) rotate(0deg); }
 | 
			
		||||
         100% { transform: translateX(-50%) translateY(-50%) rotate(359deg); }
 | 
			
		||||
     }
 | 
			
		||||
    @include animation-name(rotateCentered);
 | 
			
		||||
    @include animation-duration(0.5s);
 | 
			
		||||
    @include animation-iteration-count(infinite);
 | 
			
		||||
    @include animation-timing-function(linear);
 | 
			
		||||
    border-color: rgba($c, 0.25);
 | 
			
		||||
    border-top-color: rgba($c, 1.0);
 | 
			
		||||
    border-style: solid;
 | 
			
		||||
    border-width: 5px;
 | 
			
		||||
    @include border-radius(100%);
 | 
			
		||||
    @include box-sizing(border-box);
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    height: 0; width: 0;
 | 
			
		||||
    padding: 7%;
 | 
			
		||||
    left: 50%; top: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@-o-keyframes rotation {
 | 
			
		||||
	from {-o-transform: rotate(0deg);}
 | 
			
		||||
	to {-o-transform: rotate(359deg);}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes rotation {
 | 
			
		||||
	from {transform: rotate(0deg);}
 | 
			
		||||
	to {transform: rotate(359deg);}
 | 
			
		||||
@mixin wait-spinner($b: 5px, $c: $colorAlt1) {
 | 
			
		||||
	display: block;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	-webkit-animation: rotation .6s infinite linear;
 | 
			
		||||
	-moz-animation: rotation .6s infinite linear;
 | 
			
		||||
	-o-animation: rotation .6s infinite linear;
 | 
			
		||||
	animation: rotation .6s infinite linear;
 | 
			
		||||
	border-color: rgba($c, 0.25);
 | 
			
		||||
	border-top-color: rgba($c, 1.0);
 | 
			
		||||
	border-style: solid;
 | 
			
		||||
	border-width: $b;
 | 
			
		||||
	@include border-radius(100%);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.t-wait-spinner,
 | 
			
		||||
@@ -96,4 +117,28 @@
 | 
			
		||||
	margin-top: 0 !important;
 | 
			
		||||
	padding: 0 !important;
 | 
			
		||||
	top: 0; left: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loading {
 | 
			
		||||
	// Can be applied to any block element with height and width
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
    &:before,
 | 
			
		||||
    &:after {
 | 
			
		||||
        content: '';
 | 
			
		||||
    }
 | 
			
		||||
	&:before {
 | 
			
		||||
        @include wait-spinner2(5px, $colorLoadingFg);
 | 
			
		||||
        z-index: 10;
 | 
			
		||||
	}
 | 
			
		||||
    &:after {
 | 
			
		||||
        @include absPosDefault();
 | 
			
		||||
        background: $colorLoadingBg;
 | 
			
		||||
        display: block;
 | 
			
		||||
        z-index: 9;
 | 
			
		||||
    }
 | 
			
		||||
    &.tree-item:before {
 | 
			
		||||
        padding: $menuLineH / 4;
 | 
			
		||||
        border-width: 2px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -40,6 +40,11 @@ table {
 | 
			
		||||
	thead, .thead {
 | 
			
		||||
		border-bottom: 1px solid $colorTabHeaderBorder;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:not(.fixed-header) tr th {
 | 
			
		||||
		background-color: $colorTabHeaderBg;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tbody, .tbody {
 | 
			
		||||
		display: table-row-group;
 | 
			
		||||
		tr, .tr {
 | 
			
		||||
@@ -64,7 +69,6 @@ table {
 | 
			
		||||
			display: table-cell;
 | 
			
		||||
		}
 | 
			
		||||
		th, .th {
 | 
			
		||||
			background-color: $colorTabHeaderBg;
 | 
			
		||||
			border-left: 1px solid $colorTabHeaderBorder;
 | 
			
		||||
			color: $colorTabHeaderFg;
 | 
			
		||||
			padding: $tabularTdPadLR $tabularTdPadLR;
 | 
			
		||||
@@ -143,7 +147,7 @@ table {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				width: 100%;
 | 
			
		||||
				height: $tabularHeaderH;
 | 
			
		||||
				background: rgba(#fff, 0.15);
 | 
			
		||||
				background-color: $colorTabHeaderBg;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		tbody, .tbody {
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ $plotDisplayArea: ($legendH + $interiorMargin, 0, $xBarH + $interiorMargin, $yBa
 | 
			
		||||
	.gl-plot-label,
 | 
			
		||||
	.l-plot-label {
 | 
			
		||||
		//		@include test(yellow);
 | 
			
		||||
		color: lighten($colorBodyFg, 20%);
 | 
			
		||||
		color: $colorPlotLabelFg;
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		text-align: center;
 | 
			
		||||
//		text-transform: uppercase;
 | 
			
		||||
 
 | 
			
		||||
@@ -214,8 +214,6 @@
 | 
			
		||||
 | 
			
		||||
	.search-scroll {
 | 
			
		||||
		order: 3;
 | 
			
		||||
 | 
			
		||||
		//padding-right: $rightPadding;
 | 
			
		||||
		margin-top: 4px;
 | 
			
		||||
 | 
			
		||||
		// Adjustable scrolling size
 | 
			
		||||
@@ -227,28 +225,6 @@
 | 
			
		||||
 | 
			
		||||
		.load-icon {
 | 
			
		||||
			position: relative;
 | 
			
		||||
			&.loading {
 | 
			
		||||
				pointer-events: none;
 | 
			
		||||
				margin-left: $leftMargin;
 | 
			
		||||
 | 
			
		||||
				.title-label {
 | 
			
		||||
					// Text styling
 | 
			
		||||
					font-style: italic;
 | 
			
		||||
					font-size: .9em;
 | 
			
		||||
					opacity: 0.5;
 | 
			
		||||
 | 
			
		||||
					// Text positioning
 | 
			
		||||
					margin-left: $iconWidth + $leftMargin;
 | 
			
		||||
					line-height: 24px;
 | 
			
		||||
				}
 | 
			
		||||
				.wait-spinner {
 | 
			
		||||
					margin-left: $leftMargin;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&:not(.loading) {
 | 
			
		||||
				cursor: pointer;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.load-more-button {
 | 
			
		||||
 
 | 
			
		||||
@@ -83,7 +83,6 @@ ul.tree {
 | 
			
		||||
			.icon {
 | 
			
		||||
				&.l-icon-link,
 | 
			
		||||
				&.l-icon-alert {
 | 
			
		||||
					//@include txtShdw($shdwItemTreeIcon);
 | 
			
		||||
					position: absolute;
 | 
			
		||||
					z-index: 2;
 | 
			
		||||
				}
 | 
			
		||||
@@ -105,26 +104,12 @@ ul.tree {
 | 
			
		||||
			@include absPosDefault();
 | 
			
		||||
			display: block;
 | 
			
		||||
			left: $runningItemW + ($interiorMargin * 3);
 | 
			
		||||
			//right: $treeContextTriggerW + $interiorMargin;
 | 
			
		||||
			overflow: hidden;
 | 
			
		||||
			text-overflow: ellipsis;
 | 
			
		||||
			white-space: nowrap;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.loading {
 | 
			
		||||
		pointer-events: none;
 | 
			
		||||
		.label {
 | 
			
		||||
			opacity: 0.5;
 | 
			
		||||
			.title-label {
 | 
			
		||||
				font-style: italic;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		.wait-spinner {
 | 
			
		||||
			margin-left: 14px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.selected {
 | 
			
		||||
		background: $colorItemTreeSelectedBg;
 | 
			
		||||
		color: $colorItemTreeSelectedFg;
 | 
			
		||||
@@ -142,9 +127,6 @@ ul.tree {
 | 
			
		||||
			&:hover {
 | 
			
		||||
				background: rgba($colorBodyFg, 0.1); //lighten($colorBodyBg, 5%);
 | 
			
		||||
				color: pullForward($colorBodyFg, 20%);
 | 
			
		||||
				//.context-trigger {
 | 
			
		||||
				//	display: block;
 | 
			
		||||
				//}
 | 
			
		||||
				.icon {
 | 
			
		||||
					color: $colorItemTreeIconHover;
 | 
			
		||||
				}
 | 
			
		||||
@@ -158,7 +140,6 @@ ul.tree {
 | 
			
		||||
 | 
			
		||||
	.context-trigger {
 | 
			
		||||
		$h: 0.9rem;
 | 
			
		||||
		//display: none;
 | 
			
		||||
		top: -1px;
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		right: $interiorMarginSm;
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@
 | 
			
		||||
	}
 | 
			
		||||
	&.frame-template {
 | 
			
		||||
		.s-btn,
 | 
			
		||||
		.s-menu {
 | 
			
		||||
		.s-menu-btn {
 | 
			
		||||
			height: $ohH;
 | 
			
		||||
			line-height: $ohH;
 | 
			
		||||
			padding: 0 $interiorMargin;
 | 
			
		||||
@@ -56,7 +56,7 @@
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.s-menu:after {
 | 
			
		||||
		.s-menu-btn:after {
 | 
			
		||||
			font-size: 8px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,66 @@
 | 
			
		||||
<!--
 | 
			
		||||
 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.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<div ng-controller="DateTimePickerController" class="l-datetime-picker s-datetime-picker s-menu">
 | 
			
		||||
	<div class="holder">
 | 
			
		||||
		<div class="l-month-year-pager">
 | 
			
		||||
			<a class="pager prev" ng-click="changeMonth(-1)"></a>
 | 
			
		||||
			<span class="val">{{month}} {{year}}</span>
 | 
			
		||||
			<a class="pager next" ng-click="changeMonth(1)"></a>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="l-calendar">
 | 
			
		||||
			<ul class="l-cal-row l-header">
 | 
			
		||||
				<li ng-repeat="day in ['Su','Mo','Tu','We','Th','Fr','Sa']">{{day}}</li>
 | 
			
		||||
			</ul>
 | 
			
		||||
			<ul class="l-cal-row l-body" ng-repeat="row in table">
 | 
			
		||||
				<li ng-repeat="cell in row"
 | 
			
		||||
					ng-click="select(cell)"
 | 
			
		||||
					ng-class='{ "in-month": isInCurrentMonth(cell), selected: isSelected(cell) }'>
 | 
			
		||||
					<div class="prime">{{cell.day}}</div>
 | 
			
		||||
					<div class="sub">{{cell.dayOfYear}}</div>
 | 
			
		||||
				</li>
 | 
			
		||||
			</ul>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="l-time-selects complex datetime"
 | 
			
		||||
			ng-show="options">
 | 
			
		||||
		<div class="field-hints">
 | 
			
		||||
			<span class="hint time md"
 | 
			
		||||
				  ng-repeat="key in ['hours', 'minutes', 'seconds']"
 | 
			
		||||
				  ng-if="options[key]">
 | 
			
		||||
				{{nameFor(key)}}
 | 
			
		||||
			</span>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div>
 | 
			
		||||
			<span class="field control time md"
 | 
			
		||||
				  ng-repeat="key in ['hours', 'minutes', 'seconds']"
 | 
			
		||||
				  ng-if="options[key]">
 | 
			
		||||
				<div class='form-control select'>
 | 
			
		||||
					<select size="1"
 | 
			
		||||
							ng-model="time[key]"
 | 
			
		||||
							ng-options="i for i in optionsFor(key)">
 | 
			
		||||
					</select>
 | 
			
		||||
				</div>
 | 
			
		||||
			</span>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
-->
 | 
			
		||||
<span ng-controller="ViewSwitcherController">
 | 
			
		||||
    <div
 | 
			
		||||
	    class="view-switcher menu-element s-menu"
 | 
			
		||||
	    class="view-switcher menu-element s-menu-btn"
 | 
			
		||||
	    ng-if="view.length > 1"
 | 
			
		||||
	    ng-controller="ClickAwayController as toggle"
 | 
			
		||||
	    >
 | 
			
		||||
 
 | 
			
		||||
@@ -1,69 +1,108 @@
 | 
			
		||||
<!--
 | 
			
		||||
 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.
 | 
			
		||||
 | 
			
		||||
NOTES
 | 
			
		||||
 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.
 | 
			
		||||
 | 
			
		||||
Ticks:
 | 
			
		||||
The thinking is to divide whatever the current time span is by 5,
 | 
			
		||||
and assign values accordingly to 5 statically-positioned ticks. So the tick x-position is a static percentage
 | 
			
		||||
of the total width available, and the labels change dynamically. This is consistent
 | 
			
		||||
with our current approach to the time axis of plots.
 | 
			
		||||
I'm keeping the number of ticks low so that when the view portal gets narrow,
 | 
			
		||||
the tick labels won't collide with each other. For extra credit, add/remove ticks as the user resizes the view area.
 | 
			
		||||
Note: this eval needs to be based on the whatever is containing the
 | 
			
		||||
time-controller component, not the whole browser window.
 | 
			
		||||
 | 
			
		||||
Range indicator and slider knobs:
 | 
			
		||||
The left and right properties used in .slider .range-holder and  the .knobs are
 | 
			
		||||
CSS offsets from the left and right of their respective containers. You
 | 
			
		||||
may want or need to calculate those positions as pure offsets from the start datetime
 | 
			
		||||
(or left, as it were) and set them as left properties. No problem if so, but
 | 
			
		||||
we'll need to tweak the CSS tiny bit to get the center of the knobs to line up
 | 
			
		||||
properly on the range left and right bounds.
 | 
			
		||||
 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.
 | 
			
		||||
-->
 | 
			
		||||
<div ng-controller="TimeRangeController">
 | 
			
		||||
    <div class="l-time-range-inputs-holder">
 | 
			
		||||
        <span class="l-time-range-inputs-elem ui-symbol type-icon">C</span>
 | 
			
		||||
        <span class="l-time-range-input" ng-controller="ToggleController as t1">
 | 
			
		||||
            <!--<span class="lbl">Start</span>-->
 | 
			
		||||
            <span class="s-btn time-range-start">
 | 
			
		||||
                <input type="text"
 | 
			
		||||
                       ng-model="boundsModel.start"
 | 
			
		||||
                       ng-class="{ error: !boundsModel.startValid }">
 | 
			
		||||
                </input>
 | 
			
		||||
                <a class="ui-symbol icon icon-calendar" ng-click="t1.toggle()"></a>
 | 
			
		||||
                <mct-popup ng-if="t1.isActive()">
 | 
			
		||||
                    <div mct-click-elsewhere="t1.setState(false)">
 | 
			
		||||
                        <mct-control key="'datetime-picker'"
 | 
			
		||||
                                     ng-model="ngModel.outer"
 | 
			
		||||
                                     field="'start'"
 | 
			
		||||
                                     options="{ hours: true }">
 | 
			
		||||
                        </mct-control>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </mct-popup>
 | 
			
		||||
            </span>
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
<div ng-init="
 | 
			
		||||
notes = 'Temporarily using an array to populate ticks so I can see what I\'m doing';
 | 
			
		||||
ticks = [
 | 
			
		||||
'00:00',
 | 
			
		||||
'00:30',
 | 
			
		||||
'01:00',
 | 
			
		||||
'01:30',
 | 
			
		||||
'02:00'
 | 
			
		||||
];
 | 
			
		||||
"></div>
 | 
			
		||||
        <span class="l-time-range-inputs-elem lbl">to</span>
 | 
			
		||||
 | 
			
		||||
<div class="l-time-controller">
 | 
			
		||||
	<div class="l-time-range-inputs-holder">
 | 
			
		||||
		Start: <input type="date" />
 | 
			
		||||
		End: <input type="date" />
 | 
			
		||||
	</div>
 | 
			
		||||
        <span class="l-time-range-input" ng-controller="ToggleController as t2">
 | 
			
		||||
            <!--<span class="lbl">End</span>-->
 | 
			
		||||
            <span class="s-btn l-time-range-input">
 | 
			
		||||
                <input type="text"
 | 
			
		||||
                       ng-model="boundsModel.end"
 | 
			
		||||
                       ng-class="{ error: !boundsModel.endValid }">
 | 
			
		||||
                </input>
 | 
			
		||||
                <a class="ui-symbol icon icon-calendar" ng-click="t2.toggle()">
 | 
			
		||||
                </a>
 | 
			
		||||
                <mct-popup ng-if="t2.isActive()">
 | 
			
		||||
                    <div mct-click-elsewhere="t2.setState(false)">
 | 
			
		||||
                        <mct-control key="'datetime-picker'"
 | 
			
		||||
                                     ng-model="ngModel.outer"
 | 
			
		||||
                                     field="'end'"
 | 
			
		||||
                                     options="{ hours: true }">
 | 
			
		||||
                        </mct-control>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </mct-popup>
 | 
			
		||||
            </span> 
 | 
			
		||||
        </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
	<div class="l-time-range-slider-holder">
 | 
			
		||||
		<div class="l-time-range-slider">
 | 
			
		||||
			<div class="slider">
 | 
			
		||||
				<div class="slot range-holder">
 | 
			
		||||
					<div class="range" style="left: 0%; right: 30%;"></div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="knob knob-l" style="left: 0%;">
 | 
			
		||||
					<div class="range-value">05/22 14:46</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="knob knob-r" style="right: 30%;">
 | 
			
		||||
					<div class="range-value">07/22 01:21</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
    <div class="l-time-range-slider-holder">
 | 
			
		||||
        <div class="l-time-range-slider">
 | 
			
		||||
            <div class="slider"
 | 
			
		||||
                 mct-resize="spanWidth = bounds.width">
 | 
			
		||||
                <div class="knob knob-l"
 | 
			
		||||
                     mct-drag-down="startLeftDrag()"
 | 
			
		||||
                     mct-drag="leftDrag(delta[0])"
 | 
			
		||||
                     ng-style="{ left: startInnerPct }">
 | 
			
		||||
                    <div class="range-value">{{startInnerText}}</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="knob knob-r"
 | 
			
		||||
                     mct-drag-down="startRightDrag()"
 | 
			
		||||
                     mct-drag="rightDrag(delta[0])"
 | 
			
		||||
                     ng-style="{ right: endInnerPct }">
 | 
			
		||||
                    <div class="range-value">{{endInnerText}}</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="slot range-holder">
 | 
			
		||||
                    <div class="range"
 | 
			
		||||
                         mct-drag-down="startMiddleDrag()"
 | 
			
		||||
                         mct-drag="middleDrag(delta[0])"
 | 
			
		||||
                         ng-style="{ left: startInnerPct, right: endInnerPct}">
 | 
			
		||||
                        <div class="toi-line"></div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
	<div class="l-time-range-ticks-holder">
 | 
			
		||||
		<div class="l-time-range-ticks">
 | 
			
		||||
			<div
 | 
			
		||||
				ng-repeat="tick in ticks"
 | 
			
		||||
				ng-style="{ left: $index * 25 + '%' }"
 | 
			
		||||
				class="tick tick-x"
 | 
			
		||||
				>
 | 
			
		||||
				<span class="l-time-range-tick-label">{{tick}}</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
    <div class="l-time-range-ticks-holder">
 | 
			
		||||
        <div class="l-time-range-ticks">
 | 
			
		||||
            <div
 | 
			
		||||
                ng-repeat="tick in ticks"
 | 
			
		||||
                ng-style="{ left: $index * (100 / (ticks.length - 1)) + '%' }"
 | 
			
		||||
                class="tick tick-x"
 | 
			
		||||
                >
 | 
			
		||||
                <span class="l-time-range-tick-label">{{tick}}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,202 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,Promise*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    [ 'moment' ],
 | 
			
		||||
    function (moment) {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        var TIME_NAMES = {
 | 
			
		||||
                'hours': "Hour",
 | 
			
		||||
                'minutes': "Minute",
 | 
			
		||||
                'seconds': "Second"
 | 
			
		||||
            },
 | 
			
		||||
            MONTHS = moment.months(),
 | 
			
		||||
            TIME_OPTIONS = (function makeRanges() {
 | 
			
		||||
                var arr = [];
 | 
			
		||||
                while (arr.length < 60) {
 | 
			
		||||
                    arr.push(arr.length);
 | 
			
		||||
                }
 | 
			
		||||
                return {
 | 
			
		||||
                    hours: arr.slice(0, 24),
 | 
			
		||||
                    minutes: arr,
 | 
			
		||||
                    seconds: arr
 | 
			
		||||
                };
 | 
			
		||||
            }());
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Controller to support the date-time picker.
 | 
			
		||||
         *
 | 
			
		||||
         * Adds/uses the following properties in scope:
 | 
			
		||||
         * * `year`: Year being displayed in picker
 | 
			
		||||
         * * `month`: Month being displayed
 | 
			
		||||
         * * `table`: Table being displayed; array of arrays of
 | 
			
		||||
         *   * `day`: Day of month
 | 
			
		||||
         *   * `dayOfYear`: Day of year
 | 
			
		||||
         *   * `month`: Month associated with the day
 | 
			
		||||
         *   * `year`: Year associated with the day.
 | 
			
		||||
         * * `date`: Date chosen
 | 
			
		||||
         *   * `year`: Year selected
 | 
			
		||||
         *   * `month`: Month selected (0-indexed)
 | 
			
		||||
         *   * `day`: Day of month selected
 | 
			
		||||
         * * `time`: Chosen time (hours/minutes/seconds)
 | 
			
		||||
         *   * `hours`: Hours chosen
 | 
			
		||||
         *   * `minutes`: Minutes chosen
 | 
			
		||||
         *   * `seconds`: Seconds chosen
 | 
			
		||||
         *
 | 
			
		||||
         * Months are zero-indexed, day-of-months are one-indexed.
 | 
			
		||||
         */
 | 
			
		||||
        function DateTimePickerController($scope, now) {
 | 
			
		||||
            var year,
 | 
			
		||||
                month, // For picker state, not model state
 | 
			
		||||
                interacted = false;
 | 
			
		||||
 | 
			
		||||
            function generateTable() {
 | 
			
		||||
                var m = moment.utc({ year: year, month: month }).day(0),
 | 
			
		||||
                    table = [],
 | 
			
		||||
                    row,
 | 
			
		||||
                    col;
 | 
			
		||||
 | 
			
		||||
                for (row = 0; row < 6; row += 1) {
 | 
			
		||||
                    table.push([]);
 | 
			
		||||
                    for (col = 0; col < 7; col += 1) {
 | 
			
		||||
                        table[row].push({
 | 
			
		||||
                            year: m.year(),
 | 
			
		||||
                            month: m.month(),
 | 
			
		||||
                            day: m.date(),
 | 
			
		||||
                            dayOfYear: m.dayOfYear()
 | 
			
		||||
                        });
 | 
			
		||||
                        m.add(1, 'days'); // Next day!
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return table;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateScopeForMonth() {
 | 
			
		||||
                $scope.month = MONTHS[month];
 | 
			
		||||
                $scope.year = year;
 | 
			
		||||
                $scope.table = generateTable();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateFromModel(ngModel) {
 | 
			
		||||
                var m;
 | 
			
		||||
 | 
			
		||||
                m = moment.utc(ngModel);
 | 
			
		||||
 | 
			
		||||
                $scope.date = {
 | 
			
		||||
                    year: m.year(),
 | 
			
		||||
                    month: m.month(),
 | 
			
		||||
                    day: m.date()
 | 
			
		||||
                };
 | 
			
		||||
                $scope.time = {
 | 
			
		||||
                    hours: m.hour(),
 | 
			
		||||
                    minutes: m.minute(),
 | 
			
		||||
                    seconds: m.second()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                //window.alert($scope.date.day + " " + ngModel);
 | 
			
		||||
 | 
			
		||||
                // Zoom to that date in the picker, but
 | 
			
		||||
                // only if the user hasn't interacted with it yet.
 | 
			
		||||
                if (!interacted) {
 | 
			
		||||
                    year = m.year();
 | 
			
		||||
                    month = m.month();
 | 
			
		||||
                    updateScopeForMonth();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateFromView() {
 | 
			
		||||
                var m = moment.utc({
 | 
			
		||||
                    year: $scope.date.year,
 | 
			
		||||
                    month: $scope.date.month,
 | 
			
		||||
                    day: $scope.date.day,
 | 
			
		||||
                    hour: $scope.time.hours,
 | 
			
		||||
                    minute: $scope.time.minutes,
 | 
			
		||||
                    second: $scope.time.seconds
 | 
			
		||||
                });
 | 
			
		||||
                $scope.ngModel[$scope.field] = m.valueOf();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $scope.isInCurrentMonth = function (cell) {
 | 
			
		||||
                return cell.month === month;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            $scope.isSelected = function (cell) {
 | 
			
		||||
                var date = $scope.date || {};
 | 
			
		||||
                return cell.day === date.day &&
 | 
			
		||||
                    cell.month === date.month &&
 | 
			
		||||
                    cell.year === date.year;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            $scope.select = function (cell) {
 | 
			
		||||
                $scope.date = $scope.date || {};
 | 
			
		||||
                $scope.date.month = cell.month;
 | 
			
		||||
                $scope.date.year = cell.year;
 | 
			
		||||
                $scope.date.day = cell.day;
 | 
			
		||||
                updateFromView();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            $scope.dateEquals = function (d1, d2) {
 | 
			
		||||
                return d1.year === d2.year &&
 | 
			
		||||
                    d1.month === d2.month &&
 | 
			
		||||
                    d1.day === d2.day;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            $scope.changeMonth = function (delta) {
 | 
			
		||||
                month += delta;
 | 
			
		||||
                if (month > 11) {
 | 
			
		||||
                    month = 0;
 | 
			
		||||
                    year += 1;
 | 
			
		||||
                }
 | 
			
		||||
                if (month < 0) {
 | 
			
		||||
                    month = 11;
 | 
			
		||||
                    year -= 1;
 | 
			
		||||
                }
 | 
			
		||||
                interacted = true;
 | 
			
		||||
                updateScopeForMonth();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            $scope.nameFor = function (key) {
 | 
			
		||||
                return TIME_NAMES[key];
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            $scope.optionsFor = function (key) {
 | 
			
		||||
                return TIME_OPTIONS[key];
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            updateScopeForMonth();
 | 
			
		||||
 | 
			
		||||
            // Ensure some useful default
 | 
			
		||||
            $scope.ngModel[$scope.field] =
 | 
			
		||||
                $scope.ngModel[$scope.field] === undefined ?
 | 
			
		||||
                        now() : $scope.ngModel[$scope.field];
 | 
			
		||||
 | 
			
		||||
            $scope.$watch('ngModel[field]', updateFromModel);
 | 
			
		||||
            $scope.$watchCollection('date', updateFromView);
 | 
			
		||||
            $scope.$watchCollection('time', updateFromView);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return DateTimePickerController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										302
									
								
								platform/commonUI/general/src/controllers/TimeRangeController.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,302 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,Promise*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ['moment'],
 | 
			
		||||
    function (moment) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
 | 
			
		||||
            TICK_SPACING_PX = 150;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @memberof platform/commonUI/general
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function TimeConductorController($scope, now) {
 | 
			
		||||
            var tickCount = 2,
 | 
			
		||||
                innerMinimumSpan = 1000, // 1 second
 | 
			
		||||
                outerMinimumSpan = 1000 * 60 * 60, // 1 hour
 | 
			
		||||
                initialDragValue;
 | 
			
		||||
 | 
			
		||||
            function formatTimestamp(ts) {
 | 
			
		||||
                return moment.utc(ts).format(DATE_FORMAT);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function parseTimestamp(text) {
 | 
			
		||||
                var m = moment.utc(text, DATE_FORMAT);
 | 
			
		||||
                if (m.isValid()) {
 | 
			
		||||
                    return m.valueOf();
 | 
			
		||||
                } else {
 | 
			
		||||
                    throw new Error("Could not parse " + text);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // From 0.0-1.0 to "0%"-"1%"
 | 
			
		||||
            function toPercent(p) {
 | 
			
		||||
                return (100 * p) + "%";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateTicks() {
 | 
			
		||||
                var i, p, ts, start, end, span;
 | 
			
		||||
                end = $scope.ngModel.outer.end;
 | 
			
		||||
                start = $scope.ngModel.outer.start;
 | 
			
		||||
                span = end - start;
 | 
			
		||||
                $scope.ticks = [];
 | 
			
		||||
                for (i = 0; i < tickCount; i += 1) {
 | 
			
		||||
                    p = i / (tickCount - 1);
 | 
			
		||||
                    ts = p * span + start;
 | 
			
		||||
                    $scope.ticks.push(formatTimestamp(ts));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateSpanWidth(w) {
 | 
			
		||||
                tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2);
 | 
			
		||||
                updateTicks();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateViewForInnerSpanFromModel(ngModel) {
 | 
			
		||||
                var span = ngModel.outer.end - ngModel.outer.start;
 | 
			
		||||
 | 
			
		||||
                // Expose readable dates for the knobs
 | 
			
		||||
                $scope.startInnerText = formatTimestamp(ngModel.inner.start);
 | 
			
		||||
                $scope.endInnerText = formatTimestamp(ngModel.inner.end);
 | 
			
		||||
 | 
			
		||||
                // And positions for the knobs
 | 
			
		||||
                $scope.startInnerPct =
 | 
			
		||||
                    toPercent((ngModel.inner.start - ngModel.outer.start) / span);
 | 
			
		||||
                $scope.endInnerPct =
 | 
			
		||||
                    toPercent((ngModel.outer.end - ngModel.inner.end) / span);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function defaultBounds() {
 | 
			
		||||
                var t = now();
 | 
			
		||||
                return {
 | 
			
		||||
                    start: t - 24 * 3600 * 1000, // One day
 | 
			
		||||
                    end: t
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function copyBounds(bounds) {
 | 
			
		||||
                return { start: bounds.start, end: bounds.end };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateBoundsTextForProperty(ngModel, property) {
 | 
			
		||||
                try {
 | 
			
		||||
                    if (!$scope.boundsModel[property] ||
 | 
			
		||||
                            parseTimestamp($scope.boundsModel[property]) !==
 | 
			
		||||
                                ngModel.outer[property]) {
 | 
			
		||||
                        $scope.boundsModel[property] =
 | 
			
		||||
                            formatTimestamp(ngModel.outer[property]);
 | 
			
		||||
                    }
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    // User-entered text is invalid, so leave it be
 | 
			
		||||
                    // until they fix it.
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateBoundsText(ngModel) {
 | 
			
		||||
                updateBoundsTextForProperty(ngModel, 'start');
 | 
			
		||||
                updateBoundsTextForProperty(ngModel, 'end');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateViewFromModel(ngModel) {
 | 
			
		||||
                var t = now();
 | 
			
		||||
 | 
			
		||||
                ngModel = ngModel || {};
 | 
			
		||||
                ngModel.outer = ngModel.outer || defaultBounds();
 | 
			
		||||
                ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
 | 
			
		||||
 | 
			
		||||
                // First, dates for the date pickers for outer bounds
 | 
			
		||||
                updateBoundsText(ngModel);
 | 
			
		||||
 | 
			
		||||
                // Then various updates for the inner span
 | 
			
		||||
                updateViewForInnerSpanFromModel(ngModel);
 | 
			
		||||
 | 
			
		||||
                // Stick it back is scope (in case we just set defaults)
 | 
			
		||||
                $scope.ngModel = ngModel;
 | 
			
		||||
 | 
			
		||||
                updateTicks();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function startLeftDrag() {
 | 
			
		||||
                initialDragValue = $scope.ngModel.inner.start;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function startRightDrag() {
 | 
			
		||||
                initialDragValue = $scope.ngModel.inner.end;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function startMiddleDrag() {
 | 
			
		||||
                initialDragValue = {
 | 
			
		||||
                    start: $scope.ngModel.inner.start,
 | 
			
		||||
                    end: $scope.ngModel.inner.end
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function toMillis(pixels) {
 | 
			
		||||
                var span = $scope.ngModel.outer.end - $scope.ngModel.outer.start;
 | 
			
		||||
                return (pixels / $scope.spanWidth) * span;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function clamp(value, low, high) {
 | 
			
		||||
                return Math.max(low, Math.min(high, value));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function leftDrag(pixels) {
 | 
			
		||||
                var delta = toMillis(pixels);
 | 
			
		||||
                $scope.ngModel.inner.start = clamp(
 | 
			
		||||
                    initialDragValue + delta,
 | 
			
		||||
                    $scope.ngModel.outer.start,
 | 
			
		||||
                    $scope.ngModel.inner.end - innerMinimumSpan
 | 
			
		||||
                );
 | 
			
		||||
                updateViewFromModel($scope.ngModel);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function rightDrag(pixels) {
 | 
			
		||||
                var delta = toMillis(pixels);
 | 
			
		||||
                $scope.ngModel.inner.end = clamp(
 | 
			
		||||
                    initialDragValue + delta,
 | 
			
		||||
                    $scope.ngModel.inner.start + innerMinimumSpan,
 | 
			
		||||
                    $scope.ngModel.outer.end
 | 
			
		||||
                );
 | 
			
		||||
                updateViewFromModel($scope.ngModel);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function middleDrag(pixels) {
 | 
			
		||||
                var delta = toMillis(pixels),
 | 
			
		||||
                    edge = delta < 0 ? 'start' : 'end',
 | 
			
		||||
                    opposite = delta < 0 ? 'end' : 'start';
 | 
			
		||||
 | 
			
		||||
                // Adjust the position of the edge in the direction of drag
 | 
			
		||||
                $scope.ngModel.inner[edge] = clamp(
 | 
			
		||||
                    initialDragValue[edge] + delta,
 | 
			
		||||
                    $scope.ngModel.outer.start,
 | 
			
		||||
                    $scope.ngModel.outer.end
 | 
			
		||||
                );
 | 
			
		||||
                // Adjust opposite knob to maintain span
 | 
			
		||||
                $scope.ngModel.inner[opposite] = $scope.ngModel.inner[edge] +
 | 
			
		||||
                    initialDragValue[opposite] - initialDragValue[edge];
 | 
			
		||||
 | 
			
		||||
                updateViewFromModel($scope.ngModel);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateOuterStart(t) {
 | 
			
		||||
                var ngModel = $scope.ngModel;
 | 
			
		||||
 | 
			
		||||
                ngModel.outer.start = t;
 | 
			
		||||
 | 
			
		||||
                ngModel.outer.end = Math.max(
 | 
			
		||||
                    ngModel.outer.start + outerMinimumSpan,
 | 
			
		||||
                    ngModel.outer.end
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                ngModel.inner.start =
 | 
			
		||||
                    Math.max(ngModel.outer.start, ngModel.inner.start);
 | 
			
		||||
                ngModel.inner.end = Math.max(
 | 
			
		||||
                    ngModel.inner.start + innerMinimumSpan,
 | 
			
		||||
                    ngModel.inner.end
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                updateViewForInnerSpanFromModel(ngModel);
 | 
			
		||||
                updateTicks();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateOuterEnd(t) {
 | 
			
		||||
                var ngModel = $scope.ngModel;
 | 
			
		||||
 | 
			
		||||
                ngModel.outer.end = t;
 | 
			
		||||
 | 
			
		||||
                ngModel.outer.start = Math.min(
 | 
			
		||||
                    ngModel.outer.end - outerMinimumSpan,
 | 
			
		||||
                    ngModel.outer.start
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                ngModel.inner.end =
 | 
			
		||||
                    Math.min(ngModel.outer.end, ngModel.inner.end);
 | 
			
		||||
                ngModel.inner.start = Math.min(
 | 
			
		||||
                    ngModel.inner.end - innerMinimumSpan,
 | 
			
		||||
                    ngModel.inner.start
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                updateViewForInnerSpanFromModel(ngModel);
 | 
			
		||||
                updateTicks();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateStartFromText(value) {
 | 
			
		||||
                try {
 | 
			
		||||
                    updateOuterStart(parseTimestamp(value));
 | 
			
		||||
                    updateBoundsTextForProperty($scope.ngModel, 'end');
 | 
			
		||||
                    $scope.boundsModel.startValid = true;
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    $scope.boundsModel.startValid = false;
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateEndFromText(value) {
 | 
			
		||||
                try {
 | 
			
		||||
                    updateOuterEnd(parseTimestamp(value));
 | 
			
		||||
                    updateBoundsTextForProperty($scope.ngModel, 'start');
 | 
			
		||||
                    $scope.boundsModel.endValid = true;
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    $scope.boundsModel.endValid = false;
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateStartFromPicker(value) {
 | 
			
		||||
                updateOuterStart(value);
 | 
			
		||||
                updateBoundsText($scope.ngModel);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateEndFromPicker(value) {
 | 
			
		||||
                updateOuterEnd(value);
 | 
			
		||||
                updateBoundsText($scope.ngModel);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $scope.startLeftDrag = startLeftDrag;
 | 
			
		||||
            $scope.startRightDrag = startRightDrag;
 | 
			
		||||
            $scope.startMiddleDrag = startMiddleDrag;
 | 
			
		||||
            $scope.leftDrag = leftDrag;
 | 
			
		||||
            $scope.rightDrag = rightDrag;
 | 
			
		||||
            $scope.middleDrag = middleDrag;
 | 
			
		||||
 | 
			
		||||
            $scope.state = false;
 | 
			
		||||
            $scope.ticks = [];
 | 
			
		||||
            $scope.boundsModel = {};
 | 
			
		||||
 | 
			
		||||
            // Initialize scope to defaults
 | 
			
		||||
            updateViewFromModel($scope.ngModel);
 | 
			
		||||
 | 
			
		||||
            $scope.$watchCollection("ngModel", updateViewFromModel);
 | 
			
		||||
            $scope.$watch("spanWidth", updateSpanWidth);
 | 
			
		||||
            $scope.$watch("ngModel.outer.start", updateStartFromPicker);
 | 
			
		||||
            $scope.$watch("ngModel.outer.end", updateEndFromPicker);
 | 
			
		||||
            $scope.$watch("boundsModel.start", updateStartFromText);
 | 
			
		||||
            $scope.$watch("boundsModel.end", updateEndFromText);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return TimeConductorController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,77 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The `mct-click-elsewhere` directive will evaluate its
 | 
			
		||||
         * associated expression whenever a `mousedown` occurs anywhere
 | 
			
		||||
         * outside of the element that has the `mct-click-elsewhere`
 | 
			
		||||
         * directive attached. This is useful for dismissing popups
 | 
			
		||||
         * and the like.
 | 
			
		||||
         */
 | 
			
		||||
        function MCTClickElsewhere($document) {
 | 
			
		||||
 | 
			
		||||
            // Link; install event handlers.
 | 
			
		||||
            function link(scope, element, attrs) {
 | 
			
		||||
                // Keep a reference to the body, to attach/detach
 | 
			
		||||
                // mouse event handlers; mousedown and mouseup cannot
 | 
			
		||||
                // only be attached to the element being linked, as the
 | 
			
		||||
                // mouse may leave this element during the drag.
 | 
			
		||||
                var body = $document.find('body');
 | 
			
		||||
 | 
			
		||||
                function clickBody(event) {
 | 
			
		||||
                    var x = event.clientX,
 | 
			
		||||
                        y = event.clientY,
 | 
			
		||||
                        rect = element[0].getBoundingClientRect(),
 | 
			
		||||
                        xMin = rect.left,
 | 
			
		||||
                        xMax = xMin + rect.width,
 | 
			
		||||
                        yMin = rect.top,
 | 
			
		||||
                        yMax = yMin + rect.height;
 | 
			
		||||
 | 
			
		||||
                    if (x < xMin || x > xMax || y < yMin || y > yMax) {
 | 
			
		||||
                        scope.$eval(attrs.mctClickElsewhere);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                body.on("mousedown", clickBody);
 | 
			
		||||
                scope.$on("$destroy", function () {
 | 
			
		||||
                    body.off("mousedown", clickBody);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                // mct-drag only makes sense as an attribute
 | 
			
		||||
                restrict: "A",
 | 
			
		||||
                // Link function, to install event handlers
 | 
			
		||||
                link: link
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MCTClickElsewhere;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										73
									
								
								platform/commonUI/general/src/directives/MCTPopup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,73 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        var TEMPLATE = "<div></div>";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The `mct-popup` directive may be used to display elements
 | 
			
		||||
         * which "pop up" over other parts of the page. Typically, this is
 | 
			
		||||
         * done in conjunction with an `ng-if` to control the visibility
 | 
			
		||||
         * of the popup.
 | 
			
		||||
         *
 | 
			
		||||
         * Example of usage:
 | 
			
		||||
         *
 | 
			
		||||
         *     <mct-popup ng-if="someExpr">
 | 
			
		||||
         *         <span>These are the contents of the popup!</span>
 | 
			
		||||
         *     </mct-popup>
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/commonUI/general
 | 
			
		||||
         * @param $compile Angular's $compile service
 | 
			
		||||
         * @param {platform/commonUI/general.PopupService} popupService
 | 
			
		||||
         */
 | 
			
		||||
        function MCTPopup($compile, popupService) {
 | 
			
		||||
            function link(scope, element, attrs, ctrl, transclude) {
 | 
			
		||||
                var div = $compile(TEMPLATE)(scope),
 | 
			
		||||
                    rect = element.parent()[0].getBoundingClientRect(),
 | 
			
		||||
                    position = [ rect.left, rect.top ],
 | 
			
		||||
                    popup = popupService.display(div, position);
 | 
			
		||||
 | 
			
		||||
                transclude(function (clone) {
 | 
			
		||||
                    div.append(clone);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                scope.$on('$destroy', function () {
 | 
			
		||||
                    popup.dismiss();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                restrict: "E",
 | 
			
		||||
                transclude: true,
 | 
			
		||||
                link: link,
 | 
			
		||||
                scope: {}
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MCTPopup;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -58,6 +58,7 @@ define(
 | 
			
		||||
            // Link; start listening for changes to an element's size
 | 
			
		||||
            function link(scope, element, attrs) {
 | 
			
		||||
                var lastBounds,
 | 
			
		||||
                    linking = true,
 | 
			
		||||
                    active = true;
 | 
			
		||||
 | 
			
		||||
                // Determine how long to wait before the next update
 | 
			
		||||
@@ -74,7 +75,9 @@ define(
 | 
			
		||||
                            lastBounds.width !== bounds.width ||
 | 
			
		||||
                            lastBounds.height !== bounds.height) {
 | 
			
		||||
                        scope.$eval(attrs.mctResize, { bounds: bounds });
 | 
			
		||||
                        scope.$apply(); // Trigger a digest
 | 
			
		||||
                        if (!linking) { // Avoid apply-in-a-digest
 | 
			
		||||
                            scope.$apply();
 | 
			
		||||
                        }
 | 
			
		||||
                        lastBounds = bounds;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -101,6 +104,9 @@ define(
 | 
			
		||||
 | 
			
		||||
                // Handle the initial callback
 | 
			
		||||
                onInterval();
 | 
			
		||||
 | 
			
		||||
                // Trigger scope.$apply on subsequent changes
 | 
			
		||||
                linking = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										89
									
								
								platform/commonUI/general/src/services/Popup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,89 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * A popup is an element that has been displayed at a particular
 | 
			
		||||
         * location within the page.
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/commonUI/general
 | 
			
		||||
         * @param element the jqLite-wrapped element
 | 
			
		||||
         * @param {object} styles an object containing key-value pairs
 | 
			
		||||
         *        of styles used to position the element.
 | 
			
		||||
         */
 | 
			
		||||
        function Popup(element, styles) {
 | 
			
		||||
            this.styles = styles;
 | 
			
		||||
            this.element = element;
 | 
			
		||||
 | 
			
		||||
            element.css(styles);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Stop showing this popup.
 | 
			
		||||
         */
 | 
			
		||||
        Popup.prototype.dismiss = function () {
 | 
			
		||||
            this.element.remove();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if this popup is positioned such that it appears to the
 | 
			
		||||
         * left of its original location.
 | 
			
		||||
         * @returns {boolean} true if the popup goes left
 | 
			
		||||
         */
 | 
			
		||||
        Popup.prototype.goesLeft = function () {
 | 
			
		||||
            return !this.styles.left;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if this popup is positioned such that it appears to the
 | 
			
		||||
         * right of its original location.
 | 
			
		||||
         * @returns {boolean} true if the popup goes right
 | 
			
		||||
         */
 | 
			
		||||
        Popup.prototype.goesRight = function () {
 | 
			
		||||
            return !this.styles.right;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if this popup is positioned such that it appears above
 | 
			
		||||
         * its original location.
 | 
			
		||||
         * @returns {boolean} true if the popup goes up
 | 
			
		||||
         */
 | 
			
		||||
        Popup.prototype.goesUp = function () {
 | 
			
		||||
            return !this.styles.top;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if this popup is positioned such that it appears below
 | 
			
		||||
         * its original location.
 | 
			
		||||
         * @returns {boolean} true if the popup goes down
 | 
			
		||||
         */
 | 
			
		||||
        Popup.prototype.goesDown = function () {
 | 
			
		||||
            return !this.styles.bottom;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return Popup;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										127
									
								
								platform/commonUI/general/src/services/PopupService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,127 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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(
 | 
			
		||||
    ['./Popup'],
 | 
			
		||||
    function (Popup) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Displays popup elements at specific positions within the document.
 | 
			
		||||
         * @memberof platform/commonUI/general
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function PopupService($document, $window) {
 | 
			
		||||
            this.$document = $document;
 | 
			
		||||
            this.$window = $window;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Options controlling how the popup is displaed.
 | 
			
		||||
         *
 | 
			
		||||
         * @typedef PopupOptions
 | 
			
		||||
         * @memberof platform/commonUI/general
 | 
			
		||||
         * @property {number} [offsetX] the horizontal distance, in pixels,
 | 
			
		||||
         *           to offset the element in whichever direction it is
 | 
			
		||||
         *           displayed. Defaults to 0.
 | 
			
		||||
         * @property {number} [offsetY] the vertical distance, in pixels,
 | 
			
		||||
         *           to offset the element in whichever direction it is
 | 
			
		||||
         *           displayed. Defaults to 0.
 | 
			
		||||
         * @property {number} [marginX] the horizontal position, in pixels,
 | 
			
		||||
         *           after which to prefer to display the element to the left.
 | 
			
		||||
         *           If negative, this is relative to the right edge of the
 | 
			
		||||
         *           page. Defaults to half the window's width.
 | 
			
		||||
         * @property {number} [marginY] the vertical position, in pixels,
 | 
			
		||||
         *           after which to prefer to display the element upward.
 | 
			
		||||
         *           If negative, this is relative to the right edge of the
 | 
			
		||||
         *           page. Defaults to half the window's height.
 | 
			
		||||
         * @property {string} [leftClass] class to apply when shifting to the left
 | 
			
		||||
         * @property {string} [rightClass] class to apply when shifting to the right
 | 
			
		||||
         * @property {string} [upClass] class to apply when shifting upward
 | 
			
		||||
         * @property {string} [downClass] class to apply when shifting downward
 | 
			
		||||
         */
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Display a popup at a particular location. The location chosen will
 | 
			
		||||
         * be the corner of the element; the element will be positioned either
 | 
			
		||||
         * to the left or the right of this point depending on available
 | 
			
		||||
         * horizontal space, and will similarly be shifted upward or downward
 | 
			
		||||
         * depending on available vertical space.
 | 
			
		||||
         *
 | 
			
		||||
         * @param element the jqLite-wrapped DOM element to pop up
 | 
			
		||||
         * @param {number[]} position x,y position of the element, in
 | 
			
		||||
         *        pixel coordinates. Negative values are interpreted as
 | 
			
		||||
         *        relative to the right or bottom of the window.
 | 
			
		||||
         * @param {PopupOptions} [options] additional options to control
 | 
			
		||||
         *        positioning of the popup
 | 
			
		||||
         * @returns {platform/commonUI/general.Popup} the popup
 | 
			
		||||
         */
 | 
			
		||||
        PopupService.prototype.display = function (element, position, options) {
 | 
			
		||||
            var $document = this.$document,
 | 
			
		||||
                $window = this.$window,
 | 
			
		||||
                body = $document.find('body'),
 | 
			
		||||
                winDim = [ $window.innerWidth, $window.innerHeight ],
 | 
			
		||||
                styles = { position: 'absolute' },
 | 
			
		||||
                margin,
 | 
			
		||||
                offset,
 | 
			
		||||
                bubble;
 | 
			
		||||
 | 
			
		||||
            function adjustNegatives(value, index) {
 | 
			
		||||
                return value < 0 ? (value + winDim[index]) : value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Defaults
 | 
			
		||||
            options = options || {};
 | 
			
		||||
            offset = [
 | 
			
		||||
                options.offsetX !== undefined ? options.offsetX : 0,
 | 
			
		||||
                options.offsetY !== undefined ? options.offsetY : 0
 | 
			
		||||
            ];
 | 
			
		||||
            margin = [ options.marginX, options.marginY ].map(function (m, i) {
 | 
			
		||||
                return m === undefined ? (winDim[i] / 2) : m;
 | 
			
		||||
            }).map(adjustNegatives);
 | 
			
		||||
 | 
			
		||||
            position = position.map(adjustNegatives);
 | 
			
		||||
 | 
			
		||||
            if (position[0] > margin[0]) {
 | 
			
		||||
                styles.right = (winDim[0] - position[0] + offset[0]) + 'px';
 | 
			
		||||
            } else {
 | 
			
		||||
                styles.left =  (position[0] + offset[0]) + 'px';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (position[1] > margin[1]) {
 | 
			
		||||
                styles.bottom = (winDim[1] - position[1] + offset[1]) + 'px';
 | 
			
		||||
            } else {
 | 
			
		||||
                styles.top = (position[1] + offset[1]) + 'px';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Add the menu to the body
 | 
			
		||||
            body.append(element);
 | 
			
		||||
 | 
			
		||||
            // Return a function to dismiss the bubble
 | 
			
		||||
            return new Popup(element, styles);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return PopupService;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,63 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/controllers/DateTimePickerController"],
 | 
			
		||||
    function (DateTimePickerController) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        describe("The DateTimePickerController", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
                mockNow,
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            function fireWatch(expr, value) {
 | 
			
		||||
                mockScope.$watch.calls.forEach(function (call) {
 | 
			
		||||
                    if (call.args[0] === expr) {
 | 
			
		||||
                        call.args[1](value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockScope = jasmine.createSpyObj(
 | 
			
		||||
                    "$scope",
 | 
			
		||||
                    [ "$apply", "$watch", "$watchCollection" ]
 | 
			
		||||
                );
 | 
			
		||||
                mockScope.ngModel = {};
 | 
			
		||||
                mockScope.field = "testField";
 | 
			
		||||
                mockNow = jasmine.createSpy('now');
 | 
			
		||||
                controller = new DateTimePickerController(mockScope, mockNow);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches the model that was passed in", function () {
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "ngModel[field]",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,237 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/controllers/TimeRangeController", "moment"],
 | 
			
		||||
    function (TimeRangeController, moment) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var SEC = 1000,
 | 
			
		||||
            MIN = 60 * SEC,
 | 
			
		||||
            HOUR = 60 * MIN,
 | 
			
		||||
            DAY = 24 * HOUR;
 | 
			
		||||
 | 
			
		||||
        describe("The TimeRangeController", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
                mockNow,
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            function fireWatch(expr, value) {
 | 
			
		||||
                mockScope.$watch.calls.forEach(function (call) {
 | 
			
		||||
                    if (call.args[0] === expr) {
 | 
			
		||||
                        call.args[1](value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function fireWatchCollection(expr, value) {
 | 
			
		||||
                mockScope.$watchCollection.calls.forEach(function (call) {
 | 
			
		||||
                    if (call.args[0] === expr) {
 | 
			
		||||
                        call.args[1](value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockScope = jasmine.createSpyObj(
 | 
			
		||||
                    "$scope",
 | 
			
		||||
                    [ "$apply", "$watch", "$watchCollection" ]
 | 
			
		||||
                );
 | 
			
		||||
                mockNow = jasmine.createSpy('now');
 | 
			
		||||
                controller = new TimeRangeController(mockScope, mockNow);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches the model that was passed in", function () {
 | 
			
		||||
                expect(mockScope.$watchCollection)
 | 
			
		||||
                    .toHaveBeenCalledWith("ngModel", jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("when dragged", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockScope.ngModel = {
 | 
			
		||||
                        outer: {
 | 
			
		||||
                            start: DAY * 1000,
 | 
			
		||||
                            end: DAY * 1001
 | 
			
		||||
                        },
 | 
			
		||||
                        inner: {
 | 
			
		||||
                            start: DAY * 1000 + HOUR * 3,
 | 
			
		||||
                            end: DAY * 1001 - HOUR * 3
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                    mockScope.spanWidth = 1000;
 | 
			
		||||
                    fireWatch("spanWidth", mockScope.spanWidth);
 | 
			
		||||
                    fireWatchCollection("ngModel", mockScope.ngModel);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("updates the start time for left drags", function () {
 | 
			
		||||
                    mockScope.startLeftDrag();
 | 
			
		||||
                    mockScope.leftDrag(250);
 | 
			
		||||
                    expect(mockScope.ngModel.inner.start)
 | 
			
		||||
                        .toEqual(DAY * 1000 + HOUR * 9);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("updates the end time for right drags", function () {
 | 
			
		||||
                    mockScope.startRightDrag();
 | 
			
		||||
                    mockScope.rightDrag(-250);
 | 
			
		||||
                    expect(mockScope.ngModel.inner.end)
 | 
			
		||||
                        .toEqual(DAY * 1000 + HOUR * 15);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("updates both start and end for middle drags", function () {
 | 
			
		||||
                    mockScope.startMiddleDrag();
 | 
			
		||||
                    mockScope.middleDrag(-125);
 | 
			
		||||
                    expect(mockScope.ngModel.inner).toEqual({
 | 
			
		||||
                        start: DAY * 1000,
 | 
			
		||||
                        end: DAY * 1000 + HOUR * 18
 | 
			
		||||
                    });
 | 
			
		||||
                    mockScope.middleDrag(250);
 | 
			
		||||
                    expect(mockScope.ngModel.inner).toEqual({
 | 
			
		||||
                        start: DAY * 1000 + HOUR * 6,
 | 
			
		||||
                        end: DAY * 1001
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("enforces a minimum inner span", function () {
 | 
			
		||||
                    mockScope.startRightDrag();
 | 
			
		||||
                    mockScope.rightDrag(-9999999);
 | 
			
		||||
                    expect(mockScope.ngModel.inner.end)
 | 
			
		||||
                        .toBeGreaterThan(mockScope.ngModel.inner.start);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("when outer bounds are changed", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockScope.ngModel = {
 | 
			
		||||
                        outer: {
 | 
			
		||||
                            start: DAY * 1000,
 | 
			
		||||
                            end: DAY * 1001
 | 
			
		||||
                        },
 | 
			
		||||
                        inner: {
 | 
			
		||||
                            start: DAY * 1000 + HOUR * 3,
 | 
			
		||||
                            end: DAY * 1001 - HOUR * 3
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                    mockScope.spanWidth = 1000;
 | 
			
		||||
                    fireWatch("spanWidth", mockScope.spanWidth);
 | 
			
		||||
                    fireWatchCollection("ngModel", mockScope.ngModel);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("enforces a minimum outer span", function () {
 | 
			
		||||
                    mockScope.ngModel.outer.end =
 | 
			
		||||
                        mockScope.ngModel.outer.start - DAY * 100;
 | 
			
		||||
                    fireWatch(
 | 
			
		||||
                        "ngModel.outer.end",
 | 
			
		||||
                        mockScope.ngModel.outer.end
 | 
			
		||||
                    );
 | 
			
		||||
                    expect(mockScope.ngModel.outer.end)
 | 
			
		||||
                        .toBeGreaterThan(mockScope.ngModel.outer.start);
 | 
			
		||||
 | 
			
		||||
                    mockScope.ngModel.outer.start =
 | 
			
		||||
                        mockScope.ngModel.outer.end + DAY * 100;
 | 
			
		||||
                    fireWatch(
 | 
			
		||||
                        "ngModel.outer.start",
 | 
			
		||||
                        mockScope.ngModel.outer.start
 | 
			
		||||
                    );
 | 
			
		||||
                    expect(mockScope.ngModel.outer.end)
 | 
			
		||||
                        .toBeGreaterThan(mockScope.ngModel.outer.start);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("enforces a minimum inner span when outer span changes", function () {
 | 
			
		||||
                    mockScope.ngModel.outer.end =
 | 
			
		||||
                        mockScope.ngModel.outer.start - DAY * 100;
 | 
			
		||||
                    fireWatch(
 | 
			
		||||
                        "ngModel.outer.end",
 | 
			
		||||
                        mockScope.ngModel.outer.end
 | 
			
		||||
                    );
 | 
			
		||||
                    expect(mockScope.ngModel.inner.end)
 | 
			
		||||
                        .toBeGreaterThan(mockScope.ngModel.inner.start);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("by typing", function () {
 | 
			
		||||
                    it("updates models", function () {
 | 
			
		||||
                        var newStart = "1977-05-25 17:30:00",
 | 
			
		||||
                            newEnd = "2015-12-18 03:30:00";
 | 
			
		||||
 | 
			
		||||
                        mockScope.boundsModel.start = newStart;
 | 
			
		||||
                        fireWatch("boundsModel.start", newStart);
 | 
			
		||||
                        expect(mockScope.ngModel.outer.start)
 | 
			
		||||
                            .toEqual(moment.utc(newStart).valueOf());
 | 
			
		||||
                        expect(mockScope.boundsModel.startValid)
 | 
			
		||||
                            .toBeTruthy();
 | 
			
		||||
 | 
			
		||||
                        mockScope.boundsModel.end = newEnd;
 | 
			
		||||
                        fireWatch("boundsModel.end", newEnd);
 | 
			
		||||
                        expect(mockScope.ngModel.outer.end)
 | 
			
		||||
                            .toEqual(moment.utc(newEnd).valueOf());
 | 
			
		||||
                        expect(mockScope.boundsModel.endValid)
 | 
			
		||||
                            .toBeTruthy();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("displays error state", function () {
 | 
			
		||||
                        var newStart = "Not a date",
 | 
			
		||||
                            newEnd = "Definitely not a date",
 | 
			
		||||
                            oldStart = mockScope.ngModel.outer.start,
 | 
			
		||||
                            oldEnd = mockScope.ngModel.outer.end;
 | 
			
		||||
 | 
			
		||||
                        mockScope.boundsModel.start = newStart;
 | 
			
		||||
                        fireWatch("boundsModel.start", newStart);
 | 
			
		||||
                        expect(mockScope.ngModel.outer.start)
 | 
			
		||||
                            .toEqual(oldStart);
 | 
			
		||||
                        expect(mockScope.boundsModel.startValid)
 | 
			
		||||
                            .toBeFalsy();
 | 
			
		||||
 | 
			
		||||
                        mockScope.boundsModel.end = newEnd;
 | 
			
		||||
                        fireWatch("boundsModel.end", newEnd);
 | 
			
		||||
                        expect(mockScope.ngModel.outer.end)
 | 
			
		||||
                            .toEqual(oldEnd);
 | 
			
		||||
                        expect(mockScope.boundsModel.endValid)
 | 
			
		||||
                            .toBeFalsy();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("does not modify user input", function () {
 | 
			
		||||
                        // Don't want the controller "fixing" bad or
 | 
			
		||||
                        // irregularly-formatted input out from under
 | 
			
		||||
                        // the user's fingertips.
 | 
			
		||||
                        var newStart = "Not a date",
 | 
			
		||||
                            newEnd = "2015-3-3 01:02:04",
 | 
			
		||||
                            oldStart = mockScope.ngModel.outer.start,
 | 
			
		||||
                            oldEnd = mockScope.ngModel.outer.end;
 | 
			
		||||
 | 
			
		||||
                        mockScope.boundsModel.start = newStart;
 | 
			
		||||
                        fireWatch("boundsModel.start", newStart);
 | 
			
		||||
                        expect(mockScope.boundsModel.start)
 | 
			
		||||
                            .toEqual(newStart);
 | 
			
		||||
 | 
			
		||||
                        mockScope.boundsModel.end = newEnd;
 | 
			
		||||
                        fireWatch("boundsModel.end", newEnd);
 | 
			
		||||
                        expect(mockScope.boundsModel.end)
 | 
			
		||||
                            .toEqual(newEnd);
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,describe,it,expect,beforeEach,jasmine*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/directives/MCTClickElsewhere"],
 | 
			
		||||
    function (MCTClickElsewhere) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var JQLITE_METHODS = [ "on", "off", "find", "parent" ];
 | 
			
		||||
 | 
			
		||||
        describe("The mct-click-elsewhere directive", function () {
 | 
			
		||||
            var mockDocument,
 | 
			
		||||
                mockScope,
 | 
			
		||||
                mockElement,
 | 
			
		||||
                testAttrs,
 | 
			
		||||
                mockBody,
 | 
			
		||||
                mockParentEl,
 | 
			
		||||
                testRect,
 | 
			
		||||
                mctClickElsewhere;
 | 
			
		||||
 | 
			
		||||
            function testEvent(x, y) {
 | 
			
		||||
                return {
 | 
			
		||||
                    pageX: x,
 | 
			
		||||
                    pageY: y,
 | 
			
		||||
                    preventDefault: jasmine.createSpy("preventDefault")
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockDocument =
 | 
			
		||||
                    jasmine.createSpyObj("$document", JQLITE_METHODS);
 | 
			
		||||
                mockScope =
 | 
			
		||||
                    jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]);
 | 
			
		||||
                mockElement =
 | 
			
		||||
                    jasmine.createSpyObj("element", JQLITE_METHODS);
 | 
			
		||||
                mockBody =
 | 
			
		||||
                    jasmine.createSpyObj("body", JQLITE_METHODS);
 | 
			
		||||
                mockParentEl =
 | 
			
		||||
                    jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
 | 
			
		||||
 | 
			
		||||
                testAttrs = {
 | 
			
		||||
                    mctClickElsewhere: "some Angular expression"
 | 
			
		||||
                };
 | 
			
		||||
                testRect = {
 | 
			
		||||
                    left: 20,
 | 
			
		||||
                    top: 42,
 | 
			
		||||
                    width: 60,
 | 
			
		||||
                    height: 75
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockDocument.find.andReturn(mockBody);
 | 
			
		||||
 | 
			
		||||
                mctClickElsewhere = new MCTClickElsewhere(mockDocument);
 | 
			
		||||
                mctClickElsewhere.link(mockScope, mockElement, testAttrs);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("is valid as an attribute", function () {
 | 
			
		||||
                expect(mctClickElsewhere.restrict).toEqual("A");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										136
									
								
								platform/commonUI/general/test/directives/MCTPopupSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,136 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,describe,it,expect,beforeEach,jasmine*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/directives/MCTPopup"],
 | 
			
		||||
    function (MCTPopup) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var JQLITE_METHODS = [ "on", "off", "find", "parent", "css", "append" ];
 | 
			
		||||
 | 
			
		||||
        describe("The mct-popup directive", function () {
 | 
			
		||||
            var mockCompile,
 | 
			
		||||
                mockPopupService,
 | 
			
		||||
                mockPopup,
 | 
			
		||||
                mockScope,
 | 
			
		||||
                mockElement,
 | 
			
		||||
                testAttrs,
 | 
			
		||||
                mockBody,
 | 
			
		||||
                mockTransclude,
 | 
			
		||||
                mockParentEl,
 | 
			
		||||
                mockNewElement,
 | 
			
		||||
                testRect,
 | 
			
		||||
                mctPopup;
 | 
			
		||||
 | 
			
		||||
            function testEvent(x, y) {
 | 
			
		||||
                return {
 | 
			
		||||
                    pageX: x,
 | 
			
		||||
                    pageY: y,
 | 
			
		||||
                    preventDefault: jasmine.createSpy("preventDefault")
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockCompile =
 | 
			
		||||
                    jasmine.createSpy("$compile");
 | 
			
		||||
                mockPopupService =
 | 
			
		||||
                    jasmine.createSpyObj("popupService", ["display"]);
 | 
			
		||||
                mockPopup =
 | 
			
		||||
                    jasmine.createSpyObj("popup", ["dismiss"]);
 | 
			
		||||
                mockScope =
 | 
			
		||||
                    jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]);
 | 
			
		||||
                mockElement =
 | 
			
		||||
                    jasmine.createSpyObj("element", JQLITE_METHODS);
 | 
			
		||||
                mockBody =
 | 
			
		||||
                    jasmine.createSpyObj("body", JQLITE_METHODS);
 | 
			
		||||
                mockTransclude =
 | 
			
		||||
                    jasmine.createSpy("transclude");
 | 
			
		||||
                mockParentEl =
 | 
			
		||||
                    jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
 | 
			
		||||
                mockNewElement =
 | 
			
		||||
                    jasmine.createSpyObj("newElement", JQLITE_METHODS);
 | 
			
		||||
 | 
			
		||||
                testAttrs = {
 | 
			
		||||
                    mctClickElsewhere: "some Angular expression"
 | 
			
		||||
                };
 | 
			
		||||
                testRect = {
 | 
			
		||||
                    left: 20,
 | 
			
		||||
                    top: 42,
 | 
			
		||||
                    width: 60,
 | 
			
		||||
                    height: 75
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockCompile.andCallFake(function () {
 | 
			
		||||
                    var mockFn = jasmine.createSpy();
 | 
			
		||||
                    mockFn.andReturn(mockNewElement);
 | 
			
		||||
                    return mockFn;
 | 
			
		||||
                });
 | 
			
		||||
                mockElement.parent.andReturn([mockParentEl]);
 | 
			
		||||
                mockParentEl.getBoundingClientRect.andReturn(testRect);
 | 
			
		||||
                mockPopupService.display.andReturn(mockPopup);
 | 
			
		||||
 | 
			
		||||
                mctPopup = new MCTPopup(mockCompile, mockPopupService);
 | 
			
		||||
 | 
			
		||||
                mctPopup.link(
 | 
			
		||||
                    mockScope,
 | 
			
		||||
                    mockElement,
 | 
			
		||||
                    testAttrs,
 | 
			
		||||
                    null,
 | 
			
		||||
                    mockTransclude
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("is valid as an element", function () {
 | 
			
		||||
                expect(mctPopup.restrict).toEqual("E");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("creates an element which", function () {
 | 
			
		||||
                it("displays as a popup", function () {
 | 
			
		||||
                    expect(mockPopupService.display).toHaveBeenCalledWith(
 | 
			
		||||
                        mockNewElement,
 | 
			
		||||
                        [ testRect.left, testRect.top ]
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("displays transcluded content", function () {
 | 
			
		||||
                    var mockClone =
 | 
			
		||||
                        jasmine.createSpyObj('clone', JQLITE_METHODS);
 | 
			
		||||
                    mockTransclude.mostRecentCall.args[0](mockClone);
 | 
			
		||||
                    expect(mockNewElement.append)
 | 
			
		||||
                        .toHaveBeenCalledWith(mockClone);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("is removed when its containing scope is destroyed", function () {
 | 
			
		||||
                    expect(mockPopup.dismiss).not.toHaveBeenCalled();
 | 
			
		||||
                    mockScope.$on.calls.forEach(function (call) {
 | 
			
		||||
                        if (call.args[0] === '$destroy') {
 | 
			
		||||
                            call.args[1]();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    expect(mockPopup.dismiss).toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										98
									
								
								platform/commonUI/general/test/services/PopupServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,98 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/services/PopupService"],
 | 
			
		||||
    function (PopupService) {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        describe("PopupService", function () {
 | 
			
		||||
            var mockDocument,
 | 
			
		||||
                testWindow,
 | 
			
		||||
                mockBody,
 | 
			
		||||
                mockElement,
 | 
			
		||||
                popupService;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockDocument = jasmine.createSpyObj('$document', [ 'find' ]);
 | 
			
		||||
                testWindow = { innerWidth: 1000, innerHeight: 800 };
 | 
			
		||||
                mockBody = jasmine.createSpyObj('body', [ 'append' ]);
 | 
			
		||||
                mockElement = jasmine.createSpyObj('element', [
 | 
			
		||||
                    'css',
 | 
			
		||||
                    'remove'
 | 
			
		||||
                ]);
 | 
			
		||||
 | 
			
		||||
                mockDocument.find.andCallFake(function (query) {
 | 
			
		||||
                    return query === 'body' && mockBody;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                popupService = new PopupService(mockDocument, testWindow);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("adds elements to the body of the document", function () {
 | 
			
		||||
                popupService.display(mockElement, [ 0, 0 ]);
 | 
			
		||||
                expect(mockBody.append).toHaveBeenCalledWith(mockElement);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("when positioned in appropriate quadrants", function () {
 | 
			
		||||
                it("orients elements relative to the top-left", function () {
 | 
			
		||||
                    popupService.display(mockElement, [ 25, 50 ]);
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith({
 | 
			
		||||
                        position: 'absolute',
 | 
			
		||||
                        left: '25px',
 | 
			
		||||
                        top: '50px'
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("orients elements relative to the top-right", function () {
 | 
			
		||||
                    popupService.display(mockElement, [ 800, 50 ]);
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith({
 | 
			
		||||
                        position: 'absolute',
 | 
			
		||||
                        right: '200px',
 | 
			
		||||
                        top: '50px'
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("orients elements relative to the bottom-right", function () {
 | 
			
		||||
                    popupService.display(mockElement, [ 800, 650 ]);
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith({
 | 
			
		||||
                        position: 'absolute',
 | 
			
		||||
                        right: '200px',
 | 
			
		||||
                        bottom: '150px'
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("orients elements relative to the bottom-left", function () {
 | 
			
		||||
                    popupService.display(mockElement, [ 120, 650 ]);
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith({
 | 
			
		||||
                        position: 'absolute',
 | 
			
		||||
                        left: '120px',
 | 
			
		||||
                        bottom: '150px'
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										74
									
								
								platform/commonUI/general/test/services/PopupSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,74 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/services/Popup"],
 | 
			
		||||
    function (Popup) {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        describe("Popup", function () {
 | 
			
		||||
            var mockElement,
 | 
			
		||||
                testStyles,
 | 
			
		||||
                popup;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockElement =
 | 
			
		||||
                    jasmine.createSpyObj('element', [ 'css', 'remove' ]);
 | 
			
		||||
                testStyles = { left: '12px', top: '14px' };
 | 
			
		||||
                popup = new Popup(mockElement, testStyles);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("applies CSS styles when instantiated", function () {
 | 
			
		||||
                expect(mockElement.css)
 | 
			
		||||
                    .toHaveBeenCalledWith(testStyles);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("reports the orientation of the popup", function () {
 | 
			
		||||
                var otherStyles = {
 | 
			
		||||
                        right: '12px',
 | 
			
		||||
                        bottom: '14px'
 | 
			
		||||
                    },
 | 
			
		||||
                    otherPopup = new Popup(mockElement, otherStyles);
 | 
			
		||||
 | 
			
		||||
                expect(popup.goesLeft()).toBeFalsy();
 | 
			
		||||
                expect(popup.goesRight()).toBeTruthy();
 | 
			
		||||
                expect(popup.goesUp()).toBeFalsy();
 | 
			
		||||
                expect(popup.goesDown()).toBeTruthy();
 | 
			
		||||
 | 
			
		||||
                expect(otherPopup.goesLeft()).toBeTruthy();
 | 
			
		||||
                expect(otherPopup.goesRight()).toBeFalsy();
 | 
			
		||||
                expect(otherPopup.goesUp()).toBeTruthy();
 | 
			
		||||
                expect(otherPopup.goesDown()).toBeFalsy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("removes elements when dismissed", function () {
 | 
			
		||||
                expect(mockElement.remove).not.toHaveBeenCalled();
 | 
			
		||||
                popup.dismiss();
 | 
			
		||||
                expect(mockElement.remove).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -3,16 +3,22 @@
 | 
			
		||||
    "controllers/BottomBarController",
 | 
			
		||||
    "controllers/ClickAwayController",
 | 
			
		||||
    "controllers/ContextMenuController",
 | 
			
		||||
    "controllers/DateTimePickerController",
 | 
			
		||||
    "controllers/GetterSetterController",
 | 
			
		||||
    "controllers/SelectorController",
 | 
			
		||||
    "controllers/SplitPaneController",
 | 
			
		||||
    "controllers/TimeRangeController",
 | 
			
		||||
    "controllers/ToggleController",
 | 
			
		||||
    "controllers/TreeNodeController",
 | 
			
		||||
    "controllers/ViewSwitcherController",
 | 
			
		||||
    "directives/MCTClickElsewhere",
 | 
			
		||||
    "directives/MCTContainer",
 | 
			
		||||
    "directives/MCTDrag",
 | 
			
		||||
    "directives/MCTPopup",
 | 
			
		||||
    "directives/MCTResize",
 | 
			
		||||
    "directives/MCTScroll",
 | 
			
		||||
    "services/Popup",
 | 
			
		||||
    "services/PopupService",
 | 
			
		||||
    "services/UrlService",
 | 
			
		||||
    "StyleSheetLoader"
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -45,13 +45,12 @@
 | 
			
		||||
                "implementation": "services/InfoService.js",
 | 
			
		||||
                "depends": [
 | 
			
		||||
                    "$compile",
 | 
			
		||||
                    "$document",
 | 
			
		||||
                    "$window",
 | 
			
		||||
                    "$rootScope",
 | 
			
		||||
                    "popupService",
 | 
			
		||||
                    "agentService"
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        ],  
 | 
			
		||||
        ],
 | 
			
		||||
        "constants": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "INFO_HOVER_DELAY",
 | 
			
		||||
@@ -66,4 +65,4 @@
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,13 +31,19 @@ define({
 | 
			
		||||
    BUBBLE_TEMPLATE: "<mct-container key=\"bubble\" " +
 | 
			
		||||
                "bubble-title=\"{{bubbleTitle}}\" " +
 | 
			
		||||
                "bubble-layout=\"{{bubbleLayout}}\" " +
 | 
			
		||||
	            "class=\"bubble-container\">" +
 | 
			
		||||
                "<mct-include key=\"bubbleTemplate\" ng-model=\"bubbleModel\">" +
 | 
			
		||||
                "class=\"bubble-container\">" +
 | 
			
		||||
                "<mct-include key=\"bubbleTemplate\" " +
 | 
			
		||||
                "ng-model=\"bubbleModel\">" +
 | 
			
		||||
                "</mct-include>" +
 | 
			
		||||
                "</mct-container>",
 | 
			
		||||
    // Pixel offset for bubble, to align arrow position
 | 
			
		||||
    BUBBLE_OFFSET: [ 0, -26 ],
 | 
			
		||||
	// Max width and margins allowed for bubbles; defined in /platform/commonUI/general/res/sass/_constants.scss
 | 
			
		||||
	BUBBLE_MARGIN_LR: 10,
 | 
			
		||||
	BUBBLE_MAX_WIDTH: 300
 | 
			
		||||
    // Options and classes for bubble
 | 
			
		||||
    BUBBLE_OPTIONS: {
 | 
			
		||||
        offsetX: 0,
 | 
			
		||||
        offsetY: -26
 | 
			
		||||
    },
 | 
			
		||||
    BUBBLE_MOBILE_POSITION: [ 0, -25 ],
 | 
			
		||||
    // Max width and margins allowed for bubbles;
 | 
			
		||||
    // defined in /platform/commonUI/general/res/sass/_constants.scss
 | 
			
		||||
    BUBBLE_MARGIN_LR: 10,
 | 
			
		||||
    BUBBLE_MAX_WIDTH: 300
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -27,18 +27,18 @@ define(
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var BUBBLE_TEMPLATE = InfoConstants.BUBBLE_TEMPLATE,
 | 
			
		||||
            OFFSET = InfoConstants.BUBBLE_OFFSET;
 | 
			
		||||
            MOBILE_POSITION = InfoConstants.BUBBLE_MOBILE_POSITION,
 | 
			
		||||
            OPTIONS = InfoConstants.BUBBLE_OPTIONS;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Displays informative content ("info bubbles") for the user.
 | 
			
		||||
         * @memberof platform/commonUI/inspect
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function InfoService($compile, $document, $window, $rootScope, agentService) {
 | 
			
		||||
        function InfoService($compile, $rootScope, popupService, agentService) {
 | 
			
		||||
            this.$compile = $compile;
 | 
			
		||||
            this.$document = $document;
 | 
			
		||||
            this.$window = $window;
 | 
			
		||||
            this.$rootScope = $rootScope;
 | 
			
		||||
            this.popupService = popupService;
 | 
			
		||||
            this.agentService = agentService;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -55,53 +55,47 @@ define(
 | 
			
		||||
         */
 | 
			
		||||
        InfoService.prototype.display = function (templateKey, title, content, position) {
 | 
			
		||||
            var $compile = this.$compile,
 | 
			
		||||
                $document = this.$document,
 | 
			
		||||
                $window = this.$window,
 | 
			
		||||
                $rootScope = this.$rootScope,
 | 
			
		||||
                body = $document.find('body'),
 | 
			
		||||
                scope = $rootScope.$new(),
 | 
			
		||||
                winDim = [$window.innerWidth, $window.innerHeight],
 | 
			
		||||
                bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR + InfoConstants.BUBBLE_MAX_WIDTH,
 | 
			
		||||
                goLeft = position[0] > (winDim[0] - bubbleSpaceLR),
 | 
			
		||||
                goUp = position[1] > (winDim[1] / 2),
 | 
			
		||||
                span = $compile('<span></span>')(scope),
 | 
			
		||||
                bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR +
 | 
			
		||||
                    InfoConstants.BUBBLE_MAX_WIDTH,
 | 
			
		||||
                options,
 | 
			
		||||
                popup,
 | 
			
		||||
                bubble;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            options = Object.create(OPTIONS);
 | 
			
		||||
            options.marginX = -bubbleSpaceLR;
 | 
			
		||||
 | 
			
		||||
            // On a phone, bubble takes up more screen real estate,
 | 
			
		||||
            // so position it differently (toward the bottom)
 | 
			
		||||
            if (this.agentService.isPhone(navigator.userAgent)) {
 | 
			
		||||
                position = MOBILE_POSITION;
 | 
			
		||||
                options = {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            popup = this.popupService.display(span, position, options);
 | 
			
		||||
 | 
			
		||||
            // Pass model & container parameters into the scope
 | 
			
		||||
            scope.bubbleModel = content;
 | 
			
		||||
            scope.bubbleTemplate = templateKey;
 | 
			
		||||
            scope.bubbleLayout = (goUp ? 'arw-btm' : 'arw-top') + ' ' +
 | 
			
		||||
                (goLeft ? 'arw-right' : 'arw-left');
 | 
			
		||||
            scope.bubbleTitle = title;
 | 
			
		||||
            // Style the bubble according to how it was positioned
 | 
			
		||||
            scope.bubbleLayout = [
 | 
			
		||||
                popup.goesUp() ? 'arw-btm' : 'arw-top',
 | 
			
		||||
                popup.goesLeft() ? 'arw-right' : 'arw-left'
 | 
			
		||||
            ].join(' ');
 | 
			
		||||
            scope.bubbleLayout = 'arw-top arw-left';
 | 
			
		||||
 | 
			
		||||
            // Create the context menu
 | 
			
		||||
            // Create the info bubble, now that we know how to
 | 
			
		||||
            // point the arrow...
 | 
			
		||||
            bubble = $compile(BUBBLE_TEMPLATE)(scope);
 | 
			
		||||
            span.append(bubble);
 | 
			
		||||
 | 
			
		||||
            // Position the bubble
 | 
			
		||||
            bubble.css('position', 'absolute');
 | 
			
		||||
            if (this.agentService.isPhone(navigator.userAgent)) {
 | 
			
		||||
                bubble.css('right', '0px');
 | 
			
		||||
                bubble.css('left', '0px');
 | 
			
		||||
                bubble.css('top', 'auto');
 | 
			
		||||
                bubble.css('bottom', '25px');
 | 
			
		||||
            } else {
 | 
			
		||||
                if (goLeft) {
 | 
			
		||||
                    bubble.css('right', (winDim[0] - position[0] + OFFSET[0]) + 'px');
 | 
			
		||||
                } else {
 | 
			
		||||
                    bubble.css('left', position[0] + OFFSET[0] + 'px');
 | 
			
		||||
                }
 | 
			
		||||
                if (goUp) {
 | 
			
		||||
                    bubble.css('bottom', (winDim[1] - position[1] + OFFSET[1]) + 'px');
 | 
			
		||||
                } else {
 | 
			
		||||
                    bubble.css('top', position[1] + OFFSET[1] + 'px');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Add the menu to the body
 | 
			
		||||
            body.append(bubble);
 | 
			
		||||
 | 
			
		||||
            // Return a function to dismiss the bubble
 | 
			
		||||
            return function () {
 | 
			
		||||
                bubble.remove();
 | 
			
		||||
            // Return a function to dismiss the info bubble
 | 
			
		||||
            return function dismiss() {
 | 
			
		||||
                popup.dismiss();
 | 
			
		||||
                scope.$destroy();
 | 
			
		||||
            };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,117 +28,85 @@ define(
 | 
			
		||||
 | 
			
		||||
        describe("The info service", function () {
 | 
			
		||||
            var mockCompile,
 | 
			
		||||
                mockDocument,
 | 
			
		||||
                testWindow,
 | 
			
		||||
                mockRootScope,
 | 
			
		||||
                mockPopupService,
 | 
			
		||||
                mockAgentService,
 | 
			
		||||
                mockCompiledTemplate,
 | 
			
		||||
                testScope,
 | 
			
		||||
                mockBody,
 | 
			
		||||
                mockElement,
 | 
			
		||||
                mockScope,
 | 
			
		||||
                mockElements,
 | 
			
		||||
                mockPopup,
 | 
			
		||||
                service;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockCompile = jasmine.createSpy('$compile');
 | 
			
		||||
                mockDocument = jasmine.createSpyObj('$document', ['find']);
 | 
			
		||||
                testWindow = { innerWidth: 1000, innerHeight: 100 };
 | 
			
		||||
                mockRootScope = jasmine.createSpyObj('$rootScope', ['$new']);
 | 
			
		||||
                mockAgentService = jasmine.createSpyObj('agentService', ['isMobile', 'isPhone']);
 | 
			
		||||
                mockCompiledTemplate = jasmine.createSpy('template');
 | 
			
		||||
                testScope = {};
 | 
			
		||||
                mockBody = jasmine.createSpyObj('body', ['append']);
 | 
			
		||||
                mockElement = jasmine.createSpyObj('element', ['css', 'remove']);
 | 
			
		||||
                mockPopupService = jasmine.createSpyObj(
 | 
			
		||||
                    'popupService',
 | 
			
		||||
                    ['display']
 | 
			
		||||
                );
 | 
			
		||||
                mockPopup = jasmine.createSpyObj('popup', [
 | 
			
		||||
                    'dismiss',
 | 
			
		||||
                    'goesLeft',
 | 
			
		||||
                    'goesRight',
 | 
			
		||||
                    'goesUp',
 | 
			
		||||
                    'goesDown'
 | 
			
		||||
                ]);
 | 
			
		||||
 | 
			
		||||
                mockDocument.find.andCallFake(function (tag) {
 | 
			
		||||
                    return tag === 'body' ? mockBody : undefined;
 | 
			
		||||
                mockScope = jasmine.createSpyObj("scope", ["$destroy"]);
 | 
			
		||||
                mockElements = [];
 | 
			
		||||
 | 
			
		||||
                mockPopupService.display.andReturn(mockPopup);
 | 
			
		||||
                mockCompile.andCallFake(function () {
 | 
			
		||||
                    var mockCompiledTemplate = jasmine.createSpy('template'),
 | 
			
		||||
                        mockElement = jasmine.createSpyObj('element', [
 | 
			
		||||
                            'css',
 | 
			
		||||
                            'remove',
 | 
			
		||||
                            'append'
 | 
			
		||||
                        ]);
 | 
			
		||||
                    mockCompiledTemplate.andReturn(mockElement);
 | 
			
		||||
                    mockElements.push(mockElement);
 | 
			
		||||
                    return mockCompiledTemplate;
 | 
			
		||||
                });
 | 
			
		||||
                mockCompile.andReturn(mockCompiledTemplate);
 | 
			
		||||
                mockCompiledTemplate.andReturn(mockElement);
 | 
			
		||||
                mockRootScope.$new.andReturn(testScope);
 | 
			
		||||
                mockRootScope.$new.andReturn(mockScope);
 | 
			
		||||
 | 
			
		||||
                service = new InfoService(
 | 
			
		||||
                    mockCompile,
 | 
			
		||||
                    mockDocument,
 | 
			
		||||
                    testWindow,
 | 
			
		||||
                    mockRootScope,
 | 
			
		||||
                    mockPopupService,
 | 
			
		||||
                    mockAgentService
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("creates elements and appends them to the body to display", function () {
 | 
			
		||||
                service.display('', '', {}, [0, 0]);
 | 
			
		||||
                expect(mockBody.append).toHaveBeenCalledWith(mockElement);
 | 
			
		||||
            it("creates elements and displays them as popups", function () {
 | 
			
		||||
                service.display('', '', {}, [123, 456]);
 | 
			
		||||
                expect(mockPopupService.display).toHaveBeenCalledWith(
 | 
			
		||||
                    mockElements[0],
 | 
			
		||||
                    [ 123, 456 ],
 | 
			
		||||
                    jasmine.any(Object)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("provides a function to remove displayed info bubbles", function () {
 | 
			
		||||
                var fn = service.display('', '', {}, [0, 0]);
 | 
			
		||||
                expect(mockElement.remove).not.toHaveBeenCalled();
 | 
			
		||||
                expect(mockPopup.dismiss).not.toHaveBeenCalled();
 | 
			
		||||
                fn();
 | 
			
		||||
                expect(mockElement.remove).toHaveBeenCalled();
 | 
			
		||||
                expect(mockPopup.dismiss).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("depending on mouse position", function () {
 | 
			
		||||
                // Positioning should vary based on quadrant in window,
 | 
			
		||||
                // which is 1000 x 100 in this test case.
 | 
			
		||||
                it("displays from the top-left in the top-left quadrant", function () {
 | 
			
		||||
                    service.display('', '', {}, [250, 25]);
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith(
 | 
			
		||||
                        'left',
 | 
			
		||||
                        (250 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
 | 
			
		||||
                    );
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith(
 | 
			
		||||
                        'top',
 | 
			
		||||
                        (25 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("displays from the top-right in the top-right quadrant", function () {
 | 
			
		||||
                    service.display('', '', {}, [700, 25]);
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith(
 | 
			
		||||
                        'right',
 | 
			
		||||
                        (300 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
 | 
			
		||||
                    );
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith(
 | 
			
		||||
                        'top',
 | 
			
		||||
                        (25 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("displays from the bottom-left in the bottom-left quadrant", function () {
 | 
			
		||||
                    service.display('', '', {}, [250, 70]);
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith(
 | 
			
		||||
                        'left',
 | 
			
		||||
                        (250 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
 | 
			
		||||
                    );
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith(
 | 
			
		||||
                        'bottom',
 | 
			
		||||
                        (30 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("displays from the bottom-right in the bottom-right quadrant", function () {
 | 
			
		||||
                    service.display('', '', {}, [800, 60]);
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith(
 | 
			
		||||
                        'right',
 | 
			
		||||
                        (200 + InfoConstants.BUBBLE_OFFSET[0]) + 'px'
 | 
			
		||||
                    );
 | 
			
		||||
                    expect(mockElement.css).toHaveBeenCalledWith(
 | 
			
		||||
                        'bottom',
 | 
			
		||||
                        (40 + InfoConstants.BUBBLE_OFFSET[1]) + 'px'
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                it("when on phone device, positioning is always on bottom", function () {
 | 
			
		||||
                    mockAgentService.isPhone.andReturn(true);
 | 
			
		||||
                    service = new InfoService(
 | 
			
		||||
                        mockCompile,
 | 
			
		||||
                        mockDocument,
 | 
			
		||||
                        testWindow,
 | 
			
		||||
                        mockRootScope,
 | 
			
		||||
                        mockAgentService
 | 
			
		||||
                    );
 | 
			
		||||
                    service.display('', '', {}, [0, 0]);
 | 
			
		||||
                });
 | 
			
		||||
            it("when on phone device, positions at  bottom", function () {
 | 
			
		||||
                mockAgentService.isPhone.andReturn(true);
 | 
			
		||||
                service = new InfoService(
 | 
			
		||||
                    mockCompile,
 | 
			
		||||
                    mockRootScope,
 | 
			
		||||
                    mockPopupService,
 | 
			
		||||
                    mockAgentService
 | 
			
		||||
                );
 | 
			
		||||
                service.display('', '', {}, [123, 456]);
 | 
			
		||||
                expect(mockPopupService.display).toHaveBeenCalledWith(
 | 
			
		||||
                    mockElements[0],
 | 
			
		||||
                    [ 0, -25 ],
 | 
			
		||||
                    jasmine.any(Object)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,14 @@ $colorKey: #0099cc;
 | 
			
		||||
$colorKeySelectedBg: #005177;
 | 
			
		||||
$colorKeyFg: #fff;
 | 
			
		||||
$colorInteriorBorder: rgba($colorBodyFg, 0.1);
 | 
			
		||||
$colorA: #ccc;
 | 
			
		||||
$colorAHov: #fff;
 | 
			
		||||
$contrastRatioPercent: 7%;
 | 
			
		||||
$basicCr: 3px;
 | 
			
		||||
$controlCr: 3px;
 | 
			
		||||
$smallCr: 2px;
 | 
			
		||||
 | 
			
		||||
// Buttons
 | 
			
		||||
// Buttons and Controls
 | 
			
		||||
$colorBtnBg: pullForward($colorBodyBg, $contrastRatioPercent); //
 | 
			
		||||
$colorBtnFg: $colorBodyFg;
 | 
			
		||||
$colorBtnMajorBg: $colorKey;
 | 
			
		||||
@@ -20,6 +22,18 @@ $colorBtnMajorFg: $colorKeyFg;
 | 
			
		||||
$colorBtnIcon: $colorKey;
 | 
			
		||||
$colorInvokeMenu: #fff;
 | 
			
		||||
$contrastInvokeMenuPercent: 20%;
 | 
			
		||||
$shdwBtns: rgba(black, 0.2) 0 1px 2px;
 | 
			
		||||
$sliderColorBase: $colorKey;
 | 
			
		||||
$sliderColorRangeHolder: rgba(black, 0.1);
 | 
			
		||||
$sliderColorRange: rgba($sliderColorBase, 0.3);
 | 
			
		||||
$sliderColorRangeHov: rgba($sliderColorBase, 0.5);
 | 
			
		||||
$sliderColorKnob: rgba($sliderColorBase, 0.6);
 | 
			
		||||
$sliderColorKnobHov: $sliderColorBase;
 | 
			
		||||
$sliderColorRangeValHovBg: rgba($sliderColorBase, 0.1);
 | 
			
		||||
$sliderColorRangeValHovFg: $colorKeyFg;
 | 
			
		||||
$sliderKnobW: nth($ueTimeControlH,2)/2;
 | 
			
		||||
$timeControllerToiLineColor: #00c2ff;
 | 
			
		||||
$timeControllerToiLineColorHov: #fff;
 | 
			
		||||
 | 
			
		||||
// General Colors
 | 
			
		||||
$colorAlt1: #ffc700;
 | 
			
		||||
@@ -32,6 +46,7 @@ $colorGridLines: rgba(#fff, 0.05);
 | 
			
		||||
$colorInvokeMenu: #fff;
 | 
			
		||||
$colorObjHdrTxt: $colorBodyFg;
 | 
			
		||||
$colorObjHdrIc: pullForward($colorObjHdrTxt, 20%);
 | 
			
		||||
$colorTick: rgba(white, 0.2);
 | 
			
		||||
 | 
			
		||||
// Menu colors
 | 
			
		||||
$colorMenuBg: pullForward($colorBodyBg, 23%);
 | 
			
		||||
@@ -111,26 +126,27 @@ $colorItemBgSelected: $colorKey;
 | 
			
		||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
 | 
			
		||||
$colorTabBodyBg: darken($colorBodyBg, 10%);
 | 
			
		||||
$colorTabBodyFg: lighten($colorTabBodyBg, 40%);
 | 
			
		||||
$colorTabHeaderBg: lighten($colorBodyBg, 10%);
 | 
			
		||||
$colorTabHeaderFg: lighten($colorTabHeaderBg, 40%);
 | 
			
		||||
$colorTabHeaderBg: rgba(white, 0.1); // lighten($colorBodyBg, 10%);
 | 
			
		||||
$colorTabHeaderFg: $colorBodyFg; //lighten($colorTabHeaderBg, 40%);
 | 
			
		||||
$colorTabHeaderBorder: $colorBodyBg;
 | 
			
		||||
 | 
			
		||||
// Plot
 | 
			
		||||
$colorPlotBg: rgba(black, 0.1);
 | 
			
		||||
$colorPlotFg: $colorBodyFg;
 | 
			
		||||
$colorPlotHash: rgba(white, 0.2);
 | 
			
		||||
$colorPlotHash: $colorTick;
 | 
			
		||||
$stylePlotHash: dashed;
 | 
			
		||||
$colorPlotAreaBorder: $colorInteriorBorder;
 | 
			
		||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
 | 
			
		||||
 | 
			
		||||
// Tree
 | 
			
		||||
$colorItemTreeIcon: $colorKey;
 | 
			
		||||
$colorItemTreeIconHover: lighten($colorItemTreeIcon, 20%);
 | 
			
		||||
$colorItemTreeVCHover: $colorAlt1;
 | 
			
		||||
$colorItemTreeFg: $colorBodyFg;
 | 
			
		||||
$colorItemTreeSelectedBg: pushBack($colorKey, 15%);
 | 
			
		||||
$colorItemTreeSelectedFg: pullForward($colorBodyFg, 20%);
 | 
			
		||||
$colorItemTreeVC: rgba(#fff, 0.3);
 | 
			
		||||
$colorItemTreeSelectedVC: $colorItemTreeVC;
 | 
			
		||||
$colorItemTreeVCHover: $colorAlt1;
 | 
			
		||||
$shdwItemTreeIcon: 0.6;
 | 
			
		||||
 | 
			
		||||
// Scrollbar
 | 
			
		||||
@@ -151,5 +167,16 @@ $colorGrippyInteriorHover: $colorKey;
 | 
			
		||||
// Mobile
 | 
			
		||||
$colorMobilePaneLeft: darken($colorBodyBg, 5%);
 | 
			
		||||
 | 
			
		||||
// Datetime Picker
 | 
			
		||||
$colorCalCellHovBg: $colorKey;
 | 
			
		||||
$colorCalCellHovFg: $colorKeyFg;
 | 
			
		||||
$colorCalCellSelectedBg: $colorItemTreeSelectedBg;
 | 
			
		||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
 | 
			
		||||
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
 | 
			
		||||
 | 
			
		||||
// About Screen
 | 
			
		||||
$colorAboutLink: #84b3ff;
 | 
			
		||||
$colorAboutLink: #84b3ff;
 | 
			
		||||
 | 
			
		||||
// Loading
 | 
			
		||||
$colorLoadingBg: rgba($colorBodyFg, 0.2);
 | 
			
		||||
$colorLoadingFg: $colorAlt1;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
@mixin containerSubtle($bg: $colorBodyBg, $fg: $colorBodyFg, $hover: false) {
 | 
			
		||||
	@include containerBase($bg, $fg);
 | 
			
		||||
	@include background-image(linear-gradient(lighten($bg, 5%), $bg));
 | 
			
		||||
	@include boxShdwSubtle();
 | 
			
		||||
	@include boxShdw($shdwBtns);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin btnSubtle($bg: $colorBodyBg, $bgHov: none, $fg: $colorBodyFg, $ic: $colorBtnIcon) {
 | 
			
		||||
	@mixin btnSubtle($bg: $colorBodyBg, $bgHov: none, $fg: $colorBodyFg, $ic: $colorBtnIcon) {
 | 
			
		||||
	@include containerSubtle($bg, $fg);
 | 
			
		||||
	@include btnBase($bg, linear-gradient(lighten($bg, 15%), lighten($bg, 10%)), $fg, $ic);
 | 
			
		||||
	@include text-shadow(rgba(black, 0.3) 0 1px 1px);
 | 
			
		||||
	@include text-shadow($shdwItemText);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@function pullForward($c: $colorBodyBg, $p: 20%) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,14 @@ $colorKey: #0099cc;
 | 
			
		||||
$colorKeySelectedBg: $colorKey;
 | 
			
		||||
$colorKeyFg: #fff;
 | 
			
		||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
 | 
			
		||||
$colorA: #999;
 | 
			
		||||
$colorAHov: $colorKey;
 | 
			
		||||
$contrastRatioPercent: 40%;
 | 
			
		||||
$basicCr: 4px;
 | 
			
		||||
$controlCr: $basicCr;
 | 
			
		||||
$smallCr: 3px;
 | 
			
		||||
 | 
			
		||||
// Buttons
 | 
			
		||||
// Buttons and Controls
 | 
			
		||||
$colorBtnBg: pullForward($colorBodyBg, $contrastRatioPercent);
 | 
			
		||||
$colorBtnFg: #fff;
 | 
			
		||||
$colorBtnMajorBg: $colorKey;
 | 
			
		||||
@@ -20,10 +22,22 @@ $colorBtnMajorFg: $colorKeyFg;
 | 
			
		||||
$colorBtnIcon: #eee;
 | 
			
		||||
$colorInvokeMenu: #000;
 | 
			
		||||
$contrastInvokeMenuPercent: 40%;
 | 
			
		||||
$shdwBtns: none;
 | 
			
		||||
$sliderColorBase: $colorKey;
 | 
			
		||||
$sliderColorRangeHolder: rgba(black, 0.07);
 | 
			
		||||
$sliderColorRange: rgba($sliderColorBase, 0.2);
 | 
			
		||||
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
 | 
			
		||||
$sliderColorKnob: rgba($sliderColorBase, 0.5);
 | 
			
		||||
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
 | 
			
		||||
$sliderColorRangeValHovBg: $sliderColorRange; //rgba($sliderColorBase, 0.1);
 | 
			
		||||
$sliderColorRangeValHovFg: $colorBodyFg;
 | 
			
		||||
$sliderKnobW: nth($ueTimeControlH,2)/2;
 | 
			
		||||
$timeControllerToiLineColor: $colorBodyFg;
 | 
			
		||||
$timeControllerToiLineColorHov: #0052b5;
 | 
			
		||||
 | 
			
		||||
// General Colors
 | 
			
		||||
$colorAlt1: #ff6600;
 | 
			
		||||
$colorAlert: #ff533a;
 | 
			
		||||
$colorAlt1: #776ba2;
 | 
			
		||||
$colorAlert: #ff3c00;
 | 
			
		||||
$colorIconLink: #49dedb;
 | 
			
		||||
$colorPausedBg: #ff9900;
 | 
			
		||||
$colorPausedFg: #fff;
 | 
			
		||||
@@ -32,6 +46,7 @@ $colorGridLines: rgba(#000, 0.05);
 | 
			
		||||
$colorInvokeMenu: #fff;
 | 
			
		||||
$colorObjHdrTxt: $colorBodyFg;
 | 
			
		||||
$colorObjHdrIc: pushBack($colorObjHdrTxt, 30%);
 | 
			
		||||
$colorTick: rgba(black, 0.2);
 | 
			
		||||
 | 
			
		||||
// Menu colors
 | 
			
		||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
 | 
			
		||||
@@ -118,9 +133,10 @@ $colorTabHeaderBorder: $colorBodyBg;
 | 
			
		||||
// Plot
 | 
			
		||||
$colorPlotBg: rgba(black, 0.05);
 | 
			
		||||
$colorPlotFg: $colorBodyFg;
 | 
			
		||||
$colorPlotHash: rgba(black, 0.2);
 | 
			
		||||
$colorPlotHash: $colorTick;
 | 
			
		||||
$stylePlotHash: dashed;
 | 
			
		||||
$colorPlotAreaBorder: $colorInteriorBorder;
 | 
			
		||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
 | 
			
		||||
 | 
			
		||||
// Tree
 | 
			
		||||
$colorItemTreeIcon: $colorKey;
 | 
			
		||||
@@ -151,5 +167,16 @@ $colorGrippyInteriorHover: $colorBodyBg;
 | 
			
		||||
// Mobile
 | 
			
		||||
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
 | 
			
		||||
 | 
			
		||||
// Datetime Picker, Calendar
 | 
			
		||||
$colorCalCellHovBg: $colorKey;
 | 
			
		||||
$colorCalCellHovFg: $colorKeyFg;
 | 
			
		||||
$colorCalCellSelectedBg: $colorItemTreeSelectedBg;
 | 
			
		||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
 | 
			
		||||
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
 | 
			
		||||
 | 
			
		||||
// About Screen
 | 
			
		||||
$colorAboutLink: #84b3ff;
 | 
			
		||||
$colorAboutLink: #84b3ff;
 | 
			
		||||
 | 
			
		||||
// Loading
 | 
			
		||||
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
 | 
			
		||||
$colorLoadingFg: $colorAlt1;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
@mixin containerSubtle($bg: $colorBodyBg, $fg: $colorBodyFg) {
 | 
			
		||||
	@include containerBase($bg, $fg);
 | 
			
		||||
	@include boxShdw($shdwBtns);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin btnSubtle($bg: $colorBtnBg, $bgHov: none, $fg: $colorBtnFg, $ic: $colorBtnIcon) {
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,7 @@
 | 
			
		||||
                "depends": [
 | 
			
		||||
                    "persistenceService",
 | 
			
		||||
                    "$q",
 | 
			
		||||
                    "now",
 | 
			
		||||
                    "PERSISTENCE_SPACE",
 | 
			
		||||
                    "ADDITIONAL_PERSISTENCE_SPACES"
 | 
			
		||||
                ]
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,8 @@ define(
 | 
			
		||||
    function () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var TOPIC_PREFIX = "mutation:";
 | 
			
		||||
        var GENERAL_TOPIC = "mutation",
 | 
			
		||||
            TOPIC_PREFIX = "mutation:";
 | 
			
		||||
 | 
			
		||||
        // Utility function to overwrite a destination object
 | 
			
		||||
        // with the contents of a source object.
 | 
			
		||||
@@ -78,7 +79,11 @@ define(
 | 
			
		||||
         * @implements {Capability}
 | 
			
		||||
         */
 | 
			
		||||
        function MutationCapability(topic, now, domainObject) {
 | 
			
		||||
            this.mutationTopic = topic(TOPIC_PREFIX + domainObject.getId());
 | 
			
		||||
            this.generalMutationTopic =
 | 
			
		||||
                topic(GENERAL_TOPIC);
 | 
			
		||||
            this.specificMutationTopic =
 | 
			
		||||
                topic(TOPIC_PREFIX + domainObject.getId());
 | 
			
		||||
 | 
			
		||||
            this.now = now;
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
        }
 | 
			
		||||
@@ -115,11 +120,17 @@ define(
 | 
			
		||||
            // mutator function has a temporary copy to work with.
 | 
			
		||||
            var domainObject = this.domainObject,
 | 
			
		||||
                now = this.now,
 | 
			
		||||
                t = this.mutationTopic,
 | 
			
		||||
                generalTopic = this.generalMutationTopic,
 | 
			
		||||
                specificTopic = this.specificMutationTopic,
 | 
			
		||||
                model = domainObject.getModel(),
 | 
			
		||||
                clone = JSON.parse(JSON.stringify(model)),
 | 
			
		||||
                useTimestamp = arguments.length > 1;
 | 
			
		||||
 | 
			
		||||
            function notifyListeners(model) {
 | 
			
		||||
                generalTopic.notify(domainObject);
 | 
			
		||||
                specificTopic.notify(model);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Function to handle copying values to the actual
 | 
			
		||||
            function handleMutation(mutationResult) {
 | 
			
		||||
                // If mutation result was undefined, just use
 | 
			
		||||
@@ -136,7 +147,7 @@ define(
 | 
			
		||||
                        copyValues(model, result);
 | 
			
		||||
                    }
 | 
			
		||||
                    model.modified = useTimestamp ? timestamp : now();
 | 
			
		||||
                    t.notify(model);
 | 
			
		||||
                    notifyListeners(model);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Report the result of the mutation
 | 
			
		||||
@@ -158,7 +169,7 @@ define(
 | 
			
		||||
         * @memberof platform/core.MutationCapability#
 | 
			
		||||
         */
 | 
			
		||||
        MutationCapability.prototype.listen = function (listener) {
 | 
			
		||||
            return this.mutationTopic.listen(listener);
 | 
			
		||||
            return this.specificMutationTopic.listen(listener);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -39,14 +39,16 @@ define(
 | 
			
		||||
         * @param {PersistenceService} persistenceService the service in which
 | 
			
		||||
         *        domain object models are persisted.
 | 
			
		||||
         * @param $q Angular's $q service, for working with promises
 | 
			
		||||
         * @param {function} now a function which provides the current time
 | 
			
		||||
         * @param {string} space the name of the persistence space(s)
 | 
			
		||||
         *        from which models should be retrieved.
 | 
			
		||||
         * @param {string} spaces additional persistence spaces to use
 | 
			
		||||
         */
 | 
			
		||||
        function PersistedModelProvider(persistenceService, $q, space, spaces) {
 | 
			
		||||
        function PersistedModelProvider(persistenceService, $q, now, space, spaces) {
 | 
			
		||||
            this.persistenceService = persistenceService;
 | 
			
		||||
            this.$q = $q;
 | 
			
		||||
            this.spaces = [space].concat(spaces || []);
 | 
			
		||||
            this.now = now;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Take the most recently modified model, for cases where
 | 
			
		||||
@@ -61,7 +63,9 @@ define(
 | 
			
		||||
        PersistedModelProvider.prototype.getModels = function (ids) {
 | 
			
		||||
            var persistenceService = this.persistenceService,
 | 
			
		||||
                $q = this.$q,
 | 
			
		||||
                spaces = this.spaces;
 | 
			
		||||
                spaces = this.spaces,
 | 
			
		||||
                space = this.space,
 | 
			
		||||
                now = this.now;
 | 
			
		||||
 | 
			
		||||
            // Load a single object model from any persistence spaces
 | 
			
		||||
            function loadModel(id) {
 | 
			
		||||
@@ -72,11 +76,24 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Ensure that models read from persistence have some
 | 
			
		||||
            // sensible timestamp indicating they've been persisted.
 | 
			
		||||
            function addPersistedTimestamp(model) {
 | 
			
		||||
                if (model && (model.persisted === undefined)) {
 | 
			
		||||
                    model.persisted = model.modified !== undefined ?
 | 
			
		||||
                            model.modified : now();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return model;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Package the result as id->model
 | 
			
		||||
            function packageResult(models) {
 | 
			
		||||
                var result = {};
 | 
			
		||||
                ids.forEach(function (id, index) {
 | 
			
		||||
                    result[id] = models[index];
 | 
			
		||||
                    if (models[index]) {
 | 
			
		||||
                        result[id] = addPersistedTimestamp(models[index]);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -36,11 +36,16 @@ define(
 | 
			
		||||
         *
 | 
			
		||||
         * Returns a function that, when invoked, will invoke `fn` after
 | 
			
		||||
         * `delay` milliseconds, only if no other invocations are pending.
 | 
			
		||||
         * The optional argument `apply` determines whether.
 | 
			
		||||
         * The optional argument `apply` determines whether or not a
 | 
			
		||||
         * digest cycle should be triggered.
 | 
			
		||||
         *
 | 
			
		||||
         * The returned function will itself return a `Promise` which will
 | 
			
		||||
         * resolve to the returned value of `fn` whenever that is invoked.
 | 
			
		||||
         *
 | 
			
		||||
         * In cases where arguments are provided, only the most recent
 | 
			
		||||
         * set of arguments will be passed on to the throttled function
 | 
			
		||||
         * at the time it is executed.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns {Function}
 | 
			
		||||
         * @memberof platform/core
 | 
			
		||||
         */
 | 
			
		||||
@@ -56,12 +61,14 @@ define(
 | 
			
		||||
             * @memberof platform/core.Throttle#
 | 
			
		||||
             */
 | 
			
		||||
            return function (fn, delay, apply) {
 | 
			
		||||
                var activeTimeout;
 | 
			
		||||
                var promise,
 | 
			
		||||
                    args = [];
 | 
			
		||||
 | 
			
		||||
                // Clear active timeout, so that next invocation starts
 | 
			
		||||
                // a new one.
 | 
			
		||||
                function clearActiveTimeout() {
 | 
			
		||||
                    activeTimeout = undefined;
 | 
			
		||||
                function invoke() {
 | 
			
		||||
                    // Clear the active timeout so a new one starts next time.
 | 
			
		||||
                    promise = undefined;
 | 
			
		||||
                    // Invoke the function with the latest supplied arguments.
 | 
			
		||||
                    return fn.apply(null, args);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Defaults
 | 
			
		||||
@@ -69,14 +76,13 @@ define(
 | 
			
		||||
                apply = apply || false;
 | 
			
		||||
 | 
			
		||||
                return function () {
 | 
			
		||||
                    // Store arguments from this invocation
 | 
			
		||||
                    args = Array.prototype.slice.apply(arguments, [0]);
 | 
			
		||||
                    // Start a timeout if needed
 | 
			
		||||
                    if (!activeTimeout) {
 | 
			
		||||
                        activeTimeout = $timeout(fn, delay, apply);
 | 
			
		||||
                        activeTimeout.then(clearActiveTimeout);
 | 
			
		||||
                    }
 | 
			
		||||
                    promise = promise || $timeout(invoke, delay, apply);
 | 
			
		||||
                    // Return whichever timeout is active (to get
 | 
			
		||||
                    // a promise for the results of fn)
 | 
			
		||||
                    return activeTimeout;
 | 
			
		||||
                    return promise;
 | 
			
		||||
                };
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ define(
 | 
			
		||||
                SPACE = "space0",
 | 
			
		||||
                spaces = [ "space1" ],
 | 
			
		||||
                modTimes,
 | 
			
		||||
                mockNow,
 | 
			
		||||
                provider;
 | 
			
		||||
 | 
			
		||||
            function mockPromise(value) {
 | 
			
		||||
@@ -55,19 +56,33 @@ define(
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                modTimes = {};
 | 
			
		||||
                mockQ = { when: mockPromise, all: mockAll };
 | 
			
		||||
                mockPersistenceService = {
 | 
			
		||||
                    readObject: function (space, id) {
 | 
			
		||||
                mockPersistenceService = jasmine.createSpyObj(
 | 
			
		||||
                    'persistenceService',
 | 
			
		||||
                    [
 | 
			
		||||
                        'createObject',
 | 
			
		||||
                        'readObject',
 | 
			
		||||
                        'updateObject',
 | 
			
		||||
                        'deleteObject',
 | 
			
		||||
                        'listSpaces',
 | 
			
		||||
                        'listObjects'
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
                mockNow = jasmine.createSpy("now");
 | 
			
		||||
 | 
			
		||||
                mockPersistenceService.readObject
 | 
			
		||||
                    .andCallFake(function (space, id) {
 | 
			
		||||
                        return mockPromise({
 | 
			
		||||
                            space: space,
 | 
			
		||||
                            id: id,
 | 
			
		||||
                            modified: (modTimes[space] || {})[id]
 | 
			
		||||
                            modified: (modTimes[space] || {})[id],
 | 
			
		||||
                            persisted: 0
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                provider = new PersistedModelProvider(
 | 
			
		||||
                    mockPersistenceService,
 | 
			
		||||
                    mockQ,
 | 
			
		||||
                    mockNow,
 | 
			
		||||
                    SPACE,
 | 
			
		||||
                    spaces
 | 
			
		||||
                );
 | 
			
		||||
@@ -81,12 +96,13 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                expect(models).toEqual({
 | 
			
		||||
                    a: { space: SPACE, id: "a" },
 | 
			
		||||
                    x: { space: SPACE, id: "x" },
 | 
			
		||||
                    zz: { space: SPACE, id: "zz" }
 | 
			
		||||
                    a: { space: SPACE, id: "a", persisted: 0 },
 | 
			
		||||
                    x: { space: SPACE, id: "x", persisted: 0 },
 | 
			
		||||
                    zz: { space: SPACE, id: "zz", persisted: 0 }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            it("reads object models from multiple spaces", function () {
 | 
			
		||||
                var models;
 | 
			
		||||
 | 
			
		||||
@@ -99,9 +115,36 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                expect(models).toEqual({
 | 
			
		||||
                    a: { space: SPACE, id: "a" },
 | 
			
		||||
                    x: { space: 'space1', id: "x", modified: 12321 },
 | 
			
		||||
                    zz: { space: SPACE, id: "zz" }
 | 
			
		||||
                    a: { space: SPACE, id: "a", persisted: 0 },
 | 
			
		||||
                    x: { space: 'space1', id: "x", modified: 12321, persisted: 0 },
 | 
			
		||||
                    zz: { space: SPACE, id: "zz", persisted: 0 }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            it("ensures that persisted timestamps are present", function () {
 | 
			
		||||
                var mockCallback = jasmine.createSpy("callback"),
 | 
			
		||||
                    testModels = {
 | 
			
		||||
                        a: { modified: 123, persisted: 1984, name: "A" },
 | 
			
		||||
                        b: { persisted: 1977, name: "B" },
 | 
			
		||||
                        c: { modified: 42, name: "C" },
 | 
			
		||||
                        d: { name: "D" }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                mockPersistenceService.readObject.andCallFake(
 | 
			
		||||
                    function (space, id) {
 | 
			
		||||
                        return mockPromise(testModels[id]);
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
                mockNow.andReturn(12321);
 | 
			
		||||
 | 
			
		||||
                provider.getModels(Object.keys(testModels)).then(mockCallback);
 | 
			
		||||
 | 
			
		||||
                expect(mockCallback).toHaveBeenCalledWith({
 | 
			
		||||
                    a: { modified: 123, persisted: 1984, name: "A" },
 | 
			
		||||
                    b: { persisted: 1977, name: "B" },
 | 
			
		||||
                    c: { modified: 42, persisted: 42, name: "C" },
 | 
			
		||||
                    d: { persisted: 12321, name: "D" }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,9 @@ define(
 | 
			
		||||
                // Verify precondition: Not called at throttle-time
 | 
			
		||||
                expect(mockTimeout).not.toHaveBeenCalled();
 | 
			
		||||
                expect(throttled()).toEqual(mockPromise);
 | 
			
		||||
                expect(mockTimeout).toHaveBeenCalledWith(mockFn, 0, false);
 | 
			
		||||
                expect(mockFn).not.toHaveBeenCalled();
 | 
			
		||||
                expect(mockTimeout)
 | 
			
		||||
                    .toHaveBeenCalledWith(jasmine.any(Function), 0, false);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("schedules only one timeout at a time", function () {
 | 
			
		||||
@@ -59,10 +61,11 @@ define(
 | 
			
		||||
            it("schedules additional invocations after resolution", function () {
 | 
			
		||||
                var throttled = throttle(mockFn);
 | 
			
		||||
                throttled();
 | 
			
		||||
                mockPromise.then.mostRecentCall.args[0](); // Resolve timeout
 | 
			
		||||
                mockTimeout.mostRecentCall.args[0](); // Resolve timeout
 | 
			
		||||
                throttled();
 | 
			
		||||
                mockPromise.then.mostRecentCall.args[0]();
 | 
			
		||||
                mockTimeout.mostRecentCall.args[0]();
 | 
			
		||||
                throttled();
 | 
			
		||||
                mockTimeout.mostRecentCall.args[0]();
 | 
			
		||||
                expect(mockTimeout.calls.length).toEqual(3);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,14 @@
 | 
			
		||||
                "category": "contextual",
 | 
			
		||||
                "implementation": "actions/LinkAction.js",
 | 
			
		||||
                "depends": ["locationService", "linkService"]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "follow",
 | 
			
		||||
                "name": "Go To Original",
 | 
			
		||||
                "description": "Go to the original, un-linked instance of this object.",
 | 
			
		||||
                "glyph": "\u00F4",
 | 
			
		||||
                "category": "contextual",
 | 
			
		||||
                "implementation": "actions/GoToOriginalAction.js"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "components": [
 | 
			
		||||
@@ -52,7 +60,8 @@
 | 
			
		||||
                "key": "location",
 | 
			
		||||
                "name": "Location Capability",
 | 
			
		||||
                "description": "Provides a capability for retrieving the location of an object based upon it's context.",
 | 
			
		||||
                "implementation": "capabilities/LocationCapability"
 | 
			
		||||
                "implementation": "capabilities/LocationCapability",
 | 
			
		||||
                "depends": [ "$q", "$injector" ]
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "services": [
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								platform/entanglement/src/actions/GoToOriginalAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,62 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Implements the "Go To Original" action, which follows a link back
 | 
			
		||||
         * to an original instance of an object.
 | 
			
		||||
         *
 | 
			
		||||
         * @implements {Action}
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @private
 | 
			
		||||
         * @memberof platform/entanglement
 | 
			
		||||
         * @param {ActionContext} context the context in which the action
 | 
			
		||||
         *        will be performed
 | 
			
		||||
         */
 | 
			
		||||
        function GoToOriginalAction(context) {
 | 
			
		||||
            this.domainObject = context.domainObject;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        GoToOriginalAction.prototype.perform = function () {
 | 
			
		||||
            return this.domainObject.getCapability("location").getOriginal()
 | 
			
		||||
                .then(function (originalObject) {
 | 
			
		||||
                    var actionCapability =
 | 
			
		||||
                        originalObject.getCapability("action");
 | 
			
		||||
                    return actionCapability &&
 | 
			
		||||
                            actionCapability.perform("navigate");
 | 
			
		||||
                });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        GoToOriginalAction.appliesTo = function (context) {
 | 
			
		||||
            var domainObject = context.domainObject;
 | 
			
		||||
            return domainObject && domainObject.hasCapability("location")
 | 
			
		||||
                && domainObject.getCapability("location").isLink();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return GoToOriginalAction;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@@ -1,3 +1,25 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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(
 | 
			
		||||
@@ -12,11 +34,41 @@ define(
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function LocationCapability(domainObject) {
 | 
			
		||||
        function LocationCapability($q, $injector, domainObject) {
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.$q = $q;
 | 
			
		||||
            this.$injector = $injector;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get an instance of this domain object in its original location.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns {Promise.<DomainObject>} a promise for the original
 | 
			
		||||
         *          instance of this domain object
 | 
			
		||||
         */
 | 
			
		||||
        LocationCapability.prototype.getOriginal = function () {
 | 
			
		||||
            var id;
 | 
			
		||||
 | 
			
		||||
            if (this.isOriginal()) {
 | 
			
		||||
                return this.$q.when(this.domainObject);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            id = this.domainObject.getId();
 | 
			
		||||
 | 
			
		||||
            this.objectService =
 | 
			
		||||
                this.objectService || this.$injector.get("objectService");
 | 
			
		||||
 | 
			
		||||
            // Assume that an object will be correctly contextualized when
 | 
			
		||||
            // loaded directly from the object service; this is true
 | 
			
		||||
            // so long as LocatingObjectDecorator is present, and that
 | 
			
		||||
            // decorator is also contained in this bundle.
 | 
			
		||||
            return this.objectService.getObjects([id])
 | 
			
		||||
                .then(function (objects) {
 | 
			
		||||
                    return objects[id];
 | 
			
		||||
                });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Set the primary location (the parent id) of the current domain
 | 
			
		||||
         * object.
 | 
			
		||||
@@ -78,10 +130,6 @@ define(
 | 
			
		||||
            return !this.isLink();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        function createLocationCapability(domainObject) {
 | 
			
		||||
            return new LocationCapability(domainObject);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return createLocationCapability;
 | 
			
		||||
        return LocationCapability;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										95
									
								
								platform/entanglement/test/actions/GoToOriginalActionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,95 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,describe,beforeEach,it,jasmine,expect */
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    [
 | 
			
		||||
        '../../src/actions/GoToOriginalAction',
 | 
			
		||||
        '../DomainObjectFactory',
 | 
			
		||||
        '../ControlledPromise'
 | 
			
		||||
    ],
 | 
			
		||||
    function (GoToOriginalAction, domainObjectFactory, ControlledPromise) {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        describe("The 'go to original' action", function () {
 | 
			
		||||
            var testContext,
 | 
			
		||||
                originalDomainObject,
 | 
			
		||||
                mockLocationCapability,
 | 
			
		||||
                mockOriginalActionCapability,
 | 
			
		||||
                originalPromise,
 | 
			
		||||
                action;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockLocationCapability = jasmine.createSpyObj(
 | 
			
		||||
                    'location',
 | 
			
		||||
                    [ 'isLink', 'isOriginal', 'getOriginal' ]
 | 
			
		||||
                );
 | 
			
		||||
                mockOriginalActionCapability = jasmine.createSpyObj(
 | 
			
		||||
                    'action',
 | 
			
		||||
                    [ 'perform', 'getActions' ]
 | 
			
		||||
                );
 | 
			
		||||
                originalPromise = new ControlledPromise();
 | 
			
		||||
                mockLocationCapability.getOriginal.andReturn(originalPromise);
 | 
			
		||||
                mockLocationCapability.isLink.andReturn(true);
 | 
			
		||||
                mockLocationCapability.isOriginal.andCallFake(function () {
 | 
			
		||||
                    return !mockLocationCapability.isLink();
 | 
			
		||||
                });
 | 
			
		||||
                testContext = {
 | 
			
		||||
                    domainObject: domainObjectFactory({
 | 
			
		||||
                        capabilities: {
 | 
			
		||||
                            location: mockLocationCapability
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                };
 | 
			
		||||
                originalDomainObject = domainObjectFactory({
 | 
			
		||||
                    capabilities: {
 | 
			
		||||
                        action: mockOriginalActionCapability
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                action = new GoToOriginalAction(testContext);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("is applicable to links", function () {
 | 
			
		||||
                expect(GoToOriginalAction.appliesTo(testContext))
 | 
			
		||||
                    .toBeTruthy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("is not applicable to originals", function () {
 | 
			
		||||
                mockLocationCapability.isLink.andReturn(false);
 | 
			
		||||
                expect(GoToOriginalAction.appliesTo(testContext))
 | 
			
		||||
                    .toBeFalsy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("navigates to original objects when performed", function () {
 | 
			
		||||
                expect(mockOriginalActionCapability.perform)
 | 
			
		||||
                    .not.toHaveBeenCalled();
 | 
			
		||||
                action.perform();
 | 
			
		||||
                originalPromise.resolve(originalDomainObject);
 | 
			
		||||
                expect(mockOriginalActionCapability.perform)
 | 
			
		||||
                    .toHaveBeenCalledWith('navigate');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,3 +1,25 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,describe,it,expect,beforeEach,jasmine */
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
@@ -7,6 +29,7 @@ define(
 | 
			
		||||
        '../ControlledPromise'
 | 
			
		||||
    ],
 | 
			
		||||
    function (LocationCapability, domainObjectFactory, ControlledPromise) {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        describe("LocationCapability", function () {
 | 
			
		||||
 | 
			
		||||
@@ -14,13 +37,17 @@ define(
 | 
			
		||||
                var locationCapability,
 | 
			
		||||
                    persistencePromise,
 | 
			
		||||
                    mutationPromise,
 | 
			
		||||
                    mockQ,
 | 
			
		||||
                    mockInjector,
 | 
			
		||||
                    mockObjectService,
 | 
			
		||||
                    domainObject;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    domainObject = domainObjectFactory({
 | 
			
		||||
                        id: "testObject",
 | 
			
		||||
                        capabilities: {
 | 
			
		||||
                            context: {
 | 
			
		||||
                                getParent: function() {
 | 
			
		||||
                                getParent: function () {
 | 
			
		||||
                                    return domainObjectFactory({id: 'root'});
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
@@ -35,6 +62,11 @@ define(
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    mockQ = jasmine.createSpyObj("$q", ["when"]);
 | 
			
		||||
                    mockInjector = jasmine.createSpyObj("$injector", ["get"]);
 | 
			
		||||
                    mockObjectService =
 | 
			
		||||
                        jasmine.createSpyObj("objectService", ["getObjects"]);
 | 
			
		||||
 | 
			
		||||
                    persistencePromise = new ControlledPromise();
 | 
			
		||||
                    domainObject.capabilities.persistence.persist.andReturn(
 | 
			
		||||
                        persistencePromise
 | 
			
		||||
@@ -49,7 +81,11 @@ define(
 | 
			
		||||
                        }
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    locationCapability = new LocationCapability(domainObject);
 | 
			
		||||
                    locationCapability = new LocationCapability(
 | 
			
		||||
                        mockQ,
 | 
			
		||||
                        mockInjector,
 | 
			
		||||
                        domainObject
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("returns contextual location", function () {
 | 
			
		||||
@@ -88,6 +124,57 @@ define(
 | 
			
		||||
                    expect(whenComplete).toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("when used to load an original instance", function () {
 | 
			
		||||
                    var objectPromise,
 | 
			
		||||
                        qPromise,
 | 
			
		||||
                        originalObjects,
 | 
			
		||||
                        mockCallback;
 | 
			
		||||
 | 
			
		||||
                    function resolvePromises() {
 | 
			
		||||
                        if (mockQ.when.calls.length > 0) {
 | 
			
		||||
                            qPromise.resolve(mockQ.when.mostRecentCall.args[0]);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (mockObjectService.getObjects.calls.length > 0) {
 | 
			
		||||
                            objectPromise.resolve(originalObjects);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    beforeEach(function () {
 | 
			
		||||
                        objectPromise = new ControlledPromise();
 | 
			
		||||
                        qPromise = new ControlledPromise();
 | 
			
		||||
                        originalObjects = {
 | 
			
		||||
                            testObject: domainObjectFactory()
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        mockInjector.get.andCallFake(function (key) {
 | 
			
		||||
                            return key === 'objectService' && mockObjectService;
 | 
			
		||||
                        });
 | 
			
		||||
                        mockObjectService.getObjects.andReturn(objectPromise);
 | 
			
		||||
                        mockQ.when.andReturn(qPromise);
 | 
			
		||||
 | 
			
		||||
                        mockCallback = jasmine.createSpy('callback');
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("provides originals directly", function () {
 | 
			
		||||
                        domainObject.model.location = 'root';
 | 
			
		||||
                        locationCapability.getOriginal().then(mockCallback);
 | 
			
		||||
                        expect(mockCallback).not.toHaveBeenCalled();
 | 
			
		||||
                        resolvePromises();
 | 
			
		||||
                        expect(mockCallback)
 | 
			
		||||
                            .toHaveBeenCalledWith(domainObject);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("loads from the object service for links", function () {
 | 
			
		||||
                        domainObject.model.location = 'some-other-root';
 | 
			
		||||
                        locationCapability.getOriginal().then(mockCallback);
 | 
			
		||||
                        expect(mockCallback).not.toHaveBeenCalled();
 | 
			
		||||
                        resolvePromises();
 | 
			
		||||
                        expect(mockCallback)
 | 
			
		||||
                            .toHaveBeenCalledWith(originalObjects.testObject);
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
[
 | 
			
		||||
    "actions/AbstractComposeAction",
 | 
			
		||||
    "actions/CopyAction",
 | 
			
		||||
    "actions/GoToOriginalAction",
 | 
			
		||||
    "actions/LinkAction",
 | 
			
		||||
    "actions/MoveAction",
 | 
			
		||||
    "services/CopyService",
 | 
			
		||||
    "services/LinkService",
 | 
			
		||||
    "services/MoveService",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								platform/features/conductor/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,9 @@
 | 
			
		||||
Provides the time conductor, a control which appears at the
 | 
			
		||||
bottom of the screen allowing telemetry start and end times
 | 
			
		||||
to be modified.
 | 
			
		||||
 | 
			
		||||
Note that the term "time controller" is generally preferred
 | 
			
		||||
outside of the code base (e.g. in UI documents, issues, etc.);
 | 
			
		||||
the term "time conductor" is being used in code to avoid
 | 
			
		||||
confusion with "controllers" in the Model-View-Controller
 | 
			
		||||
sense.
 | 
			
		||||
							
								
								
									
										46
									
								
								platform/features/conductor/bundle.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,46 @@
 | 
			
		||||
{
 | 
			
		||||
    "extensions": {
 | 
			
		||||
        "representers": [
 | 
			
		||||
            {
 | 
			
		||||
                "implementation": "ConductorRepresenter.js",
 | 
			
		||||
                "depends": [
 | 
			
		||||
                    "throttle",
 | 
			
		||||
                    "conductorService",
 | 
			
		||||
                    "$compile",
 | 
			
		||||
                    "views[]"
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "components": [
 | 
			
		||||
            {
 | 
			
		||||
                "type": "decorator",
 | 
			
		||||
                "provides": "telemetryService",
 | 
			
		||||
                "implementation": "ConductorTelemetryDecorator.js",
 | 
			
		||||
                "depends": [ "conductorService" ]
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "services": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "conductorService",
 | 
			
		||||
                "implementation": "ConductorService.js",
 | 
			
		||||
                "depends": [ "now", "TIME_CONDUCTOR_DOMAINS" ]
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "templates": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "time-conductor",
 | 
			
		||||
                "templateUrl": "templates/time-conductor.html"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "constants": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "TIME_CONDUCTOR_DOMAINS",
 | 
			
		||||
                "value": [
 | 
			
		||||
                    { "key": "time", "name": "Time" },
 | 
			
		||||
                    { "key": "yesterday", "name": "Yesterday" }
 | 
			
		||||
                ],
 | 
			
		||||
                "comment": "Placeholder; to be replaced by inspection of available domains."
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
<mct-include key="'time-controller'"
 | 
			
		||||
             ng-model='ngModel.conductor'>
 | 
			
		||||
</mct-include>
 | 
			
		||||
<mct-control key="'select'"
 | 
			
		||||
             ng-model='ngModel'
 | 
			
		||||
             field="'domain'"
 | 
			
		||||
             options="ngModel.options"
 | 
			
		||||
             style="position: absolute; right: 0px; bottom: 46px;"
 | 
			
		||||
             >
 | 
			
		||||
</mct-control>
 | 
			
		||||
							
								
								
									
										201
									
								
								platform/features/conductor/src/ConductorRepresenter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,201 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var TEMPLATE = [
 | 
			
		||||
                "<mct-include key=\"'time-conductor'\" ng-model='ngModel' class='l-time-controller'>",
 | 
			
		||||
                "</mct-include>"
 | 
			
		||||
            ].join(''),
 | 
			
		||||
            THROTTLE_MS = 200,
 | 
			
		||||
            GLOBAL_SHOWING = false;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The ConductorRepresenter attaches the universal time conductor
 | 
			
		||||
         * to views.
 | 
			
		||||
         *
 | 
			
		||||
         * @implements {Representer}
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/features/conductor
 | 
			
		||||
         * @param {Function} throttle a function used to reduce the frequency
 | 
			
		||||
         *        of function invocations
 | 
			
		||||
         * @param {platform/features/conductor.ConductorService} conductorService
 | 
			
		||||
         *        service which provides the active time conductor
 | 
			
		||||
         * @param $compile Angular's $compile
 | 
			
		||||
         * @param {ViewDefinition[]} views all defined views
 | 
			
		||||
         * @param {Scope} the scope of the representation
 | 
			
		||||
         * @param element the jqLite-wrapped representation element
 | 
			
		||||
         */
 | 
			
		||||
        function ConductorRepresenter(
 | 
			
		||||
            throttle,
 | 
			
		||||
            conductorService,
 | 
			
		||||
            $compile,
 | 
			
		||||
            views,
 | 
			
		||||
            scope,
 | 
			
		||||
            element
 | 
			
		||||
        ) {
 | 
			
		||||
            this.throttle = throttle;
 | 
			
		||||
            this.scope = scope;
 | 
			
		||||
            this.conductorService = conductorService;
 | 
			
		||||
            this.element = element;
 | 
			
		||||
            this.views = views;
 | 
			
		||||
            this.$compile = $compile;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update the time conductor from the scope
 | 
			
		||||
        ConductorRepresenter.prototype.wireScope = function () {
 | 
			
		||||
            var conductor = this.conductorService.getConductor(),
 | 
			
		||||
                conductorScope = this.conductorScope(),
 | 
			
		||||
                repScope = this.scope,
 | 
			
		||||
                lastObservedBounds,
 | 
			
		||||
                broadcastBounds;
 | 
			
		||||
 | 
			
		||||
            // Combine start/end times into a single object
 | 
			
		||||
            function bounds(start, end) {
 | 
			
		||||
                return {
 | 
			
		||||
                    start: conductor.displayStart(),
 | 
			
		||||
                    end: conductor.displayEnd(),
 | 
			
		||||
                    domain: conductor.domain()
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function boundsAreStable(newlyObservedBounds) {
 | 
			
		||||
                return !lastObservedBounds ||
 | 
			
		||||
                    (lastObservedBounds.start === newlyObservedBounds.start &&
 | 
			
		||||
                    lastObservedBounds.end === newlyObservedBounds.end);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateConductorInner() {
 | 
			
		||||
                var innerBounds = conductorScope.ngModel.conductor.inner;
 | 
			
		||||
                conductor.displayStart(innerBounds.start);
 | 
			
		||||
                conductor.displayEnd(innerBounds.end);
 | 
			
		||||
                lastObservedBounds = lastObservedBounds || bounds();
 | 
			
		||||
                broadcastBounds();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateDomain(value) {
 | 
			
		||||
                conductor.domain(value);
 | 
			
		||||
                repScope.$broadcast('telemetry:display:bounds', bounds(
 | 
			
		||||
                    conductor.displayStart(),
 | 
			
		||||
                    conductor.displayEnd(),
 | 
			
		||||
                    conductor.domain()
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // telemetry domain metadata -> option for a select control
 | 
			
		||||
            function makeOption(domainOption) {
 | 
			
		||||
                return {
 | 
			
		||||
                    name: domainOption.name,
 | 
			
		||||
                    value: domainOption.key
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            broadcastBounds = this.throttle(function () {
 | 
			
		||||
                var newlyObservedBounds = bounds();
 | 
			
		||||
 | 
			
		||||
                if (boundsAreStable(newlyObservedBounds)) {
 | 
			
		||||
                    repScope.$broadcast('telemetry:display:bounds', bounds());
 | 
			
		||||
                    lastObservedBounds = undefined;
 | 
			
		||||
                } else {
 | 
			
		||||
                    lastObservedBounds = newlyObservedBounds;
 | 
			
		||||
                    broadcastBounds();
 | 
			
		||||
                }
 | 
			
		||||
            }, THROTTLE_MS);
 | 
			
		||||
 | 
			
		||||
            conductorScope.ngModel = {};
 | 
			
		||||
            conductorScope.ngModel.conductor =
 | 
			
		||||
                { outer: bounds(), inner: bounds() };
 | 
			
		||||
            conductorScope.ngModel.options =
 | 
			
		||||
                conductor.domainOptions().map(makeOption);
 | 
			
		||||
            conductorScope.ngModel.domain = conductor.domain();
 | 
			
		||||
 | 
			
		||||
            conductorScope
 | 
			
		||||
                .$watch('ngModel.conductor.inner.start', updateConductorInner);
 | 
			
		||||
            conductorScope
 | 
			
		||||
                .$watch('ngModel.conductor.inner.end', updateConductorInner);
 | 
			
		||||
            conductorScope
 | 
			
		||||
                .$watch('ngModel.domain', updateDomain);
 | 
			
		||||
 | 
			
		||||
            repScope.$on('telemetry:view', updateConductorInner);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ConductorRepresenter.prototype.conductorScope = function (s) {
 | 
			
		||||
            return (this.cScope = arguments.length > 0 ? s : this.cScope);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Handle a specific representation of a specific domain object
 | 
			
		||||
        ConductorRepresenter.prototype.represent = function represent(representation, representedObject) {
 | 
			
		||||
            this.destroy();
 | 
			
		||||
 | 
			
		||||
            if (this.views.indexOf(representation) !== -1 && !GLOBAL_SHOWING) {
 | 
			
		||||
                // Track original states
 | 
			
		||||
                this.originalHeight = this.element.css('height');
 | 
			
		||||
                this.hadAbs = this.element.hasClass('abs');
 | 
			
		||||
 | 
			
		||||
                // Create a new scope for the conductor
 | 
			
		||||
                this.conductorScope(this.scope.$new());
 | 
			
		||||
                this.wireScope();
 | 
			
		||||
                this.conductorElement =
 | 
			
		||||
                    this.$compile(TEMPLATE)(this.conductorScope());
 | 
			
		||||
                this.element.after(this.conductorElement[0]);
 | 
			
		||||
                this.element.addClass('l-controls-visible l-time-controller-visible');
 | 
			
		||||
                GLOBAL_SHOWING = true;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Respond to the destruction of the current representation.
 | 
			
		||||
        ConductorRepresenter.prototype.destroy = function destroy() {
 | 
			
		||||
            // We may not have decided to show in the first place,
 | 
			
		||||
            // so circumvent any unnecessary cleanup
 | 
			
		||||
            if (!this.conductorElement) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Restore the original size of the mct-representation
 | 
			
		||||
            if (!this.hadAbs) {
 | 
			
		||||
                this.element.removeClass('abs');
 | 
			
		||||
            }
 | 
			
		||||
            this.element.css('height', this.originalHeight);
 | 
			
		||||
 | 
			
		||||
            // ...and remove the conductor
 | 
			
		||||
            if (this.conductorElement) {
 | 
			
		||||
                this.conductorElement.remove();
 | 
			
		||||
                this.conductorElement = undefined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Finally, destroy its scope
 | 
			
		||||
            if (this.conductorScope()) {
 | 
			
		||||
                this.conductorScope().$destroy();
 | 
			
		||||
                this.conductorScope(undefined);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            GLOBAL_SHOWING = false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return ConductorRepresenter;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										64
									
								
								platform/features/conductor/src/ConductorService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,64 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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(
 | 
			
		||||
    ['./TimeConductor'],
 | 
			
		||||
    function (TimeConductor) {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        var ONE_DAY_IN_MS = 1000 * 60 * 60 * 24,
 | 
			
		||||
            SIX_HOURS_IN_MS = ONE_DAY_IN_MS / 4;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Provides a single global instance of the time conductor, which
 | 
			
		||||
         * controls both query ranges and displayed ranges for telemetry
 | 
			
		||||
         * data.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/features/conductor
 | 
			
		||||
         * @param {Function} now a function which returns the current time
 | 
			
		||||
         *        as a UNIX timestamp, in milliseconds
 | 
			
		||||
         */
 | 
			
		||||
        function ConductorService(now, domains) {
 | 
			
		||||
            var initialEnd =
 | 
			
		||||
                Math.ceil(now() /  SIX_HOURS_IN_MS) * SIX_HOURS_IN_MS;
 | 
			
		||||
 | 
			
		||||
            this.conductor = new TimeConductor(
 | 
			
		||||
                initialEnd - ONE_DAY_IN_MS,
 | 
			
		||||
                initialEnd,
 | 
			
		||||
                domains
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the global instance of the time conductor.
 | 
			
		||||
         * @returns {platform/features/conductor.TimeConductor} the
 | 
			
		||||
         *         time conductor
 | 
			
		||||
         */
 | 
			
		||||
        ConductorService.prototype.getConductor = function () {
 | 
			
		||||
            return this.conductor;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return ConductorService;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,76 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Decorates the `telemetryService` such that requests are
 | 
			
		||||
         * mediated by the time conductor.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/features/conductor
 | 
			
		||||
         * @implements {TelemetryService}
 | 
			
		||||
         * @param {platform/features/conductor.ConductorService} conductorServe
 | 
			
		||||
         *        the service which exposes the global time conductor
 | 
			
		||||
         * @param {TelemetryService} telemetryService the decorated service
 | 
			
		||||
         */
 | 
			
		||||
        function ConductorTelemetryDecorator(conductorService, telemetryService) {
 | 
			
		||||
            this.conductorService = conductorService;
 | 
			
		||||
            this.telemetryService = telemetryService;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ConductorTelemetryDecorator.prototype.amendRequests = function (requests) {
 | 
			
		||||
            var conductor = this.conductorService.getConductor(),
 | 
			
		||||
                start = conductor.displayStart(),
 | 
			
		||||
                end = conductor.displayEnd(),
 | 
			
		||||
                domain = conductor.domain();
 | 
			
		||||
 | 
			
		||||
            function amendRequest(request) {
 | 
			
		||||
                request = request || {};
 | 
			
		||||
                request.start = start;
 | 
			
		||||
                request.end = end;
 | 
			
		||||
                request.domain = domain;
 | 
			
		||||
                return request;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return (requests || []).map(amendRequest);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) {
 | 
			
		||||
            var self = this;
 | 
			
		||||
            return this.telemetryService
 | 
			
		||||
                .requestTelemetry(this.amendRequests(requests));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) {
 | 
			
		||||
            var self = this;
 | 
			
		||||
 | 
			
		||||
            return this.telemetryService
 | 
			
		||||
                .subscribe(callback, this.amendRequests(requests));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return ConductorTelemetryDecorator;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										103
									
								
								platform/features/conductor/src/TimeConductor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,103 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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*/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The time conductor bundle adds a global control to the bottom of the
 | 
			
		||||
 * outermost viewing area. This controls both the range for time-based
 | 
			
		||||
 * queries and for time-based displays.
 | 
			
		||||
 *
 | 
			
		||||
 * @namespace platform/features/conductor
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    function () {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Tracks the current state of the time conductor.
 | 
			
		||||
         *
 | 
			
		||||
         * @memberof platform/features/conductor
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param {number} start the initial start time
 | 
			
		||||
         * @param {number} end the initial end time
 | 
			
		||||
         */
 | 
			
		||||
        function TimeConductor(start, end, domains) {
 | 
			
		||||
            this.range = { start: start, end: end };
 | 
			
		||||
            this.domains = domains;
 | 
			
		||||
            this.activeDomain = domains[0].key;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get or set (if called with an argument) the start time for displays.
 | 
			
		||||
         * @param {number} [value] the start time to set
 | 
			
		||||
         * @returns {number} the start time
 | 
			
		||||
         */
 | 
			
		||||
        TimeConductor.prototype.displayStart = function (value) {
 | 
			
		||||
            if (arguments.length > 0) {
 | 
			
		||||
                this.range.start = value;
 | 
			
		||||
            }
 | 
			
		||||
            return this.range.start;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get or set (if called with an argument) the end time for displays.
 | 
			
		||||
         * @param {number} [value] the end time to set
 | 
			
		||||
         * @returns {number} the end time
 | 
			
		||||
         */
 | 
			
		||||
        TimeConductor.prototype.displayEnd = function (value) {
 | 
			
		||||
            if (arguments.length > 0) {
 | 
			
		||||
                this.range.end = value;
 | 
			
		||||
            }
 | 
			
		||||
            return this.range.end;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get available domain options which can be used to bound time
 | 
			
		||||
         * selection.
 | 
			
		||||
         * @returns {TelemetryDomain[]} available domains
 | 
			
		||||
         */
 | 
			
		||||
        TimeConductor.prototype.domainOptions = function () {
 | 
			
		||||
            return this.domains;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get or set (if called with an argument) the active domain.
 | 
			
		||||
         * @param {string} [key] the key identifying the domain choice
 | 
			
		||||
         * @returns {TelemetryDomain} the active telemetry domain
 | 
			
		||||
         */
 | 
			
		||||
        TimeConductor.prototype.domain = function (key) {
 | 
			
		||||
            function matchesKey(domain) {
 | 
			
		||||
                return domain.key === key;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (arguments.length > 0) {
 | 
			
		||||
                if (!this.domains.some(matchesKey)) {
 | 
			
		||||
                    throw new Error("Unknown domain " + key);
 | 
			
		||||
                }
 | 
			
		||||
                this.activeDomain = key;
 | 
			
		||||
            }
 | 
			
		||||
            return this.activeDomain;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return TimeConductor;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										259
									
								
								platform/features/conductor/test/ConductorRepresenterSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,259 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,describe,it,expect,beforeEach,waitsFor,afterEach,jasmine*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/ConductorRepresenter", "./TestTimeConductor"],
 | 
			
		||||
    function (ConductorRepresenter, TestTimeConductor) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var SCOPE_METHODS = [
 | 
			
		||||
                '$on',
 | 
			
		||||
                '$watch',
 | 
			
		||||
                '$broadcast',
 | 
			
		||||
                '$emit',
 | 
			
		||||
                '$new',
 | 
			
		||||
                '$destroy'
 | 
			
		||||
            ],
 | 
			
		||||
            ELEMENT_METHODS = [
 | 
			
		||||
                'hasClass',
 | 
			
		||||
                'addClass',
 | 
			
		||||
                'removeClass',
 | 
			
		||||
                'css',
 | 
			
		||||
                'after',
 | 
			
		||||
                'remove'
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
        describe("ConductorRepresenter", function () {
 | 
			
		||||
            var mockThrottle,
 | 
			
		||||
                mockConductorService,
 | 
			
		||||
                mockCompile,
 | 
			
		||||
                testViews,
 | 
			
		||||
                mockScope,
 | 
			
		||||
                mockElement,
 | 
			
		||||
                mockConductor,
 | 
			
		||||
                mockCompiledTemplate,
 | 
			
		||||
                mockNewScope,
 | 
			
		||||
                mockNewElement,
 | 
			
		||||
                representer;
 | 
			
		||||
 | 
			
		||||
            function fireWatch(scope, watch, value) {
 | 
			
		||||
                scope.$watch.calls.forEach(function (call) {
 | 
			
		||||
                    if (call.args[0] === watch) {
 | 
			
		||||
                        call.args[1](value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockThrottle = jasmine.createSpy('throttle');
 | 
			
		||||
                mockConductorService = jasmine.createSpyObj(
 | 
			
		||||
                    'conductorService',
 | 
			
		||||
                    ['getConductor']
 | 
			
		||||
                );
 | 
			
		||||
                mockCompile = jasmine.createSpy('$compile');
 | 
			
		||||
                testViews = [ { someKey: "some value" } ];
 | 
			
		||||
                mockScope = jasmine.createSpyObj('scope', SCOPE_METHODS);
 | 
			
		||||
                mockElement = jasmine.createSpyObj('element', ELEMENT_METHODS);
 | 
			
		||||
                mockConductor = new TestTimeConductor();
 | 
			
		||||
                mockCompiledTemplate = jasmine.createSpy('template');
 | 
			
		||||
                mockNewScope = jasmine.createSpyObj('newScope', SCOPE_METHODS);
 | 
			
		||||
                mockNewElement = jasmine.createSpyObj('newElement', ELEMENT_METHODS);
 | 
			
		||||
                mockNewElement[0] = mockNewElement;
 | 
			
		||||
 | 
			
		||||
                mockConductorService.getConductor.andReturn(mockConductor);
 | 
			
		||||
                mockCompile.andReturn(mockCompiledTemplate);
 | 
			
		||||
                mockCompiledTemplate.andReturn(mockNewElement);
 | 
			
		||||
                mockScope.$new.andReturn(mockNewScope);
 | 
			
		||||
                mockThrottle.andCallFake(function (fn) {
 | 
			
		||||
                    return fn;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                representer = new ConductorRepresenter(
 | 
			
		||||
                    mockThrottle,
 | 
			
		||||
                    mockConductorService,
 | 
			
		||||
                    mockCompile,
 | 
			
		||||
                    testViews,
 | 
			
		||||
                    mockScope,
 | 
			
		||||
                    mockElement
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            afterEach(function () {
 | 
			
		||||
                representer.destroy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("adds a conductor to views", function () {
 | 
			
		||||
                representer.represent(testViews[0], {});
 | 
			
		||||
                expect(mockElement.after).toHaveBeenCalledWith(mockNewElement);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("adds nothing to non-view representations", function () {
 | 
			
		||||
                representer.represent({ someKey: "something else" }, {});
 | 
			
		||||
                expect(mockElement.after).not.toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("removes the conductor when destroyed", function () {
 | 
			
		||||
                representer.represent(testViews[0], {});
 | 
			
		||||
                expect(mockNewElement.remove).not.toHaveBeenCalled();
 | 
			
		||||
                representer.destroy();
 | 
			
		||||
                expect(mockNewElement.remove).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("destroys any new scope created", function () {
 | 
			
		||||
                representer.represent(testViews[0], {});
 | 
			
		||||
                representer.destroy();
 | 
			
		||||
                expect(mockNewScope.$destroy.calls.length)
 | 
			
		||||
                    .toEqual(mockScope.$new.calls.length);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("exposes conductor state in scope", function () {
 | 
			
		||||
                mockConductor.displayStart.andReturn(1977);
 | 
			
		||||
                mockConductor.displayEnd.andReturn(1984);
 | 
			
		||||
                mockConductor.domain.andReturn('d');
 | 
			
		||||
                representer.represent(testViews[0], {});
 | 
			
		||||
 | 
			
		||||
                expect(mockNewScope.ngModel.conductor).toEqual({
 | 
			
		||||
                    inner: { start: 1977, end: 1984, domain: 'd' },
 | 
			
		||||
                    outer: { start: 1977, end: 1984, domain: 'd' }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates conductor state from scope", function () {
 | 
			
		||||
                var testState = {
 | 
			
		||||
                    inner: { start: 42, end: 1984 },
 | 
			
		||||
                    outer: { start: -1977, end: 12321 }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                representer.represent(testViews[0], {});
 | 
			
		||||
 | 
			
		||||
                mockNewScope.ngModel.conductor = testState;
 | 
			
		||||
 | 
			
		||||
                fireWatch(
 | 
			
		||||
                    mockNewScope,
 | 
			
		||||
                    'ngModel.conductor.inner.start',
 | 
			
		||||
                    testState.inner.start
 | 
			
		||||
                );
 | 
			
		||||
                expect(mockConductor.displayStart).toHaveBeenCalledWith(42);
 | 
			
		||||
 | 
			
		||||
                fireWatch(
 | 
			
		||||
                    mockNewScope,
 | 
			
		||||
                    'ngModel.conductor.inner.end',
 | 
			
		||||
                    testState.inner.end
 | 
			
		||||
                );
 | 
			
		||||
                expect(mockConductor.displayEnd).toHaveBeenCalledWith(1984);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("when bounds are changing", function () {
 | 
			
		||||
                var startWatch = "ngModel.conductor.inner.start",
 | 
			
		||||
                    endWatch = "ngModel.conductor.inner.end",
 | 
			
		||||
                    mockThrottledFn = jasmine.createSpy('throttledFn'),
 | 
			
		||||
                    testBounds;
 | 
			
		||||
 | 
			
		||||
                function fireThrottledFn() {
 | 
			
		||||
                    mockThrottle.mostRecentCall.args[0]();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockThrottle.andReturn(mockThrottledFn);
 | 
			
		||||
                    representer.represent(testViews[0], {});
 | 
			
		||||
                    testBounds = { start: 0, end: 1000 };
 | 
			
		||||
                    mockNewScope.ngModel.conductor.inner = testBounds;
 | 
			
		||||
                    mockConductor.displayStart.andCallFake(function () {
 | 
			
		||||
                        return testBounds.start;
 | 
			
		||||
                    });
 | 
			
		||||
                    mockConductor.displayEnd.andCallFake(function () {
 | 
			
		||||
                        return testBounds.end;
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("does not broadcast while bounds are changing", function () {
 | 
			
		||||
                    expect(mockScope.$broadcast).not.toHaveBeenCalled();
 | 
			
		||||
                    testBounds.start = 100;
 | 
			
		||||
                    fireWatch(mockNewScope, startWatch, testBounds.start);
 | 
			
		||||
                    testBounds.end = 500;
 | 
			
		||||
                    fireWatch(mockNewScope, endWatch, testBounds.end);
 | 
			
		||||
                    fireThrottledFn();
 | 
			
		||||
                    testBounds.start = 200;
 | 
			
		||||
                    fireWatch(mockNewScope, startWatch, testBounds.start);
 | 
			
		||||
                    testBounds.end = 400;
 | 
			
		||||
                    fireWatch(mockNewScope, endWatch, testBounds.end);
 | 
			
		||||
                    fireThrottledFn();
 | 
			
		||||
                    expect(mockScope.$broadcast).not.toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("does broadcast when bounds have stabilized", function () {
 | 
			
		||||
                    expect(mockScope.$broadcast).not.toHaveBeenCalled();
 | 
			
		||||
                    testBounds.start = 100;
 | 
			
		||||
                    fireWatch(mockNewScope, startWatch, testBounds.start);
 | 
			
		||||
                    testBounds.end = 500;
 | 
			
		||||
                    fireWatch(mockNewScope, endWatch, testBounds.end);
 | 
			
		||||
                    fireThrottledFn();
 | 
			
		||||
                    fireWatch(mockNewScope, startWatch, testBounds.start);
 | 
			
		||||
                    fireWatch(mockNewScope, endWatch, testBounds.end);
 | 
			
		||||
                    fireThrottledFn();
 | 
			
		||||
                    expect(mockScope.$broadcast).toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("exposes domain selection in scope", function () {
 | 
			
		||||
                representer.represent(testViews[0], null);
 | 
			
		||||
 | 
			
		||||
                expect(mockNewScope.ngModel.domain)
 | 
			
		||||
                    .toEqual(mockConductor.domain());
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("exposes domain options in scope", function () {
 | 
			
		||||
                representer.represent(testViews[0], null);
 | 
			
		||||
 | 
			
		||||
                mockConductor.domainOptions().forEach(function (option, i) {
 | 
			
		||||
                    expect(mockNewScope.ngModel.options[i].value)
 | 
			
		||||
                        .toEqual(option.key);
 | 
			
		||||
                    expect(mockNewScope.ngModel.options[i].name)
 | 
			
		||||
                        .toEqual(option.name);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates domain selection from scope", function () {
 | 
			
		||||
                var choice;
 | 
			
		||||
                representer.represent(testViews[0], null);
 | 
			
		||||
 | 
			
		||||
                // Choose a domain that isn't currently selected
 | 
			
		||||
                mockNewScope.ngModel.options.forEach(function (option) {
 | 
			
		||||
                    if (option.value !== mockNewScope.ngModel.domain) {
 | 
			
		||||
                        choice = option.value;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                expect(mockConductor.domain)
 | 
			
		||||
                    .not.toHaveBeenCalledWith(choice);
 | 
			
		||||
 | 
			
		||||
                mockNewScope.ngModel.domain = choice;
 | 
			
		||||
                fireWatch(mockNewScope, "ngModel.domain", choice);
 | 
			
		||||
 | 
			
		||||
                expect(mockConductor.domain)
 | 
			
		||||
                    .toHaveBeenCalledWith(choice);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										58
									
								
								platform/features/conductor/test/ConductorServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,58 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,describe,it,expect,beforeEach,waitsFor,jasmine*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/ConductorService"],
 | 
			
		||||
    function (ConductorService) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var TEST_NOW = 1020304050;
 | 
			
		||||
 | 
			
		||||
        describe("ConductorService", function () {
 | 
			
		||||
            var mockNow,
 | 
			
		||||
                conductorService;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockNow = jasmine.createSpy('now');
 | 
			
		||||
                mockNow.andReturn(TEST_NOW);
 | 
			
		||||
                conductorService = new ConductorService(mockNow, [
 | 
			
		||||
                    { key: "d1", name: "Domain #1" },
 | 
			
		||||
                    { key: "d2", name: "Domain #2" }
 | 
			
		||||
                ]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("initializes a time conductor around the current time", function () {
 | 
			
		||||
                var conductor = conductorService.getConductor();
 | 
			
		||||
                expect(conductor.displayStart() <= TEST_NOW).toBeTruthy();
 | 
			
		||||
                expect(conductor.displayEnd() >= TEST_NOW).toBeTruthy();
 | 
			
		||||
                expect(conductor.displayEnd() > conductor.displayStart())
 | 
			
		||||
                    .toBeTruthy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("provides a single shared time conductor instance", function () {
 | 
			
		||||
                expect(conductorService.getConductor())
 | 
			
		||||
                    .toBe(conductorService.getConductor());
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,160 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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,describe,it,expect,beforeEach,waitsFor,jasmine*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/ConductorTelemetryDecorator", "./TestTimeConductor"],
 | 
			
		||||
    function (ConductorTelemetryDecorator, TestTimeConductor) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        describe("ConductorTelemetryDecorator", function () {
 | 
			
		||||
            var mockTelemetryService,
 | 
			
		||||
                mockConductorService,
 | 
			
		||||
                mockConductor,
 | 
			
		||||
                mockPromise,
 | 
			
		||||
                mockSeries,
 | 
			
		||||
                decorator;
 | 
			
		||||
 | 
			
		||||
            function seriesIsInWindow(series) {
 | 
			
		||||
                var i, v, inWindow = true;
 | 
			
		||||
                for (i = 0; i < series.getPointCount(); i += 1) {
 | 
			
		||||
                    v = series.getDomainValue(i);
 | 
			
		||||
                    inWindow = inWindow && (v >= mockConductor.displayStart());
 | 
			
		||||
                    inWindow = inWindow && (v <= mockConductor.displayEnd());
 | 
			
		||||
                }
 | 
			
		||||
                return inWindow;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockTelemetryService = jasmine.createSpyObj(
 | 
			
		||||
                    'telemetryService',
 | 
			
		||||
                    [ 'requestTelemetry', 'subscribe' ]
 | 
			
		||||
                );
 | 
			
		||||
                mockConductorService = jasmine.createSpyObj(
 | 
			
		||||
                    'conductorService',
 | 
			
		||||
                    ['getConductor']
 | 
			
		||||
                );
 | 
			
		||||
                mockConductor = new TestTimeConductor();
 | 
			
		||||
                mockPromise = jasmine.createSpyObj(
 | 
			
		||||
                    'promise',
 | 
			
		||||
                    ['then']
 | 
			
		||||
                );
 | 
			
		||||
                mockSeries = jasmine.createSpyObj(
 | 
			
		||||
                    'series',
 | 
			
		||||
                    [ 'getPointCount', 'getDomainValue', 'getRangeValue' ]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockTelemetryService.requestTelemetry.andReturn(mockPromise);
 | 
			
		||||
                mockConductorService.getConductor.andReturn(mockConductor);
 | 
			
		||||
 | 
			
		||||
                // Prepare test series; make sure it has a broad range of
 | 
			
		||||
                // domain values, with at least some in the query-able range
 | 
			
		||||
                mockSeries.getPointCount.andReturn(1000);
 | 
			
		||||
                mockSeries.getDomainValue.andCallFake(function (i) {
 | 
			
		||||
                    var j = i - 500;
 | 
			
		||||
                    return j * j * j;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockConductor.displayStart.andReturn(42);
 | 
			
		||||
                mockConductor.displayEnd.andReturn(1977);
 | 
			
		||||
                mockConductor.domain.andReturn("testDomain");
 | 
			
		||||
 | 
			
		||||
                decorator = new ConductorTelemetryDecorator(
 | 
			
		||||
                    mockConductorService,
 | 
			
		||||
                    mockTelemetryService
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            describe("decorates historical requests", function () {
 | 
			
		||||
                var request;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    decorator.requestTelemetry([{ someKey: "some value" }]);
 | 
			
		||||
                    request = mockTelemetryService.requestTelemetry
 | 
			
		||||
                        .mostRecentCall.args[0][0];
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("with start times", function () {
 | 
			
		||||
                    expect(request.start).toEqual(mockConductor.displayStart());
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("with end times", function () {
 | 
			
		||||
                    expect(request.end).toEqual(mockConductor.displayEnd());
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("with domain selection", function () {
 | 
			
		||||
                    expect(request.domain).toEqual(mockConductor.domain());
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("decorates subscription requests", function () {
 | 
			
		||||
                var request;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    var mockCallback = jasmine.createSpy('callback');
 | 
			
		||||
                    decorator.subscribe(mockCallback, [{ someKey: "some value" }]);
 | 
			
		||||
                    request = mockTelemetryService.subscribe
 | 
			
		||||
                        .mostRecentCall.args[1][0];
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("with start times", function () {
 | 
			
		||||
                    expect(request.start).toEqual(mockConductor.displayStart());
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("with end times", function () {
 | 
			
		||||
                    expect(request.end).toEqual(mockConductor.displayEnd());
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("with domain selection", function () {
 | 
			
		||||
                    expect(request.domain).toEqual(mockConductor.domain());
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("adds display start/end times & domain selection to historical requests", function () {
 | 
			
		||||
                decorator.requestTelemetry([{ someKey: "some value" }]);
 | 
			
		||||
                expect(mockTelemetryService.requestTelemetry)
 | 
			
		||||
                    .toHaveBeenCalledWith([{
 | 
			
		||||
                        someKey: "some value",
 | 
			
		||||
                        start: mockConductor.displayStart(),
 | 
			
		||||
                        end: mockConductor.displayEnd(),
 | 
			
		||||
                        domain: jasmine.any(String)
 | 
			
		||||
                    }]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("adds display start/end times & domain selection to subscription requests", function () {
 | 
			
		||||
                var mockCallback = jasmine.createSpy('callback');
 | 
			
		||||
                decorator.subscribe(mockCallback, [{ someKey: "some value" }]);
 | 
			
		||||
                expect(mockTelemetryService.subscribe)
 | 
			
		||||
                    .toHaveBeenCalledWith(jasmine.any(Function), [{
 | 
			
		||||
                        someKey: "some value",
 | 
			
		||||
                        start: mockConductor.displayStart(),
 | 
			
		||||
                        end: mockConductor.displayEnd(),
 | 
			
		||||
                        domain: jasmine.any(String)
 | 
			
		||||
                    }]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||