Compare commits
	
		
			62 Commits
		
	
	
		
			mct5772
			...
			v1.4.0-rc2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					646c871c76 | ||
| 
						 | 
					ba401b3341 | ||
| 
						 | 
					5ef02ec4a2 | ||
| 
						 | 
					d788031019 | ||
| 
						 | 
					d870874649 | ||
| 
						 | 
					711a7a2eb5 | ||
| 
						 | 
					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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -138,14 +138,18 @@ export default {
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                let childContext = this.$refs.objectFrame.getSelectionContext();
 | 
			
		||||
                childContext.item = domainObject;
 | 
			
		||||
                childContext.layoutItem = this.item;
 | 
			
		||||
                childContext.index = this.index;
 | 
			
		||||
                this.context = childContext;
 | 
			
		||||
                this.removeSelectable = this.openmct.selection.selectable(
 | 
			
		||||
                    this.$el, this.context, this.immediatelySelect || this.initSelect);
 | 
			
		||||
                delete this.immediatelySelect;
 | 
			
		||||
                let reference = this.$refs.objectFrame;
 | 
			
		||||
 | 
			
		||||
                if (reference) {
 | 
			
		||||
                    let childContext = this.$refs.objectFrame.getSelectionContext();
 | 
			
		||||
                    childContext.item = domainObject;
 | 
			
		||||
                    childContext.layoutItem = this.item;
 | 
			
		||||
                    childContext.index = this.index;
 | 
			
		||||
                    this.context = childContext;
 | 
			
		||||
                    this.removeSelectable = this.openmct.selection.selectable(
 | 
			
		||||
                        this.$el, this.context, this.immediatelySelect || this.initSelect);
 | 
			
		||||
                    delete this.immediatelySelect;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 | 
			
		||||
@@ -57,22 +45,19 @@ export default {
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            notebookSnapshot: null,
 | 
			
		||||
            notebookTypes: [],
 | 
			
		||||
            showMenu: false
 | 
			
		||||
            notebookTypes: []
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    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();
 | 
			
		||||
            const elementBoundingClientRect = this.$el.getBoundingClientRect();
 | 
			
		||||
            const x = elementBoundingClientRect.x;
 | 
			
		||||
            const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
 | 
			
		||||
 | 
			
		||||
            if (defaultNotebook) {
 | 
			
		||||
                const domainObject = defaultNotebook.domainObject;
 | 
			
		||||
@@ -83,28 +68,24 @@ export default {
 | 
			
		||||
                    notebookTypes.push({
 | 
			
		||||
                        cssClass: 'icon-notebook',
 | 
			
		||||
                        name: `Save to Notebook ${defaultPath}`,
 | 
			
		||||
                        type: NOTEBOOK_DEFAULT
 | 
			
		||||
                        callBack: () => {
 | 
			
		||||
                            return this.snapshot(NOTEBOOK_DEFAULT);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            notebookTypes.push({
 | 
			
		||||
                cssClass: 'icon-notebook',
 | 
			
		||||
                cssClass: 'icon-camera',
 | 
			
		||||
                name: 'Save to Notebook Snapshots',
 | 
			
		||||
                type: NOTEBOOK_SNAPSHOT
 | 
			
		||||
                callBack: () => {
 | 
			
		||||
                    return this.snapshot(NOTEBOOK_SNAPSHOT);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.notebookTypes = notebookTypes;
 | 
			
		||||
        },
 | 
			
		||||
        toggleMenu() {
 | 
			
		||||
            this.showMenu = !this.showMenu;
 | 
			
		||||
        },
 | 
			
		||||
        hideMenu() {
 | 
			
		||||
            this.showMenu = false;
 | 
			
		||||
            this.openmct.menus.showMenu(x, y, notebookTypes);
 | 
			
		||||
        },
 | 
			
		||||
        snapshot(notebook) {
 | 
			
		||||
            this.hideMenu();
 | 
			
		||||
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                const element = document.querySelector('.c-overlay__contents')
 | 
			
		||||
                    || document.getElementsByClassName('l-shell__main-container')[0];
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
            });
 | 
			
		||||
 
 | 
			
		||||
@@ -103,7 +103,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;
 | 
			
		||||
    }
 | 
			
		||||
@@ -125,7 +125,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,
 | 
			
		||||
@@ -72,7 +70,8 @@ define([
 | 
			
		||||
                            data() {
 | 
			
		||||
                                return {
 | 
			
		||||
                                    isEditing: editMode,
 | 
			
		||||
                                    markingProp
 | 
			
		||||
                                    markingProp,
 | 
			
		||||
                                    view
 | 
			
		||||
                                };
 | 
			
		||||
                            },
 | 
			
		||||
                            provide: {
 | 
			
		||||
@@ -80,7 +79,7 @@ define([
 | 
			
		||||
                                table,
 | 
			
		||||
                                objectPath
 | 
			
		||||
                            },
 | 
			
		||||
                            template: '<table-component :isEditing="isEditing" :marking="markingProp"/>'
 | 
			
		||||
                            template: '<table-component ref="tableComponent" :isEditing="isEditing" :marking="markingProp" :view="view"/>'
 | 
			
		||||
                        });
 | 
			
		||||
                    },
 | 
			
		||||
                    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,8 +23,7 @@
 | 
			
		||||
<div class="c-table-wrapper"
 | 
			
		||||
     :class="{ 'is-paused': paused }"
 | 
			
		||||
>
 | 
			
		||||
    <!-- main contolbar  start-->
 | 
			
		||||
    <div v-if="!marking.useAlternateControlBar"
 | 
			
		||||
    <div v-if="enableLegacyToolbar"
 | 
			
		||||
         class="c-table-control-bar c-control-bar"
 | 
			
		||||
    >
 | 
			
		||||
        <button
 | 
			
		||||
@@ -94,7 +93,6 @@
 | 
			
		||||
 | 
			
		||||
        <slot name="buttons"></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- main controlbar end -->
 | 
			
		||||
 | 
			
		||||
    <!-- alternate controlbar start -->
 | 
			
		||||
    <div v-if="marking.useAlternateControlBar"
 | 
			
		||||
@@ -113,11 +111,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>
 | 
			
		||||
@@ -291,12 +289,12 @@ export default {
 | 
			
		||||
            default: true
 | 
			
		||||
        },
 | 
			
		||||
        allowFiltering: {
 | 
			
		||||
            'type': Boolean,
 | 
			
		||||
            'default': true
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            default: true
 | 
			
		||||
        },
 | 
			
		||||
        allowSorting: {
 | 
			
		||||
            'type': Boolean,
 | 
			
		||||
            'default': true
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            default: true
 | 
			
		||||
        },
 | 
			
		||||
        marking: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -309,6 +307,17 @@ export default {
 | 
			
		||||
                    rowNamePlural: ""
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        enableLegacyToolbar: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            default: false
 | 
			
		||||
        },
 | 
			
		||||
        view: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: false,
 | 
			
		||||
            default() {
 | 
			
		||||
                return {};
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
@@ -383,6 +392,40 @@ export default {
 | 
			
		||||
        markedRows: {
 | 
			
		||||
            handler(newVal, oldVal) {
 | 
			
		||||
                this.$emit('marked-rows-updated', newVal, oldVal);
 | 
			
		||||
 | 
			
		||||
                if (this.viewActionsCollection) {
 | 
			
		||||
                    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 (this.viewActionsCollection) {
 | 
			
		||||
                    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 (this.viewActionsCollection) {
 | 
			
		||||
                    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 +438,11 @@ export default {
 | 
			
		||||
        this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
 | 
			
		||||
        this.scroll = _.throttle(this.scroll, 100);
 | 
			
		||||
 | 
			
		||||
        if (!this.marking.useAlternateControlBar && !this.enableLegacyToolbar) {
 | 
			
		||||
            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 +881,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 +942,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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -492,8 +492,8 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin cClickIconButtonLayout() {
 | 
			
		||||
    $pLR: 4px;
 | 
			
		||||
    $pTB: 4px;
 | 
			
		||||
    $pLR: 5px;
 | 
			
		||||
    $pTB: 5px;
 | 
			
		||||
    padding: $pTB $pLR;
 | 
			
		||||
 | 
			
		||||
    &:before,
 | 
			
		||||
@@ -512,6 +512,7 @@
 | 
			
		||||
    @include cControl();
 | 
			
		||||
    @include cClickIconButtonLayout();
 | 
			
		||||
    background: none;
 | 
			
		||||
    color: $colorClickIconButton;
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    transition: $transOut;
 | 
			
		||||
@@ -520,7 +521,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,43 @@
 | 
			
		||||
                {{ 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
 | 
			
		||||
            }"
 | 
			
		||||
        >
 | 
			
		||||
            <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,13 +97,13 @@
 | 
			
		||||
        :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 Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
@@ -87,8 +118,7 @@ const SIMPLE_CONTENT_TYPES = [
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    components: {
 | 
			
		||||
        ObjectView,
 | 
			
		||||
        ContextMenuDropDown
 | 
			
		||||
        ObjectView
 | 
			
		||||
    },
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
@@ -107,12 +137,20 @@ 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,
 | 
			
		||||
            viewProvider,
 | 
			
		||||
            statusBarItems
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -125,6 +163,11 @@ export default {
 | 
			
		||||
            return classList.join(' ');
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    beforeDestroy() {
 | 
			
		||||
        if (this.actionCollection) {
 | 
			
		||||
            this.unlistenToActionCollection();
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        expand() {
 | 
			
		||||
            let objectView = this.$refs.objectView;
 | 
			
		||||
@@ -172,6 +215,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
 | 
			
		||||
@@ -74,8 +75,9 @@
 | 
			
		||||
            <object-view
 | 
			
		||||
                ref="browseObject"
 | 
			
		||||
                class="l-shell__main-container"
 | 
			
		||||
                :show-edit-view="true"
 | 
			
		||||
                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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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