/***************************************************************************** * Open MCT, Copyright (c) 2014-2018, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * Open MCT is licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * Open MCT includes source code licensed under additional open source * licenses. See the Open Source Licenses file (LICENSES.md) included with * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ 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: '' }); } } export default ContextMenuAPI;