mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 10:44:21 +00:00
Compare commits
75 Commits
omm-large-
...
openmct-st
Author | SHA1 | Date | |
---|---|---|---|
46ea383b51 | |||
7df7d57bc2 | |||
5f03dc45ee | |||
14ac758760 | |||
eb709a60cb | |||
eba1a48a44 | |||
4a0654dbcb | |||
9b6d339d69 | |||
f90afb9277 | |||
018dfb1e28 | |||
c72a02aaa3 | |||
bf3fd66942 | |||
8414ded1ec | |||
646c871c76 | |||
ba401b3341 | |||
5ef02ec4a2 | |||
d788031019 | |||
d870874649 | |||
711a7a2eb5 | |||
c105a08cfe | |||
b87375a809 | |||
9fed056d22 | |||
251bf21933 | |||
a180bf7c02 | |||
ed8a54f0f9 | |||
ff3c2da0f9 | |||
28d5821120 | |||
f5ee457274 | |||
9d2770e4d2 | |||
8b25009816 | |||
074fe4481a | |||
fbd928b842 | |||
110947db09 | |||
ef91e92fbc | |||
d201cac4ac | |||
dcb3ccfec7 | |||
78522cd4f1 | |||
ca232d45cc | |||
df495c841a | |||
92a37ef36b | |||
fd731ca430 | |||
263b1cd3d5 | |||
978fc8b5a3 | |||
698ccc5a35 | |||
e5aa5b5a5f | |||
b942988ef8 | |||
1eec20f2ea | |||
767a2048eb | |||
e65cf1661c | |||
0eae48646c | |||
0ba8a275d2 | |||
d8d32cc3ac | |||
a800848fe1 | |||
6881d98ba6 | |||
48d077cd2e | |||
030dd93c91 | |||
03bf6fc0a3 | |||
ef0a2ed5d2 | |||
a40aa84752 | |||
d3b69dda82 | |||
d3126ebf5c | |||
4479cbc7a2 | |||
f8ff44dac0 | |||
8f4280d15b | |||
6daa27ff31 | |||
43f6c3f85d | |||
1a7c76cf3e | |||
cee9cd7bd1 | |||
c42df20281 | |||
b4149bd2b3 | |||
f436ac9ba0 | |||
8493b481dd | |||
28723b59b7 | |||
9fa7de0b77 | |||
54bfc84ada |
@ -143,8 +143,8 @@ define([
|
|||||||
"$window"
|
"$window"
|
||||||
],
|
],
|
||||||
"group": "windowing",
|
"group": "windowing",
|
||||||
"cssClass": "icon-new-window",
|
"priority": 10,
|
||||||
"priority": "preferred"
|
"cssClass": "icon-new-window"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"runs": [
|
"runs": [
|
||||||
|
@ -139,7 +139,9 @@ define([
|
|||||||
],
|
],
|
||||||
"description": "Edit",
|
"description": "Edit",
|
||||||
"category": "view-control",
|
"category": "view-control",
|
||||||
"cssClass": "major icon-pencil"
|
"cssClass": "major icon-pencil",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "properties",
|
"key": "properties",
|
||||||
@ -150,6 +152,8 @@ define([
|
|||||||
"implementation": PropertiesAction,
|
"implementation": PropertiesAction,
|
||||||
"cssClass": "major icon-pencil",
|
"cssClass": "major icon-pencil",
|
||||||
"name": "Edit Properties...",
|
"name": "Edit Properties...",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 10,
|
||||||
"description": "Edit properties of this object.",
|
"description": "Edit properties of this object.",
|
||||||
"depends": [
|
"depends": [
|
||||||
"dialogService"
|
"dialogService"
|
||||||
|
@ -20,12 +20,12 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="c-object-label"
|
<div class="c-object-label"
|
||||||
ng-class="{ 'is-missing': model.status === 'missing' }"
|
ng-class="{ 'is-status--missing': model.status === 'missing' }"
|
||||||
>
|
>
|
||||||
<div class="c-object-label__type-icon {{type.getCssClass()}}"
|
<div class="c-object-label__type-icon {{type.getCssClass()}}"
|
||||||
ng-class="{ 'l-icon-link':location.isLink() }"
|
ng-class="{ 'l-icon-link':location.isLink() }"
|
||||||
>
|
>
|
||||||
<span class="is-missing__indicator" title="This item is missing"></span>
|
<span class="is-status__indicator" title="This item is missing or suspect"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class='c-object-label__name'>{{model.name}}</div>
|
<div class='c-object-label__name'>{{model.name}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,6 +66,8 @@ define([
|
|||||||
"description": "Move object to another location.",
|
"description": "Move object to another location.",
|
||||||
"cssClass": "icon-move",
|
"cssClass": "icon-move",
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 9,
|
||||||
"implementation": MoveAction,
|
"implementation": MoveAction,
|
||||||
"depends": [
|
"depends": [
|
||||||
"policyService",
|
"policyService",
|
||||||
@ -79,6 +81,8 @@ define([
|
|||||||
"description": "Duplicate object to another location.",
|
"description": "Duplicate object to another location.",
|
||||||
"cssClass": "icon-duplicate",
|
"cssClass": "icon-duplicate",
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 8,
|
||||||
"implementation": CopyAction,
|
"implementation": CopyAction,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$log",
|
"$log",
|
||||||
@ -95,6 +99,8 @@ define([
|
|||||||
"description": "Create Link to object in another location.",
|
"description": "Create Link to object in another location.",
|
||||||
"cssClass": "icon-link",
|
"cssClass": "icon-link",
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
|
"group": "action",
|
||||||
|
"priority": 7,
|
||||||
"implementation": LinkAction,
|
"implementation": LinkAction,
|
||||||
"depends": [
|
"depends": [
|
||||||
"policyService",
|
"policyService",
|
||||||
|
@ -47,6 +47,8 @@ define([
|
|||||||
"implementation": ExportAsJSONAction,
|
"implementation": ExportAsJSONAction,
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
"cssClass": "icon-export",
|
"cssClass": "icon-export",
|
||||||
|
"group": "json",
|
||||||
|
"priority": 2,
|
||||||
"depends": [
|
"depends": [
|
||||||
"openmct",
|
"openmct",
|
||||||
"exportService",
|
"exportService",
|
||||||
@ -61,6 +63,8 @@ define([
|
|||||||
"implementation": ImportAsJSONAction,
|
"implementation": ImportAsJSONAction,
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
"cssClass": "icon-import",
|
"cssClass": "icon-import",
|
||||||
|
"group": "json",
|
||||||
|
"priority": 2,
|
||||||
"depends": [
|
"depends": [
|
||||||
"exportService",
|
"exportService",
|
||||||
"identifierService",
|
"identifierService",
|
||||||
|
@ -242,7 +242,11 @@ define([
|
|||||||
|
|
||||||
this.overlays = new OverlayAPI.default();
|
this.overlays = new OverlayAPI.default();
|
||||||
|
|
||||||
this.contextMenu = new api.ContextMenuRegistry();
|
this.menus = new api.MenuAPI(this);
|
||||||
|
|
||||||
|
this.actions = new api.ActionsAPI(this);
|
||||||
|
|
||||||
|
this.status = new api.StatusAPI(this);
|
||||||
|
|
||||||
this.router = new ApplicationRouter();
|
this.router = new ApplicationRouter();
|
||||||
|
|
||||||
@ -271,6 +275,7 @@ define([
|
|||||||
this.install(this.plugins.URLTimeSettingsSynchronizer());
|
this.install(this.plugins.URLTimeSettingsSynchronizer());
|
||||||
this.install(this.plugins.NotificationIndicator());
|
this.install(this.plugins.NotificationIndicator());
|
||||||
this.install(this.plugins.NewFolderAction());
|
this.install(this.plugins.NewFolderAction());
|
||||||
|
this.install(this.plugins.ViewDatumAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
@ -35,5 +35,5 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
|
|||||||
|
|
||||||
legacyActions.filter(contextualCategoryOnly)
|
legacyActions.filter(contextualCategoryOnly)
|
||||||
.map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction))
|
.map(LegacyAction => new LegacyContextMenuAction(openmct, LegacyAction))
|
||||||
.forEach(openmct.contextMenu.registerAction);
|
.forEach(openmct.actions.register);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,8 @@ export default class LegacyContextMenuAction {
|
|||||||
this.description = LegacyAction.definition.description;
|
this.description = LegacyAction.definition.description;
|
||||||
this.cssClass = LegacyAction.definition.cssClass;
|
this.cssClass = LegacyAction.definition.cssClass;
|
||||||
this.LegacyAction = LegacyAction;
|
this.LegacyAction = LegacyAction;
|
||||||
|
this.group = LegacyAction.definition.group;
|
||||||
|
this.priority = LegacyAction.definition.priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke(objectPath) {
|
invoke(objectPath) {
|
||||||
|
178
src/api/actions/ActionCollection.js
Normal file
178
src/api/actions/ActionCollection.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
class ActionCollection extends EventEmitter {
|
||||||
|
constructor(applicableActions, objectPath, view, openmct) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.applicableActions = applicableActions;
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.objectPath = objectPath;
|
||||||
|
this.view = view;
|
||||||
|
this.objectUnsubscribes = [];
|
||||||
|
|
||||||
|
let debounceOptions = {
|
||||||
|
leading: false,
|
||||||
|
trailing: true
|
||||||
|
};
|
||||||
|
|
||||||
|
this._updateActions = _.debounce(this._updateActions.bind(this), 150, debounceOptions);
|
||||||
|
this._update = _.debounce(this._update.bind(this), 150, debounceOptions);
|
||||||
|
|
||||||
|
this._observeObjectPath();
|
||||||
|
this._initializeActions();
|
||||||
|
|
||||||
|
this.openmct.editor.on('isEditing', this._updateActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
disable(actionKeys) {
|
||||||
|
actionKeys.forEach(actionKey => {
|
||||||
|
if (this.applicableActions[actionKey]) {
|
||||||
|
this.applicableActions[actionKey].isDisabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
enable(actionKeys) {
|
||||||
|
actionKeys.forEach(actionKey => {
|
||||||
|
if (this.applicableActions[actionKey]) {
|
||||||
|
this.applicableActions[actionKey].isDisabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
hide(actionKeys) {
|
||||||
|
actionKeys.forEach(actionKey => {
|
||||||
|
if (this.applicableActions[actionKey]) {
|
||||||
|
this.applicableActions[actionKey].isHidden = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
show(actionKeys) {
|
||||||
|
actionKeys.forEach(actionKey => {
|
||||||
|
if (this.applicableActions[actionKey]) {
|
||||||
|
this.applicableActions[actionKey].isHidden = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.objectUnsubscribes.forEach(unsubscribe => {
|
||||||
|
unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.openmct.editor.off('isEditing', this._updateActions);
|
||||||
|
|
||||||
|
this.emit('destroy', this.view);
|
||||||
|
}
|
||||||
|
|
||||||
|
getVisibleActions() {
|
||||||
|
let actionsArray = Object.keys(this.applicableActions);
|
||||||
|
let visibleActions = [];
|
||||||
|
|
||||||
|
actionsArray.forEach(actionKey => {
|
||||||
|
let action = this.applicableActions[actionKey];
|
||||||
|
|
||||||
|
if (!action.isHidden) {
|
||||||
|
visibleActions.push(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return visibleActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatusBarActions() {
|
||||||
|
let actionsArray = Object.keys(this.applicableActions);
|
||||||
|
let statusBarActions = [];
|
||||||
|
|
||||||
|
actionsArray.forEach(actionKey => {
|
||||||
|
let action = this.applicableActions[actionKey];
|
||||||
|
|
||||||
|
if (action.showInStatusBar && !action.isDisabled && !action.isHidden) {
|
||||||
|
statusBarActions.push(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return statusBarActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
_update() {
|
||||||
|
this.emit('update', this.applicableActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
_observeObjectPath() {
|
||||||
|
let actionCollection = this;
|
||||||
|
|
||||||
|
function updateObject(oldObject, newObject) {
|
||||||
|
Object.assign(oldObject, newObject);
|
||||||
|
|
||||||
|
actionCollection._updateActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.objectPath.forEach(object => {
|
||||||
|
if (object) {
|
||||||
|
let unsubscribe = this.openmct.objects.observe(object, '*', updateObject.bind(this, object));
|
||||||
|
|
||||||
|
this.objectUnsubscribes.push(unsubscribe);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_initializeActions() {
|
||||||
|
Object.keys(this.applicableActions).forEach(key => {
|
||||||
|
this.applicableActions[key].callBack = () => {
|
||||||
|
return this.applicableActions[key].invoke(this.objectPath, this.view);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateActions() {
|
||||||
|
let newApplicableActions = this.openmct.actions._applicableActions(this.objectPath, this.view);
|
||||||
|
|
||||||
|
this.applicableActions = this._mergeOldAndNewActions(this.applicableActions, newApplicableActions);
|
||||||
|
this._initializeActions();
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
_mergeOldAndNewActions(oldActions, newActions) {
|
||||||
|
let mergedActions = {};
|
||||||
|
Object.keys(newActions).forEach(key => {
|
||||||
|
if (oldActions[key]) {
|
||||||
|
mergedActions[key] = oldActions[key];
|
||||||
|
} else {
|
||||||
|
mergedActions[key] = newActions[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return mergedActions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionCollection;
|
145
src/api/actions/ActionsAPI.js
Normal file
145
src/api/actions/ActionsAPI.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
import ActionCollection from './ActionCollection';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
class ActionsAPI extends EventEmitter {
|
||||||
|
constructor(openmct) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._allActions = {};
|
||||||
|
this._actionCollections = new WeakMap();
|
||||||
|
this._openmct = openmct;
|
||||||
|
|
||||||
|
this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json'];
|
||||||
|
|
||||||
|
this.register = this.register.bind(this);
|
||||||
|
this.get = this.get.bind(this);
|
||||||
|
this._applicableActions = this._applicableActions.bind(this);
|
||||||
|
this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
register(actionDefinition) {
|
||||||
|
this._allActions[actionDefinition.key] = actionDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(objectPath, view) {
|
||||||
|
let viewContext = view && view.getViewContext && view.getViewContext() || {};
|
||||||
|
|
||||||
|
if (view && !viewContext.skipCache) {
|
||||||
|
let cachedActionCollection = this._actionCollections.get(view);
|
||||||
|
|
||||||
|
if (cachedActionCollection) {
|
||||||
|
return cachedActionCollection;
|
||||||
|
} else {
|
||||||
|
let applicableActions = this._applicableActions(objectPath, view);
|
||||||
|
let actionCollection = new ActionCollection(applicableActions, objectPath, view, this._openmct);
|
||||||
|
|
||||||
|
this._actionCollections.set(view, actionCollection);
|
||||||
|
actionCollection.on('destroy', this._updateCachedActionCollections);
|
||||||
|
|
||||||
|
return actionCollection;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let applicableActions = this._applicableActions(objectPath, view);
|
||||||
|
|
||||||
|
Object.keys(applicableActions).forEach(key => {
|
||||||
|
let action = applicableActions[key];
|
||||||
|
|
||||||
|
action.callBack = () => {
|
||||||
|
return action.invoke(objectPath, view);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return applicableActions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGroupOrder(groupArray) {
|
||||||
|
this._groupOrder = groupArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateCachedActionCollections(key) {
|
||||||
|
if (this._actionCollections.has(key)) {
|
||||||
|
let actionCollection = this._actionCollections.get(key);
|
||||||
|
actionCollection.off('destroy', this._updateCachedActionCollections);
|
||||||
|
|
||||||
|
this._actionCollections.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_applicableActions(objectPath, view) {
|
||||||
|
let actionsObject = {};
|
||||||
|
|
||||||
|
let keys = Object.keys(this._allActions).filter(key => {
|
||||||
|
let actionDefinition = this._allActions[key];
|
||||||
|
|
||||||
|
if (actionDefinition.appliesTo === undefined) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return actionDefinition.appliesTo(objectPath, view);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
keys.forEach(key => {
|
||||||
|
let action = _.clone(this._allActions[key]);
|
||||||
|
|
||||||
|
actionsObject[key] = action;
|
||||||
|
});
|
||||||
|
|
||||||
|
return actionsObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
_groupAndSortActions(actionsArray) {
|
||||||
|
if (!Array.isArray(actionsArray) && typeof actionsArray === 'object') {
|
||||||
|
actionsArray = Object.keys(actionsArray).map(key => actionsArray[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actionsObject = {};
|
||||||
|
let groupedSortedActionsArray = [];
|
||||||
|
|
||||||
|
function sortDescending(a, b) {
|
||||||
|
return b.priority - a.priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsArray.forEach(action => {
|
||||||
|
if (actionsObject[action.group] === undefined) {
|
||||||
|
actionsObject[action.group] = [action];
|
||||||
|
} else {
|
||||||
|
actionsObject[action.group].push(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._groupOrder.forEach(group => {
|
||||||
|
let groupArray = actionsObject[group];
|
||||||
|
|
||||||
|
if (groupArray) {
|
||||||
|
groupedSortedActionsArray.push(groupArray.sort(sortDescending));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return groupedSortedActionsArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionsAPI;
|
@ -28,9 +28,10 @@ define([
|
|||||||
'./telemetry/TelemetryAPI',
|
'./telemetry/TelemetryAPI',
|
||||||
'./indicators/IndicatorAPI',
|
'./indicators/IndicatorAPI',
|
||||||
'./notifications/NotificationAPI',
|
'./notifications/NotificationAPI',
|
||||||
'./contextMenu/ContextMenuAPI',
|
'./Editor',
|
||||||
'./Editor'
|
'./menu/MenuAPI',
|
||||||
|
'./actions/ActionsAPI',
|
||||||
|
'./status/StatusAPI'
|
||||||
], function (
|
], function (
|
||||||
TimeAPI,
|
TimeAPI,
|
||||||
ObjectAPI,
|
ObjectAPI,
|
||||||
@ -39,8 +40,10 @@ define([
|
|||||||
TelemetryAPI,
|
TelemetryAPI,
|
||||||
IndicatorAPI,
|
IndicatorAPI,
|
||||||
NotificationAPI,
|
NotificationAPI,
|
||||||
ContextMenuAPI,
|
EditorAPI,
|
||||||
EditorAPI
|
MenuAPI,
|
||||||
|
ActionsAPI,
|
||||||
|
StatusAPI
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
TimeAPI: TimeAPI,
|
TimeAPI: TimeAPI,
|
||||||
@ -51,6 +54,8 @@ define([
|
|||||||
IndicatorAPI: IndicatorAPI,
|
IndicatorAPI: IndicatorAPI,
|
||||||
NotificationAPI: NotificationAPI.default,
|
NotificationAPI: NotificationAPI.default,
|
||||||
EditorAPI: EditorAPI,
|
EditorAPI: EditorAPI,
|
||||||
ContextMenuRegistry: ContextMenuAPI.default
|
MenuAPI: MenuAPI.default,
|
||||||
|
ActionsAPI: ActionsAPI.default,
|
||||||
|
StatusAPI: StatusAPI.default
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="c-menu">
|
|
||||||
<ul>
|
|
||||||
<li
|
|
||||||
v-for="action in actions"
|
|
||||||
:key="action.name"
|
|
||||||
:class="action.cssClass"
|
|
||||||
:title="action.description"
|
|
||||||
@click="action.invoke(objectPath)"
|
|
||||||
>
|
|
||||||
{{ action.name }}
|
|
||||||
</li>
|
|
||||||
<li v-if="actions.length === 0">
|
|
||||||
No actions defined.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
inject: ['actions', 'objectPath']
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,159 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
import ContextMenuComponent from './ContextMenu.vue';
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ContextMenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
|
|
||||||
* custom HTML elements.
|
|
||||||
* @interface ContextMenuAPI
|
|
||||||
* @memberof module:openmct
|
|
||||||
*/
|
|
||||||
class ContextMenuAPI {
|
|
||||||
constructor() {
|
|
||||||
this._allActions = [];
|
|
||||||
this._activeContextMenu = undefined;
|
|
||||||
|
|
||||||
this._hideActiveContextMenu = this._hideActiveContextMenu.bind(this);
|
|
||||||
this.registerAction = this.registerAction.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines an item to be added to context menus. Allows specification of text, appearance, and behavior when
|
|
||||||
* selected. Applicabilioty can be restricted by specification of an `appliesTo` function.
|
|
||||||
*
|
|
||||||
* @interface ContextMenuAction
|
|
||||||
* @memberof module:openmct
|
|
||||||
* @property {string} name the human-readable name of this view
|
|
||||||
* @property {string} description a longer-form description (typically
|
|
||||||
* a single sentence or short paragraph) of this kind of view
|
|
||||||
* @property {string} cssClass the CSS class to apply to labels for this
|
|
||||||
* view (to add icons, for instance)
|
|
||||||
* @property {string} key unique key to identify the context menu action
|
|
||||||
* (used in custom context menu eg table rows, to identify which actions to include)
|
|
||||||
* @property {boolean} hideInDefaultMenu optional flag to hide action from showing in the default context menu (tree item)
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @method appliesTo
|
|
||||||
* @memberof module:openmct.ContextMenuAction#
|
|
||||||
* @param {DomainObject[]} objectPath the path of the object that the context menu has been invoked on.
|
|
||||||
* @returns {boolean} true if the action applies to the objects specified in the 'objectPath', otherwise false.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Code to be executed when the action is selected from a context menu
|
|
||||||
* @method invoke
|
|
||||||
* @memberof module:openmct.ContextMenuAction#
|
|
||||||
* @param {DomainObject[]} objectPath the path of the object to invoke the action on.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @param {ContextMenuAction} actionDefinition
|
|
||||||
*/
|
|
||||||
registerAction(actionDefinition) {
|
|
||||||
this._allActions.push(actionDefinition);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_showContextMenuForObjectPath(objectPath, x, y, actionsToBeIncluded) {
|
|
||||||
|
|
||||||
let applicableActions = this._allActions.filter((action) => {
|
|
||||||
|
|
||||||
if (actionsToBeIncluded) {
|
|
||||||
if (action.appliesTo === undefined && actionsToBeIncluded.includes(action.key)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return action.appliesTo(objectPath, actionsToBeIncluded) && actionsToBeIncluded.includes(action.key);
|
|
||||||
} else {
|
|
||||||
if (action.appliesTo === undefined) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return action.appliesTo(objectPath) && !action.hideInDefaultMenu;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this._activeContextMenu) {
|
|
||||||
this._hideActiveContextMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._activeContextMenu = this._createContextMenuForObject(objectPath, applicableActions);
|
|
||||||
this._activeContextMenu.$mount();
|
|
||||||
document.body.appendChild(this._activeContextMenu.$el);
|
|
||||||
|
|
||||||
let position = this._calculatePopupPosition(x, y, this._activeContextMenu.$el);
|
|
||||||
this._activeContextMenu.$el.style.left = `${position.x}px`;
|
|
||||||
this._activeContextMenu.$el.style.top = `${position.y}px`;
|
|
||||||
|
|
||||||
document.addEventListener('click', this._hideActiveContextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_calculatePopupPosition(eventPosX, eventPosY, menuElement) {
|
|
||||||
let menuDimensions = menuElement.getBoundingClientRect();
|
|
||||||
let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
|
|
||||||
let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
|
|
||||||
|
|
||||||
if (overflowX > 0) {
|
|
||||||
eventPosX = eventPosX - overflowX;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overflowY > 0) {
|
|
||||||
eventPosY = eventPosY - overflowY;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: eventPosX,
|
|
||||||
y: eventPosY
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_hideActiveContextMenu() {
|
|
||||||
document.removeEventListener('click', this._hideActiveContextMenu);
|
|
||||||
document.body.removeChild(this._activeContextMenu.$el);
|
|
||||||
this._activeContextMenu.$destroy();
|
|
||||||
this._activeContextMenu = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_createContextMenuForObject(objectPath, actions) {
|
|
||||||
return new Vue({
|
|
||||||
components: {
|
|
||||||
ContextMenu: ContextMenuComponent
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
actions: actions,
|
|
||||||
objectPath: objectPath
|
|
||||||
},
|
|
||||||
template: '<ContextMenu></ContextMenu>'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default ContextMenuAPI;
|
|
67
src/api/menu/MenuAPI.js
Normal file
67
src/api/menu/MenuAPI.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import Menu from './menu.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
|
||||||
|
* custom HTML elements.
|
||||||
|
* @interface MenuAPI
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
|
||||||
|
class MenuAPI {
|
||||||
|
constructor(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
|
||||||
|
this.showMenu = this.showMenu.bind(this);
|
||||||
|
this._clearMenuComponent = this._clearMenuComponent.bind(this);
|
||||||
|
this._showObjectMenu = this._showObjectMenu.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
showMenu(x, y, actions) {
|
||||||
|
if (this.menuComponent) {
|
||||||
|
this.menuComponent.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
actions
|
||||||
|
};
|
||||||
|
|
||||||
|
this.menuComponent = new Menu(options);
|
||||||
|
this.menuComponent.once('destroy', this._clearMenuComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearMenuComponent() {
|
||||||
|
this.menuComponent = undefined;
|
||||||
|
delete this.menuComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
_showObjectMenu(objectPath, x, y, actionsToBeIncluded) {
|
||||||
|
let applicableActions = this.openmct.actions._groupedAndSortedObjectActions(objectPath, actionsToBeIncluded);
|
||||||
|
|
||||||
|
this.showMenu(x, y, applicableActions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default MenuAPI;
|
52
src/api/menu/components/Menu.vue
Normal file
52
src/api/menu/components/Menu.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-menu">
|
||||||
|
<ul v-if="actions.length && actions[0].length">
|
||||||
|
<template
|
||||||
|
v-for="(actionGroups, index) in actions"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="action in actionGroups"
|
||||||
|
:key="action.name"
|
||||||
|
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||||
|
:title="action.description"
|
||||||
|
@click="action.callBack"
|
||||||
|
>
|
||||||
|
{{ action.name }}
|
||||||
|
</li>
|
||||||
|
<div
|
||||||
|
v-if="index !== actions.length - 1"
|
||||||
|
:key="index"
|
||||||
|
class="c-menu__section-separator"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<li
|
||||||
|
v-if="actionGroups.length === 0"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
No actions defined.
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul v-else>
|
||||||
|
<li
|
||||||
|
v-for="action in actions"
|
||||||
|
:key="action.name"
|
||||||
|
:class="action.cssClass"
|
||||||
|
:title="action.description"
|
||||||
|
@click="action.callBack"
|
||||||
|
>
|
||||||
|
{{ action.name }}
|
||||||
|
</li>
|
||||||
|
<li v-if="actions.length === 0">
|
||||||
|
No actions defined.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['actions']
|
||||||
|
};
|
||||||
|
</script>
|
94
src/api/menu/menu.js
Normal file
94
src/api/menu/menu.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
import MenuComponent from './components/Menu.vue';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
class Menu extends EventEmitter {
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
this.component = new Vue({
|
||||||
|
provide: {
|
||||||
|
actions: options.actions
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MenuComponent
|
||||||
|
},
|
||||||
|
template: '<menu-component />'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.onDestroy) {
|
||||||
|
this.once('destroy', options.onDestroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dismiss = this.dismiss.bind(this);
|
||||||
|
this.show = this.show.bind(this);
|
||||||
|
|
||||||
|
this.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss() {
|
||||||
|
this.emit('destroy');
|
||||||
|
document.body.removeChild(this.component.$el);
|
||||||
|
document.removeEventListener('click', this.dismiss);
|
||||||
|
this.component.$destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
this.component.$mount();
|
||||||
|
document.body.appendChild(this.component.$el);
|
||||||
|
|
||||||
|
let position = this._calculatePopupPosition(this.options.x, this.options.y, this.component.$el);
|
||||||
|
|
||||||
|
this.component.$el.style.left = `${position.x}px`;
|
||||||
|
this.component.$el.style.top = `${position.y}px`;
|
||||||
|
|
||||||
|
document.addEventListener('click', this.dismiss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_calculatePopupPosition(eventPosX, eventPosY, menuElement) {
|
||||||
|
let menuDimensions = menuElement.getBoundingClientRect();
|
||||||
|
let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
|
||||||
|
let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
|
||||||
|
|
||||||
|
if (overflowX > 0) {
|
||||||
|
eventPosX = eventPosX - overflowX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflowY > 0) {
|
||||||
|
eventPosY = eventPosY - overflowY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: eventPosX,
|
||||||
|
y: eventPosY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Menu;
|
@ -22,6 +22,7 @@ class OverlayAPI {
|
|||||||
this.dismissLastOverlay();
|
this.dismissLastOverlay();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,6 +128,7 @@ class OverlayAPI {
|
|||||||
|
|
||||||
return progressDialog;
|
return progressDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OverlayAPI;
|
export default OverlayAPI;
|
||||||
|
67
src/api/status/StatusAPI.js
Normal file
67
src/api/status/StatusAPI.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
85
src/api/status/StatusAPISpec.js
Normal file
85
src/api/status/StatusAPISpec.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -176,7 +176,10 @@ export default {
|
|||||||
this.timestampKey = timeSystem.key;
|
this.timestampKey = timeSystem.key;
|
||||||
},
|
},
|
||||||
showContextMenu(event) {
|
showContextMenu(event) {
|
||||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
let allActions = this.openmct.actions.get(this.currentObjectPath, {}, {viewHistoricalData: true});
|
||||||
|
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
||||||
|
|
||||||
|
this.openmct.menus.showMenu(event.x, event.y, applicableActions);
|
||||||
},
|
},
|
||||||
resetValues() {
|
resetValues() {
|
||||||
this.value = '---';
|
this.value = '---';
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
export default class ClearDataAction {
|
export default class ClearDataAction {
|
||||||
constructor(openmct, appliesToObjects) {
|
constructor(openmct, appliesToObjects) {
|
||||||
this.name = 'Clear Data for Object';
|
this.name = 'Clear Data for Object';
|
||||||
|
this.key = 'clear-data-action';
|
||||||
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
|
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
|
||||||
this.cssClass = 'icon-clear-data';
|
this.cssClass = 'icon-clear-data';
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ define([
|
|||||||
openmct.indicators.add(indicator);
|
openmct.indicators.add(indicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
|
openmct.actions.register(new ClearDataAction.default(openmct, appliesToObjects));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -26,12 +26,12 @@ import ClearDataAction from '../clearDataAction.js';
|
|||||||
describe('When the Clear Data Plugin is installed,', function () {
|
describe('When the Clear Data Plugin is installed,', function () {
|
||||||
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
|
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
|
||||||
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
|
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
|
||||||
const mockContextMenuProvider = jasmine.createSpyObj('contextMenu', ['registerAction']);
|
const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
|
||||||
|
|
||||||
const openmct = {
|
const openmct = {
|
||||||
objectViews: mockObjectViews,
|
objectViews: mockObjectViews,
|
||||||
indicators: mockIndicatorProvider,
|
indicators: mockIndicatorProvider,
|
||||||
contextMenu: mockContextMenuProvider,
|
actions: mockActionsProvider,
|
||||||
install: function (plugin) {
|
install: function (plugin) {
|
||||||
plugin(this);
|
plugin(this);
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ describe('When the Clear Data Plugin is installed,', function () {
|
|||||||
it('Clear Data context menu action is installed', function () {
|
it('Clear Data context menu action is installed', function () {
|
||||||
openmct.install(ClearDataActionPlugin([]));
|
openmct.install(ClearDataActionPlugin([]));
|
||||||
|
|
||||||
expect(mockContextMenuProvider.registerAction).toHaveBeenCalled();
|
expect(mockActionsProvider.register).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clear data action emits a clearData event when invoked', function () {
|
it('clear data action emits a clearData event when invoked', function () {
|
||||||
|
@ -64,9 +64,16 @@ define([
|
|||||||
components: {
|
components: {
|
||||||
AlphanumericFormatView: AlphanumericFormatView.default
|
AlphanumericFormatView: AlphanumericFormatView.default
|
||||||
},
|
},
|
||||||
template: '<alphanumeric-format-view></alphanumeric-format-view>'
|
template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getViewContext() {
|
||||||
|
if (component) {
|
||||||
|
return component.$refs.alphanumericFormatView.getViewContext();
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
component = undefined;
|
component = undefined;
|
||||||
|
33
src/plugins/displayLayout/actions/CopyToClipboardAction.js
Normal file
33
src/plugins/displayLayout/actions/CopyToClipboardAction.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import clipboard from '@/utils/clipboard';
|
||||||
|
|
||||||
|
export default class CopyToClipboardAction {
|
||||||
|
constructor(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
|
||||||
|
this.cssClass = 'icon-duplicate';
|
||||||
|
this.description = 'Copy to Clipboard action';
|
||||||
|
this.group = "action";
|
||||||
|
this.key = 'copyToClipboard';
|
||||||
|
this.name = 'Copy to Clipboard';
|
||||||
|
this.priority = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(objectPath, viewContext) {
|
||||||
|
const formattedValue = viewContext.formattedValueForCopy();
|
||||||
|
clipboard.updateClipboard(formattedValue)
|
||||||
|
.then(() => {
|
||||||
|
this.openmct.notifications.info(`Success : copied to clipboard '${formattedValue}'`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.openmct.notifications.error(`Failed : to copy to clipboard '${formattedValue}'`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
appliesTo(objectPath, viewContext) {
|
||||||
|
if (viewContext && viewContext.getViewKey) {
|
||||||
|
return viewContext.getViewKey().includes('alphanumeric-format');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -138,14 +138,18 @@ export default {
|
|||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
|
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
let childContext = this.$refs.objectFrame.getSelectionContext();
|
let reference = this.$refs.objectFrame;
|
||||||
childContext.item = domainObject;
|
|
||||||
childContext.layoutItem = this.item;
|
if (reference) {
|
||||||
childContext.index = this.index;
|
let childContext = this.$refs.objectFrame.getSelectionContext();
|
||||||
this.context = childContext;
|
childContext.item = domainObject;
|
||||||
this.removeSelectable = this.openmct.selection.selectable(
|
childContext.layoutItem = this.item;
|
||||||
this.$el, this.context, this.immediatelySelect || this.initSelect);
|
childContext.index = this.index;
|
||||||
delete this.immediatelySelect;
|
this.context = childContext;
|
||||||
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
|
this.$el, this.context, this.immediatelySelect || this.initSelect);
|
||||||
|
delete this.immediatelySelect;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,15 +31,12 @@
|
|||||||
<div
|
<div
|
||||||
v-if="domainObject"
|
v-if="domainObject"
|
||||||
class="c-telemetry-view"
|
class="c-telemetry-view"
|
||||||
:class="{
|
:class="[styleClass]"
|
||||||
styleClass,
|
|
||||||
'is-missing': domainObject.status === 'missing'
|
|
||||||
}"
|
|
||||||
:style="styleObject"
|
:style="styleObject"
|
||||||
@contextmenu.prevent="showContextMenu"
|
@contextmenu.prevent.stop="showContextMenu"
|
||||||
>
|
>
|
||||||
<div class="is-missing__indicator"
|
<div class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
v-if="showLabel"
|
v-if="showLabel"
|
||||||
@ -74,10 +71,11 @@
|
|||||||
import LayoutFrame from './LayoutFrame.vue';
|
import LayoutFrame from './LayoutFrame.vue';
|
||||||
import printj from 'printj';
|
import printj from 'printj';
|
||||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
||||||
|
import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js';
|
||||||
|
|
||||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
|
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
|
||||||
const DEFAULT_POSITION = [1, 1];
|
const DEFAULT_POSITION = [1, 1];
|
||||||
const CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
|
const CONTEXT_MENU_ACTIONS = ['copyToClipboard', 'copyToNotebook', 'viewHistoricalData'];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
makeDefinition(openmct, gridSize, domainObject, position) {
|
makeDefinition(openmct, gridSize, domainObject, position) {
|
||||||
@ -126,13 +124,18 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
currentObjectPath: undefined,
|
||||||
datum: undefined,
|
datum: undefined,
|
||||||
formats: undefined,
|
|
||||||
domainObject: undefined,
|
domainObject: undefined,
|
||||||
currentObjectPath: undefined
|
formats: undefined,
|
||||||
|
viewKey: `alphanumeric-format-${Math.random()}`,
|
||||||
|
status: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
statusClass() {
|
||||||
|
return (this.status) ? `is-status--${this.status}` : '';
|
||||||
|
},
|
||||||
showLabel() {
|
showLabel() {
|
||||||
let displayMode = this.item.displayMode;
|
let displayMode = this.item.displayMode;
|
||||||
|
|
||||||
@ -205,9 +208,13 @@ export default {
|
|||||||
this.openmct.objects.get(this.item.identifier)
|
this.openmct.objects.get(this.item.identifier)
|
||||||
.then(this.setObject);
|
.then(this.setObject);
|
||||||
this.openmct.time.on("bounds", this.refreshData);
|
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() {
|
destroyed() {
|
||||||
this.removeSubscription();
|
this.removeSubscription();
|
||||||
|
this.removeStatusListener();
|
||||||
|
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
@ -216,6 +223,18 @@ export default {
|
|||||||
this.openmct.time.off("bounds", this.refreshData);
|
this.openmct.time.off("bounds", this.refreshData);
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
requestHistoricalData() {
|
||||||
let bounds = this.openmct.time.bounds();
|
let bounds = this.openmct.time.bounds();
|
||||||
let options = {
|
let options = {
|
||||||
@ -253,6 +272,16 @@ export default {
|
|||||||
this.requestHistoricalData(this.domainObject);
|
this.requestHistoricalData(this.domainObject);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getView() {
|
||||||
|
return {
|
||||||
|
getViewContext() {
|
||||||
|
return {
|
||||||
|
viewHistoricalData: true,
|
||||||
|
skipCache: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
setObject(domainObject) {
|
setObject(domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
@ -276,12 +305,40 @@ export default {
|
|||||||
this.removeSelectable = this.openmct.selection.selectable(
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
this.$el, this.context, this.immediatelySelect || this.initSelect);
|
this.$el, this.context, this.immediatelySelect || this.initSelect);
|
||||||
delete this.immediatelySelect;
|
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) {
|
updateTelemetryFormat(format) {
|
||||||
this.$emit('formatChanged', this.item, format);
|
this.$emit('formatChanged', this.item, format);
|
||||||
},
|
},
|
||||||
showContextMenu(event) {
|
async getContextMenuActions() {
|
||||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -29,12 +29,12 @@
|
|||||||
|
|
||||||
@include isMissing($absPos: true);
|
@include isMissing($absPos: true);
|
||||||
|
|
||||||
.is-missing__indicator {
|
.is-status__indicator {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-missing {
|
&.is-status--missing {
|
||||||
border: $borderMissing;
|
border: $borderMissing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,12 @@ import objectUtils from 'objectUtils';
|
|||||||
import DisplayLayoutType from './DisplayLayoutType.js';
|
import DisplayLayoutType from './DisplayLayoutType.js';
|
||||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
||||||
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
|
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
|
||||||
|
import CopyToClipboardAction from './actions/CopyToClipboardAction';
|
||||||
|
|
||||||
export default function DisplayLayoutPlugin(options) {
|
export default function DisplayLayoutPlugin(options) {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
|
openmct.actions.register(new CopyToClipboardAction(openmct));
|
||||||
|
|
||||||
openmct.objectViews.addProvider({
|
openmct.objectViews.addProvider({
|
||||||
key: 'layout.view',
|
key: 'layout.view',
|
||||||
canView: function (domainObject) {
|
canView: function (domainObject) {
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
class="l-grid-view__item c-grid-item"
|
class="l-grid-view__item c-grid-item"
|
||||||
:class="{
|
:class="[{
|
||||||
'is-alias': item.isAlias === true,
|
'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
|
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
|
||||||
}"
|
}, statusClass]"
|
||||||
:href="objectLink"
|
:href="objectLink"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -27,8 +26,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-grid-item__controls">
|
<div class="c-grid-item__controls">
|
||||||
<div class="is-missing__indicator"
|
<div class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
class="icon-people"
|
class="icon-people"
|
||||||
@ -46,9 +45,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
||||||
import objectLink from '../../../ui/mixins/object-link';
|
import objectLink from '../../../ui/mixins/object-link';
|
||||||
|
import statusListener from './status-listener';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -8,18 +8,18 @@
|
|||||||
<a
|
<a
|
||||||
ref="objectLink"
|
ref="objectLink"
|
||||||
class="c-object-label"
|
class="c-object-label"
|
||||||
:class="{ 'is-missing': item.model.status === 'missing' }"
|
:class="[statusClass]"
|
||||||
:href="objectLink"
|
:href="objectLink"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-object-label__type-icon c-list-item__type-icon"
|
class="c-object-label__type-icon c-list-item__name__type-icon"
|
||||||
:class="item.type.cssClass"
|
:class="item.type.cssClass"
|
||||||
>
|
>
|
||||||
<span class="is-missing__indicator"
|
<span class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-object-label__name c-list-item__name">{{ item.model.name }}</div>
|
<div class="c-object-label__name c-list-item__name__name">{{ item.model.name }}</div>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="c-list-item__type">
|
<td class="c-list-item__type">
|
||||||
@ -39,9 +39,10 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
import contextMenuGesture from '../../../ui/mixins/context-menu-gesture';
|
||||||
import objectLink from '../../../ui/mixins/object-link';
|
import objectLink from '../../../ui/mixins/object-link';
|
||||||
|
import statusListener from './status-listener';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-missing {
|
&.is-status--missing {
|
||||||
@include isMissing();
|
@include isMissing();
|
||||||
|
|
||||||
[class*='__type-icon'],
|
[class*='__type-icon'],
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
/******************************* LIST ITEM */
|
/******************************* LIST ITEM */
|
||||||
.c-list-item {
|
.c-list-item {
|
||||||
&__type-icon {
|
&__name__type-icon {
|
||||||
color: $colorItemTreeIcon;
|
color: $colorItemTreeIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name__name {
|
||||||
@include ellipsize();
|
@include ellipsize();
|
||||||
|
|
||||||
|
a & {
|
||||||
|
color: $colorItemFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.c-list-item__name) {
|
||||||
|
color: $colorItemFgDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-alias {
|
&.is-alias {
|
||||||
|
@ -28,9 +28,5 @@
|
|||||||
padding-top: $p;
|
padding-top: $p;
|
||||||
padding-bottom: $p;
|
padding-bottom: $p;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
|
|
||||||
&:not(.c-list-item__name) {
|
|
||||||
color: $colorItemFgDetails;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
src/plugins/folderView/components/status-listener.js
Normal file
33
src/plugins/folderView/components/status-listener.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
@ -25,6 +25,8 @@ export default class GoToOriginalAction {
|
|||||||
this.name = 'Go To Original';
|
this.name = 'Go To Original';
|
||||||
this.key = 'goToOriginal';
|
this.key = 'goToOriginal';
|
||||||
this.description = 'Go to the original unlinked instance of this object';
|
this.description = 'Go to the original unlinked instance of this object';
|
||||||
|
this.group = 'action';
|
||||||
|
this.priority = 4;
|
||||||
|
|
||||||
this._openmct = openmct;
|
this._openmct = openmct;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,6 @@ import GoToOriginalAction from './goToOriginalAction';
|
|||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
openmct.contextMenu.registerAction(new GoToOriginalAction(openmct));
|
openmct.actions.register(new GoToOriginalAction(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,8 @@ export default class NewFolderAction {
|
|||||||
this.key = 'newFolder';
|
this.key = 'newFolder';
|
||||||
this.description = 'Create a new folder';
|
this.description = 'Create a new folder';
|
||||||
this.cssClass = 'icon-folder-new';
|
this.cssClass = 'icon-folder-new';
|
||||||
|
this.group = "action";
|
||||||
|
this.priority = 9;
|
||||||
|
|
||||||
this._openmct = openmct;
|
this._openmct = openmct;
|
||||||
this._dialogForm = {
|
this._dialogForm = {
|
||||||
|
@ -23,6 +23,6 @@ import NewFolderAction from './newFolderAction';
|
|||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
openmct.contextMenu.registerAction(new NewFolderAction(openmct));
|
openmct.actions.register(new NewFolderAction(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,7 @@ describe("the plugin", () => {
|
|||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
|
|
||||||
newFolderAction = openmct.contextMenu._allActions.filter(action => {
|
newFolderAction = openmct.actions._allActions.newFolder;
|
||||||
return action.key === 'newFolder';
|
|
||||||
})[0];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
39
src/plugins/notebook/actions/CopyToNotebookAction.js
Normal file
39
src/plugins/notebook/actions/CopyToNotebookAction.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { getDefaultNotebook } from '../utils/notebook-storage';
|
||||||
|
import { addNotebookEntry } from '../utils/notebook-entries';
|
||||||
|
|
||||||
|
export default class CopyToNotebookAction {
|
||||||
|
constructor(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
|
||||||
|
this.cssClass = 'icon-duplicate';
|
||||||
|
this.description = 'Copy to Notebook action';
|
||||||
|
this.group = "action";
|
||||||
|
this.key = 'copyToNotebook';
|
||||||
|
this.name = 'Copy to Notebook';
|
||||||
|
this.priority = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyToNotebook(entryText) {
|
||||||
|
const notebookStorage = getDefaultNotebook();
|
||||||
|
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
|
||||||
|
.then(domainObject => {
|
||||||
|
addNotebookEntry(this.openmct, domainObject, notebookStorage, null, entryText);
|
||||||
|
|
||||||
|
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
|
||||||
|
const msg = `Saved to Notebook ${defaultPath}`;
|
||||||
|
this.openmct.notifications.info(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(objectPath, viewContext) {
|
||||||
|
this.copyToNotebook(viewContext.formattedValueForCopy());
|
||||||
|
}
|
||||||
|
|
||||||
|
appliesTo(objectPath, viewContext) {
|
||||||
|
if (viewContext && viewContext.getViewKey) {
|
||||||
|
return viewContext.getViewKey().includes('alphanumeric-format');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -111,7 +111,7 @@ import Search from '@/ui/components/search.vue';
|
|||||||
import SearchResults from './SearchResults.vue';
|
import SearchResults from './SearchResults.vue';
|
||||||
import Sidebar from './Sidebar.vue';
|
import Sidebar from './Sidebar.vue';
|
||||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
||||||
import { DEFAULT_CLASS, addNotebookEntry, createNewEmbed, getNotebookEntries } from '../utils/notebook-entries';
|
import { addNotebookEntry, createNewEmbed, getNotebookEntries } from '../utils/notebook-entries';
|
||||||
import objectUtils from 'objectUtils';
|
import objectUtils from 'objectUtils';
|
||||||
|
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
@ -416,14 +416,7 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const classList = domainObject.classList || [];
|
this.openmct.status.delete(domainObject.identifier);
|
||||||
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) {
|
searchItem(input) {
|
||||||
this.search = input;
|
this.search = input;
|
||||||
|
@ -1,29 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
<button
|
<button
|
||||||
class="c-button--menu icon-notebook"
|
class="c-icon-button c-button--menu icon-camera"
|
||||||
title="Take a Notebook Snapshot"
|
title="Take a Notebook Snapshot"
|
||||||
@click="setNotebookTypes"
|
@click.stop.prevent="showMenu"
|
||||||
@click.stop="toggleMenu"
|
|
||||||
>
|
>
|
||||||
<span class="c-button__label"></span>
|
<span
|
||||||
|
title="Take Notebook Snapshot"
|
||||||
|
class="c-icon-button__label"
|
||||||
|
>
|
||||||
|
Snapshot
|
||||||
|
</span>
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -57,22 +45,20 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
notebookSnapshot: null,
|
notebookSnapshot: null,
|
||||||
notebookTypes: [],
|
notebookTypes: []
|
||||||
showMenu: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.notebookSnapshot = new Snapshot(this.openmct);
|
this.notebookSnapshot = new Snapshot(this.openmct);
|
||||||
|
this.setDefaultNotebookStatus();
|
||||||
document.addEventListener('click', this.hideMenu);
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
document.removeEventListener('click', this.hideMenu);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setNotebookTypes() {
|
showMenu(event) {
|
||||||
const notebookTypes = [];
|
const notebookTypes = [];
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
|
const elementBoundingClientRect = this.$el.getBoundingClientRect();
|
||||||
|
const x = elementBoundingClientRect.x;
|
||||||
|
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
|
||||||
|
|
||||||
if (defaultNotebook) {
|
if (defaultNotebook) {
|
||||||
const domainObject = defaultNotebook.domainObject;
|
const domainObject = defaultNotebook.domainObject;
|
||||||
@ -83,28 +69,24 @@ export default {
|
|||||||
notebookTypes.push({
|
notebookTypes.push({
|
||||||
cssClass: 'icon-notebook',
|
cssClass: 'icon-notebook',
|
||||||
name: `Save to Notebook ${defaultPath}`,
|
name: `Save to Notebook ${defaultPath}`,
|
||||||
type: NOTEBOOK_DEFAULT
|
callBack: () => {
|
||||||
|
return this.snapshot(NOTEBOOK_DEFAULT);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notebookTypes.push({
|
notebookTypes.push({
|
||||||
cssClass: 'icon-notebook',
|
cssClass: 'icon-camera',
|
||||||
name: 'Save to Notebook Snapshots',
|
name: 'Save to Notebook Snapshots',
|
||||||
type: NOTEBOOK_SNAPSHOT
|
callBack: () => {
|
||||||
|
return this.snapshot(NOTEBOOK_SNAPSHOT);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.notebookTypes = notebookTypes;
|
this.openmct.menus.showMenu(x, y, notebookTypes);
|
||||||
},
|
|
||||||
toggleMenu() {
|
|
||||||
this.showMenu = !this.showMenu;
|
|
||||||
},
|
|
||||||
hideMenu() {
|
|
||||||
this.showMenu = false;
|
|
||||||
},
|
},
|
||||||
snapshot(notebook) {
|
snapshot(notebook) {
|
||||||
this.hideMenu();
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const element = document.querySelector('.c-overlay__contents')
|
const element = document.querySelector('.c-overlay__contents')
|
||||||
|| document.getElementsByClassName('l-shell__main-container')[0];
|
|| document.getElementsByClassName('l-shell__main-container')[0];
|
||||||
@ -124,6 +106,15 @@ export default {
|
|||||||
|
|
||||||
this.notebookSnapshot.capture(snapshotMeta, notebook.type, element);
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="l-browse-bar__start">
|
<div class="l-browse-bar__start">
|
||||||
<div class="l-browse-bar__object-name--w">
|
<div class="l-browse-bar__object-name--w">
|
||||||
<div class="l-browse-bar__object-name c-object-label">
|
<div class="l-browse-bar__object-name c-object-label">
|
||||||
<div class="c-object-label__type-icon icon-notebook"></div>
|
<div class="c-object-label__type-icon icon-camera"></div>
|
||||||
<div class="c-object-label__name">
|
<div class="c-object-label__name">
|
||||||
Notebook Snapshots
|
Notebook Snapshots
|
||||||
<span v-if="snapshots.length"
|
<span v-if="snapshots.length"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-indicator c-indicator--clickable icon-notebook"
|
<div class="c-indicator c-indicator--clickable icon-camera"
|
||||||
:class="[
|
:class="[
|
||||||
{ 's-status-off': snapshotCount === 0 },
|
{ 's-status-off': snapshotCount === 0 },
|
||||||
{ 's-status-on': snapshotCount > 0 },
|
{ 's-status-on': snapshotCount > 0 },
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import CopyToNotebookAction from './actions/CopyToNotebookAction';
|
||||||
import Notebook from './components/Notebook.vue';
|
import Notebook from './components/Notebook.vue';
|
||||||
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
|
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
|
||||||
import SnapshotContainer from './snapshot-container';
|
import SnapshotContainer from './snapshot-container';
|
||||||
@ -13,6 +14,8 @@ export default function NotebookPlugin() {
|
|||||||
|
|
||||||
installed = true;
|
installed = true;
|
||||||
|
|
||||||
|
openmct.actions.register(new CopyToNotebookAction(openmct));
|
||||||
|
|
||||||
const notebookType = {
|
const notebookType = {
|
||||||
name: 'Notebook',
|
name: 'Notebook',
|
||||||
description: 'Create and save timestamped notes with embedded object snapshots.',
|
description: 'Create and save timestamped notes with embedded object snapshots.',
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { createOpenMct, createMouseEvent, resetApplicationState } from 'utils/testing';
|
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||||
import NotebookPlugin from './plugin';
|
import NotebookPlugin from './plugin';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
@ -133,90 +133,4 @@ describe("Notebook plugin:", () => {
|
|||||||
expect(hasMajorElements).toBe(true);
|
expect(hasMajorElements).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Notebook Snapshots view:", () => {
|
|
||||||
let snapshotIndicator;
|
|
||||||
let drawerElement;
|
|
||||||
|
|
||||||
function clickSnapshotIndicator() {
|
|
||||||
const indicator = element.querySelector('.icon-notebook');
|
|
||||||
const button = indicator.querySelector('button');
|
|
||||||
const clickEvent = createMouseEvent('click');
|
|
||||||
|
|
||||||
button.dispatchEvent(clickEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
snapshotIndicator = openmct.indicators.indicatorObjects
|
|
||||||
.find(indicator => indicator.key === 'notebook-snapshot-indicator').element;
|
|
||||||
|
|
||||||
element.append(snapshotIndicator);
|
|
||||||
|
|
||||||
return Vue.nextTick();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
snapshotIndicator.remove();
|
|
||||||
if (drawerElement) {
|
|
||||||
drawerElement.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
drawerElement = document.querySelector('.l-shell__drawer');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (drawerElement) {
|
|
||||||
drawerElement.classList.remove('is-expanded');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has Snapshots indicator", () => {
|
|
||||||
const hasSnapshotIndicator = snapshotIndicator !== null && snapshotIndicator !== undefined;
|
|
||||||
expect(hasSnapshotIndicator).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("snapshots container has class isExpanded", () => {
|
|
||||||
let classes = drawerElement.classList;
|
|
||||||
const isExpandedBefore = classes.contains('is-expanded');
|
|
||||||
|
|
||||||
clickSnapshotIndicator();
|
|
||||||
classes = drawerElement.classList;
|
|
||||||
const isExpandedAfterFirstClick = classes.contains('is-expanded');
|
|
||||||
|
|
||||||
const success = isExpandedBefore === false
|
|
||||||
&& isExpandedAfterFirstClick === true;
|
|
||||||
|
|
||||||
expect(success).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("snapshots container does not have class isExpanded", () => {
|
|
||||||
let classes = drawerElement.classList;
|
|
||||||
const isExpandedBefore = classes.contains('is-expanded');
|
|
||||||
|
|
||||||
clickSnapshotIndicator();
|
|
||||||
classes = drawerElement.classList;
|
|
||||||
const isExpandedAfterFirstClick = classes.contains('is-expanded');
|
|
||||||
|
|
||||||
clickSnapshotIndicator();
|
|
||||||
classes = drawerElement.classList;
|
|
||||||
const isExpandedAfterSecondClick = classes.contains('is-expanded');
|
|
||||||
|
|
||||||
const success = isExpandedBefore === false
|
|
||||||
&& isExpandedAfterFirstClick === true
|
|
||||||
&& isExpandedAfterSecondClick === false;
|
|
||||||
|
|
||||||
expect(success).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("show notebook snapshots container text", () => {
|
|
||||||
clickSnapshotIndicator();
|
|
||||||
|
|
||||||
const notebookSnapshots = drawerElement.querySelector('.l-browse-bar__object-name');
|
|
||||||
const snapshotsText = notebookSnapshots.textContent.trim();
|
|
||||||
|
|
||||||
expect(snapshotsText).toBe('Notebook Snapshots');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -49,7 +49,7 @@ export default class Snapshot {
|
|||||||
.then(domainObject => {
|
.then(domainObject => {
|
||||||
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
|
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}`;
|
const msg = `Saved to Notebook ${defaultPath}`;
|
||||||
this._showNotification(msg);
|
this._showNotification(msg);
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import objectLink from '../../../ui/mixins/object-link';
|
import objectLink from '../../../ui/mixins/object-link';
|
||||||
|
|
||||||
export const DEFAULT_CLASS = 'is-notebook-default';
|
export const DEFAULT_CLASS = 'notebook-default';
|
||||||
const TIME_BOUNDS = {
|
const TIME_BOUNDS = {
|
||||||
START_BOUND: 'tc.startBound',
|
START_BOUND: 'tc.startBound',
|
||||||
END_BOUND: 'tc.endBound',
|
END_BOUND: 'tc.endBound',
|
||||||
@ -103,7 +103,7 @@ export function createNewEmbed(snapshotMeta, snapshot = '') {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null) {
|
export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null, entryText = '') {
|
||||||
if (!openmct || !domainObject || !notebookStorage) {
|
if (!openmct || !domainObject || !notebookStorage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -125,11 +125,11 @@ export function addNotebookEntry(openmct, domainObject, notebookStorage, embed =
|
|||||||
defaultEntries.push({
|
defaultEntries.push({
|
||||||
id,
|
id,
|
||||||
createdOn: date,
|
createdOn: date,
|
||||||
text: '',
|
text: entryText,
|
||||||
embeds
|
embeds
|
||||||
});
|
});
|
||||||
|
|
||||||
addDefaultClass(domainObject);
|
addDefaultClass(domainObject, openmct);
|
||||||
openmct.objects.mutate(domainObject, 'configuration.entries', entries);
|
openmct.objects.mutate(domainObject, 'configuration.entries', entries);
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
@ -199,11 +199,6 @@ export function deleteNotebookEntries(openmct, domainObject, selectedSection, se
|
|||||||
openmct.objects.mutate(domainObject, 'configuration.entries', entries);
|
openmct.objects.mutate(domainObject, 'configuration.entries', entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDefaultClass(domainObject) {
|
function addDefaultClass(domainObject, openmct) {
|
||||||
const classList = domainObject.classList || [];
|
openmct.status.set(domainObject.identifier, DEFAULT_CLASS);
|
||||||
if (classList.includes(DEFAULT_CLASS)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
classList.push(DEFAULT_CLASS);
|
|
||||||
}
|
}
|
||||||
|
@ -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="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"
|
<div class="plot-legend-item"
|
||||||
ng-class="{
|
ng-class="{
|
||||||
'is-missing': series.domainObject.status === 'missing'
|
'is-status--missing': series.domainObject.status === 'missing'
|
||||||
}"
|
}"
|
||||||
ng-repeat="series in series track by $index"
|
ng-repeat="series in series track by $index"
|
||||||
>
|
>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
<span class="plot-series-color-swatch"
|
<span class="plot-series-color-swatch"
|
||||||
ng-style="{ 'background-color': series.get('color').asHexString() }">
|
ng-style="{ 'background-color': series.get('color').asHexString() }">
|
||||||
</span>
|
</span>
|
||||||
<span class="is-missing__indicator" title="This item is missing"></span>
|
<span class="is-status__indicator" title="This item is missing or suspect"></span>
|
||||||
<span class="plot-series-name">{{ series.nameWithUnit() }}</span>
|
<span class="plot-series-name">{{ series.nameWithUnit() }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="plot-series-value hover-value-enabled value-to-display-{{ legend.get('valueToShowWhenCollapsed') }} {{ series.closest.mctLimitState.cssClass }}"
|
<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"
|
<tr ng-repeat="series in series"
|
||||||
class="plot-legend-item"
|
class="plot-legend-item"
|
||||||
ng-class="{
|
ng-class="{
|
||||||
'is-missing': series.domainObject.status === 'missing'
|
'is-status--missing': series.domainObject.status === 'missing'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<td class="plot-series-swatch-and-name">
|
<td class="plot-series-swatch-and-name">
|
||||||
<span class="plot-series-color-swatch"
|
<span class="plot-series-color-swatch"
|
||||||
ng-style="{ 'background-color': series.get('color').asHexString() }">
|
ng-style="{ 'background-color': series.get('color').asHexString() }">
|
||||||
</span>
|
</span>
|
||||||
<span class="is-missing__indicator" title="This item is missing"></span>
|
<span class="is-status__indicator" title="This item is missing or suspect"></span>
|
||||||
<span class="plot-series-name">{{ series.get('name') }}</span>
|
<span class="plot-series-name">{{ series.get('name') }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
@ -58,7 +58,8 @@ define([
|
|||||||
'./newFolderAction/plugin',
|
'./newFolderAction/plugin',
|
||||||
'./persistence/couch/plugin',
|
'./persistence/couch/plugin',
|
||||||
'./defaultRootName/plugin',
|
'./defaultRootName/plugin',
|
||||||
'./timeline/plugin'
|
'./timeline/plugin',
|
||||||
|
'./viewDatumAction/plugin'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
@ -97,7 +98,8 @@ define([
|
|||||||
NewFolderAction,
|
NewFolderAction,
|
||||||
CouchDBPlugin,
|
CouchDBPlugin,
|
||||||
DefaultRootName,
|
DefaultRootName,
|
||||||
Timeline
|
Timeline,
|
||||||
|
ViewDatumAction
|
||||||
) {
|
) {
|
||||||
const bundleMap = {
|
const bundleMap = {
|
||||||
LocalStorage: 'platform/persistence/local',
|
LocalStorage: 'platform/persistence/local',
|
||||||
@ -191,6 +193,7 @@ define([
|
|||||||
plugins.ISOTimeFormat = ISOTimeFormat.default;
|
plugins.ISOTimeFormat = ISOTimeFormat.default;
|
||||||
plugins.DefaultRootName = DefaultRootName.default;
|
plugins.DefaultRootName = DefaultRootName.default;
|
||||||
plugins.Timeline = Timeline.default;
|
plugins.Timeline = Timeline.default;
|
||||||
|
plugins.ViewDatumAction = ViewDatumAction.default;
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,8 @@ export default class RemoveAction {
|
|||||||
this.key = 'remove';
|
this.key = 'remove';
|
||||||
this.description = 'Remove this object from its containing object.';
|
this.description = 'Remove this object from its containing object.';
|
||||||
this.cssClass = "icon-trash";
|
this.cssClass = "icon-trash";
|
||||||
|
this.group = "action";
|
||||||
|
this.priority = 1;
|
||||||
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
}
|
}
|
||||||
@ -103,6 +105,16 @@ export default class RemoveAction {
|
|||||||
let parentType = parent && this.openmct.types.get(parent.type);
|
let parentType = parent && this.openmct.types.get(parent.type);
|
||||||
let child = objectPath[0];
|
let child = objectPath[0];
|
||||||
let locked = child.locked ? child.locked : parent && parent.locked;
|
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) {
|
if (locked) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -23,6 +23,6 @@ import RemoveAction from "./RemoveAction";
|
|||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
openmct.contextMenu.registerAction(new RemoveAction(openmct));
|
openmct.actions.register(new RemoveAction(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -29,13 +29,13 @@
|
|||||||
@click="showTab(tab, index)"
|
@click="showTab(tab, index)"
|
||||||
>
|
>
|
||||||
<div class="c-tabs-view__tab__label c-object-label"
|
<div class="c-tabs-view__tab__label c-object-label"
|
||||||
:class="{'is-missing': tab.domainObject.status === 'missing'}"
|
:class="[tab.status ? `is-${tab.status}` : '']"
|
||||||
>
|
>
|
||||||
<div class="c-object-label__type-icon"
|
<div class="c-object-label__type-icon"
|
||||||
:class="tab.type.definition.cssClass"
|
:class="tab.type.definition.cssClass"
|
||||||
>
|
>
|
||||||
<span class="is-missing__indicator"
|
<span class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<span class="c-button__label c-object-label__name">{{ tab.domainObject.name }}</span>
|
<span class="c-button__label c-object-label__name">{{ tab.domainObject.name }}</span>
|
||||||
@ -192,8 +192,10 @@ export default {
|
|||||||
},
|
},
|
||||||
addItem(domainObject) {
|
addItem(domainObject) {
|
||||||
let type = this.openmct.types.get(domainObject.type) || unknownObjectType;
|
let type = this.openmct.types.get(domainObject.type) || unknownObjectType;
|
||||||
|
let status = this.openmct.status.get(domainObject.identifier);
|
||||||
let tabItem = {
|
let tabItem = {
|
||||||
domainObject,
|
domainObject,
|
||||||
|
status,
|
||||||
type: type,
|
type: type,
|
||||||
key: this.openmct.objects.makeKeyString(domainObject.identifier)
|
key: this.openmct.objects.makeKeyString(domainObject.identifier)
|
||||||
};
|
};
|
||||||
|
@ -160,7 +160,6 @@ define([
|
|||||||
processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) {
|
processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) {
|
||||||
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
||||||
this.boundedRows.add(telemetryRows);
|
this.boundedRows.add(telemetryRows);
|
||||||
this.emit('historical-rows-processed');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,6 +26,7 @@ define([], function () {
|
|||||||
this.columns = columns;
|
this.columns = columns;
|
||||||
|
|
||||||
this.datum = createNormalizedDatum(datum, columns);
|
this.datum = createNormalizedDatum(datum, columns);
|
||||||
|
this.fullDatum = datum;
|
||||||
this.limitEvaluator = limitEvaluator;
|
this.limitEvaluator = limitEvaluator;
|
||||||
this.objectKeyString = objectKeyString;
|
this.objectKeyString = objectKeyString;
|
||||||
}
|
}
|
||||||
@ -87,7 +88,7 @@ define([], function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getContextMenuActions() {
|
getContextMenuActions() {
|
||||||
return [];
|
return ['viewDatumAction'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,15 +54,13 @@ define([
|
|||||||
view(domainObject, objectPath) {
|
view(domainObject, objectPath) {
|
||||||
let table = new TelemetryTable(domainObject, openmct);
|
let table = new TelemetryTable(domainObject, openmct);
|
||||||
let component;
|
let component;
|
||||||
|
|
||||||
let markingProp = {
|
let markingProp = {
|
||||||
enable: true,
|
enable: true,
|
||||||
useAlternateControlBar: false,
|
useAlternateControlBar: false,
|
||||||
rowName: '',
|
rowName: '',
|
||||||
rowNamePlural: ''
|
rowNamePlural: ''
|
||||||
};
|
};
|
||||||
|
const view = {
|
||||||
return {
|
|
||||||
show: function (element, editMode) {
|
show: function (element, editMode) {
|
||||||
component = new Vue({
|
component = new Vue({
|
||||||
el: element,
|
el: element,
|
||||||
@ -72,7 +70,8 @@ define([
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isEditing: editMode,
|
isEditing: editMode,
|
||||||
markingProp
|
markingProp,
|
||||||
|
view
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
@ -80,7 +79,7 @@ define([
|
|||||||
table,
|
table,
|
||||||
objectPath
|
objectPath
|
||||||
},
|
},
|
||||||
template: '<table-component :isEditing="isEditing" :marking="markingProp"/>'
|
template: '<table-component ref="tableComponent" :isEditing="isEditing" :marking="markingProp" :view="view"/>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onEditModeChange(editMode) {
|
onEditModeChange(editMode) {
|
||||||
@ -89,11 +88,22 @@ define([
|
|||||||
onClearData() {
|
onClearData() {
|
||||||
table.clearData();
|
table.clearData();
|
||||||
},
|
},
|
||||||
|
getViewContext() {
|
||||||
|
if (component) {
|
||||||
|
return component.$refs.tableComponent.getViewContext();
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: 'telemetry-table'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
destroy: function (element) {
|
destroy: function (element) {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
component = undefined;
|
component = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return view;
|
||||||
},
|
},
|
||||||
priority() {
|
priority() {
|
||||||
return 1;
|
return 1;
|
||||||
|
123
src/plugins/telemetryTable/ViewActions.js
Normal file
123
src/plugins/telemetryTable/ViewActions.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
let exportCSV = {
|
||||||
|
name: 'Export Table Data',
|
||||||
|
key: 'export-csv-all',
|
||||||
|
description: "Export this view's data",
|
||||||
|
cssClass: 'icon-download labeled',
|
||||||
|
invoke: (objectPath, viewProvider) => {
|
||||||
|
viewProvider.getViewContext().exportAllDataAsCSV();
|
||||||
|
},
|
||||||
|
group: 'view'
|
||||||
|
};
|
||||||
|
let exportMarkedRows = {
|
||||||
|
name: 'Export Marked Rows',
|
||||||
|
key: 'export-csv-marked',
|
||||||
|
description: "Export marked rows as CSV",
|
||||||
|
cssClass: 'icon-download labeled',
|
||||||
|
invoke: (objectPath, viewProvider) => {
|
||||||
|
viewProvider.getViewContext().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;
|
@ -102,7 +102,17 @@ export default {
|
|||||||
selectable[columnKeys] = this.row.columns[columnKeys].selectable;
|
selectable[columnKeys] = this.row.columns[columnKeys].selectable;
|
||||||
|
|
||||||
return selectable;
|
return selectable;
|
||||||
}, {})
|
}, {}),
|
||||||
|
actionsViewContext: {
|
||||||
|
getViewContext: () => {
|
||||||
|
return {
|
||||||
|
viewHistoricalData: true,
|
||||||
|
viewDatumAction: true,
|
||||||
|
getDatum: this.getDatum,
|
||||||
|
skipCache: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -170,14 +180,24 @@ export default {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getDatum() {
|
||||||
|
return this.row.fullDatum;
|
||||||
|
},
|
||||||
showContextMenu: function (event) {
|
showContextMenu: function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.markRow(event);
|
||||||
|
|
||||||
this.row.getContextualDomainObject(this.openmct, this.row.objectKeyString).then(domainObject => {
|
this.row.getContextualDomainObject(this.openmct, this.row.objectKeyString).then(domainObject => {
|
||||||
let contextualObjectPath = this.objectPath.slice();
|
let contextualObjectPath = this.objectPath.slice();
|
||||||
contextualObjectPath.unshift(domainObject);
|
contextualObjectPath.unshift(domainObject);
|
||||||
|
|
||||||
this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions());
|
let allActions = this.openmct.actions.get(contextualObjectPath, this.actionsViewContext);
|
||||||
|
let applicableActions = this.row.getContextMenuActions().map(key => allActions[key]);
|
||||||
|
|
||||||
|
if (applicableActions.length) {
|
||||||
|
this.openmct.menus.showMenu(event.x, event.y, applicableActions);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
<div class="c-table-wrapper"
|
<div class="c-table-wrapper"
|
||||||
:class="{ 'is-paused': paused }"
|
:class="{ 'is-paused': paused }"
|
||||||
>
|
>
|
||||||
<!-- main contolbar start-->
|
<div v-if="enableLegacyToolbar"
|
||||||
<div v-if="!marking.useAlternateControlBar"
|
|
||||||
class="c-table-control-bar c-control-bar"
|
class="c-table-control-bar c-control-bar"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@ -94,7 +93,6 @@
|
|||||||
|
|
||||||
<slot name="buttons"></slot>
|
<slot name="buttons"></slot>
|
||||||
</div>
|
</div>
|
||||||
<!-- main controlbar end -->
|
|
||||||
|
|
||||||
<!-- alternate controlbar start -->
|
<!-- alternate controlbar start -->
|
||||||
<div v-if="marking.useAlternateControlBar"
|
<div v-if="marking.useAlternateControlBar"
|
||||||
@ -113,11 +111,11 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
:class="{'hide-nice': !markedRows.length}"
|
:class="{'hide-nice': !markedRows.length}"
|
||||||
class="c-button icon-x labeled"
|
class="c-icon-button icon-x labeled"
|
||||||
title="Deselect All"
|
title="Deselect All"
|
||||||
@click="unmarkAllRows()"
|
@click="unmarkAllRows()"
|
||||||
>
|
>
|
||||||
<span class="c-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span>
|
<span class="c-icon-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<slot name="buttons"></slot>
|
<slot name="buttons"></slot>
|
||||||
@ -295,12 +293,12 @@ export default {
|
|||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
allowFiltering: {
|
allowFiltering: {
|
||||||
'type': Boolean,
|
type: Boolean,
|
||||||
'default': true
|
default: true
|
||||||
},
|
},
|
||||||
allowSorting: {
|
allowSorting: {
|
||||||
'type': Boolean,
|
type: Boolean,
|
||||||
'default': true
|
default: true
|
||||||
},
|
},
|
||||||
marking: {
|
marking: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -313,6 +311,17 @@ export default {
|
|||||||
rowNamePlural: ""
|
rowNamePlural: ""
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
enableLegacyToolbar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -388,6 +397,40 @@ export default {
|
|||||||
markedRows: {
|
markedRows: {
|
||||||
handler(newVal, oldVal) {
|
handler(newVal, oldVal) {
|
||||||
this.$emit('marked-rows-updated', 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']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -400,6 +443,11 @@ export default {
|
|||||||
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
|
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
|
||||||
this.scroll = _.throttle(this.scroll, 100);
|
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-added', this.addObject);
|
||||||
this.table.on('object-removed', this.removeObject);
|
this.table.on('object-removed', this.removeObject);
|
||||||
this.table.on('outstanding-requests', this.outstandingRequests);
|
this.table.on('outstanding-requests', this.outstandingRequests);
|
||||||
@ -840,7 +888,7 @@ export default {
|
|||||||
|
|
||||||
for (let i = firstRowIndex; i <= lastRowIndex; i++) {
|
for (let i = firstRowIndex; i <= lastRowIndex; i++) {
|
||||||
let row = allRows[i];
|
let row = allRows[i];
|
||||||
row.marked = true;
|
this.$set(row, 'marked', true);
|
||||||
|
|
||||||
if (row !== baseRow) {
|
if (row !== baseRow) {
|
||||||
this.markedRows.push(row);
|
this.markedRows.push(row);
|
||||||
@ -901,6 +949,40 @@ export default {
|
|||||||
this.isAutosizeEnabled = true;
|
this.isAutosizeEnabled = true;
|
||||||
|
|
||||||
this.$nextTick().then(this.calculateColumnWidths);
|
this.$nextTick().then(this.calculateColumnWidths);
|
||||||
|
},
|
||||||
|
getViewContext() {
|
||||||
|
return {
|
||||||
|
type: 'telemetry-table',
|
||||||
|
exportAllDataAsCSV: this.exportAllDataAsCSV,
|
||||||
|
exportMarkedRows: this.exportMarkedRows,
|
||||||
|
unmarkAllRows: this.unmarkAllRows,
|
||||||
|
togglePauseByButton: this.togglePauseByButton,
|
||||||
|
expandColumns: this.recalculateColumnWidths,
|
||||||
|
autosizeColumns: this.autosizeColumns
|
||||||
|
};
|
||||||
|
},
|
||||||
|
initializeViewActions() {
|
||||||
|
if (this.markedRows.length > 0) {
|
||||||
|
this.viewActionsCollection.enable(['export-csv-marked', 'unmark-all-rows']);
|
||||||
|
} else if (this.markedRows.length === 0) {
|
||||||
|
this.viewActionsCollection.disable(['export-csv-marked', 'unmark-all-rows']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.paused) {
|
||||||
|
this.viewActionsCollection.hide(['pause-data']);
|
||||||
|
this.viewActionsCollection.show(['play-data']);
|
||||||
|
} else {
|
||||||
|
this.viewActionsCollection.hide(['play-data']);
|
||||||
|
this.viewActionsCollection.show(['pause-data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isAutosizeEnabled) {
|
||||||
|
this.viewActionsCollection.show(['expand-columns']);
|
||||||
|
this.viewActionsCollection.hide(['autosize-columns']);
|
||||||
|
} else {
|
||||||
|
this.viewActionsCollection.show(['autosize-columns']);
|
||||||
|
this.viewActionsCollection.hide(['expand-columns']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -23,11 +23,13 @@
|
|||||||
define([
|
define([
|
||||||
'./TelemetryTableViewProvider',
|
'./TelemetryTableViewProvider',
|
||||||
'./TableConfigurationViewProvider',
|
'./TableConfigurationViewProvider',
|
||||||
'./TelemetryTableType'
|
'./TelemetryTableType',
|
||||||
|
'./ViewActions'
|
||||||
], function (
|
], function (
|
||||||
TelemetryTableViewProvider,
|
TelemetryTableViewProvider,
|
||||||
TableConfigurationViewProvider,
|
TableConfigurationViewProvider,
|
||||||
TelemetryTableType
|
TelemetryTableType,
|
||||||
|
TelemetryTableViewActions
|
||||||
) {
|
) {
|
||||||
return function plugin() {
|
return function plugin() {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
@ -41,6 +43,10 @@ define([
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TelemetryTableViewActions.default.forEach(action => {
|
||||||
|
openmct.actions.register(action);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -168,6 +168,8 @@ describe("the plugin", () => {
|
|||||||
return telemetryPromise;
|
return telemetryPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
openmct.router.path = [testTelemetryObject];
|
||||||
|
|
||||||
applicableViews = openmct.objectViews.get(testTelemetryObject);
|
applicableViews = openmct.objectViews.get(testTelemetryObject);
|
||||||
tableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'table');
|
tableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'table');
|
||||||
tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);
|
tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);
|
||||||
|
69
src/plugins/viewDatumAction/ViewDatumAction.js
Normal file
69
src/plugins/viewDatumAction/ViewDatumAction.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import MetadataListView from './components/MetadataList.vue';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default class ViewDatumAction {
|
||||||
|
constructor(openmct) {
|
||||||
|
this.name = 'View Full Datum';
|
||||||
|
this.key = 'viewDatumAction';
|
||||||
|
this.description = 'View full value of datum received';
|
||||||
|
this.cssClass = 'icon-object';
|
||||||
|
|
||||||
|
this._openmct = openmct;
|
||||||
|
}
|
||||||
|
invoke(objectPath, view) {
|
||||||
|
let viewContext = view.getViewContext && view.getViewContext();
|
||||||
|
let attributes = viewContext.getDatum && viewContext.getDatum();
|
||||||
|
let component = new Vue ({
|
||||||
|
provide: {
|
||||||
|
name: this.name,
|
||||||
|
attributes
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MetadataListView
|
||||||
|
},
|
||||||
|
template: '<MetadataListView />'
|
||||||
|
});
|
||||||
|
|
||||||
|
this._openmct.overlays.overlay({
|
||||||
|
element: component.$mount().$el,
|
||||||
|
size: 'large',
|
||||||
|
dismissable: true,
|
||||||
|
onDestroy: () => {
|
||||||
|
component.$destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
appliesTo(objectPath, view = {}) {
|
||||||
|
let viewContext = view.getViewContext && view.getViewContext() || {};
|
||||||
|
let datum = viewContext.getDatum;
|
||||||
|
let enabled = viewContext.viewDatumAction;
|
||||||
|
|
||||||
|
if (enabled && datum) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
24
src/plugins/viewDatumAction/components/MetadataList.vue
Normal file
24
src/plugins/viewDatumAction/components/MetadataList.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-attributes-view">
|
||||||
|
<div class="c-overlay__top-bar">
|
||||||
|
<div class="c-overlay__dialog-title">{{ name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-overlay__contents-main l-preview-window__object-view">
|
||||||
|
<ul class="c-attributes-view__content">
|
||||||
|
<li
|
||||||
|
v-for="attribute in Object.keys(attributes)"
|
||||||
|
:key="attribute"
|
||||||
|
>
|
||||||
|
<span class="c-attributes-view__grid-item__label">{{ attribute }}</span>
|
||||||
|
<span class="c-attributes-view__grid-item__value">{{ attributes[attribute] }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['name', 'attributes']
|
||||||
|
};
|
||||||
|
</script>
|
30
src/plugins/viewDatumAction/components/metadata-list.scss
Normal file
30
src/plugins/viewDatumAction/components/metadata-list.scss
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
.c-attributes-view {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
$p: 3px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content 1fr;
|
||||||
|
grid-row-gap: $p;
|
||||||
|
|
||||||
|
li { display: contents; }
|
||||||
|
|
||||||
|
[class*="__grid-item"] {
|
||||||
|
border-bottom: 1px solid rgba(#999, 0.2);
|
||||||
|
padding: 0 5px $p 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="__label"] {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
29
src/plugins/viewDatumAction/plugin.js
Normal file
29
src/plugins/viewDatumAction/plugin.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import ViewDatumAction from './ViewDatumAction.js';
|
||||||
|
|
||||||
|
export default function plugin() {
|
||||||
|
return function install(openmct) {
|
||||||
|
openmct.actions.register(new ViewDatumAction(openmct));
|
||||||
|
};
|
||||||
|
}
|
95
src/plugins/viewDatumAction/pluginSpec.js
Normal file
95
src/plugins/viewDatumAction/pluginSpec.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import {
|
||||||
|
createOpenMct,
|
||||||
|
resetApplicationState
|
||||||
|
} from 'utils/testing';
|
||||||
|
|
||||||
|
describe("the plugin", () => {
|
||||||
|
let openmct;
|
||||||
|
let viewDatumAction;
|
||||||
|
let mockObjectPath;
|
||||||
|
let mockView;
|
||||||
|
let mockDatum;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
openmct = createOpenMct();
|
||||||
|
|
||||||
|
openmct.on('start', done);
|
||||||
|
openmct.startHeadless();
|
||||||
|
|
||||||
|
viewDatumAction = openmct.actions._allActions.viewDatumAction;
|
||||||
|
|
||||||
|
mockObjectPath = [{
|
||||||
|
name: 'mock object',
|
||||||
|
type: 'telemetry-table',
|
||||||
|
identifier: {
|
||||||
|
key: 'mock-object',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
mockDatum = {
|
||||||
|
time: 123456789,
|
||||||
|
sin: 0.4455512,
|
||||||
|
cos: 0.4455512
|
||||||
|
};
|
||||||
|
|
||||||
|
mockView = {
|
||||||
|
getViewContext: () => {
|
||||||
|
return {
|
||||||
|
viewDatumAction: true,
|
||||||
|
getDatum: () => {
|
||||||
|
return mockDatum;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('installs the view datum action', () => {
|
||||||
|
expect(viewDatumAction).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when invoked', () => {
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
openmct.overlays.overlay = function (options) {};
|
||||||
|
|
||||||
|
spyOn(openmct.overlays, 'overlay');
|
||||||
|
|
||||||
|
viewDatumAction.invoke(mockObjectPath, mockView);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses an overlay to show user datum values', () => {
|
||||||
|
expect(openmct.overlays.overlay).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -218,7 +218,7 @@ $colorBtnActiveFg: $colorOkFg;
|
|||||||
$colorBtnSelectedBg: $colorSelectedBg;
|
$colorBtnSelectedBg: $colorSelectedBg;
|
||||||
$colorBtnSelectedFg: $colorSelectedFg;
|
$colorBtnSelectedFg: $colorSelectedFg;
|
||||||
$colorClickIconButton: $colorKey;
|
$colorClickIconButton: $colorKey;
|
||||||
$colorClickIconButtonBgHov: rgba($colorKey, 0.6);
|
$colorClickIconButtonBgHov: rgba($colorKey, 0.3);
|
||||||
$colorClickIconButtonFgHov: $colorKeyHov;
|
$colorClickIconButtonFgHov: $colorKeyHov;
|
||||||
$colorDropHint: $colorKey;
|
$colorDropHint: $colorKey;
|
||||||
$colorDropHintBg: pushBack($colorDropHint, 10%);
|
$colorDropHintBg: pushBack($colorDropHint, 10%);
|
||||||
@ -378,6 +378,11 @@ $colorItemTreeVC: $colorDisclosureCtrl;
|
|||||||
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
|
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
|
||||||
$shdwItemTreeIcon: none;
|
$shdwItemTreeIcon: none;
|
||||||
|
|
||||||
|
// Layout frame controls
|
||||||
|
$frameControlsColorFg: white;
|
||||||
|
$frameControlsColorBg: $colorKey;
|
||||||
|
$frameControlsShdw: $shdwMenu;
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
||||||
|
|
||||||
|
@ -382,6 +382,11 @@ $colorItemTreeVC: $colorDisclosureCtrl;
|
|||||||
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
|
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
|
||||||
$shdwItemTreeIcon: none;
|
$shdwItemTreeIcon: none;
|
||||||
|
|
||||||
|
// Layout frame controls
|
||||||
|
$frameControlsColorFg: white;
|
||||||
|
$frameControlsColorBg: $colorKey;
|
||||||
|
$frameControlsShdw: $shdwMenu;
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
||||||
|
|
||||||
|
@ -80,13 +80,13 @@ $uiColor: #289fec; // Resize bars, splitter bars, etc.
|
|||||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||||
$colorA: $colorBodyFg;
|
$colorA: $colorBodyFg;
|
||||||
$colorAHov: $colorKey;
|
$colorAHov: $colorKey;
|
||||||
$filterHov: brightness(0.8) contrast(2); // Tree, location items
|
$filterHov: hue-rotate(-10deg) brightness(0.8) contrast(2); // Tree, location items
|
||||||
$colorSelectedBg: rgba($colorKey, 0.2);
|
$colorSelectedBg: pushBack($colorKey, 40%);
|
||||||
$colorSelectedFg: pullForward($colorBodyFg, 10%);
|
$colorSelectedFg: pullForward($colorBodyFg, 10%);
|
||||||
|
|
||||||
// Object labels
|
// Object labels
|
||||||
$objectLabelTypeIconOpacity: 0.5;
|
$objectLabelTypeIconOpacity: 0.5;
|
||||||
$objectLabelNameFilter: brightness(0.5);
|
$objectLabelNameFilter: brightness(0.9);
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
$shellMainPad: 4px 0;
|
$shellMainPad: 4px 0;
|
||||||
@ -378,6 +378,11 @@ $colorItemTreeVC: $colorDisclosureCtrl;
|
|||||||
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
|
$colorItemTreeVCHover: $colorDisclosureCtrlHov;
|
||||||
$shdwItemTreeIcon: none;
|
$shdwItemTreeIcon: none;
|
||||||
|
|
||||||
|
// Layout frame controls
|
||||||
|
$frameControlsColorFg: $colorClickIconButton;
|
||||||
|
$frameControlsColorBg: $colorMenuBg;
|
||||||
|
$frameControlsShdw: $shdwMenu;
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
||||||
|
|
||||||
|
@ -520,7 +520,7 @@ select {
|
|||||||
|
|
||||||
&__section-hint {
|
&__section-hint {
|
||||||
$m: $interiorMargin;
|
$m: $interiorMargin;
|
||||||
margin: $m 0;
|
margin: 0 0 $m 0;
|
||||||
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
|
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
|
||||||
|
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
@ -532,7 +532,7 @@ select {
|
|||||||
$m: $interiorMargin;
|
$m: $interiorMargin;
|
||||||
border-top: 1px solid $colorInteriorBorder;
|
border-top: 1px solid $colorInteriorBorder;
|
||||||
margin: $m 0;
|
margin: $m 0;
|
||||||
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
|
padding: 0 nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,12 +59,12 @@ mct-plot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*********************** MISSING ITEM INDICATORS */
|
/*********************** MISSING ITEM INDICATORS */
|
||||||
.is-missing__indicator {
|
.is-status__indicator {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.is-missing {
|
.is-status--missing {
|
||||||
@include isMissing();
|
@include isMissing();
|
||||||
.is-missing__indicator {
|
.is-status__indicator {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,31 +129,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin isMissing($absPos: false) {
|
@mixin isStatus($absPos: false) {
|
||||||
|
// Supports CSS classing as follows:
|
||||||
|
// is-status--missing, is-status--suspect, etc.
|
||||||
// Common styles to be applied to tree items, object labels, grid and list item views
|
// 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-missing__indicator {
|
.is-status__indicator {
|
||||||
display: none ;
|
display: none ;
|
||||||
text-shadow: $colorBodyBg 0 0 2px;
|
text-shadow: $colorBodyBg 0 0 2px;
|
||||||
color: $colorAlert;
|
|
||||||
font-family: symbolsfont;
|
font-family: symbolsfont;
|
||||||
|
|
||||||
&:before {
|
&[class^='is-status'] .is-status__indicator,
|
||||||
content: $glyph-icon-alert-triangle;
|
[class^='is-status'] .is-status__indicator {
|
||||||
|
display: block !important;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@if $absPos {
|
@if $absPos {
|
||||||
.is-missing__indicator {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.is-missing .is-missing__indicator,
|
@mixin isMissing($absPos: false) {
|
||||||
.is-missing .is-missing__indicator { display: block !important; }
|
@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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
|
@mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
|
||||||
@ -492,8 +505,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mixin cClickIconButtonLayout() {
|
@mixin cClickIconButtonLayout() {
|
||||||
$pLR: 4px;
|
$pLR: 5px;
|
||||||
$pTB: 4px;
|
$pTB: 5px;
|
||||||
padding: $pTB $pLR;
|
padding: $pTB $pLR;
|
||||||
|
|
||||||
&:before,
|
&:before,
|
||||||
@ -512,6 +525,7 @@
|
|||||||
@include cControl();
|
@include cControl();
|
||||||
@include cClickIconButtonLayout();
|
@include cClickIconButtonLayout();
|
||||||
background: none;
|
background: none;
|
||||||
|
color: $colorClickIconButton;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: $transOut;
|
transition: $transOut;
|
||||||
@ -520,7 +534,8 @@
|
|||||||
@include hover() {
|
@include hover() {
|
||||||
transition: $transIn;
|
transition: $transIn;
|
||||||
background: $colorClickIconButtonBgHov;
|
background: $colorClickIconButtonBgHov;
|
||||||
color: $colorClickIconButtonFgHov;
|
//color: $colorClickIconButtonFgHov;
|
||||||
|
filter: $filterHov;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[class*="--major"] {
|
&[class*="--major"] {
|
||||||
|
@ -97,6 +97,7 @@ div.c-table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.c-table-control-bar {
|
.c-table-control-bar {
|
||||||
|
.c-icon-button,
|
||||||
.c-click-icon,
|
.c-click-icon,
|
||||||
.c-button {
|
.c-button {
|
||||||
&__label {
|
&__label {
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
@import "../plugins/timeConductor/conductor-mode-icon.scss";
|
@import "../plugins/timeConductor/conductor-mode-icon.scss";
|
||||||
@import "../plugins/timeConductor/date-picker.scss";
|
@import "../plugins/timeConductor/date-picker.scss";
|
||||||
@import "../plugins/timeline/timeline-axis.scss";
|
@import "../plugins/timeline/timeline-axis.scss";
|
||||||
|
@import "../plugins/viewDatumAction/components/metadata-list.scss";
|
||||||
@import "../ui/components/object-frame.scss";
|
@import "../ui/components/object-frame.scss";
|
||||||
@import "../ui/components/object-label.scss";
|
@import "../ui/components/object-label.scss";
|
||||||
@import "../ui/components/progress-bar.scss";
|
@import "../ui/components/progress-bar.scss";
|
||||||
|
@ -21,44 +21,72 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="c-so-view has-local-controls"
|
class="c-so-view"
|
||||||
:class="{
|
:class="[
|
||||||
'c-so-view--no-frame': !hasFrame,
|
'c-so-view--' + domainObject.type,
|
||||||
'has-complex-content': complexContent,
|
{
|
||||||
'is-missing': domainObject.status === 'missing'
|
'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"
|
<div class="c-object-label"
|
||||||
:class="{
|
:class="[ statusClass ]"
|
||||||
classList,
|
|
||||||
'is-missing': domainObject.status === 'missing'
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<div class="c-object-label__type-icon"
|
<div class="c-object-label__type-icon"
|
||||||
:class="cssClass"
|
:class="cssClass"
|
||||||
>
|
>
|
||||||
<span class="is-missing__indicator"
|
<span class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-object-label__name">
|
<div class="c-object-label__name">
|
||||||
{{ domainObject && domainObject.name }}
|
{{ domainObject && domainObject.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<context-menu-drop-down
|
|
||||||
:object-path="objectPath"
|
<div
|
||||||
/>
|
class="c-so-view__frame-controls"
|
||||||
|
:class="{
|
||||||
|
'c-so-view__frame-controls--no-frame': !hasFrame,
|
||||||
|
'has-complex-content': complexContent
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="c-so-view__frame-controls__btns">
|
||||||
|
<button
|
||||||
|
v-for="(item, index) in statusBarItems"
|
||||||
|
:key="index"
|
||||||
|
class="c-icon-button"
|
||||||
|
:class="item.cssClass"
|
||||||
|
:title="item.name"
|
||||||
|
@click="item.callBack"
|
||||||
|
>
|
||||||
|
<span class="c-icon-button__label">{{ item.name }}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="c-icon-button icon-items-expand"
|
||||||
|
title="View Large"
|
||||||
|
@click="expand"
|
||||||
|
>
|
||||||
|
<span class="c-icon-button__label">View Large</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="c-icon-button icon-3-dots c-so-view__frame-controls__more"
|
||||||
|
title="View menu items"
|
||||||
|
@click.prevent.stop="showMenuItems($event)"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-so-view__local-controls c-so-view__view-large h-local-controls c-local-controls--show-on-hover">
|
|
||||||
<button
|
<div class="is-status__indicator"
|
||||||
class="c-button icon-expand"
|
title="This item is missing or suspect"
|
||||||
title="View Large"
|
|
||||||
@click="expand"
|
|
||||||
></button>
|
|
||||||
</div>
|
|
||||||
<div class="is-missing__indicator"
|
|
||||||
title="This item is missing"
|
|
||||||
></div>
|
></div>
|
||||||
<object-view
|
<object-view
|
||||||
ref="objectView"
|
ref="objectView"
|
||||||
@ -66,13 +94,13 @@
|
|||||||
:object="domainObject"
|
:object="domainObject"
|
||||||
:show-edit-view="showEditView"
|
:show-edit-view="showEditView"
|
||||||
:object-path="objectPath"
|
:object-path="objectPath"
|
||||||
|
@change-provider="setViewProvider"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ObjectView from './ObjectView.vue';
|
import ObjectView from './ObjectView.vue';
|
||||||
import ContextMenuDropDown from './contextMenuDropDown.vue';
|
|
||||||
import PreviewHeader from '@/ui/preview/preview-header.vue';
|
import PreviewHeader from '@/ui/preview/preview-header.vue';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
@ -87,8 +115,7 @@ const SIMPLE_CONTENT_TYPES = [
|
|||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
components: {
|
components: {
|
||||||
ObjectView,
|
ObjectView
|
||||||
ContextMenuDropDown
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
@ -107,22 +134,37 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
let objectType = this.openmct.types.get(this.domainObject.type);
|
let objectType = this.openmct.types.get(this.domainObject.type);
|
||||||
|
|
||||||
let cssClass = objectType && objectType.definition ? objectType.definition.cssClass : 'icon-object-unknown';
|
let cssClass = objectType && objectType.definition ? objectType.definition.cssClass : 'icon-object-unknown';
|
||||||
|
|
||||||
let complexContent = !SIMPLE_CONTENT_TYPES.includes(this.domainObject.type);
|
let complexContent = !SIMPLE_CONTENT_TYPES.includes(this.domainObject.type);
|
||||||
|
|
||||||
|
let viewProvider = {};
|
||||||
|
|
||||||
|
let statusBarItems = {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cssClass,
|
cssClass,
|
||||||
complexContent
|
complexContent,
|
||||||
|
viewProvider,
|
||||||
|
statusBarItems,
|
||||||
|
status: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
classList() {
|
statusClass() {
|
||||||
const classList = this.domainObject.classList;
|
return (this.status) ? `is-status--${this.status}` : '';
|
||||||
if (!classList || !classList.length) {
|
}
|
||||||
return '';
|
},
|
||||||
}
|
mounted() {
|
||||||
|
this.status = this.openmct.status.get(this.domainObject.identifier);
|
||||||
|
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.removeStatusListener();
|
||||||
|
|
||||||
return classList.join(' ');
|
if (this.actionCollection) {
|
||||||
|
this.unlistenToActionCollection();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -172,6 +214,48 @@ export default {
|
|||||||
},
|
},
|
||||||
getSelectionContext() {
|
getSelectionContext() {
|
||||||
return this.$refs.objectView.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
class="c-tree__item__label c-object-label"
|
class="c-tree__item__label c-object-label"
|
||||||
:class="{
|
:class="[statusClass]"
|
||||||
classList,
|
|
||||||
'is-missing': observedObject.status === 'missing'
|
|
||||||
}"
|
|
||||||
draggable="true"
|
draggable="true"
|
||||||
:href="objectLink"
|
:href="objectLink"
|
||||||
@dragstart="dragStart"
|
@dragstart="dragStart"
|
||||||
@ -14,8 +11,8 @@
|
|||||||
class="c-tree__item__type-icon c-object-label__type-icon"
|
class="c-tree__item__type-icon c-object-label__type-icon"
|
||||||
:class="typeClass"
|
:class="typeClass"
|
||||||
>
|
>
|
||||||
<span class="is-missing__indicator"
|
<span class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-tree__item__name c-object-label__name">
|
<div class="c-tree__item__name c-object-label__name">
|
||||||
@ -49,18 +46,11 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
observedObject: this.domainObject
|
observedObject: this.domainObject,
|
||||||
|
status: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
classList() {
|
|
||||||
const classList = this.observedObject.classList;
|
|
||||||
if (!classList || !classList.length) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return classList.join(' ');
|
|
||||||
},
|
|
||||||
typeClass() {
|
typeClass() {
|
||||||
let type = this.openmct.types.get(this.observedObject.type);
|
let type = this.openmct.types.get(this.observedObject.type);
|
||||||
if (!type) {
|
if (!type) {
|
||||||
@ -68,6 +58,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return type.definition.cssClass;
|
return type.definition.cssClass;
|
||||||
|
},
|
||||||
|
statusClass() {
|
||||||
|
return (this.status) ? `is-status--${this.status}` : '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -78,8 +71,13 @@ export default {
|
|||||||
this.$once('hook:destroyed', removeListener);
|
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);
|
this.previewAction = new PreviewAction(this.openmct);
|
||||||
},
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.removeStatusListener();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
navigateOrPreview(event) {
|
navigateOrPreview(event) {
|
||||||
if (this.openmct.editor.isEditing()) {
|
if (this.openmct.editor.isEditing()) {
|
||||||
@ -110,6 +108,9 @@ export default {
|
|||||||
// (eg. notabook.)
|
// (eg. notabook.)
|
||||||
event.dataTransfer.setData("openmct/domain-object-path", serializedPath);
|
event.dataTransfer.setData("openmct/domain-object-path", serializedPath);
|
||||||
event.dataTransfer.setData(`openmct/domain-object/${keyString}`, this.domainObject);
|
event.dataTransfer.setData(`openmct/domain-object/${keyString}`, this.domainObject);
|
||||||
|
},
|
||||||
|
setStatus(status) {
|
||||||
|
this.status = status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -179,6 +179,7 @@ export default {
|
|||||||
this.$el, this.getSelectionContext(), true);
|
this.$el, this.getSelectionContext(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$emit('change-provider', this.currentView);
|
||||||
this.openmct.objectViews.on('clearData', this.clearData);
|
this.openmct.objectViews.on('clearData', this.clearData);
|
||||||
},
|
},
|
||||||
show(object, viewKey, immediatelySelect, currentObjectPath) {
|
show(object, viewKey, immediatelySelect, currentObjectPath) {
|
||||||
|
@ -2,20 +2,21 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
&.is-missing {
|
|
||||||
border: $borderMissing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*************************** HEADER */
|
/*************************** HEADER */
|
||||||
&__header {
|
&__header {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 1.05em;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: $interiorMarginSm;
|
margin-bottom: $interiorMarginSm;
|
||||||
padding: 1px 2px;
|
padding: 3px;
|
||||||
|
|
||||||
.c-object-label {
|
.c-object-label {
|
||||||
|
font-size: 1.05em;
|
||||||
|
&__type-icon {
|
||||||
|
opacity: $objectLabelTypeIconOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
filter: $objectLabelNameFilter;
|
filter: $objectLabelNameFilter;
|
||||||
}
|
}
|
||||||
@ -31,45 +32,95 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--no-frame {
|
/*************************** FRAME CONTROLS */
|
||||||
> .c-so-view__header {
|
&__frame-controls {
|
||||||
display: none;
|
display: flex;
|
||||||
|
|
||||||
|
&__btns,
|
||||||
|
&__more {
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-missing {
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HOVERS */
|
||||||
|
&:hover {
|
||||||
|
> .c-so-view__header {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-status--missing {
|
||||||
@include isMissing($absPos: true);
|
@include isMissing($absPos: true);
|
||||||
|
|
||||||
.is-missing__indicator {
|
.is-status__indicator {
|
||||||
top: $interiorMargin;
|
top: $interiorMargin;
|
||||||
left: $interiorMargin;
|
left: $interiorMargin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__local-controls {
|
/*************************** OBJECT VIEW */
|
||||||
// View Large button
|
|
||||||
box-shadow: $colorLocalControlOvrBg 0 0 0 2px;
|
|
||||||
position: absolute;
|
|
||||||
top: $interiorMargin; right: $interiorMargin;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-click-icon,
|
|
||||||
.c-button {
|
|
||||||
// Shrink buttons a bit when they appear in a frame
|
|
||||||
border-radius: $smallCr !important;
|
|
||||||
font-size: 0.9em;
|
|
||||||
padding: 3px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__view-large {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-complex-content {
|
|
||||||
> .c-so-view__view-large { display: block; }
|
|
||||||
}
|
|
||||||
|
|
||||||
&__object-view {
|
&__object-view {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 0; // Chrome 73 overflow bug fix
|
height: 0; // Chrome 73 overflow bug fix
|
||||||
@ -80,6 +131,23 @@
|
|||||||
@include abs();
|
@include abs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-click-icon,
|
||||||
|
.c-button,
|
||||||
|
.c-icon-button {
|
||||||
|
// Shrink buttons a bit when they appear in a frame
|
||||||
|
border-radius: $smallCr !important;
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-complex-content {
|
||||||
|
> .c-so-view__view-large { display: block; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-status--missing {
|
||||||
|
border: $borderMissing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-angular-ov-wrapper {
|
.l-angular-ov-wrapper {
|
||||||
|
@ -19,10 +19,9 @@
|
|||||||
display: block;
|
display: block;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
opacity: $objectLabelTypeIconOpacity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-missing {
|
&.is-status--missing {
|
||||||
@include isMissing($absPos: true);
|
@include isMissing($absPos: true);
|
||||||
|
|
||||||
[class*='__type-icon']:before,
|
[class*='__type-icon']:before,
|
||||||
@ -30,7 +29,7 @@
|
|||||||
opacity: $opacityMissing;
|
opacity: $opacityMissing;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-missing__indicator {
|
.is-status__indicator {
|
||||||
right: -3px;
|
right: -3px;
|
||||||
top: -3px;
|
top: -3px;
|
||||||
transform: scale(0.7);
|
transform: scale(0.7);
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
<div class="c-inspector__header">
|
<div class="c-inspector__header">
|
||||||
<div v-if="!multiSelect"
|
<div v-if="!multiSelect"
|
||||||
class="c-inspector__selected c-object-label"
|
class="c-inspector__selected c-object-label"
|
||||||
:class="{'is-missing': isMissing }"
|
:class="{'is-status--missing': isMissing }"
|
||||||
>
|
>
|
||||||
<div class="c-object-label__type-icon"
|
<div class="c-object-label__type-icon"
|
||||||
:class="typeCssClass"
|
:class="typeCssClass"
|
||||||
>
|
>
|
||||||
<span class="is-missing__indicator"
|
<span class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="!singleSelectNonObject"
|
<span v-if="!singleSelectNonObject"
|
||||||
|
@ -29,6 +29,10 @@
|
|||||||
filter: $objectLabelNameFilter;
|
filter: $objectLabelNameFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-object-label__type-icon {
|
||||||
|
opacity: $objectLabelTypeIconOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
&--non-domain-object .c-object-label__name {
|
&--non-domain-object .c-object-label__name {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,13 @@
|
|||||||
></button>
|
></button>
|
||||||
<div
|
<div
|
||||||
class="l-browse-bar__object-name--w c-object-label"
|
class="l-browse-bar__object-name--w c-object-label"
|
||||||
:class="{
|
:class="[statusClass]"
|
||||||
classList,
|
|
||||||
'is-missing': domainObject.status === 'missing'
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<div class="c-object-label__type-icon"
|
<div class="c-object-label__type-icon"
|
||||||
:class="type.cssClass"
|
:class="type.cssClass"
|
||||||
>
|
>
|
||||||
<span class="is-missing__indicator"
|
<span class="is-status__indicator"
|
||||||
title="This item is missing"
|
title="This item is missing or suspect"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
@ -31,10 +28,6 @@
|
|||||||
{{ domainObject.name }}
|
{{ domainObject.name }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="l-browse-bar__context-actions c-disclosure-button"
|
|
||||||
@click.prevent.stop="showContextMenu"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="l-browse-bar__end">
|
<div class="l-browse-bar__end">
|
||||||
@ -42,7 +35,6 @@
|
|||||||
v-if="!isEditing"
|
v-if="!isEditing"
|
||||||
:current-view="currentView"
|
:current-view="currentView"
|
||||||
:views="views"
|
:views="views"
|
||||||
@setView="setView"
|
|
||||||
/>
|
/>
|
||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
<NotebookMenuSwitcher v-if="notebookEnabled"
|
<NotebookMenuSwitcher v-if="notebookEnabled"
|
||||||
@ -52,15 +44,24 @@
|
|||||||
/>
|
/>
|
||||||
<div class="l-browse-bar__actions">
|
<div class="l-browse-bar__actions">
|
||||||
<button
|
<button
|
||||||
v-if="isViewEditable && !isEditing"
|
v-for="(item, index) in statusBarItems"
|
||||||
:title="lockedOrUnlocked"
|
:key="index"
|
||||||
class="c-button"
|
class="c-button"
|
||||||
|
:class="item.cssClass"
|
||||||
|
@click="item.callBack"
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="isViewEditable & !isEditing"
|
||||||
|
:title="lockedOrUnlockedTitle"
|
||||||
:class="{
|
:class="{
|
||||||
'icon-lock c-button--caution': domainObject.locked,
|
'c-button icon-lock': domainObject.locked,
|
||||||
'icon-unlocked': !domainObject.locked
|
'c-icon-button icon-unlocked': !domainObject.locked
|
||||||
}"
|
}"
|
||||||
@click="toggleLock(!domainObject.locked)"
|
@click="toggleLock(!domainObject.locked)"
|
||||||
></button>
|
></button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="isViewEditable && !isEditing && !domainObject.locked"
|
v-if="isViewEditable && !isEditing && !domainObject.locked"
|
||||||
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
|
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
|
||||||
@ -106,6 +107,11 @@
|
|||||||
title="Cancel Editing"
|
title="Cancel Editing"
|
||||||
@click="promptUserandCancelEditing()"
|
@click="promptUserandCancelEditing()"
|
||||||
></button>
|
></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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -123,6 +129,14 @@ export default {
|
|||||||
NotebookMenuSwitcher,
|
NotebookMenuSwitcher,
|
||||||
ViewSwitcher
|
ViewSwitcher
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
viewProvider: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
notebookTypes: [],
|
notebookTypes: [],
|
||||||
@ -131,17 +145,14 @@ export default {
|
|||||||
domainObject: PLACEHOLDER_OBJECT,
|
domainObject: PLACEHOLDER_OBJECT,
|
||||||
viewKey: undefined,
|
viewKey: undefined,
|
||||||
isEditing: this.openmct.editor.isEditing(),
|
isEditing: this.openmct.editor.isEditing(),
|
||||||
notebookEnabled: this.openmct.types.get('notebook')
|
notebookEnabled: this.openmct.types.get('notebook'),
|
||||||
|
statusBarItems: [],
|
||||||
|
status: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
classList() {
|
statusClass() {
|
||||||
const classList = this.domainObject.classList;
|
return (this.status) ? `is-status--${this.status}` : '';
|
||||||
if (!classList || !classList.length) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return classList.join(' ');
|
|
||||||
},
|
},
|
||||||
currentView() {
|
currentView() {
|
||||||
return this.views.filter(v => v.key === this.viewKey)[0] || {};
|
return this.views.filter(v => v.key === this.viewKey)[0] || {};
|
||||||
@ -155,7 +166,10 @@ export default {
|
|||||||
return {
|
return {
|
||||||
key: p.key,
|
key: p.key,
|
||||||
cssClass: p.cssClass,
|
cssClass: p.cssClass,
|
||||||
name: p.name
|
name: p.name,
|
||||||
|
callBack: () => {
|
||||||
|
return this.setView({key: p.key});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -187,7 +201,7 @@ export default {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
lockedOrUnlocked() {
|
lockedOrUnlockedTitle() {
|
||||||
if (this.domainObject.locked) {
|
if (this.domainObject.locked) {
|
||||||
return 'Locked for editing - click to unlock.';
|
return 'Locked for editing - click to unlock.';
|
||||||
} else {
|
} else {
|
||||||
@ -204,6 +218,28 @@ export default {
|
|||||||
this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
|
this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
|
||||||
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 () {
|
mounted: function () {
|
||||||
@ -219,6 +255,14 @@ export default {
|
|||||||
this.mutationObserver();
|
this.mutationObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.actionCollection) {
|
||||||
|
this.unlistenToActionCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.removeStatusListener) {
|
||||||
|
this.removeStatusListener();
|
||||||
|
}
|
||||||
|
|
||||||
document.removeEventListener('click', this.closeViewAndSaveMenu);
|
document.removeEventListener('click', this.closeViewAndSaveMenu);
|
||||||
window.removeEventListener('click', this.promptUserbeforeNavigatingAway);
|
window.removeEventListener('click', this.promptUserbeforeNavigatingAway);
|
||||||
},
|
},
|
||||||
@ -300,14 +344,35 @@ export default {
|
|||||||
this.openmct.editor.edit();
|
this.openmct.editor.edit();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
showContextMenu(event) {
|
|
||||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.openmct.router.path, event.clientX, event.clientY);
|
|
||||||
},
|
|
||||||
goToParent() {
|
goToParent() {
|
||||||
window.location.hash = this.parentUrl;
|
window.location.hash = this.parentUrl;
|
||||||
},
|
},
|
||||||
|
updateActionItems(actionItems) {
|
||||||
|
this.statusBarItems = this.actionCollection.getStatusBarActions();
|
||||||
|
this.menuActionItems = this.actionCollection.getVisibleActions();
|
||||||
|
},
|
||||||
|
showMenuItems(event) {
|
||||||
|
let actions;
|
||||||
|
|
||||||
|
if (this.menuActionItems.length) {
|
||||||
|
actions = this.menuActionItems;
|
||||||
|
} else {
|
||||||
|
actions = this.openmct.actions.get(this.openmct.router.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sortedActions = this.openmct.actions._groupAndSortActions(actions);
|
||||||
|
this.openmct.menus.showMenu(event.x, event.y, sortedActions);
|
||||||
|
},
|
||||||
|
unlistenToActionCollection() {
|
||||||
|
this.actionCollection.off('update', this.updateActionItems);
|
||||||
|
this.actionCollection.destroy();
|
||||||
|
delete this.actionCollection;
|
||||||
|
},
|
||||||
toggleLock(flag) {
|
toggleLock(flag) {
|
||||||
this.openmct.objects.mutate(this.domainObject, 'locked', flag);
|
this.openmct.objects.mutate(this.domainObject, 'locked', flag);
|
||||||
|
},
|
||||||
|
setStatus(status) {
|
||||||
|
this.status = status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
<browse-bar
|
<browse-bar
|
||||||
ref="browseBar"
|
ref="browseBar"
|
||||||
class="l-shell__main-view-browse-bar"
|
class="l-shell__main-view-browse-bar"
|
||||||
|
:view-provider="viewProvider"
|
||||||
@sync-tree-navigation="handleSyncTreeNavigation"
|
@sync-tree-navigation="handleSyncTreeNavigation"
|
||||||
/>
|
/>
|
||||||
<toolbar
|
<toolbar
|
||||||
@ -74,8 +75,9 @@
|
|||||||
<object-view
|
<object-view
|
||||||
ref="browseObject"
|
ref="browseObject"
|
||||||
class="l-shell__main-container"
|
class="l-shell__main-container"
|
||||||
:show-edit-view="true"
|
|
||||||
data-selectable
|
data-selectable
|
||||||
|
:show-edit-view="true"
|
||||||
|
@change-provider="setProvider"
|
||||||
/>
|
/>
|
||||||
<component
|
<component
|
||||||
:is="conductorComponent"
|
:is="conductorComponent"
|
||||||
@ -139,6 +141,7 @@ export default {
|
|||||||
conductorComponent: undefined,
|
conductorComponent: undefined,
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
hasToolbar: false,
|
hasToolbar: false,
|
||||||
|
viewProvider: undefined,
|
||||||
headExpanded,
|
headExpanded,
|
||||||
triggerSync: false
|
triggerSync: false
|
||||||
};
|
};
|
||||||
@ -215,6 +218,9 @@ export default {
|
|||||||
|
|
||||||
this.hasToolbar = structure.length > 0;
|
this.hasToolbar = structure.length > 0;
|
||||||
},
|
},
|
||||||
|
setProvider(provider) {
|
||||||
|
this.viewProvider = provider;
|
||||||
|
},
|
||||||
handleSyncTreeNavigation() {
|
handleSyncTreeNavigation() {
|
||||||
this.triggerSync = !this.triggerSync;
|
this.triggerSync = !this.triggerSync;
|
||||||
}
|
}
|
||||||
|
@ -4,36 +4,21 @@
|
|||||||
class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
|
class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="c-button--menu"
|
class="c-icon-button c-button--menu"
|
||||||
:class="currentView.cssClass"
|
:class="currentView.cssClass"
|
||||||
title="Change the current view"
|
title="Change the current view"
|
||||||
@click.stop="toggleViewMenu"
|
@click.prevent.stop="showMenu"
|
||||||
>
|
>
|
||||||
<span class="c-button__label">
|
<span class="c-icon-button__label">
|
||||||
{{ currentView.name }}
|
{{ currentView.name }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
currentView: {
|
currentView: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -44,26 +29,16 @@ export default {
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showViewMenu: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
document.addEventListener('click', this.hideViewMenu);
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
document.removeEventListener('click', this.hideViewMenu);
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
setView(view) {
|
setView(view) {
|
||||||
this.$emit('setView', view);
|
this.$emit('setView', view);
|
||||||
},
|
},
|
||||||
toggleViewMenu() {
|
showMenu() {
|
||||||
this.showViewMenu = !this.showViewMenu;
|
const elementBoundingClientRect = this.$el.getBoundingClientRect();
|
||||||
},
|
const x = elementBoundingClientRect.x;
|
||||||
hideViewMenu() {
|
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
|
||||||
this.showViewMenu = false;
|
|
||||||
|
this.openmct.menus.showMenu(x, y, this.views);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -332,7 +332,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-left: $interiorMargin;
|
margin-left: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,12 +376,16 @@
|
|||||||
filter: $objectLabelNameFilter;
|
filter: $objectLabelNameFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-object-label__type-icon {
|
||||||
|
opacity: $objectLabelTypeIconOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
&__object-name--w {
|
&__object-name--w {
|
||||||
@include headerFont(1.5em);
|
@include headerFont(1.5em);
|
||||||
margin-left: $interiorMarginLg;
|
margin-left: $interiorMarginLg;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
.is-missing__indicator {
|
.is-status__indicator {
|
||||||
right: -5px !important;
|
right: -5px !important;
|
||||||
top: -4px !important;
|
top: -4px !important;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,11 @@ export default {
|
|||||||
showContextMenu(event) {
|
showContextMenu(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.objectPath, event.clientX, event.clientY);
|
|
||||||
|
let actions = this.openmct.actions.get(this.objectPath);
|
||||||
|
let sortedActions = this.openmct.actions._groupAndSortActions(actions);
|
||||||
|
|
||||||
|
this.openmct.menus.showMenu(event.clientX, event.clientY, sortedActions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
:current-view="currentView"
|
:current-view="currentView"
|
||||||
:domain-object="domainObject"
|
:domain-object="domainObject"
|
||||||
:views="views"
|
:views="views"
|
||||||
@setView="setView"
|
|
||||||
/>
|
/>
|
||||||
<div class="l-preview-window__object-view">
|
<div class="l-preview-window__object-view">
|
||||||
<div ref="objectView"></div>
|
<div ref="objectView"></div>
|
||||||
@ -51,23 +50,20 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
domainObject: domainObject,
|
domainObject: domainObject,
|
||||||
viewKey: undefined
|
viewKey: undefined,
|
||||||
|
views: [],
|
||||||
|
currentView: {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
views() {
|
|
||||||
return this
|
|
||||||
.openmct
|
|
||||||
.objectViews
|
|
||||||
.get(this.domainObject);
|
|
||||||
},
|
|
||||||
currentView() {
|
|
||||||
return this.views.filter(v => v.key === this.viewKey)[0] || {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
let view = this.openmct.objectViews.get(this.domainObject)[0];
|
this.views = this.openmct.objectViews.get(this.domainObject).map((view) => {
|
||||||
this.setView(view);
|
view.callBack = () => {
|
||||||
|
return this.setView(view);
|
||||||
|
};
|
||||||
|
|
||||||
|
return view;
|
||||||
|
});
|
||||||
|
this.setView(this.views[0]);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.view.destroy();
|
this.view.destroy();
|
||||||
@ -91,9 +87,13 @@ export default {
|
|||||||
delete this.viewContainer;
|
delete this.viewContainer;
|
||||||
},
|
},
|
||||||
setView(view) {
|
setView(view) {
|
||||||
this.clear();
|
if (this.viewKey === view.key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clear();
|
||||||
this.viewKey = view.key;
|
this.viewKey = view.key;
|
||||||
|
this.currentView = view;
|
||||||
this.viewContainer = document.createElement('div');
|
this.viewContainer = document.createElement('div');
|
||||||
this.viewContainer.classList.add('l-angular-ov-wrapper');
|
this.viewContainer.classList.add('l-angular-ov-wrapper');
|
||||||
this.$refs.objectView.append(this.viewContainer);
|
this.$refs.objectView.append(this.viewContainer);
|
||||||
|
@ -27,10 +27,12 @@ export default class PreviewAction {
|
|||||||
/**
|
/**
|
||||||
* Metadata
|
* Metadata
|
||||||
*/
|
*/
|
||||||
this.name = 'Preview';
|
this.name = 'View';
|
||||||
this.key = 'preview';
|
this.key = 'preview';
|
||||||
this.description = 'Preview in large dialog';
|
this.description = 'View in large dialog';
|
||||||
this.cssClass = 'icon-eye-open';
|
this.cssClass = 'icon-items-expand';
|
||||||
|
this.group = 'windowing';
|
||||||
|
this.priority = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dependencies
|
* Dependencies
|
||||||
@ -81,8 +83,7 @@ export default class PreviewAction {
|
|||||||
let targetObject = objectPath[0];
|
let targetObject = objectPath[0];
|
||||||
let navigatedObject = this._openmct.router.path[0];
|
let navigatedObject = this._openmct.router.path[0];
|
||||||
|
|
||||||
return targetObject.identifier.namespace === navigatedObject.identifier.namespace
|
return this._openmct.objects.areIdsEqual(targetObject.identifier, navigatedObject.identifier);
|
||||||
&& targetObject.identifier.key === navigatedObject.identifier.key;
|
|
||||||
}
|
}
|
||||||
_preventPreview(objectPath) {
|
_preventPreview(objectPath) {
|
||||||
const noPreviewTypes = ['folder'];
|
const noPreviewTypes = ['folder'];
|
||||||
|
@ -32,4 +32,14 @@ export default class ViewHistoricalDataAction extends PreviewAction {
|
|||||||
this.cssClass = 'icon-eye-open';
|
this.cssClass = 'icon-eye-open';
|
||||||
this.hideInDefaultMenu = true;
|
this.hideInDefaultMenu = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appliesTo(objectPath, view = {}) {
|
||||||
|
let viewContext = view.getViewContext && view.getViewContext();
|
||||||
|
|
||||||
|
if (objectPath.length && viewContext && viewContext.viewHistoricalData) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import ViewHistoricalDataAction from './ViewHistoricalDataAction';
|
|||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
openmct.contextMenu.registerAction(new PreviewAction(openmct));
|
openmct.actions.register(new PreviewAction(openmct));
|
||||||
openmct.contextMenu.registerAction(new ViewHistoricalDataAction(openmct));
|
openmct.actions.register(new ViewHistoricalDataAction(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
:v-if="!hideViewSwitcher"
|
:v-if="!hideViewSwitcher"
|
||||||
:views="views"
|
:views="views"
|
||||||
:current-view="currentView"
|
:current-view="currentView"
|
||||||
@setView="setView"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
13
src/utils/clipboard.js
Normal file
13
src/utils/clipboard.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class Clipboard {
|
||||||
|
updateClipboard(newClip) {
|
||||||
|
// return promise
|
||||||
|
return navigator.clipboard.writeText(newClip);
|
||||||
|
}
|
||||||
|
|
||||||
|
readClipboard() {
|
||||||
|
// return promise
|
||||||
|
return navigator.clipboard.readText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Clipboard();
|
Reference in New Issue
Block a user