mirror of
https://github.com/nasa/openmct.git
synced 2025-04-08 03:44:24 +00:00
Context menu actions (#2229)
* Adding jsdoc to Context Menu Registry * Remove attachTo function - make context menu gesture a mixin. Update object path when objects change. * Added context menu from arrow button. Some minor refactoring * Clarify variable naming * Moved Context Menu component * Reorder function definitions * Addressed code review comments
This commit is contained in:
parent
f06427cb3e
commit
32a0baa7a3
12
src/MCT.js
12
src/MCT.js
@ -255,6 +255,12 @@ define([
|
||||
MCT.prototype.legacyObject = function (domainObject) {
|
||||
let capabilityService = this.$injector.get('capabilityService');
|
||||
|
||||
function instantiate(model, keyString) {
|
||||
var capabilities = capabilityService.getCapabilities(model, keyString);
|
||||
model.id = keyString;
|
||||
return new DomainObjectImpl(keyString, model, capabilities);
|
||||
}
|
||||
|
||||
if (Array.isArray(domainObject)) {
|
||||
// an array of domain objects. [object, ...ancestors] representing
|
||||
// a single object with a given chain of ancestors. We instantiate
|
||||
@ -275,12 +281,6 @@ define([
|
||||
let oldModel = objectUtils.toOldFormat(domainObject);
|
||||
return instantiate(oldModel, keyString);
|
||||
}
|
||||
|
||||
function instantiate(model, keyString) {
|
||||
var capabilities = capabilityService.getCapabilities(model, keyString);
|
||||
model.id = keyString;
|
||||
return new DomainObjectImpl(keyString, model, capabilities);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,40 +1,37 @@
|
||||
/*****************************************************************************
|
||||
* 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 LegacyContextMenuAction from './LegacyContextMenuAction';
|
||||
|
||||
export default function LegacyActionAdapter(openmct, legacyActions) {
|
||||
legacyActions
|
||||
.filter(contextCategoryOnly)
|
||||
.map(createContextMenuAction)
|
||||
.forEach(openmct.contextMenu.registerAction);
|
||||
|
||||
function createContextMenuAction(LegacyAction) {
|
||||
return {
|
||||
name: LegacyAction.definition.name,
|
||||
description: LegacyAction.definition.description,
|
||||
cssClass: LegacyAction.definition.cssClass,
|
||||
appliesTo(objectPath) {
|
||||
let legacyObject = openmct.legacyObject(objectPath);
|
||||
return LegacyAction.appliesTo({
|
||||
domainObject: legacyObject
|
||||
});
|
||||
},
|
||||
invoke(objectPath) {
|
||||
let context = {
|
||||
category: 'contextual',
|
||||
domainObject: openmct.legacyObject(objectPath)
|
||||
}
|
||||
let legacyAction = new LegacyAction(context);
|
||||
|
||||
if (!legacyAction.getMetadata) {
|
||||
let metadata = Object.create(LegacyAction.definition);
|
||||
metadata.context = context;
|
||||
legacyAction.getMetadata = function () {
|
||||
return metadata;
|
||||
}.bind(legacyAction);
|
||||
}
|
||||
legacyAction.perform();
|
||||
}
|
||||
function contextualCategoryOnly(action) {
|
||||
if (action.category === 'contextual') {
|
||||
return true;
|
||||
}
|
||||
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
function contextCategoryOnly(action) {
|
||||
return action.category === 'contextual';
|
||||
}
|
||||
legacyActions.filter(contextualCategoryOnly)
|
||||
.map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction))
|
||||
.forEach(openmct.contextMenu.registerAction);
|
||||
}
|
||||
|
57
src/adapter/actions/LegacyContextMenuAction.js
Normal file
57
src/adapter/actions/LegacyContextMenuAction.js
Normal file
@ -0,0 +1,57 @@
|
||||
import { timingSafeEqual } from "crypto";
|
||||
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
export default class LegacyContextMenuAction {
|
||||
constructor(openmct, LegacyAction) {
|
||||
this.openmct = openmct;
|
||||
this.name = LegacyAction.definition.name;
|
||||
this.description = LegacyAction.definition.description;
|
||||
this.cssClass = LegacyAction.definition.cssClass;
|
||||
this.LegacyAction = LegacyAction;
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
let legacyObject = this.openmct.legacyObject(objectPath);
|
||||
return this.LegacyAction.appliesTo({
|
||||
domainObject: legacyObject
|
||||
});
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
let context = {
|
||||
category: 'contextual',
|
||||
domainObject: this.openmct.legacyObject(objectPath)
|
||||
}
|
||||
let legacyAction = new this.LegacyAction(context);
|
||||
|
||||
if (!legacyAction.getMetadata) {
|
||||
let metadata = Object.create(this.LegacyAction.definition);
|
||||
metadata.context = context;
|
||||
legacyAction.getMetadata = function () {
|
||||
return metadata;
|
||||
}.bind(legacyAction);
|
||||
}
|
||||
legacyAction.perform();
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ define([
|
||||
'./telemetry/TelemetryAPI',
|
||||
'./indicators/IndicatorAPI',
|
||||
'./notifications/NotificationAPI',
|
||||
'./contextMenu/ContextMenuRegistry',
|
||||
'./contextMenu/ContextMenuAPI',
|
||||
'./Editor'
|
||||
|
||||
], function (
|
||||
@ -39,7 +39,7 @@ define([
|
||||
TelemetryAPI,
|
||||
IndicatorAPI,
|
||||
NotificationAPI,
|
||||
ContextMenuRegistry,
|
||||
ContextMenuAPI,
|
||||
EditorAPI
|
||||
) {
|
||||
return {
|
||||
@ -51,6 +51,6 @@ define([
|
||||
IndicatorAPI: IndicatorAPI,
|
||||
NotificationAPI: NotificationAPI.default,
|
||||
EditorAPI: EditorAPI,
|
||||
ContextMenuRegistry: ContextMenuRegistry.default
|
||||
ContextMenuRegistry: ContextMenuAPI.default
|
||||
};
|
||||
});
|
||||
|
@ -20,10 +20,16 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import ContextMenuComponent from '../../ui/components/controls/ContextMenu.vue';
|
||||
import ContextMenuComponent from './ContextMenu.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
class ContextMenuRegistry {
|
||||
/**
|
||||
* 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;
|
||||
@ -32,37 +38,44 @@ class ContextMenuRegistry {
|
||||
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)
|
||||
*/
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
attachTo(targetElement, objectPath, eventName) {
|
||||
eventName = eventName || 'contextmenu';
|
||||
|
||||
if (eventName !== 'contextmenu' && eventName !== 'click') {
|
||||
throw `'${eventName}' event not supported for context menu`;
|
||||
}
|
||||
|
||||
let showContextMenu = (event) => {
|
||||
this._showContextMenuForObjectPath(event, objectPath);
|
||||
};
|
||||
|
||||
targetElement.addEventListener(eventName, showContextMenu);
|
||||
|
||||
return function detach() {
|
||||
targetElement.removeEventListener(eventName, showContextMenu);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_showContextMenuForObjectPath(event, objectPath) {
|
||||
_showContextMenuForObjectPath(objectPath, x, y) {
|
||||
let applicableActions = this._allActions.filter(
|
||||
(action) => action.appliesTo(objectPath));
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (this._activeContextMenu) {
|
||||
this._hideActiveContextMenu();
|
||||
}
|
||||
@ -71,7 +84,7 @@ class ContextMenuRegistry {
|
||||
this._activeContextMenu.$mount();
|
||||
document.body.appendChild(this._activeContextMenu.$el);
|
||||
|
||||
let position = this._calculatePopupPosition(event, 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`;
|
||||
|
||||
@ -81,24 +94,22 @@ class ContextMenuRegistry {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_calculatePopupPosition(event, menuElement) {
|
||||
let x = event.clientX;
|
||||
let y = event.clientY;
|
||||
_calculatePopupPosition(eventPosX, eventPosY, menuElement) {
|
||||
let menuDimensions = menuElement.getBoundingClientRect();
|
||||
let diffX = (x + menuDimensions.width) - document.body.clientWidth;
|
||||
let diffY = (y + menuDimensions.height) - document.body.clientHeight;
|
||||
let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
|
||||
let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
|
||||
|
||||
if (diffX > 0) {
|
||||
x = x - diffX;
|
||||
if (overflowX > 0) {
|
||||
eventPosX = eventPosX - overflowX;
|
||||
}
|
||||
|
||||
if (diffY > 0) {
|
||||
y = y - diffY;
|
||||
if (overflowY > 0) {
|
||||
eventPosY = eventPosY - overflowY;
|
||||
}
|
||||
|
||||
return {
|
||||
x: x,
|
||||
y: y
|
||||
x: eventPosX,
|
||||
y: eventPosY
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -127,4 +138,4 @@ class ContextMenuRegistry {
|
||||
});
|
||||
}
|
||||
}
|
||||
export default ContextMenuRegistry;
|
||||
export default ContextMenuAPI;
|
@ -24,6 +24,6 @@
|
||||
// Meant for use as a single line import in Vue SFC's.
|
||||
// Do not include anything that renders to CSS!
|
||||
@import "constants";
|
||||
@import "constants-espresso"; // TEMP
|
||||
//@import "constants-snow"; // TEMP
|
||||
//@import "constants-espresso"; // TEMP
|
||||
@import "constants-snow"; // TEMP
|
||||
@import "mixins";
|
@ -12,9 +12,10 @@
|
||||
<script>
|
||||
|
||||
import ObjectLink from '../mixins/object-link';
|
||||
import ContextMenuGesture from '../mixins/context-menu-gesture';
|
||||
|
||||
export default {
|
||||
mixins: [ObjectLink],
|
||||
mixins: [ObjectLink, ContextMenuGesture],
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
domainObject: Object
|
||||
@ -31,8 +32,6 @@ export default {
|
||||
});
|
||||
this.$once('hook:destroyed', removeListener);
|
||||
}
|
||||
let detachContextMenu = this.openmct.contextMenu.attachTo(this.$el, this.objectPath);
|
||||
this.$once('hook:destroyed', detachContextMenu);
|
||||
},
|
||||
computed: {
|
||||
typeClass() {
|
||||
|
@ -11,7 +11,7 @@
|
||||
{{ domainObject.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="l-browse-bar__context-actions c-disclosure-button"></div>
|
||||
<div class="l-browse-bar__context-actions c-disclosure-button" @click="showContextMenu"></div>
|
||||
</div>
|
||||
|
||||
<div class="l-browse-bar__end">
|
||||
@ -81,6 +81,11 @@
|
||||
this.openmct.notifications.error('Error saving objects');
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
showContextMenu(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.openmct.router.path, event.clientX, event.clientY);
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
|
@ -239,7 +239,6 @@
|
||||
import MctTree from './mct-tree.vue';
|
||||
import ObjectView from './ObjectView.vue';
|
||||
import MctTemplate from '../legacy/mct-template.vue';
|
||||
import ContextMenu from '../controls/ContextMenu.vue';
|
||||
import CreateButton from '../controls/CreateButton.vue';
|
||||
import search from '../controls/search.vue';
|
||||
import multipane from '../controls/multipane.vue';
|
||||
@ -283,7 +282,6 @@
|
||||
MctTree,
|
||||
ObjectView,
|
||||
'mct-template': MctTemplate,
|
||||
ContextMenu,
|
||||
CreateButton,
|
||||
search,
|
||||
multipane,
|
||||
|
@ -52,6 +52,7 @@
|
||||
this.domainObject = this.node.object;
|
||||
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
|
||||
this.domainObject = newObject;
|
||||
this.node.objectPath.splice(0, 1, newObject);
|
||||
});
|
||||
this.$once('hook:destroyed', removeListener);
|
||||
if (this.openmct.composition.get(this.node.object)) {
|
||||
|
24
src/ui/components/mixins/context-menu-gesture.js
Normal file
24
src/ui/components/mixins/context-menu-gesture.js
Normal file
@ -0,0 +1,24 @@
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
'objectPath': {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//TODO: touch support
|
||||
this.$el.addEventListener('contextmenu', this.showContextMenu);
|
||||
},
|
||||
destroyed() {
|
||||
this.$el.removeEventListener('contextMenu', this.showContextMenu);
|
||||
},
|
||||
methods: {
|
||||
showContextMenu(event) {
|
||||
event.preventDefault();
|
||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.objectPath, event.clientX, event.clientY);
|
||||
}
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user