Compare commits

..

1 Commits

Author SHA1 Message Date
Nikhil Mandlik
af846b82e4 WIP: 2020-11-13 14:42:24 -08:00
101 changed files with 696 additions and 2296 deletions

View File

@@ -143,8 +143,8 @@ define([
"$window"
],
"group": "windowing",
"priority": 10,
"cssClass": "icon-new-window"
"cssClass": "icon-new-window",
"priority": "preferred"
}
],
"runs": [

View File

@@ -139,9 +139,7 @@ define([
],
"description": "Edit",
"category": "view-control",
"cssClass": "major icon-pencil",
"group": "action",
"priority": 10
"cssClass": "major icon-pencil"
},
{
"key": "properties",
@@ -152,8 +150,6 @@ define([
"implementation": PropertiesAction,
"cssClass": "major icon-pencil",
"name": "Edit Properties...",
"group": "action",
"priority": 10,
"description": "Edit properties of this object.",
"depends": [
"dialogService"

View File

@@ -86,7 +86,7 @@ define(
})
.join('/');
window.location.href = url;
openmct.router.setHash(url);
if (isFirstViewEditable(object.useCapability('adapter'))) {
openmct.editor.edit();

View File

@@ -20,12 +20,12 @@
at runtime from the About dialog for additional information.
-->
<div class="c-object-label"
ng-class="{ 'is-status--missing': model.status === 'missing' }"
ng-class="{ 'is-missing': model.status === 'missing' }"
>
<div class="c-object-label__type-icon {{type.getCssClass()}}"
ng-class="{ 'l-icon-link':location.isLink() }"
>
<span class="is-status__indicator" title="This item is missing or suspect"></span>
<span class="is-missing__indicator" title="This item is missing"></span>
</div>
<div class='c-object-label__name'>{{model.name}}</div>
</div>

View File

@@ -66,8 +66,6 @@ define([
"description": "Move object to another location.",
"cssClass": "icon-move",
"category": "contextual",
"group": "action",
"priority": 9,
"implementation": MoveAction,
"depends": [
"policyService",
@@ -81,8 +79,6 @@ define([
"description": "Duplicate object to another location.",
"cssClass": "icon-duplicate",
"category": "contextual",
"group": "action",
"priority": 8,
"implementation": CopyAction,
"depends": [
"$log",
@@ -99,8 +95,6 @@ define([
"description": "Create Link to object in another location.",
"cssClass": "icon-link",
"category": "contextual",
"group": "action",
"priority": 7,
"implementation": LinkAction,
"depends": [
"policyService",

View File

@@ -47,8 +47,6 @@ define([
"implementation": ExportAsJSONAction,
"category": "contextual",
"cssClass": "icon-export",
"group": "json",
"priority": 2,
"depends": [
"openmct",
"exportService",
@@ -63,8 +61,6 @@ define([
"implementation": ImportAsJSONAction,
"category": "contextual",
"cssClass": "icon-import",
"group": "json",
"priority": 2,
"depends": [
"exportService",
"identifierService",

View File

@@ -242,11 +242,7 @@ define([
this.overlays = new OverlayAPI.default();
this.menus = new api.MenuAPI(this);
this.actions = new api.ActionsAPI(this);
this.status = new api.StatusAPI(this);
this.contextMenu = new api.ContextMenuRegistry();
this.router = new ApplicationRouter();
@@ -275,7 +271,6 @@ 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);

View File

@@ -35,5 +35,5 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
legacyActions.filter(contextualCategoryOnly)
.map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction))
.forEach(openmct.actions.register);
.forEach(openmct.contextMenu.registerAction);
}

View File

@@ -31,8 +31,6 @@ 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) {

View File

@@ -1,178 +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 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;

View File

@@ -1,145 +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 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;

View File

@@ -1,119 +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 ActionsAPI from './ActionsAPI';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
describe('The Actions API', () => {
let openmct;
let actionsAPI;
let mockAction;
let mockObjectPath;
let mockViewContext1;
beforeEach(() => {
openmct = createOpenMct();
actionsAPI = new ActionsAPI(openmct);
mockAction = {
name: 'Test Action',
key: 'test-action',
cssClass: 'test-action',
description: 'This is a test action',
group: 'action',
priority: 9,
appliesTo: (objectPath, view = {}) => {
if (view.getViewContext) {
let viewContext = view.getViewContext();
return viewContext.onlyAppliesToTestCase;
} else if (objectPath.length) {
return objectPath[0].type === 'fake-folder';
}
return false;
},
invoke: () => {
}
};
mockObjectPath = [
{
name: 'mock folder',
type: 'fake-folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
},
{
name: 'mock parent folder',
type: 'fake-folder',
identifier: {
key: 'mock-parent-folder',
namespace: ''
}
}
];
mockViewContext1 = {
getViewContext: () => {
return {
onlyAppliesToTestCase: true,
skipCache: true
};
}
};
});
afterEach(() => {
resetApplicationState(openmct);
});
describe("register method", () => {
it("adds action to ActionsAPI", () => {
actionsAPI.register(mockAction);
let action = actionsAPI.get(mockObjectPath, mockViewContext1)[mockAction.key];
expect(action.key).toEqual(mockAction.key);
expect(action.name).toEqual(mockAction.name);
});
});
describe("get method", () => {
beforeEach(() => {
actionsAPI.register(mockAction);
});
it("returns an object with relevant actions when invoked with objectPath only", () => {
let action = actionsAPI.get(mockObjectPath, mockViewContext1)[mockAction.key];
expect(action.key).toEqual(mockAction.key);
expect(action.name).toEqual(mockAction.name);
});
it("returns an object with relevant actions when invoked with viewContext and skipCache", () => {
let action = actionsAPI.get(mockObjectPath, mockViewContext1)[mockAction.key];
expect(action.key).toEqual(mockAction.key);
expect(action.name).toEqual(mockAction.name);
});
});
});

View File

@@ -28,10 +28,9 @@ define([
'./telemetry/TelemetryAPI',
'./indicators/IndicatorAPI',
'./notifications/NotificationAPI',
'./Editor',
'./menu/MenuAPI',
'./actions/ActionsAPI',
'./status/StatusAPI'
'./contextMenu/ContextMenuAPI',
'./Editor'
], function (
TimeAPI,
ObjectAPI,
@@ -40,10 +39,8 @@ define([
TelemetryAPI,
IndicatorAPI,
NotificationAPI,
EditorAPI,
MenuAPI,
ActionsAPI,
StatusAPI
ContextMenuAPI,
EditorAPI
) {
return {
TimeAPI: TimeAPI,
@@ -54,8 +51,6 @@ define([
IndicatorAPI: IndicatorAPI,
NotificationAPI: NotificationAPI.default,
EditorAPI: EditorAPI,
MenuAPI: MenuAPI.default,
ActionsAPI: ActionsAPI.default,
StatusAPI: StatusAPI.default
ContextMenuRegistry: ContextMenuAPI.default
};
});

View File

@@ -0,0 +1,24 @@
<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>

View File

@@ -0,0 +1,159 @@
/*****************************************************************************
* 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;

View File

@@ -1,67 +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 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;

View File

@@ -1,125 +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 MenuAPI from './MenuAPI';
import Menu from './menu';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
describe ('The Menu API', () => {
let openmct;
let menuAPI;
let actionsArray;
let x;
let y;
let result;
beforeEach(() => {
openmct = createOpenMct();
menuAPI = new MenuAPI(openmct);
actionsArray = [
{
name: 'Test Action 1',
cssClass: 'test-css-class-1',
description: 'This is a test action',
callBack: () => {
result = 'Test Action 1 Invoked';
}
},
{
name: 'Test Action 2',
cssClass: 'test-css-class-2',
description: 'This is a test action',
callBack: () => {
result = 'Test Action 2 Invoked';
}
}
];
x = 8;
y = 16;
});
afterEach(() => {
resetApplicationState(openmct);
});
describe("showMenu method", () => {
it("creates an instance of Menu when invoked", () => {
menuAPI.showMenu(x, y, actionsArray);
expect(menuAPI.menuComponent).toBeInstanceOf(Menu);
});
describe("creates a menu component", () => {
let menuComponent;
let vueComponent;
beforeEach(() => {
menuAPI.showMenu(x, y, actionsArray);
vueComponent = menuAPI.menuComponent.component;
menuComponent = document.querySelector(".c-menu");
spyOn(vueComponent, '$destroy');
});
it("renders a menu component in the expected x and y coordinates", () => {
let boundingClientRect = menuComponent.getBoundingClientRect();
let left = boundingClientRect.left;
let top = boundingClientRect.top;
expect(left).toEqual(x);
expect(top).toEqual(y);
});
it("with all the actions passed in", () => {
expect(menuComponent).toBeDefined();
let listItems = menuComponent.children[0].children;
expect(listItems.length).toEqual(actionsArray.length);
});
it("with click-able menu items, that will invoke the correct callBacks", () => {
let listItem1 = menuComponent.children[0].children[0];
listItem1.click();
expect(result).toEqual("Test Action 1 Invoked");
});
it("dismisses the menu when action is clicked on", () => {
let listItem1 = menuComponent.children[0].children[0];
listItem1.click();
let menu = document.querySelector('.c-menu');
expect(menu).toBeNull();
});
it("invokes the destroy method when menu is dismissed", () => {
document.body.click();
expect(vueComponent.$destroy).toHaveBeenCalled();
});
});
});
});

View File

@@ -1,52 +0,0 @@
<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>

View File

@@ -1,94 +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 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;

View File

@@ -22,7 +22,6 @@ class OverlayAPI {
this.dismissLastOverlay();
}
});
}
/**
@@ -128,7 +127,6 @@ class OverlayAPI {
return progressDialog;
}
}
export default OverlayAPI;

View File

@@ -1,67 +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 EventEmitter from 'EventEmitter';
export default class StatusAPI extends EventEmitter {
constructor(openmct) {
super();
this._openmct = openmct;
this._statusCache = {};
this.get = this.get.bind(this);
this.set = this.set.bind(this);
this.observe = this.observe.bind(this);
}
get(identifier) {
let keyString = this._openmct.objects.makeKeyString(identifier);
return this._statusCache[keyString];
}
set(identifier, value) {
let keyString = this._openmct.objects.makeKeyString(identifier);
this._statusCache[keyString] = value;
this.emit(keyString, value);
}
delete(identifier) {
let keyString = this._openmct.objects.makeKeyString(identifier);
this._statusCache[keyString] = undefined;
this.emit(keyString, undefined);
delete this._statusCache[keyString];
}
observe(identifier, callback) {
let key = this._openmct.objects.makeKeyString(identifier);
this.on(key, callback);
return () => {
this.off(key, callback);
};
}
}

View File

@@ -1,85 +0,0 @@
import StatusAPI from './StatusAPI.js';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
describe("The Status API", () => {
let statusAPI;
let openmct;
let identifier;
let status;
let status2;
let callback;
beforeEach(() => {
openmct = createOpenMct();
statusAPI = new StatusAPI(openmct);
identifier = {
namespace: "test-namespace",
key: "test-key"
};
status = "test-status";
status2 = 'test-status-deux';
callback = jasmine.createSpy('callback', (statusUpdate) => statusUpdate);
});
afterEach(() => {
resetApplicationState(openmct);
});
describe("set function", () => {
it("sets status for identifier", () => {
statusAPI.set(identifier, status);
let resultingStatus = statusAPI.get(identifier);
expect(resultingStatus).toEqual(status);
});
});
describe("get function", () => {
it("returns status for identifier", () => {
statusAPI.set(identifier, status2);
let resultingStatus = statusAPI.get(identifier);
expect(resultingStatus).toEqual(status2);
});
});
describe("delete function", () => {
it("deletes status for identifier", () => {
statusAPI.set(identifier, status);
let resultingStatus = statusAPI.get(identifier);
expect(resultingStatus).toEqual(status);
statusAPI.delete(identifier);
resultingStatus = statusAPI.get(identifier);
expect(resultingStatus).toBeUndefined();
});
});
describe("observe function", () => {
it("allows callbacks to be attached to status set and delete events", () => {
let unsubscribe = statusAPI.observe(identifier, callback);
statusAPI.set(identifier, status);
expect(callback).toHaveBeenCalledWith(status);
statusAPI.delete(identifier);
expect(callback).toHaveBeenCalledWith(undefined);
unsubscribe();
});
it("returns a unsubscribe function", () => {
let unsubscribe = statusAPI.observe(identifier, callback);
unsubscribe();
statusAPI.set(identifier, status);
expect(callback).toHaveBeenCalledTimes(0);
});
});
});

View File

@@ -176,10 +176,7 @@ export default {
this.timestampKey = timeSystem.key;
},
showContextMenu(event) {
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);
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
},
resetValues() {
this.value = '---';

View File

@@ -51,7 +51,7 @@ export default class URLTimeSettingsSynchronizer {
initialize() {
this.updateTimeSettings();
window.addEventListener('hashchange', this.updateTimeSettings);
this.openmct.router.on('change:hash', this.updateTimeSettings);
TIME_EVENTS.forEach(event => {
this.openmct.time.on(event, this.setUrlFromTimeApi);
});
@@ -59,7 +59,7 @@ export default class URLTimeSettingsSynchronizer {
}
destroy() {
window.removeEventListener('hashchange', this.updateTimeSettings);
this.openmct.router.off('change:hash', this.updateTimeSettings);
this.openmct.off('start', this.initialize);
this.openmct.off('destroy', this.destroy);
@@ -69,7 +69,8 @@ export default class URLTimeSettingsSynchronizer {
this.openmct.time.off('bounds', this.updateBounds);
}
updateTimeSettings() {
updateTimeSettings(hash) {
// console.log('updateTimeSettings', hash);
// Prevent from triggering self
if (!this.isUrlUpdateInProgress) {
let timeParameters = this.parseParametersFromUrl();
@@ -177,7 +178,7 @@ export default class URLTimeSettingsSynchronizer {
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
this.isUrlUpdateInProgress = true;
setAllSearchParams(searchParams);
setAllSearchParams(this.openmct, searchParams);
}
areTimeParametersValid(timeParameters) {

View File

@@ -23,7 +23,6 @@
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';

View File

@@ -53,7 +53,7 @@ define([
openmct.indicators.add(indicator);
}
openmct.actions.register(new ClearDataAction.default(openmct, appliesToObjects));
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
};
};
});

View File

@@ -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 mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
const mockContextMenuProvider = jasmine.createSpyObj('contextMenu', ['registerAction']);
const openmct = {
objectViews: mockObjectViews,
indicators: mockIndicatorProvider,
actions: mockActionsProvider,
contextMenu: mockContextMenuProvider,
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(mockActionsProvider.register).toHaveBeenCalled();
expect(mockContextMenuProvider.registerAction).toHaveBeenCalled();
});
it('clear data action emits a clearData event when invoked', function () {

View File

@@ -64,16 +64,9 @@ define([
components: {
AlphanumericFormatView: AlphanumericFormatView.default
},
template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>'
template: '<alphanumeric-format-view></alphanumeric-format-view>'
});
},
getViewContext() {
if (component) {
return component.$refs.alphanumericFormatView.getViewContext();
} else {
return {};
}
},
destroy: function () {
component.$destroy();
component = undefined;

View File

@@ -1,33 +0,0 @@
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;
}
}

View File

@@ -142,18 +142,14 @@ export default {
this.domainObject = domainObject;
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
this.$nextTick(() => {
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;
}
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;
});
}
}

View File

@@ -30,15 +30,18 @@
>
<div
v-if="domainObject"
class="c-telemetry-view"
:class="[statusClass]"
class="u-style-receiver c-telemetry-view"
:class="{
styleClass,
'is-missing': domainObject.status === 'missing'
}"
:style="styleObject"
:data-font-size="item.fontSize"
:data-font="item.font"
@contextmenu.prevent="showContextMenu"
>
<div class="is-status__indicator"
title="This item is missing or suspect"
<div class="is-missing__indicator"
title="This item is missing"
></div>
<div
v-if="showLabel"
@@ -73,11 +76,10 @@
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 = ['copyToClipboard', 'copyToNotebook', 'viewHistoricalData'];
const CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
export default {
makeDefinition(openmct, gridSize, domainObject, position) {
@@ -127,18 +129,13 @@ export default {
},
data() {
return {
currentObjectPath: undefined,
datum: undefined,
domainObject: undefined,
formats: undefined,
viewKey: `alphanumeric-format-${Math.random()}`,
status: ''
domainObject: undefined,
currentObjectPath: undefined
};
},
computed: {
statusClass() {
return (this.status) ? `is-status--${this.status}` : '';
},
showLabel() {
let displayMode = this.item.displayMode;
@@ -216,13 +213,9 @@ export default {
this.openmct.objects.get(this.item.identifier)
.then(this.setObject);
this.openmct.time.on("bounds", this.refreshData);
this.status = this.openmct.status.get(this.item.identifier);
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
},
destroyed() {
this.removeSubscription();
this.removeStatusListener();
if (this.removeSelectable) {
this.removeSelectable();
@@ -231,18 +224,6 @@ 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 = {
@@ -280,16 +261,6 @@ 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);
@@ -313,40 +284,12 @@ 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);
},
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);
},
setStatus(status) {
this.status = status;
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
}
}
};

View File

@@ -29,12 +29,12 @@
@include isMissing($absPos: true);
.is-status__indicator {
.is-missing__indicator {
top: 0;
left: 0;
}
&.is-status--missing {
&.is-missing {
border: $borderMissing;
}
}

View File

@@ -26,12 +26,9 @@ 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) {

View File

@@ -1,10 +1,11 @@
<template>
<a
class="l-grid-view__item c-grid-item"
:class="[{
:class="{
'is-alias': item.isAlias === true,
'is-missing': item.model.status === 'missing',
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
}, statusClass]"
}"
:href="objectLink"
>
<div
@@ -26,8 +27,8 @@
</div>
</div>
<div class="c-grid-item__controls">
<div class="is-status__indicator"
title="This item is missing or suspect"
<div class="is-missing__indicator"
title="This item is missing"
></div>
<div
class="icon-people"
@@ -45,10 +46,9 @@
<script>
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
import objectLink from '../../../ui/mixins/object-link';
import statusListener from './status-listener';
export default {
mixins: [contextMenuGesture, objectLink, statusListener],
mixins: [contextMenuGesture, objectLink],
props: {
item: {
type: Object,

View File

@@ -8,18 +8,18 @@
<a
ref="objectLink"
class="c-object-label"
:class="[statusClass]"
:class="{ 'is-missing': item.model.status === 'missing' }"
:href="objectLink"
>
<div
class="c-object-label__type-icon c-list-item__name__type-icon"
class="c-object-label__type-icon c-list-item__type-icon"
:class="item.type.cssClass"
>
<span class="is-status__indicator"
title="This item is missing or suspect"
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<div class="c-object-label__name c-list-item__name__name">{{ item.model.name }}</div>
<div class="c-object-label__name c-list-item__name">{{ item.model.name }}</div>
</a>
</td>
<td class="c-list-item__type">
@@ -39,10 +39,9 @@
import moment from 'moment';
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
import objectLink from '../../../ui/mixins/object-link';
import statusListener from './status-listener';
export default {
mixins: [contextMenuGesture, objectLink, statusListener],
mixins: [contextMenuGesture, objectLink],
props: {
item: {
type: Object,

View File

@@ -43,7 +43,7 @@
}
}
&.is-status--missing {
&.is-missing {
@include isMissing();
[class*='__type-icon'],

View File

@@ -1,19 +1,11 @@
/******************************* LIST ITEM */
.c-list-item {
&__name__type-icon {
&__type-icon {
color: $colorItemTreeIcon;
}
&__name__name {
&__name {
@include ellipsize();
a & {
color: $colorItemFg;
}
}
&:not(.c-list-item__name) {
color: $colorItemFgDetails;
}
&.is-alias {

View File

@@ -28,5 +28,9 @@
padding-top: $p;
padding-bottom: $p;
width: 25%;
&:not(.c-list-item__name) {
color: $colorItemFgDetails;
}
}
}

View File

@@ -1,33 +0,0 @@
export default {
inject: ['openmct'],
props: {
item: {
type: Object,
required: true
}
},
computed: {
statusClass() {
return (this.status) ? `is-status--${this.status}` : '';
}
},
data() {
return {
status: ''
};
},
methods: {
setStatus(status) {
this.status = status;
}
},
mounted() {
let identifier = this.item.model.identifier;
this.status = this.openmct.status.get(identifier);
this.removeStatusListener = this.openmct.status.observe(identifier, this.setStatus);
},
destroyed() {
this.removeStatusListener();
}
};

View File

@@ -25,8 +25,6 @@ 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;
}
@@ -41,7 +39,7 @@ export default class GoToOriginalAction {
.slice(1)
.join('/');
window.location.href = url;
this._openmct.router.setHash(url);
});
}
appliesTo(objectPath) {

View File

@@ -23,6 +23,6 @@ import GoToOriginalAction from './goToOriginalAction';
export default function () {
return function (openmct) {
openmct.actions.register(new GoToOriginalAction(openmct));
openmct.contextMenu.registerAction(new GoToOriginalAction(openmct));
};
}

View File

@@ -28,8 +28,6 @@ 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 = {

View File

@@ -23,6 +23,6 @@ import NewFolderAction from './newFolderAction';
export default function () {
return function (openmct) {
openmct.actions.register(new NewFolderAction(openmct));
openmct.contextMenu.registerAction(new NewFolderAction(openmct));
};
}

View File

@@ -40,7 +40,9 @@ describe("the plugin", () => {
openmct.on('start', done);
openmct.startHeadless();
newFolderAction = openmct.actions._allActions.newFolder;
newFolderAction = openmct.contextMenu._allActions.filter(action => {
return action.key === 'newFolder';
})[0];
});
afterEach(() => {

View File

@@ -1,39 +0,0 @@
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;
}
}

View File

@@ -111,7 +111,7 @@ import Search from '@/ui/components/search.vue';
import SearchResults from './SearchResults.vue';
import Sidebar from './Sidebar.vue';
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
import { addNotebookEntry, createNewEmbed, getNotebookEntries } from '../utils/notebook-entries';
import { DEFAULT_CLASS, addNotebookEntry, createNewEmbed, getNotebookEntries } from '../utils/notebook-entries';
import objectUtils from 'objectUtils';
import { throttle } from 'lodash';
@@ -125,10 +125,11 @@ export default {
Sidebar
},
data() {
const configuration = this.domainObject.configuration || {};
return {
defaultPageId: getDefaultNotebook() ? getDefaultNotebook().page.id : '',
defaultSectionId: getDefaultNotebook() ? getDefaultNotebook().section.id : '',
defaultSort: this.domainObject.configuration.defaultSort,
defaultSort: configuration.defaultSort,
focusEntryId: null,
internalDomainObject: this.domainObject,
search: '',
@@ -416,7 +417,14 @@ export default {
return;
}
this.openmct.status.delete(domainObject.identifier);
const classList = domainObject.classList || [];
const index = classList.indexOf(DEFAULT_CLASS);
if (!classList.length || index < 0) {
return;
}
classList.splice(index, 1);
this.openmct.objects.mutate(domainObject, 'classList', classList);
},
searchItem(input) {
this.search = input;

View File

@@ -144,7 +144,7 @@ export default {
}
const url = new URL(link);
window.location.href = url.hash;
this.openmct.router.setHash(url.hash);
},
formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat);

View File

@@ -1,17 +1,29 @@
<template>
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
<button
class="c-icon-button c-button--menu icon-camera"
class="c-button--menu icon-notebook"
title="Take a Notebook Snapshot"
@click.stop.prevent="showMenu"
@click="setNotebookTypes"
@click.stop="toggleMenu"
>
<span
title="Take Notebook Snapshot"
class="c-icon-button__label"
>
Snapshot
</span>
<span class="c-button__label"></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>
@@ -45,20 +57,22 @@ export default {
data() {
return {
notebookSnapshot: null,
notebookTypes: []
notebookTypes: [],
showMenu: false
};
},
mounted() {
this.notebookSnapshot = new Snapshot(this.openmct);
this.setDefaultNotebookStatus();
document.addEventListener('click', this.hideMenu);
},
destroyed() {
document.removeEventListener('click', this.hideMenu);
},
methods: {
showMenu(event) {
setNotebookTypes() {
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;
@@ -69,24 +83,28 @@ export default {
notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
callBack: () => {
return this.snapshot(NOTEBOOK_DEFAULT);
}
type: NOTEBOOK_DEFAULT
});
}
}
notebookTypes.push({
cssClass: 'icon-camera',
cssClass: 'icon-notebook',
name: 'Save to Notebook Snapshots',
callBack: () => {
return this.snapshot(NOTEBOOK_SNAPSHOT);
}
type: NOTEBOOK_SNAPSHOT
});
this.openmct.menus.showMenu(x, y, notebookTypes);
this.notebookTypes = notebookTypes;
},
toggleMenu() {
this.showMenu = !this.showMenu;
},
hideMenu() {
this.showMenu = false;
},
snapshot(notebook) {
this.hideMenu();
this.$nextTick(() => {
const element = document.querySelector('.c-overlay__contents')
|| document.getElementsByClassName('l-shell__main-container')[0];
@@ -106,15 +124,6 @@ export default {
this.notebookSnapshot.capture(snapshotMeta, notebook.type, element);
});
},
setDefaultNotebookStatus() {
let defaultNotebookObject = getDefaultNotebook();
if (defaultNotebookObject && defaultNotebookObject.notebookMeta) {
let notebookIdentifier = defaultNotebookObject.notebookMeta.identifier;
this.openmct.status.set(notebookIdentifier, 'notebook-default');
}
}
}
};

View File

@@ -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-camera"></div>
<div class="c-object-label__type-icon icon-notebook"></div>
<div class="c-object-label__name">
Notebook Snapshots
<span v-if="snapshots.length"

View File

@@ -1,5 +1,5 @@
<template>
<div class="c-indicator c-indicator--clickable icon-camera"
<div class="c-indicator c-indicator--clickable icon-notebook"
:class="[
{ 's-status-off': snapshotCount === 0 },
{ 's-status-on': snapshotCount > 0 },

View File

@@ -1,4 +1,3 @@
import CopyToNotebookAction from './actions/CopyToNotebookAction';
import Notebook from './components/Notebook.vue';
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
import SnapshotContainer from './snapshot-container';
@@ -14,8 +13,6 @@ 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.',

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, resetApplicationState } from 'utils/testing';
import { createOpenMct, createMouseEvent, resetApplicationState } from 'utils/testing';
import NotebookPlugin from './plugin';
import Vue from 'vue';
@@ -133,4 +133,90 @@ 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');
});
});
});

View File

@@ -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);
});

View File

@@ -1,6 +1,6 @@
import objectLink from '../../../ui/mixins/object-link';
export const DEFAULT_CLASS = 'notebook-default';
export const DEFAULT_CLASS = 'is-notebook-default';
const TIME_BOUNDS = {
START_BOUND: 'tc.startBound',
END_BOUND: 'tc.endBound',
@@ -103,7 +103,7 @@ export function createNewEmbed(snapshotMeta, snapshot = '') {
};
}
export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null, entryText = '') {
export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null) {
if (!openmct || !domainObject || !notebookStorage) {
return;
}
@@ -125,11 +125,11 @@ export function addNotebookEntry(openmct, domainObject, notebookStorage, embed =
defaultEntries.push({
id,
createdOn: date,
text: entryText,
text: '',
embeds
});
addDefaultClass(domainObject, openmct);
addDefaultClass(domainObject);
openmct.objects.mutate(domainObject, 'configuration.entries', entries);
return id;
@@ -199,6 +199,11 @@ export function deleteNotebookEntries(openmct, domainObject, selectedSection, se
openmct.objects.mutate(domainObject, 'configuration.entries', entries);
}
function addDefaultClass(domainObject, openmct) {
openmct.status.set(domainObject.identifier, DEFAULT_CLASS);
function addDefaultClass(domainObject) {
const classList = domainObject.classList || [];
if (classList.includes(DEFAULT_CLASS)) {
return;
}
classList.push(DEFAULT_CLASS);
}

View File

@@ -40,7 +40,7 @@
<div class="c-state-indicator__alert-cursor-lock icon-cursor-lock" title="Cursor is point locked. Click anywhere in the plot to unlock."></div>
<div class="plot-legend-item"
ng-class="{
'is-status--missing': series.domainObject.status === 'missing'
'is-missing': series.domainObject.status === 'missing'
}"
ng-repeat="series in series track by $index"
>
@@ -48,7 +48,7 @@
<span class="plot-series-color-swatch"
ng-style="{ 'background-color': series.get('color').asHexString() }">
</span>
<span class="is-status__indicator" title="This item is missing or suspect"></span>
<span class="is-missing__indicator" title="This item is missing"></span>
<span class="plot-series-name">{{ series.nameWithUnit() }}</span>
</div>
<div class="plot-series-value hover-value-enabled value-to-display-{{ legend.get('valueToShowWhenCollapsed') }} {{ series.closest.mctLimitState.cssClass }}"
@@ -95,14 +95,14 @@
<tr ng-repeat="series in series"
class="plot-legend-item"
ng-class="{
'is-status--missing': series.domainObject.status === 'missing'
'is-missing': series.domainObject.status === 'missing'
}"
>
<td class="plot-series-swatch-and-name">
<span class="plot-series-color-swatch"
ng-style="{ 'background-color': series.get('color').asHexString() }">
</span>
<span class="is-status__indicator" title="This item is missing or suspect"></span>
<span class="is-missing__indicator" title="This item is missing"></span>
<span class="plot-series-name">{{ series.get('name') }}</span>
</td>

View File

@@ -58,8 +58,7 @@ define([
'./newFolderAction/plugin',
'./persistence/couch/plugin',
'./defaultRootName/plugin',
'./timeline/plugin',
'./viewDatumAction/plugin'
'./timeline/plugin'
], function (
_,
UTCTimeSystem,
@@ -98,8 +97,7 @@ define([
NewFolderAction,
CouchDBPlugin,
DefaultRootName,
Timeline,
ViewDatumAction
Timeline
) {
const bundleMap = {
LocalStorage: 'platform/persistence/local',
@@ -193,7 +191,6 @@ define([
plugins.ISOTimeFormat = ISOTimeFormat.default;
plugins.DefaultRootName = DefaultRootName.default;
plugins.Timeline = Timeline.default;
plugins.ViewDatumAction = ViewDatumAction.default;
return plugins;
});

View File

@@ -25,8 +25,6 @@ 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;
}
@@ -78,7 +76,7 @@ export default class RemoveAction {
.map(object => this.openmct.objects.makeKeyString(object.identifier))
.join("/");
window.location.href = '#/browse/' + urlPath;
this.openmct.router.setHash(`#/browse/${urlPath}`);
}
removeFromComposition(parent, child) {
@@ -105,16 +103,6 @@ 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;

View File

@@ -23,6 +23,6 @@ import RemoveAction from "./RemoveAction";
export default function () {
return function (openmct) {
openmct.actions.register(new RemoveAction(openmct));
openmct.contextMenu.registerAction(new RemoveAction(openmct));
};
}

View File

@@ -29,13 +29,13 @@
@click="showTab(tab, index)"
>
<div class="c-tabs-view__tab__label c-object-label"
:class="[tab.status ? `is-${tab.status}` : '']"
:class="{'is-missing': tab.domainObject.status === 'missing'}"
>
<div class="c-object-label__type-icon"
:class="tab.type.definition.cssClass"
>
<span class="is-status__indicator"
title="This item is missing or suspect"
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<span class="c-button__label c-object-label__name">{{ tab.domainObject.name }}</span>
@@ -192,10 +192,8 @@ export default {
},
addItem(domainObject) {
let type = this.openmct.types.get(domainObject.type) || unknownObjectType;
let status = this.openmct.status.get(domainObject.identifier);
let tabItem = {
domainObject,
status,
type: type,
key: this.openmct.objects.makeKeyString(domainObject.identifier)
};
@@ -266,12 +264,12 @@ export default {
let currentTabIndexInURL = getSearchParam(this.searchTabKey);
if (index !== currentTabIndexInURL) {
setSearchParam(this.searchTabKey, index);
setSearchParam(this.openmct, this.searchTabKey, index);
this.currentTabIndex = index;
}
},
clearCurrentTabIndexFromURL() {
deleteSearchParam(this.searchTabKey);
deleteSearchParam(this.openmct, this.searchTabKey);
}
}
};

View File

@@ -180,6 +180,7 @@ 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');
}
/**

View File

@@ -26,7 +26,6 @@ define([], function () {
this.columns = columns;
this.datum = createNormalizedDatum(datum, columns);
this.fullDatum = datum;
this.limitEvaluator = limitEvaluator;
this.objectKeyString = objectKeyString;
}
@@ -88,7 +87,7 @@ define([], function () {
}
getContextMenuActions() {
return ['viewDatumAction'];
return [];
}
}

View File

@@ -54,13 +54,15 @@ define([
view(domainObject, objectPath) {
let table = new TelemetryTable(domainObject, openmct);
let component;
let markingProp = {
enable: true,
useAlternateControlBar: false,
rowName: '',
rowNamePlural: ''
};
const view = {
return {
show: function (element, editMode) {
component = new Vue({
el: element,
@@ -70,8 +72,7 @@ define([
data() {
return {
isEditing: editMode,
markingProp,
view
markingProp
};
},
provide: {
@@ -79,7 +80,7 @@ define([
table,
objectPath
},
template: '<table-component ref="tableComponent" :isEditing="isEditing" :marking="markingProp" :view="view"/>'
template: '<table-component :isEditing="isEditing" :marking="markingProp"/>'
});
},
onEditModeChange(editMode) {
@@ -88,22 +89,11 @@ 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;

View File

@@ -1,123 +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.
*****************************************************************************/
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().exportMarkedRows();
},
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;

View File

@@ -102,17 +102,7 @@ export default {
selectable[columnKeys] = this.row.columns[columnKeys].selectable;
return selectable;
}, {}),
actionsViewContext: {
getViewContext: () => {
return {
viewHistoricalData: true,
viewDatumAction: true,
getDatum: this.getDatum,
skipCache: true
};
}
}
}, {})
};
},
computed: {
@@ -180,24 +170,14 @@ 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);
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);
}
this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions());
});
}
}

View File

@@ -23,7 +23,8 @@
<div class="c-table-wrapper"
:class="{ 'is-paused': paused }"
>
<div v-if="enableLegacyToolbar"
<!-- main contolbar start-->
<div v-if="!marking.useAlternateControlBar"
class="c-table-control-bar c-control-bar"
>
<button
@@ -93,6 +94,7 @@
<slot name="buttons"></slot>
</div>
<!-- main controlbar end -->
<!-- alternate controlbar start -->
<div v-if="marking.useAlternateControlBar"
@@ -111,11 +113,11 @@
<button
:class="{'hide-nice': !markedRows.length}"
class="c-icon-button icon-x labeled"
class="c-button icon-x labeled"
title="Deselect All"
@click="unmarkAllRows()"
>
<span class="c-icon-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span>
<span class="c-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span>
</button>
<slot name="buttons"></slot>
@@ -299,12 +301,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,
@@ -317,17 +319,6 @@ export default {
rowNamePlural: ""
};
}
},
enableLegacyToolbar: {
type: Boolean,
default: false
},
view: {
type: Object,
required: false,
default() {
return {};
}
}
},
data() {
@@ -403,40 +394,6 @@ 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']);
}
}
}
}
},
@@ -449,11 +406,6 @@ 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);
@@ -894,7 +846,7 @@ export default {
for (let i = firstRowIndex; i <= lastRowIndex; i++) {
let row = allRows[i];
this.$set(row, 'marked', true);
row.marked = true;
if (row !== baseRow) {
this.markedRows.push(row);
@@ -956,40 +908,6 @@ export default {
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']);
}
},
setRowHeight(height) {
this.rowHeight = height;
this.setHeight();

View File

@@ -23,13 +23,11 @@
define([
'./TelemetryTableViewProvider',
'./TableConfigurationViewProvider',
'./TelemetryTableType',
'./ViewActions'
'./TelemetryTableType'
], function (
TelemetryTableViewProvider,
TableConfigurationViewProvider,
TelemetryTableType,
TelemetryTableViewActions
TelemetryTableType
) {
return function plugin() {
return function install(openmct) {
@@ -43,10 +41,6 @@ define([
return true;
}
});
TelemetryTableViewActions.default.forEach(action => {
openmct.actions.register(action);
});
};
};
});

View File

@@ -168,8 +168,6 @@ 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]);

View File

@@ -1,69 +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 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;
}
}

View File

@@ -1,24 +0,0 @@
<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>

View File

@@ -1,30 +0,0 @@
.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;
}
}
}

View File

@@ -1,29 +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 ViewDatumAction from './ViewDatumAction.js';
export default function plugin() {
return function install(openmct) {
openmct.actions.register(new ViewDatumAction(openmct));
};
}

View File

@@ -1,95 +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 {
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();
});
});
});

View File

@@ -218,7 +218,7 @@ $colorBtnActiveFg: $colorOkFg;
$colorBtnSelectedBg: $colorSelectedBg;
$colorBtnSelectedFg: $colorSelectedFg;
$colorClickIconButton: $colorKey;
$colorClickIconButtonBgHov: rgba($colorKey, 0.3);
$colorClickIconButtonBgHov: rgba($colorKey, 0.6);
$colorClickIconButtonFgHov: $colorKeyHov;
$colorDropHint: $colorKey;
$colorDropHintBg: pushBack($colorDropHint, 10%);
@@ -378,11 +378,6 @@ $colorItemTreeVC: $colorDisclosureCtrl;
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
$shdwItemTreeIcon: none;
// Layout frame controls
$frameControlsColorFg: white;
$frameControlsColorBg: $colorKey;
$frameControlsShdw: $shdwMenu;
// Images
$colorThumbHoverBg: $colorItemTreeHoverBg;

View File

@@ -382,11 +382,6 @@ $colorItemTreeVC: $colorDisclosureCtrl;
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
$shdwItemTreeIcon: none;
// Layout frame controls
$frameControlsColorFg: white;
$frameControlsColorBg: $colorKey;
$frameControlsShdw: $shdwMenu;
// Images
$colorThumbHoverBg: $colorItemTreeHoverBg;

View File

@@ -80,13 +80,13 @@ $uiColor: #289fec; // Resize bars, splitter bars, etc.
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
$colorA: $colorBodyFg;
$colorAHov: $colorKey;
$filterHov: hue-rotate(-10deg) brightness(0.8) contrast(2); // Tree, location items
$colorSelectedBg: pushBack($colorKey, 40%);
$filterHov: brightness(0.8) contrast(2); // Tree, location items
$colorSelectedBg: rgba($colorKey, 0.2);
$colorSelectedFg: pullForward($colorBodyFg, 10%);
// Object labels
$objectLabelTypeIconOpacity: 0.5;
$objectLabelNameFilter: brightness(0.9);
$objectLabelNameFilter: brightness(0.5);
// Layout
$shellMainPad: 4px 0;
@@ -378,11 +378,6 @@ $colorItemTreeVC: $colorDisclosureCtrl;
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
$shdwItemTreeIcon: none;
// Layout frame controls
$frameControlsColorFg: $colorClickIconButton;
$frameControlsColorBg: $colorMenuBg;
$frameControlsShdw: $shdwMenu;
// Images
$colorThumbHoverBg: $colorItemTreeHoverBg;

View File

@@ -512,7 +512,7 @@ select {
&__section-hint {
$m: $interiorMargin;
margin: 0 0 $m 0;
margin: $m 0;
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
opacity: 0.6;
@@ -524,7 +524,7 @@ select {
$m: $interiorMargin;
border-top: 1px solid $colorInteriorBorder;
margin: $m 0;
padding: 0 nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
}
}

View File

@@ -59,12 +59,12 @@ mct-plot {
}
/*********************** MISSING ITEM INDICATORS */
.is-status__indicator {
.is-missing__indicator {
display: none;
}
.is-status--missing {
.is-missing {
@include isMissing();
.is-status__indicator {
.is-missing__indicator {
font-size: 0.8em;
}
}

View File

@@ -129,44 +129,31 @@
}
}
@mixin isStatus($absPos: false) {
// Supports CSS classing as follows:
// is-status--missing, is-status--suspect, etc.
@mixin isMissing($absPos: false) {
// Common styles to be applied to tree items, object labels, grid and list item views
//opacity: 0.7;
//pointer-events: none; // Don't think we can do this, as disables title hover on icon element
.is-status__indicator {
.is-missing__indicator {
display: none ;
text-shadow: $colorBodyBg 0 0 2px;
color: $colorAlert;
font-family: symbolsfont;
&[class^='is-status'] .is-status__indicator,
[class^='is-status'] .is-status__indicator {
display: block !important;
&:before {
content: $glyph-icon-alert-triangle;
}
}
@if $absPos {
@if $absPos {
.is-missing__indicator {
position: absolute;
z-index: 3;
}
}
}
@mixin isMissing($absPos: false) {
@include isStatus($absPos);
.is-status__indicator:before {
color: $colorAlert;
content: $glyph-icon-alert-triangle;
}
}
@mixin isSuspect($absPos: false) {
@include isStatus($absPos);
.is-status__indicator:before {
color: $colorWarningLo;
content: $glyph-icon-alert-rect;
}
&.is-missing .is-missing__indicator,
.is-missing .is-missing__indicator { display: block !important; }
}
@mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
@@ -515,8 +502,8 @@
}
@mixin cClickIconButtonLayout() {
$pLR: 5px;
$pTB: 5px;
$pLR: 4px;
$pTB: 4px;
padding: $pTB $pLR;
&:before,
@@ -535,7 +522,6 @@
@include cControl();
@include cClickIconButtonLayout();
background: none;
color: $colorClickIconButton;
box-shadow: none;
cursor: pointer;
transition: $transOut;
@@ -544,8 +530,7 @@
@include hover() {
transition: $transIn;
background: $colorClickIconButtonBgHov;
//color: $colorClickIconButtonFgHov;
filter: $filterHov;
color: $colorClickIconButtonFgHov;
}
&[class*="--major"] {

View File

@@ -97,7 +97,6 @@ div.c-table {
}
}
.c-table-control-bar {
.c-icon-button,
.c-click-icon,
.c-button {
&__label {

View File

@@ -27,7 +27,6 @@
@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";

View File

@@ -21,72 +21,44 @@
*****************************************************************************/
<template>
<div
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'
}
]"
class="c-so-view has-local-controls"
:class="{
'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="[ statusClass ]"
:class="{
classList,
'is-missing': domainObject.status === 'missing'
}"
>
<div class="c-object-label__type-icon"
:class="cssClass"
>
<span class="is-status__indicator"
title="This item is missing or suspect"
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<div class="c-object-label__name">
{{ domainObject && domainObject.name }}
</div>
</div>
<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>
<context-menu-drop-down
:object-path="objectPath"
/>
</div>
<div class="is-status__indicator"
title="This item is missing or suspect"
<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>
<div class="is-missing__indicator"
title="This item is missing"
></div>
<object-view
ref="objectView"
@@ -96,13 +68,13 @@
:object-path="objectPath"
:layout-font-size="layoutFontSize"
:layout-font="layoutFont"
@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';
@@ -117,7 +89,8 @@ const SIMPLE_CONTENT_TYPES = [
export default {
inject: ['openmct'],
components: {
ObjectView
ObjectView,
ContextMenuDropDown
},
props: {
domainObject: {
@@ -144,37 +117,22 @@ 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,
viewProvider,
statusBarItems,
status: ''
complexContent
};
},
computed: {
statusClass() {
return (this.status) ? `is-status--${this.status}` : '';
}
},
mounted() {
this.status = this.openmct.status.get(this.domainObject.identifier);
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
},
beforeDestroy() {
this.removeStatusListener();
classList() {
const classList = this.domainObject.classList;
if (!classList || !classList.length) {
return '';
}
if (this.actionCollection) {
this.unlistenToActionCollection();
return classList.join(' ');
}
},
methods: {
@@ -224,48 +182,6 @@ 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);
},
setStatus(status) {
this.status = status;
}
}
};

View File

@@ -1,9 +1,11 @@
<template>
<a
class="c-tree__item__label c-object-label"
:class="[statusClass]"
:class="{
classList,
'is-missing': observedObject.status === 'missing'
}"
draggable="true"
:href="objectLink"
@dragstart="dragStart"
@click="navigateOrPreview"
>
@@ -11,8 +13,8 @@
class="c-tree__item__type-icon c-object-label__type-icon"
:class="typeClass"
>
<span class="is-status__indicator"
title="This item is missing or suspect"
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<div class="c-tree__item__name c-object-label__name">
@@ -46,11 +48,18 @@ export default {
},
data() {
return {
observedObject: this.domainObject,
status: ''
observedObject: this.domainObject
};
},
computed: {
classList() {
const classList = this.observedObject.classList;
if (!classList || !classList.length) {
return '';
}
return classList.join(' ');
},
typeClass() {
let type = this.openmct.types.get(this.observedObject.type);
if (!type) {
@@ -58,9 +67,6 @@ export default {
}
return type.definition.cssClass;
},
statusClass() {
return (this.status) ? `is-status--${this.status}` : '';
}
},
mounted() {
@@ -71,19 +77,18 @@ export default {
this.$once('hook:destroyed', removeListener);
}
this.removeStatusListener = this.openmct.status.observe(this.observedObject.identifier, this.setStatus);
this.status = this.openmct.status.get(this.observedObject.identifier);
this.previewAction = new PreviewAction(this.openmct);
},
destroyed() {
this.removeStatusListener();
},
methods: {
navigateOrPreview(event) {
if (this.openmct.editor.isEditing()) {
event.preventDefault();
this.preview();
return;
}
this.openmct.router.setPath(this.objectLink);
},
preview() {
if (this.previewAction.appliesTo(this.objectPath)) {
@@ -108,9 +113,6 @@ export default {
// (eg. notabook.)
event.dataTransfer.setData("openmct/domain-object-path", serializedPath);
event.dataTransfer.setData(`openmct/domain-object/${keyString}`, this.domainObject);
},
setStatus(status) {
this.status = status;
}
}
};

View File

@@ -214,7 +214,6 @@ 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) {

View File

@@ -2,21 +2,20 @@
display: flex;
flex-direction: column;
&.is-missing {
border: $borderMissing;
}
/*************************** HEADER */
&__header {
flex: 0 0 auto;
display: flex;
justify-content: space-between;
font-size: 1.05em;
align-items: center;
margin-bottom: $interiorMarginSm;
padding: 3px;
padding: 1px 2px;
.c-object-label {
font-size: 1.05em;
&__type-icon {
opacity: $objectLabelTypeIconOpacity;
}
&__name {
filter: $objectLabelNameFilter;
}
@@ -32,123 +31,45 @@
}
}
/*************************** 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 {
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;
}
display: none;
}
/* HOVERS */
&:hover {
> .c-so-view__header {
visibility: visible;
}
}
&.is-status--missing {
&.is-missing {
@include isMissing($absPos: true);
.is-status__indicator {
.is-missing__indicator {
top: $interiorMargin;
left: $interiorMargin;
}
}
}
/*************************** OBJECT VIEW */
&__object-view {
flex: 1 1 auto;
height: 0; // Chrome 73 overflow bug fix
overflow: auto;
.u-fills-container {
// Expand component types that fill a container
@include abs();
}
&__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,
.c-icon-button {
.c-button {
// Shrink buttons a bit when they appear in a frame
border-radius: $smallCr !important;
font-size: 0.9em;
padding: 5px;
padding: 3px 5px;
}
&__view-large {
display: none;
}
&.has-complex-content {
> .c-so-view__view-large { display: block; }
}
&.is-status--missing {
border: $borderMissing;
}
&__object-view {
display: flex;
flex: 1 1 auto;

View File

@@ -19,9 +19,10 @@
display: block;
flex: 0 0 auto;
font-size: 1.1em;
opacity: $objectLabelTypeIconOpacity;
}
&.is-status--missing {
&.is-missing {
@include isMissing($absPos: true);
[class*='__type-icon']:before,
@@ -29,7 +30,7 @@
opacity: $opacityMissing;
}
.is-status__indicator {
.is-missing__indicator {
right: -3px;
top: -3px;
transform: scale(0.7);

View File

@@ -2,13 +2,13 @@
<div class="c-inspector__header">
<div v-if="!multiSelect"
class="c-inspector__selected c-object-label"
:class="{'is-status--missing': isMissing }"
:class="{'is-missing': isMissing }"
>
<div class="c-object-label__type-icon"
:class="typeCssClass"
>
<span class="is-status__indicator"
title="This item is missing or suspect"
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<span v-if="!singleSelectNonObject"

View File

@@ -29,10 +29,6 @@
filter: $objectLabelNameFilter;
}
.c-object-label__type-icon {
opacity: $objectLabelTypeIconOpacity;
}
&--non-domain-object .c-object-label__name {
font-style: italic;
}

View File

@@ -9,13 +9,16 @@
></button>
<div
class="l-browse-bar__object-name--w c-object-label"
:class="[statusClass]"
:class="{
classList,
'is-missing': domainObject.status === 'missing'
}"
>
<div class="c-object-label__type-icon"
:class="type.cssClass"
>
<span class="is-status__indicator"
title="This item is missing or suspect"
<span class="is-missing__indicator"
title="This item is missing"
></span>
</div>
<span
@@ -28,6 +31,10 @@
{{ 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">
@@ -35,6 +42,7 @@
v-if="!isEditing"
:current-view="currentView"
:views="views"
@setView="setView"
/>
<!-- Action buttons -->
<NotebookMenuSwitcher v-if="notebookEnabled"
@@ -44,24 +52,15 @@
/>
<div class="l-browse-bar__actions">
<button
v-for="(item, index) in statusBarItems"
:key="index"
v-if="isViewEditable && !isEditing"
:title="lockedOrUnlocked"
class="c-button"
:class="item.cssClass"
@click="item.callBack"
>
</button>
<button
v-if="isViewEditable & !isEditing"
:title="lockedOrUnlockedTitle"
:class="{
'c-button icon-lock': domainObject.locked,
'c-icon-button icon-unlocked': !domainObject.locked
'icon-lock c-button--caution': domainObject.locked,
'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"
@@ -107,11 +106,6 @@
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>
@@ -129,14 +123,6 @@ export default {
NotebookMenuSwitcher,
ViewSwitcher
},
props: {
viewProvider: {
type: Object,
default: () => {
return {};
}
}
},
data: function () {
return {
notebookTypes: [],
@@ -145,14 +131,17 @@ export default {
domainObject: PLACEHOLDER_OBJECT,
viewKey: undefined,
isEditing: this.openmct.editor.isEditing(),
notebookEnabled: this.openmct.types.get('notebook'),
statusBarItems: [],
status: ''
notebookEnabled: this.openmct.types.get('notebook')
};
},
computed: {
statusClass() {
return (this.status) ? `is-status--${this.status}` : '';
classList() {
const classList = this.domainObject.classList;
if (!classList || !classList.length) {
return '';
}
return classList.join(' ');
},
currentView() {
return this.views.filter(v => v.key === this.viewKey)[0] || {};
@@ -166,10 +155,7 @@ export default {
return {
key: p.key,
cssClass: p.cssClass,
name: p.name,
callBack: () => {
return this.setView({key: p.key});
}
name: p.name
};
});
},
@@ -201,7 +187,7 @@ export default {
return false;
},
lockedOrUnlockedTitle() {
lockedOrUnlocked() {
if (this.domainObject.locked) {
return 'Locked for editing - click to unlock.';
} else {
@@ -218,28 +204,6 @@ export default {
this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
this.domainObject = domainObject;
});
if (this.removeStatusListener) {
this.removeStatusListener();
}
this.status = this.openmct.status.get(this.domainObject.identifier, this.setStatus);
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
},
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 () {
@@ -255,14 +219,6 @@ export default {
this.mutationObserver();
}
if (this.actionCollection) {
this.unlistenToActionCollection();
}
if (this.removeStatusListener) {
this.removeStatusListener();
}
document.removeEventListener('click', this.closeViewAndSaveMenu);
window.removeEventListener('click', this.promptUserbeforeNavigatingAway);
},
@@ -344,35 +300,14 @@ 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;
this.openmct.router.setHash(this.parentUrl);
},
toggleLock(flag) {
this.openmct.objects.mutate(this.domainObject, 'locked', flag);
},
setStatus(status) {
this.status = status;
}
}
};

View File

@@ -70,7 +70,6 @@
<browse-bar
ref="browseBar"
class="l-shell__main-view-browse-bar"
:view-provider="viewProvider"
@sync-tree-navigation="handleSyncTreeNavigation"
/>
<toolbar
@@ -80,9 +79,8 @@
<object-view
ref="browseObject"
class="l-shell__main-container"
data-selectable
:show-edit-view="true"
@change-provider="setProvider"
data-selectable
/>
<component
:is="conductorComponent"
@@ -146,7 +144,6 @@ export default {
conductorComponent: undefined,
isEditing: false,
hasToolbar: false,
viewProvider: undefined,
headExpanded,
triggerSync: false
};
@@ -223,9 +220,6 @@ export default {
this.hasToolbar = structure.length > 0;
},
setProvider(provider) {
this.viewProvider = provider;
},
handleSyncTreeNavigation() {
this.triggerSync = !this.triggerSync;
}

View File

@@ -4,21 +4,36 @@
class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
>
<button
class="c-icon-button c-button--menu"
class="c-button--menu"
:class="currentView.cssClass"
title="Change the current view"
@click.prevent.stop="showMenu"
@click.stop="toggleViewMenu"
>
<span class="c-icon-button__label">
<span class="c-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,
@@ -29,16 +44,26 @@ 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);
},
showMenu() {
const elementBoundingClientRect = this.$el.getBoundingClientRect();
const x = elementBoundingClientRect.x;
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
this.openmct.menus.showMenu(x, y, this.views);
toggleViewMenu() {
this.showViewMenu = !this.showViewMenu;
},
hideViewMenu() {
this.showViewMenu = false;
}
}
};

View File

@@ -333,7 +333,7 @@
}
> * + * {
margin-left: $interiorMarginSm;
margin-left: $interiorMargin;
}
}
@@ -371,16 +371,12 @@
filter: $objectLabelNameFilter;
}
.c-object-label__type-icon {
opacity: $objectLabelTypeIconOpacity;
}
&__object-name--w {
@include headerFont(1.5em);
margin-left: $interiorMarginLg;
min-width: 0;
.is-status__indicator {
.is-missing__indicator {
right: -5px !important;
top: -4px !important;
}

View File

@@ -30,11 +30,7 @@ export default {
showContextMenu(event) {
event.preventDefault();
event.stopPropagation();
let actions = this.openmct.actions.get(this.objectPath);
let sortedActions = this.openmct.actions._groupAndSortActions(actions);
this.openmct.menus.showMenu(event.clientX, event.clientY, sortedActions);
this.openmct.contextMenu._showContextMenuForObjectPath(this.objectPath, event.clientX, event.clientY);
}
}
};

View File

@@ -25,6 +25,7 @@
:current-view="currentView"
:domain-object="domainObject"
:views="views"
@setView="setView"
/>
<div class="l-preview-window__object-view">
<div ref="objectView"></div>
@@ -50,20 +51,23 @@ export default {
return {
domainObject: domainObject,
viewKey: undefined,
views: [],
currentView: {}
viewKey: undefined
};
},
computed: {
views() {
return this
.openmct
.objectViews
.get(this.domainObject);
},
currentView() {
return this.views.filter(v => v.key === this.viewKey)[0] || {};
}
},
mounted() {
this.views = this.openmct.objectViews.get(this.domainObject).map((view) => {
view.callBack = () => {
return this.setView(view);
};
return view;
});
this.setView(this.views[0]);
let view = this.openmct.objectViews.get(this.domainObject)[0];
this.setView(view);
},
destroyed() {
this.view.destroy();
@@ -87,13 +91,9 @@ export default {
delete this.viewContainer;
},
setView(view) {
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);

View File

@@ -27,12 +27,10 @@ export default class PreviewAction {
/**
* Metadata
*/
this.name = 'View';
this.name = 'Preview';
this.key = 'preview';
this.description = 'View in large dialog';
this.cssClass = 'icon-items-expand';
this.group = 'windowing';
this.priority = 1;
this.description = 'Preview in large dialog';
this.cssClass = 'icon-eye-open';
/**
* Dependencies
@@ -83,7 +81,8 @@ export default class PreviewAction {
let targetObject = objectPath[0];
let navigatedObject = this._openmct.router.path[0];
return this._openmct.objects.areIdsEqual(targetObject.identifier, navigatedObject.identifier);
return targetObject.identifier.namespace === navigatedObject.identifier.namespace
&& targetObject.identifier.key === navigatedObject.identifier.key;
}
_preventPreview(objectPath) {
const noPreviewTypes = ['folder'];

View File

@@ -32,14 +32,4 @@ 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;
}
}
}

View File

@@ -24,7 +24,7 @@ import ViewHistoricalDataAction from './ViewHistoricalDataAction';
export default function () {
return function (openmct) {
openmct.actions.register(new PreviewAction(openmct));
openmct.actions.register(new ViewHistoricalDataAction(openmct));
openmct.contextMenu.registerAction(new PreviewAction(openmct));
openmct.contextMenu.registerAction(new ViewHistoricalDataAction(openmct));
};
}

View File

@@ -18,6 +18,7 @@
:v-if="!hideViewSwitcher"
:views="views"
:current-view="currentView"
@setView="setView"
/>
</div>
</div>

View File

@@ -21,8 +21,8 @@
*****************************************************************************/
/*global module*/
const LocationBar = require('location-bar');
const EventEmitter = require('EventEmitter');
const lodash = require('lodash');
function paramsToObject(searchParams) {
let params = {};
@@ -61,7 +61,9 @@ class ApplicationRouter extends EventEmitter {
super();
this.routes = [];
this.started = false;
this.locationBar = new LocationBar();
this.hashChanged = this.hashChanged.bind(this);
// this.setHash = lodash.debounce(this.setHash, 500);
}
/**
@@ -74,14 +76,31 @@ class ApplicationRouter extends EventEmitter {
this.started = true;
this.locationBar.onChange(p => this.handleLocationChange(p));
this.locationBar.start({
root: location.pathname
});
this.hashChanged();
window.addEventListener('hashchange', this.hashChanged);
}
getPathString() {
const hash = window.location.hash;
return hash.substring(1);
}
destroy() {
this.locationBar.stop();
window.removeEventListener(this.hashChanged);
}
hashChanged () {
const pathString = this.getPathString();
this.handleLocationChange(this.getPathString());
// const hasTimeSystem = pathString.includes('timeSystem=');
// if (hasTimeSystem) {
// console.log(location.hash);
// const currentState = history.state || {};
// history.pushState(currentState, 'hashchange', location.hash);
// }
// console.log('hashchanged', new Date().toISOString(), location.hash);
}
handleLocationChange(pathString) {
@@ -186,7 +205,25 @@ class ApplicationRouter extends EventEmitter {
}
set(path, queryString) {
location.hash = `${path}?${queryString}`;
const hash = `${path}?${queryString}`;
this.setHash(hash);
}
setHash(hash) {
if (hash === location.hash) {
return;
}
console.log('setHash', new Date().toISOString(), hash);
if (hash.includes('timeSystem=')) {
location.hash = hash;
} else {
this.handleLocationChange(hash);
}
this.emit('change:hash', hash);
}
setQueryString(queryString) {

View File

@@ -15,6 +15,7 @@ define([
openmct.router.route(/^\/browse\/(.*)$/, (path, results, params) => {
isRoutingInProgress = true;
let navigatePath = results[1];
navigateToPath(navigatePath, params.view);
onParamsChanged(null, null, params);

View File

@@ -1,13 +0,0 @@
class Clipboard {
updateClipboard(newClip) {
// return promise
return navigator.clipboard.writeText(newClip);
}
readClipboard() {
// return promise
return navigator.clipboard.readText();
}
}
export default new Clipboard();

Some files were not shown because too many files have changed in this diff Show More