Compare commits
	
		
			70 Commits
		
	
	
		
			remove-ang
			...
			snapshot-f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0117c4e7bd | ||
|   | b3d95e1b13 | ||
|   | ba74b67149 | ||
|   | e5fc15bfcf | ||
|   | 0a146d3914 | ||
|   | c153217f6e | ||
|   | ddb64696a5 | ||
|   | d17f3e3d6f | ||
|   | 98d48034fa | ||
|   | 75aa424085 | ||
|   | 20308ce602 | ||
|   | 5412ebb9b9 | ||
|   | b6babea577 | ||
|   | 8fb05d44ca | ||
|   | c105a08cfe | ||
|   | b87375a809 | ||
|   | 9fed056d22 | ||
|   | 251bf21933 | ||
|   | a180bf7c02 | ||
|   | ed8a54f0f9 | ||
|   | ff3c2da0f9 | ||
|   | 28d5821120 | ||
|   | f5ee457274 | ||
|   | 9d2770e4d2 | ||
|   | 8b25009816 | ||
|   | 074fe4481a | ||
|   | fbd928b842 | ||
|   | 110947db09 | ||
|   | ef91e92fbc | ||
|   | d201cac4ac | ||
|   | dcb3ccfec7 | ||
|   | 78522cd4f1 | ||
|   | ca232d45cc | ||
|   | df495c841a | ||
|   | 92a37ef36b | ||
|   | fd731ca430 | ||
|   | 263b1cd3d5 | ||
|   | 978fc8b5a3 | ||
|   | 698ccc5a35 | ||
|   | e5aa5b5a5f | ||
|   | b942988ef8 | ||
|   | 1eec20f2ea | ||
|   | 767a2048eb | ||
|   | e65cf1661c | ||
|   | 0eae48646c | ||
|   | 0ba8a275d2 | ||
|   | d8d32cc3ac | ||
|   | a800848fe1 | ||
|   | 6881d98ba6 | ||
|   | 48d077cd2e | ||
|   | 030dd93c91 | ||
|   | 03bf6fc0a3 | ||
|   | ef0a2ed5d2 | ||
|   | a40aa84752 | ||
|   | d3b69dda82 | ||
|   | d3126ebf5c | ||
|   | 4479cbc7a2 | ||
|   | f8ff44dac0 | ||
|   | 8f4280d15b | ||
|   | 6daa27ff31 | ||
|   | 43f6c3f85d | ||
|   | 1a7c76cf3e | ||
|   | cee9cd7bd1 | ||
|   | c42df20281 | ||
|   | b4149bd2b3 | ||
|   | f436ac9ba0 | ||
|   | 8493b481dd | ||
|   | 28723b59b7 | ||
|   | 9fa7de0b77 | ||
|   | 54bfc84ada | 
| @@ -143,8 +143,8 @@ define([ | ||||
|                             "$window" | ||||
|                         ], | ||||
|                         "group": "windowing", | ||||
|                         "cssClass": "icon-new-window", | ||||
|                         "priority": "preferred" | ||||
|                         "priority": 10, | ||||
|                         "cssClass": "icon-new-window" | ||||
|                     } | ||||
|                 ], | ||||
|                 "runs": [ | ||||
|   | ||||
| @@ -139,7 +139,9 @@ define([ | ||||
|                         ], | ||||
|                         "description": "Edit", | ||||
|                         "category": "view-control", | ||||
|                         "cssClass": "major icon-pencil" | ||||
|                         "cssClass": "major icon-pencil", | ||||
|                         "group": "action", | ||||
|                         "priority": 10 | ||||
|                     }, | ||||
|                     { | ||||
|                         "key": "properties", | ||||
| @@ -150,6 +152,8 @@ define([ | ||||
|                         "implementation": PropertiesAction, | ||||
|                         "cssClass": "major icon-pencil", | ||||
|                         "name": "Edit Properties...", | ||||
|                         "group": "action", | ||||
|                         "priority": 10, | ||||
|                         "description": "Edit properties of this object.", | ||||
|                         "depends": [ | ||||
|                             "dialogService" | ||||
|   | ||||
| @@ -66,6 +66,8 @@ define([ | ||||
|                         "description": "Move object to another location.", | ||||
|                         "cssClass": "icon-move", | ||||
|                         "category": "contextual", | ||||
|                         "group": "action", | ||||
|                         "priority": 9, | ||||
|                         "implementation": MoveAction, | ||||
|                         "depends": [ | ||||
|                             "policyService", | ||||
| @@ -79,6 +81,8 @@ define([ | ||||
|                         "description": "Duplicate object to another location.", | ||||
|                         "cssClass": "icon-duplicate", | ||||
|                         "category": "contextual", | ||||
|                         "group": "action", | ||||
|                         "priority": 8, | ||||
|                         "implementation": CopyAction, | ||||
|                         "depends": [ | ||||
|                             "$log", | ||||
| @@ -95,6 +99,8 @@ define([ | ||||
|                         "description": "Create Link to object in another location.", | ||||
|                         "cssClass": "icon-link", | ||||
|                         "category": "contextual", | ||||
|                         "group": "action", | ||||
|                         "priority": 7, | ||||
|                         "implementation": LinkAction, | ||||
|                         "depends": [ | ||||
|                             "policyService", | ||||
|   | ||||
| @@ -47,6 +47,8 @@ define([ | ||||
|                             "implementation": ExportAsJSONAction, | ||||
|                             "category": "contextual", | ||||
|                             "cssClass": "icon-export", | ||||
|                             "group": "json", | ||||
|                             "priority": 2, | ||||
|                             "depends": [ | ||||
|                                 "openmct", | ||||
|                                 "exportService", | ||||
| @@ -61,6 +63,8 @@ define([ | ||||
|                             "implementation": ImportAsJSONAction, | ||||
|                             "category": "contextual", | ||||
|                             "cssClass": "icon-import", | ||||
|                             "group": "json", | ||||
|                             "priority": 2, | ||||
|                             "depends": [ | ||||
|                                 "exportService", | ||||
|                                 "identifierService", | ||||
|   | ||||
| @@ -242,7 +242,9 @@ define([ | ||||
|  | ||||
|         this.overlays = new OverlayAPI.default(); | ||||
|  | ||||
|         this.contextMenu = new api.ContextMenuRegistry(); | ||||
|         this.menus = new api.MenuAPI(this); | ||||
|  | ||||
|         this.actions = new api.ActionsAPI(this); | ||||
|  | ||||
|         this.router = new ApplicationRouter(); | ||||
|  | ||||
| @@ -271,6 +273,7 @@ define([ | ||||
|         this.install(this.plugins.URLTimeSettingsSynchronizer()); | ||||
|         this.install(this.plugins.NotificationIndicator()); | ||||
|         this.install(this.plugins.NewFolderAction()); | ||||
|         this.install(this.plugins.ViewDatumAction()); | ||||
|     } | ||||
|  | ||||
|     MCT.prototype = Object.create(EventEmitter.prototype); | ||||
|   | ||||
| @@ -35,5 +35,5 @@ export default function LegacyActionAdapter(openmct, legacyActions) { | ||||
|  | ||||
|     legacyActions.filter(contextualCategoryOnly) | ||||
|         .map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction)) | ||||
|         .forEach(openmct.contextMenu.registerAction); | ||||
|         .forEach(openmct.actions.register); | ||||
| } | ||||
|   | ||||
| @@ -31,6 +31,8 @@ export default class LegacyContextMenuAction { | ||||
|         this.description = LegacyAction.definition.description; | ||||
|         this.cssClass = LegacyAction.definition.cssClass; | ||||
|         this.LegacyAction = LegacyAction; | ||||
|         this.group = LegacyAction.definition.group; | ||||
|         this.priority = LegacyAction.definition.priority; | ||||
|     } | ||||
|  | ||||
|     invoke(objectPath) { | ||||
|   | ||||
							
								
								
									
										178
									
								
								src/api/actions/ActionCollection.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/api/actions/ActionCollection.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| import EventEmitter from 'EventEmitter'; | ||||
| import _ from 'lodash'; | ||||
|  | ||||
| class ActionCollection extends EventEmitter { | ||||
|     constructor(applicableActions, objectPath, view, openmct) { | ||||
|         super(); | ||||
|  | ||||
|         this.applicableActions = applicableActions; | ||||
|         this.openmct = openmct; | ||||
|         this.objectPath = objectPath; | ||||
|         this.view = view; | ||||
|         this.objectUnsubscribes = []; | ||||
|  | ||||
|         let debounceOptions = { | ||||
|             leading: false, | ||||
|             trailing: true | ||||
|         }; | ||||
|  | ||||
|         this._updateActions = _.debounce(this._updateActions.bind(this), 150, debounceOptions); | ||||
|         this._update = _.debounce(this._update.bind(this), 150, debounceOptions); | ||||
|  | ||||
|         this._observeObjectPath(); | ||||
|         this._initializeActions(); | ||||
|  | ||||
|         this.openmct.editor.on('isEditing', this._updateActions); | ||||
|     } | ||||
|  | ||||
|     disable(actionKeys) { | ||||
|         actionKeys.forEach(actionKey => { | ||||
|             if (this.applicableActions[actionKey]) { | ||||
|                 this.applicableActions[actionKey].isDisabled = true; | ||||
|             } | ||||
|         }); | ||||
|         this._update(); | ||||
|     } | ||||
|  | ||||
|     enable(actionKeys) { | ||||
|         actionKeys.forEach(actionKey => { | ||||
|             if (this.applicableActions[actionKey]) { | ||||
|                 this.applicableActions[actionKey].isDisabled = false; | ||||
|             } | ||||
|         }); | ||||
|         this._update(); | ||||
|     } | ||||
|  | ||||
|     hide(actionKeys) { | ||||
|         actionKeys.forEach(actionKey => { | ||||
|             if (this.applicableActions[actionKey]) { | ||||
|                 this.applicableActions[actionKey].isHidden = true; | ||||
|             } | ||||
|         }); | ||||
|         this._update(); | ||||
|     } | ||||
|  | ||||
|     show(actionKeys) { | ||||
|         actionKeys.forEach(actionKey => { | ||||
|             if (this.applicableActions[actionKey]) { | ||||
|                 this.applicableActions[actionKey].isHidden = false; | ||||
|             } | ||||
|         }); | ||||
|         this._update(); | ||||
|     } | ||||
|  | ||||
|     destroy() { | ||||
|         this.objectUnsubscribes.forEach(unsubscribe => { | ||||
|             unsubscribe(); | ||||
|         }); | ||||
|  | ||||
|         this.openmct.editor.off('isEditing', this._updateActions); | ||||
|  | ||||
|         this.emit('destroy', this.view); | ||||
|     } | ||||
|  | ||||
|     getVisibleActions() { | ||||
|         let actionsArray = Object.keys(this.applicableActions); | ||||
|         let visibleActions = []; | ||||
|  | ||||
|         actionsArray.forEach(actionKey => { | ||||
|             let action = this.applicableActions[actionKey]; | ||||
|  | ||||
|             if (!action.isHidden) { | ||||
|                 visibleActions.push(action); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         return visibleActions; | ||||
|     } | ||||
|  | ||||
|     getStatusBarActions() { | ||||
|         let actionsArray = Object.keys(this.applicableActions); | ||||
|         let statusBarActions = []; | ||||
|  | ||||
|         actionsArray.forEach(actionKey => { | ||||
|             let action = this.applicableActions[actionKey]; | ||||
|  | ||||
|             if (action.showInStatusBar && !action.isDisabled && !action.isHidden) { | ||||
|                 statusBarActions.push(action); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         return statusBarActions; | ||||
|     } | ||||
|  | ||||
|     _update() { | ||||
|         this.emit('update', this.applicableActions); | ||||
|     } | ||||
|  | ||||
|     _observeObjectPath() { | ||||
|         let actionCollection = this; | ||||
|  | ||||
|         function updateObject(oldObject, newObject) { | ||||
|             Object.assign(oldObject, newObject); | ||||
|  | ||||
|             actionCollection._updateActions(); | ||||
|         } | ||||
|  | ||||
|         this.objectPath.forEach(object => { | ||||
|             if (object) { | ||||
|                 let unsubscribe = this.openmct.objects.observe(object, '*', updateObject.bind(this, object)); | ||||
|  | ||||
|                 this.objectUnsubscribes.push(unsubscribe); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     _initializeActions() { | ||||
|         Object.keys(this.applicableActions).forEach(key => { | ||||
|             this.applicableActions[key].callBack = () => { | ||||
|                 return this.applicableActions[key].invoke(this.objectPath, this.view); | ||||
|             }; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     _updateActions() { | ||||
|         let newApplicableActions = this.openmct.actions._applicableActions(this.objectPath, this.view); | ||||
|  | ||||
|         this.applicableActions = this._mergeOldAndNewActions(this.applicableActions, newApplicableActions); | ||||
|         this._initializeActions(); | ||||
|         this._update(); | ||||
|     } | ||||
|  | ||||
|     _mergeOldAndNewActions(oldActions, newActions) { | ||||
|         let mergedActions = {}; | ||||
|         Object.keys(newActions).forEach(key => { | ||||
|             if (oldActions[key]) { | ||||
|                 mergedActions[key] = oldActions[key]; | ||||
|             } else { | ||||
|                 mergedActions[key] = newActions[key]; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         return mergedActions; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default ActionCollection; | ||||
							
								
								
									
										145
									
								
								src/api/actions/ActionsAPI.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/api/actions/ActionsAPI.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
| import EventEmitter from 'EventEmitter'; | ||||
| import ActionCollection from './ActionCollection'; | ||||
| import _ from 'lodash'; | ||||
|  | ||||
| class ActionsAPI extends EventEmitter { | ||||
|     constructor(openmct) { | ||||
|         super(); | ||||
|  | ||||
|         this._allActions = {}; | ||||
|         this._actionCollections = new WeakMap(); | ||||
|         this._openmct = openmct; | ||||
|  | ||||
|         this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json']; | ||||
|  | ||||
|         this.register = this.register.bind(this); | ||||
|         this.get = this.get.bind(this); | ||||
|         this._applicableActions = this._applicableActions.bind(this); | ||||
|         this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this); | ||||
|     } | ||||
|  | ||||
|     register(actionDefinition) { | ||||
|         this._allActions[actionDefinition.key] = actionDefinition; | ||||
|     } | ||||
|  | ||||
|     get(objectPath, view) { | ||||
|         let viewContext = view && view.getViewContext && view.getViewContext() || {}; | ||||
|  | ||||
|         if (view && !viewContext.skipCache) { | ||||
|             let cachedActionCollection = this._actionCollections.get(view); | ||||
|  | ||||
|             if (cachedActionCollection) { | ||||
|                 return cachedActionCollection; | ||||
|             } else { | ||||
|                 let applicableActions = this._applicableActions(objectPath, view); | ||||
|                 let actionCollection = new ActionCollection(applicableActions, objectPath, view, this._openmct); | ||||
|  | ||||
|                 this._actionCollections.set(view, actionCollection); | ||||
|                 actionCollection.on('destroy', this._updateCachedActionCollections); | ||||
|  | ||||
|                 return actionCollection; | ||||
|             } | ||||
|         } else { | ||||
|             let applicableActions = this._applicableActions(objectPath, view); | ||||
|  | ||||
|             Object.keys(applicableActions).forEach(key => { | ||||
|                 let action = applicableActions[key]; | ||||
|  | ||||
|                 action.callBack = () => { | ||||
|                     return action.invoke(objectPath, view); | ||||
|                 }; | ||||
|             }); | ||||
|  | ||||
|             return applicableActions; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     updateGroupOrder(groupArray) { | ||||
|         this._groupOrder = groupArray; | ||||
|     } | ||||
|  | ||||
|     _updateCachedActionCollections(key) { | ||||
|         if (this._actionCollections.has(key)) { | ||||
|             let actionCollection = this._actionCollections.get(key); | ||||
|             actionCollection.off('destroy', this._updateCachedActionCollections); | ||||
|  | ||||
|             this._actionCollections.delete(key); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     _applicableActions(objectPath, view) { | ||||
|         let actionsObject = {}; | ||||
|  | ||||
|         let keys = Object.keys(this._allActions).filter(key => { | ||||
|             let actionDefinition = this._allActions[key]; | ||||
|  | ||||
|             if (actionDefinition.appliesTo === undefined) { | ||||
|                 return true; | ||||
|             } else { | ||||
|                 return actionDefinition.appliesTo(objectPath, view); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         keys.forEach(key => { | ||||
|             let action = _.clone(this._allActions[key]); | ||||
|  | ||||
|             actionsObject[key] = action; | ||||
|         }); | ||||
|  | ||||
|         return actionsObject; | ||||
|     } | ||||
|  | ||||
|     _groupAndSortActions(actionsArray) { | ||||
|         if (!Array.isArray(actionsArray) && typeof actionsArray === 'object') { | ||||
|             actionsArray = Object.keys(actionsArray).map(key => actionsArray[key]); | ||||
|         } | ||||
|  | ||||
|         let actionsObject = {}; | ||||
|         let groupedSortedActionsArray = []; | ||||
|  | ||||
|         function sortDescending(a, b) { | ||||
|             return b.priority - a.priority; | ||||
|         } | ||||
|  | ||||
|         actionsArray.forEach(action => { | ||||
|             if (actionsObject[action.group] === undefined) { | ||||
|                 actionsObject[action.group] = [action]; | ||||
|             } else { | ||||
|                 actionsObject[action.group].push(action); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         this._groupOrder.forEach(group => { | ||||
|             let groupArray = actionsObject[group]; | ||||
|  | ||||
|             if (groupArray) { | ||||
|                 groupedSortedActionsArray.push(groupArray.sort(sortDescending)); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         return groupedSortedActionsArray; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default ActionsAPI; | ||||
| @@ -28,8 +28,9 @@ define([ | ||||
|     './telemetry/TelemetryAPI', | ||||
|     './indicators/IndicatorAPI', | ||||
|     './notifications/NotificationAPI', | ||||
|     './contextMenu/ContextMenuAPI', | ||||
|     './Editor' | ||||
|     './Editor', | ||||
|     './menu/MenuAPI', | ||||
|     './actions/ActionsAPI' | ||||
|  | ||||
| ], function ( | ||||
|     TimeAPI, | ||||
| @@ -39,8 +40,9 @@ define([ | ||||
|     TelemetryAPI, | ||||
|     IndicatorAPI, | ||||
|     NotificationAPI, | ||||
|     ContextMenuAPI, | ||||
|     EditorAPI | ||||
|     EditorAPI, | ||||
|     MenuAPI, | ||||
|     ActionsAPI | ||||
| ) { | ||||
|     return { | ||||
|         TimeAPI: TimeAPI, | ||||
| @@ -51,6 +53,7 @@ define([ | ||||
|         IndicatorAPI: IndicatorAPI, | ||||
|         NotificationAPI: NotificationAPI.default, | ||||
|         EditorAPI: EditorAPI, | ||||
|         ContextMenuRegistry: ContextMenuAPI.default | ||||
|         MenuAPI: MenuAPI.default, | ||||
|         ActionsAPI: ActionsAPI.default | ||||
|     }; | ||||
| }); | ||||
|   | ||||
| @@ -1,24 +0,0 @@ | ||||
| <template> | ||||
| <div class="c-menu"> | ||||
|     <ul> | ||||
|         <li | ||||
|             v-for="action in actions" | ||||
|             :key="action.name" | ||||
|             :class="action.cssClass" | ||||
|             :title="action.description" | ||||
|             @click="action.invoke(objectPath)" | ||||
|         > | ||||
|             {{ action.name }} | ||||
|         </li> | ||||
|         <li v-if="actions.length === 0"> | ||||
|             No actions defined. | ||||
|         </li> | ||||
|     </ul> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['actions', 'objectPath'] | ||||
| }; | ||||
| </script> | ||||
| @@ -1,159 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| import ContextMenuComponent from './ContextMenu.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| /** | ||||
|  * The ContextMenuAPI allows the addition of new context menu actions, and for the context menu to be launched from | ||||
|  * custom HTML elements. | ||||
|  * @interface ContextMenuAPI | ||||
|  * @memberof module:openmct | ||||
|  */ | ||||
| class ContextMenuAPI { | ||||
|     constructor() { | ||||
|         this._allActions = []; | ||||
|         this._activeContextMenu = undefined; | ||||
|  | ||||
|         this._hideActiveContextMenu = this._hideActiveContextMenu.bind(this); | ||||
|         this.registerAction = this.registerAction.bind(this); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Defines an item to be added to context menus. Allows specification of text, appearance, and behavior when | ||||
|      * selected. Applicabilioty can be restricted by specification of an `appliesTo` function. | ||||
|      * | ||||
|      * @interface ContextMenuAction | ||||
|      * @memberof module:openmct | ||||
|      * @property {string} name the human-readable name of this view | ||||
|      * @property {string} description a longer-form description (typically | ||||
|      *           a single sentence or short paragraph) of this kind of view | ||||
|      * @property {string} cssClass the CSS class to apply to labels for this | ||||
|      *           view (to add icons, for instance) | ||||
|      * @property {string} key unique key to identify the context menu action | ||||
|      *           (used in custom context menu eg table rows, to identify which actions to include) | ||||
|      * @property {boolean} hideInDefaultMenu optional flag to hide action from showing in the default context menu (tree item) | ||||
|      */ | ||||
|     /** | ||||
|      * @method appliesTo | ||||
|      * @memberof module:openmct.ContextMenuAction# | ||||
|      * @param {DomainObject[]} objectPath the path of the object that the context menu has been invoked on. | ||||
|      * @returns {boolean} true if the action applies to the objects specified in the 'objectPath', otherwise false. | ||||
|      */ | ||||
|     /** | ||||
|      * Code to be executed when the action is selected from a context menu | ||||
|      * @method invoke | ||||
|      * @memberof module:openmct.ContextMenuAction# | ||||
|      * @param {DomainObject[]} objectPath the path of the object to invoke the action on. | ||||
|      */ | ||||
|     /** | ||||
|      * @param {ContextMenuAction} actionDefinition | ||||
|      */ | ||||
|     registerAction(actionDefinition) { | ||||
|         this._allActions.push(actionDefinition); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     _showContextMenuForObjectPath(objectPath, x, y, actionsToBeIncluded) { | ||||
|  | ||||
|         let applicableActions = this._allActions.filter((action) => { | ||||
|  | ||||
|             if (actionsToBeIncluded) { | ||||
|                 if (action.appliesTo === undefined && actionsToBeIncluded.includes(action.key)) { | ||||
|                     return true; | ||||
|                 } | ||||
|  | ||||
|                 return action.appliesTo(objectPath, actionsToBeIncluded) && actionsToBeIncluded.includes(action.key); | ||||
|             } else { | ||||
|                 if (action.appliesTo === undefined) { | ||||
|                     return true; | ||||
|                 } | ||||
|  | ||||
|                 return action.appliesTo(objectPath) && !action.hideInDefaultMenu; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         if (this._activeContextMenu) { | ||||
|             this._hideActiveContextMenu(); | ||||
|         } | ||||
|  | ||||
|         this._activeContextMenu = this._createContextMenuForObject(objectPath, applicableActions); | ||||
|         this._activeContextMenu.$mount(); | ||||
|         document.body.appendChild(this._activeContextMenu.$el); | ||||
|  | ||||
|         let position = this._calculatePopupPosition(x, y, this._activeContextMenu.$el); | ||||
|         this._activeContextMenu.$el.style.left = `${position.x}px`; | ||||
|         this._activeContextMenu.$el.style.top = `${position.y}px`; | ||||
|  | ||||
|         document.addEventListener('click', this._hideActiveContextMenu); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     _calculatePopupPosition(eventPosX, eventPosY, menuElement) { | ||||
|         let menuDimensions = menuElement.getBoundingClientRect(); | ||||
|         let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth; | ||||
|         let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight; | ||||
|  | ||||
|         if (overflowX > 0) { | ||||
|             eventPosX = eventPosX - overflowX; | ||||
|         } | ||||
|  | ||||
|         if (overflowY > 0) { | ||||
|             eventPosY = eventPosY - overflowY; | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             x: eventPosX, | ||||
|             y: eventPosY | ||||
|         }; | ||||
|     } | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     _hideActiveContextMenu() { | ||||
|         document.removeEventListener('click', this._hideActiveContextMenu); | ||||
|         document.body.removeChild(this._activeContextMenu.$el); | ||||
|         this._activeContextMenu.$destroy(); | ||||
|         this._activeContextMenu = undefined; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     _createContextMenuForObject(objectPath, actions) { | ||||
|         return new Vue({ | ||||
|             components: { | ||||
|                 ContextMenu: ContextMenuComponent | ||||
|             }, | ||||
|             provide: { | ||||
|                 actions: actions, | ||||
|                 objectPath: objectPath | ||||
|             }, | ||||
|             template: '<ContextMenu></ContextMenu>' | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| export default ContextMenuAPI; | ||||
							
								
								
									
										67
									
								
								src/api/menu/MenuAPI.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/api/menu/MenuAPI.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| import Menu from './menu.js'; | ||||
|  | ||||
| /** | ||||
|  * The MenuAPI allows the addition of new context menu actions, and for the context menu to be launched from | ||||
|  * custom HTML elements. | ||||
|  * @interface MenuAPI | ||||
|  * @memberof module:openmct | ||||
|  */ | ||||
|  | ||||
| class MenuAPI { | ||||
|     constructor(openmct) { | ||||
|         this.openmct = openmct; | ||||
|  | ||||
|         this.showMenu = this.showMenu.bind(this); | ||||
|         this._clearMenuComponent = this._clearMenuComponent.bind(this); | ||||
|         this._showObjectMenu = this._showObjectMenu.bind(this); | ||||
|     } | ||||
|  | ||||
|     showMenu(x, y, actions) { | ||||
|         if (this.menuComponent) { | ||||
|             this.menuComponent.dismiss(); | ||||
|         } | ||||
|  | ||||
|         let options = { | ||||
|             x, | ||||
|             y, | ||||
|             actions | ||||
|         }; | ||||
|  | ||||
|         this.menuComponent = new Menu(options); | ||||
|         this.menuComponent.once('destroy', this._clearMenuComponent); | ||||
|     } | ||||
|  | ||||
|     _clearMenuComponent() { | ||||
|         this.menuComponent = undefined; | ||||
|         delete this.menuComponent; | ||||
|     } | ||||
|  | ||||
|     _showObjectMenu(objectPath, x, y, actionsToBeIncluded) { | ||||
|         let applicableActions = this.openmct.actions._groupedAndSortedObjectActions(objectPath, actionsToBeIncluded); | ||||
|  | ||||
|         this.showMenu(x, y, applicableActions); | ||||
|     } | ||||
| } | ||||
| export default MenuAPI; | ||||
							
								
								
									
										52
									
								
								src/api/menu/components/Menu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/api/menu/components/Menu.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| <template> | ||||
| <div class="c-menu"> | ||||
|     <ul v-if="actions.length && actions[0].length"> | ||||
|         <template | ||||
|             v-for="(actionGroups, index) in actions" | ||||
|         > | ||||
|             <li | ||||
|                 v-for="action in actionGroups" | ||||
|                 :key="action.name" | ||||
|                 :class="[action.cssClass, action.isDisabled ? 'disabled' : '']" | ||||
|                 :title="action.description" | ||||
|                 @click="action.callBack" | ||||
|             > | ||||
|                 {{ action.name }} | ||||
|             </li> | ||||
|             <div | ||||
|                 v-if="index !== actions.length - 1" | ||||
|                 :key="index" | ||||
|                 class="c-menu__section-separator" | ||||
|             > | ||||
|             </div> | ||||
|             <li | ||||
|                 v-if="actionGroups.length === 0" | ||||
|                 :key="index" | ||||
|             > | ||||
|                 No actions defined. | ||||
|             </li> | ||||
|         </template> | ||||
|     </ul> | ||||
|  | ||||
|     <ul v-else> | ||||
|         <li | ||||
|             v-for="action in actions" | ||||
|             :key="action.name" | ||||
|             :class="action.cssClass" | ||||
|             :title="action.description" | ||||
|             @click="action.callBack" | ||||
|         > | ||||
|             {{ action.name }} | ||||
|         </li> | ||||
|         <li v-if="actions.length === 0"> | ||||
|             No actions defined. | ||||
|         </li> | ||||
|     </ul> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['actions'] | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										94
									
								
								src/api/menu/menu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/api/menu/menu.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
| import EventEmitter from 'EventEmitter'; | ||||
| import MenuComponent from './components/Menu.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| class Menu extends EventEmitter { | ||||
|     constructor(options) { | ||||
|         super(); | ||||
|  | ||||
|         this.options = options; | ||||
|  | ||||
|         this.component = new Vue({ | ||||
|             provide: { | ||||
|                 actions: options.actions | ||||
|             }, | ||||
|             components: { | ||||
|                 MenuComponent | ||||
|             }, | ||||
|             template: '<menu-component />' | ||||
|         }); | ||||
|  | ||||
|         if (options.onDestroy) { | ||||
|             this.once('destroy', options.onDestroy); | ||||
|         } | ||||
|  | ||||
|         this.dismiss = this.dismiss.bind(this); | ||||
|         this.show = this.show.bind(this); | ||||
|  | ||||
|         this.show(); | ||||
|     } | ||||
|  | ||||
|     dismiss() { | ||||
|         this.emit('destroy'); | ||||
|         document.body.removeChild(this.component.$el); | ||||
|         document.removeEventListener('click', this.dismiss); | ||||
|         this.component.$destroy(); | ||||
|     } | ||||
|  | ||||
|     show() { | ||||
|         this.component.$mount(); | ||||
|         document.body.appendChild(this.component.$el); | ||||
|  | ||||
|         let position = this._calculatePopupPosition(this.options.x, this.options.y, this.component.$el); | ||||
|  | ||||
|         this.component.$el.style.left = `${position.x}px`; | ||||
|         this.component.$el.style.top = `${position.y}px`; | ||||
|  | ||||
|         document.addEventListener('click', this.dismiss); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     _calculatePopupPosition(eventPosX, eventPosY, menuElement) { | ||||
|         let menuDimensions = menuElement.getBoundingClientRect(); | ||||
|         let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth; | ||||
|         let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight; | ||||
|  | ||||
|         if (overflowX > 0) { | ||||
|             eventPosX = eventPosX - overflowX; | ||||
|         } | ||||
|  | ||||
|         if (overflowY > 0) { | ||||
|             eventPosY = eventPosY - overflowY; | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             x: eventPosX, | ||||
|             y: eventPosY | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default Menu; | ||||
| @@ -22,6 +22,7 @@ class OverlayAPI { | ||||
|                 this.dismissLastOverlay(); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -127,6 +128,7 @@ class OverlayAPI { | ||||
|  | ||||
|         return progressDialog; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| export default OverlayAPI; | ||||
|   | ||||
| @@ -176,7 +176,10 @@ export default { | ||||
|             this.timestampKey = timeSystem.key; | ||||
|         }, | ||||
|         showContextMenu(event) { | ||||
|             this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS); | ||||
|             let allActions = this.openmct.actions.get(this.currentObjectPath, {}, {viewHistoricalData: true}); | ||||
|             let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]); | ||||
|  | ||||
|             this.openmct.menus.showMenu(event.x, event.y, applicableActions); | ||||
|         }, | ||||
|         resetValues() { | ||||
|             this.value = '---'; | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
| export default class ClearDataAction { | ||||
|     constructor(openmct, appliesToObjects) { | ||||
|         this.name = 'Clear Data for Object'; | ||||
|         this.key = 'clear-data-action'; | ||||
|         this.description = 'Clears current data for object, unsubscribes and resubscribes to data'; | ||||
|         this.cssClass = 'icon-clear-data'; | ||||
|  | ||||
|   | ||||
| @@ -53,7 +53,7 @@ define([ | ||||
|                 openmct.indicators.add(indicator); | ||||
|             } | ||||
|  | ||||
|             openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects)); | ||||
|             openmct.actions.register(new ClearDataAction.default(openmct, appliesToObjects)); | ||||
|         }; | ||||
|     }; | ||||
| }); | ||||
|   | ||||
| @@ -26,12 +26,12 @@ import ClearDataAction from '../clearDataAction.js'; | ||||
| describe('When the Clear Data Plugin is installed,', function () { | ||||
|     const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']); | ||||
|     const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']); | ||||
|     const mockContextMenuProvider = jasmine.createSpyObj('contextMenu', ['registerAction']); | ||||
|     const mockActionsProvider = jasmine.createSpyObj('actions', ['register']); | ||||
|  | ||||
|     const openmct = { | ||||
|         objectViews: mockObjectViews, | ||||
|         indicators: mockIndicatorProvider, | ||||
|         contextMenu: mockContextMenuProvider, | ||||
|         actions: mockActionsProvider, | ||||
|         install: function (plugin) { | ||||
|             plugin(this); | ||||
|         } | ||||
| @@ -51,7 +51,7 @@ describe('When the Clear Data Plugin is installed,', function () { | ||||
|     it('Clear Data context menu action is installed', function () { | ||||
|         openmct.install(ClearDataActionPlugin([])); | ||||
|  | ||||
|         expect(mockContextMenuProvider.registerAction).toHaveBeenCalled(); | ||||
|         expect(mockActionsProvider.register).toHaveBeenCalled(); | ||||
|     }); | ||||
|  | ||||
|     it('clear data action emits a clearData event when invoked', function () { | ||||
|   | ||||
| @@ -64,9 +64,16 @@ define([ | ||||
|                             components: { | ||||
|                                 AlphanumericFormatView: AlphanumericFormatView.default | ||||
|                             }, | ||||
|                             template: '<alphanumeric-format-view></alphanumeric-format-view>' | ||||
|                             template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>' | ||||
|                         }); | ||||
|                     }, | ||||
|                     getViewContext() { | ||||
|                         if (component) { | ||||
|                             return component.$refs.alphanumericFormatView.getViewContext(); | ||||
|                         } else { | ||||
|                             return {}; | ||||
|                         } | ||||
|                     }, | ||||
|                     destroy: function () { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|   | ||||
							
								
								
									
										33
									
								
								src/plugins/displayLayout/actions/CopyToClipboardAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/plugins/displayLayout/actions/CopyToClipboardAction.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import clipboard from '@/utils/clipboard'; | ||||
|  | ||||
| export default class CopyToClipboardAction { | ||||
|     constructor(openmct) { | ||||
|         this.openmct = openmct; | ||||
|  | ||||
|         this.cssClass = 'icon-duplicate'; | ||||
|         this.description = 'Copy to Clipboard action'; | ||||
|         this.group = "action"; | ||||
|         this.key = 'copyToClipboard'; | ||||
|         this.name = 'Copy to Clipboard'; | ||||
|         this.priority = 9; | ||||
|     } | ||||
|  | ||||
|     invoke(objectPath, viewContext) { | ||||
|         const formattedValue = viewContext.formattedValueForCopy(); | ||||
|         clipboard.updateClipboard(formattedValue) | ||||
|             .then(() => { | ||||
|                 this.openmct.notifications.info(`Success : copied to clipboard '${formattedValue}'`); | ||||
|             }) | ||||
|             .catch(() => { | ||||
|                 this.openmct.notifications.error(`Failed : to copy to clipboard '${formattedValue}'`); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     appliesTo(objectPath, viewContext) { | ||||
|         if (viewContext && viewContext.getViewKey) { | ||||
|             return viewContext.getViewKey().includes('alphanumeric-format'); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @@ -22,7 +22,7 @@ | ||||
|  | ||||
| <template> | ||||
| <div | ||||
|     class="l-layout__frame c-frame" | ||||
|     class="l-layout__frame c-frame js-snapshot-frame" | ||||
|     :class="{ | ||||
|         'no-frame': !item.hasFrame, | ||||
|         'u-inspectable': inspectable, | ||||
|   | ||||
| @@ -36,7 +36,7 @@ | ||||
|             'is-missing': domainObject.status === 'missing' | ||||
|         }" | ||||
|         :style="styleObject" | ||||
|         @contextmenu.prevent="showContextMenu" | ||||
|         @contextmenu.prevent.stop="showContextMenu" | ||||
|     > | ||||
|         <div class="is-missing__indicator" | ||||
|              title="This item is missing" | ||||
| @@ -74,10 +74,11 @@ | ||||
| import LayoutFrame from './LayoutFrame.vue'; | ||||
| import printj from 'printj'; | ||||
| import conditionalStylesMixin from "../mixins/objectStyles-mixin"; | ||||
| import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js'; | ||||
|  | ||||
| const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5]; | ||||
| const DEFAULT_POSITION = [1, 1]; | ||||
| const CONTEXT_MENU_ACTIONS = ['viewHistoricalData']; | ||||
| const CONTEXT_MENU_ACTIONS = ['copyToClipboard', 'copyToNotebook', 'viewHistoricalData']; | ||||
|  | ||||
| export default { | ||||
|     makeDefinition(openmct, gridSize, domainObject, position) { | ||||
| @@ -126,10 +127,11 @@ export default { | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             currentObjectPath: undefined, | ||||
|             datum: undefined, | ||||
|             formats: undefined, | ||||
|             domainObject: undefined, | ||||
|             currentObjectPath: undefined | ||||
|             formats: undefined, | ||||
|             viewKey: `alphanumeric-format-${Math.random()}` | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -216,6 +218,18 @@ export default { | ||||
|         this.openmct.time.off("bounds", this.refreshData); | ||||
|     }, | ||||
|     methods: { | ||||
|         getViewContext() { | ||||
|             return { | ||||
|                 getViewKey: () => this.viewKey, | ||||
|                 formattedValueForCopy: this.formattedValueForCopy | ||||
|             }; | ||||
|         }, | ||||
|         formattedValueForCopy() { | ||||
|             const timeFormatterKey = this.openmct.time.timeSystem().key; | ||||
|             const timeFormatter = this.formats[timeFormatterKey]; | ||||
|  | ||||
|             return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue} ${this.unit}`; | ||||
|         }, | ||||
|         requestHistoricalData() { | ||||
|             let bounds = this.openmct.time.bounds(); | ||||
|             let options = { | ||||
| @@ -253,6 +267,16 @@ export default { | ||||
|                 this.requestHistoricalData(this.domainObject); | ||||
|             } | ||||
|         }, | ||||
|         getView() { | ||||
|             return { | ||||
|                 getViewContext() { | ||||
|                     return { | ||||
|                         viewHistoricalData: true, | ||||
|                         skipCache: true | ||||
|                     }; | ||||
|                 } | ||||
|             }; | ||||
|         }, | ||||
|         setObject(domainObject) { | ||||
|             this.domainObject = domainObject; | ||||
|             this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier); | ||||
| @@ -276,12 +300,37 @@ export default { | ||||
|             this.removeSelectable = this.openmct.selection.selectable( | ||||
|                 this.$el, this.context, this.immediatelySelect || this.initSelect); | ||||
|             delete this.immediatelySelect; | ||||
|  | ||||
|             let allActions = this.openmct.actions.get(this.currentObjectPath, this.getView()); | ||||
|  | ||||
|             this.applicableActions = CONTEXT_MENU_ACTIONS.map(actionKey => { | ||||
|                 return allActions[actionKey]; | ||||
|             }); | ||||
|         }, | ||||
|         updateTelemetryFormat(format) { | ||||
|             this.$emit('formatChanged', this.item, format); | ||||
|         }, | ||||
|         showContextMenu(event) { | ||||
|             this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS); | ||||
|         async getContextMenuActions() { | ||||
|             const defaultNotebook = getDefaultNotebook(); | ||||
|             const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier); | ||||
|  | ||||
|             const actionsObject = this.openmct.actions.get(this.currentObjectPath, this.getViewContext(), { viewHistoricalData: true }).applicableActions; | ||||
|             let applicableActionKeys = Object.keys(actionsObject) | ||||
|                 .filter(key => { | ||||
|                     const isCopyToNotebook = actionsObject[key].key === 'copyToNotebook'; | ||||
|                     if (defaultNotebook && isCopyToNotebook) { | ||||
|                         const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`; | ||||
|                         actionsObject[key].name = `Copy to Notebook ${defaultPath}`; | ||||
|                     } | ||||
|  | ||||
|                     return CONTEXT_MENU_ACTIONS.includes(actionsObject[key].key); | ||||
|                 }); | ||||
|  | ||||
|             return applicableActionKeys.map(key => actionsObject[key]); | ||||
|         }, | ||||
|         async showContextMenu(event) { | ||||
|             const contextMenuActions = await this.getContextMenuActions(); | ||||
|             this.openmct.menus.showMenu(event.x, event.y, contextMenuActions); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -26,9 +26,12 @@ import objectUtils from 'objectUtils'; | ||||
| import DisplayLayoutType from './DisplayLayoutType.js'; | ||||
| import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'; | ||||
| import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js'; | ||||
| import CopyToClipboardAction from './actions/CopyToClipboardAction'; | ||||
|  | ||||
| export default function DisplayLayoutPlugin(options) { | ||||
|     return function (openmct) { | ||||
|         openmct.actions.register(new CopyToClipboardAction(openmct)); | ||||
|  | ||||
|         openmct.objectViews.addProvider({ | ||||
|             key: 'layout.view', | ||||
|             canView: function (domainObject) { | ||||
|   | ||||
| @@ -22,7 +22,7 @@ | ||||
|  | ||||
| <template> | ||||
| <div | ||||
|     class="c-fl-container" | ||||
|     class="c-fl-container js-snapshot-frame" | ||||
|     :style="[{'flex-basis': sizeString}]" | ||||
|     :class="{'is-empty': !frames.length}" | ||||
| > | ||||
|   | ||||
| @@ -12,14 +12,14 @@ | ||||
|             :href="objectLink" | ||||
|         > | ||||
|             <div | ||||
|                 class="c-object-label__type-icon c-list-item__type-icon" | ||||
|                 class="c-object-label__type-icon c-list-item__name__type-icon" | ||||
|                 :class="item.type.cssClass" | ||||
|             > | ||||
|                 <span class="is-missing__indicator" | ||||
|                       title="This item is missing" | ||||
|                 ></span> | ||||
|             </div> | ||||
|             <div class="c-object-label__name c-list-item__name">{{ item.model.name }}</div> | ||||
|             <div class="c-object-label__name c-list-item__name__name">{{ item.model.name }}</div> | ||||
|         </a> | ||||
|     </td> | ||||
|     <td class="c-list-item__type"> | ||||
|   | ||||
| @@ -1,11 +1,19 @@ | ||||
| /******************************* LIST ITEM */ | ||||
| .c-list-item { | ||||
|     &__type-icon { | ||||
|     &__name__type-icon { | ||||
|         color: $colorItemTreeIcon; | ||||
|     } | ||||
|  | ||||
|     &__name { | ||||
|     &__name__name { | ||||
|         @include ellipsize(); | ||||
|  | ||||
|         a & { | ||||
|             color: $colorItemFg; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &:not(.c-list-item__name) { | ||||
|         color: $colorItemFgDetails; | ||||
|     } | ||||
|  | ||||
|     &.is-alias { | ||||
|   | ||||
| @@ -28,9 +28,5 @@ | ||||
|         padding-top: $p; | ||||
|         padding-bottom: $p; | ||||
|         width: 25%; | ||||
|  | ||||
|         &:not(.c-list-item__name) { | ||||
|             color: $colorItemFgDetails; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -25,6 +25,8 @@ export default class GoToOriginalAction { | ||||
|         this.name = 'Go To Original'; | ||||
|         this.key = 'goToOriginal'; | ||||
|         this.description = 'Go to the original unlinked instance of this object'; | ||||
|         this.group = 'action'; | ||||
|         this.priority = 4; | ||||
|  | ||||
|         this._openmct = openmct; | ||||
|     } | ||||
|   | ||||
| @@ -23,6 +23,6 @@ import GoToOriginalAction from './goToOriginalAction'; | ||||
|  | ||||
| export default function () { | ||||
|     return function (openmct) { | ||||
|         openmct.contextMenu.registerAction(new GoToOriginalAction(openmct)); | ||||
|         openmct.actions.register(new GoToOriginalAction(openmct)); | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -28,6 +28,8 @@ export default class NewFolderAction { | ||||
|         this.key = 'newFolder'; | ||||
|         this.description = 'Create a new folder'; | ||||
|         this.cssClass = 'icon-folder-new'; | ||||
|         this.group = "action"; | ||||
|         this.priority = 9; | ||||
|  | ||||
|         this._openmct = openmct; | ||||
|         this._dialogForm = { | ||||
|   | ||||
| @@ -23,6 +23,6 @@ import NewFolderAction from './newFolderAction'; | ||||
|  | ||||
| export default function () { | ||||
|     return function (openmct) { | ||||
|         openmct.contextMenu.registerAction(new NewFolderAction(openmct)); | ||||
|         openmct.actions.register(new NewFolderAction(openmct)); | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -40,9 +40,7 @@ describe("the plugin", () => { | ||||
|         openmct.on('start', done); | ||||
|         openmct.startHeadless(); | ||||
|  | ||||
|         newFolderAction = openmct.contextMenu._allActions.filter(action => { | ||||
|             return action.key === 'newFolder'; | ||||
|         })[0]; | ||||
|         newFolderAction = openmct.actions._allActions.newFolder; | ||||
|     }); | ||||
|  | ||||
|     afterEach(() => { | ||||
|   | ||||
							
								
								
									
										39
									
								
								src/plugins/notebook/actions/CopyToNotebookAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/plugins/notebook/actions/CopyToNotebookAction.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| import { getDefaultNotebook } from '../utils/notebook-storage'; | ||||
| import { addNotebookEntry } from '../utils/notebook-entries'; | ||||
|  | ||||
| export default class CopyToNotebookAction { | ||||
|     constructor(openmct) { | ||||
|         this.openmct = openmct; | ||||
|  | ||||
|         this.cssClass = 'icon-duplicate'; | ||||
|         this.description = 'Copy to Notebook action'; | ||||
|         this.group = "action"; | ||||
|         this.key = 'copyToNotebook'; | ||||
|         this.name = 'Copy to Notebook'; | ||||
|         this.priority = 9; | ||||
|     } | ||||
|  | ||||
|     copyToNotebook(entryText) { | ||||
|         const notebookStorage = getDefaultNotebook(); | ||||
|         this.openmct.objects.get(notebookStorage.notebookMeta.identifier) | ||||
|             .then(domainObject => { | ||||
|                 addNotebookEntry(this.openmct, domainObject, notebookStorage, null, entryText); | ||||
|  | ||||
|                 const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`; | ||||
|                 const msg = `Saved to Notebook ${defaultPath}`; | ||||
|                 this.openmct.notifications.info(msg); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     invoke(objectPath, viewContext) { | ||||
|         this.copyToNotebook(viewContext.formattedValueForCopy()); | ||||
|     } | ||||
|  | ||||
|     appliesTo(objectPath, viewContext) { | ||||
|         if (viewContext && viewContext.getViewKey) { | ||||
|             return viewContext.getViewKey().includes('alphanumeric-format'); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @@ -1,29 +1,17 @@ | ||||
| <template> | ||||
| <div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left"> | ||||
|     <button | ||||
|         class="c-button--menu icon-notebook" | ||||
|         class="c-icon-button c-button--menu icon-camera" | ||||
|         title="Take a Notebook Snapshot" | ||||
|         @click="setNotebookTypes" | ||||
|         @click.stop="toggleMenu" | ||||
|         @click.stop.prevent="showMenu" | ||||
|     > | ||||
|         <span class="c-button__label"></span> | ||||
|         <span | ||||
|             title="Take Notebook Snapshot" | ||||
|             class="c-icon-button__label" | ||||
|         > | ||||
|             Snapshot | ||||
|         </span> | ||||
|     </button> | ||||
|     <div | ||||
|         v-show="showMenu" | ||||
|         class="c-menu" | ||||
|     > | ||||
|         <ul> | ||||
|             <li | ||||
|                 v-for="(type, index) in notebookTypes" | ||||
|                 :key="index" | ||||
|                 :class="type.cssClass" | ||||
|                 :title="type.name" | ||||
|                 @click="snapshot(type)" | ||||
|             > | ||||
|                 {{ type.name }} | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -56,58 +44,48 @@ export default { | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             notebookSnapshot: null, | ||||
|             notebookTypes: [], | ||||
|             showMenu: false | ||||
|             notebookSnapshot: null | ||||
|         }; | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.notebookSnapshot = new Snapshot(this.openmct); | ||||
|  | ||||
|         document.addEventListener('click', this.hideMenu); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         document.removeEventListener('click', this.hideMenu); | ||||
|     }, | ||||
|     methods: { | ||||
|         setNotebookTypes() { | ||||
|         showMenu(event) { | ||||
|             const notebookTypes = []; | ||||
|             const defaultNotebook = getDefaultNotebook(); | ||||
|  | ||||
|             if (defaultNotebook) { | ||||
|                 const domainObject = defaultNotebook.domainObject; | ||||
|                 const name = defaultNotebook.notebookMeta.name; | ||||
|                 const sectionName = defaultNotebook.section.name; | ||||
|                 const pageName = defaultNotebook.page.name; | ||||
|                 const defaultPath = `${name} - ${sectionName} - ${pageName}`; | ||||
|  | ||||
|                 if (domainObject.location) { | ||||
|                     const defaultPath = `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`; | ||||
|  | ||||
|                     notebookTypes.push({ | ||||
|                         cssClass: 'icon-notebook', | ||||
|                         name: `Save to Notebook ${defaultPath}`, | ||||
|                         type: NOTEBOOK_DEFAULT | ||||
|                     }); | ||||
|                 } | ||||
|                 notebookTypes.push({ | ||||
|                     cssClass: 'icon-notebook', | ||||
|                     name: `Save to Notebook ${defaultPath}`, | ||||
|                     callBack: () => this.snapshot(NOTEBOOK_DEFAULT, event.target) | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             notebookTypes.push({ | ||||
|                 cssClass: 'icon-notebook', | ||||
|                 cssClass: 'icon-camera', | ||||
|                 name: 'Save to Notebook Snapshots', | ||||
|                 type: NOTEBOOK_SNAPSHOT | ||||
|                 callBack: () => this.snapshot(NOTEBOOK_SNAPSHOT, event.target) | ||||
|             }); | ||||
|  | ||||
|             this.notebookTypes = notebookTypes; | ||||
|             const elementBoundingClientRect = this.$el.getBoundingClientRect(); | ||||
|             const x = elementBoundingClientRect.x; | ||||
|             const y = elementBoundingClientRect.y + elementBoundingClientRect.height; | ||||
|             this.openmct.menus.showMenu(x, y, notebookTypes); | ||||
|         }, | ||||
|         toggleMenu() { | ||||
|             this.showMenu = !this.showMenu; | ||||
|         }, | ||||
|         hideMenu() { | ||||
|             this.showMenu = false; | ||||
|         }, | ||||
|         snapshot(notebook) { | ||||
|             this.hideMenu(); | ||||
|  | ||||
|         snapshot(notebookType, target) { | ||||
|             this.$nextTick(() => { | ||||
|                 const element = document.querySelector('.c-overlay__contents') | ||||
|                     || document.getElementsByClassName('l-shell__main-container')[0]; | ||||
|                 let element = target.closest('.js-snapshot-frame') || target.querySelector('.js-snapshot-frame'); | ||||
|                 if (!element) { | ||||
|                     const container = target.closest('.js-snapshot-container'); | ||||
|                     element = container.querySelector('.js-snapshot-frame'); | ||||
|                 } | ||||
|  | ||||
|                 const bounds = this.openmct.time.bounds(); | ||||
|                 const link = !this.ignoreLink | ||||
| @@ -122,7 +100,7 @@ export default { | ||||
|                     openmct: this.openmct | ||||
|                 }; | ||||
|  | ||||
|                 this.notebookSnapshot.capture(snapshotMeta, notebook.type, element); | ||||
|                 this.notebookSnapshot.capture(snapshotMeta, notebookType, element); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|         <div class="l-browse-bar__start"> | ||||
|             <div class="l-browse-bar__object-name--w"> | ||||
|                 <div class="l-browse-bar__object-name c-object-label"> | ||||
|                     <div class="c-object-label__type-icon icon-notebook"></div> | ||||
|                     <div class="c-object-label__type-icon icon-camera"></div> | ||||
|                     <div class="c-object-label__name"> | ||||
|                         Notebook Snapshots | ||||
|                         <span v-if="snapshots.length" | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="c-indicator c-indicator--clickable icon-notebook" | ||||
| <div class="c-indicator c-indicator--clickable icon-camera" | ||||
|      :class="[ | ||||
|          { 's-status-off': snapshotCount === 0 }, | ||||
|          { 's-status-on': snapshotCount > 0 }, | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import CopyToNotebookAction from './actions/CopyToNotebookAction'; | ||||
| import Notebook from './components/Notebook.vue'; | ||||
| import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue'; | ||||
| import SnapshotContainer from './snapshot-container'; | ||||
| @@ -13,6 +14,8 @@ export default function NotebookPlugin() { | ||||
|  | ||||
|         installed = true; | ||||
|  | ||||
|         openmct.actions.register(new CopyToNotebookAction(openmct)); | ||||
|  | ||||
|         const notebookType = { | ||||
|             name: 'Notebook', | ||||
|             description: 'Create and save timestamped notes with embedded object snapshots.', | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| import { createOpenMct, createMouseEvent, resetApplicationState } from 'utils/testing'; | ||||
| import { createOpenMct, resetApplicationState } from 'utils/testing'; | ||||
| import NotebookPlugin from './plugin'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| @@ -133,90 +133,4 @@ describe("Notebook plugin:", () => { | ||||
|             expect(hasMajorElements).toBe(true); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe("Notebook Snapshots view:", () => { | ||||
|         let snapshotIndicator; | ||||
|         let drawerElement; | ||||
|  | ||||
|         function clickSnapshotIndicator() { | ||||
|             const indicator = element.querySelector('.icon-notebook'); | ||||
|             const button = indicator.querySelector('button'); | ||||
|             const clickEvent = createMouseEvent('click'); | ||||
|  | ||||
|             button.dispatchEvent(clickEvent); | ||||
|         } | ||||
|  | ||||
|         beforeAll(() => { | ||||
|             snapshotIndicator = openmct.indicators.indicatorObjects | ||||
|                 .find(indicator => indicator.key === 'notebook-snapshot-indicator').element; | ||||
|  | ||||
|             element.append(snapshotIndicator); | ||||
|  | ||||
|             return Vue.nextTick(); | ||||
|         }); | ||||
|  | ||||
|         afterAll(() => { | ||||
|             snapshotIndicator.remove(); | ||||
|             if (drawerElement) { | ||||
|                 drawerElement.remove(); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         beforeEach(() => { | ||||
|             drawerElement = document.querySelector('.l-shell__drawer'); | ||||
|         }); | ||||
|  | ||||
|         afterEach(() => { | ||||
|             if (drawerElement) { | ||||
|                 drawerElement.classList.remove('is-expanded'); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         it("has Snapshots indicator", () => { | ||||
|             const hasSnapshotIndicator = snapshotIndicator !== null && snapshotIndicator !== undefined; | ||||
|             expect(hasSnapshotIndicator).toBe(true); | ||||
|         }); | ||||
|  | ||||
|         it("snapshots container has class isExpanded", () => { | ||||
|             let classes = drawerElement.classList; | ||||
|             const isExpandedBefore = classes.contains('is-expanded'); | ||||
|  | ||||
|             clickSnapshotIndicator(); | ||||
|             classes = drawerElement.classList; | ||||
|             const isExpandedAfterFirstClick = classes.contains('is-expanded'); | ||||
|  | ||||
|             const success = isExpandedBefore === false | ||||
|                 && isExpandedAfterFirstClick === true; | ||||
|  | ||||
|             expect(success).toBe(true); | ||||
|         }); | ||||
|  | ||||
|         it("snapshots container does not have class isExpanded", () => { | ||||
|             let classes = drawerElement.classList; | ||||
|             const isExpandedBefore = classes.contains('is-expanded'); | ||||
|  | ||||
|             clickSnapshotIndicator(); | ||||
|             classes = drawerElement.classList; | ||||
|             const isExpandedAfterFirstClick = classes.contains('is-expanded'); | ||||
|  | ||||
|             clickSnapshotIndicator(); | ||||
|             classes = drawerElement.classList; | ||||
|             const isExpandedAfterSecondClick = classes.contains('is-expanded'); | ||||
|  | ||||
|             const success = isExpandedBefore === false | ||||
|                 && isExpandedAfterFirstClick === true | ||||
|                 && isExpandedAfterSecondClick === false; | ||||
|  | ||||
|             expect(success).toBe(true); | ||||
|         }); | ||||
|  | ||||
|         it("show notebook snapshots container text", () => { | ||||
|             clickSnapshotIndicator(); | ||||
|  | ||||
|             const notebookSnapshots = drawerElement.querySelector('.l-browse-bar__object-name'); | ||||
|             const snapshotsText = notebookSnapshots.textContent.trim(); | ||||
|  | ||||
|             expect(snapshotsText).toBe('Notebook Snapshots'); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -49,7 +49,7 @@ export default class Snapshot { | ||||
|             .then(domainObject => { | ||||
|                 addNotebookEntry(this.openmct, domainObject, notebookStorage, embed); | ||||
|  | ||||
|                 const defaultPath = `${domainObject.name} > ${notebookStorage.section.name} > ${notebookStorage.page.name}`; | ||||
|                 const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`; | ||||
|                 const msg = `Saved to Notebook ${defaultPath}`; | ||||
|                 this._showNotification(msg); | ||||
|             }); | ||||
|   | ||||
| @@ -102,7 +102,7 @@ export function createNewEmbed(snapshotMeta, snapshot = '') { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null) { | ||||
| export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null, entryText = '') { | ||||
|     if (!openmct || !domainObject || !notebookStorage) { | ||||
|         return; | ||||
|     } | ||||
| @@ -124,7 +124,7 @@ export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = | ||||
|     defaultEntries.push({ | ||||
|         id, | ||||
|         createdOn: date, | ||||
|         text: '', | ||||
|         text: entryText, | ||||
|         embeds | ||||
|     }); | ||||
|  | ||||
|   | ||||
| @@ -58,7 +58,8 @@ define([ | ||||
|     './newFolderAction/plugin', | ||||
|     './persistence/couch/plugin', | ||||
|     './defaultRootName/plugin', | ||||
|     './timeline/plugin' | ||||
|     './timeline/plugin', | ||||
|     './viewDatumAction/plugin' | ||||
| ], function ( | ||||
|     _, | ||||
|     UTCTimeSystem, | ||||
| @@ -97,7 +98,8 @@ define([ | ||||
|     NewFolderAction, | ||||
|     CouchDBPlugin, | ||||
|     DefaultRootName, | ||||
|     Timeline | ||||
|     Timeline, | ||||
|     ViewDatumAction | ||||
| ) { | ||||
|     const bundleMap = { | ||||
|         LocalStorage: 'platform/persistence/local', | ||||
| @@ -191,6 +193,7 @@ define([ | ||||
|     plugins.ISOTimeFormat = ISOTimeFormat.default; | ||||
|     plugins.DefaultRootName = DefaultRootName.default; | ||||
|     plugins.Timeline = Timeline.default; | ||||
|     plugins.ViewDatumAction = ViewDatumAction.default; | ||||
|  | ||||
|     return plugins; | ||||
| }); | ||||
|   | ||||
| @@ -25,6 +25,8 @@ export default class RemoveAction { | ||||
|         this.key = 'remove'; | ||||
|         this.description = 'Remove this object from its containing object.'; | ||||
|         this.cssClass = "icon-trash"; | ||||
|         this.group = "action"; | ||||
|         this.priority = 1; | ||||
|  | ||||
|         this.openmct = openmct; | ||||
|     } | ||||
| @@ -103,6 +105,16 @@ export default class RemoveAction { | ||||
|         let parentType = parent && this.openmct.types.get(parent.type); | ||||
|         let child = objectPath[0]; | ||||
|         let locked = child.locked ? child.locked : parent && parent.locked; | ||||
|         let isEditing = this.openmct.editor.isEditing(); | ||||
|  | ||||
|         if (isEditing) { | ||||
|             let currentItemInView = this.openmct.router.path[0]; | ||||
|             let domainObject = objectPath[0]; | ||||
|  | ||||
|             if (this.openmct.objects.areIdsEqual(currentItemInView.identifier, domainObject.identifier)) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (locked) { | ||||
|             return false; | ||||
|   | ||||
| @@ -23,6 +23,6 @@ import RemoveAction from "./RemoveAction"; | ||||
|  | ||||
| export default function () { | ||||
|     return function (openmct) { | ||||
|         openmct.contextMenu.registerAction(new RemoveAction(openmct)); | ||||
|         openmct.actions.register(new RemoveAction(openmct)); | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -160,7 +160,6 @@ define([ | ||||
|         processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) { | ||||
|             let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator)); | ||||
|             this.boundedRows.add(telemetryRows); | ||||
|             this.emit('historical-rows-processed'); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -26,6 +26,7 @@ define([], function () { | ||||
|             this.columns = columns; | ||||
|  | ||||
|             this.datum = createNormalizedDatum(datum, columns); | ||||
|             this.fullDatum = datum; | ||||
|             this.limitEvaluator = limitEvaluator; | ||||
|             this.objectKeyString = objectKeyString; | ||||
|         } | ||||
| @@ -87,7 +88,7 @@ define([], function () { | ||||
|         } | ||||
|  | ||||
|         getContextMenuActions() { | ||||
|             return []; | ||||
|             return ['viewDatumAction']; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -54,15 +54,13 @@ define([ | ||||
|             view(domainObject, objectPath) { | ||||
|                 let table = new TelemetryTable(domainObject, openmct); | ||||
|                 let component; | ||||
|  | ||||
|                 let markingProp = { | ||||
|                     enable: true, | ||||
|                     useAlternateControlBar: false, | ||||
|                     rowName: '', | ||||
|                     rowNamePlural: '' | ||||
|                 }; | ||||
|  | ||||
|                 return { | ||||
|                 const view = { | ||||
|                     show: function (element, editMode) { | ||||
|                         component = new Vue({ | ||||
|                             el: element, | ||||
| @@ -78,9 +76,10 @@ define([ | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 table, | ||||
|                                 objectPath | ||||
|                                 objectPath, | ||||
|                                 view | ||||
|                             }, | ||||
|                             template: '<table-component :isEditing="isEditing" :marking="markingProp"/>' | ||||
|                             template: '<table-component ref="tableComponent" :isEditing="isEditing" :marking="markingProp"/>' | ||||
|                         }); | ||||
|                     }, | ||||
|                     onEditModeChange(editMode) { | ||||
| @@ -89,11 +88,22 @@ define([ | ||||
|                     onClearData() { | ||||
|                         table.clearData(); | ||||
|                     }, | ||||
|                     getViewContext() { | ||||
|                         if (component) { | ||||
|                             return component.$refs.tableComponent.getViewContext(); | ||||
|                         } else { | ||||
|                             return { | ||||
|                                 type: 'telemetry-table' | ||||
|                             }; | ||||
|                         } | ||||
|                     }, | ||||
|                     destroy: function (element) { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 return view; | ||||
|             }, | ||||
|             priority() { | ||||
|                 return 1; | ||||
|   | ||||
							
								
								
									
										123
									
								
								src/plugins/telemetryTable/ViewActions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/plugins/telemetryTable/ViewActions.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| let exportCSV = { | ||||
|     name: 'Export Table Data', | ||||
|     key: 'export-csv-all', | ||||
|     description: "Export this view's data", | ||||
|     cssClass: 'icon-download labeled', | ||||
|     invoke: (objectPath, viewProvider) => { | ||||
|         viewProvider.getViewContext().exportAllDataAsCSV(); | ||||
|     }, | ||||
|     group: 'view' | ||||
| }; | ||||
| let exportMarkedRows = { | ||||
|     name: 'Export Marked Rows', | ||||
|     key: 'export-csv-marked', | ||||
|     description: "Export marked rows as CSV", | ||||
|     cssClass: 'icon-download labeled', | ||||
|     invoke: (objectPath, viewProvider) => { | ||||
|         viewProvider.getViewContext().exportMarkedDataAsCSV(); | ||||
|     }, | ||||
|     group: 'view' | ||||
| }; | ||||
| let unmarkAllRows = { | ||||
|     name: 'Unmark All Rows', | ||||
|     key: 'unmark-all-rows', | ||||
|     description: 'Unmark all rows', | ||||
|     cssClass: 'icon-x labeled', | ||||
|     invoke: (objectPath, viewProvider) => { | ||||
|         viewProvider.getViewContext().unmarkAllRows(); | ||||
|     }, | ||||
|     showInStatusBar: true, | ||||
|     group: 'view' | ||||
| }; | ||||
| let pause = { | ||||
|     name: 'Pause', | ||||
|     key: 'pause-data', | ||||
|     description: 'Pause real-time data flow', | ||||
|     cssClass: 'icon-pause', | ||||
|     invoke: (objectPath, viewProvider) => { | ||||
|         viewProvider.getViewContext().togglePauseByButton(); | ||||
|     }, | ||||
|     showInStatusBar: true, | ||||
|     group: 'view' | ||||
| }; | ||||
| let play = { | ||||
|     name: 'Play', | ||||
|     key: 'play-data', | ||||
|     description: 'Continue real-time data flow', | ||||
|     cssClass: 'c-button pause-play is-paused', | ||||
|     invoke: (objectPath, viewProvider) => { | ||||
|         viewProvider.getViewContext().togglePauseByButton(); | ||||
|     }, | ||||
|     showInStatusBar: true, | ||||
|     group: 'view' | ||||
| }; | ||||
| let expandColumns = { | ||||
|     name: 'Expand Columns', | ||||
|     key: 'expand-columns', | ||||
|     description: "Increase column widths to fit currently available data.", | ||||
|     cssClass: 'icon-arrows-right-left labeled', | ||||
|     invoke: (objectPath, viewProvider) => { | ||||
|         viewProvider.getViewContext().expandColumns(); | ||||
|     }, | ||||
|     showInStatusBar: true, | ||||
|     group: 'view' | ||||
| }; | ||||
| let autosizeColumns = { | ||||
|     name: 'Autosize Columns', | ||||
|     key: 'autosize-columns', | ||||
|     description: "Automatically size columns to fit the table into the available space.", | ||||
|     cssClass: 'icon-expand labeled', | ||||
|     invoke: (objectPath, viewProvider) => { | ||||
|         viewProvider.getViewContext().autosizeColumns(); | ||||
|     }, | ||||
|     showInStatusBar: true, | ||||
|     group: 'view' | ||||
| }; | ||||
|  | ||||
| let viewActions = [ | ||||
|     exportCSV, | ||||
|     exportMarkedRows, | ||||
|     unmarkAllRows, | ||||
|     pause, | ||||
|     play, | ||||
|     expandColumns, | ||||
|     autosizeColumns | ||||
| ]; | ||||
|  | ||||
| viewActions.forEach(action => { | ||||
|     action.appliesTo = (objectPath, viewProvider = {}) => { | ||||
|         let viewContext = viewProvider.getViewContext && viewProvider.getViewContext(); | ||||
|  | ||||
|         if (viewContext) { | ||||
|             let type = viewContext.type; | ||||
|  | ||||
|             return type === 'telemetry-table'; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| export default viewActions; | ||||
| @@ -102,7 +102,17 @@ export default { | ||||
|                 selectable[columnKeys] = this.row.columns[columnKeys].selectable; | ||||
|  | ||||
|                 return selectable; | ||||
|             }, {}) | ||||
|             }, {}), | ||||
|             actionsViewContext: { | ||||
|                 getViewContext: () => { | ||||
|                     return { | ||||
|                         viewHistoricalData: true, | ||||
|                         viewDatumAction: true, | ||||
|                         getDatum: this.getDatum, | ||||
|                         skipCache: true | ||||
|                     }; | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -170,14 +180,24 @@ export default { | ||||
|                 event.stopPropagation(); | ||||
|             } | ||||
|         }, | ||||
|         getDatum() { | ||||
|             return this.row.fullDatum; | ||||
|         }, | ||||
|         showContextMenu: function (event) { | ||||
|             event.preventDefault(); | ||||
|  | ||||
|             this.markRow(event); | ||||
|  | ||||
|             this.row.getContextualDomainObject(this.openmct, this.row.objectKeyString).then(domainObject => { | ||||
|                 let contextualObjectPath = this.objectPath.slice(); | ||||
|                 contextualObjectPath.unshift(domainObject); | ||||
|  | ||||
|                 this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions()); | ||||
|                 let allActions = this.openmct.actions.get(contextualObjectPath, this.actionsViewContext); | ||||
|                 let applicableActions = this.row.getContextMenuActions().map(key => allActions[key]); | ||||
|  | ||||
|                 if (applicableActions.length) { | ||||
|                     this.openmct.menus.showMenu(event.x, event.y, applicableActions); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -23,79 +23,6 @@ | ||||
| <div class="c-table-wrapper" | ||||
|      :class="{ 'is-paused': paused }" | ||||
| > | ||||
|     <!-- main contolbar  start--> | ||||
|     <div v-if="!marking.useAlternateControlBar" | ||||
|          class="c-table-control-bar c-control-bar" | ||||
|     > | ||||
|         <button | ||||
|             v-if="allowExport" | ||||
|             v-show="!markedRows.length" | ||||
|             class="c-button icon-download labeled" | ||||
|             title="Export this view's data" | ||||
|             @click="exportAllDataAsCSV()" | ||||
|         > | ||||
|             <span class="c-button__label">Export Table Data</span> | ||||
|         </button> | ||||
|         <button | ||||
|             v-if="allowExport" | ||||
|             v-show="markedRows.length" | ||||
|             class="c-button icon-download labeled" | ||||
|             title="Export marked rows as CSV" | ||||
|             @click="exportMarkedDataAsCSV()" | ||||
|         > | ||||
|             <span class="c-button__label">Export Marked Rows</span> | ||||
|         </button> | ||||
|         <button | ||||
|             v-show="markedRows.length" | ||||
|             class="c-button icon-x labeled" | ||||
|             title="Unmark all rows" | ||||
|             @click="unmarkAllRows()" | ||||
|         > | ||||
|             <span class="c-button__label">Unmark All Rows</span> | ||||
|         </button> | ||||
|         <div | ||||
|             v-if="marking.enable" | ||||
|             class="c-separator" | ||||
|         ></div> | ||||
|         <button | ||||
|             v-if="marking.enable" | ||||
|             class="c-button icon-pause pause-play labeled" | ||||
|             :class=" paused ? 'icon-play is-paused' : 'icon-pause'" | ||||
|             :title="paused ? 'Continue real-time data flow' : 'Pause real-time data flow'" | ||||
|             @click="togglePauseByButton()" | ||||
|         > | ||||
|             <span class="c-button__label"> | ||||
|                 {{ paused ? 'Play' : 'Pause' }} | ||||
|             </span> | ||||
|         </button> | ||||
|  | ||||
|         <template v-if="!isEditing"> | ||||
|             <div | ||||
|                 class="c-separator" | ||||
|             > | ||||
|             </div> | ||||
|             <button | ||||
|                 v-if="isAutosizeEnabled" | ||||
|                 class="c-button icon-arrows-right-left labeled" | ||||
|                 title="Increase column widths to fit currently available data." | ||||
|                 @click="recalculateColumnWidths" | ||||
|             > | ||||
|                 <span class="c-button__label">Expand Columns</span> | ||||
|             </button> | ||||
|             <button | ||||
|                 v-else | ||||
|                 class="c-button icon-expand labeled" | ||||
|                 title="Automatically size columns to fit the table into the available space." | ||||
|                 @click="autosizeColumns" | ||||
|             > | ||||
|                 <span class="c-button__label">Autosize Columns</span> | ||||
|             </button> | ||||
|         </template> | ||||
|  | ||||
|         <slot name="buttons"></slot> | ||||
|     </div> | ||||
|     <!-- main controlbar end --> | ||||
|  | ||||
|     <!-- alternate controlbar start --> | ||||
|     <div v-if="marking.useAlternateControlBar" | ||||
|          class="c-table-control-bar c-control-bar" | ||||
| @@ -113,11 +40,11 @@ | ||||
|  | ||||
|         <button | ||||
|             :class="{'hide-nice': !markedRows.length}" | ||||
|             class="c-button icon-x labeled" | ||||
|             class="c-icon-button icon-x labeled" | ||||
|             title="Deselect All" | ||||
|             @click="unmarkAllRows()" | ||||
|         > | ||||
|             <span class="c-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span> | ||||
|             <span class="c-icon-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span> | ||||
|         </button> | ||||
|  | ||||
|         <slot name="buttons"></slot> | ||||
| @@ -280,7 +207,7 @@ export default { | ||||
|         TelemetryFilterIndicator, | ||||
|         ToggleSwitch | ||||
|     }, | ||||
|     inject: ['table', 'openmct', 'objectPath'], | ||||
|     inject: ['table', 'openmct', 'objectPath', 'view'], | ||||
|     props: { | ||||
|         isEditing: { | ||||
|             type: Boolean, | ||||
| @@ -383,6 +310,34 @@ export default { | ||||
|         markedRows: { | ||||
|             handler(newVal, oldVal) { | ||||
|                 this.$emit('marked-rows-updated', newVal, oldVal); | ||||
|  | ||||
|                 if (newVal.length > 0) { | ||||
|                     this.viewActionsCollection.enable(['export-csv-marked', 'unmark-all-rows']); | ||||
|                 } else if (newVal.length === 0) { | ||||
|                     this.viewActionsCollection.disable(['export-csv-marked', 'unmark-all-rows']); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         paused: { | ||||
|             handler(newVal) { | ||||
|                 if (newVal) { | ||||
|                     this.viewActionsCollection.hide(['pause-data']); | ||||
|                     this.viewActionsCollection.show(['play-data']); | ||||
|                 } else { | ||||
|                     this.viewActionsCollection.hide(['play-data']); | ||||
|                     this.viewActionsCollection.show(['pause-data']); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         isAutosizeEnabled: { | ||||
|             handler(newVal) { | ||||
|                 if (newVal) { | ||||
|                     this.viewActionsCollection.show(['expand-columns']); | ||||
|                     this.viewActionsCollection.hide(['autosize-columns']); | ||||
|                 } else { | ||||
|                     this.viewActionsCollection.show(['autosize-columns']); | ||||
|                     this.viewActionsCollection.hide(['expand-columns']); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
| @@ -395,6 +350,9 @@ export default { | ||||
|         this.rowsRemoved = _.throttle(this.rowsRemoved, 200); | ||||
|         this.scroll = _.throttle(this.scroll, 100); | ||||
|  | ||||
|         this.viewActionsCollection = this.openmct.actions.get(this.objectPath, this.view); | ||||
|         this.initializeViewActions(); | ||||
|  | ||||
|         this.table.on('object-added', this.addObject); | ||||
|         this.table.on('object-removed', this.removeObject); | ||||
|         this.table.on('outstanding-requests', this.outstandingRequests); | ||||
| @@ -833,7 +791,7 @@ export default { | ||||
|  | ||||
|                 for (let i = firstRowIndex; i <= lastRowIndex; i++) { | ||||
|                     let row = allRows[i]; | ||||
|                     row.marked = true; | ||||
|                     this.$set(row, 'marked', true); | ||||
|  | ||||
|                     if (row !== baseRow) { | ||||
|                         this.markedRows.push(row); | ||||
| @@ -894,6 +852,40 @@ export default { | ||||
|             this.isAutosizeEnabled = true; | ||||
|  | ||||
|             this.$nextTick().then(this.calculateColumnWidths); | ||||
|         }, | ||||
|         getViewContext() { | ||||
|             return { | ||||
|                 type: 'telemetry-table', | ||||
|                 exportAllDataAsCSV: this.exportAllDataAsCSV, | ||||
|                 exportMarkedRows: this.exportMarkedRows, | ||||
|                 unmarkAllRows: this.unmarkAllRows, | ||||
|                 togglePauseByButton: this.togglePauseByButton, | ||||
|                 expandColumns: this.recalculateColumnWidths, | ||||
|                 autosizeColumns: this.autosizeColumns | ||||
|             }; | ||||
|         }, | ||||
|         initializeViewActions() { | ||||
|             if (this.markedRows.length > 0) { | ||||
|                 this.viewActionsCollection.enable(['export-csv-marked', 'unmark-all-rows']); | ||||
|             } else if (this.markedRows.length === 0) { | ||||
|                 this.viewActionsCollection.disable(['export-csv-marked', 'unmark-all-rows']); | ||||
|             } | ||||
|  | ||||
|             if (this.paused) { | ||||
|                 this.viewActionsCollection.hide(['pause-data']); | ||||
|                 this.viewActionsCollection.show(['play-data']); | ||||
|             } else { | ||||
|                 this.viewActionsCollection.hide(['play-data']); | ||||
|                 this.viewActionsCollection.show(['pause-data']); | ||||
|             } | ||||
|  | ||||
|             if (this.isAutosizeEnabled) { | ||||
|                 this.viewActionsCollection.show(['expand-columns']); | ||||
|                 this.viewActionsCollection.hide(['autosize-columns']); | ||||
|             } else { | ||||
|                 this.viewActionsCollection.show(['autosize-columns']); | ||||
|                 this.viewActionsCollection.hide(['expand-columns']); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -23,11 +23,13 @@ | ||||
| define([ | ||||
|     './TelemetryTableViewProvider', | ||||
|     './TableConfigurationViewProvider', | ||||
|     './TelemetryTableType' | ||||
|     './TelemetryTableType', | ||||
|     './ViewActions' | ||||
| ], function ( | ||||
|     TelemetryTableViewProvider, | ||||
|     TableConfigurationViewProvider, | ||||
|     TelemetryTableType | ||||
|     TelemetryTableType, | ||||
|     TelemetryTableViewActions | ||||
| ) { | ||||
|     return function plugin() { | ||||
|         return function install(openmct) { | ||||
| @@ -41,6 +43,10 @@ define([ | ||||
|                     return true; | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             TelemetryTableViewActions.default.forEach(action => { | ||||
|                 openmct.actions.register(action); | ||||
|             }); | ||||
|         }; | ||||
|     }; | ||||
| }); | ||||
|   | ||||
| @@ -168,6 +168,8 @@ describe("the plugin", () => { | ||||
|                 return telemetryPromise; | ||||
|             }); | ||||
|  | ||||
|             openmct.router.path = [testTelemetryObject]; | ||||
|  | ||||
|             applicableViews = openmct.objectViews.get(testTelemetryObject); | ||||
|             tableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'table'); | ||||
|             tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]); | ||||
|   | ||||
							
								
								
									
										69
									
								
								src/plugins/viewDatumAction/ViewDatumAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/plugins/viewDatumAction/ViewDatumAction.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| import MetadataListView from './components/MetadataList.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default class ViewDatumAction { | ||||
|     constructor(openmct) { | ||||
|         this.name = 'View Full Datum'; | ||||
|         this.key = 'viewDatumAction'; | ||||
|         this.description = 'View full value of datum received'; | ||||
|         this.cssClass = 'icon-object'; | ||||
|  | ||||
|         this._openmct = openmct; | ||||
|     } | ||||
|     invoke(objectPath, view) { | ||||
|         let viewContext = view.getViewContext && view.getViewContext(); | ||||
|         let attributes = viewContext.getDatum && viewContext.getDatum(); | ||||
|         let component = new Vue ({ | ||||
|             provide: { | ||||
|                 name: this.name, | ||||
|                 attributes | ||||
|             }, | ||||
|             components: { | ||||
|                 MetadataListView | ||||
|             }, | ||||
|             template: '<MetadataListView />' | ||||
|         }); | ||||
|  | ||||
|         this._openmct.overlays.overlay({ | ||||
|             element: component.$mount().$el, | ||||
|             size: 'large', | ||||
|             dismissable: true, | ||||
|             onDestroy: () => { | ||||
|                 component.$destroy(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     appliesTo(objectPath, view = {}) { | ||||
|         let viewContext = view.getViewContext && view.getViewContext() || {}; | ||||
|         let datum = viewContext.getDatum; | ||||
|         let enabled = viewContext.viewDatumAction; | ||||
|  | ||||
|         if (enabled && datum) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/plugins/viewDatumAction/components/MetadataList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/plugins/viewDatumAction/components/MetadataList.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| <template> | ||||
| <div class="c-attributes-view"> | ||||
|     <div class="c-overlay__top-bar"> | ||||
|         <div class="c-overlay__dialog-title">{{ name }}</div> | ||||
|     </div> | ||||
|     <div class="c-overlay__contents-main l-preview-window__object-view"> | ||||
|         <ul class="c-attributes-view__content"> | ||||
|             <li | ||||
|                 v-for="attribute in Object.keys(attributes)" | ||||
|                 :key="attribute" | ||||
|             > | ||||
|                 <span class="c-attributes-view__grid-item__label">{{ attribute }}</span> | ||||
|                 <span class="c-attributes-view__grid-item__value">{{ attributes[attribute] }}</span> | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['name', 'attributes'] | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										30
									
								
								src/plugins/viewDatumAction/components/metadata-list.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/plugins/viewDatumAction/components/metadata-list.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| .c-attributes-view { | ||||
|     display: flex; | ||||
|     flex: 1 1 auto; | ||||
|     flex-direction: column; | ||||
|  | ||||
|     > * { | ||||
|         flex: 0 0 auto; | ||||
|     } | ||||
|  | ||||
|     &__content { | ||||
|         $p: 3px; | ||||
|  | ||||
|         display: grid; | ||||
|         grid-template-columns: max-content 1fr; | ||||
|         grid-row-gap: $p; | ||||
|  | ||||
|         li { display: contents; } | ||||
|  | ||||
|         [class*="__grid-item"] { | ||||
|             border-bottom: 1px solid rgba(#999, 0.2); | ||||
|             padding: 0 5px $p 0; | ||||
|         } | ||||
|  | ||||
|         [class*="__label"] { | ||||
|             opacity: 0.8; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/plugins/viewDatumAction/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/plugins/viewDatumAction/plugin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| import ViewDatumAction from './ViewDatumAction.js'; | ||||
|  | ||||
| export default function plugin() { | ||||
|     return function install(openmct) { | ||||
|         openmct.actions.register(new ViewDatumAction(openmct)); | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										95
									
								
								src/plugins/viewDatumAction/pluginSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/plugins/viewDatumAction/pluginSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
| import { | ||||
|     createOpenMct, | ||||
|     resetApplicationState | ||||
| } from 'utils/testing'; | ||||
|  | ||||
| describe("the plugin", () => { | ||||
|     let openmct; | ||||
|     let viewDatumAction; | ||||
|     let mockObjectPath; | ||||
|     let mockView; | ||||
|     let mockDatum; | ||||
|  | ||||
|     beforeEach((done) => { | ||||
|         openmct = createOpenMct(); | ||||
|  | ||||
|         openmct.on('start', done); | ||||
|         openmct.startHeadless(); | ||||
|  | ||||
|         viewDatumAction = openmct.actions._allActions.viewDatumAction; | ||||
|  | ||||
|         mockObjectPath = [{ | ||||
|             name: 'mock object', | ||||
|             type: 'telemetry-table', | ||||
|             identifier: { | ||||
|                 key: 'mock-object', | ||||
|                 namespace: '' | ||||
|             } | ||||
|         }]; | ||||
|  | ||||
|         mockDatum = { | ||||
|             time: 123456789, | ||||
|             sin: 0.4455512, | ||||
|             cos: 0.4455512 | ||||
|         }; | ||||
|  | ||||
|         mockView = { | ||||
|             getViewContext: () => { | ||||
|                 return { | ||||
|                     viewDatumAction: true, | ||||
|                     getDatum: () => { | ||||
|                         return mockDatum; | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         done(); | ||||
|     }); | ||||
|  | ||||
|     afterEach(() => { | ||||
|         return resetApplicationState(openmct); | ||||
|     }); | ||||
|  | ||||
|     it('installs the view datum action', () => { | ||||
|         expect(viewDatumAction).toBeDefined(); | ||||
|     }); | ||||
|  | ||||
|     describe('when invoked', () => { | ||||
|  | ||||
|         beforeEach((done) => { | ||||
|             openmct.overlays.overlay = function (options) {}; | ||||
|  | ||||
|             spyOn(openmct.overlays, 'overlay'); | ||||
|  | ||||
|             viewDatumAction.invoke(mockObjectPath, mockView); | ||||
|  | ||||
|             done(); | ||||
|         }); | ||||
|  | ||||
|         it('uses an overlay to show user datum values', () => { | ||||
|             expect(openmct.overlays.overlay).toHaveBeenCalled(); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -217,7 +217,7 @@ $colorBtnActiveFg: $colorOkFg; | ||||
| $colorBtnSelectedBg: $colorSelectedBg; | ||||
| $colorBtnSelectedFg: $colorSelectedFg; | ||||
| $colorClickIconButton: $colorKey; | ||||
| $colorClickIconButtonBgHov: rgba($colorKey, 0.6); | ||||
| $colorClickIconButtonBgHov: rgba($colorKey, 0.3); | ||||
| $colorClickIconButtonFgHov: $colorKeyHov; | ||||
| $colorDropHint: $colorKey; | ||||
| $colorDropHintBg: pushBack($colorDropHint, 10%); | ||||
| @@ -377,6 +377,11 @@ $colorItemTreeVC: $colorDisclosureCtrl; | ||||
| $colorItemTreeVCHover: $colorDisclosureCtrlHov; | ||||
| $shdwItemTreeIcon: none; | ||||
|  | ||||
| // Layout frame controls | ||||
| $frameControlsColorFg: white; | ||||
| $frameControlsColorBg: $colorKey; | ||||
| $frameControlsShdw: $shdwMenu; | ||||
|  | ||||
| // Images | ||||
| $colorThumbHoverBg: $colorItemTreeHoverBg; | ||||
|  | ||||
|   | ||||
| @@ -381,6 +381,11 @@ $colorItemTreeVC: $colorDisclosureCtrl; | ||||
| $colorItemTreeVCHover: $colorDisclosureCtrlHov; | ||||
| $shdwItemTreeIcon: none; | ||||
|  | ||||
| // Layout frame controls | ||||
| $frameControlsColorFg: white; | ||||
| $frameControlsColorBg: $colorKey; | ||||
| $frameControlsShdw: $shdwMenu; | ||||
|  | ||||
| // Images | ||||
| $colorThumbHoverBg: $colorItemTreeHoverBg; | ||||
|  | ||||
|   | ||||
| @@ -80,13 +80,13 @@ $uiColor: #289fec; // Resize bars, splitter bars, etc. | ||||
| $colorInteriorBorder: rgba($colorBodyFg, 0.2); | ||||
| $colorA: $colorBodyFg; | ||||
| $colorAHov: $colorKey; | ||||
| $filterHov: brightness(0.8) contrast(2); // Tree, location items | ||||
| $colorSelectedBg: rgba($colorKey, 0.2); | ||||
| $filterHov: hue-rotate(-10deg) brightness(0.8) contrast(2); // Tree, location items | ||||
| $colorSelectedBg: pushBack($colorKey, 40%); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 10%); | ||||
|  | ||||
| // Object labels | ||||
| $objectLabelTypeIconOpacity: 0.5; | ||||
| $objectLabelNameFilter: brightness(0.5); | ||||
| $objectLabelNameFilter: brightness(0.9); | ||||
|  | ||||
| // Layout | ||||
| $shellMainPad: 4px 0; | ||||
| @@ -377,6 +377,11 @@ $colorItemTreeVC: $colorDisclosureCtrl; | ||||
| $colorItemTreeVCHover: $colorDisclosureCtrlHov; | ||||
| $shdwItemTreeIcon: none; | ||||
|  | ||||
| // Layout frame controls | ||||
| $frameControlsColorFg: $colorClickIconButton; | ||||
| $frameControlsColorBg: $colorMenuBg; | ||||
| $frameControlsShdw: $shdwMenu; | ||||
|  | ||||
| // Images | ||||
| $colorThumbHoverBg: $colorItemTreeHoverBg; | ||||
|  | ||||
|   | ||||
| @@ -520,7 +520,7 @@ select { | ||||
|  | ||||
|     &__section-hint { | ||||
|         $m: $interiorMargin; | ||||
|         margin: $m 0; | ||||
|         margin: 0 0 $m 0; | ||||
|         padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2); | ||||
|  | ||||
|         opacity: 0.6; | ||||
| @@ -532,7 +532,7 @@ select { | ||||
|         $m: $interiorMargin; | ||||
|         border-top: 1px solid $colorInteriorBorder; | ||||
|         margin: $m 0; | ||||
|         padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2); | ||||
|         padding: 0 nth($menuItemPad, 2) 0 nth($menuItemPad, 2); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -480,8 +480,8 @@ | ||||
| } | ||||
|  | ||||
| @mixin cClickIconButtonLayout() { | ||||
|     $pLR: 4px; | ||||
|     $pTB: 4px; | ||||
|     $pLR: 5px; | ||||
|     $pTB: 5px; | ||||
|     padding: $pTB $pLR; | ||||
|  | ||||
|     &:before, | ||||
| @@ -500,6 +500,7 @@ | ||||
|     @include cControl(); | ||||
|     @include cClickIconButtonLayout(); | ||||
|     background: none; | ||||
|     color: $colorClickIconButton; | ||||
|     box-shadow: none; | ||||
|     cursor: pointer; | ||||
|     transition: $transOut; | ||||
| @@ -508,7 +509,8 @@ | ||||
|     @include hover() { | ||||
|         transition: $transIn; | ||||
|         background: $colorClickIconButtonBgHov; | ||||
|         color: $colorClickIconButtonFgHov; | ||||
|         //color: $colorClickIconButtonFgHov; | ||||
|         filter: $filterHov; | ||||
|     } | ||||
|  | ||||
|     &[class*="--major"] { | ||||
|   | ||||
| @@ -97,6 +97,7 @@ div.c-table { | ||||
|             } | ||||
|         } | ||||
|         .c-table-control-bar { | ||||
|             .c-icon-button, | ||||
|             .c-click-icon, | ||||
|             .c-button { | ||||
|                 &__label { | ||||
|   | ||||
| @@ -27,6 +27,7 @@ | ||||
| @import "../plugins/timeConductor/conductor-mode-icon.scss"; | ||||
| @import "../plugins/timeConductor/date-picker.scss"; | ||||
| @import "../plugins/timeline/timeline-axis.scss"; | ||||
| @import "../plugins/viewDatumAction/components/metadata-list.scss"; | ||||
| @import "../ui/components/object-frame.scss"; | ||||
| @import "../ui/components/object-label.scss"; | ||||
| @import "../ui/components/progress-bar.scss"; | ||||
|   | ||||
| @@ -21,14 +21,19 @@ | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
| <div | ||||
|     class="c-so-view has-local-controls" | ||||
|     :class="{ | ||||
|         'c-so-view--no-frame': !hasFrame, | ||||
|         'has-complex-content': complexContent, | ||||
|         'is-missing': domainObject.status === 'missing' | ||||
|     }" | ||||
|     class="c-so-view" | ||||
|     :class="[ | ||||
|         'c-so-view--' + domainObject.type, | ||||
|         { | ||||
|             'c-so-view--no-frame': !hasFrame, | ||||
|             'has-complex-content': complexContent, | ||||
|             'is-missing': domainObject.status === 'missing' | ||||
|         } | ||||
|     ]" | ||||
| > | ||||
|     <div class="c-so-view__header"> | ||||
|     <div | ||||
|         class="c-so-view__header" | ||||
|     > | ||||
|         <div class="c-object-label" | ||||
|              :class="{ | ||||
|                  classList, | ||||
| @@ -46,17 +51,48 @@ | ||||
|                 {{ domainObject && domainObject.name }} | ||||
|             </div> | ||||
|         </div> | ||||
|         <context-menu-drop-down | ||||
|             :object-path="objectPath" | ||||
|         /> | ||||
|     </div> | ||||
|     <div class="c-so-view__local-controls c-so-view__view-large h-local-controls c-local-controls--show-on-hover"> | ||||
|         <button | ||||
|             class="c-button icon-expand" | ||||
|             title="View Large" | ||||
|             @click="expand" | ||||
|         ></button> | ||||
|  | ||||
|         <div | ||||
|             class="c-so-view__frame-controls" | ||||
|             :class="{ | ||||
|                 'c-so-view__frame-controls--no-frame': !hasFrame, | ||||
|                 'has-complex-content': complexContent | ||||
|             }" | ||||
|         > | ||||
|             <NotebookMenuSwitcher v-if="notebookEnabled" | ||||
|                                   :domain-object="domainObject" | ||||
|                                   :object-path="objectPath" | ||||
|                                   class="c-notebook-snapshot-menubutton" | ||||
|             /> | ||||
|             <div class="c-so-view__frame-controls__btns"> | ||||
|                 <button | ||||
|                     v-for="(item, index) in statusBarItems" | ||||
|                     :key="index" | ||||
|                     class="c-icon-button" | ||||
|                     :class="item.cssClass" | ||||
|                     :title="item.name" | ||||
|                     @click="item.callBack" | ||||
|                 > | ||||
|                     <span class="c-icon-button__label">{{ item.name }}</span> | ||||
|                 </button> | ||||
|  | ||||
|                 <button | ||||
|                     class="c-icon-button icon-items-expand" | ||||
|                     title="View Large" | ||||
|                     @click="expand" | ||||
|                 > | ||||
|                     <span class="c-icon-button__label">View Large</span> | ||||
|                 </button> | ||||
|  | ||||
|             </div> | ||||
|             <button | ||||
|                 class="c-icon-button icon-3-dots c-so-view__frame-controls__more" | ||||
|                 title="View menu items" | ||||
|                 @click.prevent.stop="showMenuItems($event)" | ||||
|             ></button> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <div class="is-missing__indicator" | ||||
|          title="This item is missing" | ||||
|     ></div> | ||||
| @@ -66,14 +102,15 @@ | ||||
|         :object="domainObject" | ||||
|         :show-edit-view="showEditView" | ||||
|         :object-path="objectPath" | ||||
|         @change-provider="setViewProvider" | ||||
|     /> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import ObjectView from './ObjectView.vue'; | ||||
| import ContextMenuDropDown from './contextMenuDropDown.vue'; | ||||
| import PreviewHeader from '@/ui/preview/preview-header.vue'; | ||||
| import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| const SIMPLE_CONTENT_TYPES = [ | ||||
| @@ -88,7 +125,7 @@ export default { | ||||
|     inject: ['openmct'], | ||||
|     components: { | ||||
|         ObjectView, | ||||
|         ContextMenuDropDown | ||||
|         NotebookMenuSwitcher | ||||
|     }, | ||||
|     props: { | ||||
|         domainObject: { | ||||
| @@ -107,12 +144,21 @@ export default { | ||||
|     }, | ||||
|     data() { | ||||
|         let objectType = this.openmct.types.get(this.domainObject.type); | ||||
|  | ||||
|         let cssClass = objectType && objectType.definition ? objectType.definition.cssClass : 'icon-object-unknown'; | ||||
|  | ||||
|         let complexContent = !SIMPLE_CONTENT_TYPES.includes(this.domainObject.type); | ||||
|  | ||||
|         let viewProvider = {}; | ||||
|  | ||||
|         let statusBarItems = {}; | ||||
|  | ||||
|         return { | ||||
|             cssClass, | ||||
|             complexContent | ||||
|             complexContent, | ||||
|             notebookEnabled: this.openmct.types.get('notebook'), | ||||
|             viewProvider, | ||||
|             statusBarItems | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -125,6 +171,11 @@ export default { | ||||
|             return classList.join(' '); | ||||
|         } | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         if (this.actionCollection) { | ||||
|             this.unlistenToActionCollection(); | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         expand() { | ||||
|             let objectView = this.$refs.objectView; | ||||
| @@ -172,6 +223,45 @@ export default { | ||||
|         }, | ||||
|         getSelectionContext() { | ||||
|             return this.$refs.objectView.getSelectionContext(); | ||||
|         }, | ||||
|         setViewProvider(provider) { | ||||
|             this.viewProvider = provider; | ||||
|             this.initializeStatusBarItems(); | ||||
|         }, | ||||
|         initializeStatusBarItems() { | ||||
|             if (this.actionCollection) { | ||||
|                 this.unlistenToActionCollection(); | ||||
|             } | ||||
|  | ||||
|             if (this.viewProvider) { | ||||
|                 this.actionCollection = this.openmct.actions.get(this.objectPath, this.viewProvider); | ||||
|                 this.actionCollection.on('update', this.updateActionItems); | ||||
|                 this.updateActionItems(this.actionCollection.applicableActions); | ||||
|             } else { | ||||
|                 this.statusBarItems = []; | ||||
|                 this.menuActionItems = []; | ||||
|             } | ||||
|         }, | ||||
|         unlistenToActionCollection() { | ||||
|             this.actionCollection.off('update', this.updateActionItems); | ||||
|             this.actionCollection.destroy(); | ||||
|             delete this.actionCollection; | ||||
|         }, | ||||
|         updateActionItems(actionItems) { | ||||
|             this.statusBarItems = this.actionCollection.getStatusBarActions(); | ||||
|             this.menuActionItems = this.actionCollection.getVisibleActions(); | ||||
|         }, | ||||
|         showMenuItems(event) { | ||||
|             let actions; | ||||
|  | ||||
|             if (this.menuActionItems.length) { | ||||
|                 actions = this.menuActionItems; | ||||
|             } else { | ||||
|                 actions = this.openmct.actions.get(this.objectPath); | ||||
|             } | ||||
|  | ||||
|             let sortedActions = this.openmct.actions._groupAndSortActions(actions); | ||||
|             this.openmct.menus.showMenu(event.x, event.y, sortedActions); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -179,6 +179,7 @@ export default { | ||||
|                     this.$el, this.getSelectionContext(), true); | ||||
|             } | ||||
|  | ||||
|             this.$emit('change-provider', this.currentView); | ||||
|             this.openmct.objectViews.on('clearData', this.clearData); | ||||
|         }, | ||||
|         show(object, viewKey, immediatelySelect, currentObjectPath) { | ||||
|   | ||||
| @@ -2,20 +2,21 @@ | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|  | ||||
|     &.is-missing { | ||||
|         border: $borderMissing; | ||||
|     } | ||||
|  | ||||
|     /*************************** HEADER */ | ||||
|     &__header { | ||||
|         flex: 0 0 auto; | ||||
|         display: flex; | ||||
|         font-size: 1.05em; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
|         margin-bottom: $interiorMarginSm; | ||||
|         padding: 1px 2px; | ||||
|         padding: 3px; | ||||
|  | ||||
|         .c-object-label { | ||||
|             font-size: 1.05em; | ||||
|             &__type-icon { | ||||
|                 opacity: $objectLabelTypeIconOpacity; | ||||
|             } | ||||
|  | ||||
|             &__name { | ||||
|                 filter: $objectLabelNameFilter; | ||||
|             } | ||||
| @@ -31,9 +32,82 @@ | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /*************************** FRAME CONTROLS */ | ||||
|     &__frame-controls { | ||||
|         display: flex; | ||||
|  | ||||
|         &__btns, | ||||
|         &__more { | ||||
|             flex: 0 0 auto; | ||||
|         } | ||||
|  | ||||
|         .is-in-small-container &, | ||||
|         .c-fl-frame & { | ||||
|             [class*="__label"] { | ||||
|                 // button labels | ||||
|                 display: none; | ||||
|  | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /*************************** HIDDEN FRAME */ | ||||
|     &--no-frame { | ||||
|         > .c-so-view__header { | ||||
|             display: none; | ||||
|             visibility: hidden; | ||||
|             pointer-events: none; | ||||
|             position: absolute; | ||||
|             top: 0; right: 0; bottom: auto; left: 0; | ||||
|             z-index: 2; | ||||
|  | ||||
|             .c-object-label { | ||||
|                 visibility: hidden; | ||||
|             } | ||||
|  | ||||
|             .c-so-view__frame-controls { | ||||
|                 background: $frameControlsColorBg; | ||||
|                 border-radius: $controlCr; | ||||
|                 box-shadow: $frameControlsShdw; | ||||
|                 padding: 1px; | ||||
|                 pointer-events: all; | ||||
|  | ||||
|                 .c-icon-button { | ||||
|                     color: $frameControlsColorFg; | ||||
|  | ||||
|                     &:hover { | ||||
|                         background: rgba($frameControlsColorFg, 0.3); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 &__btns { | ||||
|                     display: none; | ||||
|                 } | ||||
|  | ||||
|                 &:hover { | ||||
|                     [class*="__btns"] { | ||||
|                         display: block; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 [class*="__label"] { | ||||
|                     // button labels | ||||
|                     display: none; | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         &.c-so-view--layout { | ||||
|             // For sub-layouts with hidden frames, completely hide the header to avoid overlapping buttons | ||||
|             > .c-so-view__header { | ||||
|                 display: none; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* HOVERS */ | ||||
|         &:hover { | ||||
|             > .c-so-view__header { | ||||
|                 visibility: visible; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.is-missing { | ||||
| @@ -46,30 +120,7 @@ | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__local-controls { | ||||
|         // View Large button | ||||
|         box-shadow: $colorLocalControlOvrBg 0 0 0 2px; | ||||
|         position: absolute; | ||||
|         top: $interiorMargin; right: $interiorMargin; | ||||
|         z-index: 10; | ||||
|     } | ||||
|  | ||||
|     .c-click-icon, | ||||
|     .c-button { | ||||
|         // Shrink buttons a bit when they appear in a frame | ||||
|         border-radius: $smallCr !important; | ||||
|         font-size: 0.9em; | ||||
|         padding: 3px 5px; | ||||
|     } | ||||
|  | ||||
|     &__view-large { | ||||
|         display: none; | ||||
|     } | ||||
|  | ||||
|     &.has-complex-content { | ||||
|         > .c-so-view__view-large { display: block; } | ||||
|     } | ||||
|  | ||||
|     /*************************** OBJECT VIEW */ | ||||
|     &__object-view { | ||||
|         flex: 1 1 auto; | ||||
|         height: 0; // Chrome 73 overflow bug fix | ||||
| @@ -80,6 +131,23 @@ | ||||
|             @include abs(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-click-icon, | ||||
|     .c-button, | ||||
|     .c-icon-button { | ||||
|         // Shrink buttons a bit when they appear in a frame | ||||
|         border-radius: $smallCr !important; | ||||
|         font-size: 0.9em; | ||||
|         padding: 5px; | ||||
|     } | ||||
|  | ||||
|     &.has-complex-content { | ||||
|         > .c-so-view__view-large { display: block; } | ||||
|     } | ||||
|  | ||||
|     &.is-missing { | ||||
|         border: $borderMissing; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .l-angular-ov-wrapper { | ||||
|   | ||||
| @@ -19,7 +19,6 @@ | ||||
|         display: block; | ||||
|         flex: 0 0 auto; | ||||
|         font-size: 1.1em; | ||||
|         opacity: $objectLabelTypeIconOpacity; | ||||
|     } | ||||
|  | ||||
|     &.is-missing { | ||||
|   | ||||
| @@ -29,6 +29,10 @@ | ||||
|             filter: $objectLabelNameFilter; | ||||
|         } | ||||
|  | ||||
|         .c-object-label__type-icon { | ||||
|             opacity: $objectLabelTypeIconOpacity; | ||||
|         } | ||||
|  | ||||
|         &--non-domain-object .c-object-label__name { | ||||
|             font-style: italic; | ||||
|         } | ||||
|   | ||||
| @@ -31,10 +31,6 @@ | ||||
|                 {{ domainObject.name }} | ||||
|             </span> | ||||
|         </div> | ||||
|         <div | ||||
|             class="l-browse-bar__context-actions c-disclosure-button" | ||||
|             @click.prevent.stop="showContextMenu" | ||||
|         ></div> | ||||
|     </div> | ||||
|  | ||||
|     <div class="l-browse-bar__end"> | ||||
| @@ -42,7 +38,6 @@ | ||||
|             v-if="!isEditing" | ||||
|             :current-view="currentView" | ||||
|             :views="views" | ||||
|             @setView="setView" | ||||
|         /> | ||||
|         <!-- Action buttons --> | ||||
|         <NotebookMenuSwitcher v-if="notebookEnabled" | ||||
| @@ -52,15 +47,24 @@ | ||||
|         /> | ||||
|         <div class="l-browse-bar__actions"> | ||||
|             <button | ||||
|                 v-if="isViewEditable && !isEditing" | ||||
|                 :title="lockedOrUnlocked" | ||||
|                 v-for="(item, index) in statusBarItems" | ||||
|                 :key="index" | ||||
|                 class="c-button" | ||||
|                 :class="item.cssClass" | ||||
|                 @click="item.callBack" | ||||
|             > | ||||
|             </button> | ||||
|  | ||||
|             <button | ||||
|                 v-if="isViewEditable & !isEditing" | ||||
|                 :title="lockedOrUnlockedTitle" | ||||
|                 :class="{ | ||||
|                     'icon-lock c-button--caution': domainObject.locked, | ||||
|                     'icon-unlocked': !domainObject.locked | ||||
|                     'c-button icon-lock': domainObject.locked, | ||||
|                     'c-icon-button icon-unlocked': !domainObject.locked | ||||
|                 }" | ||||
|                 @click="toggleLock(!domainObject.locked)" | ||||
|             ></button> | ||||
|  | ||||
|             <button | ||||
|                 v-if="isViewEditable && !isEditing && !domainObject.locked" | ||||
|                 class="l-browse-bar__actions__edit c-button c-button--major icon-pencil" | ||||
| @@ -106,6 +110,11 @@ | ||||
|                 title="Cancel Editing" | ||||
|                 @click="promptUserandCancelEditing()" | ||||
|             ></button> | ||||
|             <button | ||||
|                 class="l-browse-bar__actions c-icon-button icon-3-dots" | ||||
|                 title="More options" | ||||
|                 @click.prevent.stop="showMenuItems($event)" | ||||
|             ></button> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -123,6 +132,14 @@ export default { | ||||
|         NotebookMenuSwitcher, | ||||
|         ViewSwitcher | ||||
|     }, | ||||
|     props: { | ||||
|         viewProvider: { | ||||
|             type: Object, | ||||
|             default: () => { | ||||
|                 return {}; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     data: function () { | ||||
|         return { | ||||
|             notebookTypes: [], | ||||
| @@ -131,7 +148,8 @@ export default { | ||||
|             domainObject: PLACEHOLDER_OBJECT, | ||||
|             viewKey: undefined, | ||||
|             isEditing: this.openmct.editor.isEditing(), | ||||
|             notebookEnabled: this.openmct.types.get('notebook') | ||||
|             notebookEnabled: this.openmct.types.get('notebook'), | ||||
|             statusBarItems: [] | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -155,7 +173,10 @@ export default { | ||||
|                     return { | ||||
|                         key: p.key, | ||||
|                         cssClass: p.cssClass, | ||||
|                         name: p.name | ||||
|                         name: p.name, | ||||
|                         callBack: () => { | ||||
|                             return this.setView({key: p.key}); | ||||
|                         } | ||||
|                     }; | ||||
|                 }); | ||||
|         }, | ||||
| @@ -187,7 +208,7 @@ export default { | ||||
|  | ||||
|             return false; | ||||
|         }, | ||||
|         lockedOrUnlocked() { | ||||
|         lockedOrUnlockedTitle() { | ||||
|             if (this.domainObject.locked) { | ||||
|                 return 'Locked for editing - click to unlock.'; | ||||
|             } else { | ||||
| @@ -204,6 +225,21 @@ export default { | ||||
|             this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => { | ||||
|                 this.domainObject = domainObject; | ||||
|             }); | ||||
|         }, | ||||
|         viewProvider(viewProvider) { | ||||
|             if (this.actionCollection) { | ||||
|                 this.unlistenToActionCollection(); | ||||
|             } | ||||
|  | ||||
|             if (viewProvider) { | ||||
|                 this.actionCollection = this.openmct.actions.get(this.openmct.router.path, viewProvider); | ||||
|                 this.actionCollection.on('update', this.updateActionItems); | ||||
|                 this.updateActionItems(this.actionCollection.applicableActions); | ||||
|             } else { | ||||
|                 this.statusBarViewKey = undefined; | ||||
|                 this.statusBarItems = []; | ||||
|                 this.menuActionItems = []; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     mounted: function () { | ||||
| @@ -219,6 +255,10 @@ export default { | ||||
|             this.mutationObserver(); | ||||
|         } | ||||
|  | ||||
|         if (this.actionCollection) { | ||||
|             this.unlistenToActionCollection(); | ||||
|         } | ||||
|  | ||||
|         document.removeEventListener('click', this.closeViewAndSaveMenu); | ||||
|         window.removeEventListener('click', this.promptUserbeforeNavigatingAway); | ||||
|     }, | ||||
| @@ -300,12 +340,30 @@ export default { | ||||
|                 this.openmct.editor.edit(); | ||||
|             }); | ||||
|         }, | ||||
|         showContextMenu(event) { | ||||
|             this.openmct.contextMenu._showContextMenuForObjectPath(this.openmct.router.path, event.clientX, event.clientY); | ||||
|         }, | ||||
|         goToParent() { | ||||
|             window.location.hash = this.parentUrl; | ||||
|         }, | ||||
|         updateActionItems(actionItems) { | ||||
|             this.statusBarItems = this.actionCollection.getStatusBarActions(); | ||||
|             this.menuActionItems = this.actionCollection.getVisibleActions(); | ||||
|         }, | ||||
|         showMenuItems(event) { | ||||
|             let actions; | ||||
|  | ||||
|             if (this.menuActionItems.length) { | ||||
|                 actions = this.menuActionItems; | ||||
|             } else { | ||||
|                 actions = this.openmct.actions.get(this.openmct.router.path); | ||||
|             } | ||||
|  | ||||
|             let sortedActions = this.openmct.actions._groupAndSortActions(actions); | ||||
|             this.openmct.menus.showMenu(event.x, event.y, sortedActions); | ||||
|         }, | ||||
|         unlistenToActionCollection() { | ||||
|             this.actionCollection.off('update', this.updateActionItems); | ||||
|             this.actionCollection.destroy(); | ||||
|             delete this.actionCollection; | ||||
|         }, | ||||
|         toggleLock(flag) { | ||||
|             this.openmct.objects.mutate(this.domainObject, 'locked', flag); | ||||
|         } | ||||
|   | ||||
| @@ -65,6 +65,7 @@ | ||||
|             <browse-bar | ||||
|                 ref="browseBar" | ||||
|                 class="l-shell__main-view-browse-bar" | ||||
|                 :view-provider="viewProvider" | ||||
|                 @sync-tree-navigation="handleSyncTreeNavigation" | ||||
|             /> | ||||
|             <toolbar | ||||
| @@ -73,9 +74,10 @@ | ||||
|             /> | ||||
|             <object-view | ||||
|                 ref="browseObject" | ||||
|                 class="l-shell__main-container" | ||||
|                 :show-edit-view="true" | ||||
|                 class="l-shell__main-container js-snapshot-frame" | ||||
|                 data-selectable | ||||
|                 :show-edit-view="true" | ||||
|                 @change-provider="setProvider" | ||||
|             /> | ||||
|             <component | ||||
|                 :is="conductorComponent" | ||||
| @@ -139,6 +141,7 @@ export default { | ||||
|             conductorComponent: undefined, | ||||
|             isEditing: false, | ||||
|             hasToolbar: false, | ||||
|             viewProvider: undefined, | ||||
|             headExpanded, | ||||
|             triggerSync: false | ||||
|         }; | ||||
| @@ -215,6 +218,9 @@ export default { | ||||
|  | ||||
|             this.hasToolbar = structure.length > 0; | ||||
|         }, | ||||
|         setProvider(provider) { | ||||
|             this.viewProvider = provider; | ||||
|         }, | ||||
|         handleSyncTreeNavigation() { | ||||
|             this.triggerSync = !this.triggerSync; | ||||
|         } | ||||
|   | ||||
| @@ -4,36 +4,21 @@ | ||||
|     class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left" | ||||
| > | ||||
|     <button | ||||
|         class="c-button--menu" | ||||
|         class="c-icon-button c-button--menu" | ||||
|         :class="currentView.cssClass" | ||||
|         title="Change the current view" | ||||
|         @click.stop="toggleViewMenu" | ||||
|         @click.prevent.stop="showMenu" | ||||
|     > | ||||
|         <span class="c-button__label"> | ||||
|         <span class="c-icon-button__label"> | ||||
|             {{ currentView.name }} | ||||
|         </span> | ||||
|     </button> | ||||
|     <div | ||||
|         v-show="showViewMenu" | ||||
|         class="c-menu" | ||||
|     > | ||||
|         <ul> | ||||
|             <li | ||||
|                 v-for="(view, index) in views" | ||||
|                 :key="index" | ||||
|                 :class="view.cssClass" | ||||
|                 :title="view.name" | ||||
|                 @click="setView(view)" | ||||
|             > | ||||
|                 {{ view.name }} | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         currentView: { | ||||
|             type: Object, | ||||
| @@ -44,26 +29,16 @@ export default { | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             showViewMenu: false | ||||
|         }; | ||||
|     }, | ||||
|     mounted() { | ||||
|         document.addEventListener('click', this.hideViewMenu); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         document.removeEventListener('click', this.hideViewMenu); | ||||
|     }, | ||||
|     methods: { | ||||
|         setView(view) { | ||||
|             this.$emit('setView', view); | ||||
|         }, | ||||
|         toggleViewMenu() { | ||||
|             this.showViewMenu = !this.showViewMenu; | ||||
|         }, | ||||
|         hideViewMenu() { | ||||
|             this.showViewMenu = false; | ||||
|         showMenu() { | ||||
|             const elementBoundingClientRect = this.$el.getBoundingClientRect(); | ||||
|             const x = elementBoundingClientRect.x; | ||||
|             const y = elementBoundingClientRect.y + elementBoundingClientRect.height; | ||||
|  | ||||
|             this.openmct.menus.showMenu(x, y, this.views); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -331,7 +331,7 @@ | ||||
|         } | ||||
|  | ||||
|         > * + * { | ||||
|             margin-left: $interiorMargin; | ||||
|             margin-left: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -375,6 +375,10 @@ | ||||
|         filter: $objectLabelNameFilter; | ||||
|     } | ||||
|  | ||||
|     .c-object-label__type-icon { | ||||
|         opacity: $objectLabelTypeIconOpacity; | ||||
|     } | ||||
|  | ||||
|     &__object-name--w { | ||||
|         @include headerFont(1.5em); | ||||
|         margin-left: $interiorMarginLg; | ||||
|   | ||||
| @@ -33,7 +33,7 @@ | ||||
|     > | ||||
|         <span class="l-pane__expand-button__label">{{ label }}</span> | ||||
|     </button> | ||||
|     <div class="l-pane__contents"> | ||||
|     <div class="l-pane__contents js-snapshot-container"> | ||||
|         <slot></slot> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -30,7 +30,11 @@ export default { | ||||
|         showContextMenu(event) { | ||||
|             event.preventDefault(); | ||||
|             event.stopPropagation(); | ||||
|             this.openmct.contextMenu._showContextMenuForObjectPath(this.objectPath, event.clientX, event.clientY); | ||||
|  | ||||
|             let actions = this.openmct.actions.get(this.objectPath); | ||||
|             let sortedActions = this.openmct.actions._groupAndSortActions(actions); | ||||
|  | ||||
|             this.openmct.menus.showMenu(event.clientX, event.clientY, sortedActions); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -25,7 +25,6 @@ | ||||
|         :current-view="currentView" | ||||
|         :domain-object="domainObject" | ||||
|         :views="views" | ||||
|         @setView="setView" | ||||
|     /> | ||||
|     <div class="l-preview-window__object-view"> | ||||
|         <div ref="objectView"></div> | ||||
| @@ -51,23 +50,20 @@ export default { | ||||
|  | ||||
|         return { | ||||
|             domainObject: domainObject, | ||||
|             viewKey: undefined | ||||
|             viewKey: undefined, | ||||
|             views: [], | ||||
|             currentView: {} | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
|         views() { | ||||
|             return this | ||||
|                 .openmct | ||||
|                 .objectViews | ||||
|                 .get(this.domainObject); | ||||
|         }, | ||||
|         currentView() { | ||||
|             return this.views.filter(v => v.key === this.viewKey)[0] || {}; | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         let view = this.openmct.objectViews.get(this.domainObject)[0]; | ||||
|         this.setView(view); | ||||
|         this.views = this.openmct.objectViews.get(this.domainObject).map((view) => { | ||||
|             view.callBack = () => { | ||||
|                 return this.setView(view); | ||||
|             }; | ||||
|  | ||||
|             return view; | ||||
|         }); | ||||
|         this.setView(this.views[0]); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         this.view.destroy(); | ||||
| @@ -91,9 +87,13 @@ export default { | ||||
|             delete this.viewContainer; | ||||
|         }, | ||||
|         setView(view) { | ||||
|             this.clear(); | ||||
|             if (this.viewKey === view.key) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.clear(); | ||||
|             this.viewKey = view.key; | ||||
|             this.currentView = view; | ||||
|             this.viewContainer = document.createElement('div'); | ||||
|             this.viewContainer.classList.add('l-angular-ov-wrapper'); | ||||
|             this.$refs.objectView.append(this.viewContainer); | ||||
|   | ||||
| @@ -27,10 +27,12 @@ export default class PreviewAction { | ||||
|         /** | ||||
|          * Metadata | ||||
|          */ | ||||
|         this.name = 'Preview'; | ||||
|         this.name = 'View'; | ||||
|         this.key = 'preview'; | ||||
|         this.description = 'Preview in large dialog'; | ||||
|         this.cssClass = 'icon-eye-open'; | ||||
|         this.description = 'View in large dialog'; | ||||
|         this.cssClass = 'icon-items-expand'; | ||||
|         this.group = 'windowing'; | ||||
|         this.priority = 1; | ||||
|  | ||||
|         /** | ||||
|          * Dependencies | ||||
| @@ -81,8 +83,7 @@ export default class PreviewAction { | ||||
|         let targetObject = objectPath[0]; | ||||
|         let navigatedObject = this._openmct.router.path[0]; | ||||
|  | ||||
|         return targetObject.identifier.namespace === navigatedObject.identifier.namespace | ||||
|             && targetObject.identifier.key === navigatedObject.identifier.key; | ||||
|         return this._openmct.objects.areIdsEqual(targetObject.identifier, navigatedObject.identifier); | ||||
|     } | ||||
|     _preventPreview(objectPath) { | ||||
|         const noPreviewTypes = ['folder']; | ||||
|   | ||||
| @@ -32,4 +32,14 @@ export default class ViewHistoricalDataAction extends PreviewAction { | ||||
|         this.cssClass = 'icon-eye-open'; | ||||
|         this.hideInDefaultMenu = true; | ||||
|     } | ||||
|  | ||||
|     appliesTo(objectPath, view = {}) { | ||||
|         let viewContext = view.getViewContext && view.getViewContext(); | ||||
|  | ||||
|         if (objectPath.length && viewContext && viewContext.viewHistoricalData) { | ||||
|             return true; | ||||
|         } else { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,7 +24,7 @@ import ViewHistoricalDataAction from './ViewHistoricalDataAction'; | ||||
|  | ||||
| export default function () { | ||||
|     return function (openmct) { | ||||
|         openmct.contextMenu.registerAction(new PreviewAction(openmct)); | ||||
|         openmct.contextMenu.registerAction(new ViewHistoricalDataAction(openmct)); | ||||
|         openmct.actions.register(new PreviewAction(openmct)); | ||||
|         openmct.actions.register(new ViewHistoricalDataAction(openmct)); | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -18,7 +18,6 @@ | ||||
|                 :v-if="!hideViewSwitcher" | ||||
|                 :views="views" | ||||
|                 :current-view="currentView" | ||||
|                 @setView="setView" | ||||
|             /> | ||||
|         </div> | ||||
|     </div> | ||||
|   | ||||
							
								
								
									
										13
									
								
								src/utils/clipboard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/utils/clipboard.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| class Clipboard { | ||||
|     updateClipboard(newClip) { | ||||
|         // return promise | ||||
|         return navigator.clipboard.writeText(newClip); | ||||
|     } | ||||
|  | ||||
|     readClipboard() { | ||||
|         // return promise | ||||
|         return navigator.clipboard.readText(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default new Clipboard(); | ||||
		Reference in New Issue
	
	Block a user