Compare commits
	
		
			1 Commits
		
	
	
		
			time-condu
			...
			table-fixe
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d2e064dc52 | 
							
								
								
									
										4
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								app.js
									
									
									
									
									
								
							| @@ -16,7 +16,7 @@ const request = require('request'); | ||||
|  | ||||
| // Defaults | ||||
| options.port = options.port || options.p || 8080; | ||||
| options.host = options.host || options.h || 'localhost'; | ||||
| options.host = options.host || options.h || 'localhost' | ||||
| options.directory = options.directory || options.D || '.'; | ||||
|  | ||||
| // Show command line options | ||||
| @@ -46,7 +46,7 @@ webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); | ||||
| webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) }); | ||||
|  | ||||
| webpackConfig.entry.openmct = [ | ||||
|     'webpack-hot-middleware/client?reload=true', | ||||
|     'webpack-hot-middleware/client', | ||||
|     webpackConfig.entry.openmct | ||||
| ]; | ||||
|  | ||||
|   | ||||
| @@ -49,7 +49,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "eventGenerator", | ||||
|                     "name": "Event Message Generator", | ||||
|                     "cssClass": "icon-generator-events", | ||||
|                     "cssClass": "icon-folder-new", | ||||
|                     "description": "For development use. Creates sample event message data that mimics a live data stream.", | ||||
|                     "priority": 10, | ||||
|                     "features": "creation", | ||||
|   | ||||
| @@ -37,25 +37,25 @@ define([ | ||||
|         }, | ||||
|         LIMITS = { | ||||
|             rh: { | ||||
|                 cssClass: "is-limit--upr is-limit--red", | ||||
|                 cssClass: "s-limit-upr s-limit-red", | ||||
|                 low: RED, | ||||
|                 high: Number.POSITIVE_INFINITY, | ||||
|                 name: "Red High" | ||||
|             }, | ||||
|             rl: { | ||||
|                 cssClass: "is-limit--lwr is-limit--red", | ||||
|                 cssClass: "s-limit-lwr s-limit-red", | ||||
|                 high: -RED, | ||||
|                 low: Number.NEGATIVE_INFINITY, | ||||
|                 name: "Red Low" | ||||
|             }, | ||||
|             yh: { | ||||
|                 cssClass: "is-limit--upr is-limit--yellow", | ||||
|                 cssClass: "s-limit-upr s-limit-yellow", | ||||
|                 low: YELLOW, | ||||
|                 high: RED, | ||||
|                 name: "Yellow High" | ||||
|             }, | ||||
|             yl: { | ||||
|                 cssClass: "is-limit--lwr is-limit--yellow", | ||||
|                 cssClass: "s-limit-lwr s-limit-yellow", | ||||
|                 low: -RED, | ||||
|                 high: -YELLOW, | ||||
|                 name: "Yellow Low" | ||||
|   | ||||
| @@ -38,7 +38,7 @@ define([ | ||||
|         openmct.types.addType("example.state-generator", { | ||||
|             name: "State Generator", | ||||
|             description: "For development use.  Generates test enumerated telemetry by cycling through a given set of states", | ||||
|             cssClass: "icon-generator-telemetry", | ||||
|             cssClass: "icon-telemetry", | ||||
|             creatable: true, | ||||
|             form: [ | ||||
|                 { | ||||
| @@ -66,7 +66,7 @@ define([ | ||||
|         openmct.types.addType("generator", { | ||||
|             name: "Sine Wave Generator", | ||||
|             description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.", | ||||
|             cssClass: "icon-generator-telemetry", | ||||
|             cssClass: "icon-telemetry", | ||||
|             creatable: true, | ||||
|             form: [ | ||||
|                 { | ||||
|   | ||||
| @@ -26,16 +26,12 @@ define([ | ||||
|     "./src/NotificationLaunchController", | ||||
|     "./src/DialogLaunchIndicator", | ||||
|     "./src/NotificationLaunchIndicator", | ||||
|     "./res/dialog-launch.html", | ||||
|     "./res/notification-launch.html", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     DialogLaunchController, | ||||
|     NotificationLaunchController, | ||||
|     DialogLaunchIndicator, | ||||
|     NotificationLaunchIndicator, | ||||
|     DialogLaunch, | ||||
|     NotificationLaunch, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|     "use strict"; | ||||
| @@ -45,11 +41,11 @@ define([ | ||||
|             "templates": [ | ||||
|                 { | ||||
|                     "key": "dialogLaunchTemplate", | ||||
|                     "template": DialogLaunch | ||||
|                     "templateUrl": "dialog-launch.html" | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "notificationLaunchTemplate", | ||||
|                     "template": NotificationLaunch | ||||
|                     "templateUrl": "notification-launch.html" | ||||
|                 } | ||||
|             ], | ||||
|             "controllers": [ | ||||
|   | ||||
| @@ -51,26 +51,76 @@ define( | ||||
|                 return actionTexts[Math.floor(Math.random()*3)]; | ||||
|             } | ||||
|  | ||||
|             function getExampleActions() { | ||||
|                 var actions = [ | ||||
|                     { | ||||
|                         label: "Try Again", | ||||
|                         callback: function () { | ||||
|                             $log.debug("Try Again pressed"); | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         label: "Remove", | ||||
|                         callback: function () { | ||||
|                             $log.debug("Remove pressed"); | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         label: "Cancel", | ||||
|                         callback: function () { | ||||
|                             $log.debug("Cancel pressed"); | ||||
|                         } | ||||
|                     } | ||||
|                 ]; | ||||
|  | ||||
|                 // Randomly remove some actions off the top; leave at least one | ||||
|                 actions.splice(0,Math.floor(Math.random() * actions.length)); | ||||
|  | ||||
|                 return actions; | ||||
|             } | ||||
|  | ||||
|             function getExampleSeverity() { | ||||
|                 var severities = [ | ||||
|                     "info", | ||||
|                     "alert", | ||||
|                     "error" | ||||
|                 ]; | ||||
|                 return severities[Math.floor(Math.random() * severities.length)]; | ||||
|             } | ||||
|  | ||||
|             /** | ||||
|              * Launch a new notification with a severity level of 'Error'. | ||||
|              */ | ||||
|             $scope.newError = function () { | ||||
|             $scope.newError = function(){ | ||||
|  | ||||
|                 notificationService.notify({ | ||||
|                     title: "Example error notification " + messageCounter++, | ||||
|                     hint: "An error has occurred", | ||||
|                     severity: "error" | ||||
|                 }); | ||||
|                     severity: "error", | ||||
|                     primaryOption: { | ||||
|                         label: 'Retry', | ||||
|                         callback: function() { | ||||
|                             $log.info('Retry clicked'); | ||||
|                         } | ||||
|                     }, | ||||
|                     options: getExampleActions()}); | ||||
|             }; | ||||
|             /** | ||||
|              * Launch a new notification with a severity of 'Alert'. | ||||
|              */ | ||||
|             $scope.newAlert = function () { | ||||
|             $scope.newAlert = function(){ | ||||
|  | ||||
|                 notificationService.notify({ | ||||
|                     title: "Alert notification " + (messageCounter++), | ||||
|                     hint: "This is an alert message", | ||||
|                     severity: "alert", | ||||
|                     autoDismiss: true | ||||
|                 }); | ||||
|                     primaryOption: { | ||||
|                         label: 'Retry', | ||||
|                         callback: function() { | ||||
|                             $log.info('Retry clicked'); | ||||
|                         } | ||||
|                     }, | ||||
|                     options: getExampleActions()}); | ||||
|             }; | ||||
|  | ||||
|  | ||||
| @@ -78,42 +128,39 @@ define( | ||||
|              * Launch a new notification with a progress bar that is updated | ||||
|              * periodically, tracking an ongoing process. | ||||
|              */ | ||||
|             $scope.newProgress = function () { | ||||
|                 let progress = 0; | ||||
|             $scope.newProgress = function(){ | ||||
|  | ||||
|                 var notificationModel = { | ||||
|                     title: "Progress notification example", | ||||
|                     severity: "info", | ||||
|                     progress: progress, | ||||
|                     actionText: getExampleActionText() | ||||
|                     progress: 0, | ||||
|                     actionText: getExampleActionText(), | ||||
|                     unknownProgress: false | ||||
|                 }; | ||||
|                 let notification; | ||||
|  | ||||
|                 /** | ||||
|                  * Simulate an ongoing process and update the progress bar. | ||||
|                  * @param notification | ||||
|                  */ | ||||
|                 function incrementProgress() { | ||||
|                     progress = Math.min(100, Math.floor(progress + Math.random() * 30)) | ||||
|                     let progressText = ["Estimated time" + | ||||
|                 function incrementProgress(notificationModel) { | ||||
|                     notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30)); | ||||
|                     notificationModel.progressText = ["Estimated time" + | ||||
|                     " remaining:" + | ||||
|                     " about ", 60 - Math.floor((progress / 100) * 60), " seconds"].join(" "); | ||||
|                     notification.progress(progress, progressText); | ||||
|  | ||||
|                     if (progress < 100) { | ||||
|                         $timeout(function () { | ||||
|                             incrementProgress(notificationModel); | ||||
|                         }, 1000); | ||||
|                     " about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" "); | ||||
|                     if (notificationModel.progress < 100) { | ||||
|                         $timeout(function(){incrementProgress(notificationModel);}, 1000); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 notification = notificationService.notify(notificationModel); | ||||
|                 incrementProgress(); | ||||
|                 notificationService.notify(notificationModel); | ||||
|                 incrementProgress(notificationModel); | ||||
|             }; | ||||
|  | ||||
|             /** | ||||
|              * Launch a new notification with severity level of INFO. | ||||
|              */ | ||||
|             $scope.newInfo = function () { | ||||
|             $scope.newInfo = function(){ | ||||
|  | ||||
|                 notificationService.info({ | ||||
|                     title: "Example Info notification " + messageCounter++ | ||||
|                 }); | ||||
|   | ||||
							
								
								
									
										203
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										203
									
								
								index.html
									
									
									
									
									
								
							| @@ -49,7 +49,6 @@ | ||||
|         openmct.install(openmct.plugins.ExampleImagery()); | ||||
|         openmct.install(openmct.plugins.UTCTimeSystem()); | ||||
|         openmct.install(openmct.plugins.ImportExport()); | ||||
|         openmct.install(openmct.plugins.FixedView()); | ||||
|         openmct.install(openmct.plugins.AutoflowView({ | ||||
|             type: "telemetry.panel" | ||||
|         })); | ||||
| @@ -76,210 +75,8 @@ | ||||
|         })); | ||||
|         openmct.install(openmct.plugins.SummaryWidget()); | ||||
|         openmct.install(openmct.plugins.Notebook()); | ||||
|         openmct.install(openmct.plugins.FolderView()); | ||||
|         openmct.install(openmct.plugins.Tabs()); | ||||
|         openmct.install(openmct.plugins.FlexibleLayout()); | ||||
|         openmct.install(openmct.plugins.LADTable()); | ||||
|         openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0}); | ||||
|         openmct.time.timeSystem('utc'); | ||||
|         openmct.start(); | ||||
|  | ||||
|         // openmct.toolbars.addProvider({ | ||||
|         //     name: "Testing Toolbar", | ||||
|         //     key: "testing", | ||||
|         //     description: "a mock toolbar that exercises all controls", | ||||
|         //     forSelection: function (selection) { | ||||
|         //         return true; // always applies. | ||||
|         //     }, | ||||
|         //     toolbar: function (selection) { | ||||
|         //         return [ | ||||
|         //             { | ||||
|         //                 control: 'menu', | ||||
|         //                 icon: 'icon-plus', | ||||
|         //                 label: 'Add', | ||||
|         //                 options: [ | ||||
|         //                     { name: 'Box', class: 'icon-box', title: 'Add Box' }, | ||||
|         //                     { name: 'Line', class: 'icon-line-horz', title: 'Add Line' }, | ||||
|         //                     { name: 'Text', class: 'icon-font', title: 'Add Text' }, | ||||
|         //                     { name: 'Image', class: 'icon-image', title: 'Add Image' } | ||||
|         //                 ] | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'separator' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'color-picker', | ||||
|         //                 icon: 'icon-paint-bucket', | ||||
|         //                 value: '#33ff00', | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'color-picker', | ||||
|         //                 icon: 'icon-pencil', | ||||
|         //                 value: '#ffffff', | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'color-picker', | ||||
|         //                 icon: 'icon-font', | ||||
|         //                 value: '#333333', | ||||
|         //             }, | ||||
|         // | ||||
|         //             { | ||||
|         //                 control: 'separator' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'select-menu', | ||||
|         //                 value: 11, | ||||
|         //                 options: [ | ||||
|         //                     { value: 9, name: '9 px' }, | ||||
|         //                     { value: 10, name: '10 px' }, | ||||
|         //                     { value: 11, name: '11 px' }, | ||||
|         //                     { value: 12, name: '12 px' }, | ||||
|         //                     { value: 13, name: '13 px' }, | ||||
|         //                     { value: 14, name: '14 px' }, | ||||
|         //                     { value: 16, name: '16 px' }, | ||||
|         //                     { value: 18, name: '18 px' }, | ||||
|         //                     { value: 20, name: '20 px' }, | ||||
|         //                     { value: 24, name: '24 px' }, | ||||
|         //                     { value: 28, name: '28 px' }, | ||||
|         //                     { value: 32, name: '32 px' }, | ||||
|         //                     { value: 40, name: '40 px' }, | ||||
|         //                     { value: 48, name: '48 px' }, | ||||
|         //                     { value: 56, name: '56 px' }, | ||||
|         //                     { value: 64, name: '64 px' }, | ||||
|         //                     { value: 72, name: '72 px' }, | ||||
|         //                     { value: 80, name: '80 px' }, | ||||
|         //                     { value: 88, name: '88 px' }, | ||||
|         //                     { value: 96, name: '96 px' }, | ||||
|         //                     { value: 128, name: '128 px' }, | ||||
|         //                     { value: 160, name: '160 px' } | ||||
|         //                 ] | ||||
|         //             }, | ||||
|         // | ||||
|         //             { | ||||
|         //                 control: 'separator' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'menu', | ||||
|         //                 icon: 'icon-layers', | ||||
|         //                 options: [ | ||||
|         //                     { name: 'Move to top', class: 'icon-arrow-double-up', title: 'Move to top' }, | ||||
|         //                     { name: 'Move up', class: 'icon-arrow-up', title: 'Move up' }, | ||||
|         //                     { name: 'Move down', class: 'icon-arrow-down', title: 'Move down' }, | ||||
|         //                     { name: 'Move to bottom', class: 'icon-arrow-double-down', title: 'Move to bottom' } | ||||
|         //                 ] | ||||
|         //             }, | ||||
|         // | ||||
|         //             { | ||||
|         //                 control: 'separator' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'button', | ||||
|         //                 icon: 'icon-gear' | ||||
|         //             }, | ||||
|         // | ||||
|         //             { | ||||
|         //                 control: 'separator' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'input', | ||||
|         //                 type: 'number', | ||||
|         //                 label: 'X', | ||||
|         //                 value: 1, | ||||
|         //                 title: 'X position' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'input', | ||||
|         //                 type: 'number', | ||||
|         //                 label: 'Y', | ||||
|         //                 value: 2, | ||||
|         //                 title: 'Y position' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'input', | ||||
|         //                 type: 'number', | ||||
|         //                 label: 'W', | ||||
|         //                 value: 3, | ||||
|         //                 title: 'Width' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'input', | ||||
|         //                 type: 'number', | ||||
|         //                 label: 'H', | ||||
|         //                 value: 4, | ||||
|         //                 title: 'Height' | ||||
|         //             }, | ||||
|         // | ||||
|         //             { | ||||
|         //                 control: 'separator' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'button', | ||||
|         //                 icon: 'icon-trash', | ||||
|         //                 label: 'delete', | ||||
|         //                 modifier: 'caution' | ||||
|         //             }, | ||||
|         // | ||||
|         //             { | ||||
|         //                 control: 'separator' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'checkbox', | ||||
|         //                 name: 'this is a checkbox', | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'separator' | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'toggle-button', | ||||
|         //                 title: 'Toggle Frame', | ||||
|         //                 property: 'hideFrame', | ||||
|         //                 value: false, | ||||
|         //                 options: [ | ||||
|         //                     { | ||||
|         //                         value: true, | ||||
|         //                         icon: 'icon-frame-hide' | ||||
|         //                     }, | ||||
|         //                     { | ||||
|         //                         value: false, | ||||
|         //                         icon: 'icon-frame-show' | ||||
|         //                     } | ||||
|         //                 ] | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'toggle-button', | ||||
|         //                 title: 'Snap to grid', | ||||
|         //                 property: 'snapToGrid', | ||||
|         //                 value: true, | ||||
|         //                 options: [ | ||||
|         //                     { | ||||
|         //                         value: true, | ||||
|         //                         icon: 'icon-grid-snap-to' | ||||
|         //                     }, | ||||
|         //                     { | ||||
|         //                         value: false, | ||||
|         //                         icon: 'icon-grid-snap-no' | ||||
|         //                     } | ||||
|         //                 ] | ||||
|         //             }, | ||||
|         //             { | ||||
|         //                 control: 'toggle-button', | ||||
|         //                 title: 'Toggle label', | ||||
|         //                 property: 'showLabel', | ||||
|         //                 value: true, | ||||
|         //                 options: [ | ||||
|         //                     { | ||||
|         //                         value: true, | ||||
|         //                         icon: 'icon-two-parts-both' | ||||
|         //                     }, | ||||
|         //                     { | ||||
|         //                         value: false, | ||||
|         //                         icon: 'icon-two-parts-one-only' | ||||
|         //                     } | ||||
|         //                 ] | ||||
|         //             } | ||||
|         //         ]; | ||||
|         //     } | ||||
|         // }); | ||||
|  | ||||
|     </script> | ||||
| </html> | ||||
|   | ||||
| @@ -58,6 +58,7 @@ | ||||
|     "printj": "^1.1.0", | ||||
|     "raw-loader": "^0.5.1", | ||||
|     "request": "^2.69.0", | ||||
|     "screenfull": "^3.3.2", | ||||
|     "split": "^1.0.0", | ||||
|     "style-loader": "^0.21.0", | ||||
|     "v8-compile-cache": "^1.1.0", | ||||
|   | ||||
| @@ -31,13 +31,16 @@ define([ | ||||
|     "./src/navigation/NavigateAction", | ||||
|     "./src/navigation/OrphanNavigationHandler", | ||||
|     "./src/windowing/NewTabAction", | ||||
|     "./src/windowing/FullscreenAction", | ||||
|     "./src/windowing/WindowTitler", | ||||
|     "./res/templates/browse.html", | ||||
|     "./res/templates/browse-object.html", | ||||
|     "./res/templates/items/grid-item.html", | ||||
|     "./res/templates/browse/object-header.html", | ||||
|     "./res/templates/browse/object-header-frame.html", | ||||
|     "./res/templates/menu-arrow.html", | ||||
|     "./res/templates/back-arrow.html", | ||||
|     "./res/templates/items/items.html", | ||||
|     "./res/templates/browse/object-properties.html", | ||||
|     "./res/templates/browse/inspector-region.html", | ||||
|     'legacyRegistry' | ||||
| @@ -52,13 +55,16 @@ define([ | ||||
|     NavigateAction, | ||||
|     OrphanNavigationHandler, | ||||
|     NewTabAction, | ||||
|     FullscreenAction, | ||||
|     WindowTitler, | ||||
|     browseTemplate, | ||||
|     browseObjectTemplate, | ||||
|     gridItemTemplate, | ||||
|     objectHeaderTemplate, | ||||
|     objectHeaderFrameTemplate, | ||||
|     menuArrowTemplate, | ||||
|     backArrowTemplate, | ||||
|     itemsTemplate, | ||||
|     objectPropertiesTemplate, | ||||
|     inspectorRegionTemplate, | ||||
|     legacyRegistry | ||||
| @@ -150,6 +156,19 @@ define([ | ||||
|                         "view" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "grid-item", | ||||
|                     "template": gridItemTemplate, | ||||
|                     "uses": [ | ||||
|                         "type", | ||||
|                         "action", | ||||
|                         "location" | ||||
|                     ], | ||||
|                     "gestures": [ | ||||
|                         "info", | ||||
|                         "menu" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "object-header", | ||||
|                     "template": objectHeaderTemplate, | ||||
| @@ -223,6 +242,30 @@ define([ | ||||
|                     "group": "windowing", | ||||
|                     "cssClass": "icon-new-window", | ||||
|                     "priority": "preferred" | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "fullscreen", | ||||
|                     "implementation": FullscreenAction, | ||||
|                     "category": "view-control", | ||||
|                     "group": "windowing", | ||||
|                     "priority": "default" | ||||
|                 } | ||||
|             ], | ||||
|             "views": [ | ||||
|                 { | ||||
|                     "key": "items", | ||||
|                     "name": "Grid", | ||||
|                     "cssClass": "icon-thumbs-strip", | ||||
|                     "description": "Grid of available items", | ||||
|                     "template": itemsTemplate, | ||||
|                     "uses": [ | ||||
|                         "composition" | ||||
|                     ], | ||||
|                     "gestures": [ | ||||
|                         "drop" | ||||
|                     ], | ||||
|                     "type": "folder", | ||||
|                     "editable": false | ||||
|                 } | ||||
|             ], | ||||
|             "runs": [ | ||||
| @@ -256,6 +299,18 @@ define([ | ||||
|                     key: "inspectorRegion", | ||||
|                     template: inspectorRegionTemplate | ||||
|                 } | ||||
|             ], | ||||
|             "licenses": [ | ||||
|                 { | ||||
|                     "name": "screenfull.js", | ||||
|                     "version": "1.2.0", | ||||
|                     "description": "Wrapper for cross-browser usage of fullscreen API", | ||||
|                     "author": "Sindre Sorhus", | ||||
|                     "website": "https://github.com/sindresorhus/screenfull.js/", | ||||
|                     "copyright": "Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)", | ||||
|                     "license": "license-mit", | ||||
|                     "link": "https://github.com/sindresorhus/screenfull.js/blob/gh-pages/license" | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
|   | ||||
							
								
								
									
										45
									
								
								platform/commonUI/browse/res/templates/items/grid-item.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								platform/commonUI/browse/res/templates/items/grid-item.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| <!-- | ||||
|  Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  as represented by the Administrator of the National Aeronautics and Space | ||||
|  Administration. All rights reserved. | ||||
|  | ||||
|  Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  "License"); you may not use this file except in compliance with the License. | ||||
|  You may obtain a copy of the License at | ||||
|  http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  | ||||
|  Unless required by applicable law or agreed to in writing, software | ||||
|  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  License for the specific language governing permissions and limitations | ||||
|  under the License. | ||||
|  | ||||
|  Open MCT includes source code licensed under additional open source | ||||
|  licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <!-- For selected, add class 'selected' to outer div --> | ||||
| <div class='item grid-item' ng-click='action.perform("navigate")'> | ||||
|     <div class='contents abs'> | ||||
|         <div class='top-bar bar abs'> | ||||
|             <span class='icon-people' title='Shared'></span> | ||||
|             <mct-representation class="desktop-hide" key="'info-button'" mct-object="domainObject"></mct-representation> | ||||
|         </div> | ||||
|         <div class='item-main abs lg'> | ||||
|             <span class="t-item-icon" ng-class="{ 'l-icon-link':location.isLink() }"> | ||||
|                 <span class="t-item-icon-glyph ng-binding {{type.getCssClass()}}"></span> | ||||
|             </span> | ||||
|             <div class='abs item-open icon-pointer-right'></div> | ||||
|         </div> | ||||
|         <div class='bottom-bar bar abs'> | ||||
|             <div class='title'>{{model.name}}</div> | ||||
|             <div class='details'> | ||||
|                 <span>{{type.getName()}}</span> | ||||
|                 <span ng-show="model.composition !== undefined"> | ||||
|                     - {{model.composition.length}} Item<span ng-show="model.composition.length > 1">s</span> | ||||
|                 </span> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -2,41 +2,26 @@ | ||||
|  Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  as represented by the Administrator of the National Aeronautics and Space | ||||
|  Administration. All rights reserved. | ||||
| 
 | ||||
|  Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  "License"); you may not use this file except in compliance with the License. | ||||
|  You may obtain a copy of the License at | ||||
|  http://www.apache.org/licenses/LICENSE-2.0. | ||||
| 
 | ||||
|  Unless required by applicable law or agreed to in writing, software | ||||
|  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  License for the specific language governing permissions and limitations | ||||
|  under the License. | ||||
| 
 | ||||
|  Open MCT includes source code licensed under additional open source | ||||
|  licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <template> | ||||
|     <span class="c-status"> | ||||
|     <indicators></indicators> | ||||
|     <notification-banner></notification-banner> | ||||
|     </span> | ||||
| </template> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
|     .c-status { | ||||
|         width: 100%; | ||||
|     } | ||||
| </style> | ||||
| 
 | ||||
| <script> | ||||
|     import Indicators from './Indicators.vue'; | ||||
|     import NotificationBanner from './NotificationBanner.vue'; | ||||
| 
 | ||||
|     export default { | ||||
|         components: { | ||||
|             Indicators, | ||||
|             NotificationBanner | ||||
|         } | ||||
|     } | ||||
| </script> | ||||
| <div class='items-holder grid abs'> | ||||
|     <mct-representation key="'grid-item'" | ||||
|                         ng-repeat="childObject in composition" | ||||
|                         mct-object="childObject"> | ||||
|     </mct-representation> | ||||
| </div> | ||||
| @@ -58,7 +58,7 @@ define([], function () { | ||||
|  | ||||
|         function checkNavigation() { | ||||
|             var navigatedObject = navigationService.getNavigation(); | ||||
|             if (navigatedObject && navigatedObject.hasCapability('context')) { | ||||
|             if (navigatedObject.hasCapability('context')) { | ||||
|                 if (!navigatedObject.getCapability('editor').isEditContextRoot()) { | ||||
|                     preventOrphanNavigation(navigatedObject); | ||||
|                 } | ||||
|   | ||||
							
								
								
									
										64
									
								
								platform/commonUI/browse/src/windowing/FullscreenAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								platform/commonUI/browse/src/windowing/FullscreenAction.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * Module defining FullscreenAction. Created by vwoeltje on 11/18/14. | ||||
|  */ | ||||
| define( | ||||
|     ["screenfull"], | ||||
|     function (screenfull) { | ||||
|  | ||||
|         var ENTER_FULLSCREEN = "Enter full screen mode", | ||||
|             EXIT_FULLSCREEN = "Exit full screen mode"; | ||||
|  | ||||
|         /** | ||||
|          * The fullscreen action toggles between fullscreen display | ||||
|          * and regular in-window display. | ||||
|          * @memberof platform/commonUI/browse | ||||
|          * @constructor | ||||
|          * @implements {Action} | ||||
|          */ | ||||
|         function FullscreenAction(context) { | ||||
|             this.context = context; | ||||
|         } | ||||
|  | ||||
|         FullscreenAction.prototype.perform = function () { | ||||
|             screenfull.toggle(); | ||||
|         }; | ||||
|  | ||||
|         FullscreenAction.prototype.getMetadata = function () { | ||||
|             // We override getMetadata, because the icon cssClass and | ||||
|             // description need to be determined at run-time | ||||
|             // based on whether or not we are currently | ||||
|             // full screen. | ||||
|             var metadata = Object.create(FullscreenAction); | ||||
|             metadata.cssClass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse"; | ||||
|             metadata.description = screenfull.isFullscreen ? | ||||
|                 EXIT_FULLSCREEN : ENTER_FULLSCREEN; | ||||
|             metadata.group = "windowing"; | ||||
|             metadata.context = this.context; | ||||
|             return metadata; | ||||
|         }; | ||||
|  | ||||
|         return FullscreenAction; | ||||
|     } | ||||
| ); | ||||
| @@ -0,0 +1,59 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * MCTRepresentationSpec. Created by vwoeltje on 11/6/14. | ||||
|  */ | ||||
| define( | ||||
|     ["../../src/windowing/FullscreenAction", "screenfull"], | ||||
|     function (FullscreenAction, screenfull) { | ||||
|  | ||||
|         describe("The fullscreen action", function () { | ||||
|             var action, | ||||
|                 oldToggle; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 // Screenfull is not shimmed or injected, so | ||||
|                 // we need to spy on it in the global scope. | ||||
|                 oldToggle = screenfull.toggle; | ||||
|  | ||||
|                 screenfull.toggle = jasmine.createSpy("toggle"); | ||||
|  | ||||
|                 action = new FullscreenAction({}); | ||||
|             }); | ||||
|  | ||||
|             afterEach(function () { | ||||
|                 screenfull.toggle = oldToggle; | ||||
|             }); | ||||
|  | ||||
|             it("toggles fullscreen mode when performed", function () { | ||||
|                 action.perform(); | ||||
|                 expect(screenfull.toggle).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("provides displayable metadata", function () { | ||||
|                 expect(action.getMetadata().cssClass).toBeDefined(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -2,14 +2,31 @@ | ||||
|      ng-class="'message-severity-' + ngModel.severity"> | ||||
|     <div class="w-message-contents"> | ||||
|         <div class="top-bar"> | ||||
|             <div class="title">{{ngModel.message}}</div> | ||||
|             <div class="title">{{ngModel.title}}</div> | ||||
|         </div> | ||||
|         <div class="hint" ng-hide="ngModel.hint === undefined"> | ||||
|             {{ngModel.hint}} | ||||
|             <span ng-if="ngModel.timestamp !== undefined">[{{ngModel.timestamp}}]</span> | ||||
|         </div> | ||||
|         <div class="message-body"> | ||||
|             <div class="message-action"> | ||||
|                 {{ngModel.actionText}} | ||||
|             </div> | ||||
|             <mct-include key="'progress-bar'" | ||||
|                          ng-model="ngModel" | ||||
|                          ng-show="ngModel.progressPerc !== undefined"></mct-include> | ||||
|                          ng-show="ngModel.progress !== undefined || ngModel.unknownProgress"></mct-include> | ||||
|         </div> | ||||
|         <div class="bottom-bar"> | ||||
|             <a ng-repeat="dialogOption in ngModel.options" | ||||
|                class="s-button" | ||||
|                ng-click="dialogOption.callback()"> | ||||
|                 {{dialogOption.label}} | ||||
|             </a> | ||||
|             <a class="s-button major" | ||||
|                ng-if="ngModel.primaryOption" | ||||
|                ng-click="ngModel.primaryOption.callback()"> | ||||
|                 {{ngModel.primaryOption.label}} | ||||
|             </a> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -95,10 +95,7 @@ define( | ||||
|  | ||||
|             // Create the overlay element and add it to the document's body | ||||
|             element = this.$compile(TEMPLATE)(scope); | ||||
|              | ||||
|             // Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when | ||||
|             // multiple overlays with the same z-index are active. | ||||
|             this.findBody().append(element); | ||||
|             this.findBody().prepend(element); | ||||
|  | ||||
|             return { | ||||
|                 dismiss: dismiss | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
| define([ | ||||
|     "./src/controllers/EditActionController", | ||||
|     "./src/controllers/EditPanesController", | ||||
|     "./src/controllers/ElementsController", | ||||
|     "./src/controllers/EditObjectController", | ||||
|     "./src/actions/EditAndComposeAction", | ||||
|     "./src/actions/EditAction", | ||||
| @@ -32,7 +33,11 @@ define([ | ||||
|     "./src/actions/SaveAndStopEditingAction", | ||||
|     "./src/actions/SaveAsAction", | ||||
|     "./src/actions/CancelAction", | ||||
|     "./src/policies/EditActionPolicy", | ||||
|     "./src/policies/EditPersistableObjectsPolicy", | ||||
|     "./src/policies/EditableLinkPolicy", | ||||
|     "./src/policies/EditableMovePolicy", | ||||
|     "./src/policies/EditContextualActionPolicy", | ||||
|     "./src/representers/EditRepresenter", | ||||
|     "./src/capabilities/EditorCapability", | ||||
|     "./src/capabilities/TransactionCapabilityDecorator", | ||||
| @@ -42,6 +47,7 @@ define([ | ||||
|     "./src/creation/LocatorController", | ||||
|     "./src/creation/CreationPolicy", | ||||
|     "./src/creation/CreateActionProvider", | ||||
|     "./src/creation/AddActionProvider", | ||||
|     "./src/creation/CreationService", | ||||
|     "./res/templates/create/locator.html", | ||||
|     "./res/templates/create/create-button.html", | ||||
| @@ -49,11 +55,13 @@ define([ | ||||
|     "./res/templates/library.html", | ||||
|     "./res/templates/edit-object.html", | ||||
|     "./res/templates/edit-action-buttons.html", | ||||
|     "./res/templates/elements.html", | ||||
|     "./res/templates/topbar-edit.html", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     EditActionController, | ||||
|     EditPanesController, | ||||
|     ElementsController, | ||||
|     EditObjectController, | ||||
|     EditAndComposeAction, | ||||
|     EditAction, | ||||
| @@ -63,7 +71,11 @@ define([ | ||||
|     SaveAndStopEditingAction, | ||||
|     SaveAsAction, | ||||
|     CancelAction, | ||||
|     EditActionPolicy, | ||||
|     EditPersistableObjectsPolicy, | ||||
|     EditableLinkPolicy, | ||||
|     EditableMovePolicy, | ||||
|     EditContextualActionPolicy, | ||||
|     EditRepresenter, | ||||
|     EditorCapability, | ||||
|     TransactionCapabilityDecorator, | ||||
| @@ -73,6 +85,7 @@ define([ | ||||
|     LocatorController, | ||||
|     CreationPolicy, | ||||
|     CreateActionProvider, | ||||
|     AddActionProvider, | ||||
|     CreationService, | ||||
|     locatorTemplate, | ||||
|     createButtonTemplate, | ||||
| @@ -80,6 +93,7 @@ define([ | ||||
|     libraryTemplate, | ||||
|     editObjectTemplate, | ||||
|     editActionButtonsTemplate, | ||||
|     elementsTemplate, | ||||
|     topbarEditTemplate, | ||||
|     legacyRegistry | ||||
| ) { | ||||
| @@ -101,6 +115,14 @@ define([ | ||||
|                         "$scope" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "ElementsController", | ||||
|                     "implementation": ElementsController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "openmct" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "EditObjectController", | ||||
|                     "implementation": EditObjectController, | ||||
| @@ -166,7 +188,7 @@ define([ | ||||
|                     "name": "Remove", | ||||
|                     "description": "Remove this object from its containing object.", | ||||
|                     "depends": [ | ||||
|                         "openmct", | ||||
|                         "dialogService", | ||||
|                         "navigationService" | ||||
|                     ] | ||||
|                 }, | ||||
| @@ -203,10 +225,10 @@ define([ | ||||
|                     "description": "Save changes made to these objects.", | ||||
|                     "depends": [ | ||||
|                         "$injector", | ||||
|                         "policyService", | ||||
|                         "dialogService", | ||||
|                         "copyService", | ||||
|                         "notificationService", | ||||
|                         "openmct" | ||||
|                         "notificationService" | ||||
|                     ], | ||||
|                     "priority": "mandatory" | ||||
|                 }, | ||||
| @@ -223,11 +245,28 @@ define([ | ||||
|                 } | ||||
|             ], | ||||
|             "policies": [ | ||||
|                 { | ||||
|                     "category": "action", | ||||
|                     "implementation": EditActionPolicy | ||||
|                 }, | ||||
|                 { | ||||
|                     "category": "action", | ||||
|                     "implementation": EditPersistableObjectsPolicy, | ||||
|                     "depends": ["openmct"] | ||||
|                 }, | ||||
|                 { | ||||
|                     "category": "action", | ||||
|                     "implementation": EditContextualActionPolicy, | ||||
|                     "depends": ["navigationService", "editModeBlacklist", "nonEditContextBlacklist"] | ||||
|                 }, | ||||
|                 { | ||||
|                     "category": "action", | ||||
|                     "implementation": EditableMovePolicy | ||||
|                 }, | ||||
|                 { | ||||
|                     "category": "action", | ||||
|                     "implementation": EditableLinkPolicy | ||||
|                 }, | ||||
|                 { | ||||
|                     "implementation": CreationPolicy, | ||||
|                     "category": "creation" | ||||
| @@ -257,6 +296,13 @@ define([ | ||||
|                         "action" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "edit-elements", | ||||
|                     "template": elementsTemplate, | ||||
|                     "gestures": [ | ||||
|                         "drop" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "topbar-edit", | ||||
|                     "template": topbarEditTemplate | ||||
| @@ -273,6 +319,12 @@ define([ | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "templates": [ | ||||
|                 { | ||||
|                     key: "elementsPool", | ||||
|                     template: elementsTemplate | ||||
|                 } | ||||
|             ], | ||||
|             "components": [ | ||||
|                 { | ||||
|                     "type": "decorator", | ||||
| @@ -304,6 +356,18 @@ define([ | ||||
|                         "policyService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "AddActionProvider", | ||||
|                     "provides": "actionService", | ||||
|                     "type": "provider", | ||||
|                     "implementation": AddActionProvider, | ||||
|                     "depends": [ | ||||
|                         "$q", | ||||
|                         "typeService", | ||||
|                         "dialogService", | ||||
|                         "policyService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "CreationService", | ||||
|                     "provides": "creationService", | ||||
| @@ -324,6 +388,16 @@ define([ | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "constants": [ | ||||
|                 { | ||||
|                     "key": "editModeBlacklist", | ||||
|                     "value": ["copy", "follow", "link", "locate"] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "nonEditContextBlacklist", | ||||
|                     "value": ["copy", "follow", "properties", "move", "link", "remove", "locate"] | ||||
|                 } | ||||
|             ], | ||||
|             "capabilities": [ | ||||
|                 { | ||||
|                     "key": "editor", | ||||
| @@ -331,8 +405,7 @@ define([ | ||||
|                     "description": "Provides transactional editing capabilities", | ||||
|                     "implementation": EditorCapability, | ||||
|                     "depends": [ | ||||
|                         "transactionService", | ||||
|                         "openmct" | ||||
|                         "transactionService" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|   | ||||
							
								
								
									
										49
									
								
								platform/commonUI/edit/res/templates/elements.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								platform/commonUI/edit/res/templates/elements.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| <!-- | ||||
|  Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  as represented by the Administrator of the National Aeronautics and Space | ||||
|  Administration. All rights reserved. | ||||
|  | ||||
|  Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  "License"); you may not use this file except in compliance with the License. | ||||
|  You may obtain a copy of the License at | ||||
|  http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  | ||||
|  Unless required by applicable law or agreed to in writing, software | ||||
|  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  License for the specific language governing permissions and limitations | ||||
|  under the License. | ||||
|  | ||||
|  Open MCT includes source code licensed under additional open source | ||||
|  licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <div ng-controller="ElementsController" class="flex-elem l-flex-col holder grows"> | ||||
|     <mct-include key="'input-filter'" | ||||
|                  class="flex-elem holder" | ||||
|                  ng-model="filterBy"> | ||||
|     </mct-include> | ||||
|     <div class="flex-elem grows vscroll scroll-pad"> | ||||
|         <ul class="tree" id="inspector-elements-tree" | ||||
|             ng-if="composition.length > 0"> | ||||
|             <li ng-repeat="containedObject in composition | filter:searchElements"> | ||||
|                 <span class="tree-item"> | ||||
|                     <span class="grippy-sm" | ||||
|                           ng-if="composition.length > 1" | ||||
|                           data-id="{{ containedObject.id }}" | ||||
|                           mct-drag-down="dragDown($event)" | ||||
|                           mct-drag="drag($event)" | ||||
|                           mct-drag-up="dragUp($event)"> | ||||
|                     </span> | ||||
|                     <mct-representation | ||||
|                             class="rep-object-label" | ||||
|                             key="'label'" | ||||
|                             mct-object="containedObject"> | ||||
|                     </mct-representation> | ||||
|                 </span> | ||||
|             </li> | ||||
|         </ul> | ||||
|         <div ng-if="composition.length === 0">No contained elements</div>     | ||||
|     </div> | ||||
| </div> | ||||
| @@ -42,9 +42,9 @@ define([ | ||||
|      * @constructor | ||||
|      * @implements {Action} | ||||
|      */ | ||||
|     function RemoveAction(openmct, navigationService, context) { | ||||
|     function RemoveAction(dialogService, navigationService, context) { | ||||
|         this.domainObject = (context || {}).domainObject; | ||||
|         this.openmct = openmct; | ||||
|         this.dialogService = dialogService; | ||||
|         this.navigationService = navigationService; | ||||
|     } | ||||
|  | ||||
| @@ -53,6 +53,7 @@ define([ | ||||
|      */ | ||||
|     RemoveAction.prototype.perform = function () { | ||||
|         var dialog, | ||||
|             dialogService = this.dialogService, | ||||
|             domainObject = this.domainObject, | ||||
|             navigationService = this.navigationService; | ||||
|         /* | ||||
| @@ -103,13 +104,13 @@ define([ | ||||
|          * capability. Based on object's location and selected object's location | ||||
|          * user may be navigated to existing parent object | ||||
|          */ | ||||
|         function removeFromContext() { | ||||
|             var contextCapability = domainObject.getCapability('context'), | ||||
|         function removeFromContext(object) { | ||||
|             var contextCapability = object.getCapability('context'), | ||||
|                 parent = contextCapability.getParent(); | ||||
|  | ||||
|             // If currently within path of removed object(s), | ||||
|             // navigates to existing object up tree | ||||
|             checkObjectNavigation(domainObject, parent); | ||||
|             checkObjectNavigation(object, parent); | ||||
|  | ||||
|             return parent.useCapability('mutation', doMutate); | ||||
|         } | ||||
| @@ -118,7 +119,7 @@ define([ | ||||
|          * Pass in the function to remove the domain object so it can be | ||||
|          * associated with an 'OK' button press | ||||
|          */ | ||||
|         dialog = new RemoveDialog(this.openmct, domainObject, removeFromContext); | ||||
|         dialog = new RemoveDialog(dialogService, domainObject, removeFromContext); | ||||
|         dialog.show(); | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -36,8 +36,8 @@ define([], function () { | ||||
|      * @memberof platform/commonUI/edit | ||||
|      * @constructor | ||||
|      */ | ||||
|     function RemoveDialog(openmct, domainObject, removeCallback) { | ||||
|         this.openmct = openmct; | ||||
|     function RemoveDialog(dialogService, domainObject, removeCallback) { | ||||
|         this.dialogService = dialogService; | ||||
|         this.domainObject = domainObject; | ||||
|         this.removeCallback = removeCallback; | ||||
|     } | ||||
| @@ -46,26 +46,31 @@ define([], function () { | ||||
|      * Display a dialog to confirm the removal of a domain object. | ||||
|      */ | ||||
|     RemoveDialog.prototype.show = function () { | ||||
|         let dialog = this.openmct.overlays.dialog({ | ||||
|             title: 'Remove ' + this.domainObject.getModel().name, | ||||
|             iconClass: 'alert', | ||||
|             message: 'Warning! This action will permanently remove this object. Are you sure you want to continue?', | ||||
|             buttons: [ | ||||
|                 { | ||||
|         var dialog, | ||||
|             domainObject = this.domainObject, | ||||
|             removeCallback = this.removeCallback, | ||||
|             model = { | ||||
|                 title: 'Remove ' + domainObject.getModel().name, | ||||
|                 actionText: 'Warning! This action will permanently remove this object. Are you sure you want to continue?', | ||||
|                 severity: 'alert', | ||||
|                 primaryOption: { | ||||
|                     label: 'OK', | ||||
|                     callback: () => { | ||||
|                         this.removeCallback(); | ||||
|                     callback: function () { | ||||
|                         removeCallback(domainObject); | ||||
|                         dialog.dismiss(); | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     label: 'Cancel', | ||||
|                     callback: () => { | ||||
|                         dialog.dismiss(); | ||||
|                 options: [ | ||||
|                     { | ||||
|                         label: 'Cancel', | ||||
|                         callback: function () { | ||||
|                             dialog.dismiss(); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             ] | ||||
|         }); | ||||
|                 ] | ||||
|             }; | ||||
|  | ||||
|         dialog = this.dialogService.showBlockingMessage(model); | ||||
|     }; | ||||
|  | ||||
|     return RemoveDialog; | ||||
|   | ||||
| @@ -40,20 +40,20 @@ function ( | ||||
|          */ | ||||
|     function SaveAsAction( | ||||
|         $injector, | ||||
|         policyService, | ||||
|         dialogService, | ||||
|         copyService, | ||||
|         notificationService, | ||||
|         openmct, | ||||
|         context | ||||
|     ) { | ||||
|         this.domainObject = (context || {}).domainObject; | ||||
|         this.injectObjectService = function () { | ||||
|             this.objectService = $injector.get("objectService"); | ||||
|         }; | ||||
|         this.policyService = policyService; | ||||
|         this.dialogService = dialogService; | ||||
|         this.copyService = copyService; | ||||
|         this.notificationService = notificationService; | ||||
|         this.openmct = openmct; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -63,7 +63,7 @@ function ( | ||||
|         return new CreateWizard( | ||||
|             this.domainObject, | ||||
|             parent, | ||||
|             this.openmct | ||||
|             this.policyService | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -36,11 +36,9 @@ define( | ||||
|          */ | ||||
|         function EditorCapability( | ||||
|             transactionService, | ||||
|             openmct, | ||||
|             domainObject | ||||
|         ) { | ||||
|             this.transactionService = transactionService; | ||||
|             this.openmct = openmct; | ||||
|             this.domainObject = domainObject; | ||||
|         } | ||||
|  | ||||
| @@ -50,22 +48,27 @@ define( | ||||
|          * or finish() are called. | ||||
|          */ | ||||
|         EditorCapability.prototype.edit = function () { | ||||
|             console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.'); | ||||
|  | ||||
|             if (!this.openmct.editor.isEditing()) { | ||||
|                 this.openmct.editor.edit(); | ||||
|                 this.domainObject.getCapability('status').set('editing', true); | ||||
|             } | ||||
|             this.transactionService.startTransaction(); | ||||
|             this.domainObject.getCapability('status').set('editing', true); | ||||
|         }; | ||||
|  | ||||
|         function isEditContextRoot(domainObject) { | ||||
|             return domainObject.getCapability('status').get('editing'); | ||||
|         } | ||||
|  | ||||
|         function isEditing(domainObject) { | ||||
|             return isEditContextRoot(domainObject) || | ||||
|                 domainObject.hasCapability('context') && | ||||
|                 isEditing(domainObject.getCapability('context').getParent()); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Determines whether this object, or any of its ancestors are | ||||
|          * currently being edited. | ||||
|          * @returns boolean | ||||
|          */ | ||||
|         EditorCapability.prototype.inEditContext = function () { | ||||
|             console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.'); | ||||
|             return this.openmct.editor.isEditing(); | ||||
|             return isEditing(this.domainObject); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -74,8 +77,7 @@ define( | ||||
|          * @returns {*} | ||||
|          */ | ||||
|         EditorCapability.prototype.isEditContextRoot = function () { | ||||
|             console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.'); | ||||
|             return this.openmct.editor.isEditing(); | ||||
|             return isEditContextRoot(this.domainObject); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -84,8 +86,10 @@ define( | ||||
|          * @returns {*} | ||||
|          */ | ||||
|         EditorCapability.prototype.save = function () { | ||||
|             console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.'); | ||||
|             return Promise.resolve(); | ||||
|             var transactionService = this.transactionService; | ||||
|             return transactionService.commit().then(function () { | ||||
|                 transactionService.startTransaction(); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         EditorCapability.prototype.invoke = EditorCapability.prototype.edit; | ||||
| @@ -96,8 +100,16 @@ define( | ||||
|          * @returns {*} | ||||
|          */ | ||||
|         EditorCapability.prototype.finish = function () { | ||||
|             console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.'); | ||||
|             return Promise.resolve(); | ||||
|             var domainObject = this.domainObject; | ||||
|  | ||||
|             if (this.transactionService.isActive()) { | ||||
|                 return this.transactionService.cancel().then(function () { | ||||
|                     domainObject.getCapability("status").set("editing", false); | ||||
|                     return domainObject; | ||||
|                 }); | ||||
|             } else { | ||||
|                 return Promise.resolve(domainObject); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|   | ||||
							
								
								
									
										197
									
								
								platform/commonUI/edit/src/controllers/ElementsController.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								platform/commonUI/edit/src/controllers/ElementsController.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ['zepto'], | ||||
|     function ($) { | ||||
|  | ||||
|         /** | ||||
|          * The ElementsController prepares the elements view for display | ||||
|          * | ||||
|          * @constructor | ||||
|          */ | ||||
|         function ElementsController($scope, openmct) { | ||||
|             this.scope = $scope; | ||||
|             this.scope.composition = []; | ||||
|             this.openmct = openmct; | ||||
|             this.dragDown = this.dragDown.bind(this); | ||||
|             this.dragUp = this.dragUp.bind(this); | ||||
|  | ||||
|             var self = this; | ||||
|  | ||||
|             function filterBy(text) { | ||||
|                 if (typeof text === 'undefined') { | ||||
|                     return $scope.searchText; | ||||
|                 } else { | ||||
|                     $scope.searchText = text; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             function searchElements(value) { | ||||
|                 if ($scope.searchText) { | ||||
|                     return value.getModel().name.toLowerCase().search( | ||||
|                         $scope.searchText.toLowerCase()) !== -1; | ||||
|                 } else { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             function setSelection(selection) { | ||||
|                 if (!selection[0]) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 if (self.mutationListener) { | ||||
|                     self.mutationListener(); | ||||
|                     delete self.mutationListener; | ||||
|                 } | ||||
|  | ||||
|                 var domainObject = selection[0].context.oldItem; | ||||
|                 self.refreshComposition(domainObject); | ||||
|  | ||||
|                 if (domainObject) { | ||||
|  | ||||
|                     self.mutationListener = domainObject.getCapability('mutation') | ||||
|                         .listen(self.refreshComposition.bind(self, domainObject)); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             $scope.filterBy = filterBy; | ||||
|             $scope.searchElements = searchElements; | ||||
|  | ||||
|             openmct.selection.on('change', setSelection); | ||||
|             setSelection(openmct.selection.get()); | ||||
|  | ||||
|             $scope.dragDown = this.dragDown; | ||||
|             $scope.drag = this.drag; | ||||
|             $scope.dragUp = this.dragUp; | ||||
|  | ||||
|             $scope.$on("$destroy", function () { | ||||
|                 openmct.selection.off("change", setSelection); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Invoked on DragStart - Adds reordering class to parent UL element | ||||
|          * Sets selected object ID, to be used on Drag End | ||||
|          * | ||||
|          * @param {object} event | Mouse Event | ||||
|          */ | ||||
|         ElementsController.prototype.dragDown = function (event) { | ||||
|             if (!this.parentUL) { | ||||
|                 this.parentUL = $(document).find('#inspector-elements-tree'); | ||||
|             } | ||||
|  | ||||
|             this.selectedTreeItem = $(event.target).parent(); | ||||
|             this.selectedObjectId = event.target.getAttribute('data-id'); | ||||
|  | ||||
|             this.parentUL.addClass('reordering'); | ||||
|             this.selectedTreeItem.addClass('reorder-actor'); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Invoked on dragEnd - Removes selected object from position in composition | ||||
|          * and replaces it at the target position. Composition is then updated with current | ||||
|          * scope | ||||
|          * | ||||
|          * @param {object} event - Mouse Event | ||||
|          */ | ||||
|         ElementsController.prototype.dragUp = function (event) { | ||||
|             this.targetObjectId = event.target.getAttribute('data-id'); | ||||
|  | ||||
|             if (this.targetObjectId && this.selectedObjectId) { | ||||
|                 var selectedObjectPosition, | ||||
|                     targetObjectPosition; | ||||
|  | ||||
|                 selectedObjectPosition = findObjectInCompositionFromId(this.selectedObjectId, this.scope.composition); | ||||
|                 targetObjectPosition = findObjectInCompositionFromId(this.targetObjectId, this.scope.composition); | ||||
|  | ||||
|                 if ((selectedObjectPosition !== -1) && (targetObjectPosition !== -1)) { | ||||
|                     var selectedObject = this.scope.composition.splice(selectedObjectPosition, 1), | ||||
|                         selection = this.openmct.selection.get(), | ||||
|                         domainObject = selection ? selection[0].context.oldItem : undefined; | ||||
|  | ||||
|                     this.scope.composition.splice(targetObjectPosition, 0, selectedObject[0]); | ||||
|  | ||||
|                     if (domainObject) { | ||||
|                         domainObject.getCapability('mutation').mutate(function (model) { | ||||
|                             model.composition = this.scope.composition.map(function (dObject) { | ||||
|                                 return dObject.id; | ||||
|                             }); | ||||
|                         }.bind(this)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (this.parentUL) { | ||||
|                 this.parentUL.removeClass('reordering'); | ||||
|             } | ||||
|  | ||||
|             if (this.selectedTreeItem) { | ||||
|                 this.selectedTreeItem.removeClass('reorder-actor'); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         ElementsController.prototype.drag = function (event) { | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Gets the composition for the selected object and populates the scope with it. | ||||
|          * | ||||
|          * @param domainObject the selected object | ||||
|          * @private | ||||
|          */ | ||||
|         ElementsController.prototype.refreshComposition = function (domainObject) { | ||||
|             var refreshTracker = {}; | ||||
|             this.currentRefresh = refreshTracker; | ||||
|  | ||||
|             var selectedObjectComposition = domainObject && domainObject.useCapability('composition'); | ||||
|             if (selectedObjectComposition) { | ||||
|                 selectedObjectComposition.then(function (composition) { | ||||
|                     if (this.currentRefresh === refreshTracker) { | ||||
|                         this.scope.composition = composition; | ||||
|                     } | ||||
|                 }.bind(this)); | ||||
|             } else { | ||||
|                 this.scope.composition = []; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Finds position of object with given ID in Composition | ||||
|          * | ||||
|          * @param {String} id | ||||
|          * @param {Array} composition | ||||
|          * @private | ||||
|          */ | ||||
|         function findObjectInCompositionFromId(id, composition) { | ||||
|             var mapped = composition.map(function (element) { | ||||
|                 return element.id; | ||||
|             }); | ||||
|  | ||||
|             return mapped.indexOf(id); | ||||
|         } | ||||
|  | ||||
|         return ElementsController; | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										133
									
								
								platform/commonUI/edit/src/creation/AddAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								platform/commonUI/edit/src/creation/AddAction.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * Module defining AddAction. Created by ahenry on 01/21/16. | ||||
|  */ | ||||
| define( | ||||
|     [ | ||||
|         './CreateWizard' | ||||
|     ], | ||||
|     function (CreateWizard) { | ||||
|  | ||||
|         /** | ||||
|          * The Add Action is performed to create new instances of | ||||
|          * domain objects of a specific type that are subobjects of an | ||||
|          * object being edited. This is the action that is performed when a | ||||
|          * user uses the Add menu option. | ||||
|          * | ||||
|          * @memberof platform/commonUI/browse | ||||
|          * @implements {Action} | ||||
|          * @constructor | ||||
|          * | ||||
|          * @param {Type} type the type of domain object to create | ||||
|          * @param {DomainObject} parent the domain object that should | ||||
|          *        act as a container for the newly-created object | ||||
|          *        (note that the user will have an opportunity to | ||||
|          *        override this) | ||||
|          * @param {ActionContext} context the context in which the | ||||
|          *        action is being performed | ||||
|          * @param {DialogService} dialogService | ||||
|          */ | ||||
|         function AddAction(type, parent, context, $q, dialogService, policyService) { | ||||
|             this.metadata = { | ||||
|                 key: 'add', | ||||
|                 cssClass: type.getCssClass(), | ||||
|                 name: type.getName(), | ||||
|                 type: type.getKey(), | ||||
|                 description: type.getDescription(), | ||||
|                 context: context | ||||
|             }; | ||||
|  | ||||
|             this.type = type; | ||||
|             this.parent = parent; | ||||
|             this.$q = $q; | ||||
|             this.dialogService = dialogService; | ||||
|             this.policyService = policyService; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * Create a new object of the given type. | ||||
|          * This will prompt for user input first. | ||||
|          * | ||||
|          * @returns {Promise} that will be resolved with the object that the | ||||
|          * action was originally invoked on (ie. the 'parent') | ||||
|          */ | ||||
|         AddAction.prototype.perform = function () { | ||||
|             var newModel = this.type.getInitialModel(), | ||||
|                 newObject, | ||||
|                 parentObject = this.parent, | ||||
|                 wizard; | ||||
|  | ||||
|             newModel.type = this.type.getKey(); | ||||
|             newObject = parentObject.getCapability('instantiation').instantiate(newModel); | ||||
|             newObject.useCapability('mutation', function (model) { | ||||
|                 model.location = parentObject.getId(); | ||||
|             }); | ||||
|  | ||||
|             wizard = new CreateWizard(newObject, this.parent, this.policyService); | ||||
|  | ||||
|             function populateObjectFromInput(formValue) { | ||||
|                 return wizard.populateObjectFromInput(formValue, newObject); | ||||
|             } | ||||
|  | ||||
|             function persistAndReturn(domainObject) { | ||||
|                 return domainObject.getCapability('persistence') | ||||
|                     .persist() | ||||
|                     .then(function () { | ||||
|                         return domainObject; | ||||
|                     }); | ||||
|             } | ||||
|  | ||||
|             function addToParent(populatedObject) { | ||||
|                 parentObject.getCapability('composition').add(populatedObject); | ||||
|                 return persistAndReturn(parentObject); | ||||
|             } | ||||
|  | ||||
|             return this.dialogService | ||||
|                 .getUserInput(wizard.getFormStructure(false), wizard.getInitialFormValue()) | ||||
|                 .then(populateObjectFromInput) | ||||
|                 .then(persistAndReturn) | ||||
|                 .then(addToParent); | ||||
|  | ||||
|         }; | ||||
|  | ||||
|  | ||||
|         /** | ||||
|          * Metadata associated with a Add action. | ||||
|          * @typedef {ActionMetadata} AddActionMetadata | ||||
|          * @property {string} type the key for the type of domain object | ||||
|          *           to be created | ||||
|          */ | ||||
|  | ||||
|         /** | ||||
|          * Get metadata about this action. | ||||
|          * @returns {AddActionMetadata} metadata about this action | ||||
|          */ | ||||
|         AddAction.prototype.getMetadata = function () { | ||||
|             return this.metadata; | ||||
|         }; | ||||
|  | ||||
|         return AddAction; | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										82
									
								
								platform/commonUI/edit/src/creation/AddActionProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								platform/commonUI/edit/src/creation/AddActionProvider.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * Module defining AddActionProvider.js. Created by ahenry on 01/21/16. | ||||
|  */ | ||||
| define( | ||||
|     ["./AddAction"], | ||||
|     function (AddAction) { | ||||
|  | ||||
|         /** | ||||
|          * The AddActionProvider is an ActionProvider which introduces | ||||
|          * an Add action for creating sub objects. | ||||
|          * | ||||
|          * @memberof platform/commonUI/browse | ||||
|          * @constructor | ||||
|          * @implements {ActionService} | ||||
|          * | ||||
|          * @param {TypeService} typeService the type service, used to discover | ||||
|          *        available types | ||||
|          * @param {DialogService} dialogService the dialog service, used by | ||||
|          *        specific Create actions to get user input to populate the | ||||
|          *        model of the newly-created domain object. | ||||
|          * @param {CreationService} creationService the creation service (also | ||||
|          *        introduced in this bundle), responsible for handling actual | ||||
|          *        object creation. | ||||
|          */ | ||||
|         function AddActionProvider($q, typeService, dialogService, policyService) { | ||||
|             this.typeService = typeService; | ||||
|             this.dialogService = dialogService; | ||||
|             this.$q = $q; | ||||
|             this.policyService = policyService; | ||||
|         } | ||||
|  | ||||
|         AddActionProvider.prototype.getActions = function (actionContext) { | ||||
|             var context = actionContext || {}, | ||||
|                 key = context.key, | ||||
|                 destination = context.domainObject; | ||||
|  | ||||
|             // We only provide Add actions, and we need a | ||||
|             // domain object to serve as the container for the | ||||
|             // newly-created object (although the user may later | ||||
|             // make a different selection) | ||||
|             if (key !== 'add' || !destination) { | ||||
|                 return []; | ||||
|             } | ||||
|  | ||||
|             // Introduce one create action per type | ||||
|             return ['timeline', 'activity'].map(function (type) { | ||||
|                 return new AddAction( | ||||
|                     this.typeService.getType(type), | ||||
|                     destination, | ||||
|                     context, | ||||
|                     this.$q, | ||||
|                     this.dialogService, | ||||
|                     this.policyService | ||||
|                 ); | ||||
|             }, this); | ||||
|         }; | ||||
|  | ||||
|         return AddActionProvider; | ||||
|     } | ||||
| ); | ||||
| @@ -44,7 +44,7 @@ define( | ||||
|          * @param {ActionContext} context the context in which the | ||||
|          *        action is being performed | ||||
|          */ | ||||
|         function CreateAction(type, parent, context, openmct) { | ||||
|         function CreateAction(type, parent, context) { | ||||
|             this.metadata = { | ||||
|                 key: 'create', | ||||
|                 cssClass: type.getCssClass(), | ||||
| @@ -55,7 +55,6 @@ define( | ||||
|             }; | ||||
|             this.type = type; | ||||
|             this.parent = parent; | ||||
|             this.openmct = openmct; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -64,28 +63,37 @@ define( | ||||
|          */ | ||||
|         CreateAction.prototype.perform = function () { | ||||
|             var newModel = this.type.getInitialModel(), | ||||
|                 openmct = this.openmct, | ||||
|                 newObject, | ||||
|                 editAction; | ||||
|                 editAction, | ||||
|                 editorCapability; | ||||
|  | ||||
|             function closeEditor() { | ||||
|                 return editorCapability.finish(); | ||||
|             } | ||||
|  | ||||
|             function onSave() { | ||||
|                 openmct.editor.save(); | ||||
|                 return editorCapability.save() | ||||
|                     .then(closeEditor); | ||||
|             } | ||||
|  | ||||
|             function onCancel() { | ||||
|                 openmct.editor.cancel(); | ||||
|                 return closeEditor(); | ||||
|             } | ||||
|  | ||||
|             newModel.type = this.type.getKey(); | ||||
|             newModel.location = this.parent.getId(); | ||||
|             newObject = this.parent.useCapability('instantiation', newModel); | ||||
|             editorCapability = newObject.hasCapability('editor') && newObject.getCapability("editor"); | ||||
|  | ||||
|             openmct.editor.edit(); | ||||
|             editAction = newObject.getCapability("action").getActions("edit")[0]; | ||||
|             newObject.getCapability("action").perform("save-as").then(onSave, onCancel); | ||||
|             // TODO: support editing object without saving object first. | ||||
|             // Which means we have to toggle createwizard afterwards.  For now, | ||||
|             // We will disable this. | ||||
|             //If an edit action is available, perform it | ||||
|             if (editAction) { | ||||
|                 return editAction.perform(); | ||||
|             } else if (editorCapability) { | ||||
|                 //otherwise, use the save as action | ||||
|                 editorCapability.edit(); | ||||
|                 return newObject.getCapability("action").perform("save-as").then(onSave, onCancel); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -34,13 +34,13 @@ define( | ||||
|          * @memberof platform/commonUI/browse | ||||
|          * @constructor | ||||
|          */ | ||||
|         function CreateWizard(domainObject, parent, openmct) { | ||||
|         function CreateWizard(domainObject, parent, policyService) { | ||||
|             this.type = domainObject.getCapability('type'); | ||||
|             this.model = domainObject.getModel(); | ||||
|             this.domainObject = domainObject; | ||||
|             this.properties = this.type.getProperties(); | ||||
|             this.parent = parent; | ||||
|             this.openmct = openmct; | ||||
|             this.policyService = policyService; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -56,10 +56,15 @@ define( | ||||
|          */ | ||||
|         CreateWizard.prototype.getFormStructure = function (includeLocation) { | ||||
|             var sections = [], | ||||
|                 domainObject = this.domainObject; | ||||
|                 domainObject = this.domainObject, | ||||
|                 policyService = this.policyService; | ||||
|  | ||||
|             function validateLocation(parent) { | ||||
|                 return parent && this.openmct.composition.checkPolicy(parent.useCapability('adapter'), domainObject.useCapability('adapter')); | ||||
|                 return parent && policyService.allow( | ||||
|                     "composition", | ||||
|                     parent, | ||||
|                     domainObject | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             sections.push({ | ||||
| @@ -88,7 +93,7 @@ define( | ||||
|                     rows: [{ | ||||
|                         name: "Save In", | ||||
|                         control: "locator", | ||||
|                         validate: validateLocation.bind(this), | ||||
|                         validate: validateLocation, | ||||
|                         key: "createParent" | ||||
|                     }] | ||||
|                 }); | ||||
|   | ||||
							
								
								
									
										111
									
								
								platform/commonUI/edit/src/policies/EditActionPolicy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								platform/commonUI/edit/src/policies/EditActionPolicy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Policy controlling when the `edit` and/or `properties` actions | ||||
|          * can appear as applicable actions of the `view-control` category | ||||
|          * (shown as buttons in the top-right of browse mode.) | ||||
|          * @memberof platform/commonUI/edit | ||||
|          * @constructor | ||||
|          * @implements {Policy.<Action, ActionContext>} | ||||
|          */ | ||||
|         function EditActionPolicy(policyService) { | ||||
|             this.policyService = policyService; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Get a count of views which are not flagged as non-editable. | ||||
|          * @private | ||||
|          */ | ||||
|         EditActionPolicy.prototype.countEditableViews = function (context) { | ||||
|             var domainObject = context.domainObject, | ||||
|                 count = 0, | ||||
|                 type, views; | ||||
|  | ||||
|             if (!domainObject) { | ||||
|                 return count; | ||||
|             } | ||||
|  | ||||
|             type = domainObject.getCapability('type'); | ||||
|             views = domainObject.useCapability('view'); | ||||
|  | ||||
|  | ||||
|             // A view is editable unless explicitly flagged as not | ||||
|             (views || []).forEach(function (view) { | ||||
|                 if (isEditable(view) || | ||||
|                     (view.key === 'plot' && type.getKey() === 'telemetry.panel') || | ||||
|                     (view.key === 'table' && type.getKey() === 'table') || | ||||
|                     (view.key === 'rt-table' && type.getKey() === 'rttable') | ||||
|                 ) { | ||||
|                     count++; | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             function isEditable(view) { | ||||
|                 if (typeof view.editable === Function) { | ||||
|                     return view.editable(domainObject.useCapability('adapter')); | ||||
|                 } else { | ||||
|                     return view.editable === true; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return count; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Checks whether the domain object is currently being edited. If | ||||
|          * so, the edit action is not applicable. | ||||
|          * @param context | ||||
|          * @returns {*|boolean} | ||||
|          */ | ||||
|         function isEditing(context) { | ||||
|             var domainObject = (context || {}).domainObject; | ||||
|             return domainObject && | ||||
|                 domainObject.hasCapability('editor') && | ||||
|                 domainObject.getCapability('editor').isEditContextRoot(); | ||||
|         } | ||||
|  | ||||
|         EditActionPolicy.prototype.allow = function (action, context) { | ||||
|             var key = action.getMetadata().key, | ||||
|                 category = (context || {}).category; | ||||
|  | ||||
|             // Restrict 'edit' to cases where there are editable | ||||
|             // views (similarly, restrict 'properties' to when | ||||
|             // the converse is true), and where the domain object is not | ||||
|             // already being edited. | ||||
|             if (key === 'edit') { | ||||
|                 return this.countEditableViews(context) > 0 && !isEditing(context); | ||||
|             } else if (key === 'properties' && category === 'view-control') { | ||||
|                 return this.countEditableViews(context) < 1 && !isEditing(context); | ||||
|             } | ||||
|  | ||||
|             // Like all policies, allow by default. | ||||
|             return true; | ||||
|         }; | ||||
|  | ||||
|         return EditActionPolicy; | ||||
|     } | ||||
| ); | ||||
| @@ -0,0 +1,72 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Policy controlling whether the context menu is visible when | ||||
|          * objects are being edited | ||||
|          * @param navigationService | ||||
|          * @param editModeBlacklist A blacklist of actions disallowed from | ||||
|          * context menu when navigated object is being edited | ||||
|          * @param nonEditContextBlacklist A blacklist of actions disallowed | ||||
|          * from context menu of non-editable objects, when navigated object | ||||
|          * is being edited | ||||
|          * @constructor | ||||
|          * @param editModeBlacklist A blacklist of actions disallowed from | ||||
|          * context menu when navigated object is being edited | ||||
|          * @param nonEditContextBlacklist A blacklist of actions disallowed | ||||
|          * from context menu of non-editable objects, when navigated object | ||||
|          * @implements {Policy.<Action, ActionContext>} | ||||
|          */ | ||||
|         function EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist) { | ||||
|             this.navigationService = navigationService; | ||||
|  | ||||
|             //The list of objects disallowed on target object when in edit mode | ||||
|             this.editModeBlacklist = editModeBlacklist; | ||||
|             //The list of objects disallowed on target object that is not in | ||||
|             // edit mode (ie. the context menu in the tree on the LHS). | ||||
|             this.nonEditContextBlacklist = nonEditContextBlacklist; | ||||
|         } | ||||
|  | ||||
|         EditContextualActionPolicy.prototype.allow = function (action, context) { | ||||
|             var selectedObject = context.domainObject, | ||||
|                 navigatedObject = this.navigationService.getNavigation(), | ||||
|                 actionMetadata = action.getMetadata ? action.getMetadata() : {}; | ||||
|  | ||||
|             // if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) { | ||||
|                 if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) { | ||||
|                     return this.editModeBlacklist.indexOf(actionMetadata.key) === -1; | ||||
|                 } else { | ||||
|                     //Target is in the context menu | ||||
|                     return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1; | ||||
|                 } | ||||
|             // } else { | ||||
|             //     return true; | ||||
|             // } | ||||
|         }; | ||||
|  | ||||
|         return EditContextualActionPolicy; | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										51
									
								
								platform/commonUI/edit/src/policies/EditableLinkPolicy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								platform/commonUI/edit/src/policies/EditableLinkPolicy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([], function () { | ||||
|  | ||||
|     /** | ||||
|      * Policy suppressing links when the linked-to domain object is in | ||||
|      * edit mode. Domain objects being edited may not have been persisted, | ||||
|      * so creating links to these can result in inconsistent state. | ||||
|      * | ||||
|      * @memberof platform/commonUI/edit | ||||
|      * @constructor | ||||
|      * @implements {Policy.<View, DomainObject>} | ||||
|      */ | ||||
|     function EditableLinkPolicy() { | ||||
|     } | ||||
|  | ||||
|     EditableLinkPolicy.prototype.allow = function (action, context) { | ||||
|         var key = action.getMetadata().key, | ||||
|             object; | ||||
|  | ||||
|         if (key === 'link') { | ||||
|             object = context.selectedObject || context.domainObject; | ||||
|             return !(object.hasCapability("editor") && object.getCapability("editor").inEditContext()); | ||||
|         } | ||||
|  | ||||
|         // Like all policies, allow by default. | ||||
|         return true; | ||||
|     }; | ||||
|  | ||||
|     return EditableLinkPolicy; | ||||
| }); | ||||
							
								
								
									
										52
									
								
								platform/commonUI/edit/src/policies/EditableMovePolicy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								platform/commonUI/edit/src/policies/EditableMovePolicy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([], function () { | ||||
|  | ||||
|     /** | ||||
|      * Policy suppressing move actions among editable and non-editable | ||||
|      * domain objects. | ||||
|      * @memberof platform/commonUI/edit | ||||
|      * @constructor | ||||
|      * @implements {Policy.<View, DomainObject>} | ||||
|      */ | ||||
|     function EditableMovePolicy() { | ||||
|     } | ||||
|  | ||||
|     EditableMovePolicy.prototype.allow = function (action, context) { | ||||
|         var domainObject = context.domainObject, | ||||
|             selectedObject = context.selectedObject, | ||||
|             key = action.getMetadata().key, | ||||
|             isDomainObjectEditing = domainObject.hasCapability('editor') && | ||||
|                 domainObject.getCapability('editor').inEditContext(); | ||||
|  | ||||
|         if (key === 'move' && isDomainObjectEditing) { | ||||
|             return !!selectedObject && selectedObject.hasCapability('editor') && | ||||
|                 selectedObject.getCapability('editor').inEditContext(); | ||||
|         } | ||||
|  | ||||
|         // Like all policies, allow by default. | ||||
|         return true; | ||||
|     }; | ||||
|  | ||||
|     return EditableMovePolicy; | ||||
| }); | ||||
							
								
								
									
										49
									
								
								platform/commonUI/edit/src/policies/EditableViewPolicy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								platform/commonUI/edit/src/policies/EditableViewPolicy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Policy controlling which views should be visible in Edit mode. | ||||
|          * @memberof platform/commonUI/edit | ||||
|          * @constructor | ||||
|          * @implements {Policy.<View, DomainObject>} | ||||
|          */ | ||||
|         function EditableViewPolicy() { | ||||
|         } | ||||
|  | ||||
|         EditableViewPolicy.prototype.allow = function (view, domainObject) { | ||||
|             // If a view is flagged as non-editable, only allow it | ||||
|             // while we're not in Edit mode. | ||||
|             if ((view || {}).editable === false) { | ||||
|                 return !(domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext()); | ||||
|             } | ||||
|  | ||||
|             // Like all policies, allow by default. | ||||
|             return true; | ||||
|         }; | ||||
|  | ||||
|         return EditableViewPolicy; | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										254
									
								
								platform/commonUI/edit/src/representers/EditToolbar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								platform/commonUI/edit/src/representers/EditToolbar.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,254 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| define( | ||||
|     [ | ||||
|         '../../../../../src/api/objects/object-utils', | ||||
|         'lodash' | ||||
|     ], | ||||
|     function ( | ||||
|         objectUtils, | ||||
|         _ | ||||
|     ) { | ||||
|  | ||||
|         /** | ||||
|          * Provides initial structure and state (as suitable for provision | ||||
|          * to the `mct-toolbar` directive) for a view's toolbar, based on | ||||
|          * that view's declaration of what belongs in its toolbar and on | ||||
|          * the current selection. | ||||
|          * | ||||
|          * @param $scope the Angular scope | ||||
|          * @param {Object} openmct the openmct object | ||||
|          * @param structure the toolbar structure | ||||
|          * @memberof platform/commonUI/edit | ||||
|          * @constructor | ||||
|          */ | ||||
|         function EditToolbar($scope, openmct, structure) { | ||||
|             this.toolbarStructure = []; | ||||
|             this.properties = []; | ||||
|             this.toolbarState = []; | ||||
|             this.openmct = openmct; | ||||
|             this.domainObjectsById = {}; | ||||
|             this.unobserveObjects = []; | ||||
|             this.stateTracker = []; | ||||
|  | ||||
|             $scope.$watchCollection(this.getState.bind(this), this.handleStateChanges.bind(this)); | ||||
|             $scope.$on("$destroy", this.destroy.bind(this)); | ||||
|  | ||||
|             this.updateToolbar(structure); | ||||
|             this.registerListeners(structure); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Updates the toolbar with a new structure. | ||||
|          * | ||||
|          * @param {Array} structure the toolbar structure | ||||
|          */ | ||||
|         EditToolbar.prototype.updateToolbar = function (structure) { | ||||
|             var self = this; | ||||
|  | ||||
|             function addKey(item) { | ||||
|                 self.stateTracker.push({ | ||||
|                     id: objectUtils.makeKeyString(item.domainObject.identifier), | ||||
|                     domainObject: item.domainObject, | ||||
|                     property: item.property | ||||
|                 }); | ||||
|                 self.properties.push(item.property); | ||||
|  | ||||
|                 return self.properties.length - 1; // Return index of property | ||||
|             } | ||||
|  | ||||
|             function convertItem(item) { | ||||
|                 var converted = Object.create(item || {}); | ||||
|  | ||||
|                 if (item.property) { | ||||
|                     converted.key = addKey(item); | ||||
|                 } | ||||
|  | ||||
|                 if (item.method) { | ||||
|                     converted.click = function (value) { | ||||
|                         item.method(value); | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 return converted; | ||||
|             } | ||||
|  | ||||
|             // Get initial value for a given property | ||||
|             function initializeState(property) { | ||||
|                 var result; | ||||
|                 structure.forEach(function (item) { | ||||
|                     if (item.property === property) { | ||||
|                         result = _.get(item.domainObject, item.property); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 return result; | ||||
|             } | ||||
|  | ||||
|             // Tracks the domain object and property for every element in the state array | ||||
|             this.stateTracker = []; | ||||
|             this.toolbarStructure = structure.map(convertItem); | ||||
|             this.toolbarState = this.properties.map(initializeState); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Gets the structure of the toolbar, as appropriate to | ||||
|          * pass to `mct-toolbar`. | ||||
|          * | ||||
|          * @returns {Array} the toolbar structure | ||||
|          */ | ||||
|         EditToolbar.prototype.getStructure = function () { | ||||
|             return this.toolbarStructure; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Gets the current state of the toolbar, as appropriate | ||||
|          * to two-way bind to the state handled by `mct-toolbar`. | ||||
|          * | ||||
|          * @returns {Array} state of the toolbar | ||||
|          */ | ||||
|         EditToolbar.prototype.getState = function () { | ||||
|             return this.toolbarState; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Mutates the domain object's property with a new value. | ||||
|          * | ||||
|          * @param {Object} dominObject the domain object | ||||
|          * @param {string} property the domain object's property to update | ||||
|          * @param value the property's new value | ||||
|          */ | ||||
|         EditToolbar.prototype.updateDomainObject = function (domainObject, property, value) { | ||||
|             this.openmct.objects.mutate(domainObject, property, value); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Updates state with the new value. | ||||
|          * | ||||
|          * @param {number} index the index of the corresponding | ||||
|          *        element in the state array | ||||
|          * @param value the new value to update the state array with | ||||
|          */ | ||||
|         EditToolbar.prototype.updateState = function (index, value) { | ||||
|             this.toolbarState[index] = value; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Register listeners for domain objects to watch for updates. | ||||
|          * | ||||
|          * @param {Array} the toolbar structure | ||||
|          */ | ||||
|         EditToolbar.prototype.registerListeners = function (structure) { | ||||
|             var self = this; | ||||
|  | ||||
|             function observeObject(domainObject, id) { | ||||
|                 var unobserveObject = self.openmct.objects.observe(domainObject, '*', function (newObject) { | ||||
|                     self.domainObjectsById[id].newObject = JSON.parse(JSON.stringify(newObject)); | ||||
|                     self.scheduleStateUpdate(); | ||||
|                 }); | ||||
|                 self.unobserveObjects.push(unobserveObject); | ||||
|             } | ||||
|  | ||||
|             structure.forEach(function (item) { | ||||
|                 var domainObject = item.domainObject; | ||||
|                 var id = objectUtils.makeKeyString(domainObject.identifier); | ||||
|  | ||||
|                 if (!self.domainObjectsById[id]) { | ||||
|                     self.domainObjectsById[id] = { | ||||
|                         domainObject: domainObject, | ||||
|                         properties: [] | ||||
|                     }; | ||||
|                     observeObject(domainObject, id); | ||||
|                 } | ||||
|  | ||||
|                 self.domainObjectsById[id].properties.push(item.property); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Delays updating the state. | ||||
|          */ | ||||
|         EditToolbar.prototype.scheduleStateUpdate = function () { | ||||
|             if (this.stateUpdateScheduled) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.stateUpdateScheduled = true; | ||||
|             setTimeout(this.updateStateAfterMutation.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         EditToolbar.prototype.updateStateAfterMutation = function () { | ||||
|             this.stateTracker.forEach(function (state, index) { | ||||
|                 if (!this.domainObjectsById[state.id].newObject) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 var domainObject = this.domainObjectsById[state.id].domainObject; | ||||
|                 var newObject = this.domainObjectsById[state.id].newObject; | ||||
|                 var currentValue = _.get(domainObject, state.property); | ||||
|                 var newValue = _.get(newObject, state.property); | ||||
|  | ||||
|                 state.domainObject = newObject; | ||||
|  | ||||
|                 if (currentValue !== newValue) { | ||||
|                     this.updateState(index, newValue); | ||||
|                 } | ||||
|             }, this); | ||||
|  | ||||
|             Object.values(this.domainObjectsById).forEach(function (tracker) { | ||||
|                 if (tracker.newObject) { | ||||
|                     tracker.domainObject = tracker.newObject; | ||||
|                 } | ||||
|                 delete tracker.newObject; | ||||
|             }); | ||||
|             this.stateUpdateScheduled = false; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Removes the listeners. | ||||
|          */ | ||||
|         EditToolbar.prototype.deregisterListeners = function () { | ||||
|             this.unobserveObjects.forEach(function (unobserveObject) { | ||||
|                 unobserveObject(); | ||||
|             }); | ||||
|             this.unobserveObjects = []; | ||||
|         }; | ||||
|  | ||||
|         EditToolbar.prototype.handleStateChanges = function (state) { | ||||
|             (state || []).map(function (newValue, index) { | ||||
|                 var domainObject = this.stateTracker[index].domainObject; | ||||
|                 var property = this.stateTracker[index].property; | ||||
|                 var currentValue = _.get(domainObject, property); | ||||
|  | ||||
|                 if (currentValue !== newValue) { | ||||
|                     this.updateDomainObject(domainObject, property, newValue); | ||||
|                 } | ||||
|             }, this); | ||||
|         }; | ||||
|  | ||||
|         EditToolbar.prototype.destroy = function () { | ||||
|             this.deregisterListeners(); | ||||
|         }; | ||||
|  | ||||
|         return EditToolbar; | ||||
|     } | ||||
| ); | ||||
| @@ -29,7 +29,7 @@ define( | ||||
|                 actionContext, | ||||
|                 capabilities, | ||||
|                 mockContext, | ||||
|                 mockOverlayAPI, | ||||
|                 mockDialogService, | ||||
|                 mockDomainObject, | ||||
|                 mockMutation, | ||||
|                 mockNavigationService, | ||||
| @@ -68,9 +68,9 @@ define( | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 mockOverlayAPI = jasmine.createSpyObj( | ||||
|                     "overlayAPI", | ||||
|                     ["dialog"] | ||||
|                 mockDialogService = jasmine.createSpyObj( | ||||
|                     "dialogService", | ||||
|                     ["showBlockingMessage"] | ||||
|                 ); | ||||
|  | ||||
|                 mockNavigationService = jasmine.createSpyObj( | ||||
| @@ -96,7 +96,7 @@ define( | ||||
|  | ||||
|                 actionContext = { domainObject: mockDomainObject }; | ||||
|  | ||||
|                 action = new RemoveAction({overlays: mockOverlayAPI}, mockNavigationService, actionContext); | ||||
|                 action = new RemoveAction(mockDialogService, mockNavigationService, actionContext); | ||||
|             }); | ||||
|  | ||||
|             it("only applies to objects with parents", function () { | ||||
| @@ -118,7 +118,7 @@ define( | ||||
|  | ||||
|                 action.perform(); | ||||
|  | ||||
|                 expect(mockOverlayAPI.dialog).toHaveBeenCalled(); | ||||
|                 expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); | ||||
|  | ||||
|                 // Also check that no mutation happens at this point | ||||
|                 expect(mockParent.useCapability).not.toHaveBeenCalledWith("mutation", jasmine.any(Function)); | ||||
| @@ -158,13 +158,13 @@ define( | ||||
|                     mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]); | ||||
|                     mockRootContext = jasmine.createSpyObj("context", ["getParent"]); | ||||
|  | ||||
|                     mockOverlayAPI.dialog.and.returnValue(mockDialogHandle); | ||||
|                     mockDialogService.showBlockingMessage.and.returnValue(mockDialogHandle); | ||||
|                 }); | ||||
|  | ||||
|                 it("mutates the parent when performed", function () { | ||||
|                     action.perform(); | ||||
|                     mockOverlayAPI.dialog.calls.mostRecent().args[0] | ||||
|                         .buttons[0].callback(); | ||||
|                     mockDialogService.showBlockingMessage.calls.mostRecent().args[0] | ||||
|                         .primaryOption.callback(); | ||||
|  | ||||
|                     expect(mockMutation.invoke) | ||||
|                         .toHaveBeenCalledWith(jasmine.any(Function)); | ||||
| @@ -174,8 +174,8 @@ define( | ||||
|                     var mutator, result; | ||||
|  | ||||
|                     action.perform(); | ||||
|                     mockOverlayAPI.dialog.calls.mostRecent().args[0] | ||||
|                         .buttons[0].callback(); | ||||
|                     mockDialogService.showBlockingMessage.calls.mostRecent().args[0] | ||||
|                         .primaryOption.callback(); | ||||
|  | ||||
|                     mutator = mockMutation.invoke.calls.mostRecent().args[0]; | ||||
|                     result = mutator(model); | ||||
| @@ -212,8 +212,8 @@ define( | ||||
|                     mockType.hasFeature.and.returnValue(true); | ||||
|  | ||||
|                     action.perform(); | ||||
|                     mockOverlayAPI.dialog.calls.mostRecent().args[0] | ||||
|                         .buttons[0].callback(); | ||||
|                     mockDialogService.showBlockingMessage.calls.mostRecent().args[0] | ||||
|                         .primaryOption.callback(); | ||||
|  | ||||
|                     // Expects navigation to parent of domainObject (removed object) | ||||
|                     expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(mockParent); | ||||
| @@ -242,8 +242,8 @@ define( | ||||
|                     mockType.hasFeature.and.returnValue(true); | ||||
|  | ||||
|                     action.perform(); | ||||
|                     mockOverlayAPI.dialog.calls.mostRecent().args[0] | ||||
|                         .buttons[0].callback(); | ||||
|                     mockDialogService.showBlockingMessage.calls.mostRecent().args[0] | ||||
|                         .primaryOption.callback(); | ||||
|  | ||||
|                     // Expects no navigation to occur | ||||
|                     expect(mockNavigationService.setNavigation).not.toHaveBeenCalled(); | ||||
|   | ||||
| @@ -0,0 +1,184 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /*global describe,it,expect,beforeEach,jasmine*/ | ||||
|  | ||||
| define( | ||||
|     ["../../src/controllers/ElementsController"], | ||||
|     function (ElementsController) { | ||||
|  | ||||
|         describe("The Elements Pane controller", function () { | ||||
|             var mockScope, | ||||
|                 mockOpenMCT, | ||||
|                 mockSelection, | ||||
|                 mockDomainObject, | ||||
|                 mockMutationCapability, | ||||
|                 mockCompositionCapability, | ||||
|                 mockCompositionObjects, | ||||
|                 mockComposition, | ||||
|                 mockUnlisten, | ||||
|                 selectable = [], | ||||
|                 controller; | ||||
|  | ||||
|             function mockPromise(value) { | ||||
|                 return { | ||||
|                     then: function (thenFunc) { | ||||
|                         return mockPromise(thenFunc(value)); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             function createDomainObject() { | ||||
|                 return { | ||||
|                     useCapability: function () { | ||||
|                         return mockCompositionCapability; | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockComposition = ["a", "b"]; | ||||
|                 mockCompositionObjects = mockComposition.map(createDomainObject); | ||||
|                 mockCompositionCapability = mockPromise(mockCompositionObjects); | ||||
|  | ||||
|                 mockUnlisten = jasmine.createSpy('unlisten'); | ||||
|                 mockMutationCapability = jasmine.createSpyObj("mutationCapability", [ | ||||
|                     "listen" | ||||
|                 ]); | ||||
|                 mockMutationCapability.listen.and.returnValue(mockUnlisten); | ||||
|                 mockDomainObject = jasmine.createSpyObj("domainObject", [ | ||||
|                     "getCapability", | ||||
|                     "useCapability" | ||||
|                 ]); | ||||
|                 mockDomainObject.useCapability.and.returnValue(mockCompositionCapability); | ||||
|                 mockDomainObject.getCapability.and.returnValue(mockMutationCapability); | ||||
|  | ||||
|                 mockScope = jasmine.createSpyObj("$scope", ['$on']); | ||||
|                 mockSelection = jasmine.createSpyObj("selection", [ | ||||
|                     'on', | ||||
|                     'off', | ||||
|                     'get' | ||||
|                 ]); | ||||
|                 mockSelection.get.and.returnValue([]); | ||||
|                 mockOpenMCT = { | ||||
|                     selection: mockSelection | ||||
|                 }; | ||||
|  | ||||
|                 selectable[0] = { | ||||
|                     context: { | ||||
|                         oldItem: mockDomainObject | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 spyOn(ElementsController.prototype, 'refreshComposition').and.callThrough(); | ||||
|  | ||||
|                 controller = new ElementsController(mockScope, mockOpenMCT); | ||||
|             }); | ||||
|  | ||||
|             function getModel(model) { | ||||
|                 return function () { | ||||
|                     return model; | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             it("filters objects in elements pool based on input text and" + | ||||
|                 " object name", function () { | ||||
|                 var objects = [ | ||||
|                     { | ||||
|                         getModel: getModel({name: "first element"}) | ||||
|                     }, | ||||
|                     { | ||||
|                         getModel: getModel({name: "second element"}) | ||||
|                     }, | ||||
|                     { | ||||
|                         getModel: getModel({name: "third element"}) | ||||
|                     }, | ||||
|                     { | ||||
|                         getModel: getModel({name: "THIRD Element 1"}) | ||||
|                     } | ||||
|                 ]; | ||||
|  | ||||
|                 mockScope.filterBy("third element"); | ||||
|                 expect(objects.filter(mockScope.searchElements).length).toBe(2); | ||||
|                 mockScope.filterBy("element"); | ||||
|                 expect(objects.filter(mockScope.searchElements).length).toBe(4); | ||||
|             }); | ||||
|  | ||||
|             it("refreshes composition on selection", function () { | ||||
|                 mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable); | ||||
|  | ||||
|                 expect(ElementsController.prototype.refreshComposition).toHaveBeenCalledWith(mockDomainObject); | ||||
|             }); | ||||
|  | ||||
|             it("listens on mutation and refreshes composition", function () { | ||||
|                 mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable); | ||||
|  | ||||
|                 expect(mockDomainObject.getCapability).toHaveBeenCalledWith('mutation'); | ||||
|                 expect(mockMutationCapability.listen).toHaveBeenCalled(); | ||||
|                 expect(ElementsController.prototype.refreshComposition.calls.count()).toBe(1); | ||||
|  | ||||
|                 mockMutationCapability.listen.calls.mostRecent().args[0](mockDomainObject); | ||||
|  | ||||
|                 expect(ElementsController.prototype.refreshComposition.calls.count()).toBe(2); | ||||
|             }); | ||||
|  | ||||
|             it("cleans up mutation listener when selection changes", function () { | ||||
|                 mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable); | ||||
|  | ||||
|                 expect(mockMutationCapability.listen).toHaveBeenCalled(); | ||||
|  | ||||
|                 mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable); | ||||
|  | ||||
|                 expect(mockUnlisten).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("does not listen on mutation for element proxy selectable", function () { | ||||
|                 selectable[0] = { | ||||
|                     context: { | ||||
|                         elementProxy: {} | ||||
|                     } | ||||
|                 }; | ||||
|                 mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable); | ||||
|  | ||||
|                 expect(mockDomainObject.getCapability).not.toHaveBeenCalledWith('mutation'); | ||||
|             }); | ||||
|  | ||||
|             it("checks concurrent changes to composition", function () { | ||||
|                 var secondMockComposition = ["a", "b", "c"], | ||||
|                     secondMockCompositionObjects = secondMockComposition.map(createDomainObject), | ||||
|                     firstCompositionCallback, | ||||
|                     secondCompositionCallback; | ||||
|  | ||||
|                 spyOn(mockCompositionCapability, "then").and.callThrough(); | ||||
|  | ||||
|                 controller.refreshComposition(mockDomainObject); | ||||
|                 controller.refreshComposition(mockDomainObject); | ||||
|  | ||||
|                 firstCompositionCallback = mockCompositionCapability.then.calls.all()[0].args[0]; | ||||
|                 secondCompositionCallback = mockCompositionCapability.then.calls.all()[1].args[0]; | ||||
|                 secondCompositionCallback(secondMockCompositionObjects); | ||||
|                 firstCompositionCallback(mockCompositionObjects); | ||||
|  | ||||
|                 expect(mockScope.composition).toBe(secondMockCompositionObjects); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										105
									
								
								platform/commonUI/edit/test/creation/AddActionProviderSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								platform/commonUI/edit/test/creation/AddActionProviderSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * MCTRepresentationSpec. Created by ahenry on 01/21/14. | ||||
|  */ | ||||
| define( | ||||
|     ["../../src/creation/AddActionProvider"], | ||||
|     function (AddActionProvider) { | ||||
|  | ||||
|         describe("The add action provider", function () { | ||||
|             var mockTypeService, | ||||
|                 mockDialogService, | ||||
|                 mockPolicyService, | ||||
|                 mockTypeMap, | ||||
|                 mockTypes, | ||||
|                 mockDomainObject, | ||||
|                 mockQ, | ||||
|                 provider; | ||||
|  | ||||
|             function createMockType(name) { | ||||
|                 var mockType = jasmine.createSpyObj( | ||||
|                     "type" + name, | ||||
|                     [ | ||||
|                         "getKey", | ||||
|                         "getGlyph", | ||||
|                         "getCssClass", | ||||
|                         "getName", | ||||
|                         "getDescription", | ||||
|                         "getProperties", | ||||
|                         "getInitialModel", | ||||
|                         "hasFeature" | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockType.hasFeature.and.returnValue(true); | ||||
|                 mockType.getName.and.returnValue(name); | ||||
|                 mockType.getKey.and.returnValue(name); | ||||
|                 return mockType; | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockTypeService = jasmine.createSpyObj( | ||||
|                     "typeService", | ||||
|                     ["getType"] | ||||
|                 ); | ||||
|                 mockDialogService = {}; | ||||
|                 mockPolicyService = {}; | ||||
|                 mockDomainObject = {}; | ||||
|  | ||||
|                 mockTypes = [ | ||||
|                     "timeline", | ||||
|                     "activity", | ||||
|                     "other" | ||||
|                 ].map(createMockType); | ||||
|                 mockTypeMap = {}; | ||||
|  | ||||
|                 mockTypes.forEach(function (type) { | ||||
|                     mockTypeMap[type.getKey()] = type; | ||||
|                 }); | ||||
|  | ||||
|                 mockTypeService.getType.and.callFake(function (key) { | ||||
|                     return mockTypeMap[key]; | ||||
|                 }); | ||||
|  | ||||
|                 provider = new AddActionProvider( | ||||
|                     mockQ, | ||||
|                     mockTypeService, | ||||
|                     mockDialogService, | ||||
|                     mockPolicyService | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("provides actions for timeline and activity", function () { | ||||
|                 var actions = provider.getActions({ | ||||
|                     key: "add", | ||||
|                     domainObject: mockDomainObject | ||||
|                 }); | ||||
|                 expect(actions.length).toBe(2); | ||||
|                 expect(actions[0].metadata.type).toBe('timeline'); | ||||
|                 expect(actions[1].metadata.type).toBe('activity'); | ||||
|  | ||||
|                 // Make sure it was creation which was used to check | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										138
									
								
								platform/commonUI/edit/test/policies/EditActionPolicySpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								platform/commonUI/edit/test/policies/EditActionPolicySpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ["../../src/policies/EditActionPolicy"], | ||||
|     function (EditActionPolicy) { | ||||
|  | ||||
|         describe("The Edit action policy", function () { | ||||
|             var editableView, | ||||
|                 nonEditableView, | ||||
|                 testViews, | ||||
|                 testContext, | ||||
|                 mockDomainObject, | ||||
|                 mockEditAction, | ||||
|                 mockPropertiesAction, | ||||
|                 mockTypeCapability, | ||||
|                 mockEditorCapability, | ||||
|                 capabilities, | ||||
|                 plotView, | ||||
|                 policy; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
|                     'domainObject', | ||||
|                     [ | ||||
|                         'useCapability', | ||||
|                         'hasCapability', | ||||
|                         'getCapability' | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockEditorCapability = jasmine.createSpyObj('editorCapability', ['isEditContextRoot']); | ||||
|                 mockTypeCapability = jasmine.createSpyObj('type', ['getKey']); | ||||
|                 capabilities = { | ||||
|                     'editor': mockEditorCapability, | ||||
|                     'type': mockTypeCapability | ||||
|                 }; | ||||
|  | ||||
|                 mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']); | ||||
|                 mockPropertiesAction = jasmine.createSpyObj('edit', ['getMetadata']); | ||||
|  | ||||
|                 mockDomainObject.getCapability.and.callFake(function (capability) { | ||||
|                     return capabilities[capability]; | ||||
|                 }); | ||||
|                 mockDomainObject.hasCapability.and.callFake(function (capability) { | ||||
|                     return !!capabilities[capability]; | ||||
|                 }); | ||||
|  | ||||
|                 editableView = { editable: true }; | ||||
|                 nonEditableView = { editable: false }; | ||||
|                 plotView = { key: "plot", editable: false }; | ||||
|                 testViews = []; | ||||
|  | ||||
|                 mockDomainObject.useCapability.and.callFake(function (c) { | ||||
|                     // Provide test views, only for the view capability | ||||
|                     return c === 'view' && testViews; | ||||
|                 }); | ||||
|  | ||||
|                 mockEditAction.getMetadata.and.returnValue({ key: 'edit' }); | ||||
|                 mockPropertiesAction.getMetadata.and.returnValue({ key: 'properties' }); | ||||
|  | ||||
|                 testContext = { | ||||
|                     domainObject: mockDomainObject, | ||||
|                     category: 'view-control' | ||||
|                 }; | ||||
|  | ||||
|                 policy = new EditActionPolicy(); | ||||
|             }); | ||||
|  | ||||
|             it("allows the edit action when there are editable views", function () { | ||||
|                 testViews = [editableView]; | ||||
|                 expect(policy.allow(mockEditAction, testContext)).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             it("allows the edit properties action when there are no editable views", function () { | ||||
|                 testViews = [nonEditableView, nonEditableView]; | ||||
|                 expect(policy.allow(mockPropertiesAction, testContext)).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             it("disallows the edit action when there are no editable views", function () { | ||||
|                 testViews = [nonEditableView, nonEditableView]; | ||||
|                 expect(policy.allow(mockEditAction, testContext)).toBe(false); | ||||
|             }); | ||||
|  | ||||
|             it("disallows the edit properties action when there are" + | ||||
|                 " editable views", function () { | ||||
|                 testViews = [editableView]; | ||||
|                 expect(policy.allow(mockPropertiesAction, testContext)).toBe(false); | ||||
|             }); | ||||
|  | ||||
|             it("disallows the edit action when object is already being" + | ||||
|                 " edited", function () { | ||||
|                 testViews = [editableView]; | ||||
|                 mockEditorCapability.isEditContextRoot.and.returnValue(true); | ||||
|                 expect(policy.allow(mockEditAction, testContext)).toBe(false); | ||||
|             }); | ||||
|  | ||||
|             it("allows editing of panels in plot view", function () { | ||||
|                 testViews = [plotView]; | ||||
|                 mockTypeCapability.getKey.and.returnValue('telemetry.panel'); | ||||
|  | ||||
|                 expect(policy.allow(mockEditAction, testContext)).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             it("disallows editing of plot view when object not a panel type", function () { | ||||
|                 testViews = [plotView]; | ||||
|                 mockTypeCapability.getKey.and.returnValue('something.else'); | ||||
|  | ||||
|                 expect(policy.allow(mockEditAction, testContext)).toBe(false); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|             it("allows the edit properties outside of the 'view-control' category", function () { | ||||
|                 testViews = [nonEditableView]; | ||||
|                 testContext.category = "something-else"; | ||||
|                 expect(policy.allow(mockPropertiesAction, testContext)).toBe(true); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -0,0 +1,120 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /*global describe,it,expect,beforeEach,jasmine*/ | ||||
|  | ||||
| define( | ||||
|     ["../../src/policies/EditContextualActionPolicy"], | ||||
|     function (EditContextualActionPolicy) { | ||||
|  | ||||
|         describe("The Edit contextual action policy", function () { | ||||
|             var policy, | ||||
|                 navigationService, | ||||
|                 mockAction, | ||||
|                 context, | ||||
|                 navigatedObject, | ||||
|                 mockDomainObject, | ||||
|                 mockEditorCapability, | ||||
|                 metadata, | ||||
|                 editModeBlacklist = ["copy", "follow", "window", "link", "locate"], | ||||
|                 nonEditContextBlacklist = ["copy", "follow", "properties", "move", "link", "remove", "locate"]; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockEditorCapability = jasmine.createSpyObj("editorCapability", ["isEditContextRoot", "inEditContext"]); | ||||
|  | ||||
|                 navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability", "getCapability"]); | ||||
|                 navigatedObject.getCapability.and.returnValue(mockEditorCapability); | ||||
|                 navigatedObject.hasCapability.and.returnValue(false); | ||||
|  | ||||
|  | ||||
|                 mockDomainObject = jasmine.createSpyObj("domainObject", ["hasCapability", "getCapability"]); | ||||
|                 mockDomainObject.hasCapability.and.returnValue(false); | ||||
|                 mockDomainObject.getCapability.and.returnValue(mockEditorCapability); | ||||
|  | ||||
|                 navigationService = jasmine.createSpyObj("navigationService", ["getNavigation"]); | ||||
|                 navigationService.getNavigation.and.returnValue(navigatedObject); | ||||
|  | ||||
|                 metadata = {key: "move"}; | ||||
|                 mockAction = jasmine.createSpyObj("action", ["getMetadata"]); | ||||
|                 mockAction.getMetadata.and.returnValue(metadata); | ||||
|  | ||||
|                 context = {domainObject: mockDomainObject}; | ||||
|  | ||||
|                 policy = new EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist); | ||||
|             }); | ||||
|  | ||||
|             it('Allows all actions when navigated object not in edit mode', function () { | ||||
|                 expect(policy.allow(mockAction, context)).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             it('Allows "window" action when navigated object in edit mode,' + | ||||
|                 ' but selected object not in edit mode ', function () { | ||||
|                 navigatedObject.hasCapability.and.returnValue(true); | ||||
|                 mockEditorCapability.isEditContextRoot.and.returnValue(true); | ||||
|                 metadata.key = "window"; | ||||
|                 expect(policy.allow(mockAction, context)).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             it('Allows "remove" action when navigated object in edit mode,' + | ||||
|                 ' and selected object not editable, but its parent is.', | ||||
|             function () { | ||||
|                 var mockParent = jasmine.createSpyObj("parentObject", ["hasCapability"]), | ||||
|                     mockContextCapability = jasmine.createSpyObj("contextCapability", ["getParent"]); | ||||
|  | ||||
|                 mockParent.hasCapability.and.returnValue(true); | ||||
|                 mockContextCapability.getParent.and.returnValue(mockParent); | ||||
|                 navigatedObject.hasCapability.and.returnValue(true); | ||||
|  | ||||
|                 mockDomainObject.getCapability.and.returnValue(mockContextCapability); | ||||
|                 mockDomainObject.hasCapability.and.callFake(function (capability) { | ||||
|                     switch (capability) { | ||||
|                     case "editor": return false; | ||||
|                     case "context": return true; | ||||
|                     } | ||||
|                 }); | ||||
|                 metadata.key = "remove"; | ||||
|  | ||||
|                 expect(policy.allow(mockAction, context)).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             it('Disallows "move" action when navigated object in edit mode,' + | ||||
|                 ' but selected object not in edit mode ', function () { | ||||
|                 navigatedObject.hasCapability.and.returnValue(true); | ||||
|                 mockEditorCapability.isEditContextRoot.and.returnValue(true); | ||||
|                 mockEditorCapability.inEditContext.and.returnValue(false); | ||||
|                 metadata.key = "move"; | ||||
|                 expect(policy.allow(mockAction, context)).toBe(false); | ||||
|             }); | ||||
|  | ||||
|             it('Disallows copy action when navigated object and' + | ||||
|                 ' selected object in edit mode', function () { | ||||
|                 navigatedObject.hasCapability.and.returnValue(true); | ||||
|                 mockDomainObject.hasCapability.and.returnValue(true); | ||||
|                 mockEditorCapability.isEditContextRoot.and.returnValue(true); | ||||
|                 mockEditorCapability.inEditContext.and.returnValue(true); | ||||
|  | ||||
|                 metadata.key = "copy"; | ||||
|                 expect(policy.allow(mockAction, context)).toBe(false); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -0,0 +1,79 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ["../../src/policies/EditableViewPolicy"], | ||||
|     function (EditableViewPolicy) { | ||||
|  | ||||
|         describe("The editable view policy", function () { | ||||
|             var mockDomainObject, | ||||
|                 testMode, | ||||
|                 policy; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 testMode = true; // Act as if we're in Edit mode by default | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
|                     'domainObject', | ||||
|                     ['hasCapability', 'getCapability'] | ||||
|                 ); | ||||
|                 mockDomainObject.getCapability.and.returnValue({ | ||||
|                     inEditContext: function () { | ||||
|                         return true; | ||||
|                     } | ||||
|                 }); | ||||
|                 mockDomainObject.hasCapability.and.callFake(function (c) { | ||||
|                     return (c === 'editor') && testMode; | ||||
|                 }); | ||||
|  | ||||
|                 policy = new EditableViewPolicy(); | ||||
|             }); | ||||
|  | ||||
|             it("disallows views in edit mode that are flagged as non-editable", function () { | ||||
|                 expect(policy.allow({ editable: false }, mockDomainObject)) | ||||
|                     .toBeFalsy(); | ||||
|             }); | ||||
|  | ||||
|             it("allows views in edit mode that are flagged as editable", function () { | ||||
|                 expect(policy.allow({ editable: true }, mockDomainObject)) | ||||
|                     .toBeTruthy(); | ||||
|             }); | ||||
|  | ||||
|             it("allows any view outside of edit mode", function () { | ||||
|                 var testViews = [ | ||||
|                     { editable: false }, | ||||
|                     { editable: true }, | ||||
|                     { someKey: "some value" } | ||||
|                 ]; | ||||
|                 testMode = false; // Act as if we're not in Edit mode | ||||
|  | ||||
|                 testViews.forEach(function (testView) { | ||||
|                     expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             it("treats views with no defined 'editable' property as editable", function () { | ||||
|                 expect(policy.allow({ someKey: "some value" }, mockDomainObject)) | ||||
|                     .toBeTruthy(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										75
									
								
								platform/commonUI/edit/test/representers/EditToolbarSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								platform/commonUI/edit/test/representers/EditToolbarSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ['../../src/representers/EditToolbar'], | ||||
|     function (EditToolbar) { | ||||
|  | ||||
|         describe("An Edit mode toolbar", function () { | ||||
|             var mockOpenMCT, | ||||
|                 mockScope, | ||||
|                 mockObjects, | ||||
|                 mockDomainObject, | ||||
|                 testStructure, | ||||
|                 toolbar; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockOpenMCT = jasmine.createSpy('openmct', ['objects']); | ||||
|                 mockObjects = jasmine.createSpyObj('objects', ['observe']); | ||||
|                 mockObjects.observe.and.returnValue(); | ||||
|                 mockOpenMCT.objects = mockObjects; | ||||
|                 mockScope = jasmine.createSpyObj("$scope", [ | ||||
|                     "$watchCollection", | ||||
|                     "$on" | ||||
|                 ]); | ||||
|                 mockScope.$watchCollection.and.returnValue(); | ||||
|                 mockDomainObject = jasmine.createSpyObj("domainObject", [ | ||||
|                     'identifier' | ||||
|                 ]); | ||||
|  | ||||
|                 testStructure = [ | ||||
|                     { name: "A", property: "a", domainObject: mockDomainObject }, | ||||
|                     { name: "B", property: "b", domainObject: mockDomainObject }, | ||||
|                     { name: "C", property: "c", domainObject: mockDomainObject }, | ||||
|                     { name: "X", property: "x", domainObject: mockDomainObject }, | ||||
|                     { name: "Y", property: "y", domainObject: mockDomainObject }, | ||||
|                     { name: "Z", property: "z", domainObject: mockDomainObject }, | ||||
|                     { name: "M", method: "m", domainObject: mockDomainObject } | ||||
|                 ]; | ||||
|  | ||||
|                 toolbar = new EditToolbar(mockScope, mockOpenMCT, testStructure); | ||||
|             }); | ||||
|  | ||||
|             it("adds click functions when a method is specified", function () { | ||||
|                 var structure = toolbar.getStructure(); | ||||
|                 expect(structure[6].click).toBeDefined(); | ||||
|             }); | ||||
|  | ||||
|             it("adds key for controls that define a property", function () { | ||||
|                 var structure = toolbar.getStructure(); | ||||
|                 expect(structure[0].key).toEqual(0); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|  | ||||
|  | ||||
| @@ -56,7 +56,7 @@ define([ | ||||
|     }; | ||||
|  | ||||
|     DurationFormat.prototype.validate = function (text) { | ||||
|         return moment.utc(text, DATE_FORMATS, true).isValid(); | ||||
|         return moment.utc(text, DATE_FORMATS).isValid(); | ||||
|     }; | ||||
|  | ||||
|     return DurationFormat; | ||||
|   | ||||
| @@ -29,7 +29,6 @@ define([ | ||||
|     var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS", | ||||
|         DATE_FORMATS = [ | ||||
|             DATE_FORMAT, | ||||
|             DATE_FORMAT + "Z", | ||||
|             "YYYY-MM-DD HH:mm:ss", | ||||
|             "YYYY-MM-DD HH:mm", | ||||
|             "YYYY-MM-DD" | ||||
| @@ -53,14 +52,70 @@ define([ | ||||
|         this.key = "utc"; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns an appropriate time format based on the provided value and | ||||
|      * the threshold required. | ||||
|      * @private | ||||
|      */ | ||||
|     function getScaledFormat(d) { | ||||
|         var momentified = moment.utc(d); | ||||
|         /** | ||||
|          * Uses logic from d3 Time-Scales, v3 of the API. See | ||||
|          * https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md | ||||
|          * | ||||
|          * Licensed | ||||
|          */ | ||||
|         var format = [ | ||||
|             [".SSS", function (m) { | ||||
|                 return m.milliseconds(); | ||||
|             }], | ||||
|             [":ss", function (m) { | ||||
|                 return m.seconds(); | ||||
|             }], | ||||
|             ["HH:mm", function (m) { | ||||
|                 return m.minutes(); | ||||
|             }], | ||||
|             ["HH", function (m) { | ||||
|                 return m.hours(); | ||||
|             }], | ||||
|             ["ddd DD", function (m) { | ||||
|                 return m.days() && | ||||
|                     m.date() !== 1; | ||||
|             }], | ||||
|             ["MMM DD", function (m) { | ||||
|                 return m.date() !== 1; | ||||
|             }], | ||||
|             ["MMMM", function (m) { | ||||
|                 return m.month(); | ||||
|             }], | ||||
|             ["YYYY", function () { | ||||
|                 return true; | ||||
|             }] | ||||
|         ].filter(function (row) { | ||||
|             return row[1](momentified); | ||||
|         })[0][0]; | ||||
|  | ||||
|         if (format !== undefined) { | ||||
|             return moment.utc(d).format(format); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param {number} value The value to format. | ||||
|      * @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor | ||||
|      * and plot axes. Specifies the smallest number on the scale. | ||||
|      * @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor | ||||
|      * and plot axes. Specifies the largest number on the scale | ||||
|      * @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor | ||||
|      * and plot axes. The number of labels on the scale. | ||||
|      * @returns {string} the formatted date(s). If multiple values were requested, then an array of | ||||
|      * formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position | ||||
|      * in the array. | ||||
|      */ | ||||
|     UTCTimeFormat.prototype.format = function (value) { | ||||
|         if (value !== undefined) { | ||||
|         if (arguments.length > 1) { | ||||
|             return getScaledFormat(value); | ||||
|         } else if (value !== undefined) { | ||||
|             return moment.utc(value).format(DATE_FORMAT) + "Z"; | ||||
|         } else { | ||||
|             return value; | ||||
| @@ -75,7 +130,7 @@ define([ | ||||
|     }; | ||||
|  | ||||
|     UTCTimeFormat.prototype.validate = function (text) { | ||||
|         return moment.utc(text, DATE_FORMATS, true).isValid(); | ||||
|         return moment.utc(text, DATE_FORMATS).isValid(); | ||||
|     }; | ||||
|  | ||||
|     return UTCTimeFormat; | ||||
|   | ||||
							
								
								
									
										62
									
								
								platform/commonUI/formats/test/UTCTimeFormatSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								platform/commonUI/formats/test/UTCTimeFormatSpec.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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     "../src/UTCTimeFormat", | ||||
|     "moment" | ||||
| ], function ( | ||||
|     UTCTimeFormat, | ||||
|     moment | ||||
| ) { | ||||
|     describe("The UTCTimeFormat class", function () { | ||||
|         var format; | ||||
|         var scale; | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             format = new UTCTimeFormat(); | ||||
|             scale = {min: 0, max: 0}; | ||||
|         }); | ||||
|  | ||||
|         it("Provides an appropriately scaled time format based on the input" + | ||||
|             " time", function () { | ||||
|             var TWO_HUNDRED_MS = 200; | ||||
|             var THREE_SECONDS = 3000; | ||||
|             var FIVE_MINUTES = 5 * 60 * 1000; | ||||
|             var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000); | ||||
|             var TEN_HOURS = (10 * 60 * 60 * 1000); | ||||
|  | ||||
|             var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD"); | ||||
|             var APRIL = moment.utc("2016-04", "YYYY-MM"); | ||||
|             var TWENTY_SIXTEEN = moment.utc("2016", "YYYY"); | ||||
|  | ||||
|             expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200"); | ||||
|             expect(format.format(THREE_SECONDS, scale)).toBe(":03"); | ||||
|             expect(format.format(FIVE_MINUTES, scale)).toBe("00:05"); | ||||
|             expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20"); | ||||
|             expect(format.format(TEN_HOURS, scale)).toBe("10"); | ||||
|  | ||||
|             expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03"); | ||||
|             expect(format.format(APRIL, scale)).toBe("April"); | ||||
|             expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016"); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -31,6 +31,7 @@ define([ | ||||
|     "./src/controllers/TreeNodeController", | ||||
|     "./src/controllers/ActionGroupController", | ||||
|     "./src/controllers/ToggleController", | ||||
|     "./src/controllers/ContextMenuController", | ||||
|     "./src/controllers/ClickAwayController", | ||||
|     "./src/controllers/ViewSwitcherController", | ||||
|     "./src/controllers/GetterSetterController", | ||||
| @@ -48,6 +49,8 @@ define([ | ||||
|     "./src/directives/MCTSplitter", | ||||
|     "./src/directives/MCTTree", | ||||
|     "./src/directives/MCTIndicators", | ||||
|     "./src/directives/MCTPreview", | ||||
|     "./src/actions/MCTPreviewAction", | ||||
|     "./src/filters/ReverseFilter", | ||||
|     "./res/templates/bottombar.html", | ||||
|     "./res/templates/controls/action-button.html", | ||||
| @@ -62,11 +65,13 @@ define([ | ||||
|     "./res/templates/tree-node.html", | ||||
|     "./res/templates/label.html", | ||||
|     "./res/templates/controls/action-group.html", | ||||
|     "./res/templates/menu/context-menu.html", | ||||
|     "./res/templates/controls/switcher.html", | ||||
|     "./res/templates/object-inspector.html", | ||||
|     "./res/templates/controls/selector.html", | ||||
|     "./res/templates/controls/datetime-picker.html", | ||||
|     "./res/templates/controls/datetime-field.html", | ||||
|     "./res/templates/preview.html", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     UrlService, | ||||
| @@ -79,6 +84,7 @@ define([ | ||||
|     TreeNodeController, | ||||
|     ActionGroupController, | ||||
|     ToggleController, | ||||
|     ContextMenuController, | ||||
|     ClickAwayController, | ||||
|     ViewSwitcherController, | ||||
|     GetterSetterController, | ||||
| @@ -96,6 +102,8 @@ define([ | ||||
|     MCTSplitter, | ||||
|     MCTTree, | ||||
|     MCTIndicators, | ||||
|     MCTPreview, | ||||
|     MCTPreviewAction, | ||||
|     ReverseFilter, | ||||
|     bottombarTemplate, | ||||
|     actionButtonTemplate, | ||||
| @@ -110,11 +118,13 @@ define([ | ||||
|     treeNodeTemplate, | ||||
|     labelTemplate, | ||||
|     actionGroupTemplate, | ||||
|     contextMenuTemplate, | ||||
|     switcherTemplate, | ||||
|     objectInspectorTemplate, | ||||
|     selectorTemplate, | ||||
|     datetimePickerTemplate, | ||||
|     datetimeFieldTemplate, | ||||
|     previewTemplate, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
| @@ -242,6 +252,13 @@ define([ | ||||
|                     "key": "ToggleController", | ||||
|                     "implementation": ToggleController | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "ContextMenuController", | ||||
|                     "implementation": ContextMenuController, | ||||
|                     "depends": [ | ||||
|                         "$scope" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "ClickAwayController", | ||||
|                     "implementation": ClickAwayController, | ||||
| @@ -377,6 +394,31 @@ define([ | ||||
|                     "key": "mctIndicators", | ||||
|                     "implementation": MCTIndicators, | ||||
|                     "depends": ['openmct'] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "mctPreview", | ||||
|                     "implementation": MCTPreview, | ||||
|                     "depends": [ | ||||
|                         "$document" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "actions": [ | ||||
|                 { | ||||
|                     "key": "mct-preview-action", | ||||
|                     "implementation": MCTPreviewAction, | ||||
|                     "name": "Preview", | ||||
|                     "cssClass": "hide-in-t-main-view icon-eye-open", | ||||
|                     "description": "Preview in large dialog", | ||||
|                     "category": [ | ||||
|                         "contextual", | ||||
|                         "view-control" | ||||
|                     ], | ||||
|                     "depends": [ | ||||
|                         "$compile", | ||||
|                         "$rootScope" | ||||
|                     ], | ||||
|                     "priority": "preferred" | ||||
|                 } | ||||
|             ], | ||||
|             "constants": [ | ||||
| @@ -475,6 +517,13 @@ define([ | ||||
|                         "action" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "context-menu", | ||||
|                     "template": contextMenuTemplate, | ||||
|                     "uses": [ | ||||
|                         "action" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "switcher", | ||||
|                     "template": switcherTemplate, | ||||
| @@ -485,6 +534,10 @@ define([ | ||||
|                 { | ||||
|                     "key": "object-inspector", | ||||
|                     "template": objectInspectorTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "mct-preview", | ||||
|                     "template": previewTemplate | ||||
|                 } | ||||
|             ], | ||||
|             "controls": [ | ||||
|   | ||||
| @@ -19,18 +19,15 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <span ng-controller="SnapshotPreviewController"  | ||||
|     class='form-control shell'> | ||||
|     <span class='field control {{structure.cssClass}}'> | ||||
|         <image  | ||||
|                class="c-ne__embed__snap-thumb" | ||||
|                src="{{imageUrl || structure.src}}" | ||||
|                ng-click="previewImage(imageUrl || structure.src)" | ||||
|                name="mctControl"> | ||||
|         </image> | ||||
|         <br> | ||||
|         <a title="Annotate" class="s-button icon-pencil" ng-click="annotateImage(ngModel, field, imageUrl || structure.src)"> | ||||
|             <span class="title-label">Annotate</span> | ||||
|         </a> | ||||
|     </span> | ||||
| </span> | ||||
| <div class="menu-element context-menu-wrapper mobile-disable-select" ng-controller="ContextMenuController"> | ||||
|     <div class="menu context-menu"> | ||||
|         <ul> | ||||
|             <li ng-repeat="menuAction in menuActions" | ||||
|                 ng-click="menuAction.perform()" | ||||
|                 title="{{menuAction.getMetadata().description}}" | ||||
|                 class="{{menuAction.getMetadata().cssClass}}"> | ||||
|                 {{menuAction.getMetadata().name}} | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										45
									
								
								platform/commonUI/general/res/templates/preview.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								platform/commonUI/general/res/templates/preview.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| <!-- | ||||
|  Open MCT, Copyright (c) 2014-2017, United States Government | ||||
|  as represented by the Administrator of the National Aeronautics and Space | ||||
|  Administration. All rights reserved. | ||||
|  | ||||
|  Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  "License"); you may not use this file except in compliance with the License. | ||||
|  You may obtain a copy of the License at | ||||
|  http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  | ||||
|  Unless required by applicable law or agreed to in writing, software | ||||
|  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  License for the specific language governing permissions and limitations | ||||
|  under the License. | ||||
|  | ||||
|  Open MCT includes source code licensed under additional open source | ||||
|  licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <div class="t-frame-inner abs t-object-type-{{ domainObject.getModel().type }}" mct-preview> | ||||
|     <div class="abs object-browse-bar l-flex-row"> | ||||
|         <div class="left flex-elem l-flex-row grows"> | ||||
|             <mct-representation | ||||
|                     key="'object-header-frame'" | ||||
|                     mct-object="domainObject" | ||||
|                     class="l-flex-row flex-elem object-header grows"> | ||||
|             </mct-representation> | ||||
|         </div> | ||||
|         <div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed"> | ||||
|             <mct-representation | ||||
|                     key="'switcher'" | ||||
|                     ng-model="representation" | ||||
|                     mct-object="domainObject"> | ||||
|             </mct-representation> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="abs object-holder"> | ||||
|         <mct-representation | ||||
|                 key="representation.selected.key" | ||||
|                 mct-object="representation.selected.key && domainObject"> | ||||
|         </mct-representation> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,10 +1,10 @@ | ||||
| <span class="l-progress-bar s-progress-bar" | ||||
|       ng-class="{ indeterminate:ngModel.progressPerc === 'unknown' }"> | ||||
|       ng-class="{ indeterminate:ngModel.unknownProgress }"> | ||||
|     <span class="progress-amt-holder"> | ||||
|         <span class="progress-amt" style="width: {{ngModel.progressPerc === 'unknown' ? 100 : ngModel.progressPerc}}%"></span> | ||||
|         <span class="progress-amt" style="width: {{ngModel.progress}}%"></span> | ||||
|     </span> | ||||
| </span> | ||||
| <div class="progress-info hint" ng-hide="ngModel.progressText === undefined"> | ||||
|     <span class="progress-amt-text" ng-show="ngModel.progressPerc !== 'unknown' && ngModel.progressPerc > 0">{{ngModel.progressPerc}}% complete. </span> | ||||
|     <span class="progress-amt-text" ng-show="ngModel.progress > 0">{{ngModel.progress}}% complete. </span> | ||||
|     {{ngModel.progressText}} | ||||
| </div> | ||||
|   | ||||
							
								
								
									
										55
									
								
								platform/commonUI/general/src/actions/MCTPreviewAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								platform/commonUI/general/src/actions/MCTPreviewAction.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2017, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         var PREVIEW_TEMPLATE = '<mct-representation key="\'mct-preview\'"' + | ||||
|                                     'class="t-rep-frame holder"' + | ||||
|                                     'mct-object="domainObject">' + | ||||
|                                 '</mct-representation>'; | ||||
|  | ||||
|         function MCTPreviewAction($compile, $rootScope, context) { | ||||
|             context = context || {}; | ||||
|             this.domainObject = context.selectedObject || context.domainObject; | ||||
|             this.$rootScope = $rootScope; | ||||
|             this.$compile = $compile; | ||||
|         } | ||||
|  | ||||
|         MCTPreviewAction.prototype.perform = function () { | ||||
|             var newScope = this.$rootScope.$new(); | ||||
|             newScope.domainObject = this.domainObject; | ||||
|  | ||||
|             this.$compile(PREVIEW_TEMPLATE)(newScope); | ||||
|         }; | ||||
|  | ||||
|         MCTPreviewAction.appliesTo = function (context) { | ||||
|             var domainObject = (context || {}).domainObject, | ||||
|                 status = domainObject.getCapability('status'); | ||||
|  | ||||
|             return !(status && status.get('editing')); | ||||
|         }; | ||||
|  | ||||
|         return MCTPreviewAction; | ||||
|     } | ||||
| ); | ||||
| @@ -50,7 +50,7 @@ define( | ||||
|             }; | ||||
|             $scope.dismiss = function (notification, $event) { | ||||
|                 $event.stopPropagation(); | ||||
|                 notification.dismiss(); | ||||
|                 notification.dismissOrMinimize(); | ||||
|             }; | ||||
|             $scope.maximize = function (notification) { | ||||
|                 if (notification.model.severity !== "info") { | ||||
| @@ -60,7 +60,7 @@ define( | ||||
|                     }; | ||||
|                     //If the notification is dismissed by the user, close | ||||
|                     // the dialog. | ||||
|                     notification.on('dismiss', function () { | ||||
|                     notification.onDismiss(function () { | ||||
|                         dialog.dismiss(); | ||||
|                     }); | ||||
|  | ||||
|   | ||||
| @@ -20,23 +20,32 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| 
 | ||||
| export default function legacyCompositionPolicyAdapter(openmct) { | ||||
|     const instantiate = this.openmct.$injector.get('instantiate'); | ||||
|     const policyService = this.openmct.$injector.get('policyService'); | ||||
| /** | ||||
|  * Module defining ContextMenuController. Created by vwoeltje on 11/17/14. | ||||
|  */ | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
| 
 | ||||
|     openmct.composition.addPolicy((parent, child) => { | ||||
|         /** | ||||
|          * Controller for the context menu. Maintains an up-to-date | ||||
|          * list of applicable actions (those from category "contextual") | ||||
|          * | ||||
|          * @memberof platform/commonUI/general | ||||
|          * @constructor | ||||
|          */ | ||||
|         function ContextMenuController($scope) { | ||||
|             // Refresh variable "menuActions" in the scope
 | ||||
|             function updateActions() { | ||||
|                 $scope.menuActions = $scope.action ? | ||||
|                     $scope.action.getActions({ category: 'contextual' }) : | ||||
|                     []; | ||||
|             } | ||||
| 
 | ||||
|         let parentId = this.openmct.objects.makeKeyString(parent.identifier); | ||||
|         let childId = this.openmct.objects.makeKeyString(child.identifier); | ||||
|             // Update using the action capability
 | ||||
|             $scope.$watch("action", updateActions); | ||||
|         } | ||||
| 
 | ||||
|         let legacyParent = instantiate(parent, parentId); | ||||
|         let legacyChild = instantiate(child, childId); | ||||
|         let result = policyService.allow( | ||||
|             'composition', | ||||
|             legacyParent, | ||||
|             legacyChild | ||||
|         ); | ||||
| 
 | ||||
|         return result; | ||||
|     }); | ||||
| } | ||||
|         return ContextMenuController; | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										64
									
								
								platform/commonUI/general/src/directives/MCTPreview.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								platform/commonUI/general/src/directives/MCTPreview.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['zepto', '../services/Overlay'], function ($, Overlay) { | ||||
|     function MCTPreview($document) { | ||||
|  | ||||
|         function link($scope, $element) { | ||||
|             var actions = $scope.domainObject.getCapability('action'), | ||||
|                 notebookAction = actions.getActions({key: 'notebook-new-entry'})[0]; | ||||
|  | ||||
|             var notebookButton = notebookAction ? | ||||
|                 [ | ||||
|                     { | ||||
|                         class: 'icon-notebook new-notebook-entry', | ||||
|                         title: 'New Notebook Entry', | ||||
|                         clickHandler: function (event) { | ||||
|                             event.stopPropagation(); | ||||
|                             notebookAction.perform(); | ||||
|                         } | ||||
|                     } | ||||
|                 ] : []; | ||||
|  | ||||
|             var overlayService = new Overlay({ | ||||
|                 $document: $document, | ||||
|                 $element: $element[0], | ||||
|                 $scope: $scope, | ||||
|                 browseBarButtons: notebookButton | ||||
|             }); | ||||
|  | ||||
|             overlayService.toggleOverlay(); | ||||
|  | ||||
|             $scope.$on('$destroy', function () { | ||||
|                 $element.remove(); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             restrict: 'A', | ||||
|             link: link | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     return MCTPreview; | ||||
|  | ||||
| }); | ||||
| @@ -82,7 +82,7 @@ define( | ||||
|             } | ||||
|             var searchPath = "?" + arr.join('&'), | ||||
|                 newTabPath = | ||||
|                     "#" + this.urlForLocation(mode, domainObject) + | ||||
|                     "index.html#" + this.urlForLocation(mode, domainObject) + | ||||
|                             searchPath; | ||||
|             return newTabPath; | ||||
|         }; | ||||
|   | ||||
| @@ -0,0 +1,60 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ["../../src/controllers/ContextMenuController"], | ||||
|     function (ContextMenuController) { | ||||
|  | ||||
|         describe("The context menu controller", function () { | ||||
|             var mockScope, | ||||
|                 mockActions, | ||||
|                 controller; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockActions = jasmine.createSpyObj("action", ["getActions"]); | ||||
|                 mockScope = jasmine.createSpyObj("$scope", ["$watch"]); | ||||
|                 controller = new ContextMenuController(mockScope); | ||||
|             }); | ||||
|  | ||||
|             it("watches scope that may change applicable actions", function () { | ||||
|                 // The action capability | ||||
|                 expect(mockScope.$watch).toHaveBeenCalledWith( | ||||
|                     "action", | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("populates the scope with grouped and ungrouped actions", function () { | ||||
|                 mockScope.action = mockActions; | ||||
|                 mockScope.parameters = { category: "test" }; | ||||
|  | ||||
|                 mockActions.getActions.and.returnValue(["a", "b", "c"]); | ||||
|  | ||||
|                 // Call the watch | ||||
|                 mockScope.$watch.calls.mostRecent().args[1](); | ||||
|  | ||||
|                 // Should have grouped and ungrouped actions in scope now | ||||
|                 expect(mockScope.menuActions.length).toEqual(3); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -36,6 +36,20 @@ define([ | ||||
|  | ||||
|     legacyRegistry.register("platform/commonUI/notification", { | ||||
|         "extensions": { | ||||
|             "constants": [ | ||||
|                 { | ||||
|                     "key": "DEFAULT_AUTO_DISMISS", | ||||
|                     "value": 3000 | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "FORCE_AUTO_DISMISS", | ||||
|                     "value": 1000 | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "MINIMIZE_TIMEOUT", | ||||
|                     "value": 300 | ||||
|                 } | ||||
|             ], | ||||
|             "templates": [ | ||||
|                 { | ||||
|                     "key": "notificationIndicatorTemplate", | ||||
| @@ -48,7 +62,7 @@ define([ | ||||
|                     "implementation": NotificationIndicatorController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "openmct", | ||||
|                         "notificationService", | ||||
|                         "dialogService" | ||||
|                     ] | ||||
|                 } | ||||
| @@ -62,11 +76,12 @@ define([ | ||||
|             "services": [ | ||||
|                 { | ||||
|                     "key": "notificationService", | ||||
|                     "implementation": function (openmct) { | ||||
|                         return new NotificationService.default(openmct); | ||||
|                     }, | ||||
|                     "implementation": NotificationService, | ||||
|                     "depends": [ | ||||
|                         "openmct" | ||||
|                         "$timeout", | ||||
|                         "topic", | ||||
|                         "DEFAULT_AUTO_DISMISS", | ||||
|                         "MINIMIZE_TIMEOUT" | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|   | ||||
| @@ -35,9 +35,9 @@ define( | ||||
|          * @param dialogService | ||||
|          * @constructor | ||||
|          */ | ||||
|         function NotificationIndicatorController($scope, openmct, dialogService) { | ||||
|             $scope.notifications = openmct.notifications.notifications; | ||||
|             $scope.highest = openmct.notifications.highest; | ||||
|         function NotificationIndicatorController($scope, notificationService, dialogService) { | ||||
|             $scope.notifications = notificationService.notifications; | ||||
|             $scope.highest = notificationService.highest; | ||||
|  | ||||
|             /** | ||||
|              * Launch a dialog showing a list of current notifications. | ||||
| @@ -48,7 +48,7 @@ define( | ||||
|                         title: "Messages", | ||||
|                         //Launch the message list dialog with the models | ||||
|                         // from the notifications | ||||
|                         messages: openmct.notifications.notifications | ||||
|                         messages: notificationService.notifications | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|   | ||||
| @@ -19,46 +19,419 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| export default class NotificationService { | ||||
|     constructor(openmct) { | ||||
|         this.openmct = openmct; | ||||
|     } | ||||
|     info(message) { | ||||
|         if (typeof message === 'string') { | ||||
|             return this.openmct.notifications.info(message); | ||||
|         } else { | ||||
|             if (message.hasOwnProperty('progress')) { | ||||
|                 return this.openmct.notifications.progress(message.title, message.progress, message.progressText); | ||||
|             } else { | ||||
|                 return this.openmct.notifications.info(message.title); | ||||
|  | ||||
| /** | ||||
|  * This bundle implements the notification service, which can be used to | ||||
|  * show banner notifications to the user. Banner notifications | ||||
|  * are used to inform users of events in a non-intrusive way. As | ||||
|  * much as possible, notifications share a model with blocking | ||||
|  * dialogs so that the same information can be provided in a dialog | ||||
|  * and then minimized to a banner notification if needed. | ||||
|  * | ||||
|  * @namespace platform/commonUI/notification | ||||
|  */ | ||||
| define( | ||||
|     ['moment'], | ||||
|     function (moment) { | ||||
|  | ||||
|         /** | ||||
|          * A representation of a user action. Options are provided to | ||||
|          * dialogs and notifications and are shown as buttons. | ||||
|          * | ||||
|          * @typedef {object} NotificationOption | ||||
|          * @property {string} label the label to appear on the button for | ||||
|          * this action | ||||
|          * @property {function} callback a callback function to be invoked | ||||
|          * when the button is clicked | ||||
|         */ | ||||
|  | ||||
|         /** | ||||
|          * A representation of a banner notification. Banner notifications | ||||
|          * are used to inform users of events in a non-intrusive way. As | ||||
|          * much as possible, notifications share a model with blocking | ||||
|          * dialogs so that the same information can be provided in a dialog | ||||
|          * and then minimized to a banner notification if needed, or vice-versa. | ||||
|          * | ||||
|          * @typedef {object} NotificationModel | ||||
|          * @property {string} title The title of the message | ||||
|          * @property {string} severity The importance of the message (one of | ||||
|          * 'info', 'alert', or 'error' where info < alert <error) | ||||
|          * @property {number} [progress] The completion status of a task | ||||
|          * represented numerically | ||||
|          * @property {boolean} [unknownProgress] a boolean indicating that the | ||||
|          * progress of the underlying task is unknown. This will result in a | ||||
|          * visually distinct progress bar. | ||||
|          * @property {boolean} [autoDismiss] If truthy, dialog will | ||||
|          * be automatically minimized or dismissed (depending on severity). | ||||
|          * Additionally, if the provided value is a number, it will be used | ||||
|          * as the delay period before being dismissed. | ||||
|          * @property {boolean} [dismissable=true] If true, notification will | ||||
|          * include an option to dismiss it completely. | ||||
|          * @property {NotificationOption} [primaryOption] the default user | ||||
|          * response to | ||||
|          * this message. Will be represented as a button with the provided | ||||
|          * label and action. May be used by banner notifications to display | ||||
|          * only the most important option to users. | ||||
|          * @property {NotificationOption[]} [options] any additional | ||||
|          * actions the user can take. Will be represented as additional buttons | ||||
|          * that may or may not be available from a banner. | ||||
|          * @see DialogModel | ||||
|          */ | ||||
|  | ||||
|         /** | ||||
|          * A wrapper object that is returned as a handle to a newly created | ||||
|          * notification. Wraps the notifications model and decorates with | ||||
|          * functions to dismiss or minimize the notification. | ||||
|          * | ||||
|          * @typedef {object} Notification | ||||
|          * @property {function} dismiss This method is added to the object | ||||
|          * returned by {@link NotificationService#notify} and can be used to | ||||
|          * dismiss this notification. Dismissing a notification will remove | ||||
|          * it completely and it will not appear in the notification indicator | ||||
|          * @property {function} minimize This method is added to the object | ||||
|          * returned by {@link NotificationService#notify} and can be used to | ||||
|          * minimize this notification. Minimizing a notification will send | ||||
|          * it to the notification indicator | ||||
|          * @property {function} dismissOrMinimize This method is added to the | ||||
|          * object returned by {@link NotificationService#notify}. It will | ||||
|          * hide the notification by either dismissing or minimizing it, | ||||
|          * depending on severity. Typically this is the method that should | ||||
|          * be used for dismissing a notification. If more control is | ||||
|          * required, then the minimize or dismiss functions can be called | ||||
|          * individually. | ||||
|          * @property {function} onDismiss Allows listening for on dismiss | ||||
|          * events. This allows cleanup etc. when the notification is dismissed. | ||||
|          */ | ||||
|  | ||||
|         /** | ||||
|          * The notification service is responsible for informing the user of | ||||
|          * events via the use of banner notifications. | ||||
|          * @memberof platform/commonUI/notification | ||||
|          * @constructor | ||||
|          * @param $timeout the Angular $timeout service | ||||
|          * @param defaultAutoDismissTimeout The period of time that an | ||||
|          * auto-dismissed message will be displayed for. | ||||
|          * @param minimizeAnimationTimeout When notifications are minimized, a brief | ||||
|          * animation is shown. This animation requires some time to execute, | ||||
|          * so a timeout is required before the notification is hidden | ||||
|          */ | ||||
|         function NotificationService($timeout, topic, defaultAutoDismissTimeout, minimizeAnimationTimeout) { | ||||
|             this.notifications = []; | ||||
|             this.$timeout = $timeout; | ||||
|             this.highest = { severity: "info" }; | ||||
|             this.AUTO_DISMISS_TIMEOUT = defaultAutoDismissTimeout; | ||||
|             this.MINIMIZE_ANIMATION_TIMEOUT = minimizeAnimationTimeout; | ||||
|             this.topic = topic; | ||||
|  | ||||
|             /* | ||||
|              * A context in which to hold the active notification and a | ||||
|              * handle to its timeout. | ||||
|              */ | ||||
|             this.active = {}; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Minimize a notification. The notification will still be available | ||||
|          * from the notification list. Typically notifications with a | ||||
|          * severity of 'info' should not be minimized, but rather | ||||
|          * dismissed. If you're not sure which is appropriate, | ||||
|          * use {@link Notification#dismissOrMinimize} | ||||
|          * | ||||
|          * @private | ||||
|          */ | ||||
|         NotificationService.prototype.minimize = function (service, notification) { | ||||
|             //Check this is a known notification | ||||
|             var index = service.notifications.indexOf(notification); | ||||
|  | ||||
|             if (service.active.timeout) { | ||||
|                 /* | ||||
|                  Method can be called manually (clicking dismiss) or | ||||
|                  automatically from an auto-timeout. this.active.timeout | ||||
|                  acts as a semaphore to prevent race conditions. Cancel any | ||||
|                  timeout in progress (for the case where a manual dismiss | ||||
|                  has shortcut an active auto-dismiss), and clear the | ||||
|                  semaphore. | ||||
|                  */ | ||||
|                 service.$timeout.cancel(service.active.timeout); | ||||
|                 delete service.active.timeout; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|             if (index >= 0) { | ||||
|                 notification.model.minimized = true; | ||||
|                 //Add a brief timeout before showing the next notification | ||||
|                 // in order to allow the minimize animation to run through. | ||||
|                 service.$timeout(function () { | ||||
|                     service.setActiveNotification(service.selectNextNotification()); | ||||
|                 }, service.MINIMIZE_ANIMATION_TIMEOUT); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Completely removes a notification. This will dismiss it from the | ||||
|          * message banner and remove it from the list of notifications. | ||||
|          * Typically only notifications with a severity of info should be | ||||
|          * dismissed. If you're not sure whether to dismiss or minimize a | ||||
|          * notification, use {@link Notification#dismissOrMinimize}. | ||||
|          * dismiss | ||||
|          * | ||||
|          * @private | ||||
|          */ | ||||
|         NotificationService.prototype.dismiss = function (service, notification) { | ||||
|             //Check this is a known notification | ||||
|             var index = service.notifications.indexOf(notification); | ||||
|  | ||||
|             if (service.active.timeout) { | ||||
|                 /* Method can be called manually (clicking dismiss) or | ||||
|                  * automatically from an auto-timeout. this.active.timeout | ||||
|                  * acts as a semaphore to prevent race conditions. Cancel any | ||||
|                  * timeout in progress (for the case where a manual dismiss | ||||
|                  * has shortcut an active auto-dismiss), and clear the | ||||
|                  * semaphore. | ||||
|                  */ | ||||
|  | ||||
|                 service.$timeout.cancel(service.active.timeout); | ||||
|                 delete service.active.timeout; | ||||
|             } | ||||
|  | ||||
|             if (index >= 0) { | ||||
|                 service.notifications.splice(index, 1); | ||||
|             } | ||||
|             service.setActiveNotification(service.selectNextNotification()); | ||||
|  | ||||
|             this.setHighestSeverity(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Depending on the severity of the notification will selectively | ||||
|          * dismiss or minimize where appropriate. | ||||
|          * | ||||
|          * @private | ||||
|          */ | ||||
|         NotificationService.prototype.dismissOrMinimize = function (notification) { | ||||
|             var model = notification.model; | ||||
|             if (model.severity === "info") { | ||||
|                 if (model.autoDismiss === false) { | ||||
|                     notification.minimize(); | ||||
|                 } else { | ||||
|                     notification.dismiss(); | ||||
|                 } | ||||
|             } else { | ||||
|                 notification.minimize(); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Returns the notification that is currently visible in the banner area | ||||
|          * @returns {Notification} | ||||
|          */ | ||||
|         NotificationService.prototype.getActiveNotification = function () { | ||||
|             return this.active.notification; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * A convenience method for info notifications. Notifications | ||||
|          * created via this method will be auto-dismissed after a default | ||||
|          * wait period unless explicitly forbidden by the caller through | ||||
|          * the {autoDismiss} property on the {NotificationModel}, in which | ||||
|          * case the notification will be minimized after the wait. | ||||
|          * @param {NotificationModel | string} message either a string for | ||||
|          * the title of the notification message, or a {@link NotificationModel} | ||||
|          * defining the options notification to display | ||||
|          * @returns {Notification} the provided notification decorated with | ||||
|          * functions to dismiss or minimize | ||||
|          */ | ||||
|         NotificationService.prototype.info = function (message) { | ||||
|             var notificationModel = typeof message === "string" ? {title: message} : message; | ||||
|             notificationModel.severity = "info"; | ||||
|             return this.notify(notificationModel); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * A convenience method for alert notifications. Notifications | ||||
|          * created via this method will will have severity of "alert" enforced | ||||
|          * @param {NotificationModel | string} message either a string for | ||||
|          * the title of the alert message with default options, or a | ||||
|          * {@link NotificationModel} defining the options notification to | ||||
|          * display | ||||
|          * @returns {Notification} the provided notification decorated with | ||||
|          * functions to dismiss or minimize | ||||
|          */ | ||||
|         NotificationService.prototype.alert = function (message) { | ||||
|             var notificationModel = typeof message === "string" ? {title: message} : message; | ||||
|             notificationModel.severity = "alert"; | ||||
|             return this.notify(notificationModel); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * A convenience method for error notifications. Notifications | ||||
|          * created via this method will will have severity of "error" enforced | ||||
|          * @param {NotificationModel | string} message either a string for | ||||
|          * the title of the error message with default options, or a | ||||
|          * {@link NotificationModel} defining the options notification to | ||||
|          * display | ||||
|          * @returns {Notification} the provided notification decorated with | ||||
|          * functions to dismiss or minimize | ||||
|          */ | ||||
|         NotificationService.prototype.error = function (message) { | ||||
|             var notificationModel = typeof message === "string" ? {title: message} : message; | ||||
|             notificationModel.severity = "error"; | ||||
|             return this.notify(notificationModel); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         NotificationService.prototype.setHighestSeverity = function () { | ||||
|             var severity = { | ||||
|                 "info": 1, | ||||
|                 "alert": 2, | ||||
|                 "error": 3 | ||||
|             }; | ||||
|             this.highest.severity = this.notifications.reduce(function (previous, notification) { | ||||
|                 if (severity[notification.model.severity] > severity[previous]) { | ||||
|                     return notification.model.severity; | ||||
|                 } else { | ||||
|                     return previous; | ||||
|                 } | ||||
|             }, "info"); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Notifies the user of an event. If there is a banner notification | ||||
|          * already active, then it will be dismissed or minimized automatically, | ||||
|          * and the provided notification displayed in its place. | ||||
|          * | ||||
|          * @param {NotificationModel} notificationModel The notification to | ||||
|          * display | ||||
|          * @returns {Notification} the provided notification decorated with | ||||
|          * functions to {@link Notification#dismiss} or {@link Notification#minimize} | ||||
|          */ | ||||
|         NotificationService.prototype.notify = function (notificationModel) { | ||||
|             var self = this, | ||||
|                 notification, | ||||
|                 activeNotification = self.active.notification, | ||||
|                 topic = this.topic(); | ||||
|  | ||||
|             notificationModel.severity = notificationModel.severity || "info"; | ||||
|             notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms'); | ||||
|  | ||||
|             notification = { | ||||
|                 model: notificationModel, | ||||
|  | ||||
|                 minimize: function () { | ||||
|                     self.minimize(self, notification); | ||||
|                 }, | ||||
|  | ||||
|                 dismiss: function () { | ||||
|                     self.dismiss(self, notification); | ||||
|                     topic.notify(); | ||||
|                 }, | ||||
|  | ||||
|                 dismissOrMinimize: function () { | ||||
|                     self.dismissOrMinimize(notification); | ||||
|                 }, | ||||
|  | ||||
|                 onDismiss: function (callback) { | ||||
|                     topic.listen(callback); | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             //Notifications support a 'dismissable' attribute. This is a | ||||
|             // convenience to support adding a 'dismiss' option to the | ||||
|             // notification for the common case of dismissing a | ||||
|             // notification. Could also be done manually by specifying an | ||||
|             // option on the model | ||||
|             if (notificationModel.dismissable !== false) { | ||||
|                 notificationModel.options = notificationModel.options || []; | ||||
|                 notificationModel.options.unshift({ | ||||
|                     label: "Dismiss", | ||||
|                     callback: function () { | ||||
|                         notification.dismiss(); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             this.notifications.push(notification); | ||||
|  | ||||
|             this.setHighestSeverity(); | ||||
|  | ||||
|             /* | ||||
|             Check if there is already an active (ie. visible) notification | ||||
|              */ | ||||
|             if (!this.active.notification) { | ||||
|                 this.setActiveNotification(notification); | ||||
|  | ||||
|             } else if (!this.active.timeout) { | ||||
|                 /* | ||||
|                  If there is already an active notification, time it out. If it's | ||||
|                  already got a timeout in progress (either because it has had | ||||
|                  timeout forced because of a queue of messages, or it had an | ||||
|                  autodismiss specified), leave it to run. Otherwise force a | ||||
|                   timeout. | ||||
|  | ||||
|                  This notification has been added to queue and will be | ||||
|                   serviced as soon as possible. | ||||
|                  */ | ||||
|                 this.active.timeout = this.$timeout(function () { | ||||
|                     activeNotification.dismissOrMinimize(); | ||||
|                 }, this.AUTO_DISMISS_TIMEOUT); | ||||
|             } | ||||
|  | ||||
|             return notification; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Used internally by the NotificationService | ||||
|          * @private | ||||
|          */ | ||||
|         NotificationService.prototype.setActiveNotification = function (notification) { | ||||
|             var shouldAutoDismiss; | ||||
|             this.active.notification = notification; | ||||
|  | ||||
|             if (!notification) { | ||||
|                 delete this.active.timeout; | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (notification.model.severity === "info") { | ||||
|                 shouldAutoDismiss = true; | ||||
|             } else { | ||||
|                 shouldAutoDismiss = notification.model.autoDismiss; | ||||
|             } | ||||
|  | ||||
|             if (shouldAutoDismiss || this.selectNextNotification()) { | ||||
|                 this.active.timeout = this.$timeout(function () { | ||||
|                     notification.dismissOrMinimize(); | ||||
|                 }, this.AUTO_DISMISS_TIMEOUT); | ||||
|             } else { | ||||
|                 delete this.active.timeout; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Used internally by the NotificationService | ||||
|          * | ||||
|          * @private | ||||
|          */ | ||||
|         NotificationService.prototype.selectNextNotification = function () { | ||||
|             var notification, | ||||
|                 i = 0; | ||||
|  | ||||
|             /* | ||||
|             Loop through the notifications queue and find the first one that | ||||
|             has not already been minimized (manually or otherwise). | ||||
|              */ | ||||
|             for (; i < this.notifications.length; i++) { | ||||
|                 notification = this.notifications[i]; | ||||
|  | ||||
|                 if (!notification.model.minimized && | ||||
|                     notification !== this.active.notification) { | ||||
|  | ||||
|                     return notification; | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return NotificationService; | ||||
|     } | ||||
|     alert(message) { | ||||
|         if (typeof message === 'string') { | ||||
|             return this.openmct.notifications.alert(message); | ||||
|         } else { | ||||
|             return this.openmct.notifications.alert(message.title); | ||||
|         } | ||||
|     } | ||||
|     error(message) { | ||||
|         if (typeof message === 'string') { | ||||
|             return this.openmct.notifications.error(message); | ||||
|         } else { | ||||
|             return this.openmct.notifications.error(message.title); | ||||
|         } | ||||
|     } | ||||
|     notify(options) { | ||||
|         switch (options.severity) { | ||||
|         case 'info': | ||||
|             return this.info(options); | ||||
|         case 'alert': | ||||
|             return this.alert(options); | ||||
|         case 'error': | ||||
|             return this.error(options); | ||||
|         } | ||||
|     } | ||||
|     getAllNotifications() { | ||||
|         return this.openmct.notifications.notifications; | ||||
|     } | ||||
| } | ||||
| ); | ||||
|   | ||||
							
								
								
									
										275
									
								
								platform/commonUI/notification/test/NotificationServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								platform/commonUI/notification/test/NotificationServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,275 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /*global describe,it,expect,beforeEach,jasmine*/ | ||||
|  | ||||
| define( | ||||
|     ['../src/NotificationService'], | ||||
|     function (NotificationService) { | ||||
|  | ||||
|         describe("The notification service ", function () { | ||||
|             var notificationService, | ||||
|                 mockTimeout, | ||||
|                 mockAutoDismiss, | ||||
|                 mockMinimizeTimeout, | ||||
|                 mockTopicFunction, | ||||
|                 mockTopicObject, | ||||
|                 infoModel, | ||||
|                 alertModel, | ||||
|                 errorModel; | ||||
|  | ||||
|             function elapseTimeout() { | ||||
|                 mockTimeout.calls.mostRecent().args[0](); | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockTimeout = jasmine.createSpy("$timeout"); | ||||
|                 mockTopicFunction = jasmine.createSpy("topic"); | ||||
|                 mockTopicObject = jasmine.createSpyObj("topicObject", ["listen", "notify"]); | ||||
|                 mockTopicFunction.and.returnValue(mockTopicObject); | ||||
|  | ||||
|                 mockAutoDismiss = mockMinimizeTimeout = 1000; | ||||
|                 notificationService = new NotificationService(mockTimeout, mockTopicFunction, mockAutoDismiss, mockMinimizeTimeout); | ||||
|  | ||||
|                 infoModel = { | ||||
|                     title: "Mock Info Notification", | ||||
|                     severity: "info" | ||||
|                 }; | ||||
|  | ||||
|                 alertModel = { | ||||
|                     title: "Mock Alert Notification", | ||||
|                     severity: "alert" | ||||
|                 }; | ||||
|  | ||||
|                 errorModel = { | ||||
|                     title: "Mock Error Notification", | ||||
|                     severity: "error" | ||||
|                 }; | ||||
|             }); | ||||
|  | ||||
|             it("notifies listeners on dismissal of notification", function () { | ||||
|                 var dismissListener = jasmine.createSpy("ondismiss"); | ||||
|                 var notification = notificationService.notify(infoModel); | ||||
|                 notification.onDismiss(dismissListener); | ||||
|                 expect(mockTopicObject.listen).toHaveBeenCalled(); | ||||
|                 notification.dismiss(); | ||||
|                 expect(mockTopicObject.notify).toHaveBeenCalled(); | ||||
|                 mockTopicObject.listen.calls.mostRecent().args[0](); | ||||
|                 expect(dismissListener).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("dismisses a notification when the notification's dismiss method is used", function () { | ||||
|                 var notification = notificationService.info(infoModel); | ||||
|                 notification.dismiss(); | ||||
|                 expect(notificationService.getActiveNotification()).toBeUndefined(); | ||||
|                 expect(notificationService.notifications.length).toEqual(0); | ||||
|             }); | ||||
|  | ||||
|             it("minimizes a notification when the notification's minimize method is used", function () { | ||||
|                 var notification = notificationService.info(infoModel); | ||||
|                 notification.minimize(); | ||||
|                 elapseTimeout(); // needed for the minimize animation timeout | ||||
|                 expect(notificationService.getActiveNotification()).toBeUndefined(); | ||||
|                 expect(notificationService.notifications.length).toEqual(1); | ||||
|                 expect(notificationService.notifications[0]).toEqual(notification); | ||||
|             }); | ||||
|  | ||||
|             describe("when receiving info notifications", function () { | ||||
|                 it("minimizes info notifications if the caller disables auto-dismiss", function () { | ||||
|                     infoModel.autoDismiss = false; | ||||
|                     var notification = notificationService.info(infoModel); | ||||
|                     elapseTimeout(); | ||||
|                     // 2nd elapse for the minimize animation timeout | ||||
|                     elapseTimeout(); | ||||
|                     expect(notificationService.getActiveNotification()).toBeUndefined(); | ||||
|                     expect(notificationService.notifications.length).toEqual(1); | ||||
|                     expect(notificationService.notifications[0]).toEqual(notification); | ||||
|                 }); | ||||
|  | ||||
|                 it("dismisses info notifications if the caller ignores auto-dismiss", function () { | ||||
|                     notificationService.info(infoModel); | ||||
|                     elapseTimeout(); | ||||
|                     expect(notificationService.getActiveNotification()).toBeUndefined(); | ||||
|                     expect(notificationService.notifications.length).toEqual(0); | ||||
|                 }); | ||||
|  | ||||
|                 it("dismisses info notifications if the caller requests auto-dismiss", function () { | ||||
|                     infoModel.autoDismiss = true; | ||||
|                     notificationService.info(infoModel); | ||||
|                     elapseTimeout(); | ||||
|                     expect(notificationService.getActiveNotification()).toBeUndefined(); | ||||
|                     expect(notificationService.notifications.length).toEqual(0); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             describe("when receiving alert notifications", function () { | ||||
|                 it("minimizes alert notifications if the caller enables auto-dismiss", function () { | ||||
|                     alertModel.autoDismiss = true; | ||||
|                     var notification = notificationService.alert(alertModel); | ||||
|                     elapseTimeout(); | ||||
|                     elapseTimeout(); | ||||
|                     expect(notificationService.getActiveNotification()).toBeUndefined(); | ||||
|                     expect(notificationService.notifications.length).toEqual(1); | ||||
|                     expect(notificationService.notifications[0]).toEqual(notification); | ||||
|                 }); | ||||
|  | ||||
|                 it("keeps alert notifications active if the caller disables auto-dismiss", function () { | ||||
|                     mockTimeout.and.callFake(function (callback, time) { | ||||
|                         callback(); | ||||
|                     }); | ||||
|                     alertModel.autoDismiss = false; | ||||
|                     var notification = notificationService.alert(alertModel); | ||||
|                     expect(notificationService.getActiveNotification()).toEqual(notification); | ||||
|                     expect(notificationService.notifications.length).toEqual(1); | ||||
|                     expect(notificationService.notifications[0]).toEqual(notification); | ||||
|                 }); | ||||
|  | ||||
|                 it("keeps alert notifications active if the caller ignores auto-dismiss", function () { | ||||
|                     mockTimeout.and.callFake(function (callback, time) { | ||||
|                         callback(); | ||||
|                     }); | ||||
|                     var notification = notificationService.alert(alertModel); | ||||
|                     expect(notificationService.getActiveNotification()).toEqual(notification); | ||||
|                     expect(notificationService.notifications.length).toEqual(1); | ||||
|                     expect(notificationService.notifications[0]).toEqual(notification); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             describe("when receiving error notifications", function () { | ||||
|                 it("minimizes error notifications if the caller enables auto-dismiss", function () { | ||||
|                     errorModel.autoDismiss = true; | ||||
|                     var notification = notificationService.error(errorModel); | ||||
|                     elapseTimeout(); | ||||
|                     elapseTimeout(); | ||||
|                     expect(notificationService.getActiveNotification()).toBeUndefined(); | ||||
|                     expect(notificationService.notifications.length).toEqual(1); | ||||
|                     expect(notificationService.notifications[0]).toEqual(notification); | ||||
|                 }); | ||||
|  | ||||
|                 it("keeps error notifications active if the caller disables auto-dismiss", function () { | ||||
|                     mockTimeout.and.callFake(function (callback, time) { | ||||
|                         callback(); | ||||
|                     }); | ||||
|                     errorModel.autoDismiss = false; | ||||
|                     var notification = notificationService.error(errorModel); | ||||
|                     expect(notificationService.getActiveNotification()).toEqual(notification); | ||||
|                     expect(notificationService.notifications.length).toEqual(1); | ||||
|                     expect(notificationService.notifications[0]).toEqual(notification); | ||||
|                 }); | ||||
|  | ||||
|                 it("keeps error notifications active if the caller ignores auto-dismiss", function () { | ||||
|                     mockTimeout.and.callFake(function (callback, time) { | ||||
|                         callback(); | ||||
|                     }); | ||||
|                     var notification = notificationService.error(errorModel); | ||||
|                     expect(notificationService.getActiveNotification()).toEqual(notification); | ||||
|                     expect(notificationService.notifications.length).toEqual(1); | ||||
|                     expect(notificationService.notifications[0]).toEqual(notification); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             describe("when called with multiple notifications", function () { | ||||
|                 it("auto-dismisses the previously active notification, making the new notification active", function () { | ||||
|                     var activeNotification; | ||||
|                     infoModel.autoDismiss = false; | ||||
|                     //First pre-load with a info message | ||||
|                     notificationService.notify(infoModel); | ||||
|                     activeNotification = notificationService.getActiveNotification(); | ||||
|                     //Initially expect the active notification to be info | ||||
|                     expect(activeNotification.model).toBe(infoModel); | ||||
|                     //Then notify of an error | ||||
|                     notificationService.notify(errorModel); | ||||
|                     //But it should be auto-dismissed and replaced with the | ||||
|                     // error notification | ||||
|                     elapseTimeout(); | ||||
|                     //Two timeouts, one is to force minimization after | ||||
|                     // displaying the message for a minimum period, the | ||||
|                     // second is to allow minimization animation to take place. | ||||
|                     elapseTimeout(); | ||||
|                     activeNotification = notificationService.getActiveNotification(); | ||||
|                     expect(activeNotification.model).toBe(errorModel); | ||||
|                 }); | ||||
|  | ||||
|                 it("auto-minimizes an active error notification", function () { | ||||
|                     var activeNotification; | ||||
|                     //First pre-load with an error message | ||||
|                     notificationService.notify(errorModel); | ||||
|                     //Then notify of info | ||||
|                     notificationService.notify(infoModel); | ||||
|                     expect(notificationService.notifications.length).toEqual(2); | ||||
|                     //Mock the auto-minimize | ||||
|                     elapseTimeout(); | ||||
|                     //Two timeouts, one is to force minimization after | ||||
|                     // displaying the message for a minimum period, the | ||||
|                     // second is to allow minimization animation to take place. | ||||
|                     elapseTimeout(); | ||||
|                     //Previous error message should be minimized, not | ||||
|                     // dismissed | ||||
|                     expect(notificationService.notifications.length).toEqual(2); | ||||
|                     activeNotification = notificationService.getActiveNotification(); | ||||
|                     expect(activeNotification.model).toBe(infoModel); | ||||
|                     expect(errorModel.minimized).toEqual(true); | ||||
|                 }); | ||||
|  | ||||
|                 it("auto-minimizes errors when a number of them arrive in short succession", function () { | ||||
|                     var activeNotification, | ||||
|                         error2 = { | ||||
|                             title: "Second Mock Error Notification", | ||||
|                             severity: "error" | ||||
|                         }, | ||||
|                         error3 = { | ||||
|                             title: "Third Mock Error Notification", | ||||
|                             severity: "error" | ||||
|                         }; | ||||
|  | ||||
|                     //First pre-load with a info message | ||||
|                     notificationService.notify(errorModel); | ||||
|                     //Then notify of a third error | ||||
|                     notificationService.notify(error2); | ||||
|                     notificationService.notify(error3); | ||||
|                     expect(notificationService.notifications.length).toEqual(3); | ||||
|                     //Mock the auto-minimize | ||||
|                     elapseTimeout(); | ||||
|                     //Two timeouts, one is to force minimization after | ||||
|                     // displaying the message for a minimum period, the | ||||
|                     // second is to allow minimization animation to take place. | ||||
|                     elapseTimeout(); | ||||
|                     //Previous error message should be minimized, not | ||||
|                     // dismissed | ||||
|                     expect(notificationService.notifications.length).toEqual(3); | ||||
|                     activeNotification = notificationService.getActiveNotification(); | ||||
|                     expect(activeNotification.model).toBe(error2); | ||||
|                     expect(errorModel.minimized).toEqual(true); | ||||
|  | ||||
|                     //Mock the second auto-minimize | ||||
|                     elapseTimeout(); | ||||
|                     //Two timeouts, one is to force minimization after | ||||
|                     // displaying the message for a minimum period, the | ||||
|                     // second is to allow minimization animation to take place. | ||||
|                     elapseTimeout(); | ||||
|                     activeNotification = notificationService.getActiveNotification(); | ||||
|                     expect(activeNotification.model).toBe(error3); | ||||
|                     expect(error2.minimized).toEqual(true); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -58,8 +58,7 @@ define([ | ||||
|                     "category": "action", | ||||
|                     "implementation": ComposeActionPolicy, | ||||
|                     "depends": [ | ||||
|                         "$injector", | ||||
|                         "openmct" | ||||
|                         "$injector" | ||||
|                     ], | ||||
|                     "message": "Objects of this type cannot contain objects of that type." | ||||
|                 }, | ||||
|   | ||||
| @@ -36,11 +36,10 @@ define( | ||||
|          * @memberof platform/containment | ||||
|          * @implements {Policy.<Action, ActionContext>} | ||||
|          */ | ||||
|         function ComposeActionPolicy($injector, openmct) { | ||||
|         function ComposeActionPolicy($injector) { | ||||
|             this.getPolicyService = function () { | ||||
|                 return $injector.get('policyService'); | ||||
|             }; | ||||
|             this.openmct = openmct; | ||||
|         } | ||||
|  | ||||
|         ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) { | ||||
| @@ -50,8 +49,11 @@ define( | ||||
|  | ||||
|             // ...and delegate to the composition policy | ||||
|             return containerObject.getId() !== selectedObject.getId() && | ||||
|                 this.openmct.composition.checkPolicy(containerObject.useCapability('adapter'), | ||||
|                     selectedObject.useCapability('adapter')); | ||||
|                 this.policyService.allow( | ||||
|                     'composition', | ||||
|                     containerObject, | ||||
|                     selectedObject | ||||
|                 ); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -170,7 +170,7 @@ define([ | ||||
|                     "description": "Provides a service for moving objects", | ||||
|                     "implementation": MoveService, | ||||
|                     "depends": [ | ||||
|                         "openmct", | ||||
|                         "policyService", | ||||
|                         "linkService", | ||||
|                         "$q" | ||||
|                     ] | ||||
| @@ -181,7 +181,7 @@ define([ | ||||
|                     "description": "Provides a service for linking objects", | ||||
|                     "implementation": LinkService, | ||||
|                     "depends": [ | ||||
|                         "openmct" | ||||
|                         "policyService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
| @@ -192,7 +192,7 @@ define([ | ||||
|                     "depends": [ | ||||
|                         "$q", | ||||
|                         "policyService", | ||||
|                         "openmct" | ||||
|                         "now" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|   | ||||
| @@ -33,10 +33,9 @@ define( | ||||
|          * @memberof platform/entanglement | ||||
|          * @implements {platform/entanglement.AbstractComposeService} | ||||
|          */ | ||||
|         function CopyService($q, policyService, openmct) { | ||||
|         function CopyService($q, policyService) { | ||||
|             this.$q = $q; | ||||
|             this.policyService = policyService; | ||||
|             this.openmct = openmct; | ||||
|         } | ||||
|  | ||||
|         CopyService.prototype.validate = function (object, parentCandidate) { | ||||
| @@ -46,7 +45,11 @@ define( | ||||
|             if (parentCandidate.getId() === object.getId()) { | ||||
|                 return false; | ||||
|             } | ||||
|             return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter')); | ||||
|             return this.policyService.allow( | ||||
|                 "composition", | ||||
|                 parentCandidate, | ||||
|                 object | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -32,8 +32,8 @@ define( | ||||
|          * @memberof platform/entanglement | ||||
|          * @implements {platform/entanglement.AbstractComposeService} | ||||
|          */ | ||||
|         function LinkService(openmct) { | ||||
|             this.openmct = openmct; | ||||
|         function LinkService(policyService) { | ||||
|             this.policyService = policyService; | ||||
|         } | ||||
|  | ||||
|         LinkService.prototype.validate = function (object, parentCandidate) { | ||||
| @@ -49,7 +49,11 @@ define( | ||||
|             if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) { | ||||
|                 return false; | ||||
|             } | ||||
|             return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter')); | ||||
|             return this.policyService.allow( | ||||
|                 "composition", | ||||
|                 parentCandidate, | ||||
|                 object | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|         LinkService.prototype.perform = function (object, parentObject) { | ||||
|   | ||||
| @@ -31,8 +31,8 @@ define( | ||||
|          * @memberof platform/entanglement | ||||
|          * @implements {platform/entanglement.AbstractComposeService} | ||||
|          */ | ||||
|         function MoveService(openmct, linkService) { | ||||
|             this.openmct = openmct; | ||||
|         function MoveService(policyService, linkService) { | ||||
|             this.policyService = policyService; | ||||
|             this.linkService = linkService; | ||||
|         } | ||||
|  | ||||
| @@ -53,9 +53,10 @@ define( | ||||
|             if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) { | ||||
|                 return false; | ||||
|             } | ||||
|             return this.openmct.composition.checkPolicy( | ||||
|                 parentCandidate.useCapability('adapter'), | ||||
|                 object.useCapability('adapter') | ||||
|             return this.policyService.allow( | ||||
|                 "composition", | ||||
|                 parentCandidate, | ||||
|                 object | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -32,7 +32,7 @@ define([ | ||||
|                 { | ||||
|                     key: "exportService", | ||||
|                     implementation: function () { | ||||
|                         return new ExportService(saveAs.saveAs); | ||||
|                         return new ExportService(saveAs); | ||||
|                     } | ||||
|                 } | ||||
|             ], | ||||
|   | ||||
| @@ -19,14 +19,16 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <div class="c-clock l-time-display" ng-controller="ClockController as clock"> | ||||
| 	<div class="c-clock__timezone"> | ||||
| 		{{clock.zone()}} | ||||
| 	</div> | ||||
| 	<div class="c-clock__value"> | ||||
| 		{{clock.text()}} | ||||
| 	</div> | ||||
| 	<div class="c-clock__ampm"> | ||||
| 		{{clock.ampm()}} | ||||
| <div class="l-time-display l-digital l-clock s-clock" ng-controller="ClockController as clock"> | ||||
| 	<div class="l-elem-wrapper"> | ||||
| 	    <span class="l-elem timezone"> | ||||
| 	        {{clock.zone()}} | ||||
| 	    </span> | ||||
| 	    <span class="l-elem value active"> | ||||
| 	        {{clock.text()}} | ||||
| 	    </span> | ||||
| 	    <span class="l-elem ampm"> | ||||
| 	        {{clock.ampm()}} | ||||
| 	    </span> | ||||
| 	</div> | ||||
| </div> | ||||
|   | ||||
| @@ -19,19 +19,21 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <div class="c-timer is-{{timer.timerState}}" ng-controller="TimerController as timer"> | ||||
|     <div class="c-timer__controls"> | ||||
|         <button ng-click="timer.clickStopButton()" | ||||
|                 ng-hide="timer.timerState == 'stopped'" | ||||
|                 title="Reset" | ||||
|                 class="c-timer__ctrl-reset c-click-icon c-click-icon--major icon-reset"></button> | ||||
|         <button ng-click="timer.clickButton()" | ||||
|                 title="{{timer.buttonText()}}" | ||||
|                 class="c-timer__ctrl-pause-play c-click-icon c-click-icon--major {{timer.buttonCssClass()}}"></button> | ||||
|     </div> | ||||
|     <div class="c-timer__direction {{timer.signClass()}}" | ||||
|         ng-hide="!timer.signClass()"></div> | ||||
| 	<div class="c-timer__value">{{timer.text() || "--:--:--"}} | ||||
| <div class="l-time-display l-digital l-timer s-timer s-state-{{timer.timerState}}" ng-controller="TimerController as timer"> | ||||
| 	<div class="l-elem-wrapper l-flex-row"> | ||||
|         <div class="l-elem-wrapper l-flex-row controls"> | ||||
|             <a ng-click="timer.clickStopButton()" | ||||
|                title="Stop" | ||||
|                class="flex-elem s-icon-button t-btn-stop icon-box"></a> | ||||
|             <a ng-click="timer.clickButton()" | ||||
|                title="{{timer.buttonText()}}" | ||||
|                class="flex-elem s-icon-button t-btn-pauseplay {{timer.buttonCssClass()}}"></a> | ||||
|         </div> | ||||
| 	    <span class="flex-elem l-value {{timer.signClass()}}"> | ||||
| 	        <span class="value" | ||||
| 		        ng-class="{ active:timer.text() }">{{timer.text() || "--:--:--"}} | ||||
|             </span> | ||||
| 	    </span> | ||||
| 	    <span ng-controller="RefreshingController"></span> | ||||
| 	</div> | ||||
| 	<span class="c-timer__ng-controller u-contents" ng-controller="RefreshingController"></span> | ||||
| </div> | ||||
|   | ||||
							
								
								
									
										43
									
								
								platform/features/conductor/compatibility/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								platform/features/conductor/compatibility/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     "./src/ConductorRepresenter", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     ConductorRepresenter, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
|     legacyRegistry.register("platform/features/conductor/compatibility", { | ||||
|         "extensions": { | ||||
|             "representers": [ | ||||
|                 { | ||||
|                     "implementation": ConductorRepresenter, | ||||
|                     "depends": [ | ||||
|                         "openmct" | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Representer that provides a compatibility layer between the new | ||||
|          * time conductor and existing representations / views. Listens to | ||||
|          * the v2 time conductor API and generates v1 style events using the | ||||
|          * Angular event bus. This is transitional code code and will be | ||||
|          * removed. | ||||
|          * | ||||
|          * Deprecated immediately as this is temporary code | ||||
|          * | ||||
|          * @deprecated | ||||
|          * @constructor | ||||
|          */ | ||||
|         function ConductorRepresenter( | ||||
|             openmct, | ||||
|             scope, | ||||
|             element | ||||
|         ) { | ||||
|             this.timeAPI = openmct.time; | ||||
|             this.scope = scope; | ||||
|             this.element = element; | ||||
|  | ||||
|             this.boundsListener = this.boundsListener.bind(this); | ||||
|             this.timeSystemListener = this.timeSystemListener.bind(this); | ||||
|             this.followListener = this.followListener.bind(this); | ||||
|         } | ||||
|  | ||||
|         ConductorRepresenter.prototype.boundsListener = function (bounds) { | ||||
|             var timeSystem = this.timeAPI.timeSystem(); | ||||
|             this.scope.$broadcast('telemetry:display:bounds', { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end, | ||||
|                 domain: timeSystem.key | ||||
|             }, this.timeAPI.clock() !== undefined); | ||||
|         }; | ||||
|  | ||||
|         ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             this.scope.$broadcast('telemetry:display:bounds', { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end, | ||||
|                 domain: timeSystem.key | ||||
|             }, this.timeAPI.clock() !== undefined); | ||||
|         }; | ||||
|  | ||||
|         ConductorRepresenter.prototype.followListener = function () { | ||||
|             this.boundsListener(this.timeAPI.bounds()); | ||||
|         }; | ||||
|  | ||||
|         // Handle a specific representation of a specific domain object | ||||
|         ConductorRepresenter.prototype.represent = function represent(representation) { | ||||
|             if (representation.key === 'browse-object') { | ||||
|                 this.destroy(); | ||||
|  | ||||
|                 this.timeAPI.on("bounds", this.boundsListener); | ||||
|                 this.timeAPI.on("timeSystem", this.timeSystemListener); | ||||
|                 this.timeAPI.on("follow", this.followListener); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         ConductorRepresenter.prototype.destroy = function destroy() { | ||||
|             this.timeAPI.off("bounds", this.boundsListener); | ||||
|             this.timeAPI.off("timeSystem", this.timeSystemListener); | ||||
|             this.timeAPI.off("follow", this.followListener); | ||||
|         }; | ||||
|  | ||||
|         return ConductorRepresenter; | ||||
|     } | ||||
| ); | ||||
|  | ||||
							
								
								
									
										148
									
								
								platform/features/conductor/core/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								platform/features/conductor/core/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     "./src/ui/TimeConductorController", | ||||
|     "./src/ui/ConductorAxisController", | ||||
|     "./src/ui/ConductorTOIController", | ||||
|     "./src/ui/ConductorTOIDirective", | ||||
|     "./src/ui/TimeOfInterestController", | ||||
|     "./src/ui/ConductorAxisDirective", | ||||
|     "./src/ui/NumberFormat", | ||||
|     "./src/ui/StringFormat", | ||||
|     "./res/templates/time-conductor.html", | ||||
|     "./res/templates/mode-selector/mode-selector.html", | ||||
|     "./res/templates/mode-selector/mode-menu.html", | ||||
|     "./res/templates/time-of-interest.html", | ||||
|     "legacyRegistry" | ||||
| ], function ( | ||||
|     TimeConductorController, | ||||
|     ConductorAxisController, | ||||
|     ConductorTOIController, | ||||
|     ConductorTOIDirective, | ||||
|     TimeOfInterestController, | ||||
|     ConductorAxisDirective, | ||||
|     NumberFormat, | ||||
|     StringFormat, | ||||
|     timeConductorTemplate, | ||||
|     modeSelectorTemplate, | ||||
|     modeMenuTemplate, | ||||
|     timeOfInterest, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
|     legacyRegistry.register("platform/features/conductor/core", { | ||||
|         "extensions": { | ||||
|             "controllers": [ | ||||
|                 { | ||||
|                     "key": "TimeConductorController", | ||||
|                     "implementation": TimeConductorController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "$window", | ||||
|                         "openmct", | ||||
|                         "formatService", | ||||
|                         "CONDUCTOR_CONFIG" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "ConductorTOIController", | ||||
|                     "implementation": ConductorTOIController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "openmct", | ||||
|                         "formatService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "TimeOfInterestController", | ||||
|                     "implementation": TimeOfInterestController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "openmct", | ||||
|                         "formatService" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "directives": [ | ||||
|                 { | ||||
|                     "key": "conductorAxis", | ||||
|                     "implementation": ConductorAxisDirective, | ||||
|                     "depends": [ | ||||
|                         "openmct", | ||||
|                         "formatService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "conductorToi", | ||||
|                     "implementation": ConductorTOIDirective | ||||
|                 } | ||||
|             ], | ||||
|             "templates": [ | ||||
|                 { | ||||
|                     "key": "conductor", | ||||
|                     "template": timeConductorTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "mode-menu", | ||||
|                     "template": modeMenuTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "mode-selector", | ||||
|                     "template": modeSelectorTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "time-of-interest", | ||||
|                     "template": timeOfInterest | ||||
|                 } | ||||
|             ], | ||||
|             "representations": [ | ||||
|                 { | ||||
|                     "key": "time-conductor", | ||||
|                     "template": timeConductorTemplate | ||||
|                 } | ||||
|             ], | ||||
|             "licenses": [ | ||||
|                 { | ||||
|                     "name": "D3: Data-Driven Documents", | ||||
|                     "version": "4.1.0", | ||||
|                     "author": "Mike Bostock", | ||||
|                     "description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.", | ||||
|                     "website": "https://d3js.org/", | ||||
|                     "copyright": "Copyright 2010-2016 Mike Bostock", | ||||
|                     "license": "BSD-3-Clause", | ||||
|                     "link": "https://github.com/d3/d3/blob/master/LICENSE" | ||||
|                 } | ||||
|             ], | ||||
|             "formats": [ | ||||
|                 { | ||||
|                     "key": "number", | ||||
|                     "implementation": NumberFormat | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "string", | ||||
|                     "implementation": StringFormat | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -0,0 +1,46 @@ | ||||
| <!-- | ||||
|  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 class="w-menu"> | ||||
|     <div class="col menu-items"> | ||||
|         <ul> | ||||
|             <li ng-repeat="metadata in ngModel.options" | ||||
|                 ng-click="ngModel.select(metadata)"> | ||||
|                 <a ng-mouseover="ngModel.activeMetadata = metadata" | ||||
|                    ng-mouseleave="ngModel.activeMetadata = undefined" | ||||
|                    class="menu-item-a {{metadata.cssClass}}"> | ||||
|                     {{metadata.name}} | ||||
|                 </a> | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
|     <div class="col menu-item-description"> | ||||
|         <div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div> | ||||
|         <div class="w-title-desc"> | ||||
|             <div class="desc-area title"> | ||||
|                 {{ngModel.activeMetadata.name}} | ||||
|             </div> | ||||
|             <div class="desc-area description"> | ||||
|                 {{ngModel.activeMetadata.description}} | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -0,0 +1,33 @@ | ||||
| <!-- | ||||
|  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. | ||||
| --> | ||||
| <span ng-controller="ClickAwayController as modeController"> | ||||
|     <div class="s-menu-button" | ||||
|          ng-click="modeController.toggle()"> | ||||
| 		<span class="title-label">{{ngModel.selected.name}}</span> | ||||
|     </div> | ||||
|     <div class="menu super-menu mini l-mode-selector-menu" | ||||
|          ng-show="modeController.isActive()"> | ||||
|         <mct-include key="'mode-menu'" | ||||
|                      ng-model="ngModel"> | ||||
|         </mct-include> | ||||
|     </div> | ||||
| </span> | ||||
| @@ -0,0 +1,117 @@ | ||||
| <!-- Parent holder for time conductor. follow-mode | fixed-mode --> | ||||
| <div ng-controller="TimeConductorController as tcController" | ||||
|     class="holder grows flex-elem l-flex-row l-time-conductor {{tcController.isFixed ? 'fixed-mode' : 'realtime-mode'}} {{timeSystemModel.selected.metadata.key}}-time-system" | ||||
|         ng-class="{'status-panning': tcController.panning}"> | ||||
|     <div class="flex-elem holder time-conductor-icon"> | ||||
|         <div class="hand-little"></div> | ||||
|         <div class="hand-big"></div> | ||||
|     </div> | ||||
|  | ||||
|     <div class="flex-elem holder grows l-flex-col l-time-conductor-inner"> | ||||
|         <!-- Holds inputs and ticks --> | ||||
|         <div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin"> | ||||
|             <form class="l-time-conductor-inputs-holder" | ||||
|                   ng-submit="tcController.isFixed ? tcController.setBoundsFromView(boundsModel) : tcController.setOffsetsFromView(boundsModel)"> | ||||
|                 <span class="l-time-range-w start-w"> | ||||
|                     <span class="l-time-conductor-inputs"> | ||||
|                         <span class="l-time-range-input-w start-date"> | ||||
|                             <span class="title"></span> | ||||
|                             <mct-control key="'datetime-field'" | ||||
|                                          structure="{ | ||||
|                                              format: timeSystemModel.format, | ||||
|                                              validate: tcController.validation.validateStart | ||||
|                                          }" | ||||
|                                          ng-model="boundsModel" | ||||
|                                          ng-blur="tcController.setBoundsFromView(boundsModel)" | ||||
|                                          field="'start'" | ||||
|                                          class="time-range-input"> | ||||
|                             </mct-control> | ||||
|                         </span> | ||||
|                             <span class="l-time-range-input-w time-delta start-delta" | ||||
|                               ng-class="{'hide':tcController.isFixed}"> | ||||
|                                 - | ||||
|                             <mct-control key="'datetime-field'" | ||||
|                                          structure="{ | ||||
|                                             format: timeSystemModel.durationFormat, | ||||
|                                             validate: tcController.validation.validateStartOffset | ||||
|                                          }" | ||||
|                                          ng-model="boundsModel" | ||||
|                                          ng-blur="tcController.setOffsetsFromView(boundsModel)" | ||||
|                                          field="'startOffset'" | ||||
|                                          class="s-input-inline hrs-min-input"> | ||||
|                             </mct-control> | ||||
|                         </span> | ||||
|                     </span> | ||||
|                 </span> | ||||
|                 <span class="l-time-range-w end-w"> | ||||
|                     <span class="l-time-conductor-inputs"> | ||||
|                         <span class="l-time-range-input-w end-date" | ||||
|                               ng-controller="ToggleController as t2"> | ||||
|                             <span class="title"></span> | ||||
|                             <mct-control key="'datetime-field'" | ||||
|                                          structure="{ | ||||
|                                              format: timeSystemModel.format, | ||||
|                                              validate: tcController.validation.validateEnd | ||||
|                                          }" | ||||
|                                          ng-model="boundsModel" | ||||
|                                          ng-blur="tcController.setBoundsFromView(boundsModel)" | ||||
|                                          ng-disabled="!tcController.isFixed" | ||||
|                                          field="'end'" | ||||
|                                          class="time-range-input"> | ||||
|                             </mct-control> | ||||
|                         </span> | ||||
|                         <span class="l-time-range-input-w time-delta end-delta" | ||||
|                               ng-class="{'hide': tcController.isFixed}"> | ||||
|                                 + | ||||
|                             <mct-control key="'datetime-field'" | ||||
|                                          structure="{ | ||||
|                                             format: timeSystemModel.durationFormat, | ||||
|                                             validate: tcController.validation.validateEndOffset | ||||
|                                          }" | ||||
|                                          ng-model="boundsModel" | ||||
|                                          ng-blur="tcController.setOffsetsFromView(boundsModel)" | ||||
|                                          field="'endOffset'" | ||||
|                                          class="s-input-inline hrs-min-input"> | ||||
|                             </mct-control> | ||||
|                         </span> | ||||
|                     </span> | ||||
|                 </span> | ||||
|  | ||||
|                 <input type="submit" class="invisible"> | ||||
|             </form> | ||||
|             <conductor-axis class="mobile-hide" view-service="tcController.conductorViewService"></conductor-axis> | ||||
|         </div> | ||||
|  | ||||
|         <!-- Holds time system and session selectors, and zoom control --> | ||||
|         <div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem"> | ||||
|             <mct-include | ||||
|                 key="'mode-selector'" | ||||
|                 ng-model="tcController.menu" | ||||
|                 class="holder flex-elem menus-up mode-selector"> | ||||
|             </mct-include> | ||||
|             <mct-control | ||||
|                     key="'menu-button'" | ||||
|                     class="holder flex-elem menus-up time-system" | ||||
|                     structure="{ | ||||
|                         text: timeSystemModel.selected.name, | ||||
|                         click: tcController.setTimeSystemFromView, | ||||
|                         options: tcController.timeSystemsForClocks[tcController.menu.selected.key] | ||||
|                     }"> | ||||
|             </mct-control> | ||||
|             <!-- Zoom control --> | ||||
|             <div ng-if="tcController.zoom" | ||||
|                  class="l-time-conductor-zoom-w grows flex-elem l-flex-row"> | ||||
|                 {{currentZoom}} | ||||
|                 <span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span> | ||||
|                 <input class="time-conductor-zoom flex-elem" type="range" | ||||
|                        ng-model="tcController.currentZoom" | ||||
|                        ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)" | ||||
|                        ng-change="tcController.onZoom(tcController.currentZoom)" | ||||
|                        min="0.01" | ||||
|                        step="0.01" | ||||
|                        max="0.99" /> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|     </div> | ||||
| </div> | ||||
| @@ -0,0 +1,12 @@ | ||||
| <div class="abs angular-controller" | ||||
|      ng-controller="TimeOfInterestController as toi"> | ||||
|     <div class="l-flex-row l-toi"> | ||||
|         <span class="flex-elem l-flex-row l-toi-buttons"> | ||||
|             <a class="flex-elem t-button-resync icon-button" title="Re-sync Time of Interest" | ||||
|                ng-click="toi.resync()"></a> | ||||
|             <a class="flex-elem t-button-unpin icon-button" title="Unset Time of Interest" | ||||
|                ng-click="toi.dismiss()"></a> | ||||
|         </span> | ||||
|         <span class="flex-elem l-toi-val">{{toi.toiText}}</span> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -0,0 +1,236 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         "d3-selection", | ||||
|         "d3-scale", | ||||
|         "d3-axis" | ||||
|     ], | ||||
|     function (d3Selection, d3Scale, d3Axis) { | ||||
|         var PADDING = 1; | ||||
|  | ||||
|         /** | ||||
|          * Controller that renders a horizontal time scale spanning the current bounds defined in the time conductor. | ||||
|          * Used by the mct-conductor-axis directive | ||||
|          * @constructor | ||||
|          */ | ||||
|         function ConductorAxisController(openmct, formatService, scope, element) { | ||||
|             // Dependencies | ||||
|             this.formatService = formatService; | ||||
|             this.timeAPI = openmct.time; | ||||
|  | ||||
|             this.scope = scope; | ||||
|  | ||||
|             this.bounds = this.timeAPI.bounds(); | ||||
|  | ||||
|             //Bind all class functions to 'this' | ||||
|             Object.keys(ConductorAxisController.prototype).filter(function (key) { | ||||
|                 return typeof ConductorAxisController.prototype[key] === 'function'; | ||||
|             }).forEach(function (key) { | ||||
|                 this[key] = ConductorAxisController.prototype[key].bind(this); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             this.initialize(element); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorAxisController.prototype.destroy = function () { | ||||
|             this.timeAPI.off('timeSystem', this.changeTimeSystem); | ||||
|             this.timeAPI.off('bounds', this.changeBounds); | ||||
|             this.viewService.off("zoom", this.onZoom); | ||||
|             this.viewService.off("zoom-stop", this.onZoomStop); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorAxisController.prototype.initialize = function (element) { | ||||
|             this.target = element[0].firstChild; | ||||
|             var height = this.target.offsetHeight; | ||||
|             var vis = d3Selection.select(this.target) | ||||
|                 .append("svg:svg") | ||||
|                 .attr("width", "100%") | ||||
|                 .attr("height", height); | ||||
|  | ||||
|             this.xAxis = d3Axis.axisTop(); | ||||
|  | ||||
|             // draw x axis with labels. CSS is used to position them. | ||||
|             this.axisElement = vis.append("g"); | ||||
|  | ||||
|             if (this.timeAPI.timeSystem() !== undefined) { | ||||
|                 this.changeTimeSystem(this.timeAPI.timeSystem()); | ||||
|                 this.setScale(); | ||||
|             } | ||||
|  | ||||
|             //Respond to changes in conductor | ||||
|             this.timeAPI.on("timeSystem", this.changeTimeSystem); | ||||
|             this.timeAPI.on("bounds", this.changeBounds); | ||||
|  | ||||
|             this.scope.$on("$destroy", this.destroy); | ||||
|  | ||||
|             this.viewService.on("zoom", this.onZoom); | ||||
|             this.viewService.on("zoom-stop", this.onZoomStop); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorAxisController.prototype.changeBounds = function (bounds) { | ||||
|             this.bounds = bounds; | ||||
|             if (!this.zooming) { | ||||
|                 this.setScale(); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Set the scale of the axis, based on current conductor bounds. | ||||
|          */ | ||||
|         ConductorAxisController.prototype.setScale = function () { | ||||
|             var width = this.target.offsetWidth; | ||||
|             var timeSystem = this.timeAPI.timeSystem(); | ||||
|             var bounds = this.bounds; | ||||
|  | ||||
|             if (timeSystem.isUTCBased) { | ||||
|                 this.xScale = this.xScale || d3Scale.scaleUtc(); | ||||
|                 this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]); | ||||
|             } else { | ||||
|                 this.xScale = this.xScale || d3Scale.scaleLinear(); | ||||
|                 this.xScale.domain([bounds.start, bounds.end]); | ||||
|             } | ||||
|  | ||||
|             this.xAxis.scale(this.xScale); | ||||
|  | ||||
|             this.xScale.range([PADDING, width - PADDING * 2]); | ||||
|             this.axisElement.call(this.xAxis); | ||||
|  | ||||
|             this.msPerPixel = (bounds.end - bounds.start) / width; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When the time system changes, update the scale and formatter used for showing times. | ||||
|          * @param timeSystem | ||||
|          */ | ||||
|         ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) { | ||||
|             var key = timeSystem.timeFormat; | ||||
|             if (key !== undefined) { | ||||
|                 var format = this.formatService.getFormat(key); | ||||
|                 var bounds = this.timeAPI.bounds(); | ||||
|  | ||||
|                 //The D3 scale used depends on the type of time system as d3 | ||||
|                 // supports UTC out of the box. | ||||
|                 if (timeSystem.isUTCBased) { | ||||
|                     this.xScale = d3Scale.scaleUtc(); | ||||
|                 } else { | ||||
|                     this.xScale = d3Scale.scaleLinear(); | ||||
|                 } | ||||
|  | ||||
|                 this.xAxis.scale(this.xScale); | ||||
|  | ||||
|                 //Define a custom format function | ||||
|                 this.xAxis.tickFormat(function (tickValue) { | ||||
|                     // Normalize date representations to numbers | ||||
|                     if (tickValue instanceof Date) { | ||||
|                         tickValue = tickValue.getTime(); | ||||
|                     } | ||||
|                     return format.format(tickValue, { | ||||
|                         min: bounds.start, | ||||
|                         max: bounds.end | ||||
|                     }); | ||||
|                 }); | ||||
|                 this.axisElement.call(this.xAxis); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * The user has stopped panning the time conductor scale element. | ||||
|          * @event panStop | ||||
|          */ | ||||
|         /** | ||||
|          * Called on release of mouse button after dragging the scale left or right. | ||||
|          * @fires platform.features.conductor.ConductorAxisController~panStop | ||||
|          */ | ||||
|         ConductorAxisController.prototype.panStop = function () { | ||||
|             //resync view bounds with time conductor bounds | ||||
|             this.viewService.emit("pan-stop"); | ||||
|             this.timeAPI.bounds(this.bounds); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Rescales the axis when the user zooms. Although zoom ultimately results in a bounds change once the user | ||||
|          * releases the zoom slider, dragging the slider will not immediately change the conductor bounds. It will | ||||
|          * however immediately update the scale and the bounds displayed in the UI. | ||||
|          * @private | ||||
|          * @param {ZoomLevel} | ||||
|          */ | ||||
|         ConductorAxisController.prototype.onZoom = function (zoom) { | ||||
|             this.zooming = true; | ||||
|  | ||||
|             this.bounds = zoom.bounds; | ||||
|             this.setScale(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorAxisController.prototype.onZoomStop = function (zoom) { | ||||
|             this.zooming = false; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @event platform.features.conductor.ConductorAxisController~pan | ||||
|          * Fired when the time conductor is panned | ||||
|          */ | ||||
|         /** | ||||
|          * Initiate panning via a click + drag gesture on the time conductor | ||||
|          * scale. Panning triggers a "pan" event | ||||
|          * @param {number} delta the offset from the original click event | ||||
|          * @see TimeConductorViewService# | ||||
|          * @fires platform.features.conductor.ConductorAxisController~pan | ||||
|          */ | ||||
|         ConductorAxisController.prototype.pan = function (delta) { | ||||
|             if (this.timeAPI.clock() === undefined) { | ||||
|                 var deltaInMs = delta[0] * this.msPerPixel; | ||||
|                 var bounds = this.timeAPI.bounds(); | ||||
|                 var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000; | ||||
|                 var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000; | ||||
|                 this.bounds = { | ||||
|                     start: start, | ||||
|                     end: end | ||||
|                 }; | ||||
|                 this.setScale(); | ||||
|                 this.viewService.emit("pan", this.bounds); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Invoked on element resize. Will rebuild the scale based on the new dimensions of the element. | ||||
|          */ | ||||
|         ConductorAxisController.prototype.resize = function () { | ||||
|             this.setScale(); | ||||
|         }; | ||||
|  | ||||
|         return ConductorAxisController; | ||||
|     } | ||||
| ); | ||||
| @@ -0,0 +1,169 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './ConductorAxisController', | ||||
|     'zepto', | ||||
|     'd3-selection', | ||||
|     'd3-scale' | ||||
| ], function ( | ||||
|     ConductorAxisController, | ||||
|     $, | ||||
|     d3Selection, | ||||
|     d3Scale | ||||
| ) { | ||||
|     describe("The ConductorAxisController", function () { | ||||
|         var controller, | ||||
|             mockConductor, | ||||
|             mockConductorViewService, | ||||
|             mockFormatService, | ||||
|             mockScope, | ||||
|             mockBounds, | ||||
|             element, | ||||
|             mockTimeSystem, | ||||
|             mockFormat; | ||||
|  | ||||
|         function getCallback(target, name) { | ||||
|             return target.calls.all().filter(function (call) { | ||||
|                 return call.args[0] === name; | ||||
|             })[0].args[1]; | ||||
|         } | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             mockScope = jasmine.createSpyObj("scope", [ | ||||
|                 "$on" | ||||
|             ]); | ||||
|  | ||||
|             //Add some HTML elements | ||||
|             mockBounds = { | ||||
|                 start: 100, | ||||
|                 end: 200 | ||||
|             }; | ||||
|             mockConductor = jasmine.createSpyObj("conductor", [ | ||||
|                 "timeSystem", | ||||
|                 "bounds", | ||||
|                 "on", | ||||
|                 "off", | ||||
|                 "clock" | ||||
|             ]); | ||||
|             mockConductor.bounds.and.returnValue(mockBounds); | ||||
|  | ||||
|             mockFormatService = jasmine.createSpyObj("formatService", [ | ||||
|                 "getFormat" | ||||
|             ]); | ||||
|  | ||||
|             mockConductorViewService = jasmine.createSpyObj("conductorViewService", [ | ||||
|                 "on", | ||||
|                 "off", | ||||
|                 "emit" | ||||
|             ]); | ||||
|  | ||||
|             spyOn(d3Scale, 'scaleUtc').and.callThrough(); | ||||
|             spyOn(d3Scale, 'scaleLinear').and.callThrough(); | ||||
|  | ||||
|             element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>'); | ||||
|             $(document).find('body').append(element); | ||||
|             ConductorAxisController.prototype.viewService = mockConductorViewService; | ||||
|             controller = new ConductorAxisController({time: mockConductor}, mockFormatService, mockScope, element); | ||||
|  | ||||
|             mockTimeSystem = {}; | ||||
|             mockFormat = jasmine.createSpyObj("format", [ | ||||
|                 "format" | ||||
|             ]); | ||||
|  | ||||
|             mockTimeSystem.timeFormat = "mockFormat"; | ||||
|             mockFormatService.getFormat.and.returnValue(mockFormat); | ||||
|             mockConductor.timeSystem.and.returnValue(mockTimeSystem); | ||||
|             mockTimeSystem.isUTCBased = false; | ||||
|         }); | ||||
|  | ||||
|         it("listens for changes to time system and bounds", function () { | ||||
|             expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|             expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|         }); | ||||
|  | ||||
|         it("on scope destruction, deregisters listeners", function () { | ||||
|             expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy); | ||||
|             controller.destroy(); | ||||
|             expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|             expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|         }); | ||||
|  | ||||
|         describe("when the time system changes", function () { | ||||
|             it("uses a UTC scale for UTC time systems", function () { | ||||
|                 mockTimeSystem.isUTCBased = true; | ||||
|                 controller.changeTimeSystem(mockTimeSystem); | ||||
|  | ||||
|                 expect(d3Scale.scaleUtc).toHaveBeenCalled(); | ||||
|                 expect(d3Scale.scaleLinear).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("uses a linear scale for non-UTC time systems", function () { | ||||
|                 mockTimeSystem.isUTCBased = false; | ||||
|                 controller.changeTimeSystem(mockTimeSystem); | ||||
|                 expect(d3Scale.scaleLinear).toHaveBeenCalled(); | ||||
|                 expect(d3Scale.scaleUtc).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("sets axis domain to time conductor bounds", function () { | ||||
|                 mockTimeSystem.isUTCBased = false; | ||||
|                 controller.setScale(); | ||||
|                 expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]); | ||||
|             }); | ||||
|  | ||||
|             it("uses the format specified by the time system to format tick" + | ||||
|                 " labels", function () { | ||||
|                 controller.changeTimeSystem(mockTimeSystem); | ||||
|                 expect(mockFormat.format).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it('responds to zoom events', function () { | ||||
|                 expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", controller.onZoom); | ||||
|                 var cb = getCallback(mockConductorViewService.on, "zoom"); | ||||
|                 spyOn(controller, 'setScale').and.callThrough(); | ||||
|                 cb({bounds: {start: 0, end: 100}}); | ||||
|                 expect(controller.setScale).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it('adjusts scale on pan', function () { | ||||
|                 spyOn(controller, 'setScale').and.callThrough(); | ||||
|                 controller.pan(100); | ||||
|                 expect(controller.setScale).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it('emits event on pan', function () { | ||||
|                 spyOn(controller, 'setScale').and.callThrough(); | ||||
|                 controller.pan(100); | ||||
|                 expect(mockConductorViewService.emit).toHaveBeenCalledWith("pan", jasmine.any(Object)); | ||||
|             }); | ||||
|  | ||||
|             it('cleans up listeners on destruction', function () { | ||||
|                 controller.destroy(); | ||||
|                 expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|                 expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|  | ||||
|                 expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", controller.onZoom); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -0,0 +1,56 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./ConductorAxisController'], function (ConductorAxisController) { | ||||
|     function ConductorAxisDirective() { | ||||
|         /** | ||||
|          * The mct-conductor-axis renders a horizontal axis with regular | ||||
|          * labelled 'ticks'. It requires 'start' and 'end' integer values to | ||||
|          * be specified as attributes. | ||||
|          */ | ||||
|         return { | ||||
|             controller: [ | ||||
|                 'openmct', | ||||
|                 'formatService', | ||||
|                 '$scope', | ||||
|                 '$element', | ||||
|                 ConductorAxisController | ||||
|             ], | ||||
|             controllerAs: 'axis', | ||||
|             scope: { | ||||
|                 viewService: "=" | ||||
|             }, | ||||
|             bindToController: true, | ||||
|  | ||||
|             restrict: 'E', | ||||
|             priority: 1000, | ||||
|  | ||||
|             template: '<div class="l-axis-holder" ' + | ||||
|             '    mct-drag-down="axis.panStart()"' + | ||||
|             '    mct-drag-up="axis.panStop(delta)"' + | ||||
|             '    mct-drag="axis.pan(delta)"' + | ||||
|             '    mct-resize="axis.resize()"></div>' | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     return ConductorAxisDirective; | ||||
| }); | ||||
| @@ -0,0 +1,123 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ["zepto"], | ||||
|     function ($) { | ||||
|  | ||||
|         /** | ||||
|          * Controller for the Time of Interest indicator in the conductor itself. Sets the horizontal position of the | ||||
|          * TOI indicator based on the current value of the TOI, and the width of the TOI conductor. | ||||
|          * @memberof platform.features.conductor | ||||
|          */ | ||||
|         function ConductorTOIController($scope, openmct) { | ||||
|             this.timeAPI = openmct.time; | ||||
|  | ||||
|             //Bind all class functions to 'this' | ||||
|             Object.keys(ConductorTOIController.prototype).filter(function (key) { | ||||
|                 return typeof ConductorTOIController.prototype[key] === 'function'; | ||||
|             }).forEach(function (key) { | ||||
|                 this[key] = ConductorTOIController.prototype[key].bind(this); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest); | ||||
|             this.viewService.on('zoom', this.setOffsetFromZoom); | ||||
|             this.viewService.on('pan', this.setOffsetFromBounds); | ||||
|  | ||||
|             var timeOfInterest = this.timeAPI.timeOfInterest(); | ||||
|             if (timeOfInterest) { | ||||
|                 this.changeTimeOfInterest(timeOfInterest); | ||||
|             } | ||||
|  | ||||
|             $scope.$on('$destroy', this.destroy); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorTOIController.prototype.destroy = function () { | ||||
|             this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest); | ||||
|             this.viewService.off('zoom', this.setOffsetFromZoom); | ||||
|             this.viewService.off('pan', this.setOffsetFromBounds); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Given some bounds, set horizontal position of TOI indicator based | ||||
|          * on current conductor TOI value. Bounds are provided so that | ||||
|          * ephemeral bounds from zoom and pan events can be used as well | ||||
|          * as current conductor bounds, allowing TOI to be updated in | ||||
|          * realtime during scroll and zoom. | ||||
|          * @param {TimeConductorBounds} bounds | ||||
|          */ | ||||
|         ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) { | ||||
|             var toi = this.timeAPI.timeOfInterest(); | ||||
|             if (toi !== undefined) { | ||||
|                 var offset = toi - bounds.start; | ||||
|                 var duration = bounds.end - bounds.start; | ||||
|                 this.left = offset / duration * 100; | ||||
|                 this.pinned = true; | ||||
|             } else { | ||||
|                 this.left = 0; | ||||
|                 this.pinned = false; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorTOIController.prototype.setOffsetFromZoom = function (zoom) { | ||||
|             return this.setOffsetFromBounds(zoom.bounds); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Invoked when time of interest changes. Will set the horizontal offset of the TOI indicator. | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorTOIController.prototype.changeTimeOfInterest = function () { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             if (bounds) { | ||||
|                 this.setOffsetFromBounds(bounds); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * On a mouse click event within the TOI element, convert position within element to a time of interest, and | ||||
|          * set the time of interest on the conductor. | ||||
|          * @param e The angular $event object | ||||
|          */ | ||||
|         ConductorTOIController.prototype.setTOIFromPosition = function (e) { | ||||
|             //TOI is set using the alt key modified + primary click | ||||
|             if (e.altKey) { | ||||
|                 var element = $(e.currentTarget); | ||||
|                 var width = element.width(); | ||||
|                 var relativeX = e.pageX - element.offset().left; | ||||
|                 var percX = relativeX / width; | ||||
|                 var bounds = this.timeAPI.bounds(); | ||||
|                 var timeRange = bounds.end - bounds.start; | ||||
|  | ||||
|                 this.timeAPI.timeOfInterest(timeRange * percX + bounds.start); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return ConductorTOIController; | ||||
|     } | ||||
| ); | ||||
| @@ -0,0 +1,153 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './ConductorTOIController' | ||||
| ], function ( | ||||
|     ConductorTOIController | ||||
| ) { | ||||
|     var mockConductor; | ||||
|     var mockConductorViewService; | ||||
|     var mockScope; | ||||
|     var mockAPI; | ||||
|     var conductorTOIController; | ||||
|  | ||||
|     function getNamedCallback(thing, name) { | ||||
|         return thing.calls.all().filter(function (call) { | ||||
|             return call.args[0] === name; | ||||
|         }).map(function (call) { | ||||
|             return call.args; | ||||
|         })[0][1]; | ||||
|     } | ||||
|  | ||||
|     describe("The ConductorTOIController", function () { | ||||
|         beforeEach(function () { | ||||
|             mockConductor = jasmine.createSpyObj("conductor", [ | ||||
|                 "bounds", | ||||
|                 "timeOfInterest", | ||||
|                 "on", | ||||
|                 "off" | ||||
|             ]); | ||||
|             mockAPI = {time: mockConductor}; | ||||
|  | ||||
|             mockConductorViewService = jasmine.createSpyObj("conductorViewService", [ | ||||
|                 "on", | ||||
|                 "off" | ||||
|             ]); | ||||
|  | ||||
|             mockScope = jasmine.createSpyObj("openMCT", [ | ||||
|                 "$on" | ||||
|             ]); | ||||
|             ConductorTOIController.prototype.viewService = mockConductorViewService; | ||||
|             conductorTOIController = new ConductorTOIController(mockScope, mockAPI); | ||||
|         }); | ||||
|  | ||||
|         it("listens to changes in the time of interest on the conductor", function () { | ||||
|             expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", jasmine.any(Function)); | ||||
|         }); | ||||
|  | ||||
|         describe("when responding to changes in the time of interest", function () { | ||||
|             var toiCallback; | ||||
|             beforeEach(function () { | ||||
|                 var bounds = { | ||||
|                     start: 0, | ||||
|                     end: 200 | ||||
|                 }; | ||||
|                 mockConductor.bounds.and.returnValue(bounds); | ||||
|                 toiCallback = getNamedCallback(mockConductor.on, "timeOfInterest"); | ||||
|             }); | ||||
|  | ||||
|             it("calculates the correct horizontal offset based on bounds and current TOI", function () { | ||||
|                 //Expect time of interest position to be 50% of element width | ||||
|                 mockConductor.timeOfInterest.and.returnValue(100); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.left).toBe(50); | ||||
|  | ||||
|                 //Expect time of interest position to be 25% of element width | ||||
|                 mockConductor.timeOfInterest.and.returnValue(50); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.left).toBe(25); | ||||
|  | ||||
|                 //Expect time of interest position to be 0% of element width | ||||
|                 mockConductor.timeOfInterest.and.returnValue(0); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.left).toBe(0); | ||||
|  | ||||
|                 //Expect time of interest position to be 100% of element width | ||||
|                 mockConductor.timeOfInterest.and.returnValue(200); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.left).toBe(100); | ||||
|             }); | ||||
|  | ||||
|             it("renders the TOI indicator visible", function () { | ||||
|                 expect(conductorTOIController.pinned).toBeFalsy(); | ||||
|                 mockConductor.timeOfInterest.and.returnValue(100); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.pinned).toBe(true); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("responds to zoom events", function () { | ||||
|             var mockZoom = { | ||||
|                 bounds: { | ||||
|                     start: 500, | ||||
|                     end: 1000 | ||||
|                 } | ||||
|             }; | ||||
|             expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", jasmine.any(Function)); | ||||
|  | ||||
|             // Should correspond to horizontal offset of 50% | ||||
|             mockConductor.timeOfInterest.and.returnValue(750); | ||||
|             var zoomCallback = getNamedCallback(mockConductorViewService.on, "zoom"); | ||||
|             zoomCallback(mockZoom); | ||||
|             expect(conductorTOIController.left).toBe(50); | ||||
|         }); | ||||
|  | ||||
|         it("responds to pan events", function () { | ||||
|             var mockPanBounds = { | ||||
|                 start: 1000, | ||||
|                 end: 3000 | ||||
|             }; | ||||
|  | ||||
|             expect(mockConductorViewService.on).toHaveBeenCalledWith("pan", jasmine.any(Function)); | ||||
|  | ||||
|             // Should correspond to horizontal offset of 25% | ||||
|             mockConductor.timeOfInterest.and.returnValue(1500); | ||||
|             var panCallback = getNamedCallback(mockConductorViewService.on, "pan"); | ||||
|             panCallback(mockPanBounds); | ||||
|             expect(conductorTOIController.left).toBe(25); | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         it("Cleans up all listeners when controller destroyed", function () { | ||||
|             var zoomCB = getNamedCallback(mockConductorViewService.on, "zoom"); | ||||
|             var panCB = getNamedCallback(mockConductorViewService.on, "pan"); | ||||
|             var toiCB = getNamedCallback(mockConductor.on, "timeOfInterest"); | ||||
|  | ||||
|             expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function)); | ||||
|             getNamedCallback(mockScope.$on, "$destroy")(); | ||||
|             expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", zoomCB); | ||||
|             expect(mockConductorViewService.off).toHaveBeenCalledWith("pan", panCB); | ||||
|             expect(mockConductor.off).toHaveBeenCalledWith("timeOfInterest", toiCB); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./ConductorTOIController'], function (ConductorTOIController) { | ||||
|     /** | ||||
|      * A directive that encapsulates the TOI specific behavior of the Time Conductor UI. | ||||
|      * @constructor | ||||
|      */ | ||||
|     function ConductorTOIDirective() { | ||||
|         /** | ||||
|          * The mct-conductor-axis renders a horizontal axis with regular | ||||
|          * labelled 'ticks'. It requires 'start' and 'end' integer values to | ||||
|          * be specified as attributes. | ||||
|          */ | ||||
|         return { | ||||
|             controller: [ | ||||
|                 '$scope', | ||||
|                 'openmct', | ||||
|                 ConductorTOIController | ||||
|             ], | ||||
|             controllerAs: 'toi', | ||||
|             scope: { | ||||
|                 viewService: "=" | ||||
|             }, | ||||
|             bindToController: true, | ||||
|  | ||||
|             restrict: 'E', | ||||
|             priority: 1000, | ||||
|  | ||||
|             template: | ||||
|                 '<div class="l-data-visualization-holder l-row-elem flex-elem">' + | ||||
|                 '   <a class="l-page-button s-icon-button icon-pointer-left"></a>' + | ||||
|                 '   <div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">' + | ||||
|                 '       <mct-include key="\'time-of-interest\'" class="l-toi-holder show-val" ' + | ||||
|                 '       ng-class="{ pinned: toi.pinned, \'val-to-left\': toi.left > 80 }" ' + | ||||
|                 '       ng-style="{\'left\': toi.left + \'%\'}"></mct-include>' + | ||||
|                 '   </div>' + | ||||
|                 '   <a class="l-page-button align-right s-icon-button icon-pointer-right"></a>' + | ||||
|                 '</div>' | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     return ConductorTOIDirective; | ||||
| }); | ||||
							
								
								
									
										54
									
								
								platform/features/conductor/core/src/ui/NumberFormat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								platform/features/conductor/core/src/ui/NumberFormat.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([], function () { | ||||
|  | ||||
|     /** | ||||
|      * Formatter for basic numbers. Provides basic support for non-UTC | ||||
|      * numbering systems | ||||
|      * | ||||
|      * @implements {Format} | ||||
|      * @constructor | ||||
|      * @memberof platform/commonUI/formats | ||||
|      */ | ||||
|     function NumberFormat() { | ||||
|         this.key = 'number'; | ||||
|     } | ||||
|  | ||||
|     NumberFormat.prototype.format = function (value) { | ||||
|         if (isNaN(value)) { | ||||
|             return ''; | ||||
|         } else { | ||||
|             return '' + value; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     NumberFormat.prototype.parse = function (text) { | ||||
|         return parseFloat(text); | ||||
|     }; | ||||
|  | ||||
|     NumberFormat.prototype.validate = function (text) { | ||||
|         return !isNaN(text); | ||||
|     }; | ||||
|  | ||||
|     return NumberFormat; | ||||
| }); | ||||
							
								
								
									
										49
									
								
								platform/features/conductor/core/src/ui/NumberFormatSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								platform/features/conductor/core/src/ui/NumberFormatSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./NumberFormat'], function (NumberFormat) { | ||||
|     describe("The NumberFormat class", function () { | ||||
|         var format; | ||||
|         beforeEach(function () { | ||||
|             format = new NumberFormat(); | ||||
|         }); | ||||
|  | ||||
|         it("The format function takes a string and produces a number", function () { | ||||
|             var text = format.format(1); | ||||
|             expect(text).toBe("1"); | ||||
|             expect(typeof text).toBe("string"); | ||||
|         }); | ||||
|  | ||||
|         it("The parse function takes a string and produces a number", function () { | ||||
|             var number = format.parse("1"); | ||||
|             expect(number).toBe(1); | ||||
|             expect(typeof number).toBe("number"); | ||||
|         }); | ||||
|  | ||||
|         it("validates that the input is a number", function () { | ||||
|             expect(format.validate("1")).toBe(true); | ||||
|             expect(format.validate(1)).toBe(true); | ||||
|             expect(format.validate("1.1")).toBe(true); | ||||
|             expect(format.validate("abc")).toBe(false); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -20,23 +20,34 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| 
 | ||||
| define([ | ||||
|     './tabs' | ||||
| ], function ( | ||||
|     Tabs | ||||
| ) { | ||||
|     return function plugin() { | ||||
|         return function install(openmct) { | ||||
|             openmct.objectViews.addProvider(new Tabs(openmct)); | ||||
| define([], function () { | ||||
| 
 | ||||
|             openmct.types.addType('tabs', { | ||||
|                 name: "Tabs View", | ||||
|                 creatable: true, | ||||
|                 cssClass: 'icon-tabs-view', | ||||
|                 initialize(domainObject) { | ||||
|                     domainObject.composition = []; | ||||
|                 } | ||||
|             }); | ||||
|         }; | ||||
|     /** | ||||
|      * Formatter for basic strings. | ||||
|      * | ||||
|      * @implements {Format} | ||||
|      * @constructor | ||||
|      * @memberof platform/commonUI/formats | ||||
|      */ | ||||
|     function StringFormat() { | ||||
|         this.key = 'string'; | ||||
|     } | ||||
| 
 | ||||
|     StringFormat.prototype.format = function (string) { | ||||
|         if (typeof string === 'string') { | ||||
|             return string; | ||||
|         } else { | ||||
|             return '' + string; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     StringFormat.prototype.parse = function (string) { | ||||
|         return string; | ||||
|     }; | ||||
| 
 | ||||
|     StringFormat.prototype.validate = function (string) { | ||||
|         return typeof string === 'string'; | ||||
|     }; | ||||
| 
 | ||||
|     return StringFormat; | ||||
| }); | ||||
| @@ -0,0 +1,554 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         'moment', | ||||
|         './TimeConductorValidation', | ||||
|         './TimeConductorViewService' | ||||
|     ], | ||||
|     function ( | ||||
|         moment, | ||||
|         TimeConductorValidation, | ||||
|         TimeConductorViewService | ||||
|     ) { | ||||
|  | ||||
|         var timeUnitsMegastructure = [ | ||||
|             ["Decades", function (r) { | ||||
|                 return r.years() > 15; | ||||
|             }], | ||||
|             ["Years", function (r) { | ||||
|                 return r.years() > 1; | ||||
|             }], | ||||
|             ["Months", function (r) { | ||||
|                 return r.years() === 1 || r.months() > 1; | ||||
|             }], | ||||
|             ["Days", function (r) { | ||||
|                 return r.months() === 1 || r.days() > 1; | ||||
|             }], | ||||
|             ["Hours", function (r) { | ||||
|                 return r.days() === 1 || r.hours() > 1; | ||||
|             }], | ||||
|             ["Minutes", function (r) { | ||||
|                 return r.hours() === 1 || r.minutes() > 1; | ||||
|             }], | ||||
|             ["Seconds", function (r) { | ||||
|                 return r.minutes() === 1 || r.seconds() > 1; | ||||
|             }], | ||||
|             ["Milliseconds", function (r) { | ||||
|                 return true; | ||||
|             }] | ||||
|         ]; | ||||
|  | ||||
|         /** | ||||
|          * Controller for the Time Conductor UI element. The Time Conductor | ||||
|          * includes form fields for specifying time bounds and relative time | ||||
|          * offsets for queries, as well as controls for selection mode, | ||||
|          * time systems, and zooming. | ||||
|          * @memberof platform.features.conductor | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TimeConductorController( | ||||
|             $scope, | ||||
|             $window, | ||||
|             openmct, | ||||
|             formatService, | ||||
|             config | ||||
|         ) { | ||||
|  | ||||
|             //Bind functions that are used as callbacks to 'this'. | ||||
|             [ | ||||
|                 "selectMenuOption", | ||||
|                 "onPan", | ||||
|                 "onPanStop", | ||||
|                 "setViewFromBounds", | ||||
|                 "setViewFromClock", | ||||
|                 "setViewFromOffsets", | ||||
|                 "setViewFromTimeSystem", | ||||
|                 "setTimeSystemFromView", | ||||
|                 "destroy" | ||||
|             ].forEach(function (name) { | ||||
|                 this[name] = this[name].bind(this); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             this.$scope = $scope; | ||||
|             this.$window = $window; | ||||
|             this.timeAPI = openmct.time; | ||||
|             this.conductorViewService = new TimeConductorViewService(openmct); | ||||
|             this.validation = new TimeConductorValidation(this.timeAPI); | ||||
|             this.formatService = formatService; | ||||
|             this.config = config; | ||||
|             this.timeSystemsForClocks = {}; | ||||
|             this.$scope.timeSystemModel = {}; | ||||
|             this.$scope.boundsModel = {}; | ||||
|  | ||||
|             this.timeSystems = this.timeAPI.getAllTimeSystems().reduce(function (map, timeSystem) { | ||||
|                 map[timeSystem.key] = timeSystem; | ||||
|                 return map; | ||||
|             }, {}); | ||||
|  | ||||
|             this.isFixed = this.timeAPI.clock() === undefined; | ||||
|  | ||||
|             var options = this.optionsFromConfig(config); | ||||
|             this.menu = { | ||||
|                 selected: undefined, | ||||
|                 options: options, | ||||
|                 select: this.selectMenuOption | ||||
|             }; | ||||
|  | ||||
|             //Set the initial state of the UI from the conductor state | ||||
|             var timeSystem = this.timeAPI.timeSystem(); | ||||
|             if (timeSystem) { | ||||
|                 this.setViewFromTimeSystem(timeSystem); | ||||
|             } | ||||
|  | ||||
|             this.setViewFromClock(this.timeAPI.clock()); | ||||
|  | ||||
|             var offsets = this.timeAPI.clockOffsets(); | ||||
|             if (offsets) { | ||||
|                 this.setViewFromOffsets(offsets); | ||||
|             } | ||||
|  | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             if (bounds && bounds.start !== undefined && bounds.end !== undefined) { | ||||
|                 this.setViewFromBounds(bounds); | ||||
|             } | ||||
|  | ||||
|             this.conductorViewService.on('pan', this.onPan); | ||||
|             this.conductorViewService.on('pan-stop', this.onPanStop); | ||||
|  | ||||
|             //Respond to any subsequent conductor changes | ||||
|             this.timeAPI.on('bounds', this.setViewFromBounds); | ||||
|             this.timeAPI.on('timeSystem', this.setViewFromTimeSystem); | ||||
|             this.timeAPI.on('clock', this.setViewFromClock); | ||||
|             this.timeAPI.on('clockOffsets', this.setViewFromOffsets); | ||||
|             this.$scope.$on('$destroy', this.destroy); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Given a key for a clock, retrieve the clock object. | ||||
|          * @private | ||||
|          * @param key | ||||
|          * @returns {Clock} | ||||
|          */ | ||||
|         TimeConductorController.prototype.getClock = function (key) { | ||||
|             return this.timeAPI.getAllClocks().filter(function (clock) { | ||||
|                 return clock.key === key; | ||||
|             })[0]; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Activate the selected menu option. Menu options correspond to clocks. | ||||
|          * A distinction is made to avoid confusion between the menu options and | ||||
|          * their metadata, and actual {@link Clock} objects. | ||||
|          * | ||||
|          * @private | ||||
|          * @param newOption | ||||
|          */ | ||||
|         TimeConductorController.prototype.selectMenuOption = function (newOption) { | ||||
|             if (this.menu.selected.key === newOption.key) { | ||||
|                 return; | ||||
|             } | ||||
|             this.menu.selected = newOption; | ||||
|  | ||||
|             var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock); | ||||
|             if (!config) { | ||||
|                 // Clock does not support this timeSystem, fallback to first | ||||
|                 // option provided for clock. | ||||
|                 config = this.config.menuOptions.filter(function (menuOption) { | ||||
|                     return menuOption.clock === (newOption.clock && newOption.clock.key); | ||||
|                 })[0]; | ||||
|             } | ||||
|  | ||||
|             if (config.clock) { | ||||
|                 this.timeAPI.clock(config.clock, config.clockOffsets); | ||||
|                 this.timeAPI.timeSystem(config.timeSystem); | ||||
|             } else { | ||||
|                 this.timeAPI.stopClock(); | ||||
|                 this.timeAPI.timeSystem(config.timeSystem, config.bounds); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * From the provided configuration, build the available menu options. | ||||
|          * @private | ||||
|          * @param config | ||||
|          * @returns {*[]} | ||||
|          */ | ||||
|         TimeConductorController.prototype.optionsFromConfig = function (config) { | ||||
|             /* | ||||
|              * "Fixed Mode" is always the first available option. | ||||
|              */ | ||||
|             var options = [{ | ||||
|                 key: 'fixed', | ||||
|                 name: 'Fixed Timespan Mode', | ||||
|                 description: 'Query and explore data that falls between two fixed datetimes.', | ||||
|                 cssClass: 'icon-calendar' | ||||
|             }]; | ||||
|             var clocks = {}; | ||||
|             var timeSystemsForClocks = this.timeSystemsForClocks; | ||||
|  | ||||
|             (config.menuOptions || []).forEach(function (menuOption) { | ||||
|                 var clockKey = menuOption.clock || 'fixed'; | ||||
|                 var clock = this.getClock(clockKey); | ||||
|  | ||||
|                 if (clock !== undefined) { | ||||
|                     clocks[clock.key] = clock; | ||||
|                 } | ||||
|  | ||||
|                 var timeSystem = this.timeSystems[menuOption.timeSystem]; | ||||
|                 if (timeSystem !== undefined) { | ||||
|                     timeSystemsForClocks[clockKey] = timeSystemsForClocks[clockKey] || []; | ||||
|                     timeSystemsForClocks[clockKey].push(timeSystem); | ||||
|                 } | ||||
|             }, this); | ||||
|  | ||||
|             /* | ||||
|              * Populate the clocks menu with metadata from the available clocks | ||||
|              */ | ||||
|             Object.values(clocks).forEach(function (clock) { | ||||
|                 options.push({ | ||||
|                     key: clock.key, | ||||
|                     name: clock.name, | ||||
|                     description: "Monitor streaming data in real-time. The Time " + | ||||
|                     "Conductor and displays will automatically advance themselves based on this clock. " + clock.description, | ||||
|                     cssClass: clock.cssClass || 'icon-clock', | ||||
|                     clock: clock | ||||
|                 }); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             return options; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When bounds change, set UI values from the new bounds. | ||||
|          * @param {TimeBounds} bounds the bounds | ||||
|          */ | ||||
|         TimeConductorController.prototype.setViewFromBounds = function (bounds) { | ||||
|             if (!this.zooming && !this.panning) { | ||||
|                 this.$scope.boundsModel.start = bounds.start; | ||||
|                 this.$scope.boundsModel.end = bounds.end; | ||||
|  | ||||
|                 if (this.supportsZoom()) { | ||||
|                     var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock()); | ||||
|                     this.currentZoom = this.toSliderValue(bounds.end - bounds.start, config.zoomOutLimit, config.zoomInLimit); | ||||
|                     this.toTimeUnits(bounds.end - bounds.start); | ||||
|                 } | ||||
|  | ||||
|                 /* | ||||
|                     Ensure that a digest occurs, capped at the browser's refresh | ||||
|                     rate. | ||||
|                  */ | ||||
|                 if (!this.pendingUpdate) { | ||||
|                     this.pendingUpdate = true; | ||||
|                     this.$window.requestAnimationFrame(function () { | ||||
|                         this.pendingUpdate = false; | ||||
|                         this.$scope.$digest(); | ||||
|                     }.bind(this)); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Retrieve any configuration defined for the provided time system and | ||||
|          * clock | ||||
|          * @private | ||||
|          * @param timeSystem | ||||
|          * @param clock | ||||
|          * @returns {object} The Time Conductor configuration corresponding to | ||||
|          * the provided combination of time system and clock | ||||
|          */ | ||||
|         TimeConductorController.prototype.getConfig = function (timeSystem, clock) { | ||||
|             var clockKey = clock && clock.key; | ||||
|             var timeSystemKey = timeSystem && timeSystem.key; | ||||
|  | ||||
|             var option = this.config.menuOptions.filter(function (menuOption) { | ||||
|                 return menuOption.timeSystem === timeSystemKey && menuOption.clock === clockKey; | ||||
|             })[0]; | ||||
|             return option; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When the clock offsets change, update the values in the UI | ||||
|          * @param {ClockOffsets} offsets | ||||
|          * @private | ||||
|          */ | ||||
|         TimeConductorController.prototype.setViewFromOffsets = function (offsets) { | ||||
|             this.$scope.boundsModel.startOffset = Math.abs(offsets.start); | ||||
|             this.$scope.boundsModel.endOffset = offsets.end; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When form values for bounds change, update the bounds in the Time API | ||||
|          * to trigger an application-wide bounds change. | ||||
|          * @param {object} boundsModel | ||||
|          */ | ||||
|         TimeConductorController.prototype.setBoundsFromView = function (boundsModel) { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             if (boundsModel.start !== bounds.start || boundsModel.end !== bounds.end) { | ||||
|                 this.timeAPI.bounds({ | ||||
|                     start: boundsModel.start, | ||||
|                     end: boundsModel.end | ||||
|                 }); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When form values for bounds change, update the bounds in the Time API | ||||
|          * to trigger an application-wide bounds change. | ||||
|          * @param {object} formModel | ||||
|          */ | ||||
|         TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) { | ||||
|             if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) { | ||||
|                 var offsets = { | ||||
|                     start: 0 - boundsModel.startOffset, | ||||
|                     end: boundsModel.endOffset | ||||
|                 }; | ||||
|                 var existingOffsets = this.timeAPI.clockOffsets(); | ||||
|  | ||||
|                 if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) { | ||||
|                     //Sychronize offsets between form and time API | ||||
|                     this.timeAPI.clockOffsets(offsets); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          * @returns {boolean} | ||||
|          */ | ||||
|         TimeConductorController.prototype.supportsZoom = function () { | ||||
|             var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock()); | ||||
|             return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Update the UI state to reflect a change in clock. Provided conductor | ||||
|          * configuration will be checked for compatibility between the new clock | ||||
|          * and the currently selected time system. If configuration is not available, | ||||
|          * an attempt will be made to default to a time system that is compatible | ||||
|          * with the new clock | ||||
|          * | ||||
|          * @private | ||||
|          * @param {Clock} clock | ||||
|          */ | ||||
|         TimeConductorController.prototype.setViewFromClock = function (clock) { | ||||
|             var newClockKey = clock ? clock.key : 'fixed'; | ||||
|             var timeSystems = this.timeSystemsForClocks[newClockKey]; | ||||
|             var menuOption = this.menu.options.filter(function (option) { | ||||
|                 return option.key === (newClockKey); | ||||
|             })[0]; | ||||
|  | ||||
|             this.menu.selected = menuOption; | ||||
|  | ||||
|             //Try to find currently selected time system in time systems for clock | ||||
|             var selectedTimeSystem = timeSystems.filter(function (timeSystem) { | ||||
|                 return timeSystem.key === this.$scope.timeSystemModel.selected.key; | ||||
|             }.bind(this))[0]; | ||||
|  | ||||
|             var config = this.getConfig(selectedTimeSystem, clock); | ||||
|  | ||||
|             if (selectedTimeSystem === undefined) { | ||||
|                 selectedTimeSystem = timeSystems[0]; | ||||
|                 config = this.getConfig(selectedTimeSystem, clock); | ||||
|  | ||||
|                 if (clock === undefined) { | ||||
|                     this.timeAPI.timeSystem(selectedTimeSystem, config.bounds); | ||||
|                 } else { | ||||
|                     //When time system changes, some start bounds need to be provided | ||||
|                     this.timeAPI.timeSystem(selectedTimeSystem, { | ||||
|                         start: clock.currentValue() + config.clockOffsets.start, | ||||
|                         end: clock.currentValue() + config.clockOffsets.end | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.isFixed = clock === undefined; | ||||
|  | ||||
|             if (clock === undefined) { | ||||
|                 this.setViewFromBounds(this.timeAPI.bounds()); | ||||
|             } | ||||
|  | ||||
|             this.zoom = this.supportsZoom(); | ||||
|             this.$scope.timeSystemModel.options = timeSystems; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Respond to time system selection from UI | ||||
|          * | ||||
|          * Allows time system to be changed by key. This supports selection | ||||
|          * from the menu. Resolves a TimeSystem object and then invokes | ||||
|          * TimeConductorController#setTimeSystem | ||||
|          * @param key | ||||
|          * @see TimeConductorController#setTimeSystem | ||||
|          */ | ||||
|         TimeConductorController.prototype.setTimeSystemFromView = function (key) { | ||||
|             var clock = this.menu.selected.clock; | ||||
|             var timeSystem = this.timeSystems[key]; | ||||
|             var config = this.getConfig(timeSystem, clock); | ||||
|  | ||||
|             this.$scope.timeSystemModel.selected = timeSystem; | ||||
|             this.$scope.timeSystemModel.format = timeSystem.timeFormat; | ||||
|  | ||||
|             if (clock === undefined) { | ||||
|                 this.timeAPI.timeSystem(timeSystem, config.bounds); | ||||
|             } else { | ||||
|                 this.timeAPI.clock(clock, config.clockOffsets); | ||||
|                 this.timeAPI.timeSystem(timeSystem); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Handles time system change from time conductor | ||||
|          * | ||||
|          * Sets the selected time system. Will populate form with the default | ||||
|          * bounds and offsets defined in the selected time system. | ||||
|          * | ||||
|          * @param newTimeSystem | ||||
|          */ | ||||
|         TimeConductorController.prototype.setViewFromTimeSystem = function (timeSystem) { | ||||
|             var oldKey = (this.$scope.timeSystemModel.selected || {}).key; | ||||
|             var timeSystemModel = this.$scope.timeSystemModel; | ||||
|  | ||||
|             if (timeSystem && (timeSystem.key !== oldKey)) { | ||||
|                 var config = this.getConfig(timeSystem, this.timeAPI.clock()); | ||||
|  | ||||
|                 timeSystemModel.selected = timeSystem; | ||||
|                 timeSystemModel.format = timeSystem.timeFormat; | ||||
|                 timeSystemModel.durationFormat = timeSystem.durationFormat; | ||||
|  | ||||
|                 if (this.supportsZoom()) { | ||||
|                     timeSystemModel.minZoom = config.zoomOutLimit; | ||||
|                     timeSystemModel.maxZoom = config.zoomInLimit; | ||||
|                 } | ||||
|             } | ||||
|             this.zoom = this.supportsZoom(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Takes a time span and calculates a slider increment value, used | ||||
|          * to set the horizontal offset of the slider. | ||||
|          * @private | ||||
|          * @param {number} timeSpan a duration of time, in ms | ||||
|          * @returns {number} a value between 0.01 and 0.99, in increments of .01 | ||||
|          */ | ||||
|         TimeConductorController.prototype.toSliderValue = function (timeSpan, zoomOutLimit, zoomInLimit) { | ||||
|             var perc = timeSpan / (zoomOutLimit - zoomInLimit); | ||||
|             return 1 - Math.pow(perc, 1 / 4); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Given a time span, set a label for the units of time that it, | ||||
|          * roughly, represents. Leverages | ||||
|          * @param {TimeSpan} timeSpan | ||||
|          */ | ||||
|         TimeConductorController.prototype.toTimeUnits = function (timeSpan) { | ||||
|             var timeSystem = this.timeAPI.timeSystem(); | ||||
|             if (timeSystem && timeSystem.isUTCBased) { | ||||
|                 var momentified = moment.duration(timeSpan); | ||||
|  | ||||
|                 this.$scope.timeUnits = timeUnitsMegastructure.filter(function (row) { | ||||
|                     return row[1](momentified); | ||||
|                 })[0][0]; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Zooming occurs when the user manipulates the zoom slider. | ||||
|          * Zooming updates the scale and bounds fields immediately, but does | ||||
|          * not trigger a bounds change to other views until the mouse button | ||||
|          * is released. | ||||
|          * @param bounds | ||||
|          */ | ||||
|         TimeConductorController.prototype.onZoom = function (sliderValue) { | ||||
|             var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock()); | ||||
|             var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit); | ||||
|  | ||||
|             var zoom = this.conductorViewService.zoom(timeSpan); | ||||
|             this.zooming = true; | ||||
|  | ||||
|             this.$scope.boundsModel.start = zoom.bounds.start; | ||||
|             this.$scope.boundsModel.end = zoom.bounds.end; | ||||
|             this.toTimeUnits(zoom.bounds.end - zoom.bounds.start); | ||||
|  | ||||
|             if (zoom.offsets) { | ||||
|                 this.setViewFromOffsets(zoom.offsets); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Fired when user has released the zoom slider | ||||
|          * @event platform.features.conductor.TimeConductorController~zoomStop | ||||
|          */ | ||||
|         /** | ||||
|          * Invoked when zoom slider is released by user. Will update the time conductor with the new bounds, triggering | ||||
|          * a global bounds change event. | ||||
|          * @fires platform.features.conductor.TimeConductorController~zoomStop | ||||
|          */ | ||||
|         TimeConductorController.prototype.onZoomStop = function () { | ||||
|             if (this.timeAPI.clock() !== undefined) { | ||||
|                 this.setOffsetsFromView(this.$scope.boundsModel); | ||||
|             } | ||||
|             this.setBoundsFromView(this.$scope.boundsModel); | ||||
|  | ||||
|             this.zooming = false; | ||||
|             this.conductorViewService.emit('zoom-stop'); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Panning occurs when the user grabs the conductor scale and drags | ||||
|          * it left or right to slide the window of time represented by the | ||||
|          * conductor. Panning updates the scale and bounds fields | ||||
|          * immediately, but does not trigger a bounds change to other views | ||||
|          * until the mouse button is released. | ||||
|          * @param {TimeConductorBounds} bounds | ||||
|          */ | ||||
|         TimeConductorController.prototype.onPan = function (bounds) { | ||||
|             this.panning = true; | ||||
|             this.$scope.boundsModel.start = bounds.start; | ||||
|             this.$scope.boundsModel.end = bounds.end; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Called when the user releases the mouse button after panning. | ||||
|          */ | ||||
|         TimeConductorController.prototype.onPanStop = function () { | ||||
|             this.panning = false; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         TimeConductorController.prototype.destroy = function () { | ||||
|             this.timeAPI.off('bounds', this.setViewFromBounds); | ||||
|             this.timeAPI.off('timeSystem', this.setViewFromTimeSystem); | ||||
|             this.timeAPI.off('clock', this.setViewFromClock); | ||||
|             this.timeAPI.off('follow', this.setFollow); | ||||
|             this.timeAPI.off('clockOffsets', this.setViewFromOffsets); | ||||
|  | ||||
|             this.conductorViewService.off('pan', this.onPan); | ||||
|             this.conductorViewService.off('pan-stop', this.onPanStop); | ||||
|         }; | ||||
|  | ||||
|         return TimeConductorController; | ||||
|     } | ||||
| ); | ||||
| @@ -0,0 +1,513 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./TimeConductorController'], function (TimeConductorController) { | ||||
|     xdescribe("The time conductor controller", function () { | ||||
|         var mockScope; | ||||
|         var mockWindow; | ||||
|         var mockTimeConductor; | ||||
|         var mockConductorViewService; | ||||
|         var mockTimeSystems; | ||||
|         var controller; | ||||
|         var mockFormatService; | ||||
|         var mockFormat; | ||||
|         var mockLocation; | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             mockScope = jasmine.createSpyObj("$scope", [ | ||||
|                 "$watch", | ||||
|                 "$on" | ||||
|             ]); | ||||
|  | ||||
|             mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]); | ||||
|             mockTimeConductor = jasmine.createSpyObj( | ||||
|                 "TimeConductor", | ||||
|                 [ | ||||
|                     "bounds", | ||||
|                     "timeSystem", | ||||
|                     "on", | ||||
|                     "off" | ||||
|                 ] | ||||
|             ); | ||||
|             mockTimeConductor.bounds.and.returnValue({start: undefined, end: undefined}); | ||||
|  | ||||
|             mockConductorViewService = jasmine.createSpyObj( | ||||
|                 "ConductorViewService", | ||||
|                 [ | ||||
|                     "availableModes", | ||||
|                     "mode", | ||||
|                     "availableTimeSystems", | ||||
|                     "deltas", | ||||
|                     "deltas", | ||||
|                     "on", | ||||
|                     "off" | ||||
|                 ] | ||||
|             ); | ||||
|             mockConductorViewService.availableModes.and.returnValue([]); | ||||
|             mockConductorViewService.availableTimeSystems.and.returnValue([]); | ||||
|  | ||||
|             mockFormatService = jasmine.createSpyObj('formatService', [ | ||||
|                 'getFormat' | ||||
|             ]); | ||||
|             mockFormat = jasmine.createSpyObj('format', [ | ||||
|                 'format' | ||||
|             ]); | ||||
|             mockFormatService.getFormat.and.returnValue(mockFormat); | ||||
|             mockLocation = jasmine.createSpyObj('location', [ | ||||
|                 'search' | ||||
|             ]); | ||||
|             mockLocation.search.and.returnValue({}); | ||||
|  | ||||
|             mockTimeSystems = []; | ||||
|         }); | ||||
|  | ||||
|         function getListener(target, event) { | ||||
|             return target.calls.all().filter(function (call) { | ||||
|                 return call.args[0] === event; | ||||
|             })[0].args[1]; | ||||
|         } | ||||
|  | ||||
|         describe("when time conductor state changes", function () { | ||||
|             var mockDeltaFormat; | ||||
|             var defaultBounds; | ||||
|             var defaultDeltas; | ||||
|             var mockDefaults; | ||||
|             var timeSystem; | ||||
|             var tsListener; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockFormat = {}; | ||||
|                 mockDeltaFormat = {}; | ||||
|                 defaultBounds = { | ||||
|                     start: 2, | ||||
|                     end: 3 | ||||
|                 }; | ||||
|                 defaultDeltas = { | ||||
|                     start: 10, | ||||
|                     end: 20 | ||||
|                 }; | ||||
|                 mockDefaults = { | ||||
|                     deltas: defaultDeltas, | ||||
|                     bounds: defaultBounds | ||||
|                 }; | ||||
|                 timeSystem = { | ||||
|                     metadata: { | ||||
|                         key: 'mock' | ||||
|                     }, | ||||
|                     formats: function () { | ||||
|                         return [mockFormat]; | ||||
|                     }, | ||||
|                     deltaFormat: function () { | ||||
|                         return mockDeltaFormat; | ||||
|                     }, | ||||
|                     defaults: function () { | ||||
|                         return mockDefaults; | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 controller = new TimeConductorController( | ||||
|                     mockScope, | ||||
|                     mockWindow, | ||||
|                     mockLocation, | ||||
|                     {conductor: mockTimeConductor}, | ||||
|                     mockConductorViewService, | ||||
|                     mockFormatService, | ||||
|                     'fixed', | ||||
|                     true | ||||
|  | ||||
|                 ); | ||||
|  | ||||
|                 tsListener = getListener(mockTimeConductor.on, "timeSystem"); | ||||
|             }); | ||||
|  | ||||
|             it("listens for changes to conductor state", function () { | ||||
|                 expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|                 expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|             }); | ||||
|  | ||||
|             it("deregisters conductor listens when scope is destroyed", function () { | ||||
|                 expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy); | ||||
|  | ||||
|                 controller.destroy(); | ||||
|                 expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|                 expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|             }); | ||||
|  | ||||
|             it("when time system changes, sets time system on scope", function () { | ||||
|                 expect(tsListener).toBeDefined(); | ||||
|                 tsListener(timeSystem); | ||||
|  | ||||
|                 expect(mockScope.timeSystemModel).toBeDefined(); | ||||
|                 expect(mockScope.timeSystemModel.selected).toBe(timeSystem); | ||||
|                 expect(mockScope.timeSystemModel.format).toBe(mockFormat); | ||||
|                 expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat); | ||||
|             }); | ||||
|  | ||||
|             it("when time system changes, sets defaults on scope", function () { | ||||
|                 mockDefaults.zoom = { | ||||
|                     min: 100, | ||||
|                     max: 10 | ||||
|                 }; | ||||
|                 mockTimeConductor.timeSystem.and.returnValue(timeSystem); | ||||
|                 tsListener(timeSystem); | ||||
|  | ||||
|                 expect(mockScope.boundsModel.start).toEqual(defaultBounds.start); | ||||
|                 expect(mockScope.boundsModel.end).toEqual(defaultBounds.end); | ||||
|  | ||||
|                 expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start); | ||||
|                 expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end); | ||||
|  | ||||
|                 expect(mockScope.timeSystemModel.minZoom).toBe(mockDefaults.zoom.min); | ||||
|                 expect(mockScope.timeSystemModel.maxZoom).toBe(mockDefaults.zoom.max); | ||||
|             }); | ||||
|  | ||||
|             it("supports zoom if time system defines zoom defaults", function () { | ||||
|  | ||||
|                 mockDefaults.zoom = undefined; | ||||
|  | ||||
|                 tsListener(timeSystem); | ||||
|                 expect(controller.supportsZoom).toBe(false); | ||||
|  | ||||
|                 mockDefaults.zoom = { | ||||
|                     min: 100, | ||||
|                     max: 10 | ||||
|                 }; | ||||
|  | ||||
|                 var anotherTimeSystem = Object.create(timeSystem); | ||||
|                 timeSystem.defaults = function () { | ||||
|                     return mockDefaults; | ||||
|                 }; | ||||
|  | ||||
|                 tsListener(anotherTimeSystem); | ||||
|                 expect(controller.supportsZoom).toBe(true); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             it("when bounds change, sets the correct zoom slider value", function () { | ||||
|                 var bounds = { | ||||
|                     start: 0, | ||||
|                     end: 50 | ||||
|                 }; | ||||
|                 mockDefaults.zoom = { | ||||
|                     min: 100, | ||||
|                     max: 0 | ||||
|                 }; | ||||
|  | ||||
|                 function exponentializer(rawValue) { | ||||
|                     return 1 - Math.pow(rawValue, 1 / 4); | ||||
|                 } | ||||
|  | ||||
|                 mockTimeConductor.timeSystem.and.returnValue(timeSystem); | ||||
|                 //Set zoom defaults | ||||
|                 tsListener(timeSystem); | ||||
|  | ||||
|                 controller.changeBounds(bounds); | ||||
|                 expect(controller.currentZoom).toEqual(exponentializer(0.5)); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             it("when bounds change, sets them on scope", function () { | ||||
|                 var bounds = { | ||||
|                     start: 1, | ||||
|                     end: 2 | ||||
|                 }; | ||||
|  | ||||
|                 var boundsListener = getListener(mockTimeConductor.on, "bounds"); | ||||
|                 expect(boundsListener).toBeDefined(); | ||||
|                 boundsListener(bounds); | ||||
|  | ||||
|                 expect(mockScope.boundsModel).toBeDefined(); | ||||
|                 expect(mockScope.boundsModel.start).toEqual(bounds.start); | ||||
|                 expect(mockScope.boundsModel.end).toEqual(bounds.end); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         describe("when user makes changes from UI", function () { | ||||
|             var mode = "realtime"; | ||||
|             var ts1Metadata; | ||||
|             var ts2Metadata; | ||||
|             var ts3Metadata; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mode = "realtime"; | ||||
|                 ts1Metadata = { | ||||
|                     'key': 'ts1', | ||||
|                     'name': 'Time System One', | ||||
|                     'cssClass': 'cssClassOne' | ||||
|                 }; | ||||
|                 ts2Metadata = { | ||||
|                     'key': 'ts2', | ||||
|                     'name': 'Time System Two', | ||||
|                     'cssClass': 'cssClassTwo' | ||||
|                 }; | ||||
|                 ts3Metadata = { | ||||
|                     'key': 'ts3', | ||||
|                     'name': 'Time System Three', | ||||
|                     'cssClass': 'cssClassThree' | ||||
|                 }; | ||||
|                 mockTimeSystems = [ | ||||
|                     { | ||||
|                         metadata: ts1Metadata | ||||
|                     }, | ||||
|                     { | ||||
|                         metadata: ts2Metadata | ||||
|                     }, | ||||
|                     { | ||||
|                         metadata: ts3Metadata | ||||
|                     } | ||||
|                 ]; | ||||
|  | ||||
|                 //Wrap in mock constructors | ||||
|                 mockConductorViewService.systems = mockTimeSystems; | ||||
|  | ||||
|                 controller = new TimeConductorController( | ||||
|                     mockScope, | ||||
|                     mockWindow, | ||||
|                     mockLocation, | ||||
|                     {conductor: mockTimeConductor}, | ||||
|                     mockConductorViewService, | ||||
|                     mockFormatService, | ||||
|                     "fixed", | ||||
|                     true | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("sets the mode on scope", function () { | ||||
|                 mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems); | ||||
|                 controller.setMode(mode); | ||||
|  | ||||
|                 expect(mockScope.modeModel.selectedKey).toEqual(mode); | ||||
|             }); | ||||
|  | ||||
|             it("sets available time systems on scope when mode changes", function () { | ||||
|                 mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems); | ||||
|                 controller.setMode(mode); | ||||
|  | ||||
|                 expect(mockScope.timeSystemModel.options.length).toEqual(3); | ||||
|                 expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata); | ||||
|                 expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata); | ||||
|                 expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata); | ||||
|             }); | ||||
|  | ||||
|             it("sets bounds on the time conductor", function () { | ||||
|                 var formModel = { | ||||
|                     start: 1, | ||||
|                     end: 10 | ||||
|                 }; | ||||
|  | ||||
|                 controller.setBounds(formModel); | ||||
|                 expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel); | ||||
|             }); | ||||
|  | ||||
|             it("applies deltas when they change in form", function () { | ||||
|                 var deltas = { | ||||
|                     start: 1000, | ||||
|                     end: 2000 | ||||
|                 }; | ||||
|                 var formModel = { | ||||
|                     startDelta: deltas.start, | ||||
|                     endDelta: deltas.end | ||||
|                 }; | ||||
|  | ||||
|                 controller.setDeltas(formModel); | ||||
|                 expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas); | ||||
|             }); | ||||
|  | ||||
|             it("sets the time system on the time conductor", function () { | ||||
|                 var defaultBounds = { | ||||
|                     start: 5, | ||||
|                     end: 6 | ||||
|                 }; | ||||
|                 var timeSystem = { | ||||
|                     metadata: { | ||||
|                         key: 'testTimeSystem' | ||||
|                     }, | ||||
|                     defaults: function () { | ||||
|                         return { | ||||
|                             bounds: defaultBounds | ||||
|                         }; | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 controller.timeSystems = [timeSystem]; | ||||
|  | ||||
|                 controller.selectTimeSystemByKey('testTimeSystem'); | ||||
|                 expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds); | ||||
|             }); | ||||
|  | ||||
|             it("updates form bounds during pan events", function () { | ||||
|                 var testBounds = { | ||||
|                     start: 10, | ||||
|                     end: 20 | ||||
|                 }; | ||||
|  | ||||
|                 expect(controller.$scope.boundsModel.start).not.toBe(testBounds.start); | ||||
|                 expect(controller.$scope.boundsModel.end).not.toBe(testBounds.end); | ||||
|  | ||||
|                 expect(controller.conductorViewService.on).toHaveBeenCalledWith("pan", | ||||
|                     controller.onPan); | ||||
|  | ||||
|                 getListener(controller.conductorViewService.on, "pan")(testBounds); | ||||
|  | ||||
|                 expect(controller.$scope.boundsModel.start).toBe(testBounds.start); | ||||
|                 expect(controller.$scope.boundsModel.end).toBe(testBounds.end); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         describe("when the URL defines conductor state", function () { | ||||
|             var urlBounds; | ||||
|             var urlTimeSystem; | ||||
|             var urlDeltas; | ||||
|  | ||||
|             var mockDeltaFormat; | ||||
|             var defaultBounds; | ||||
|             var defaultDeltas; | ||||
|             var mockDefaults; | ||||
|             var timeSystem; | ||||
|             var otherTimeSystem; | ||||
|             var mockSearchObject; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|  | ||||
|                 mockFormat = {}; | ||||
|                 mockDeltaFormat = {}; | ||||
|                 defaultBounds = { | ||||
|                     start: 2, | ||||
|                     end: 3 | ||||
|                 }; | ||||
|                 defaultDeltas = { | ||||
|                     start: 10, | ||||
|                     end: 20 | ||||
|                 }; | ||||
|                 mockDefaults = { | ||||
|                     deltas: defaultDeltas, | ||||
|                     bounds: defaultBounds | ||||
|                 }; | ||||
|                 timeSystem = { | ||||
|                     metadata: { | ||||
|                         key: 'mockTimeSystem' | ||||
|                     }, | ||||
|                     formats: function () { | ||||
|                         return [mockFormat]; | ||||
|                     }, | ||||
|                     deltaFormat: function () { | ||||
|                         return mockDeltaFormat; | ||||
|                     }, | ||||
|                     defaults: function () { | ||||
|                         return mockDefaults; | ||||
|                     } | ||||
|                 }; | ||||
|                 otherTimeSystem = { | ||||
|                     metadata: { | ||||
|                         key: 'otherTimeSystem' | ||||
|                     }, | ||||
|                     formats: function () { | ||||
|                         return [mockFormat]; | ||||
|                     }, | ||||
|                     deltaFormat: function () { | ||||
|                         return mockDeltaFormat; | ||||
|                     }, | ||||
|                     defaults: function () { | ||||
|                         return mockDefaults; | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 mockConductorViewService.systems = [timeSystem, otherTimeSystem]; | ||||
|  | ||||
|                 urlBounds = { | ||||
|                     start: 100, | ||||
|                     end: 200 | ||||
|                 }; | ||||
|                 urlTimeSystem = "otherTimeSystem"; | ||||
|                 urlDeltas = { | ||||
|                     start: 300, | ||||
|                     end: 400 | ||||
|                 }; | ||||
|                 mockSearchObject = { | ||||
|                     "tc.startBound": urlBounds.start, | ||||
|                     "tc.endBound": urlBounds.end, | ||||
|                     "tc.startDelta": urlDeltas.start, | ||||
|                     "tc.endDelta": urlDeltas.end, | ||||
|                     "tc.timeSystem": urlTimeSystem | ||||
|                 }; | ||||
|                 mockLocation.search.and.returnValue(mockSearchObject); | ||||
|                 mockTimeConductor.timeSystem.and.returnValue(timeSystem); | ||||
|  | ||||
|                 controller = new TimeConductorController( | ||||
|                     mockScope, | ||||
|                     mockWindow, | ||||
|                     mockLocation, | ||||
|                     {conductor: mockTimeConductor}, | ||||
|                     mockConductorViewService, | ||||
|                     mockFormatService, | ||||
|                     "fixed", | ||||
|                     true | ||||
|                 ); | ||||
|  | ||||
|                 spyOn(controller, "setMode"); | ||||
|                 spyOn(controller, "selectTimeSystemByKey"); | ||||
|             }); | ||||
|  | ||||
|             it("sets conductor state from URL", function () { | ||||
|                 mockSearchObject["tc.mode"] = "fixed"; | ||||
|                 controller.setStateFromSearchParams(mockSearchObject); | ||||
|                 expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem"); | ||||
|                 expect(mockTimeConductor.bounds).toHaveBeenCalledWith(urlBounds); | ||||
|             }); | ||||
|  | ||||
|             it("sets mode from URL", function () { | ||||
|                 mockTimeConductor.bounds.reset(); | ||||
|                 mockSearchObject["tc.mode"] = "realtime"; | ||||
|                 controller.setStateFromSearchParams(mockSearchObject); | ||||
|                 expect(controller.setMode).toHaveBeenCalledWith("realtime"); | ||||
|                 expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem"); | ||||
|                 expect(mockConductorViewService.deltas).toHaveBeenCalledWith(urlDeltas); | ||||
|                 expect(mockTimeConductor.bounds).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             describe("when conductor state changes", function () { | ||||
|                 it("updates the URL with the mode", function () { | ||||
|                     controller.setMode("realtime", "fixed"); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.mode", "fixed"); | ||||
|                 }); | ||||
|  | ||||
|                 it("updates the URL with the bounds", function () { | ||||
|                     mockConductorViewService.mode.and.returnValue("fixed"); | ||||
|                     controller.changeBounds({start: 500, end: 600}); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.startBound", 500); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.endBound", 600); | ||||
|                 }); | ||||
|  | ||||
|                 it("updates the URL with the deltas", function () { | ||||
|                     controller.setDeltas({startDelta: 700, endDelta: 800}); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.startDelta", 700); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.endDelta", 800); | ||||
|                 }); | ||||
|  | ||||
|                 it("updates the URL with the time system", function () { | ||||
|                     controller.changeTimeSystem(otherTimeSystem); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.timeSystem", "otherTimeSystem"); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -0,0 +1,69 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Form validation for the TimeConductorController. | ||||
|          * @param conductor | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TimeConductorValidation(timeAPI) { | ||||
|             var self = this; | ||||
|             this.timeAPI = timeAPI; | ||||
|  | ||||
|             /* | ||||
|              * Bind all class functions to 'this' | ||||
|              */ | ||||
|             Object.keys(TimeConductorValidation.prototype).filter(function (key) { | ||||
|                 return typeof TimeConductorValidation.prototype[key] === 'function'; | ||||
|             }).forEach(function (key) { | ||||
|                 self[key] = self[key].bind(self); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Validation methods below are invoked directly from controls in the TimeConductor form | ||||
|          */ | ||||
|         TimeConductorValidation.prototype.validateStart = function (start) { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true; | ||||
|         }; | ||||
|  | ||||
|         TimeConductorValidation.prototype.validateEnd = function (end) { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true; | ||||
|         }; | ||||
|  | ||||
|         TimeConductorValidation.prototype.validateStartOffset = function (startOffset) { | ||||
|             return !isNaN(startOffset) && startOffset > 0; | ||||
|         }; | ||||
|  | ||||
|         TimeConductorValidation.prototype.validateEndOffset = function (endOffset) { | ||||
|             return !isNaN(endOffset) && endOffset >= 0; | ||||
|         }; | ||||
|  | ||||
|         return TimeConductorValidation; | ||||
|     } | ||||
| ); | ||||
| @@ -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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./TimeConductorValidation'], function (TimeConductorValidation) { | ||||
|     describe("The Time Conductor Validation class", function () { | ||||
|         var timeConductorValidation, | ||||
|             mockTimeConductor; | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             mockTimeConductor = jasmine.createSpyObj("timeConductor", [ | ||||
|                 "validateBounds", | ||||
|                 "bounds" | ||||
|             ]); | ||||
|             timeConductorValidation = new TimeConductorValidation(mockTimeConductor); | ||||
|         }); | ||||
|  | ||||
|         describe("Validates start and end values using Time Conductor", function () { | ||||
|             beforeEach(function () { | ||||
|                 var mockBounds = { | ||||
|                     start: 10, | ||||
|                     end: 20 | ||||
|                 }; | ||||
|  | ||||
|                 mockTimeConductor.bounds.and.returnValue(mockBounds); | ||||
|  | ||||
|             }); | ||||
|             it("Validates start values using Time Conductor", function () { | ||||
|                 var startValue = 30; | ||||
|                 timeConductorValidation.validateStart(startValue); | ||||
|                 expect(mockTimeConductor.validateBounds).toHaveBeenCalled(); | ||||
|             }); | ||||
|             it("Validates end values using Time Conductor", function () { | ||||
|                 var endValue = 40; | ||||
|                 timeConductorValidation.validateEnd(endValue); | ||||
|                 expect(mockTimeConductor.validateBounds).toHaveBeenCalled(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("Validates that start Offset is valid number > 0", function () { | ||||
|             expect(timeConductorValidation.validateStartOffset(-1)).toBe(false); | ||||
|             expect(timeConductorValidation.validateStartOffset("abc")).toBe(false); | ||||
|             expect(timeConductorValidation.validateStartOffset("1")).toBe(true); | ||||
|             expect(timeConductorValidation.validateStartOffset(1)).toBe(true); | ||||
|         }); | ||||
|  | ||||
|         it("Validates that end Offset is valid number >= 0", function () { | ||||
|             expect(timeConductorValidation.validateEndOffset(-1)).toBe(false); | ||||
|             expect(timeConductorValidation.validateEndOffset("abc")).toBe(false); | ||||
|             expect(timeConductorValidation.validateEndOffset("1")).toBe(true); | ||||
|             expect(timeConductorValidation.validateEndOffset(0)).toBe(true); | ||||
|             expect(timeConductorValidation.validateEndOffset(1)).toBe(true); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -0,0 +1,97 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         'EventEmitter' | ||||
|     ], | ||||
|     function (EventEmitter) { | ||||
|  | ||||
|         /** | ||||
|          * The TimeConductorViewService acts as an event bus between different | ||||
|          * elements of the Time Conductor UI. Zooming and panning occur via this | ||||
|          * service, as they are specific behaviour of the UI, and not general | ||||
|          * functions of the time API. | ||||
|          * | ||||
|          * Synchronization of conductor state between the Time API and the URL | ||||
|          * also occurs from the conductor view service, whose lifecycle persists | ||||
|          * between view changes. | ||||
|          * | ||||
|          * @memberof platform.features.conductor | ||||
|          * @param conductor | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TimeConductorViewService(openmct) { | ||||
|  | ||||
|             EventEmitter.call(this); | ||||
|  | ||||
|             this.timeAPI = openmct.time; | ||||
|         } | ||||
|  | ||||
|         TimeConductorViewService.prototype = Object.create(EventEmitter.prototype); | ||||
|  | ||||
|         /** | ||||
|          * An event to indicate that zooming is taking place | ||||
|          * @event platform.features.conductor.TimeConductorViewService~zoom | ||||
|          * @property {ZoomLevel} zoom the new zoom level. | ||||
|          */ | ||||
|         /** | ||||
|          * Zoom to given time span. Will fire a zoom event with new zoom | ||||
|          * bounds. Zoom bounds emitted in this way are considered ephemeral | ||||
|          * and should be overridden by any time conductor bounds events. Does | ||||
|          * not set bounds globally. | ||||
|          * @param {number} zoom A time duration in ms | ||||
|          * @fires platform.features.conductor.TimeConductorViewService~zoom | ||||
|          * @see module:openmct.TimeConductor#bounds | ||||
|          */ | ||||
|         TimeConductorViewService.prototype.zoom = function (timeSpan) { | ||||
|             var zoom = {}; | ||||
|  | ||||
|             // If a tick source is defined, then the concept of 'now' is | ||||
|             // important. Calculate zoom based on 'now'. | ||||
|             if (this.timeAPI.clock() !== undefined) { | ||||
|                 zoom.offsets = { | ||||
|                     start: -timeSpan, | ||||
|                     end: this.timeAPI.clockOffsets().end | ||||
|                 }; | ||||
|  | ||||
|                 var currentVal = this.timeAPI.clock().currentValue(); | ||||
|  | ||||
|                 zoom.bounds = { | ||||
|                     start: currentVal + zoom.offsets.start, | ||||
|                     end: currentVal + zoom.offsets.end | ||||
|                 }; | ||||
|             } else { | ||||
|                 var bounds = this.timeAPI.bounds(); | ||||
|                 var center = bounds.start + ((bounds.end - bounds.start)) / 2; | ||||
|                 bounds.start = center - timeSpan / 2; | ||||
|                 bounds.end = center + timeSpan / 2; | ||||
|                 zoom.bounds = bounds; | ||||
|             } | ||||
|  | ||||
|             this.emit("zoom", zoom); | ||||
|             return zoom; | ||||
|         }; | ||||
|  | ||||
|         return TimeConductorViewService; | ||||
|     } | ||||
| ); | ||||
| @@ -0,0 +1,109 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Controller for the Time of Interest element used in various views to display the TOI. Responsible for setting | ||||
|          * the text label for the current TOI, and for toggling the (un)pinned state which determines whether the TOI | ||||
|          * indicator is visible. | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TimeOfInterestController($scope, openmct, formatService) { | ||||
|             this.timeAPI = openmct.time; | ||||
|             this.formatService = formatService; | ||||
|             this.format = undefined; | ||||
|             this.toiText = undefined; | ||||
|             this.$scope = $scope; | ||||
|  | ||||
|             //Bind all class functions to 'this' | ||||
|             Object.keys(TimeOfInterestController.prototype).filter(function (key) { | ||||
|                 return typeof TimeOfInterestController.prototype[key] === 'function'; | ||||
|             }).forEach(function (key) { | ||||
|                 this[key] = TimeOfInterestController.prototype[key].bind(this); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest); | ||||
|             this.timeAPI.on('timeSystem', this.changeTimeSystem); | ||||
|             if (this.timeAPI.timeSystem() !== undefined) { | ||||
|                 this.changeTimeSystem(this.timeAPI.timeSystem()); | ||||
|                 var toi = this.timeAPI.timeOfInterest(); | ||||
|                 if (toi) { | ||||
|                     this.changeTimeOfInterest(toi); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             $scope.$on('$destroy', this.destroy); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Called when the time of interest changes on the conductor. Will pin (display) the TOI indicator, and set the | ||||
|          * text using the default formatter of the currently active Time System. | ||||
|          * @private | ||||
|          * @param {integer} toi Current time of interest in ms | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.changeTimeOfInterest = function (toi) { | ||||
|             if (toi !== undefined) { | ||||
|                 this.$scope.pinned = true; | ||||
|                 this.toiText = this.format.format(toi); | ||||
|             } else { | ||||
|                 this.$scope.pinned = false; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When time system is changed, update the formatter used to | ||||
|          * display the current TOI label | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) { | ||||
|             this.format = this.formatService.getFormat(timeSystem.timeFormat); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.destroy = function () { | ||||
|             this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest); | ||||
|             this.timeAPI.off('timeSystem', this.changeTimeSystem); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Will unpin (hide) the TOI indicator. Has the effect of setting the time of interest to `undefined` on the | ||||
|          * Time Conductor | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.dismiss = function () { | ||||
|             this.timeAPI.timeOfInterest(undefined); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Sends out a time of interest event with the effect of resetting | ||||
|          * the TOI displayed in views. | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.resync = function () { | ||||
|             this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest()); | ||||
|         }; | ||||
|  | ||||
|         return TimeOfInterestController; | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										399
									
								
								platform/features/fixed/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								platform/features/fixed/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,399 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     "../layout/res/templates/fixed.html", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     fixedTemplate, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
|     legacyRegistry.register("platform/features/fixed", { | ||||
|         "name": "Fixed position components.", | ||||
|         "description": "Plug in adding Fixed Position object type.", | ||||
|         "extensions": { | ||||
|             "views": [ | ||||
|                 { | ||||
|                     "key": "fixed-display", | ||||
|                     "name": "Fixed Position Display", | ||||
|                     "cssClass": "icon-box-with-dashed-lines", | ||||
|                     "type": "telemetry.fixed", | ||||
|                     "template": fixedTemplate, | ||||
|                     "uses": [], | ||||
|                     "editable": true | ||||
|                 } | ||||
|             ], | ||||
|             "toolbars": [ | ||||
|                 { | ||||
|                     name: "Fixed Position Toolbar", | ||||
|                     key: "fixed.position", | ||||
|                     description: "Toolbar for the selected element inside a fixed position display.", | ||||
|                     forSelection: function (selection) { | ||||
|                         if (!selection) { | ||||
|                             return; | ||||
|                         } | ||||
|  | ||||
|                         return ( | ||||
|                             selection[0] && selection[0].context.elementProxy && | ||||
|                             selection[1] && selection[1].context.item.type === 'telemetry.fixed' || | ||||
|                             selection[0] && selection[0].context.item.type === 'telemetry.fixed' | ||||
|                         ); | ||||
|                     }, | ||||
|                     toolbar: function (selection) { | ||||
|                         var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"]; | ||||
|                         var boxProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill"]; | ||||
|                         var textProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "text"]; | ||||
|                         var lineProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "x2", "y2"]; | ||||
|                         var telemetryProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "titled"]; | ||||
|                         var fixedPageProperties = ["add"]; | ||||
|  | ||||
|                         var properties = [], | ||||
|                             fixedItem = selection[0] && selection[0].context.item, | ||||
|                             elementProxy = selection[0] && selection[0].context.elementProxy, | ||||
|                             domainObject = selection[1] && selection[1].context.item, | ||||
|                             path; | ||||
|  | ||||
|                         if (elementProxy) { | ||||
|                             var type = elementProxy.element.type; | ||||
|                             path = "configuration['fixed-display'].elements[" + elementProxy.index + "]"; | ||||
|                             properties = | ||||
|                                 type === 'fixed.image' ? imageProperties : | ||||
|                                     type === 'fixed.text' ? textProperties : | ||||
|                                         type === 'fixed.box' ? boxProperties : | ||||
|                                             type === 'fixed.line' ? lineProperties : | ||||
|                                                 type === 'fixed.telemetry' ? telemetryProperties : []; | ||||
|                         } else if (fixedItem) { | ||||
|                             properties = domainObject && domainObject.type === 'layout' ? [] : fixedPageProperties; | ||||
|                         } | ||||
|  | ||||
|                         return [ | ||||
|                             { | ||||
|                                 control: "menu-button", | ||||
|                                 domainObject: domainObject || selection[0].context.item, | ||||
|                                 method: function (value) { | ||||
|                                     selection[0].context.fixedController.add(value); | ||||
|                                 }, | ||||
|                                 key: "add", | ||||
|                                 cssClass: "icon-plus", | ||||
|                                 text: "Add", | ||||
|                                 options: [ | ||||
|                                     { | ||||
|                                         "name": "Box", | ||||
|                                         "cssClass": "icon-box", | ||||
|                                         "key": "fixed.box" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "name": "Line", | ||||
|                                         "cssClass": "icon-line-horz", | ||||
|                                         "key": "fixed.line" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "name": "Text", | ||||
|                                         "cssClass": "icon-T", | ||||
|                                         "key": "fixed.text" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "name": "Image", | ||||
|                                         "cssClass": "icon-image", | ||||
|                                         "key": "fixed.image" | ||||
|                                     } | ||||
|                                 ] | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "menu-button", | ||||
|                                 domainObject: domainObject, | ||||
|                                 method: function (value) { | ||||
|                                     selection[0].context.fixedController.order( | ||||
|                                         selection[0].context.elementProxy, | ||||
|                                         value | ||||
|                                     ); | ||||
|                                 }, | ||||
|                                 key: "order", | ||||
|                                 cssClass: "icon-layers", | ||||
|                                 title: "Layering", | ||||
|                                 description: "Move the selected object above or below other objects", | ||||
|                                 options: [ | ||||
|                                     { | ||||
|                                         "name": "Move to Top", | ||||
|                                         "cssClass": "icon-arrow-double-up", | ||||
|                                         "key": "top" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "name": "Move Up", | ||||
|                                         "cssClass": "icon-arrow-up", | ||||
|                                         "key": "up" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "name": "Move Down", | ||||
|                                         "cssClass": "icon-arrow-down", | ||||
|                                         "key": "down" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "name": "Move to Bottom", | ||||
|                                         "cssClass": "icon-arrow-double-down", | ||||
|                                         "key": "bottom" | ||||
|                                     } | ||||
|                                 ] | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "color", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".fill", | ||||
|                                 cssClass: "icon-paint-bucket", | ||||
|                                 title: "Fill color", | ||||
|                                 description: "Set fill color", | ||||
|                                 key: 'fill' | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "color", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".stroke", | ||||
|                                 cssClass: "icon-line-horz", | ||||
|                                 title: "Border color", | ||||
|                                 description: "Set border color", | ||||
|                                 key: 'stroke' | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "dialog-button", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".url", | ||||
|                                 cssClass: "icon-image", | ||||
|                                 title: "Image Properties", | ||||
|                                 description: "Edit image properties", | ||||
|                                 key: 'url', | ||||
|                                 dialog: { | ||||
|                                     control: "textfield", | ||||
|                                     name: "Image URL", | ||||
|                                     cssClass: "l-input-lg", | ||||
|                                     required: true | ||||
|                                 } | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "color", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".color", | ||||
|                                 cssClass: "icon-T", | ||||
|                                 title: "Text color", | ||||
|                                 mandatory: true, | ||||
|                                 description: "Set text color", | ||||
|                                 key: 'color' | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "select", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".size", | ||||
|                                 title: "Text size", | ||||
|                                 description: "Set text size", | ||||
|                                 "options": [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) { | ||||
|                                     return { "name": size + " px", "value": size + "px" }; | ||||
|                                 }), | ||||
|                                 key: 'size' | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "numberfield", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".x", | ||||
|                                 text: "X", | ||||
|                                 name: "X", | ||||
|                                 key: "x", | ||||
|                                 cssClass: "l-input-sm", | ||||
|                                 min: "0" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "numberfield", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".y", | ||||
|                                 text: "Y", | ||||
|                                 name: "Y", | ||||
|                                 key: "y", | ||||
|                                 cssClass: "l-input-sm", | ||||
|                                 min: "0" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "numberfield", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".x", | ||||
|                                 text: "X1", | ||||
|                                 name: "X1", | ||||
|                                 key: "x1", | ||||
|                                 cssClass: "l-input-sm", | ||||
|                                 min: "0" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "numberfield", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".y", | ||||
|                                 text: "Y1", | ||||
|                                 name: "Y1", | ||||
|                                 key: "y1", | ||||
|                                 cssClass: "l-input-sm", | ||||
|                                 min: "0" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "numberfield", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".x2", | ||||
|                                 text: "X2", | ||||
|                                 name: "X2", | ||||
|                                 key: "x2", | ||||
|                                 cssClass: "l-input-sm", | ||||
|                                 min: "0" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "numberfield", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".y2", | ||||
|                                 text: "Y2", | ||||
|                                 name: "Y2", | ||||
|                                 key: "y2", | ||||
|                                 cssClass: "l-input-sm", | ||||
|                                 min: "0" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "numberfield", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".height", | ||||
|                                 text: "H", | ||||
|                                 name: "H", | ||||
|                                 key: "height", | ||||
|                                 cssClass: "l-input-sm", | ||||
|                                 description: "Resize object height", | ||||
|                                 min: "1" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "numberfield", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".width", | ||||
|                                 text: "W", | ||||
|                                 name: "W", | ||||
|                                 key: "width", | ||||
|                                 cssClass: "l-input-sm", | ||||
|                                 description: "Resize object width", | ||||
|                                 min: "1" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "checkbox", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".useGrid", | ||||
|                                 name: "Snap to Grid", | ||||
|                                 key: "useGrid" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "dialog-button", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".text", | ||||
|                                 cssClass: "icon-gear", | ||||
|                                 title: "Text Properties", | ||||
|                                 description: "Edit text properties", | ||||
|                                 key: "text", | ||||
|                                 dialog: { | ||||
|                                     control: "textfield", | ||||
|                                     name: "Text", | ||||
|                                     required: true | ||||
|                                 } | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "checkbox", | ||||
|                                 domainObject: domainObject, | ||||
|                                 property: path + ".titled", | ||||
|                                 name: "Show Title", | ||||
|                                 key: "titled" | ||||
|                             }, | ||||
|                             { | ||||
|                                 control: "button", | ||||
|                                 domainObject: domainObject, | ||||
|                                 method: function () { | ||||
|                                     selection[0].context.fixedController.remove( | ||||
|                                         selection[0].context.elementProxy | ||||
|                                     ); | ||||
|                                 }, | ||||
|                                 key: "remove", | ||||
|                                 cssClass: "icon-trash" | ||||
|                             } | ||||
|                         ].filter(function (item) { | ||||
|                             var filtered; | ||||
|  | ||||
|                             properties.forEach(function (property) { | ||||
|                                 if (item.property && item.key === property || | ||||
|                                     item.method && item.key === property) { | ||||
|                                     filtered = item; | ||||
|                                 } | ||||
|                             }); | ||||
|  | ||||
|                             return filtered; | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|             ], | ||||
|             "types": [ | ||||
|                 { | ||||
|                     "key": "telemetry.fixed", | ||||
|                     "name": "Fixed Position Display", | ||||
|                     "cssClass": "icon-box-with-dashed-lines", | ||||
|                     "description": "Collect and display telemetry elements in " + | ||||
|                     "alphanumeric format in a simple canvas workspace. " + | ||||
|                     "Elements can be positioned and sized. " + | ||||
|                     "Lines, boxes and images can be added as well.", | ||||
|                     "priority": 899, | ||||
|                     "delegates": [ | ||||
|                         "telemetry" | ||||
|                     ], | ||||
|                     "features": "creation", | ||||
|                     "contains": [ | ||||
|                         { | ||||
|                             "has": "telemetry" | ||||
|                         } | ||||
|                     ], | ||||
|                     "model": { | ||||
|                         "layoutGrid": [64, 16], | ||||
|                         "composition": [] | ||||
|                     }, | ||||
|                     "properties": [ | ||||
|                         { | ||||
|                             "name": "Layout Grid", | ||||
|                             "control": "composite", | ||||
|                             "items": [ | ||||
|                                 { | ||||
|                                     "name": "Horizontal grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Vertical grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "pattern": "^(\\d*[1-9]\\d*)?$", | ||||
|                             "property": "layoutGrid", | ||||
|                             "conversion": "number[]" | ||||
|                         } | ||||
|                     ], | ||||
|                     "views": [ | ||||
|                         "fixed-display" | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -1,475 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     "./src/FixedController", | ||||
|     "./templates/fixed.html", | ||||
|     "./templates/frame.html", | ||||
|     "./templates/elements/telemetry.html", | ||||
|     "./templates/elements/box.html", | ||||
|     "./templates/elements/line.html", | ||||
|     "./templates/elements/text.html", | ||||
|     "./templates/elements/image.html", | ||||
|     "legacyRegistry" | ||||
| ], function ( | ||||
|     FixedController, | ||||
|     fixedTemplate, | ||||
|     frameTemplate, | ||||
|     telemetryTemplate, | ||||
|     boxTemplate, | ||||
|     lineTemplate, | ||||
|     textTemplate, | ||||
|     imageTemplate, | ||||
|     legacyRegistry     | ||||
| ) { | ||||
|     return function() { | ||||
|         return function (openmct) { | ||||
|             openmct.legacyRegistry.register("platform/features/fixed", { | ||||
|                 "name": "Fixed position components.", | ||||
|                 "description": "Plug in adding Fixed Position object type.", | ||||
|                 "extensions": { | ||||
|                     "views": [ | ||||
|                         { | ||||
|                             "key": "fixed-display", | ||||
|                             "name": "Fixed Position Display", | ||||
|                             "cssClass": "icon-box-with-dashed-lines", | ||||
|                             "type": "telemetry.fixed", | ||||
|                             "template": fixedTemplate, | ||||
|                             "uses": ["composition"], | ||||
|                             "editable": true | ||||
|                         } | ||||
|                     ], | ||||
|                     "templates": [ | ||||
|                         { | ||||
|                             "key": "fixed.telemetry", | ||||
|                             "template": telemetryTemplate | ||||
|                         }, | ||||
|                         { | ||||
|                             "key": "fixed.box", | ||||
|                             "template": boxTemplate | ||||
|                         }, | ||||
|                         { | ||||
|                             "key": "fixed.line", | ||||
|                             "template": lineTemplate | ||||
|                         }, | ||||
|                         { | ||||
|                             "key": "fixed.text", | ||||
|                             "template": textTemplate | ||||
|                         }, | ||||
|                         { | ||||
|                             "key": "fixed.image", | ||||
|                             "template": imageTemplate | ||||
|                         } | ||||
|                     ], | ||||
|                     "controllers": [ | ||||
|                         { | ||||
|                             "key": "FixedController", | ||||
|                             "implementation": FixedController, | ||||
|                             "depends": [ | ||||
|                                 "$scope", | ||||
|                                 "$q", | ||||
|                                 "dialogService", | ||||
|                                 "openmct", | ||||
|                                 "$element" | ||||
|                             ] | ||||
|                         } | ||||
|                     ], | ||||
|                     "toolbars": [ | ||||
|                         { | ||||
|                             name: "Fixed Position Toolbar", | ||||
|                             key: "fixed.position", | ||||
|                             description: "Toolbar for the selected element inside a fixed position display.", | ||||
|                             forSelection: function (selection) { | ||||
|                                 if (!selection) { | ||||
|                                     return; | ||||
|                                 } | ||||
|                                  | ||||
|                                 return (openmct.editor.isEditing() && | ||||
|                                     selection[0] && selection[0].context.elementProxy && | ||||
|                                     ((selection[1] && selection[1].context.item.type === 'telemetry.fixed') || | ||||
|                                     (selection[0] && selection[0].context.item && selection[0].context.item.type === 'telemetry.fixed'))); | ||||
|                             }, | ||||
|                             toolbar: function (selection) { | ||||
|                                 var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"]; | ||||
|                                 var boxProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill"]; | ||||
|                                 var textProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "text"]; | ||||
|                                 var lineProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "x2", "y2"]; | ||||
|                                 var telemetryProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "titled"]; | ||||
|                                 var fixedPageProperties = ["add"]; | ||||
|  | ||||
|                                 var properties = [], | ||||
|                                     fixedItem = selection[0] && selection[0].context.item, | ||||
|                                     elementProxy = selection[0] && selection[0].context.elementProxy, | ||||
|                                     domainObject = selection[1] && selection[1].context.item, | ||||
|                                     path; | ||||
|  | ||||
|                                 if (elementProxy) { | ||||
|                                     var type = elementProxy.element.type; | ||||
|                                     path = "configuration['fixed-display'].elements[" + elementProxy.index + "]"; | ||||
|                                     properties = | ||||
|                                         type === 'fixed.image' ? imageProperties : | ||||
|                                             type === 'fixed.text' ? textProperties : | ||||
|                                                 type === 'fixed.box' ? boxProperties : | ||||
|                                                     type === 'fixed.line' ? lineProperties : | ||||
|                                                         type === 'fixed.telemetry' ? telemetryProperties : []; | ||||
|                                 } else if (fixedItem) { | ||||
|                                     properties = domainObject && domainObject.type === 'layout' ? [] : fixedPageProperties; | ||||
|                                 } | ||||
|  | ||||
|                                 return [ | ||||
|                                     { | ||||
|                                         control: "menu", | ||||
|                                         domainObject: domainObject || selection[0].context.item, | ||||
|                                         method: function (option) { | ||||
|                                             selection[0].context.fixedController.add(option.key); | ||||
|                                         }, | ||||
|                                         key: "add", | ||||
|                                         icon: "icon-plus", | ||||
|                                         label: "Add", | ||||
|                                         options: [ | ||||
|                                             { | ||||
|                                                 "name": "Box", | ||||
|                                                 "class": "icon-box", | ||||
|                                                 "key": "fixed.box" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Line", | ||||
|                                                 "class": "icon-line-horz", | ||||
|                                                 "key": "fixed.line" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Text", | ||||
|                                                 "class": "icon-T", | ||||
|                                                 "key": "fixed.text" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Image", | ||||
|                                                 "class": "icon-image", | ||||
|                                                 "key": "fixed.image" | ||||
|                                             } | ||||
|                                         ] | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "menu", | ||||
|                                         domainObject: domainObject, | ||||
|                                         method: function (option) { | ||||
|                                             console.log('option', option) | ||||
|                                             selection[0].context.fixedController.order( | ||||
|                                                 selection[0].context.elementProxy, | ||||
|                                                 option.key | ||||
|                                             ); | ||||
|                                         }, | ||||
|                                         key: "order", | ||||
|                                         icon: "icon-layers", | ||||
|                                         title: "Move the selected object above or below other objects", | ||||
|                                         options: [ | ||||
|                                             { | ||||
|                                                 "name": "Move to Top", | ||||
|                                                 "class": "icon-arrow-double-up", | ||||
|                                                 "key": "top" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move Up", | ||||
|                                                 "class": "icon-arrow-up", | ||||
|                                                 "key": "up" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move Down", | ||||
|                                                 "class": "icon-arrow-down", | ||||
|                                                 "key": "down" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move to Bottom", | ||||
|                                                 "class": "icon-arrow-double-down", | ||||
|                                                 "key": "bottom" | ||||
|                                             } | ||||
|                                         ] | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "color-picker", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".fill", | ||||
|                                         icon: "icon-paint-bucket", | ||||
|                                         title: "Set fill color", | ||||
|                                         key: 'fill' | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "color-picker", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".stroke", | ||||
|                                         icon: "icon-line-horz", | ||||
|                                         title: "Set border color", | ||||
|                                         key: 'stroke' | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "button", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".url", | ||||
|                                         icon: "icon-image", | ||||
|                                         title: "Edit image properties", | ||||
|                                         key: 'url', | ||||
|                                         dialog: { | ||||
|                                             control: "input", | ||||
|                                             type: "text", | ||||
|                                             name: "Image URL", | ||||
|                                             class: "l-input-lg", | ||||
|                                             required: true | ||||
|                                         } | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "color-picker", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".color", | ||||
|                                         icon: "icon-T", | ||||
|                                         mandatory: true, | ||||
|                                         title: "Set text color", | ||||
|                                         key: 'color' | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "select-menu", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".size", | ||||
|                                         title: "Set text size", | ||||
|                                         key: 'size', | ||||
|                                         options: [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) { | ||||
|                                             return { "value": size + "px"}; | ||||
|                                         }) | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "input", | ||||
|                                         type: "number", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".x", | ||||
|                                         label: "X", | ||||
|                                         title: "X position", | ||||
|                                         key: "x", | ||||
|                                         class: "l-input-sm", | ||||
|                                         min: "0" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "input", | ||||
|                                         type: "number", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".y", | ||||
|                                         label: "Y", | ||||
|                                         title: "Y position", | ||||
|                                         key: "y", | ||||
|                                         class: "l-input-sm", | ||||
|                                         min: "0" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "input", | ||||
|                                         type: "number", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".x", | ||||
|                                         label: "X1", | ||||
|                                         title: "X1 position", | ||||
|                                         key: "x1", | ||||
|                                         class: "l-input-sm", | ||||
|                                         min: "0" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "input", | ||||
|                                         type: "number", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".y", | ||||
|                                         label: "Y1", | ||||
|                                         title: "Y1 position", | ||||
|                                         key: "y1", | ||||
|                                         class: "l-input-sm", | ||||
|                                         min: "0" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "input", | ||||
|                                         type: "number", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".x2", | ||||
|                                         label: "X2", | ||||
|                                         title: "X2 position", | ||||
|                                         key: "x2", | ||||
|                                         class: "l-input-sm", | ||||
|                                         min: "0" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "input", | ||||
|                                         type: "number", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".y2", | ||||
|                                         label: "Y2", | ||||
|                                         title: "Y2 position", | ||||
|                                         key: "y2", | ||||
|                                         class: "l-input-sm", | ||||
|                                         min: "0" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "input", | ||||
|                                         type: "number", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".height", | ||||
|                                         label: "H", | ||||
|                                         title: "Resize object height", | ||||
|                                         key: "height", | ||||
|                                         class: "l-input-sm", | ||||
|                                         min: "1" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "input", | ||||
|                                         type: "number", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".width", | ||||
|                                         label: "W", | ||||
|                                         title: "Resize object width", | ||||
|                                         key: "width", | ||||
|                                         class: "l-input-sm", | ||||
|                                         min: "1" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "toggle-button", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".useGrid", | ||||
|                                         key: "useGrid", | ||||
|                                         options: [ | ||||
|                                             { | ||||
|                                                 value: true, | ||||
|                                                 icon: 'icon-grid-snap-to', | ||||
|                                                 title: 'Snap to grid' | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 value: false, | ||||
|                                                 icon: 'icon-grid-snap-no', | ||||
|                                                 title: "Do not snap to grid" | ||||
|                                             } | ||||
|                                         ] | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "button", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".text", | ||||
|                                         icon: "icon-gear", | ||||
|                                         title: "Edit text properties", | ||||
|                                         key: "text", | ||||
|                                         dialog: { | ||||
|                                             control: "input", | ||||
|                                             type: "text", | ||||
|                                             name: "Text", | ||||
|                                             required: true | ||||
|                                         } | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "toggle-button", | ||||
|                                         domainObject: domainObject, | ||||
|                                         property: path + ".titled", | ||||
|                                         key: "titled", | ||||
|                                         options: [ | ||||
|                                             { | ||||
|                                                 value: true, | ||||
|                                                 icon: 'icon-two-parts-both', | ||||
|                                                 title: 'Show label' | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 value: false, | ||||
|                                                 icon: 'icon-two-parts-one-only', | ||||
|                                                 title: "Hide label" | ||||
|                                             } | ||||
|                                         ] | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         control: "button", | ||||
|                                         domainObject: domainObject, | ||||
|                                         method: function () { | ||||
|                                             selection[0].context.fixedController.remove( | ||||
|                                                 selection[0].context.elementProxy | ||||
|                                             ); | ||||
|                                         }, | ||||
|                                         key: "remove", | ||||
|                                         icon: "icon-trash" | ||||
|                                     } | ||||
|                                 ].filter(function (item) { | ||||
|                                     var filtered; | ||||
|  | ||||
|                                     properties.forEach(function (property) { | ||||
|                                         if (item.property && item.key === property || | ||||
|                                             item.method && item.key === property) { | ||||
|                                             filtered = item; | ||||
|                                         } | ||||
|                                     }); | ||||
|  | ||||
|                                     return filtered; | ||||
|                                 }); | ||||
|                             } | ||||
|                         } | ||||
|                     ], | ||||
|                     "types": [ | ||||
|                         { | ||||
|                             "key": "telemetry.fixed", | ||||
|                             "name": "Fixed Position Display", | ||||
|                             "cssClass": "icon-box-with-dashed-lines", | ||||
|                             "description": "Collect and display telemetry elements in " + | ||||
|                             "alphanumeric format in a simple canvas workspace. " + | ||||
|                             "Elements can be positioned and sized. " + | ||||
|                             "Lines, boxes and images can be added as well.", | ||||
|                             "priority": 899, | ||||
|                             "delegates": [ | ||||
|                                 "telemetry" | ||||
|                             ], | ||||
|                             "features": "creation", | ||||
|                             "contains": [ | ||||
|                                 { | ||||
|                                     "has": "telemetry" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "model": { | ||||
|                                 "layoutGrid": [64, 16], | ||||
|                                 "composition": [] | ||||
|                             }, | ||||
|                             "properties": [ | ||||
|                                 { | ||||
|                                     "name": "Layout Grid", | ||||
|                                     "control": "composite", | ||||
|                                     "items": [ | ||||
|                                         { | ||||
|                                             "name": "Horizontal grid (px)", | ||||
|                                             "control": "textfield", | ||||
|                                             "cssClass": "l-input-sm l-numeric" | ||||
|                                         }, | ||||
|                                         { | ||||
|                                             "name": "Vertical grid (px)", | ||||
|                                             "control": "textfield", | ||||
|                                             "cssClass": "l-input-sm l-numeric" | ||||
|                                         } | ||||
|                                     ], | ||||
|                                     "pattern": "^(\\d*[1-9]\\d*)?$", | ||||
|                                     "property": "layoutGrid", | ||||
|                                     "conversion": "number[]" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "views": [ | ||||
|                                 "fixed-display" | ||||
|                             ] | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|             }); | ||||
|             openmct.legacyRegistry.enable("platform/features/fixed"); | ||||
|         }            | ||||
|     } | ||||
| }); | ||||
| @@ -19,10 +19,10 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <a class="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}" | ||||
| <a class="l-hyperlink s-hyperlink" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}" | ||||
|    ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}" | ||||
|    ng-class="{ | ||||
|    'c-hyperlink--button u-fills-container' : hyperlink.isButton(), | ||||
|    'c-hyperlink--link' : !hyperlink.isButton() }"> | ||||
|     <span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span> | ||||
|        's-button': hyperlink.isButton() | ||||
|    }"> | ||||
|     <span class="label">{{domainObject.getModel().displayText}}</span> | ||||
| </a> | ||||
|   | ||||
							
								
								
									
										356
									
								
								platform/features/layout/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										356
									
								
								platform/features/layout/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,356 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     "./src/LayoutController", | ||||
|     "./src/FixedController", | ||||
|     "./src/LayoutCompositionPolicy", | ||||
|     './src/MCTTriggerModal', | ||||
|     "./res/templates/layout.html", | ||||
|     "./res/templates/fixed.html", | ||||
|     "./res/templates/frame.html", | ||||
|     "./res/templates/elements/telemetry.html", | ||||
|     "./res/templates/elements/box.html", | ||||
|     "./res/templates/elements/line.html", | ||||
|     "./res/templates/elements/text.html", | ||||
|     "./res/templates/elements/image.html", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     LayoutController, | ||||
|     FixedController, | ||||
|     LayoutCompositionPolicy, | ||||
|     MCTTriggerModal, | ||||
|     layoutTemplate, | ||||
|     fixedTemplate, | ||||
|     frameTemplate, | ||||
|     telemetryTemplate, | ||||
|     boxTemplate, | ||||
|     lineTemplate, | ||||
|     textTemplate, | ||||
|     imageTemplate, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
|     legacyRegistry.register("platform/features/layout", { | ||||
|         "name": "Layout components.", | ||||
|         "description": "Plug in adding Layout capabilities.", | ||||
|         "extensions": { | ||||
|             "views": [ | ||||
|                 { | ||||
|                     "key": "layout", | ||||
|                     "name": "Display Layout", | ||||
|                     "cssClass": "icon-layout", | ||||
|                     "type": "layout", | ||||
|                     "template": layoutTemplate, | ||||
|                     "editable": true, | ||||
|                     "uses": [] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "fixed", | ||||
|                     "name": "Fixed Position", | ||||
|                     "cssClass": "icon-box-with-dashed-lines", | ||||
|                     "type": "telemetry.panel", | ||||
|                     "template": fixedTemplate, | ||||
|                     "uses": [ | ||||
|                         "composition" | ||||
|                     ], | ||||
|                     "toolbar": { | ||||
|                         "sections": [ | ||||
|                             { | ||||
|                                 "items": [ | ||||
|                                     { | ||||
|                                         "method": "add", | ||||
|                                         "cssClass": "icon-plus", | ||||
|                                         "control": "menu-button", | ||||
|                                         "text": "Add", | ||||
|                                         "title": "Add", | ||||
|                                         "description": "Add new items", | ||||
|                                         "options": [ | ||||
|                                             { | ||||
|                                                 "name": "Box", | ||||
|                                                 "cssClass": "icon-box", | ||||
|                                                 "key": "fixed.box" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Line", | ||||
|                                                 "cssClass": "icon-line-horz", | ||||
|                                                 "key": "fixed.line" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Text", | ||||
|                                                 "cssClass": "icon-T", | ||||
|                                                 "key": "fixed.text" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Image", | ||||
|                                                 "cssClass": "icon-image", | ||||
|                                                 "key": "fixed.image" | ||||
|                                             } | ||||
|                                         ] | ||||
|                                     } | ||||
|                                 ] | ||||
|                             }, | ||||
|                             { | ||||
|                                 "items": [ | ||||
|                                     { | ||||
|                                         "method": "order", | ||||
|                                         "cssClass": "icon-layers", | ||||
|                                         "control": "menu-button", | ||||
|                                         "title": "Layering", | ||||
|                                         "description": "Move the selected object above or below other objects", | ||||
|                                         "options": [ | ||||
|                                             { | ||||
|                                                 "name": "Move to Top", | ||||
|                                                 "cssClass": "icon-arrow-double-up", | ||||
|                                                 "key": "top" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move Up", | ||||
|                                                 "cssClass": "icon-arrow-up", | ||||
|                                                 "key": "up" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move Down", | ||||
|                                                 "cssClass": "icon-arrow-down", | ||||
|                                                 "key": "down" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move to Bottom", | ||||
|                                                 "cssClass": "icon-arrow-double-down", | ||||
|                                                 "key": "bottom" | ||||
|                                             } | ||||
|                                         ] | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "fill", | ||||
|                                         "cssClass": "icon-paint-bucket", | ||||
|                                         "title": "Fill color", | ||||
|                                         "description": "Set fill color", | ||||
|                                         "control": "color" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "stroke", | ||||
|                                         "cssClass": "icon-line-horz", | ||||
|                                         "title": "Border color", | ||||
|                                         "description": "Set border color", | ||||
|                                         "control": "color" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "color", | ||||
|                                         "cssClass": "icon-T", | ||||
|                                         "title": "Text color", | ||||
|                                         "description": "Set text color", | ||||
|                                         "mandatory": true, | ||||
|                                         "control": "color" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "url", | ||||
|                                         "cssClass": "icon-image", | ||||
|                                         "control": "dialog-button", | ||||
|                                         "title": "Image Properties", | ||||
|                                         "description": "Edit image properties", | ||||
|                                         "dialog": { | ||||
|                                             "control": "textfield", | ||||
|                                             "name": "Image URL", | ||||
|                                             "cssClass": "l-input-lg", | ||||
|                                             "required": true | ||||
|                                         } | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "text", | ||||
|                                         "cssClass": "icon-gear", | ||||
|                                         "control": "dialog-button", | ||||
|                                         "title": "Text Properties", | ||||
|                                         "description": "Edit text properties", | ||||
|                                         "dialog": { | ||||
|                                             "control": "textfield", | ||||
|                                             "name": "Text", | ||||
|                                             "required": true | ||||
|                                         } | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "method": "showTitle", | ||||
|                                         "cssClass": "icon-two-parts-both", | ||||
|                                         "control": "button", | ||||
|                                         "title": "Show title", | ||||
|                                         "description": "Show telemetry element title" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "method": "hideTitle", | ||||
|                                         "cssClass": "icon-two-parts-one-only", | ||||
|                                         "control": "button", | ||||
|                                         "title": "Hide title", | ||||
|                                         "description": "Hide telemetry element title" | ||||
|                                     } | ||||
|                                 ] | ||||
|                             }, | ||||
|                             { | ||||
|                                 "items": [ | ||||
|                                     { | ||||
|                                         "method": "remove", | ||||
|                                         "control": "button", | ||||
|                                         "cssClass": "icon-trash", | ||||
|                                         "title": "Delete", | ||||
|                                         "description": "Delete the selected item" | ||||
|                                     } | ||||
|                                 ] | ||||
|                             } | ||||
|                         ] | ||||
|                     } | ||||
|                 } | ||||
|             ], | ||||
|             "representations": [ | ||||
|                 { | ||||
|                     "key": "frame", | ||||
|                     "template": frameTemplate | ||||
|                 } | ||||
|             ], | ||||
|             "directives": [ | ||||
|                 { | ||||
|                     "key": "mctTriggerModal", | ||||
|                     "implementation": MCTTriggerModal, | ||||
|                     "depends": [ | ||||
|                         "$document" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "controllers": [ | ||||
|                 { | ||||
|                     "key": "LayoutController", | ||||
|                     "implementation": LayoutController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "$element", | ||||
|                         "openmct" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "FixedController", | ||||
|                     "implementation": FixedController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "$q", | ||||
|                         "dialogService", | ||||
|                         "openmct", | ||||
|                         "$element" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "templates": [ | ||||
|                 { | ||||
|                     "key": "fixed.telemetry", | ||||
|                     "template": telemetryTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "fixed.box", | ||||
|                     "template": boxTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "fixed.line", | ||||
|                     "template": lineTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "fixed.text", | ||||
|                     "template": textTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "fixed.image", | ||||
|                     "template": imageTemplate | ||||
|                 } | ||||
|             ], | ||||
|             "policies": [ | ||||
|                 { | ||||
|                     "category": "composition", | ||||
|                     "implementation": LayoutCompositionPolicy | ||||
|                 } | ||||
|             ], | ||||
|             "toolbars": [ | ||||
|                 { | ||||
|                     name: "Display Layout Toolbar", | ||||
|                     key: "layout", | ||||
|                     description: "A toolbar for objects inside a display layout.", | ||||
|                     forSelection: function (selection) { | ||||
|                         // Apply the layout toolbar if the selected object is inside a layout. | ||||
|                         return (selection && selection[1] && selection[1].context.item.type === 'layout'); | ||||
|                     }, | ||||
|                     toolbar: function (selection) { | ||||
|                         return [ | ||||
|                             { | ||||
|                                 control: "checkbox", | ||||
|                                 name: "Show frame", | ||||
|                                 domainObject: selection[1].context.item, | ||||
|                                 property: "configuration.layout.panels[" + selection[0].context.oldItem.id + "].hasFrame" | ||||
|                             } | ||||
|                         ]; | ||||
|                     } | ||||
|                 } | ||||
|             ], | ||||
|             "types": [ | ||||
|                 { | ||||
|                     "key": "layout", | ||||
|                     "name": "Display Layout", | ||||
|                     "cssClass": "icon-layout", | ||||
|                     "description": "Assemble other objects and components together into a reusable screen layout. Working in a simple canvas workspace, simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.", | ||||
|                     "priority": 900, | ||||
|                     "features": "creation", | ||||
|                     "model": { | ||||
|                         "composition": [], | ||||
|                         configuration: { | ||||
|                             layout: { | ||||
|                                 panels: { | ||||
|  | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     "properties": [ | ||||
|                         { | ||||
|                             "name": "Layout Grid", | ||||
|                             "control": "composite", | ||||
|                             "pattern": "^(\\d*[1-9]\\d*)?$", | ||||
|                             "items": [ | ||||
|                                 { | ||||
|                                     "name": "Horizontal grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Vertical grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "key": "layoutGrid", | ||||
|                             "conversion": "number[]" | ||||
|                         }, | ||||
|                         { | ||||
|                             "name": "Advanced", | ||||
|                             "control": "textfield", | ||||
|                             "key": "layoutAdvancedCss", | ||||
|                             "cssClass": "l-input-lg" | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB | 
| @@ -23,18 +23,18 @@ | ||||
|      ng-controller="FixedController as controller"> | ||||
| 
 | ||||
|     <!-- Background grid --> | ||||
|     <div class="l-fixed-position__grid-holder l-grid-holder c-grid" ng-click="controller.bypassSelection($event)"> | ||||
|         <div class="c-grid__x l-grid l-grid-x" | ||||
|     <div class="l-grid-holder" ng-click="controller.bypassSelection($event)"> | ||||
|         <div class="l-grid l-grid-x" | ||||
|              ng-if="!controller.getGridSize()[0] < 3" | ||||
|              ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div> | ||||
|         <div class="c-grid__y l-grid l-grid-y" | ||||
|         <div class="l-grid l-grid-y" | ||||
|              ng-if="!controller.getGridSize()[1] < 3" | ||||
|              ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div> | ||||
|     </div> | ||||
| 
 | ||||
|     <!-- Fixed position elements --> | ||||
|     <div ng-repeat="element in controller.getElements()" | ||||
|          class="l-fixed-position-item s-selectable s-moveable is-selectable is-moveable" | ||||
|          class="l-fixed-position-item s-selectable s-moveable s-hover-border" | ||||
|          ng-style="element.style" | ||||
|          mct-selectable="controller.getContext(element)" | ||||
|          mct-init-select="controller.shouldSelect(element)"> | ||||
| @@ -44,15 +44,15 @@ | ||||
|         </mct-include> | ||||
|     </div> | ||||
|     <!-- Selection highlight, handles --> | ||||
|     <span class="c-frame-edit" ng-if="controller.isElementSelected()"> | ||||
|         <div class="c-frame-edit__move" | ||||
|     <span class="s-selected s-moveable" ng-if="controller.isElementSelected()"> | ||||
|         <div class="l-fixed-position-item t-edit-handle-holder" | ||||
|              mct-drag-down="controller.moveHandle().startDrag()" | ||||
|              mct-drag="controller.moveHandle().continueDrag(delta)" | ||||
|              mct-drag-up="controller.endDrag()" | ||||
|              ng-style="controller.getSelectedElementStyle()"> | ||||
|         </div> | ||||
|         <div ng-repeat="handle in controller.handles()" | ||||
|              class="c-frame-edit__handle c-frame-edit__handle--nwse" | ||||
|              class="l-fixed-position-item-handle edit-corner" | ||||
|              ng-style="handle.style()" | ||||
|              mct-drag-down="handle.startDrag()" | ||||
|              mct-drag="handle.continueDrag(delta)" | ||||
							
								
								
									
										83
									
								
								platform/features/layout/res/templates/layout.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								platform/features/layout/res/templates/layout.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| <!-- | ||||
|  Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  as represented by the Administrator of the National Aeronautics and Space | ||||
|  Administration. All rights reserved. | ||||
|  | ||||
|  Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  "License"); you may not use this file except in compliance with the License. | ||||
|  You may obtain a copy of the License at | ||||
|  http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  | ||||
|  Unless required by applicable law or agreed to in writing, software | ||||
|  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  License for the specific language governing permissions and limitations | ||||
|  under the License. | ||||
|  | ||||
|  Open MCT includes source code licensed under additional open source | ||||
|  licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
|  | ||||
| <div class="abs l-layout {{ domainObject.getModel().layoutAdvancedCss }}" | ||||
|      ng-controller="LayoutController as controller" | ||||
|      ng-click="controller.bypassSelection($event)"> | ||||
|  | ||||
|     <!-- Background grid --> | ||||
|     <div class="l-grid-holder" | ||||
|          ng-show="!controller.drilledIn" | ||||
|          ng-click="controller.bypassSelection($event)"> | ||||
|         <div class="l-grid l-grid-x" | ||||
|              ng-if="!controller.getGridSize()[0] < 3" | ||||
|              ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div> | ||||
|         <div class="l-grid l-grid-y" | ||||
|              ng-if="!controller.getGridSize()[1] < 3" | ||||
|              ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div> | ||||
|     </div> | ||||
|  | ||||
|     <div class="frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}" | ||||
|          data-layout-id="{{childObject.getId() + '-' + $id}}" | ||||
|          ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }" | ||||
|          ng-repeat="childObject in composition" | ||||
|          ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)" | ||||
|          mct-selectable="controller.getContext(childObject)" | ||||
|          ng-dblclick="controller.drill($event, childObject)" | ||||
|          ng-style="controller.getFrameStyle(childObject.getId())"> | ||||
|  | ||||
|         <mct-representation key="'frame'" | ||||
|                             class="t-rep-frame holder contents abs" | ||||
|                             mct-object="childObject"> | ||||
|         </mct-representation> | ||||
|         <!-- Drag handles --> | ||||
|         <span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)"> | ||||
|             <span class="edit-handle edit-move" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|  | ||||
|             <span class="edit-corner edit-resize-nw" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|             <span class="edit-corner edit-resize-ne" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|             <span class="edit-corner edit-resize-sw" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|             <span class="edit-corner edit-resize-se" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|         </span> | ||||
|     </div> | ||||
|  | ||||
| </div> | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user