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