Compare commits

...

86 Commits

Author SHA1 Message Date
f04c274d33 fix lint issues 2020-11-12 16:02:57 -08:00
3624236c26 Merge branch 'three-dot-menu-proto' of https://github.com/nasa/openmct into openmct-status-api 2020-11-12 15:05:28 -08:00
0126542411 merge latest master 2020-11-12 15:04:09 -08:00
2c49e62863 merge with latest three-dots 2020-11-12 15:01:37 -08:00
c2df3cdd14 CSS and markup refactoring to support addition of 'suspect' telemetry (#3499)
* CSS and markup refactoring to support addition of 'suspect' telemetry

- Significant refactoring of CSS classing: `is-missing` is now
`is-status--missing` and `is-missing__indicator` is now simply
`is-status__indicator`, allowing the wrapping `is-missing--*` class to
control what is displayed;
- New SCSS mixin @isStatus, and changes to mixin @isMissing to support
new `is-status--suspect` class;
- Changed titling for missing objects from 'This item is missing' to
'This item is missing or suspect'. **IMPORTANT NOTE** This is temporary
and should be replaced with a more robust approach to titling that
allows title strings to be defined in configuration and dynamically
applied;
- Refactored computed property `statusClass` across multiple components
to return an empty string when status is undefined - this was
previously erroneously returning `is-undefined` in that circumstance;
- Removed commented code;

* CSS and markup refactoring to support addition of 'suspect' telemetry

- Refinements to broaden capability of `is-status*` mixin;
2020-11-12 14:58:51 -08:00
b0203f2272 Preparing master for the next sprint v1.4.1-SNAPSHOT (#3508) 2020-11-09 13:10:50 -08:00
77b720d00d Fix Imagery for VERVE #266 (#3507)
* Fairly extensive refactoring to fix layout in Safari for VERVE #266

- VERY WIP at this time!
- Many instances of `height: 100%` converted or amended to include
`flex: 1 1 auto`;
- Some high-use containers like `c-so-view__object-view` converted to use
flex layout;
- Views fixed generally for sub-object view, and specifically for
Conditionals, Folder grid view and Imagery;
- Imagery background image holder converted to use absolute positioning;
- TODO: Notebook has a problem where the side nav pane isn't overlaying
in Safari - it's a JS thing, c-drawer--push isn't be replaced with
c-drawer--overlays as it should;

* CSS and markup refactoring to support addition of 'suspect' telemetry

- Remove commented code;
2020-11-09 09:33:25 -08:00
ba982671b2 Quick idea on a splash screen that will not increase load time (#3376)
* New splash screen

Co-authored-by: charlesh88 <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-11-06 13:58:57 -08:00
02be8a9875 merge with master 2020-11-03 13:48:28 -08:00
3bd57c8fff add tests for actionsAPI 2020-11-03 13:43:45 -08:00
5df7d92d64 [Navigation Tree] Fix tree loading issue (#3500)
* added resize observer for FIRST load of mainTree

* new Promise driven height calculation test

* cleaning up code, sticking with promise height caclcuations

* more cleanup

* returning from the initialize function
2020-11-03 12:06:49 -08:00
94091b25ec remove focused describe 2020-11-02 11:09:59 -08:00
c191ffb37d add tests for MenuAPI 2020-11-02 11:06:59 -08:00
5f03dc45ee fix style application in telemetry vue 2020-10-29 15:44:57 -07:00
14ac758760 Merge branch 'three-dot-menu-proto' of https://github.com/nasa/openmct into openmct-status-api 2020-10-29 15:30:48 -07:00
eb709a60cb Merge branch 'master' of https://github.com/nasa/openmct into three-dot-menu-proto 2020-10-29 15:24:51 -07:00
eba1a48a44 fix lint errors 2020-10-29 15:19:42 -07:00
4a0654dbcb add tests for status API 2020-10-29 15:07:02 -07:00
9b6d339d69 add status to tabs view 2020-10-29 13:48:51 -07:00
f90afb9277 simplifying class applications 2020-10-29 13:35:19 -07:00
018dfb1e28 Merge branch 'master' of https://github.com/nasa/openmct into openmct-status-api 2020-10-29 11:59:03 -07:00
c72a02aaa3 wip 2020-10-29 11:07:49 -07:00
bf3fd66942 fix broken export marked rows 2020-10-20 16:54:53 -07:00
8414ded1ec Merge branch 'master' into three-dot-menu-proto 2020-10-19 18:01:44 -07:00
646c871c76 Merge branch 'master' of https://github.com/nasa/openmct into three-dot-menu-proto 2020-10-19 10:29:38 -07:00
ba401b3341 fix lint errors 2020-10-18 15:55:39 -07:00
5ef02ec4a2 git enable legacy toolbar for legacy tables 2020-10-15 11:55:21 -07:00
d788031019 Merge branch 'master' of https://github.com/nasa/openmct into three-dot-menu-proto 2020-10-15 09:49:32 -07:00
d870874649 Alphanumeric notebook entry (#3387)
* [Notebook] Copy label and value from alphanumeric in Layout directly to a Notebook entry #3299

* changes to use updated action API.

* added 'copyToNotebook' + some refactor.

* Added current default notebook name to 'Copy to Notebook' string.

* string delimiter updated.

* cleanup

* updated per review comments

* updated as per review comments.

* fixed rebase issue.

* corrected case in import.

* fixed lint errors.

* fixed test error, Unhandled promise rejection: TypeError: 'clipboard-write'

* removed navigator.permissions.query check
2020-10-07 14:01:36 -07:00
711a7a2eb5 fix console error on changing types and canceling edit 2020-10-07 12:30:07 -07:00
c105a08cfe add tests for viewDatum action plugin 2020-10-06 15:35:08 -07:00
b87375a809 remove vue style 2020-10-06 12:37:28 -07:00
9fed056d22 add a viewDatum Action 2020-10-06 12:37:00 -07:00
251bf21933 Merge branch 'master' into three-dot-menu-proto 2020-10-05 11:25:55 -07:00
a180bf7c02 fix lint error 2020-10-05 11:16:34 -07:00
ed8a54f0f9 fix broken spec files 2020-10-05 11:12:07 -07:00
ff3c2da0f9 fix broken tests and add update on isEditing for actionCollections 2020-10-05 10:31:01 -07:00
28d5821120 fix lint issues 2020-10-02 15:08:07 -07:00
f5ee457274 Merge branch 'master' into three-dot-menu-proto 2020-10-02 15:02:20 -07:00
9d2770e4d2 update according to andrews comments 2020-10-02 15:01:04 -07:00
8b25009816 reverse check for viewProvider.getViewContext 2020-10-02 11:48:47 -07:00
074fe4481a linting fixes 2020-10-02 11:42:41 -07:00
fbd928b842 Merge branch 'master' of https://github.com/nasa/openmct into three-dot-menu-proto 2020-10-02 11:25:43 -07:00
110947db09 update according to review comments 2020-10-02 11:22:18 -07:00
ef91e92fbc wip 2020-09-30 13:13:50 -07:00
d201cac4ac update changes 2020-09-30 13:07:18 -07:00
dcb3ccfec7 use weak map to cache actionCollections 2020-09-30 12:44:28 -07:00
78522cd4f1 remove unecessary computed properties 2020-09-16 10:07:20 -07:00
ca232d45cc notebook and menu switchers to use menu api 2020-09-16 09:57:29 -07:00
df495c841a merge master 2020-09-16 09:07:26 -07:00
92a37ef36b Merge branch 'master' into three-dot-menu-proto 2020-09-14 09:52:10 -07:00
fd731ca430 fix ladrow and tablerow use of contextMenus 2020-09-09 14:18:20 -07:00
263b1cd3d5 fix telemetry view view historical 2020-09-09 13:58:28 -07:00
978fc8b5a3 performance enhancements 2020-09-09 11:37:49 -07:00
698ccc5a35 fix bug with object update 2020-09-09 10:15:19 -07:00
e5aa5b5a5f fix onDestroy error 2020-09-08 16:06:45 -07:00
b942988ef8 Merge branch 'master' of https://github.com/nasa/openmct into three-dot-menu-proto 2020-09-08 16:00:10 -07:00
1eec20f2ea 3 dot menu refactor (#3360)
* refactoring actions api

* wip

* wip

* almost done

* fix lint issues
2020-09-08 15:59:42 -07:00
767a2048eb Three dot menu implementation WIP
- Hide frame control labels when object is within a Flexible Layout;
2020-09-03 13:04:48 -07:00
e65cf1661c Three dot menu implementation WIP
- Recast "Preview" action as "View";
2020-09-01 10:08:36 -07:00
0eae48646c Three dot menu implementation WIP
- Merge latest master, fix conflicts;
2020-08-31 14:09:01 -07:00
0ba8a275d2 Three dot menu implementation WIP
- Fixed button title;
2020-08-27 08:56:32 -07:00
d8d32cc3ac Three dot menu implementation WIP
- Changed Snapshot-related buttons and menus to use `icon-camera`;
- Normalized padding for c-icon-buttons;
- Added a CSS class `c-so-view--<domainObject.type>` to allow
sub-layouts with hidden frames to completely hide their headers and
buttons - this is needed to avoid overlap collisions with further
sub-objects;
- Changed button styling in main view to be more in line with 'iconic'
approach used elsewhere, enabled button labels where applicable;
- Better, more consistent hover approach for `c-button` and
`c-icon-button` controls;
- Changed Snow theme constant hover `filter` value for
better color matching;
- Fixed `c-object-label` type-icon opacity;
- Changed Snow theme constant for object name to fix inline editing of
object name being too dark;
2020-08-25 18:47:55 -07:00
a800848fe1 Three dot menu implementation WIP
- Make button labels hide/show based on frame size in Layout;
2020-08-25 14:30:40 -07:00
6881d98ba6 Three dot menu implementation WIP
- Initial styling and hover behavior for Layout frame controls;
- Defined default color for c-icon-buttons;
- Changed buttons from `c-button` to `c-icon-button`;
2020-08-21 18:42:32 -07:00
48d077cd2e Three dot menu implementation WIP
- Merge in `add-glyphs-082020`;
2020-08-20 14:08:15 -07:00
030dd93c91 Add new glyphs
- Grid on, grid off and camera;
2020-08-20 11:10:22 -07:00
03bf6fc0a3 Three dot menu implementation WIP
- Add icomoon config JSON file;
2020-08-20 10:09:03 -07:00
ef0a2ed5d2 Three dot menu implementation WIP
- Add new `icon-3-dots` glyph;
2020-08-20 10:08:52 -07:00
a40aa84752 Three dot menu implementation WIP
- Add icomoon config JSON file;
2020-08-19 14:40:22 -07:00
d3b69dda82 Three dot menu implementation WIP
- Add new `icon-3-dots` glyph;
2020-08-19 14:38:47 -07:00
d3126ebf5c Three dot menu implementation WIP
- Replace inline styling;
- Style for c-menu `__section-separator` and `__section-hint` refined;
2020-08-19 12:59:20 -07:00
4479cbc7a2 abstract logic into actionAPI 2020-08-18 16:26:20 -07:00
f8ff44dac0 wip 2020-08-18 15:52:59 -07:00
8f4280d15b fix table row marking 2020-08-18 15:43:00 -07:00
6daa27ff31 merge with master 2020-08-18 15:24:15 -07:00
43f6c3f85d wip 2020-08-18 15:12:39 -07:00
1a7c76cf3e updated to new actions API 2020-08-18 13:56:26 -07:00
cee9cd7bd1 working groups 2020-08-14 10:15:13 -07:00
c42df20281 combine domainObject actions and view actions 2020-08-13 18:42:58 -07:00
b4149bd2b3 working in objectFrame 2020-08-13 17:39:35 -07:00
f436ac9ba0 working proto 2020-08-13 16:26:29 -07:00
8493b481dd make reviewer requested changes 2020-08-13 14:46:18 -07:00
28723b59b7 Merge branch 'master' into context-menu-option 2020-06-10 16:36:33 -07:00
9fa7de0b77 remove unused files 2020-06-10 16:30:24 -07:00
54bfc84ada replaced contextMenu with overlay menu 2020-06-10 16:06:42 -07:00
101 changed files with 2360 additions and 661 deletions

View File

@ -30,6 +30,38 @@
<link rel="icon" type="image/png" href="dist/favicons/favicon-96x96.png" sizes="96x96" type="image/x-icon">
<link rel="icon" type="image/png" href="dist/favicons/favicon-32x32.png" sizes="32x32" type="image/x-icon">
<link rel="icon" type="image/png" href="dist/favicons/favicon-16x16.png" sizes="16x16" type="image/x-icon">
<style type="text/css">
@keyframes splash-spinner {
0% {
transform: translate(-50%, -50%) rotate(0deg); }
100% {
transform: translate(-50%, -50%) rotate(360deg); } }
#splash-screen {
background-color: black;
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
z-index: 10000;
}
#splash-screen:before {
animation-name: splash-spinner;
animation-duration: 0.5s;
animation-iteration-count: infinite;
animation-timing-function: linear;
border-radius: 50%;
border-color: rgba(255,255,255,0.25);
border-top-color: white;
border-style: solid;
border-width: 10px;
content: '';
display: block;
opacity: 0.25;
position: absolute;
left: 50%; top: 50%;
height: 100px; width: 100px;
}
</style>
</head>
<body>
</body>

View File

@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "1.3.3-SNAPSHOT",
"version": "1.4.1-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {},
"devDependencies": {

View File

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

View File

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

View File

@ -20,12 +20,12 @@
at runtime from the About dialog for additional information.
-->
<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()}}"
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 class='c-object-label__name'>{{model.name}}</div>
</div>

View File

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

View File

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

View File

@ -242,7 +242,11 @@ define([
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();
@ -271,6 +275,7 @@ define([
this.install(this.plugins.URLTimeSettingsSynchronizer());
this.install(this.plugins.NotificationIndicator());
this.install(this.plugins.NewFolderAction());
this.install(this.plugins.ViewDatumAction());
}
MCT.prototype = Object.create(EventEmitter.prototype);

View File

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

View File

@ -31,6 +31,8 @@ export default class LegacyContextMenuAction {
this.description = LegacyAction.definition.description;
this.cssClass = LegacyAction.definition.cssClass;
this.LegacyAction = LegacyAction;
this.group = LegacyAction.definition.group;
this.priority = LegacyAction.definition.priority;
}
invoke(objectPath) {

View 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;

View 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;

View File

@ -0,0 +1,119 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ActionsAPI from './ActionsAPI';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
describe('The Actions API', () => {
let openmct;
let actionsAPI;
let mockAction;
let mockObjectPath;
let mockViewContext1;
beforeEach(() => {
openmct = createOpenMct();
actionsAPI = new ActionsAPI(openmct);
mockAction = {
name: 'Test Action',
key: 'test-action',
cssClass: 'test-action',
description: 'This is a test action',
group: 'action',
priority: 9,
appliesTo: (objectPath, view = {}) => {
if (view.getViewContext) {
let viewContext = view.getViewContext();
return viewContext.onlyAppliesToTestCase;
} else if (objectPath.length) {
return objectPath[0].type === 'fake-folder';
}
return false;
},
invoke: () => {
}
};
mockObjectPath = [
{
name: 'mock folder',
type: 'fake-folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
},
{
name: 'mock parent folder',
type: 'fake-folder',
identifier: {
key: 'mock-parent-folder',
namespace: ''
}
}
];
mockViewContext1 = {
getViewContext: () => {
return {
onlyAppliesToTestCase: true,
skipCache: true
};
}
};
});
afterEach(() => {
resetApplicationState(openmct);
});
describe("register method", () => {
it("adds action to ActionsAPI", () => {
actionsAPI.register(mockAction);
let action = actionsAPI.get(mockObjectPath, mockViewContext1)[mockAction.key];
expect(action.key).toEqual(mockAction.key);
expect(action.name).toEqual(mockAction.name);
});
});
describe("get method", () => {
beforeEach(() => {
actionsAPI.register(mockAction);
});
it("returns an object with relevant actions when invoked with objectPath only", () => {
let action = actionsAPI.get(mockObjectPath, mockViewContext1)[mockAction.key];
expect(action.key).toEqual(mockAction.key);
expect(action.name).toEqual(mockAction.name);
});
it("returns an object with relevant actions when invoked with viewContext and skipCache", () => {
let action = actionsAPI.get(mockObjectPath, mockViewContext1)[mockAction.key];
expect(action.key).toEqual(mockAction.key);
expect(action.name).toEqual(mockAction.name);
});
});
});

View File

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

View File

@ -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>

View File

@ -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
View 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;

125
src/api/menu/MenuAPISpec.js Normal file
View File

@ -0,0 +1,125 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import MenuAPI from './MenuAPI';
import Menu from './menu';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
describe ('The Menu API', () => {
let openmct;
let menuAPI;
let actionsArray;
let x;
let y;
let result;
beforeEach(() => {
openmct = createOpenMct();
menuAPI = new MenuAPI(openmct);
actionsArray = [
{
name: 'Test Action 1',
cssClass: 'test-css-class-1',
description: 'This is a test action',
callBack: () => {
result = 'Test Action 1 Invoked';
}
},
{
name: 'Test Action 2',
cssClass: 'test-css-class-2',
description: 'This is a test action',
callBack: () => {
result = 'Test Action 2 Invoked';
}
}
];
x = 8;
y = 16;
});
afterEach(() => {
resetApplicationState(openmct);
});
describe("showMenu method", () => {
it("creates an instance of Menu when invoked", () => {
menuAPI.showMenu(x, y, actionsArray);
expect(menuAPI.menuComponent).toBeInstanceOf(Menu);
});
describe("creates a menu component", () => {
let menuComponent;
let vueComponent;
beforeEach(() => {
menuAPI.showMenu(x, y, actionsArray);
vueComponent = menuAPI.menuComponent.component;
menuComponent = document.querySelector(".c-menu");
spyOn(vueComponent, '$destroy');
});
it("renders a menu component in the expected x and y coordinates", () => {
let boundingClientRect = menuComponent.getBoundingClientRect();
let left = boundingClientRect.left;
let top = boundingClientRect.top;
expect(left).toEqual(x);
expect(top).toEqual(y);
});
it("with all the actions passed in", () => {
expect(menuComponent).toBeDefined();
let listItems = menuComponent.children[0].children;
expect(listItems.length).toEqual(actionsArray.length);
});
it("with click-able menu items, that will invoke the correct callBacks", () => {
let listItem1 = menuComponent.children[0].children[0];
listItem1.click();
expect(result).toEqual("Test Action 1 Invoked");
});
it("dismisses the menu when action is clicked on", () => {
let listItem1 = menuComponent.children[0].children[0];
listItem1.click();
let menu = document.querySelector('.c-menu');
expect(menu).toBeNull();
});
it("invokes the destroy method when menu is dismissed", () => {
document.body.click();
expect(vueComponent.$destroy).toHaveBeenCalled();
});
});
});
});

View File

@ -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
View 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;

View File

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

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

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

View File

@ -176,7 +176,10 @@ export default {
this.timestampKey = timeSystem.key;
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
let allActions = this.openmct.actions.get(this.currentObjectPath, {}, {viewHistoricalData: true});
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
this.openmct.menus.showMenu(event.x, event.y, applicableActions);
},
resetValues() {
this.value = '---';

View File

@ -23,6 +23,7 @@
export default class ClearDataAction {
constructor(openmct, appliesToObjects) {
this.name = 'Clear Data for Object';
this.key = 'clear-data-action';
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
this.cssClass = 'icon-clear-data';

View File

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

View File

@ -26,12 +26,12 @@ import ClearDataAction from '../clearDataAction.js';
describe('When the Clear Data Plugin is installed,', function () {
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
const mockContextMenuProvider = jasmine.createSpyObj('contextMenu', ['registerAction']);
const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
const openmct = {
objectViews: mockObjectViews,
indicators: mockIndicatorProvider,
contextMenu: mockContextMenuProvider,
actions: mockActionsProvider,
install: function (plugin) {
plugin(this);
}
@ -51,7 +51,7 @@ describe('When the Clear Data Plugin is installed,', function () {
it('Clear Data context menu action is installed', function () {
openmct.install(ClearDataActionPlugin([]));
expect(mockContextMenuProvider.registerAction).toHaveBeenCalled();
expect(mockActionsProvider.register).toHaveBeenCalled();
});
it('clear data action emits a clearData event when invoked', function () {

View File

@ -50,6 +50,7 @@
.c-cs {
display: flex;
flex-direction: column;
flex: 1 1 auto;
height: 100%;
overflow: hidden;

View File

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

View 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;
}
}

View File

@ -142,14 +142,18 @@ export default {
this.domainObject = domainObject;
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
this.$nextTick(() => {
let childContext = this.$refs.objectFrame.getSelectionContext();
childContext.item = domainObject;
childContext.layoutItem = this.item;
childContext.index = this.index;
this.context = childContext;
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.immediatelySelect || this.initSelect);
delete this.immediatelySelect;
let reference = this.$refs.objectFrame;
if (reference) {
let childContext = this.$refs.objectFrame.getSelectionContext();
childContext.item = domainObject;
childContext.layoutItem = this.item;
childContext.index = this.index;
this.context = childContext;
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.immediatelySelect || this.initSelect);
delete this.immediatelySelect;
}
});
}
}

View File

@ -30,18 +30,15 @@
>
<div
v-if="domainObject"
class="u-style-receiver c-telemetry-view"
:class="{
styleClass,
'is-missing': domainObject.status === 'missing'
}"
class="c-telemetry-view"
:class="[statusClass]"
:style="styleObject"
:data-font-size="item.fontSize"
:data-font="item.font"
@contextmenu.prevent="showContextMenu"
>
<div class="is-missing__indicator"
title="This item is missing"
<div class="is-status__indicator"
title="This item is missing or suspect"
></div>
<div
v-if="showLabel"
@ -76,10 +73,11 @@
import LayoutFrame from './LayoutFrame.vue';
import printj from 'printj';
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js';
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
const DEFAULT_POSITION = [1, 1];
const CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
const CONTEXT_MENU_ACTIONS = ['copyToClipboard', 'copyToNotebook', 'viewHistoricalData'];
export default {
makeDefinition(openmct, gridSize, domainObject, position) {
@ -129,13 +127,18 @@ export default {
},
data() {
return {
currentObjectPath: undefined,
datum: undefined,
formats: undefined,
domainObject: undefined,
currentObjectPath: undefined
formats: undefined,
viewKey: `alphanumeric-format-${Math.random()}`,
status: ''
};
},
computed: {
statusClass() {
return (this.status) ? `is-status--${this.status}` : '';
},
showLabel() {
let displayMode = this.item.displayMode;
@ -213,9 +216,13 @@ export default {
this.openmct.objects.get(this.item.identifier)
.then(this.setObject);
this.openmct.time.on("bounds", this.refreshData);
this.status = this.openmct.status.get(this.item.identifier);
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
},
destroyed() {
this.removeSubscription();
this.removeStatusListener();
if (this.removeSelectable) {
this.removeSelectable();
@ -224,6 +231,18 @@ export default {
this.openmct.time.off("bounds", this.refreshData);
},
methods: {
getViewContext() {
return {
getViewKey: () => this.viewKey,
formattedValueForCopy: this.formattedValueForCopy
};
},
formattedValueForCopy() {
const timeFormatterKey = this.openmct.time.timeSystem().key;
const timeFormatter = this.formats[timeFormatterKey];
return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue} ${this.unit}`;
},
requestHistoricalData() {
let bounds = this.openmct.time.bounds();
let options = {
@ -261,6 +280,16 @@ export default {
this.requestHistoricalData(this.domainObject);
}
},
getView() {
return {
getViewContext() {
return {
viewHistoricalData: true,
skipCache: true
};
}
};
},
setObject(domainObject) {
this.domainObject = domainObject;
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
@ -284,12 +313,40 @@ export default {
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.immediatelySelect || this.initSelect);
delete this.immediatelySelect;
let allActions = this.openmct.actions.get(this.currentObjectPath, this.getView());
this.applicableActions = CONTEXT_MENU_ACTIONS.map(actionKey => {
return allActions[actionKey];
});
},
updateTelemetryFormat(format) {
this.$emit('formatChanged', this.item, format);
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
async getContextMenuActions() {
const defaultNotebook = getDefaultNotebook();
const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
const actionsObject = this.openmct.actions.get(this.currentObjectPath, this.getViewContext(), { viewHistoricalData: true }).applicableActions;
let applicableActionKeys = Object.keys(actionsObject)
.filter(key => {
const isCopyToNotebook = actionsObject[key].key === 'copyToNotebook';
if (defaultNotebook && isCopyToNotebook) {
const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
actionsObject[key].name = `Copy to Notebook ${defaultPath}`;
}
return CONTEXT_MENU_ACTIONS.includes(actionsObject[key].key);
});
return applicableActionKeys.map(key => actionsObject[key]);
},
async showContextMenu(event) {
const contextMenuActions = await this.getContextMenuActions();
this.openmct.menus.showMenu(event.x, event.y, contextMenuActions);
},
setStatus(status) {
this.status = status;
}
}
};

View File

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

View File

@ -26,9 +26,12 @@ import objectUtils from 'objectUtils';
import DisplayLayoutType from './DisplayLayoutType.js';
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
import CopyToClipboardAction from './actions/CopyToClipboardAction';
export default function DisplayLayoutPlugin(options) {
return function (openmct) {
openmct.actions.register(new CopyToClipboardAction(openmct));
openmct.objectViews.addProvider({
key: 'layout.view',
canView: function (domainObject) {

View File

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

View File

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

View File

@ -11,6 +11,8 @@
body.desktop & {
flex-flow: row wrap;
align-content: flex-start;
&__item {
height: $gridItemDesk;
width: $gridItemDesk;
@ -41,7 +43,7 @@
}
}
&.is-missing {
&.is-status--missing {
@include isMissing();
[class*='__type-icon'],

View File

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

View File

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

View 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();
}
};

View File

@ -25,6 +25,8 @@ export default class GoToOriginalAction {
this.name = 'Go To Original';
this.key = 'goToOriginal';
this.description = 'Go to the original unlinked instance of this object';
this.group = 'action';
this.priority = 4;
this._openmct = openmct;
}

View File

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

View File

@ -1,8 +1,8 @@
.c-imagery {
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: hidden;
height: 100%;
&:focus {
outline: none;
@ -19,14 +19,10 @@
}
&__main-image {
&__bg,
&__image {
height: 100%;
}
&__bg {
background-color: $colorPlotBg;
border: 1px solid transparent;
flex: 1 1 auto;
&.unnsynced{
@include sUnsynced();
@ -34,6 +30,7 @@
}
&__image {
@include abs(); // Safari fix
background-position: center;
background-repeat: no-repeat;
background-size: contain;

View File

@ -28,6 +28,8 @@ export default class NewFolderAction {
this.key = 'newFolder';
this.description = 'Create a new folder';
this.cssClass = 'icon-folder-new';
this.group = "action";
this.priority = 9;
this._openmct = openmct;
this._dialogForm = {

View File

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

View File

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

View 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;
}
}

View File

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

View File

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

View File

@ -4,7 +4,7 @@
<div class="l-browse-bar__start">
<div class="l-browse-bar__object-name--w">
<div class="l-browse-bar__object-name c-object-label">
<div class="c-object-label__type-icon icon-notebook"></div>
<div class="c-object-label__type-icon icon-camera"></div>
<div class="c-object-label__name">
Notebook Snapshots
<span v-if="snapshots.length"

View File

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

View File

@ -1,3 +1,4 @@
import CopyToNotebookAction from './actions/CopyToNotebookAction';
import Notebook from './components/Notebook.vue';
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
import SnapshotContainer from './snapshot-container';
@ -13,6 +14,8 @@ export default function NotebookPlugin() {
installed = true;
openmct.actions.register(new CopyToNotebookAction(openmct));
const notebookType = {
name: 'Notebook',
description: 'Create and save timestamped notes with embedded object snapshots.',

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, createMouseEvent, resetApplicationState } from 'utils/testing';
import { createOpenMct, resetApplicationState } from 'utils/testing';
import NotebookPlugin from './plugin';
import Vue from 'vue';
@ -133,90 +133,4 @@ describe("Notebook plugin:", () => {
expect(hasMajorElements).toBe(true);
});
});
describe("Notebook Snapshots view:", () => {
let snapshotIndicator;
let drawerElement;
function clickSnapshotIndicator() {
const indicator = element.querySelector('.icon-notebook');
const button = indicator.querySelector('button');
const clickEvent = createMouseEvent('click');
button.dispatchEvent(clickEvent);
}
beforeAll(() => {
snapshotIndicator = openmct.indicators.indicatorObjects
.find(indicator => indicator.key === 'notebook-snapshot-indicator').element;
element.append(snapshotIndicator);
return Vue.nextTick();
});
afterAll(() => {
snapshotIndicator.remove();
if (drawerElement) {
drawerElement.remove();
}
});
beforeEach(() => {
drawerElement = document.querySelector('.l-shell__drawer');
});
afterEach(() => {
if (drawerElement) {
drawerElement.classList.remove('is-expanded');
}
});
it("has Snapshots indicator", () => {
const hasSnapshotIndicator = snapshotIndicator !== null && snapshotIndicator !== undefined;
expect(hasSnapshotIndicator).toBe(true);
});
it("snapshots container has class isExpanded", () => {
let classes = drawerElement.classList;
const isExpandedBefore = classes.contains('is-expanded');
clickSnapshotIndicator();
classes = drawerElement.classList;
const isExpandedAfterFirstClick = classes.contains('is-expanded');
const success = isExpandedBefore === false
&& isExpandedAfterFirstClick === true;
expect(success).toBe(true);
});
it("snapshots container does not have class isExpanded", () => {
let classes = drawerElement.classList;
const isExpandedBefore = classes.contains('is-expanded');
clickSnapshotIndicator();
classes = drawerElement.classList;
const isExpandedAfterFirstClick = classes.contains('is-expanded');
clickSnapshotIndicator();
classes = drawerElement.classList;
const isExpandedAfterSecondClick = classes.contains('is-expanded');
const success = isExpandedBefore === false
&& isExpandedAfterFirstClick === true
&& isExpandedAfterSecondClick === false;
expect(success).toBe(true);
});
it("show notebook snapshots container text", () => {
clickSnapshotIndicator();
const notebookSnapshots = drawerElement.querySelector('.l-browse-bar__object-name');
const snapshotsText = notebookSnapshots.textContent.trim();
expect(snapshotsText).toBe('Notebook Snapshots');
});
});
});

View File

@ -49,7 +49,7 @@ export default class Snapshot {
.then(domainObject => {
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
const defaultPath = `${domainObject.name} > ${notebookStorage.section.name} > ${notebookStorage.page.name}`;
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
const msg = `Saved to Notebook ${defaultPath}`;
this._showNotification(msg);
});

View File

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

View File

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

View File

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

View File

@ -25,6 +25,8 @@ export default class RemoveAction {
this.key = 'remove';
this.description = 'Remove this object from its containing object.';
this.cssClass = "icon-trash";
this.group = "action";
this.priority = 1;
this.openmct = openmct;
}
@ -103,6 +105,16 @@ export default class RemoveAction {
let parentType = parent && this.openmct.types.get(parent.type);
let child = objectPath[0];
let locked = child.locked ? child.locked : parent && parent.locked;
let isEditing = this.openmct.editor.isEditing();
if (isEditing) {
let currentItemInView = this.openmct.router.path[0];
let domainObject = objectPath[0];
if (this.openmct.objects.areIdsEqual(currentItemInView.identifier, domainObject.identifier)) {
return false;
}
}
if (locked) {
return false;

View File

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

View File

@ -29,13 +29,13 @@
@click="showTab(tab, index)"
>
<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"
:class="tab.type.definition.cssClass"
>
<span class="is-missing__indicator"
title="This item is missing"
<span class="is-status__indicator"
title="This item is missing or suspect"
></span>
</div>
<span class="c-button__label c-object-label__name">{{ tab.domainObject.name }}</span>
@ -192,8 +192,10 @@ export default {
},
addItem(domainObject) {
let type = this.openmct.types.get(domainObject.type) || unknownObjectType;
let status = this.openmct.status.get(domainObject.identifier);
let tabItem = {
domainObject,
status,
type: type,
key: this.openmct.objects.makeKeyString(domainObject.identifier)
};

View File

@ -180,7 +180,6 @@ define([
processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) {
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
this.boundedRows.add(telemetryRows);
this.emit('historical-rows-processed');
}
/**

View File

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

View File

@ -54,15 +54,13 @@ define([
view(domainObject, objectPath) {
let table = new TelemetryTable(domainObject, openmct);
let component;
let markingProp = {
enable: true,
useAlternateControlBar: false,
rowName: '',
rowNamePlural: ''
};
return {
const view = {
show: function (element, editMode) {
component = new Vue({
el: element,
@ -72,7 +70,8 @@ define([
data() {
return {
isEditing: editMode,
markingProp
markingProp,
view
};
},
provide: {
@ -80,7 +79,7 @@ define([
table,
objectPath
},
template: '<table-component :isEditing="isEditing" :marking="markingProp"/>'
template: '<table-component ref="tableComponent" :isEditing="isEditing" :marking="markingProp" :view="view"/>'
});
},
onEditModeChange(editMode) {
@ -89,11 +88,22 @@ define([
onClearData() {
table.clearData();
},
getViewContext() {
if (component) {
return component.$refs.tableComponent.getViewContext();
} else {
return {
type: 'telemetry-table'
};
}
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
return view;
},
priority() {
return 1;

View 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;

View File

@ -102,7 +102,17 @@ export default {
selectable[columnKeys] = this.row.columns[columnKeys].selectable;
return selectable;
}, {})
}, {}),
actionsViewContext: {
getViewContext: () => {
return {
viewHistoricalData: true,
viewDatumAction: true,
getDatum: this.getDatum,
skipCache: true
};
}
}
};
},
computed: {
@ -170,14 +180,24 @@ export default {
event.stopPropagation();
}
},
getDatum() {
return this.row.fullDatum;
},
showContextMenu: function (event) {
event.preventDefault();
this.markRow(event);
this.row.getContextualDomainObject(this.openmct, this.row.objectKeyString).then(domainObject => {
let contextualObjectPath = this.objectPath.slice();
contextualObjectPath.unshift(domainObject);
this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions());
let allActions = this.openmct.actions.get(contextualObjectPath, this.actionsViewContext);
let applicableActions = this.row.getContextMenuActions().map(key => allActions[key]);
if (applicableActions.length) {
this.openmct.menus.showMenu(event.x, event.y, applicableActions);
}
});
}
}

View File

@ -23,8 +23,7 @@
<div class="c-table-wrapper"
:class="{ 'is-paused': paused }"
>
<!-- main contolbar start-->
<div v-if="!marking.useAlternateControlBar"
<div v-if="enableLegacyToolbar"
class="c-table-control-bar c-control-bar"
>
<button
@ -94,7 +93,6 @@
<slot name="buttons"></slot>
</div>
<!-- main controlbar end -->
<!-- alternate controlbar start -->
<div v-if="marking.useAlternateControlBar"
@ -113,11 +111,11 @@
<button
:class="{'hide-nice': !markedRows.length}"
class="c-button icon-x labeled"
class="c-icon-button icon-x labeled"
title="Deselect All"
@click="unmarkAllRows()"
>
<span class="c-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span>
<span class="c-icon-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span>
</button>
<slot name="buttons"></slot>
@ -301,12 +299,12 @@ export default {
default: true
},
allowFiltering: {
'type': Boolean,
'default': true
type: Boolean,
default: true
},
allowSorting: {
'type': Boolean,
'default': true
type: Boolean,
default: true
},
marking: {
type: Object,
@ -319,6 +317,17 @@ export default {
rowNamePlural: ""
};
}
},
enableLegacyToolbar: {
type: Boolean,
default: false
},
view: {
type: Object,
required: false,
default() {
return {};
}
}
},
data() {
@ -394,6 +403,40 @@ export default {
markedRows: {
handler(newVal, oldVal) {
this.$emit('marked-rows-updated', newVal, oldVal);
if (this.viewActionsCollection) {
if (newVal.length > 0) {
this.viewActionsCollection.enable(['export-csv-marked', 'unmark-all-rows']);
} else if (newVal.length === 0) {
this.viewActionsCollection.disable(['export-csv-marked', 'unmark-all-rows']);
}
}
}
},
paused: {
handler(newVal) {
if (this.viewActionsCollection) {
if (newVal) {
this.viewActionsCollection.hide(['pause-data']);
this.viewActionsCollection.show(['play-data']);
} else {
this.viewActionsCollection.hide(['play-data']);
this.viewActionsCollection.show(['pause-data']);
}
}
}
},
isAutosizeEnabled: {
handler(newVal) {
if (this.viewActionsCollection) {
if (newVal) {
this.viewActionsCollection.show(['expand-columns']);
this.viewActionsCollection.hide(['autosize-columns']);
} else {
this.viewActionsCollection.show(['autosize-columns']);
this.viewActionsCollection.hide(['expand-columns']);
}
}
}
}
},
@ -406,6 +449,11 @@ export default {
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
this.scroll = _.throttle(this.scroll, 100);
if (!this.marking.useAlternateControlBar && !this.enableLegacyToolbar) {
this.viewActionsCollection = this.openmct.actions.get(this.objectPath, this.view);
this.initializeViewActions();
}
this.table.on('object-added', this.addObject);
this.table.on('object-removed', this.removeObject);
this.table.on('outstanding-requests', this.outstandingRequests);
@ -846,7 +894,7 @@ export default {
for (let i = firstRowIndex; i <= lastRowIndex; i++) {
let row = allRows[i];
row.marked = true;
this.$set(row, 'marked', true);
if (row !== baseRow) {
this.markedRows.push(row);
@ -908,6 +956,40 @@ export default {
this.$nextTick().then(this.calculateColumnWidths);
},
getViewContext() {
return {
type: 'telemetry-table',
exportAllDataAsCSV: this.exportAllDataAsCSV,
exportMarkedRows: this.exportMarkedRows,
unmarkAllRows: this.unmarkAllRows,
togglePauseByButton: this.togglePauseByButton,
expandColumns: this.recalculateColumnWidths,
autosizeColumns: this.autosizeColumns
};
},
initializeViewActions() {
if (this.markedRows.length > 0) {
this.viewActionsCollection.enable(['export-csv-marked', 'unmark-all-rows']);
} else if (this.markedRows.length === 0) {
this.viewActionsCollection.disable(['export-csv-marked', 'unmark-all-rows']);
}
if (this.paused) {
this.viewActionsCollection.hide(['pause-data']);
this.viewActionsCollection.show(['play-data']);
} else {
this.viewActionsCollection.hide(['play-data']);
this.viewActionsCollection.show(['pause-data']);
}
if (this.isAutosizeEnabled) {
this.viewActionsCollection.show(['expand-columns']);
this.viewActionsCollection.hide(['autosize-columns']);
} else {
this.viewActionsCollection.show(['autosize-columns']);
this.viewActionsCollection.hide(['expand-columns']);
}
},
setRowHeight(height) {
this.rowHeight = height;
this.setHeight();

View File

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

View File

@ -168,6 +168,8 @@ describe("the plugin", () => {
return telemetryPromise;
});
openmct.router.path = [testTelemetryObject];
applicableViews = openmct.objectViews.get(testTelemetryObject);
tableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'table');
tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);

View 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;
}
}

View 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>

View 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;
}
}
}

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

View 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();
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
//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 ;
text-shadow: $colorBodyBg 0 0 2px;
color: $colorAlert;
font-family: symbolsfont;
&:before {
content: $glyph-icon-alert-triangle;
&[class^='is-status'] .is-status__indicator,
[class^='is-status'] .is-status__indicator {
display: block !important;
}
}
@if $absPos {
.is-missing__indicator {
@if $absPos {
position: absolute;
z-index: 3;
}
}
}
&.is-missing .is-missing__indicator,
.is-missing .is-missing__indicator { display: block !important; }
@mixin isMissing($absPos: false) {
@include isStatus($absPos);
.is-status__indicator:before {
color: $colorAlert;
content: $glyph-icon-alert-triangle;
}
}
@mixin isSuspect($absPos: false) {
@include isStatus($absPos);
.is-status__indicator:before {
color: $colorWarningLo;
content: $glyph-icon-alert-rect;
}
}
@mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
@ -502,8 +515,8 @@
}
@mixin cClickIconButtonLayout() {
$pLR: 4px;
$pTB: 4px;
$pLR: 5px;
$pTB: 5px;
padding: $pTB $pLR;
&:before,
@ -522,6 +535,7 @@
@include cControl();
@include cClickIconButtonLayout();
background: none;
color: $colorClickIconButton;
box-shadow: none;
cursor: pointer;
transition: $transOut;
@ -530,7 +544,8 @@
@include hover() {
transition: $transIn;
background: $colorClickIconButtonBgHov;
color: $colorClickIconButtonFgHov;
//color: $colorClickIconButtonFgHov;
filter: $filterHov;
}
&[class*="--major"] {

View File

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

View File

@ -25,6 +25,7 @@
$headerFontSize: 1.3em;
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: hidden;
height: 100%;

View File

@ -27,6 +27,7 @@
@import "../plugins/timeConductor/conductor-mode-icon.scss";
@import "../plugins/timeConductor/date-picker.scss";
@import "../plugins/timeline/timeline-axis.scss";
@import "../plugins/viewDatumAction/components/metadata-list.scss";
@import "../ui/components/object-frame.scss";
@import "../ui/components/object-label.scss";
@import "../ui/components/progress-bar.scss";
@ -47,3 +48,7 @@
@import "../ui/toolbar/components/toolbar-checkbox.scss";
@import "./notebook.scss";
@import "../plugins/notebook/components/sidebar.scss";
#splash-screen {
display: none;
}

View File

@ -21,44 +21,72 @@
*****************************************************************************/
<template>
<div
class="c-so-view has-local-controls"
:class="{
'c-so-view--no-frame': !hasFrame,
'has-complex-content': complexContent,
'is-missing': domainObject.status === 'missing'
}"
class="c-so-view"
:class="[
'c-so-view--' + domainObject.type,
{
'c-so-view--no-frame': !hasFrame,
'has-complex-content': complexContent,
'is-missing': domainObject.status === 'missing'
}
]"
>
<div class="c-so-view__header">
<div
class="c-so-view__header"
>
<div class="c-object-label"
:class="{
classList,
'is-missing': domainObject.status === 'missing'
}"
:class="[ statusClass ]"
>
<div class="c-object-label__type-icon"
:class="cssClass"
>
<span class="is-missing__indicator"
title="This item is missing"
<span class="is-status__indicator"
title="This item is missing or suspect"
></span>
</div>
<div class="c-object-label__name">
{{ domainObject && domainObject.name }}
</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 class="c-so-view__local-controls c-so-view__view-large h-local-controls c-local-controls--show-on-hover">
<button
class="c-button icon-expand"
title="View Large"
@click="expand"
></button>
</div>
<div class="is-missing__indicator"
title="This item is missing"
<div class="is-status__indicator"
title="This item is missing or suspect"
></div>
<object-view
ref="objectView"
@ -68,13 +96,13 @@
:object-path="objectPath"
:layout-font-size="layoutFontSize"
:layout-font="layoutFont"
@change-provider="setViewProvider"
/>
</div>
</template>
<script>
import ObjectView from './ObjectView.vue';
import ContextMenuDropDown from './contextMenuDropDown.vue';
import PreviewHeader from '@/ui/preview/preview-header.vue';
import Vue from 'vue';
@ -89,8 +117,7 @@ const SIMPLE_CONTENT_TYPES = [
export default {
inject: ['openmct'],
components: {
ObjectView,
ContextMenuDropDown
ObjectView
},
props: {
domainObject: {
@ -117,22 +144,37 @@ export default {
},
data() {
let objectType = this.openmct.types.get(this.domainObject.type);
let cssClass = objectType && objectType.definition ? objectType.definition.cssClass : 'icon-object-unknown';
let complexContent = !SIMPLE_CONTENT_TYPES.includes(this.domainObject.type);
let viewProvider = {};
let statusBarItems = {};
return {
cssClass,
complexContent
complexContent,
viewProvider,
statusBarItems,
status: ''
};
},
computed: {
classList() {
const classList = this.domainObject.classList;
if (!classList || !classList.length) {
return '';
}
statusClass() {
return (this.status) ? `is-status--${this.status}` : '';
}
},
mounted() {
this.status = this.openmct.status.get(this.domainObject.identifier);
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
},
beforeDestroy() {
this.removeStatusListener();
return classList.join(' ');
if (this.actionCollection) {
this.unlistenToActionCollection();
}
},
methods: {
@ -182,6 +224,48 @@ export default {
},
getSelectionContext() {
return this.$refs.objectView.getSelectionContext();
},
setViewProvider(provider) {
this.viewProvider = provider;
this.initializeStatusBarItems();
},
initializeStatusBarItems() {
if (this.actionCollection) {
this.unlistenToActionCollection();
}
if (this.viewProvider) {
this.actionCollection = this.openmct.actions.get(this.objectPath, this.viewProvider);
this.actionCollection.on('update', this.updateActionItems);
this.updateActionItems(this.actionCollection.applicableActions);
} else {
this.statusBarItems = [];
this.menuActionItems = [];
}
},
unlistenToActionCollection() {
this.actionCollection.off('update', this.updateActionItems);
this.actionCollection.destroy();
delete this.actionCollection;
},
updateActionItems(actionItems) {
this.statusBarItems = this.actionCollection.getStatusBarActions();
this.menuActionItems = this.actionCollection.getVisibleActions();
},
showMenuItems(event) {
let actions;
if (this.menuActionItems.length) {
actions = this.menuActionItems;
} else {
actions = this.openmct.actions.get(this.objectPath);
}
let sortedActions = this.openmct.actions._groupAndSortActions(actions);
this.openmct.menus.showMenu(event.x, event.y, sortedActions);
},
setStatus(status) {
this.status = status;
}
}
};

View File

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

View File

@ -214,6 +214,7 @@ export default {
this.$el, this.getSelectionContext(), true);
}
this.$emit('change-provider', this.currentView);
this.openmct.objectViews.on('clearData', this.clearData);
},
show(object, viewKey, immediatelySelect, currentObjectPath) {

View File

@ -2,20 +2,21 @@
display: flex;
flex-direction: column;
&.is-missing {
border: $borderMissing;
}
/*************************** HEADER */
&__header {
flex: 0 0 auto;
display: flex;
font-size: 1.05em;
justify-content: space-between;
align-items: center;
margin-bottom: $interiorMarginSm;
padding: 1px 2px;
padding: 3px;
.c-object-label {
font-size: 1.05em;
&__type-icon {
opacity: $objectLabelTypeIconOpacity;
}
&__name {
filter: $objectLabelNameFilter;
}
@ -31,48 +32,126 @@
}
}
&--no-frame {
> .c-so-view__header {
display: none;
/*************************** FRAME CONTROLS */
&__frame-controls {
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);
.is-missing__indicator {
.is-status__indicator {
top: $interiorMargin;
left: $interiorMargin;
}
}
}
&__local-controls {
// View Large button
box-shadow: $colorLocalControlOvrBg 0 0 0 2px;
position: absolute;
top: $interiorMargin; right: $interiorMargin;
z-index: 10;
/*************************** OBJECT VIEW */
&__object-view {
flex: 1 1 auto;
height: 0; // Chrome 73 overflow bug fix
overflow: auto;
.u-fills-container {
// Expand component types that fill a container
@include abs();
}
}
.c-click-icon,
.c-button {
.c-button,
.c-icon-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;
padding: 5px;
}
&.has-complex-content {
> .c-so-view__view-large { display: block; }
}
&.is-status--missing {
border: $borderMissing;
}
&__object-view {
display: flex;
flex: 1 1 auto;
height: 0; // Chrome 73 overflow bug fix
overflow: auto;
.u-fills-container {
@ -84,6 +163,6 @@
.l-angular-ov-wrapper {
// This element is the recipient for object styling; cannot be display: contents
height: 100%;
flex: 1 1 auto;
overflow: hidden;
}

View File

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

View File

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

View File

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

View File

@ -9,16 +9,13 @@
></button>
<div
class="l-browse-bar__object-name--w c-object-label"
:class="{
classList,
'is-missing': domainObject.status === 'missing'
}"
:class="[statusClass]"
>
<div class="c-object-label__type-icon"
:class="type.cssClass"
>
<span class="is-missing__indicator"
title="This item is missing"
<span class="is-status__indicator"
title="This item is missing or suspect"
></span>
</div>
<span
@ -31,10 +28,6 @@
{{ domainObject.name }}
</span>
</div>
<div
class="l-browse-bar__context-actions c-disclosure-button"
@click.prevent.stop="showContextMenu"
></div>
</div>
<div class="l-browse-bar__end">
@ -42,7 +35,6 @@
v-if="!isEditing"
:current-view="currentView"
:views="views"
@setView="setView"
/>
<!-- Action buttons -->
<NotebookMenuSwitcher v-if="notebookEnabled"
@ -52,15 +44,24 @@
/>
<div class="l-browse-bar__actions">
<button
v-if="isViewEditable && !isEditing"
:title="lockedOrUnlocked"
v-for="(item, index) in statusBarItems"
:key="index"
class="c-button"
:class="item.cssClass"
@click="item.callBack"
>
</button>
<button
v-if="isViewEditable & !isEditing"
:title="lockedOrUnlockedTitle"
:class="{
'icon-lock c-button--caution': domainObject.locked,
'icon-unlocked': !domainObject.locked
'c-button icon-lock': domainObject.locked,
'c-icon-button icon-unlocked': !domainObject.locked
}"
@click="toggleLock(!domainObject.locked)"
></button>
<button
v-if="isViewEditable && !isEditing && !domainObject.locked"
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
@ -106,6 +107,11 @@
title="Cancel Editing"
@click="promptUserandCancelEditing()"
></button>
<button
class="l-browse-bar__actions c-icon-button icon-3-dots"
title="More options"
@click.prevent.stop="showMenuItems($event)"
></button>
</div>
</div>
</div>
@ -123,6 +129,14 @@ export default {
NotebookMenuSwitcher,
ViewSwitcher
},
props: {
viewProvider: {
type: Object,
default: () => {
return {};
}
}
},
data: function () {
return {
notebookTypes: [],
@ -131,17 +145,14 @@ export default {
domainObject: PLACEHOLDER_OBJECT,
viewKey: undefined,
isEditing: this.openmct.editor.isEditing(),
notebookEnabled: this.openmct.types.get('notebook')
notebookEnabled: this.openmct.types.get('notebook'),
statusBarItems: [],
status: ''
};
},
computed: {
classList() {
const classList = this.domainObject.classList;
if (!classList || !classList.length) {
return '';
}
return classList.join(' ');
statusClass() {
return (this.status) ? `is-status--${this.status}` : '';
},
currentView() {
return this.views.filter(v => v.key === this.viewKey)[0] || {};
@ -155,7 +166,10 @@ export default {
return {
key: p.key,
cssClass: p.cssClass,
name: p.name
name: p.name,
callBack: () => {
return this.setView({key: p.key});
}
};
});
},
@ -187,7 +201,7 @@ export default {
return false;
},
lockedOrUnlocked() {
lockedOrUnlockedTitle() {
if (this.domainObject.locked) {
return 'Locked for editing - click to unlock.';
} else {
@ -204,6 +218,28 @@ export default {
this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
this.domainObject = domainObject;
});
if (this.removeStatusListener) {
this.removeStatusListener();
}
this.status = this.openmct.status.get(this.domainObject.identifier, this.setStatus);
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
},
viewProvider(viewProvider) {
if (this.actionCollection) {
this.unlistenToActionCollection();
}
if (viewProvider) {
this.actionCollection = this.openmct.actions.get(this.openmct.router.path, viewProvider);
this.actionCollection.on('update', this.updateActionItems);
this.updateActionItems(this.actionCollection.applicableActions);
} else {
this.statusBarViewKey = undefined;
this.statusBarItems = [];
this.menuActionItems = [];
}
}
},
mounted: function () {
@ -219,6 +255,14 @@ export default {
this.mutationObserver();
}
if (this.actionCollection) {
this.unlistenToActionCollection();
}
if (this.removeStatusListener) {
this.removeStatusListener();
}
document.removeEventListener('click', this.closeViewAndSaveMenu);
window.removeEventListener('click', this.promptUserbeforeNavigatingAway);
},
@ -300,14 +344,35 @@ export default {
this.openmct.editor.edit();
});
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.openmct.router.path, event.clientX, event.clientY);
},
goToParent() {
window.location.hash = this.parentUrl;
},
updateActionItems(actionItems) {
this.statusBarItems = this.actionCollection.getStatusBarActions();
this.menuActionItems = this.actionCollection.getVisibleActions();
},
showMenuItems(event) {
let actions;
if (this.menuActionItems.length) {
actions = this.menuActionItems;
} else {
actions = this.openmct.actions.get(this.openmct.router.path);
}
let sortedActions = this.openmct.actions._groupAndSortActions(actions);
this.openmct.menus.showMenu(event.x, event.y, sortedActions);
},
unlistenToActionCollection() {
this.actionCollection.off('update', this.updateActionItems);
this.actionCollection.destroy();
delete this.actionCollection;
},
toggleLock(flag) {
this.openmct.objects.mutate(this.domainObject, 'locked', flag);
},
setStatus(status) {
this.status = status;
}
}
};

View File

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

View File

@ -4,36 +4,21 @@
class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
>
<button
class="c-button--menu"
class="c-icon-button c-button--menu"
:class="currentView.cssClass"
title="Change the current view"
@click.stop="toggleViewMenu"
@click.prevent.stop="showMenu"
>
<span class="c-button__label">
<span class="c-icon-button__label">
{{ currentView.name }}
</span>
</button>
<div
v-show="showViewMenu"
class="c-menu"
>
<ul>
<li
v-for="(view, index) in views"
:key="index"
:class="view.cssClass"
:title="view.name"
@click="setView(view)"
>
{{ view.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
inject: ['openmct'],
props: {
currentView: {
type: Object,
@ -44,26 +29,16 @@ export default {
required: true
}
},
data() {
return {
showViewMenu: false
};
},
mounted() {
document.addEventListener('click', this.hideViewMenu);
},
destroyed() {
document.removeEventListener('click', this.hideViewMenu);
},
methods: {
setView(view) {
this.$emit('setView', view);
},
toggleViewMenu() {
this.showViewMenu = !this.showViewMenu;
},
hideViewMenu() {
this.showViewMenu = false;
showMenu() {
const elementBoundingClientRect = this.$el.getBoundingClientRect();
const x = elementBoundingClientRect.x;
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
this.openmct.menus.showMenu(x, y, this.views);
}
}
};

View File

@ -228,8 +228,9 @@
/******************************* MAIN AREA */
&__main-container {
// Wrapper for main views
display: flex;
flex: 1 1 auto !important;
height: 0; // Chrome 73 overflow bug fix
height: 100%; // Chrome 73 overflow bug fix
overflow: auto;
}
@ -332,13 +333,12 @@
}
> * + * {
margin-left: $interiorMargin;
margin-left: $interiorMarginSm;
}
}
&__start {
flex: 1 1 auto;
//margin-right: $interiorMargin;
min-width: 0; // Forces interior to compress when pushed on
[class*='button'] {
@ -357,11 +357,6 @@
&__nav-to-parent-button {
// This is an icon-button
//$p: $interiorMargin;
//margin-right: $interiorMargin;
//padding-left: $p;
//padding-right: $p;
.is-editing & {
display: none;
}
@ -376,12 +371,16 @@
filter: $objectLabelNameFilter;
}
.c-object-label__type-icon {
opacity: $objectLabelTypeIconOpacity;
}
&__object-name--w {
@include headerFont(1.5em);
margin-left: $interiorMarginLg;
min-width: 0;
.is-missing__indicator {
.is-status__indicator {
right: -5px !important;
top: -4px !important;
}

View File

@ -226,7 +226,7 @@ export default {
return this.mainTreeHeight - this.ancestorsHeight;
},
showNoItems() {
return this.visibleItems.length === 0 && !this.activeSearch;
return this.visibleItems.length === 0 && !this.activeSearch && !this.isLoading;
},
showNoSearchResults() {
return this.searchValue && this.searchResultItems.length === 0 && !this.searchLoading;
@ -276,7 +276,9 @@ export default {
}
},
async mounted() {
this.initialize();
this.isLoading = true;
await this.initialize();
let savedPath = this.getStoredTreePath();
let rootComposition = await this.loadRoot();
@ -302,26 +304,15 @@ export default {
destroyed() {
window.removeEventListener('resize', this.handleWindowResize);
this.stopObservingAncestors();
document.removeEventListener('readystatechange', this.setTreeTopMargin);
},
methods: {
initialize() {
async initialize() {
this.searchService = this.openmct.$injector.get('searchService');
window.addEventListener('resize', this.handleWindowResize);
this.readyStateCheck();
this.backwardsCompatibilityCheck();
},
readyStateCheck() {
if (document.readyState !== 'complete') {
document.addEventListener('readystatechange', this.onReadyState);
} else {
this.onReadyState();
}
},
onReadyState() {
if (document.readyState === 'complete') {
this.calculateHeights();
}
await this.calculateHeights();
return;
},
backwardsCompatibilityCheck() {
let oldTreeExpanded = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED__OLD));
@ -781,17 +772,44 @@ export default {
return { height };
},
getElementStyleValue(el, style) {
if (!el) {
return;
}
let styleString = window.getComputedStyle(el)[style];
let index = styleString.indexOf('px');
return Number(styleString.slice(0, index));
},
calculateHeights() {
this.mainTreeTopMargin = this.getElementStyleValue(this.$refs.mainTree, 'marginTop');
this.mainTreeHeight = this.$el.offsetHeight
- this.$refs.search.offsetHeight
- this.mainTreeTopMargin;
this.itemHeight = this.getElementStyleValue(this.$refs.dummyItem, 'height');
const RECHECK = 100;
return new Promise((resolve, reject) => {
let checkHeights = () => {
let treeTopMargin = this.getElementStyleValue(this.$refs.mainTree, 'marginTop');
if (
this.$el
&& this.$refs.search
&& this.$refs.mainTree
&& this.$refs.dummyItem
&& this.$el.offsetHeight !== 0
&& treeTopMargin > 0
) {
this.mainTreeTopMargin = treeTopMargin;
this.mainTreeHeight = this.$el.offsetHeight
- this.$refs.search.offsetHeight
- this.mainTreeTopMargin;
this.itemHeight = this.getElementStyleValue(this.$refs.dummyItem, 'height');
resolve();
} else {
setTimeout(checkHeights, RECHECK);
}
};
checkHeights();
});
}
}
};

View File

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

View File

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

View File

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

View File

@ -32,4 +32,14 @@ export default class ViewHistoricalDataAction extends PreviewAction {
this.cssClass = 'icon-eye-open';
this.hideInDefaultMenu = true;
}
appliesTo(objectPath, view = {}) {
let viewContext = view.getViewContext && view.getViewContext();
if (objectPath.length && viewContext && viewContext.viewHistoricalData) {
return true;
} else {
return false;
}
}
}

View File

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

View File

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

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