Compare commits

...

24 Commits

Author SHA1 Message Date
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
6137700c82 Merge pull request #3092 from nasa/contributor-guide-grammar
Fixes a grammatical issue in the wording of the contributor's guide
2020-06-10 11:06:59 -07:00
91a1b3f31d Fixes a grammatical issue in the wording of the contributor's guide 2020-06-10 10:29:47 -07:00
357b25a76b LAD views should respond to conductor bounds changes (#2946)
Added bounds listener, moved history request to function, checking for race conditions
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-06-09 16:51:32 -07:00
bab53ad9bd Fix static styles bug - adding null checks. (#3084)
* Fix static styles bug - adding null checks. Fixes #3076

* Adds tests for updating conditional and static styles for layout drawing items

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-06-05 15:30:35 -07:00
d0d4579f13 Give users the option to hide table headers (#3085)
* working hide header config

* Simplified Inspector UI layout

* fix lint errors

Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-06-05 15:09:59 -07:00
02b537580c Merge pull request #3057 from nasa/code-standards-update
Updated code standard
2020-06-02 13:31:21 -07:00
7073b0717f Merge branch 'master' into code-standards-update 2020-06-02 13:05:32 -07:00
d6bb1b2a12 Added note about requiring a single return statement. 2020-06-02 13:05:15 -07:00
c256696790 Image view should react to time conductor changes - 2712 (#2934)
Image view reacts to time conductor changes
Fixes #2712
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-06-02 12:27:55 -07:00
d5480e7524 Multiple item conditional styles (#3017)
Conditional Styles for multiple items #3076

* Preview condition styles on selecting that condition or one of it's styles
Do not evaluate conditional styles in edit mode

* Conditions styling tweaked

- Condition match `is-current` styling for browse and edit modes;
- Disallowed pointer events on Conditions to prevent selection in
Inspector when not editing;

* Highlight current condition in conditionSet view

* Addresses review comments.

* Condition matching highlighting tweaked

- Enable match highlighting during Editing;
- Tweaks to `is-current` styling;

* Don't reset the callback on destroy

* Combine multiple and single selection styling of objects

* Fix issue with applying styles in edit mode

* Fix item styles bug

* Remove comment

* Adds back visibility toggle

* Set isEditing on initialization.

* Addresses review comments - removes use of lodash.

Co-authored-by: charlesh88 <charlesh88@gmail.com>
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-06-01 12:40:40 -07:00
ab463e93fe Refactor Notifications to use Vue, and add clear all option (#3068)
[Notifications] Need a clear all notifications option #3006

* create new notifications indicator in vue, and sunset old one

* add notifications list overlay

* working notifications

* add support for progress notifications

* update percentage on mounted

* Markup cleanups in new Vue Notifications files

- Removed unneeded markup and class wrappers;
- Removed unneeded inline styling;

* remove example notifications

* fix lint errors

* make reviewer requested changes, remove old not needed files, add test

* update testTools to testUtils

Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-06-01 11:45:33 -07:00
8363c65312 [Notebook] display bounds change notification only if bounds changed (#3062) Resolves (#3051)
* display bounds change notification only if bounds changed

* avoid empty notification messages.
2020-05-28 12:29:32 -07:00
04598b6cf1 Bug fixes for plots (#3019) (#3069)
* prevent plots from breaking when more than one NaN is received.

* fix y axis label issue

* dont emit a viewport change event when marquee doesnt happen
2020-05-28 09:58:22 -07:00
2fa29124bf Merge branch 'master' into code-standards-update 2020-05-18 11:45:27 -07:00
33faeafa98 New proposed ESLint rules that require no code changes 2020-05-16 16:19:36 -07:00
a40ff07353 Updated guidance on anonymous functions 2020-05-16 16:11:42 -07:00
41dc9c794d Fixed capitalization on CONST 2020-05-16 16:08:43 -07:00
f1faf3965d Changed reference to constructors to classes 2020-05-16 16:07:49 -07:00
da2ecbbcad Fixed formatting issues, removed outdated example code. 2020-05-16 16:06:59 -07:00
32c892fe98 Updated code standards 2020-05-16 15:56:30 -07:00
44 changed files with 2341 additions and 459 deletions

View File

@ -1,3 +1,4 @@
const LEGACY_FILES = ["platform/**", "example/**"];
module.exports = {
"env": {
"browser": true,
@ -70,6 +71,56 @@ module.exports = {
],
"dot-notation": "error",
"indent": ["error", 4],
// https://eslint.org/docs/rules/no-case-declarations
"no-case-declarations": "error",
// https://eslint.org/docs/rules/max-classes-per-file
"max-classes-per-file": ["error", 1],
// https://eslint.org/docs/rules/no-eq-null
"no-eq-null": "error",
// https://eslint.org/docs/rules/no-eval
"no-eval": "error",
// https://eslint.org/docs/rules/no-floating-decimal
"no-floating-decimal": "error",
// https://eslint.org/docs/rules/no-implicit-globals
"no-implicit-globals": "error",
// https://eslint.org/docs/rules/no-implied-eval
"no-implied-eval": "error",
// https://eslint.org/docs/rules/no-lone-blocks
"no-lone-blocks": "error",
// https://eslint.org/docs/rules/no-loop-func
"no-loop-func": "error",
// https://eslint.org/docs/rules/no-new-func
"no-new-func": "error",
// https://eslint.org/docs/rules/no-new-wrappers
"no-new-wrappers": "error",
// https://eslint.org/docs/rules/no-octal-escape
"no-octal-escape": "error",
// https://eslint.org/docs/rules/no-proto
"no-proto": "error",
// https://eslint.org/docs/rules/no-return-await
"no-return-await": "error",
// https://eslint.org/docs/rules/no-script-url
"no-script-url": "error",
// https://eslint.org/docs/rules/no-self-compare
"no-self-compare": "error",
// https://eslint.org/docs/rules/no-sequences
"no-sequences": "error",
// https://eslint.org/docs/rules/no-unmodified-loop-condition
"no-unmodified-loop-condition": "error",
// https://eslint.org/docs/rules/no-useless-call
"no-useless-call": "error",
// https://eslint.org/docs/rules/wrap-iife
"wrap-iife": "error",
// https://eslint.org/docs/rules/no-nested-ternary
"no-nested-ternary": "error",
// https://eslint.org/docs/rules/switch-colon-spacing
"switch-colon-spacing": "error",
// https://eslint.org/docs/rules/no-useless-computed-key
"no-useless-computed-key": "error",
// https://eslint.org/docs/rules/rest-spread-spacing
"rest-spread-spacing": ["error"],
"vue/html-indent": [
"error",
4,
@ -116,6 +167,13 @@ module.exports = {
}
]
}
}, {
"files": LEGACY_FILES,
"rules": {
// https://eslint.org/docs/rules/no-nested-ternary
"no-nested-ternary": "off",
"no-var": "off"
}
}
]
};

View File

@ -131,89 +131,96 @@ changes.
### Code Standards
JavaScript sources in Open MCT must satisfy JSLint under its default
settings. This is verified by the command line build.
JavaScript sources in Open MCT must satisfy the ESLint rules defined in
this repository. This is verified by the command line build.
#### Code Guidelines
JavaScript sources in Open MCT should:
* Use four spaces for indentation. Tabs should not be used.
* Include JSDoc for any exposed API (e.g. public methods, constructors).
* Include non-JSDoc comments as-needed for explaining private variables,
methods, or algorithms when they are non-obvious.
* Define one public class per script, expressed as a constructor function
returned from an AMD-style module.
* Follow “Java-like” naming conventions. These includes:
* Classes should use camel case, first letter capitalized
(e.g. SomeClassName).
* Methods, variables, fields, and function names should use camel case,
first letter lower-case (e.g. someVariableName).
* Constants (variables or fields which are meant to be declared and
initialized statically, and never changed) should use only capital
letters, with underscores between words (e.g. SOME_CONSTANT).
* File names should be the name of the exported class, plus a .js extension
(e.g. SomeClassName.js).
* Avoid anonymous functions, except when functions are short (a few lines)
and/or their inclusion makes sense within the flow of the code
(e.g. as arguments to a forEach call).
* Avoid deep nesting (especially of functions), except where necessary
(e.g. due to closure scope).
* End with a single new-line character.
* Expose public methods by declaring them on the class's prototype.
* Within a given function's scope, do not mix declarations and imperative
code, and present these in the following order:
* First, variable declarations and initialization.
* Second, function declarations.
* Third, imperative statements.
* Finally, the returned value.
The following guidelines are provided for anyone contributing source code to the Open MCT project:
1. Write clean code. Heres a good summary - https://github.com/ryanmcdermott/clean-code-javascript.
1. Include JSDoc for any exposed API (e.g. public methods, classes).
1. Include non-JSDoc comments as-needed for explaining private variables,
methods, or algorithms when they are non-obvious. Otherwise code
should be self-documenting.
1. Classes and Vue components should use camel case, first letter capitalized
(e.g. SomeClassName).
1. Methods, variables, fields, events, and function names should use camelCase,
first letter lower-case (e.g. someVariableName).
1. Source files that export functions should use camelCase, first letter lower-case (eg. testTools.js)
1. Constants (variables or fields which are meant to be declared and
initialized statically, and never changed) should use only capital
letters, with underscores between words (e.g. SOME_CONSTANT). They should always be declared as `const`s
1. File names should be the name of the exported class, plus a .js extension
(e.g. SomeClassName.js).
1. Avoid anonymous functions, except when functions are short (one or two lines)
and their inclusion makes sense within the flow of the code
(e.g. as arguments to a forEach call). Anonymous functions should always be arrow functions.
1. Named functions are preferred over functions assigned to variables.
eg.
```JavaScript
function renameObject(object, newName) {
Object.name = newName;
}
```
is preferable to
```JavaScript
const rename = (object, newName) => {
Object.name = newName;
}
```
1. Avoid deep nesting (especially of functions), except where necessary
(e.g. due to closure scope).
1. End with a single new-line character.
1. Always use ES6 `Class`es and inheritence rather than the pre-ES6 prototypal
pattern.
1. Within a given function's scope, do not mix declarations and imperative
code, and present these in the following order:
* First, variable declarations and initialization.
* Secondly, imperative statements.
* Finally, the returned value. Functions should only have a single return statement.
1. Avoid the use of "magic" values.
eg.
```JavaScript
Const UNAUTHORIZED = 401
if (responseCode === UNAUTHORIZED)
```
is preferable to
```JavaScript
if (responseCode === 401)
```
1. Dont use the ternary operator. Yes it's terse, but there's probably a clearer way of writing it.
1. Test specs should reside alongside the source code they test, not in a separate directory.
1. Organize code by feature, not by type.
eg.
```
- telemetryTable
- row
TableRow.js
TableRowCollection.js
TableRow.vue
- column
TableColumn.js
TableColumn.vue
plugin.js
pluginSpec.js
```
is preferable to
```
- telemetryTable
- components
TableRow.vue
TableColumn.vue
- collections
TableRowCollection.js
TableColumn.js
TableRow.js
plugin.js
pluginSpec.js
```
Deviations from Open MCT code style guidelines require two-party agreement,
typically from the author of the change and its reviewer.
#### Code Example
```js
/*global define*/
/**
* Bundles should declare themselves as namespaces in whichever source
* file is most like the "main point of entry" to the bundle.
* @namespace some/bundle
*/
define(
['./OtherClass'],
function (OtherClass) {
"use strict";
/**
* A summary of how to use this class goes here.
*
* @constructor
* @memberof some/bundle
*/
function ExampleClass() {
}
// Methods which are not intended for external use should
// not have JSDoc (or should be marked @private)
ExampleClass.prototype.privateMethod = function () {
};
/**
* A summary of this method goes here.
* @param {number} n a parameter
* @returns {number} a return value
*/
ExampleClass.prototype.publicMethod = function (n) {
return n * 2;
}
return ExampleClass;
}
);
```
### Test Standards
Automated testing shall occur whenever changes are merged into the main

View File

@ -52,6 +52,7 @@ define([
return {
name: name,
utc: Math.floor(timestamp / 5000) * 5000,
local: Math.floor(timestamp / 5000) * 5000,
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
};
}
@ -78,7 +79,7 @@ define([
},
request: function (domainObject, options) {
var start = options.start;
var end = options.end;
var end = Math.min(options.end, Date.now());
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, domainObject.name));
@ -118,6 +119,14 @@ define([
name: 'Time',
key: 'utc',
format: 'utc',
hints: {
domain: 2
}
},
{
name: 'Local Time',
key: 'local',
format: 'local-format',
hints: {
domain: 1
}

View File

@ -6,6 +6,12 @@
ng-show="ngModel.dialog.messages.length > 1 ||
ngModel.dialog.messages.length == 0">s</span>
</div>
<button
ng-if="ngModel.dialog.topBarButton"
class="c-button c-button--major"
ng-click="ngModel.topBarButton.onClick">
{{ ngModel.topBarButton.label }}
</button>
</div>
<div class="w-messages c-overlay__messages">
<mct-include
@ -16,7 +22,7 @@
<button ng-repeat="dialogAction in ngModel.dialog.actions"
class="c-button c-button--major"
ng-click="dialogAction.action()">
{{dialogAction.label}}
{{ dialogAction.label }}
</button>
</div>
</div>

View File

@ -21,44 +21,15 @@
*****************************************************************************/
define([
"./src/NotificationIndicatorController",
"./src/NotificationIndicator",
"./src/NotificationService",
"./res/notification-indicator.html"
"./src/NotificationService"
], function (
NotificationIndicatorController,
NotificationIndicator,
NotificationService,
notificationIndicatorTemplate
NotificationService
) {
return {
name:"platform/commonUI/notification",
definition: {
"extensions": {
"templates": [
{
"key": "notificationIndicatorTemplate",
"template": notificationIndicatorTemplate
}
],
"controllers": [
{
"key": "NotificationIndicatorController",
"implementation": NotificationIndicatorController,
"depends": [
"$scope",
"openmct",
"dialogService"
]
}
],
"indicators": [
{
"implementation": NotificationIndicator,
"priority": "fallback"
}
],
"services": [
{
"key": "notificationService",

View File

@ -1,8 +0,0 @@
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div ng-show="notifications.length > 0" class="c-indicator c-indicator--clickable s-status-{{highest.severity}} icon-bell"
ng-controller="NotificationIndicatorController">
<span class="label c-indicator__label">
<button ng-click="showNotificationsList()">
{{notifications.length}} Notification<span ng-show="notifications.length > 1">s</span></button>
</span><span class="c-indicator__count">{{notifications.length}}</span>
</div>

View File

@ -1,73 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Provides an indicator that is visible when there are
* banner notifications that have been minimized. Will also indicate
* the number of notifications. Notifications can be viewed by
* clicking on the indicator to launch a dialog showing a list of
* notifications.
* @param $scope
* @param notificationService
* @param dialogService
* @constructor
*/
function NotificationIndicatorController($scope, openmct, dialogService) {
$scope.notifications = openmct.notifications.notifications;
$scope.highest = openmct.notifications.highest;
/**
* Launch a dialog showing a list of current notifications.
*/
$scope.showNotificationsList = function () {
let notificationsList = openmct.notifications.notifications.map(notification => {
if (notification.model.severity === 'alert' || notification.model.severity === 'info') {
notification.model.primaryOption = {
label: 'Dismiss',
callback: () => {
let currentIndex = notificationsList.indexOf(notification);
notification.dismiss();
notificationsList.splice(currentIndex, 1);
}
}
}
return notification;
})
dialogService.getDialogResponse('overlay-message-list', {
dialog: {
title: "Messages",
//Launch the message list dialog with the models
// from the notifications
messages: notificationsList
}
});
};
}
return NotificationIndicatorController;
}
);

View File

@ -1,60 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['../src/NotificationIndicatorController'],
function (NotificationIndicatorController) {
xdescribe("The notification indicator controller ", function () {
var mockNotificationService,
mockScope,
mockDialogService,
controller;
beforeEach(function () {
mockNotificationService = jasmine.createSpy("notificationService");
mockScope = jasmine.createSpy("$scope");
mockDialogService = jasmine.createSpyObj(
"dialogService",
["getDialogResponse","dismiss"]
);
mockNotificationService.highest = {
severity: "error"
};
controller = new NotificationIndicatorController(mockScope, mockNotificationService, mockDialogService);
});
it("exposes the highest notification severity to the template", function () {
expect(mockScope.highest).toBeTruthy();
expect(mockScope.highest.severity).toBe("error");
});
it("invokes the dialog service to show list of messages", function () {
expect(mockScope.showNotificationsList).toBeDefined();
mockScope.showNotificationsList();
expect(mockDialogService.getDialogResponse).toHaveBeenCalled();
expect(mockDialogService.getDialogResponse.calls.mostRecent().args[0]).toBe('overlay-message-list');
expect(mockDialogService.getDialogResponse.calls.mostRecent().args[1].dialog).toBeDefined();
});
});
}
);

View File

@ -0,0 +1,621 @@
{
"header": {
"event": "Allocation failed - JavaScript heap out of memory",
"location": "OnFatalError",
"filename": "report.20200527.134750.93992.001.json",
"dumpEventTime": "2020-05-27T13:47:50Z",
"dumpEventTimeStamp": "1590612470877",
"processId": 93992,
"commandLine": [
"node",
"/Users/dtailor/Desktop/openmct/node_modules/.bin/karma",
"start",
"--single-run"
],
"nodejsVersion": "v11.9.0",
"wordSize": 64,
"componentVersions": {
"node": "11.9.0",
"v8": "7.0.276.38-node.16",
"uv": "1.25.0",
"zlib": "1.2.11",
"brotli": "1.0.7",
"ares": "1.15.0",
"modules": "67",
"nghttp2": "1.34.0",
"napi": "4",
"llhttp": "1.0.1",
"http_parser": "2.8.0",
"openssl": "1.1.1a",
"cldr": "34.0",
"icu": "63.1",
"tz": "2018e",
"unicode": "11.0",
"arch": "x64",
"platform": "darwin",
"release": "node"
},
"osVersion": "Darwin 18.7.0 Darwin Kernel Version 18.7.0: Thu Jan 23 06:52:12 PST 2020; root:xnu-4903.278.25~1/RELEASE_X86_64",
"machine": "Darwin 18.7.0 Darwin Kernel Version 18.7.0: Thu Jan 23 06:52:12 PST 2020; root:xnu-4903.278.25~1/RELEASE_X86_64tailor x86_64"
},
"javascriptStack": {
"message": "No stack.",
"stack": [
"Unavailable."
]
},
"nativeStack": [
" [pc=0x10013090e] report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, v8::Local<v8::String>) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x100063744] node::OnFatalError(char const*, char const*) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1001a8c47] v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1001a8be4] v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005add42] v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005b0273] v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005ac7a8] v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005aa965] v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005b720c] v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1005b728f] v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x100586484] v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationSpace) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x1008389a4] v8::internal::Runtime_AllocateInNewSpace(int, v8::internal::Object**, v8::internal::Isolate*) [/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node]",
" [pc=0x14acfddcfc7d] "
],
"javascriptHeap": {
"totalMemory": 1479229440,
"totalCommittedMemory": 1477309024,
"usedMemory": 1445511032,
"availableMemory": 50296592,
"memoryLimit": 1526909922,
"heapSpaces": {
"read_only_space": {
"memorySize": 524288,
"committedMemory": 42224,
"capacity": 515584,
"used": 33520,
"available": 482064
},
"new_space": {
"memorySize": 4194304,
"committedMemory": 4194288,
"capacity": 2062336,
"used": 59016,
"available": 2003320
},
"old_space": {
"memorySize": 305860608,
"committedMemory": 305138544,
"capacity": 283264904,
"used": 282942208,
"available": 322696
},
"code_space": {
"memorySize": 6291456,
"committedMemory": 5687328,
"capacity": 5237152,
"used": 5237152,
"available": 0
},
"map_space": {
"memorySize": 5255168,
"committedMemory": 5143024,
"capacity": 2523280,
"used": 2523280,
"available": 0
},
"large_object_space": {
"memorySize": 1157103616,
"committedMemory": 1157103616,
"capacity": 1202204368,
"used": 1154715856,
"available": 47488512
},
"new_large_object_space": {
"memorySize": 0,
"committedMemory": 0,
"capacity": 0,
"used": 0,
"available": 0
}
}
},
"resourceUsage": {
"userCpuSeconds": 43.1616,
"kernelCpuSeconds": 43.1616,
"cpuConsumptionPercent": 5.42705e-06,
"maxRss": 1966080000000,
"pageFaults": {
"IORequired": 245,
"IONotRequired": 832598
},
"fsActivity": {
"reads": 0,
"writes": 0
}
},
"libuv": [
],
"environmentVariables": {
"npm_config_save_dev": "",
"npm_config_legacy_bundling": "",
"npm_config_dry_run": "",
"npm_package_devDependencies_markdown_toc": "^0.11.7",
"npm_config_only": "",
"npm_config_browser": "",
"npm_config_viewer": "man",
"npm_config_commit_hooks": "true",
"npm_package_gitHead": "7126abe7ec1d66d3252f3598fbd6bd27217018bc",
"npm_config_also": "",
"npm_package_scripts_otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
"npm_package_devDependencies_minimist": "^1.1.1",
"npm_config_sign_git_commit": "",
"npm_config_rollback": "true",
"npm_package_devDependencies_fast_sass_loader": "1.4.6",
"TERM_PROGRAM": "Apple_Terminal",
"npm_config_usage": "",
"npm_config_audit": "true",
"npm_package_devDependencies_git_rev_sync": "^1.4.0",
"npm_package_devDependencies_file_loader": "^1.1.11",
"npm_package_devDependencies_d3_selection": "1.3.x",
"NODE": "/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node",
"npm_package_homepage": "https://github.com/nasa/openmct#readme",
"INIT_CWD": "/Users/dtailor/Desktop/openmct",
"NVM_CD_FLAGS": "",
"npm_config_globalignorefile": "/Users/dtailor/.nvm/versions/node/v11.9.0/etc/npmignore",
"npm_package_devDependencies_comma_separated_values": "^3.6.4",
"SHELL": "/bin/bash",
"TERM": "xterm-256color",
"npm_config_init_author_url": "",
"npm_config_shell": "/bin/bash",
"npm_config_maxsockets": "50",
"npm_package_devDependencies_vue_template_compiler": "2.5.6",
"npm_package_devDependencies_style_loader": "^1.0.1",
"npm_package_devDependencies_moment_duration_format": "^2.2.2",
"npm_config_parseable": "",
"npm_config_shrinkwrap": "true",
"npm_config_metrics_registry": "https://registry.npmjs.org/",
"TMPDIR": "/var/folders/ks/ytghmh9x4lj3cchr5km5lhkcb7v9y2/T/",
"npm_config_timing": "",
"npm_config_init_license": "ISC",
"npm_package_scripts_lint": "eslint platform example src --ext .js,.vue openmct.js",
"npm_package_devDependencies_d3_array": "1.2.x",
"Apple_PubSub_Socket_Render": "/private/tmp/com.apple.launchd.PsV6Dfq4Tm/Render",
"npm_config_if_present": "",
"npm_package_devDependencies_concurrently": "^3.6.1",
"TERM_PROGRAM_VERSION": "421.2",
"npm_package_scripts_jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
"npm_config_sign_git_tag": "",
"npm_config_init_author_email": "",
"npm_config_cache_max": "Infinity",
"npm_config_preid": "",
"npm_config_long": "",
"npm_config_local_address": "",
"npm_config_cert": "",
"npm_config_git_tag_version": "true",
"npm_package_devDependencies_exports_loader": "^0.7.0",
"TERM_SESSION_ID": "0630D2FA-BAC2-48D3-A21D-9AB58A79FB14",
"npm_config_noproxy": "",
"npm_config_registry": "https://registry.npmjs.org/",
"npm_config_fetch_retries": "2",
"npm_package_private": "true",
"npm_package_devDependencies_karma_jasmine": "^1.1.2",
"npm_package_repository_url": "git+https://github.com/nasa/openmct.git",
"npm_config_versions": "",
"npm_config_key": "",
"npm_config_message": "%s",
"npm_package_readmeFilename": "README.md",
"npm_package_devDependencies_painterro": "^0.2.65",
"npm_package_scripts_verify": "concurrently 'npm:test' 'npm:lint'",
"npm_package_devDependencies_webpack": "^4.16.2",
"npm_package_devDependencies_eventemitter3": "^1.2.0",
"npm_package_description": "The Open MCT core platform",
"USER": "dtailor",
"NVM_DIR": "/Users/dtailor/.nvm",
"npm_package_license": "Apache-2.0",
"npm_package_scripts_build_dev": "webpack",
"npm_package_devDependencies_webpack_cli": "^3.1.0",
"npm_package_devDependencies_location_bar": "^3.0.1",
"npm_package_devDependencies_jasmine_core": "^3.1.0",
"npm_config_globalconfig": "/Users/dtailor/.nvm/versions/node/v11.9.0/etc/npmrc",
"npm_package_devDependencies_karma": "^2.0.3",
"npm_config_prefer_online": "",
"npm_config_always_auth": "",
"npm_config_logs_max": "10",
"npm_package_devDependencies_angular": "1.7.9",
"SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.JH8E4KgH06/Listeners",
"npm_package_devDependencies_request": "^2.69.0",
"npm_package_devDependencies_eslint": "5.2.0",
"__CF_USER_TEXT_ENCODING": "0x167DA7C2:0x0:0x0",
"npm_execpath": "/Users/dtailor/.nvm/versions/node/v11.9.0/lib/node_modules/npm/bin/npm-cli.js",
"npm_config_global_style": "",
"npm_config_cache_lock_retries": "10",
"npm_config_cafile": "",
"npm_config_update_notifier": "true",
"npm_package_scripts_test_debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"npm_package_devDependencies_glob": ">= 3.0.0",
"npm_config_heading": "npm",
"npm_config_audit_level": "low",
"npm_package_devDependencies_mini_css_extract_plugin": "^0.4.1",
"npm_package_devDependencies_copy_webpack_plugin": "^4.5.2",
"npm_config_read_only": "",
"npm_config_offline": "",
"npm_config_searchlimit": "20",
"npm_config_fetch_retry_mintimeout": "10000",
"npm_package_devDependencies_webpack_dev_middleware": "^3.1.3",
"npm_config_json": "",
"npm_config_access": "",
"npm_config_argv": "{\"remain\":[],\"cooked\":[\"run\",\"test\"],\"original\":[\"run\",\"test\"]}",
"npm_package_scripts_lint_fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
"npm_package_devDependencies_uuid": "^3.3.3",
"npm_package_devDependencies_karma_coverage": "^1.1.2",
"PATH": "/Users/dtailor/.nvm/versions/node/v11.9.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/dtailor/Desktop/openmct/node_modules/.bin:/Users/dtailor/.nvm/versions/node/v11.9.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/dtailor/Desktop/openmct/node_modules/.bin:/Users/dtailor/.nvm/versions/node/v11.9.0/bin:/Users/dtailor/.homebrew/bin:/Users/dtailor/local/bin:/Users/dtailor/.homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Users/dtailor/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/opt/local/bin:/Users/dtailor/.homebrew/bin:/Users/dtailor/.homebrew/bin:/Users/dtailor/.homebrew/bin:/Users/dtailor/local/bin:/Users/dtailor/.homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin",
"npm_config_allow_same_version": "",
"npm_config_https_proxy": "",
"npm_config_engine_strict": "",
"npm_config_description": "true",
"npm_package_devDependencies_html2canvas": "^1.0.0-alpha.12",
"_": "/Users/dtailor/Desktop/openmct/node_modules/.bin/karma",
"npm_config_userconfig": "/Users/dtailor/.npmrc",
"npm_config_init_module": "/Users/dtailor/.npm-init.js",
"npm_package_author": "",
"npm_package_devDependencies_karma_chrome_launcher": "^2.2.0",
"npm_package_devDependencies_d3_scale": "1.0.x",
"npm_config_cidr": "",
"npm_package_devDependencies_printj": "^1.2.1",
"PWD": "/Users/dtailor/Desktop/openmct",
"npm_config_user": "377333698",
"npm_config_node_version": "11.9.0",
"npm_package_bugs_url": "https://github.com/nasa/openmct/issues",
"npm_package_scripts_test_watch": "karma start --no-single-run",
"npm_lifecycle_event": "test",
"npm_package_devDependencies_v8_compile_cache": "^1.1.0",
"npm_config_ignore_prepublish": "",
"npm_config_save": "true",
"npm_config_editor": "vi",
"npm_config_auth_type": "legacy",
"npm_package_repository_type": "git",
"npm_package_devDependencies_vue": "2.5.6",
"npm_package_devDependencies_marked": "^0.3.5",
"npm_package_devDependencies_angular_route": "1.4.14",
"npm_package_name": "openmct",
"LANG": "en_US.UTF-8",
"npm_config_script_shell": "",
"npm_config_tag": "latest",
"npm_config_global": "",
"npm_config_progress": "true",
"npm_package_scripts_start": "node app.js",
"npm_package_devDependencies_karma_coverage_istanbul_reporter": "^2.1.1",
"npm_config_ham_it_up": "",
"npm_config_searchstaleness": "900",
"npm_config_optional": "true",
"npm_package_scripts_docs": "npm run jsdoc ; npm run otherdoc",
"npm_package_devDependencies_istanbul_instrumenter_loader": "^3.0.1",
"XPC_FLAGS": "0x0",
"npm_config_save_prod": "",
"npm_config_force": "",
"npm_config_bin_links": "true",
"npm_package_devDependencies_moment": "2.25.3",
"npm_package_devDependencies_karma_webpack": "^3.0.0",
"npm_package_devDependencies_express": "^4.13.1",
"npm_config_searchopts": "",
"npm_package_devDependencies_d3_time": "1.0.x",
"FORCE_COLOR": "2",
"npm_config_node_gyp": "/Users/dtailor/.nvm/versions/node/v11.9.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js",
"npm_config_depth": "Infinity",
"npm_package_scripts_build_prod": "cross-env NODE_ENV=production webpack",
"npm_config_sso_poll_frequency": "500",
"npm_config_rebuild_bundle": "true",
"npm_package_version": "1.0.0-snapshot",
"XPC_SERVICE_NAME": "0",
"npm_config_unicode": "true",
"npm_package_devDependencies_jsdoc": "^3.3.2",
"SHLVL": "4",
"HOME": "/Users/dtailor",
"npm_config_fetch_retry_maxtimeout": "60000",
"npm_package_scripts_test": "karma start --single-run",
"npm_package_devDependencies_zepto": "^1.2.0",
"npm_package_devDependencies_eslint_plugin_vue": "^6.0.0",
"npm_config_ca": "",
"npm_config_tag_version_prefix": "v",
"npm_config_strict_ssl": "true",
"npm_config_sso_type": "oauth",
"npm_config_scripts_prepend_node_path": "warn-only",
"npm_config_save_prefix": "^",
"npm_config_loglevel": "notice",
"npm_package_devDependencies_lodash": "^3.10.1",
"npm_package_devDependencies_karma_cli": "^1.0.1",
"npm_package_devDependencies_d3_color": "1.0.x",
"npm_config_save_exact": "",
"npm_config_dev": "",
"npm_config_group": "1286109195",
"npm_config_fetch_retry_factor": "10",
"npm_package_devDependencies_webpack_hot_middleware": "^2.22.3",
"npm_package_devDependencies_cross_env": "^6.0.3",
"npm_package_devDependencies_babel_eslint": "8.2.6",
"HOMEBREW_PREFIX": "/Users/dtailor/.homebrew",
"npm_config_version": "",
"npm_config_prefer_offline": "",
"npm_config_cache_lock_stale": "60000",
"npm_config_otp": "",
"npm_config_cache_min": "10",
"npm_package_devDependencies_vue_loader": "^15.2.6",
"npm_config_searchexclude": "",
"npm_config_cache": "/Users/dtailor/.npm",
"npm_package_scripts_test_coverage": "./scripts/test-coverage.sh",
"npm_package_devDependencies_d3_interpolate": "1.1.x",
"npm_package_devDependencies_d3_format": "1.2.x",
"LOGNAME": "dtailor",
"npm_lifecycle_script": "karma start --single-run",
"npm_config_color": "true",
"npm_package_devDependencies_node_bourbon": "^4.2.3",
"npm_package_devDependencies_karma_sourcemap_loader": "^0.3.7",
"npm_package_devDependencies_karma_html_reporter": "^0.2.7",
"npm_config_proxy": "",
"npm_config_package_lock": "true",
"npm_package_devDependencies_d3_time_format": "2.1.x",
"npm_package_devDependencies_d3_axis": "1.0.x",
"npm_config_package_lock_only": "",
"npm_package_devDependencies_moment_timezone": "0.5.28",
"npm_config_save_optional": "",
"NVM_BIN": "/Users/dtailor/.nvm/versions/node/v11.9.0/bin",
"npm_config_ignore_scripts": "",
"npm_config_user_agent": "npm/6.5.0 node/v11.9.0 darwin x64",
"npm_package_devDependencies_imports_loader": "^0.8.0",
"npm_package_devDependencies_file_saver": "^1.3.8",
"npm_config_cache_lock_wait": "10000",
"npm_config_production": "",
"npm_package_scripts_build_watch": "webpack --watch",
"DISPLAY": "/private/tmp/com.apple.launchd.E3N8oC6RMf/org.macosforge.xquartz:0",
"npm_config_send_metrics": "",
"npm_config_save_bundle": "",
"npm_package_scripts_prepare": "npm run build:prod",
"npm_config_node_options": "",
"npm_config_umask": "0022",
"npm_config_init_version": "1.0.0",
"npm_package_devDependencies_split": "^1.0.0",
"npm_package_devDependencies_raw_loader": "^0.5.1",
"npm_config_init_author_name": "",
"npm_config_git": "git",
"npm_config_scope": "",
"npm_package_scripts_clean": "rm -rf ./dist",
"npm_package_devDependencies_node_sass": "^4.9.2",
"npm_package_devDependencies_css_loader": "^1.0.0",
"DISABLE_UPDATE_CHECK": "1",
"npm_config_onload_script": "",
"npm_config_unsafe_perm": "true",
"npm_config_tmp": "/var/folders/ks/ytghmh9x4lj3cchr5km5lhkcb7v9y2/T",
"npm_package_devDependencies_d3_collection": "1.0.x",
"npm_node_execpath": "/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node",
"npm_config_link": "",
"npm_config_prefix": "/Users/dtailor/.nvm/versions/node/v11.9.0",
"npm_package_devDependencies_html_loader": "^0.5.5"
},
"userLimits": {
"core_file_size_blocks": {
"soft": 0,
"hard": "unlimited"
},
"data_seg_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_locked_memory_bytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_memory_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"open_files": {
"soft": 24576,
"hard": "unlimited"
},
"stack_size_bytes": {
"soft": 8388608,
"hard": 67104768
},
"cpu_time_seconds": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_user_processes": {
"soft": 1418,
"hard": 2128
},
"virtual_memory_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
}
},
"sharedObjects": [
"/Users/dtailor/.nvm/versions/node/v11.9.0/bin/node",
"/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
"/usr/lib/libSystem.B.dylib",
"/usr/lib/libc++.1.dylib",
"/usr/lib/libobjc.A.dylib",
"/usr/lib/libDiagnosticMessagesClient.dylib",
"/usr/lib/libicucore.A.dylib",
"/usr/lib/libz.1.dylib",
"/usr/lib/libc++abi.dylib",
"/usr/lib/system/libcache.dylib",
"/usr/lib/system/libcommonCrypto.dylib",
"/usr/lib/system/libcompiler_rt.dylib",
"/usr/lib/system/libcopyfile.dylib",
"/usr/lib/system/libcorecrypto.dylib",
"/usr/lib/system/libdispatch.dylib",
"/usr/lib/system/libdyld.dylib",
"/usr/lib/system/libkeymgr.dylib",
"/usr/lib/system/liblaunch.dylib",
"/usr/lib/system/libmacho.dylib",
"/usr/lib/system/libquarantine.dylib",
"/usr/lib/system/libremovefile.dylib",
"/usr/lib/system/libsystem_asl.dylib",
"/usr/lib/system/libsystem_blocks.dylib",
"/usr/lib/system/libsystem_c.dylib",
"/usr/lib/system/libsystem_configuration.dylib",
"/usr/lib/system/libsystem_coreservices.dylib",
"/usr/lib/system/libsystem_darwin.dylib",
"/usr/lib/system/libsystem_dnssd.dylib",
"/usr/lib/system/libsystem_info.dylib",
"/usr/lib/system/libsystem_m.dylib",
"/usr/lib/system/libsystem_malloc.dylib",
"/usr/lib/system/libsystem_networkextension.dylib",
"/usr/lib/system/libsystem_notify.dylib",
"/usr/lib/system/libsystem_sandbox.dylib",
"/usr/lib/system/libsystem_secinit.dylib",
"/usr/lib/system/libsystem_kernel.dylib",
"/usr/lib/system/libsystem_platform.dylib",
"/usr/lib/system/libsystem_pthread.dylib",
"/usr/lib/system/libsystem_symptoms.dylib",
"/usr/lib/system/libsystem_trace.dylib",
"/usr/lib/system/libunwind.dylib",
"/usr/lib/system/libxpc.dylib",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices",
"/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics",
"/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO",
"/System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/LangAnalysis.framework/Versions/A/LangAnalysis",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis",
"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight",
"/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface",
"/usr/lib/libxml2.2.dylib",
"/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate",
"/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation",
"/usr/lib/libcompression.dylib",
"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration",
"/System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay",
"/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
"/System/Library/Frameworks/Metal.framework/Versions/A/Metal",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders",
"/System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport",
"/System/Library/Frameworks/Security.framework/Versions/A/Security",
"/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore",
"/usr/lib/libbsm.0.dylib",
"/usr/lib/liblzma.5.dylib",
"/usr/lib/libauto.dylib",
"/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration",
"/usr/lib/libarchive.2.dylib",
"/usr/lib/liblangid.dylib",
"/usr/lib/libCRFSuite.dylib",
"/usr/lib/libenergytrace.dylib",
"/usr/lib/system/libkxld.dylib",
"/System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression",
"/usr/lib/libOpenScriptingUtil.dylib",
"/usr/lib/libcoretls.dylib",
"/usr/lib/libcoretls_cfhelpers.dylib",
"/usr/lib/libpam.2.dylib",
"/usr/lib/libsqlite3.dylib",
"/usr/lib/libxar.1.dylib",
"/usr/lib/libbz2.1.0.dylib",
"/usr/lib/libnetwork.dylib",
"/usr/lib/libapple_nghttp2.dylib",
"/usr/lib/libpcap.A.dylib",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices",
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList",
"/System/Library/Frameworks/NetFS.framework/Versions/A/NetFS",
"/System/Library/PrivateFrameworks/NetAuth.framework/Versions/A/NetAuth",
"/System/Library/PrivateFrameworks/login.framework/Versions/A/Frameworks/loginsupport.framework/Versions/A/loginsupport",
"/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC",
"/System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP",
"/System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities",
"/usr/lib/libmecabra.dylib",
"/usr/lib/libmecab.1.0.0.dylib",
"/usr/lib/libgermantok.dylib",
"/usr/lib/libThaiTokenizer.dylib",
"/usr/lib/libChineseTokenizer.dylib",
"/usr/lib/libiconv.2.dylib",
"/usr/lib/libcharset.1.dylib",
"/System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling",
"/System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji",
"/System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon",
"/System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData",
"/usr/lib/libcmph.dylib",
"/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData",
"/System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory",
"/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS",
"/usr/lib/libutil.dylib",
"/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement",
"/System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement",
"/usr/lib/libxslt.1.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib",
"/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib",
"/System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler",
"/System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator",
"/System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment",
"/System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSCore.framework/Versions/A/MPSCore",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSImage.framework/Versions/A/MPSImage",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix",
"/System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector",
"/System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools",
"/System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary",
"/usr/lib/libMobileGestalt.dylib",
"/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage",
"/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL",
"/System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer",
"/System/Library/PrivateFrameworks/FaceCore.framework/Versions/A/FaceCore",
"/System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL",
"/usr/lib/libFosl_dynamic.dylib",
"/System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontParser.dylib",
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib",
"/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib",
"/System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib",
"/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib",
"/usr/lib/libcups.2.dylib",
"/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos",
"/System/Library/Frameworks/GSS.framework/Versions/A/GSS",
"/usr/lib/libresolv.9.dylib",
"/System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal",
"/usr/lib/libheimdal-asn1.dylib",
"/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory",
"/System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth",
"/System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation",
"/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio",
"/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox",
"/System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce",
"/System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices",
"/System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard"
]
}

View File

@ -240,7 +240,7 @@ define([
this.overlays = new OverlayAPI.default();
this.contextMenu = new api.ContextMenuRegistry();
this.menus = new api.MenuAPI(this);
this.router = new ApplicationRouter();
@ -266,6 +266,7 @@ define([
this.install(this.plugins.WebPage());
this.install(this.plugins.Condition());
this.install(this.plugins.ConditionWidget());
this.install(this.plugins.NotificationIndicator());
}
MCT.prototype = Object.create(EventEmitter.prototype);

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -20,29 +20,32 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ContextMenuComponent from './ContextMenu.vue';
import Vue from 'vue';
import Menu from './menu.js';
/**
* The ContextMenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
* The MenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
* custom HTML elements.
* @interface ContextMenuAPI
* @interface MenuAPI
* @memberof module:openmct
*/
class ContextMenuAPI {
constructor() {
this._allActions = [];
this._activeContextMenu = undefined;
this._hideActiveContextMenu = this._hideActiveContextMenu.bind(this);
this.registerAction = this.registerAction.bind(this);
class MenuAPI {
constructor(openmct) {
this.openmct = openmct;
this._allObjectActions = [];
this.showMenu = this.showMenu.bind(this);
this.registerObjectAction = this.registerObjectAction.bind(this);
this._clearMenuComponent = this._clearMenuComponent.bind(this);
this._applicableObjectActions = this._applicableObjectActions.bind(this);
this._showObjectMenu = this._showObjectMenu.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
* @interface ObjectAction
* @memberof module:openmct
* @property {string} name the human-readable name of this view
* @property {string} description a longer-form description (typically
@ -68,17 +71,32 @@ class ContextMenuAPI {
/**
* @param {ContextMenuAction} actionDefinition
*/
registerAction(actionDefinition) {
this._allActions.push(actionDefinition);
registerObjectAction(actionDefinition) {
this._allObjectActions.push(actionDefinition);
}
/**
* @private
*/
_showContextMenuForObjectPath(objectPath, x, y, actionsToBeIncluded) {
showMenu(x, y, actions) {
if (this.menuComponent) {
this.menuComponent.dismiss();
}
let applicableActions = this._allActions.filter((action) => {
let options = {
x,
y,
actions
}
this.menuComponent = new Menu(options);
this.menuComponent.on('destroy', this._clearMenuComponent);
}
_clearMenuComponent() {
this.menuComponent = undefined;
delete this.menuComponent;
}
_applicableObjectActions(objectPath, actionsToBeIncluded) {
let applicableActions = this._allObjectActions.filter((action) => {
if (actionsToBeIncluded) {
if (action.appliesTo === undefined && actionsToBeIncluded.includes(action.key)) {
return true;
@ -92,66 +110,19 @@ class ContextMenuAPI {
}
});
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>'
applicableActions.forEach(action => {
action.callBack = () => {
return action.invoke(objectPath);
};
});
return applicableActions;
}
_showObjectMenu(objectPath, x, y, actionsToBeIncluded) {
let applicableActions = this._applicableObjectActions(objectPath, actionsToBeIncluded);
this.showMenu(x, y, applicableActions);
}
}
export default ContextMenuAPI;
export default MenuAPI;

View File

@ -6,7 +6,7 @@
:key="action.name"
:class="action.cssClass"
:title="action.description"
@click="action.invoke(objectPath)"
@click="action.callBack"
>
{{ action.name }}
</li>
@ -19,6 +19,6 @@
<script>
export default {
inject: ['actions', 'objectPath']
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

@ -128,6 +128,11 @@ export default class NotificationAPI extends EventEmitter {
return this._notify(notificationModel);
}
dismissAllNotifications() {
this.notifications = [];
this.emit('dismiss-all');
}
/**
* Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a

View File

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

View File

@ -1,6 +1,6 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -24,10 +24,8 @@
<template>
<tr @contextmenu.prevent="showContextMenu">
<td>{{ name }}</td>
<td>{{ timestamp }}</td>
<td :class="valueClass">
{{ value }}
</td>
<td>{{ formattedTimestamp }}</td>
<td :class="valueClass">{{ value }}</td>
</tr>
</template>
@ -52,16 +50,22 @@ export default {
return {
name: this.domainObject.name,
timestamp: '---',
timestamp: undefined,
value: '---',
valueClass: '',
currentObjectPath
}
},
computed: {
formattedTimestamp() {
return this.timestamp !== undefined ? this.formats[this.timestampKey].format(this.timestamp) : '---';
}
},
mounted() {
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.bounds = this.openmct.time.bounds();
this.limitEvaluator = this.openmct
.telemetry
@ -76,6 +80,7 @@ export default {
);
this.openmct.time.on('timeSystem', this.updateTimeSystem);
this.openmct.time.on('bounds', this.updateBounds);
this.timestampKey = this.openmct.time.timeSystem().key;
@ -89,46 +94,66 @@ export default {
.telemetry
.subscribe(this.domainObject, this.updateValues);
this.openmct
.telemetry
.request(this.domainObject, {strategy: 'latest'})
.then((array) => this.updateValues(array[array.length - 1]));
this.requestHistory();
},
destroyed() {
this.stopWatchingMutation();
this.unsubscribe();
this.openmct.off('timeSystem', this.updateTimeSystem);
this.openmct.time.off('timeSystem', this.updateTimeSystem);
this.openmct.time.off('bounds', this.updateBounds);
},
methods: {
updateValues(datum) {
this.timestamp = this.formats[this.timestampKey].format(datum);
this.value = this.formats[this.valueKey].format(datum);
let newTimestamp = this.formats[this.timestampKey].parse(datum),
limit;
var limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
if(this.shouldUpdate(newTimestamp)) {
this.timestamp = this.formats[this.timestampKey].parse(datum);
this.value = this.formats[this.valueKey].format(datum);
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
}
}
},
shouldUpdate(newTimestamp) {
return (this.timestamp === undefined) ||
(this.inBounds(newTimestamp) &&
newTimestamp > this.timestamp);
},
requestHistory() {
this.openmct
.telemetry
.request(this.domainObject, {
start: this.bounds.start,
end: this.bounds.end,
size: 1,
strategy: 'latest'
})
.then((array) => this.updateValues(array[array.length - 1]));
},
updateName(name) {
this.name = name;
},
updateBounds(bounds, isTick) {
this.bounds = bounds;
if(!isTick) {
this.requestHistory();
}
},
inBounds(timestamp) {
return timestamp >= this.bounds.start && timestamp <= this.bounds.end;
},
updateTimeSystem(timeSystem) {
this.value = '---';
this.timestamp = '---';
this.valueClass = '';
this.timestampKey = timeSystem.key;
this.openmct
.telemetry
.request(this.domainObject, {strategy: 'latest'})
.then((array) => this.updateValues(array[array.length - 1]));
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
this.openmct.menus._showObjectMenu(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
}
}
}

View File

@ -48,7 +48,7 @@ define([
openmct.indicators.add(indicator);
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
openmct.menus.registerObjectAction(new ClearDataAction.default(openmct, appliesToObjects));
};
};
});

View File

@ -29,6 +29,7 @@ export default class StyleRuleManager extends EventEmitter {
this.callback = callback;
if (suppressSubscriptionOnEdit) {
this.openmct.editor.on('isEditing', this.toggleSubscription.bind(this));
this.isEditing = this.openmct.editor.editing;
}
if (styleConfiguration) {
this.initialize(styleConfiguration);
@ -156,7 +157,6 @@ export default class StyleRuleManager extends EventEmitter {
}
delete this.stopProvidingTelemetry;
this.conditionSetIdentifier = undefined;
this.isEditing = undefined;
}
}

View File

@ -0,0 +1,601 @@
/*****************************************************************************
* 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.
*****************************************************************************/
<template>
<div class="c-inspector__styles c-inspect-styles">
<div v-if="isStaticAndConditionalStyles"
class="c-inspect-styles__mixed-static-and-conditional u-alert u-alert--block u-alert--with-icon"
>
Your selection includes one or more items that use Conditional Styling. Applying a static style below will replace any Conditional Styling with the new choice.
</div>
<template v-if="!conditionSetDomainObject">
<div class="c-inspect-styles__header">
Object Style
</div>
<div class="c-inspect-styles__content">
<div v-if="staticStyle"
class="c-inspect-styles__style"
>
<style-editor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="isEditing"
:mixed-styles="mixedStyles"
@persist="updateStaticStyle"
/>
</div>
<button
id="addConditionSet"
class="c-button c-button--major c-toggle-styling-button labeled"
@click="addConditionSet"
>
<span class="c-cs-button__label">Use Conditional Styling...</span>
</button>
</div>
</template>
<template v-else>
<div class="c-inspect-styles__header">
Conditional Object Styles
</div>
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
<a v-if="conditionSetDomainObject"
class="c-object-label icon-conditional"
:href="navigateToPath"
@click="navigateOrPreview"
>
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
</a>
<template v-if="isEditing">
<button
id="changeConditionSet"
class="c-button labeled"
@click="addConditionSet"
>
<span class="c-button__label">Change...</span>
</button>
<button class="c-click-icon icon-x"
title="Remove conditional styles"
@click="removeConditionSet"
></button>
</template>
</div>
<div v-if="conditionsLoaded"
class="c-inspect-styles__conditions"
>
<div v-for="(conditionStyle, index) in conditionalStyles"
:key="index"
class="c-inspect-styles__condition"
:class="{'is-current': conditionStyle.conditionId === selectedConditionId}"
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
>
<condition-error :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:is-editing="isEditing"
@persist="updateConditionalStyle"
/>
</div>
</div>
</template>
</div>
</template>
<script>
import StyleEditor from "./StyleEditor.vue";
import PreviewAction from "@/ui/preview/PreviewAction.js";
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionSetIdentifierForItem } from "@/plugins/condition/utils/styleUtils";
import ConditionSetSelectorDialog from "@/plugins/condition/components/inspector/ConditionSetSelectorDialog.vue";
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
import Vue from 'vue';
export default {
name: 'StylesView',
components: {
StyleEditor,
ConditionError,
ConditionDescription
},
inject: [
'openmct',
'selection'
],
data() {
return {
staticStyle: undefined,
isEditing: this.openmct.editor.isEditing(),
mixedStyles: [],
isStaticAndConditionalStyles: false,
conditionalStyles: [],
conditionSetDomainObject: undefined,
conditions: undefined,
conditionsLoaded: false,
navigateToPath: '',
selectedConditionId: ''
}
},
destroyed() {
this.removeListeners();
},
mounted() {
this.items = [];
this.previewAction = new PreviewAction(this.openmct);
this.isMultipleSelection = this.selection.length > 1;
this.getObjectsAndItemsFromSelection();
if (!this.isMultipleSelection) {
let objectStyles = this.getObjectStyles();
this.initializeStaticStyle(objectStyles);
if (objectStyles && objectStyles.conditionSetIdentifier) {
this.openmct.objects.get(objectStyles.conditionSetIdentifier).then(this.initialize);
this.conditionalStyles = objectStyles.styles;
}
} else {
this.initializeStaticStyle();
}
this.openmct.editor.on('isEditing', this.setEditState);
},
methods: {
getObjectStyles() {
let objectStyles;
if (this.domainObjectsById) {
const domainObject = Object.values(this.domainObjectsById)[0];
if (domainObject.configuration && domainObject.configuration.objectStyles) {
objectStyles = domainObject.configuration.objectStyles;
}
} else if (this.items.length) {
const itemId = this.items[0].id;
if (this.domainObject.configuration && this.domainObject.configuration.objectStyles && this.domainObject.configuration.objectStyles[itemId]) {
objectStyles = this.domainObject.configuration.objectStyles[itemId];
}
} else if (this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
objectStyles = this.domainObject.configuration.objectStyles;
}
return objectStyles;
},
setEditState(isEditing) {
this.isEditing = isEditing;
if (this.isEditing) {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
} else {
this.subscribeToConditionSet();
}
},
enableConditionSetNav() {
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
(objectPath) => {
this.objectPath = objectPath;
this.navigateToPath = '#/browse/' + this.objectPath
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/');
}
);
},
navigateOrPreview(event) {
// If editing, display condition set in Preview overlay; otherwise nav to it while browsing
if (this.openmct.editor.isEditing()) {
event.preventDefault();
this.previewAction.invoke(this.objectPath);
}
},
isItemType(type, item) {
return item && (item.type === type);
},
hasConditionalStyle(domainObject, layoutItem) {
const id = layoutItem ? layoutItem.id : undefined;
return getConditionSetIdentifierForItem(domainObject, id) !== undefined;
},
getObjectsAndItemsFromSelection() {
let domainObject;
let subObjects = [];
let itemsWithConditionalStyles = 0;
//multiple selection
let itemInitialStyles = [];
let itemStyle;
this.selection.forEach((selectionItem) => {
const item = selectionItem[0].context.item;
const layoutItem = selectionItem[0].context.layoutItem;
const isChildItem = selectionItem.length > 1;
if (!isChildItem) {
domainObject = item;
itemStyle = getApplicableStylesForItem(item);
if (this.hasConditionalStyle(item)) {
itemsWithConditionalStyles += 1;
}
} else {
this.canHide = true;
domainObject = selectionItem[1].context.item;
if (item && !layoutItem || this.isItemType('subobject-view', layoutItem)) {
subObjects.push(item);
itemStyle = getApplicableStylesForItem(item);
if (this.hasConditionalStyle(item)) {
itemsWithConditionalStyles += 1;
}
} else {
itemStyle = getApplicableStylesForItem(domainObject, layoutItem || item);
this.items.push({
id: layoutItem.id,
applicableStyles: itemStyle
});
if (this.hasConditionalStyle(item, layoutItem)) {
itemsWithConditionalStyles += 1;
}
}
}
itemInitialStyles.push(itemStyle);
});
this.isStaticAndConditionalStyles = this.isMultipleSelection && itemsWithConditionalStyles;
const {styles, mixedStyles} = getConsolidatedStyleValues(itemInitialStyles);
this.initialStyles = styles;
this.mixedStyles = mixedStyles;
this.domainObject = domainObject;
this.removeListeners();
if (this.domainObject) {
this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
}
subObjects.forEach(this.registerListener);
},
updateDomainObjectItemStyles(newItems) {
let keys = Object.keys(this.domainObject.configuration.objectStyles || {});
keys.forEach((key) => {
if (this.isKeyItemId(key)) {
if (!(newItems.find(item => item.id === key))) {
this.removeItemStyles(key);
}
}
});
},
isKeyItemId(key) {
return (key !== 'styles') &&
(key !== 'staticStyle') &&
(key !== 'defaultConditionId') &&
(key !== 'selectedConditionId') &&
(key !== 'conditionSetIdentifier');
},
registerListener(domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.domainObjectsById) {
this.domainObjectsById = {};
}
if (!this.domainObjectsById[id]) {
this.domainObjectsById[id] = domainObject;
this.observeObject(domainObject, id);
}
},
observeObject(domainObject, id) {
let unobserveObject = this.openmct.objects.observe(domainObject, '*', (newObject) => {
this.domainObjectsById[id] = JSON.parse(JSON.stringify(newObject));
});
this.unObserveObjects.push(unobserveObject);
},
removeListeners() {
if (this.stopObserving) {
this.stopObserving();
}
if (this.stopObservingItems) {
this.stopObservingItems();
}
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
if (this.unObserveObjects) {
this.unObserveObjects.forEach((unObserveObject) => {
unObserveObject();
});
}
this.unObserveObjects = [];
},
subscribeToConditionSet() {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
if (this.conditionSetDomainObject) {
this.openmct.telemetry.request(this.conditionSetDomainObject)
.then(output => {
if (output && output.length) {
this.handleConditionSetResultUpdated(output[0]);
}
});
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(this.conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this));
}
},
handleConditionSetResultUpdated(resultData) {
this.selectedConditionId = resultData ? resultData.conditionId : '';
},
initialize(conditionSetDomainObject) {
//If there are new conditions in the conditionSet we need to set those styles to default
this.conditionSetDomainObject = conditionSetDomainObject;
this.enableConditionSetNav();
this.initializeConditionalStyles();
},
initializeConditionalStyles() {
if (!this.conditions) {
this.conditions = {};
}
let conditionalStyles = [];
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
if (conditionConfiguration.isDefault) {
this.selectedConditionId = conditionConfiguration.id;
}
this.conditions[conditionConfiguration.id] = conditionConfiguration;
let foundStyle = this.findStyleByConditionId(conditionConfiguration.id);
if (foundStyle) {
foundStyle.style = Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, foundStyle.style);
conditionalStyles.push(foundStyle);
} else {
conditionalStyles.splice(index, 0, {
conditionId: conditionConfiguration.id,
style: Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles)
});
}
});
//we're doing this so that we remove styles for any conditions that have been removed from the condition set
this.conditionalStyles = conditionalStyles;
this.conditionsLoaded = true;
this.getAndPersistStyles(null, this.selectedConditionId);
if (!this.isEditing) {
this.subscribeToConditionSet();
}
},
//TODO: Double check how this works for single styles
initializeStaticStyle(objectStyles) {
let staticStyle = objectStyles && objectStyles.staticStyle;
if (staticStyle) {
this.staticStyle = {
style: Object.assign({}, this.initialStyles, staticStyle.style)
};
} else {
this.staticStyle = {
style: Object.assign({}, this.initialStyles)
};
}
},
removeItemStyles(itemId) {
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (itemId && domainObjectStyles[itemId]) {
delete domainObjectStyles[itemId];
if (Object.keys(domainObjectStyles).length <= 0) {
domainObjectStyles = undefined;
}
this.persist(this.domainObject, domainObjectStyles);
}
},
findStyleByConditionId(id) {
return this.conditionalStyles.find(conditionalStyle => conditionalStyle.conditionId === id);
},
getCondition(id) {
return this.conditions ? this.conditions[id] : {};
},
addConditionSet() {
let conditionSetDomainObject;
const handleItemSelection = (item) => {
if (item) {
conditionSetDomainObject = item;
}
};
const dismissDialog = (overlay, initialize) => {
overlay.dismiss();
if (initialize && conditionSetDomainObject) {
this.conditionSetDomainObject = conditionSetDomainObject;
this.conditionalStyles = [];
this.initializeConditionalStyles();
}
};
let vm = new Vue({
provide: {
openmct: this.openmct
},
components: {ConditionSetSelectorDialog},
data() {
return {
handleItemSelection
}
},
template: '<condition-set-selector-dialog @conditionSetSelected="handleItemSelection"></condition-set-selector-dialog>'
}).$mount();
let overlay = this.openmct.overlays.overlay({
element: vm.$el,
size: 'small',
buttons: [
{
label: 'OK',
emphasis: 'true',
callback: () => dismissDialog(overlay, true)
},
{
label: 'Cancel',
callback: () => dismissDialog(overlay, false)
}
],
onDestroy: () => vm.$destroy()
});
},
removeConditionSet() {
this.conditionSetDomainObject = undefined;
this.conditionalStyles = [];
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (this.domainObjectsById) {
const domainObjects = Object.values(this.domainObjectsById);
domainObjects.forEach(domainObject => {
let objectStyles = (domainObject.configuration && domainObject.configuration.objectStyles) || {};
this.removeConditionalStyles(objectStyles);
if (objectStyles && Object.keys(objectStyles).length <= 0) {
objectStyles = undefined;
}
this.persist(domainObject, objectStyles);
});
}
if (this.items.length) {
this.items.forEach((item) => {
const itemId = item.id;
this.removeConditionalStyles(domainObjectStyles, itemId);
if (domainObjectStyles[itemId] && Object.keys(domainObjectStyles[itemId]).length <= 0) {
delete domainObjectStyles[itemId];
}
});
} else {
this.removeConditionalStyles(domainObjectStyles);
}
if (domainObjectStyles && Object.keys(domainObjectStyles).length <= 0) {
domainObjectStyles = undefined;
}
this.persist(this.domainObject, domainObjectStyles);
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
},
removeConditionalStyles(domainObjectStyles, itemId) {
if (itemId && domainObjectStyles[itemId]) {
domainObjectStyles[itemId].conditionSetIdentifier = undefined;
delete domainObjectStyles[itemId].conditionSetIdentifier;
domainObjectStyles[itemId].selectedConditionId = undefined;
domainObjectStyles[itemId].defaultConditionId = undefined;
domainObjectStyles[itemId].styles = undefined;
delete domainObjectStyles[itemId].styles;
} else {
domainObjectStyles.conditionSetIdentifier = undefined;
delete domainObjectStyles.conditionSetIdentifier;
domainObjectStyles.selectedConditionId = undefined;
domainObjectStyles.defaultConditionId = undefined;
domainObjectStyles.styles = undefined;
delete domainObjectStyles.styles;
}
},
updateStaticStyle(staticStyle, property) {
//update the static style for each of the layoutItems as well as each sub object item
this.staticStyle = staticStyle;
this.removeConditionSet();
this.getAndPersistStyles(property);
},
updateConditionalStyle(conditionStyle, property) {
let foundStyle = this.findStyleByConditionId(conditionStyle.conditionId);
if (foundStyle) {
foundStyle.style = conditionStyle.style;
this.selectedConditionId = foundStyle.conditionId;
this.getAndPersistStyles(property);
}
},
getAndPersistStyles(property, defaultConditionId) {
this.persist(this.domainObject, this.getDomainObjectStyle(this.domainObject, property, this.items, defaultConditionId));
if (this.domainObjectsById) {
const domainObjects = Object.values(this.domainObjectsById);
domainObjects.forEach(domainObject => {
this.persist(domainObject, this.getDomainObjectStyle(domainObject, property, null, defaultConditionId));
});
}
if (!this.items.length && !this.domainObjectsById) {
this.persist(this.domainObject, this.getDomainObjectStyle(this.domainObject, property, null, defaultConditionId));
}
this.isStaticAndConditionalStyles = false;
if (property) {
let foundIndex = this.mixedStyles.indexOf(property);
if (foundIndex > -1) {
this.mixedStyles.splice(foundIndex, 1);
}
}
},
getDomainObjectStyle(domainObject, property, items, defaultConditionId) {
let objectStyle = {
styles: this.conditionalStyles,
staticStyle: this.staticStyle,
selectedConditionId: this.selectedConditionId
};
if (defaultConditionId) {
objectStyle.defaultConditionId = defaultConditionId;
}
if (this.conditionSetDomainObject) {
objectStyle.conditionSetIdentifier = this.conditionSetDomainObject.identifier;
}
let domainObjectStyles = (domainObject.configuration && domainObject.configuration.objectStyles) || {};
if (items) {
items.forEach(item => {
let itemStaticStyle = {};
let itemConditionalStyle = { styles: []};
if (!this.conditionSetDomainObject) {
if (domainObjectStyles[item.id] && domainObjectStyles[item.id].staticStyle) {
itemStaticStyle = Object.assign({}, domainObjectStyles[item.id].staticStyle.style);
}
if (item.applicableStyles[property] !== undefined) {
itemStaticStyle[property] = this.staticStyle.style[property];
}
if (Object.keys(itemStaticStyle).length <= 0) {
itemStaticStyle = undefined;
}
domainObjectStyles[item.id] = { staticStyle: { style: itemStaticStyle } };
} else {
objectStyle.styles.forEach((conditionalStyle, index) => {
let style = {};
Object.keys(item.applicableStyles).concat(['isStyleInvisible']).forEach(key => {
style[key] = conditionalStyle.style[key];
});
itemConditionalStyle.styles.push({
...conditionalStyle,
style
});
});
domainObjectStyles[item.id] = {
...domainObjectStyles[item.id],
...objectStyle,
...itemConditionalStyle
};
}
});
} else {
domainObjectStyles = {
...domainObjectStyles,
...objectStyle
};
}
return domainObjectStyles;
},
applySelectedConditionStyle(conditionId) {
this.selectedConditionId = conditionId;
this.getAndPersistStyles();
},
persist(domainObject, style) {
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
}
}
}
</script>

View File

@ -22,6 +22,9 @@
import { createOpenMct } from "testUtils";
import ConditionPlugin from "./plugin";
import StylesView from "./components/inspector/StylesView.vue";
import Vue from 'vue';
import {getApplicableStylesForItem} from "./utils/styleUtils";
describe('the plugin', function () {
let conditionSetDefinition;
@ -30,7 +33,7 @@ describe('the plugin', function () {
let child;
let openmct;
beforeAll((done) => {
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(new ConditionPlugin());
@ -90,4 +93,259 @@ describe('the plugin', function () {
});
});
describe('the condition set usage for multiple display layout items', () => {
let displayLayoutItem;
let lineLayoutItem;
let boxLayoutItem;
let selection;
let component;
let styleViewComponentObject;
const conditionSetDomainObject = {
"configuration":{
"conditionTestData":[
{
"telemetry":"",
"metadata":"",
"input":""
}
],
"conditionCollection":[
{
"id":"39584410-cbf9-499e-96dc-76f27e69885d",
"configuration":{
"name":"Unnamed Condition",
"output":"Sine > 0",
"trigger":"all",
"criteria":[
{
"id":"85fbb2f7-7595-42bd-9767-a932266c5225",
"telemetry":{
"namespace":"",
"key":"be0ba97f-b510-4f40-a18d-4ff121d5ea1a"
},
"operation":"greaterThan",
"input":[
"0"
],
"metadata":"sin"
},
{
"id":"35400132-63b0-425c-ac30-8197df7d5862",
"telemetry":"any",
"operation":"enumValueIs",
"input":[
"0"
],
"metadata":"state"
}
]
},
"summary":"Match if all criteria are met: Sine Wave Generator Sine > 0 and any telemetry State is OFF "
},
{
"isDefault":true,
"id":"2532d90a-e0d6-4935-b546-3123522da2de",
"configuration":{
"name":"Default",
"output":"Default",
"trigger":"all",
"criteria":[
]
},
"summary":""
}
]
},
"composition":[
{
"namespace":"",
"key":"be0ba97f-b510-4f40-a18d-4ff121d5ea1a"
},
{
"namespace":"",
"key":"077ffa67-e78f-4e99-80e0-522ac33a3888"
}
],
"telemetry":{
},
"name":"Condition Set",
"type":"conditionSet",
"identifier":{
"namespace":"",
"key":"863012c1-f6ca-4ab0-aed7-fd43d5e4cd12"
}
};
const staticStyle = {
"style":{
"backgroundColor":"#717171",
"border":"1px solid #00ffff"
}
};
const conditionalStyle = {
"conditionId":"39584410-cbf9-499e-96dc-76f27e69885d",
"style":{
"isStyleInvisible":"",
"backgroundColor":"#717171",
"border":"1px solid #ffff00"
}
};
beforeEach(() => {
displayLayoutItem = {
"composition":[
],
"configuration":{
"items":[
{
"fill":"#717171",
"stroke":"",
"x":1,
"y":1,
"width":10,
"height":5,
"type":"box-view",
"id":"89b88746-d325-487b-aec4-11b79afff9e8"
},
{
"x":18,
"y":9,
"x2":23,
"y2":4,
"stroke":"#717171",
"type":"line-view",
"id":"57d49a28-7863-43bd-9593-6570758916f0"
}
],
"layoutGrid":[
10,
10
]
},
"name":"Display Layout",
"type":"layout",
"identifier":{
"namespace":"",
"key":"c5e636c1-6771-4c9c-b933-8665cab189b3"
}
};
lineLayoutItem = {
"x":18,
"y":9,
"x2":23,
"y2":4,
"stroke":"#717171",
"type":"line-view",
"id":"57d49a28-7863-43bd-9593-6570758916f0"
};
boxLayoutItem = {
"fill": "#717171",
"stroke": "",
"x": 1,
"y": 1,
"width": 10,
"height": 5,
"type": "box-view",
"id": "89b88746-d325-487b-aec4-11b79afff9e8"
};
selection = [
[{
context: {
"layoutItem": lineLayoutItem,
"index":1
}
},
{
context: {
"item": displayLayoutItem,
"supportsMultiSelect":true
}
}],
[{
context: {
"layoutItem": boxLayoutItem,
"index": 0
}
},
{
context: {
item: displayLayoutItem,
"supportsMultiSelect":true
}
}]
];
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
provide: {
openmct: openmct,
selection: selection
},
el: viewContainer,
components: {
StylesView
},
template: '<styles-view/>'
});
return Vue.nextTick().then(() => {
styleViewComponentObject = component.$root.$children[0];
styleViewComponentObject.setEditState(true);
});
});
it('initializes the items in the view', () => {
expect(styleViewComponentObject.items.length).toBe(2);
});
it('initializes conditional styles', () => {
styleViewComponentObject.conditionSetDomainObject = conditionSetDomainObject;
styleViewComponentObject.conditionalStyles = [];
styleViewComponentObject.initializeConditionalStyles();
expect(styleViewComponentObject.conditionalStyles.length).toBe(2);
});
it('updates applicable conditional styles', () => {
styleViewComponentObject.conditionSetDomainObject = conditionSetDomainObject;
styleViewComponentObject.conditionalStyles = [];
styleViewComponentObject.initializeConditionalStyles();
expect(styleViewComponentObject.conditionalStyles.length).toBe(2);
styleViewComponentObject.updateConditionalStyle(conditionalStyle, 'border');
return Vue.nextTick().then(() => {
expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
[boxLayoutItem, lineLayoutItem].forEach((item) => {
const itemStyles = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].styles;
expect(itemStyles.length).toBe(2);
const foundStyle = itemStyles.find((style) => {
return style.conditionId === conditionalStyle.conditionId;
});
expect(foundStyle).toBeDefined();
const applicableStyles = getApplicableStylesForItem(styleViewComponentObject.domainObject, item);
const applicableStylesKeys = Object.keys(applicableStyles).concat(['isStyleInvisible']);
Object.keys(foundStyle.style).forEach((key) => {
expect(applicableStylesKeys.indexOf(key)).toBeGreaterThan(-1);
expect(foundStyle.style[key]).toEqual(conditionalStyle.style[key]);
});
});
});
});
it('updates applicable static styles', () => {
styleViewComponentObject.updateStaticStyle(staticStyle, 'border');
return Vue.nextTick().then(() => {
expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
[boxLayoutItem, lineLayoutItem].forEach((item) => {
const itemStyle = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].staticStyle;
expect(itemStyle).toBeDefined();
const applicableStyles = getApplicableStylesForItem(styleViewComponentObject.domainObject, item);
const applicableStylesKeys = Object.keys(applicableStyles).concat(['isStyleInvisible']);
Object.keys(itemStyle.style).forEach((key) => {
expect(applicableStylesKeys.indexOf(key)).toBeGreaterThan(-1);
expect(itemStyle.style[key]).toEqual(staticStyle.style[key]);
});
});
});
});
});
});

View File

@ -124,12 +124,25 @@ export const getConditionalStyleForItem = (domainObject, id) => {
if (domainObjectStyles[id] && domainObjectStyles[id].conditionSetIdentifier) {
return domainObjectStyles[id].styles;
}
} else if (domainObjectStyles.staticStyle) {
} else if (domainObjectStyles.conditionSetIdentifier) {
return domainObjectStyles.styles;
}
}
};
export const getConditionSetIdentifierForItem = (domainObject, id) => {
let domainObjectStyles = domainObject && domainObject.configuration && domainObject.configuration.objectStyles;
if (domainObjectStyles) {
if (id) {
if (domainObjectStyles[id] && domainObjectStyles[id].conditionSetIdentifier) {
return domainObjectStyles[id].conditionSetIdentifier;
}
} else if (domainObjectStyles.conditionSetIdentifier) {
return domainObjectStyles.conditionSetIdentifier;
}
}
};
//Returns either existing static styles or uses SVG defaults if available
export const getApplicableStylesForItem = (domainObject, item) => {
const type = item && item.type;

View File

@ -248,7 +248,7 @@ export default {
this.$emit('formatChanged', this.item, format);
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
this.openmct.menus._showObjectMenu(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
}
}
}

View File

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

View File

@ -66,7 +66,6 @@ export default {
data() {
return {
autoScroll: true,
date: '',
filters : {
brightness: 100,
contrast: 100
@ -78,22 +77,39 @@ export default {
imageHistory: [],
imageUrl: '',
isPaused: false,
metadata: {},
requestCount: 0,
timeFormat: ''
}
},
mounted() {
// set
this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.subscribe(this.domainObject);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.imageFormat = this.openmct.telemetry.getValueFormatter(this.metadata.valuesForHints(['image'])[0]);
// initialize
this.timeKey = this.openmct.time.timeSystem().key;
this.timeFormat = this.openmct.telemetry.getValueFormatter(this.metadata.value(this.timeKey));
// listen
this.openmct.time.on('bounds', this.boundsChange);
this.openmct.time.on('timeSystem', this.timeSystemChange);
// kickoff
this.subscribe();
this.requestHistory();
},
updated() {
this.scrollToRight();
},
beforeDestroy() {
this.stopListening();
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
}
this.openmct.time.off('bounds', this.boundsChange);
this.openmct.time.off('timeSystem', this.timeSystemChange);
},
methods: {
datumMatchesMostRecent(datum) {
datumIsNotValid(datum) {
if (this.imageHistory.length === 0) {
return false;
}
@ -103,7 +119,14 @@ export default {
const lastHistoryTime = this.timeFormat.format(this.imageHistory.slice(-1)[0]);
const lastHistoryURL = this.imageFormat.format(this.imageHistory.slice(-1)[0]);
return (datumTime === lastHistoryTime) && (datumURL === lastHistoryURL);
// datum is not valid if it matches the last datum in history,
// or it is before the last datum in the history
const datumTimeCheck = this.timeFormat.parse(datum);
const historyTimeCheck = this.timeFormat.parse(this.imageHistory.slice(-1)[0]);
const matchesLast = (datumTime === lastHistoryTime) && (datumURL === lastHistoryURL);
const isStale = datumTimeCheck < historyTimeCheck;
return matchesLast || isStale;
},
getImageUrl(datum) {
return datum ?
@ -147,21 +170,6 @@ export default {
return this.isPaused;
},
requestHistory(bounds) {
this.requestCount++;
this.imageHistory = [];
const requestId = this.requestCount;
this.openmct.telemetry
.request(this.domainObject, bounds)
.then((values = []) => {
if (this.requestCount > requestId) {
return Promise.resolve('Stale request');
}
values.forEach(this.updateHistory);
this.updateValues(values[values.length - 1]);
});
},
scrollToRight() {
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
return;
@ -188,40 +196,56 @@ export default {
image.selected = true;
}
},
stopListening() {
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
boundsChange(bounds, isTick) {
if(!isTick) {
this.requestHistory();
}
},
subscribe(domainObject) {
this.date = ''
this.imageUrl = '';
this.openmct.objects.get(this.keystring)
.then((object) => {
const metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.timeKey = this.openmct.time.timeSystem().key;
this.timeFormat = this.openmct.telemetry.getValueFormatter(metadata.value(this.timeKey));
this.imageFormat = this.openmct.telemetry.getValueFormatter(metadata.valuesForHints(['image'])[0]);
this.unsubscribe = this.openmct.telemetry
.subscribe(this.domainObject, (datum) => {
this.updateHistory(datum);
this.updateValues(datum);
});
requestHistory() {
let bounds = this.openmct.time.bounds();
this.requestCount++;
const requestId = this.requestCount;
this.imageHistory = [];
this.openmct.telemetry
.request(this.domainObject, bounds)
.then((values = []) => {
if (this.requestCount === requestId) {
values.forEach(this.updateHistory, false);
this.updateValues(values[values.length - 1]);
}
});
},
timeSystemChange(system) {
// reset timesystem dependent variables
this.timeKey = system.key;
this.timeFormat = this.openmct.telemetry.getValueFormatter(this.metadata.value(this.timeKey));
},
subscribe() {
this.unsubscribe = this.openmct.telemetry
.subscribe(this.domainObject, (datum) => {
let parsedTimestamp = this.timeFormat.parse(datum[this.timeKey]),
bounds = this.openmct.time.bounds();
this.requestHistory(this.openmct.time.bounds());
if(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
this.updateHistory(datum);
this.updateValues(datum);
}
});
},
unselectAllImages() {
this.imageHistory.forEach(image => image.selected = false);
},
updateHistory(datum) {
if (this.datumMatchesMostRecent(datum)) {
updateHistory(datum, updateValues = true) {
if (this.datumIsNotValid(datum)) {
return;
}
const index = _.sortedIndexBy(this.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat));
this.imageHistory.splice(index, 0, datum);
if(updateValues) {
this.updateValues(datum);
}
},
updateValues(datum) {
if (this.isPaused) {

View File

@ -169,12 +169,10 @@ export default {
const bounds = this.openmct.time.bounds();
const isTimeBoundChanged = this.embed.bounds.start !== bounds.start
&& this.embed.bounds.end !== bounds.end;
|| this.embed.bounds.end !== bounds.end;
const isFixedTimespanMode = !this.openmct.time.clock();
this.openmct.time.stopClock();
window.location.href = link;
let message = '';
if (isTimeBoundChanged) {
this.openmct.time.bounds({
@ -188,7 +186,11 @@ export default {
message = 'Time bound values changed to fixed timespan mode';
}
this.openmct.notifications.alert(message);
if (message.length) {
this.openmct.notifications.alert(message);
}
window.location.href = link;
},
formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat);

View File

@ -0,0 +1,70 @@
<template>
<div
v-if="notifications.length > 0"
class="c-indicator c-indicator--clickable icon-bell"
:class="[severityClass]"
>
<span class="c-indicator__label">
<button @click="toggleNotificationsList(true)">
{{ notificationsCountMessage(notifications.length) }}
</button>
<button @click="dismissAllNotifications()">
Clear All
</button>
</span>
<span class="c-indicator__count">{{ notifications.length }}</span>
<notifications-list
v-if="showNotificationsOverlay"
:notifications="notifications"
@close="toggleNotificationsList"
@clear-all="dismissAllNotifications"
/>
</div>
</template>
<script>
import NotificationsList from './NotificationsList.vue';
export default {
inject: ['openmct'],
components: {
NotificationsList
},
data() {
return {
notifications: this.openmct.notifications.notifications,
highest: this.openmct.notifications.highest,
showNotificationsOverlay: false
}
},
computed: {
severityClass() {
return `s-status-${this.highest.severity}`;
}
},
mounted() {
this.openmct.notifications.on('notification', this.updateNotifications);
this.openmct.notifications.on('dismiss-all', this.updateNotifications);
},
methods: {
dismissAllNotifications() {
this.openmct.notifications.dismissAllNotifications();
},
toggleNotificationsList(flag) {
this.showNotificationsOverlay = flag;
},
updateNotifications() {
this.notifications = this.openmct.notifications.notifications;
this.highest = this.openmct.notifications.highest;
},
notificationsCountMessage(count) {
if (count > 1) {
return `${count} Notifications`;
} else {
return `${count} Notification`;
}
}
}
}
</script>

View File

@ -0,0 +1,85 @@
<template>
<div
class="c-message"
:class="'message-severity-' + notification.model.severity"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<span>{{ notification.model.timestamp }}</span>
</div>
<div class="c-ne__content">
<div class="w-message-contents">
<div class="c-message__top-bar">
<div class="c-message__title">{{ notification.model.message }}</div>
</div>
<div class="message-body">
<progress-bar
v-if="isProgressNotification"
:model="progressObject"
/>
</div>
</div>
</div>
<div class="c-overlay__button-bar">
<button
v-for="(dialogOption, index) in notification.model.options"
:key="index"
class="c-button"
@click="dialogOption.callback()"
>
{{ dialogOption.label }}
</button>
<button
v-if="notification.model.primaryOption"
class="c-button c-button--major"
@click="notification.model.primaryOption.callback()"
>
{{ notification.model.primaryOption.label }}
</button>
</div>
</div>
</div>
</template>
<script>
import ProgressBar from '../../../ui/components/ProgressBar.vue';
export default {
components: {
ProgressBar
},
props:{
notification: {
type: Object,
required: true
}
},
data() {
return {
isProgressNotification: false,
progressPerc: this.notification.model.progressPerc,
progressText: this.notification.model.progressText
}
},
computed: {
progressObject() {
return {
progressPerc: this.progressPerc,
progressText: this.progressText
}
}
},
mounted() {
if (this.notification.model.progressPerc) {
this.isProgressNotification = true;
this.notification.on('progress', this.updateProgressBar)
}
},
methods: {
updateProgressBar(progressPerc, progressText) {
this.progressPerc = progressPerc;
this.progressText = progressText;
}
}
}
</script>

View File

@ -0,0 +1,69 @@
<template>
<div class="t-message-list c-overlay__contents">
<div class="c-overlay__top-bar">
<div class="c-overlay__dialog-title">Notifications</div>
<div class="c-overlay__dialog-hint">
{{ notificationsCountDisplayMessage(notifications.length) }}
</div>
</div>
<div class="w-messages c-overlay__messages">
<notification-message
v-for="notification in notifications"
:key="notification.model.timestamp"
:notification="notification"
/>
</div>
</div>
</template>
<script>
import NotificationMessage from './NotificationMessage.vue';
export default {
components: {
NotificationMessage
},
inject: ['openmct'],
props: {
notifications: {
type: Array,
required: true
}
},
data() {
return {}
},
mounted() {
this.openOverlay();
},
methods: {
openOverlay() {
this.overlay = this.openmct.overlays.overlay({
element: this.$el,
size: 'large',
dismissable: true,
buttons: [
{
label: 'Clear All Notifications',
emphasis: true,
callback:() => {
this.$emit('clear-all');
this.overlay.dismiss();
}
}
],
onDestroy: () => {
this.$emit('close', false);
}
});
},
notificationsCountDisplayMessage(count) {
if (count > 1 || count === 0) {
return `Displaying ${count} notifications`;
} else {
return `Displaying ${count} notification`;
}
}
}
}
</script>

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -19,15 +19,25 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Vue from 'vue';
import NotificationIndicator from './components/NotificationIndicator.vue';
define(
[],
function () {
export default function plugin() {
return function install(openmct) {
let component = new Vue ({
provide: {
openmct
},
components: {
NotificationIndicator: NotificationIndicator
},
template: '<NotificationIndicator></NotificationIndicator>'
}),
indicator = {
key: 'notifications-indicator',
element: component.$mount().$el
};
function NotificationIndicator() {}
NotificationIndicator.template = 'notificationIndicatorTemplate';
return NotificationIndicator;
}
);
openmct.indicators.add(indicator);
};
}

View File

@ -0,0 +1,71 @@
/*****************************************************************************
* 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 NotificationIndicatorPlugin from './plugin.js';
import Vue from 'vue';
import {
createOpenMct
} from 'testUtils';
describe('the plugin', () => {
let notificationIndicatorPlugin,
openmct,
indicatorObject,
indicatorElement,
parentElement,
mockMessages = ['error', 'test', 'notifications'];
beforeEach((done) => {
openmct = createOpenMct();
notificationIndicatorPlugin = new NotificationIndicatorPlugin();
openmct.install(notificationIndicatorPlugin);
parentElement = document.createElement('div');
indicatorObject = openmct.indicators.indicatorObjects.find(indicator => indicator.key === 'notifications-indicator');
indicatorElement = indicatorObject.element;
openmct.on('start', () => {
mockMessages.forEach(message => {
openmct.notifications.error(message);
});
done();
});
openmct.startHeadless();
});
describe('the indicator plugin element', () => {
beforeEach(() => {
parentElement.append(indicatorElement);
return Vue.nextTick();
});
it('notifies the user of the number of notifications', () => {
let notificationCountElement = parentElement.querySelector('.c-indicator__count');
expect(notificationCountElement.innerText).toEqual(mockMessages.length.toString());
});
});
});

View File

@ -324,7 +324,15 @@ define([
* a point to the end without dupe checking.
*/
add: function (point, appendOnly) {
var insertIndex = this.data.length;
var insertIndex = this.data.length,
currentYVal = this.getYVal(point),
lastYVal = this.getYVal(this.data[insertIndex - 1]);
if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) {
console.warn('[Plot] Invalid Y Values detected');
return;
}
if (!appendOnly) {
insertIndex = this.sortedIndex(point);
if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) {
@ -334,11 +342,21 @@ define([
return;
}
}
this.updateStats(point);
point.mctLimitState = this.evaluate(point);
this.data.splice(insertIndex, 0, point);
this.emit('add', point, insertIndex, this);
},
/**
*
* @private
*/
isValueInvalid: function (val) {
return Number.isNaN(val) || val === undefined;
},
/**
* Remove a point from the data array and notify listeners.
* @private

View File

@ -182,21 +182,6 @@ define([
this.set('format', yFormat.format.bind(yFormat));
this.set('values', yMetadata.values);
if (!label) {
var labelUnits = series.map(function (s) {
return s.metadata.value(s.get('yKey')).units;
}).reduce(function (a, b) {
if (a === undefined) {
return b;
}
if (a === b) {
return a;
}
return '';
}, undefined);
if (labelUnits) {
this.set('label', labelUnits);
return;
}
var labelName = series.map(function (s) {
return s.metadata.value(s.get('yKey')).name;
}).reduce(function (a, b) {
@ -208,7 +193,28 @@ define([
}
return '';
}, undefined);
this.set('label', labelName);
if (labelName) {
this.set('label', labelName);
return;
}
var labelUnits = series.map(function (s) {
return s.metadata.value(s.get('yKey')).units;
}).reduce(function (a, b) {
if (a === undefined) {
return b;
}
if (a === b) {
return a;
}
return '';
}, undefined);
if (labelUnits) {
this.set('label', labelUnits);
return;
}
}
},
defaults: function (options) {

View File

@ -51,7 +51,8 @@ define([
'./conditionWidget/plugin',
'./themes/espresso',
'./themes/maelstrom',
'./themes/snow'
'./themes/snow',
'./notificationIndicator/plugin'
], function (
_,
UTCTimeSystem,
@ -83,7 +84,8 @@ define([
ConditionWidgetPlugin,
Espresso,
Maelstrom,
Snow
Snow,
NotificationIndicator
) {
var bundleMap = {
LocalStorage: 'platform/persistence/local',
@ -192,6 +194,7 @@ define([
plugins.Snow = Snow.default;
plugins.Condition = ConditionPlugin.default;
plugins.ConditionWidget = ConditionWidgetPlugin.default;
plugins.NotificationIndicator = NotificationIndicator.default;
return plugins;
});

View File

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

View File

@ -2,7 +2,7 @@
<div class="c-inspect-properties">
<template v-if="isEditing">
<div class="c-inspect-properties__header">
Table Column Size
Table Layout
</div>
<ul class="c-inspect-properties__section">
<li class="c-inspect-properties__row">
@ -21,6 +21,22 @@
>
</div>
</li>
<li class="c-inspect-properties__row">
<div
class="c-inspect-properties__label"
title="Show or hide headers"
>
<label for="header-visibility">Hide Header</label>
</div>
<div class="c-inspect-properties__value">
<input
id="header-visibility"
type="checkbox"
:checked="configuration.hideHeaders === true"
@change="toggleHeaderVisibility"
>
</div>
</li>
</ul>
<div class="c-inspect-properties__header">
Table Column Visibility
@ -120,6 +136,12 @@ export default {
let column = new TelemetryTableColumn(this.openmct, metadatum);
this.tableConfiguration.addSingleColumnForObject(telemetryObject, column);
});
},
toggleHeaderVisibility() {
let hideHeaders = this.configuration.hideHeaders;
this.configuration.hideHeaders = !hideHeaders;
this.tableConfiguration.updateConfiguration(this.configuration);
}
}
}

View File

@ -175,7 +175,7 @@ export default {
let contextualObjectPath = this.objectPath.slice();
contextualObjectPath.unshift(domainObject);
this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions());
this.openmct.menus._showObjectMenu(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions());
});
}
}

View File

@ -139,6 +139,7 @@
></div>
<!-- Headers table -->
<div
v-show="!hideHeaders"
ref="headersTable"
class="c-telemetry-table__headers-w js-table__headers-w"
:style="{ 'max-width': widthWithScroll}"
@ -336,7 +337,8 @@ export default {
markCounter: 0,
paused: false,
markedRows: [],
isShowingMarkedRowsOnly: false
isShowingMarkedRowsOnly: false,
hideHeaders: configuration.hideHeaders
}
},
computed: {
@ -615,6 +617,7 @@ export default {
},
updateConfiguration(configuration) {
this.isAutosizeEnabled = configuration.autosize;
this.hideHeaders = configuration.hideHeaders;
this.updateHeaders();
this.$nextTick().then(this.calculateColumnWidths);

View File

@ -25,8 +25,7 @@
</template>
<script>
import ConditionalStylesView from '../../plugins/condition/components/inspector/ConditionalStylesView.vue';
import MultiSelectStylesView from '../../plugins/condition/components/inspector/MultiSelectStylesView.vue';
import StylesView from '../../plugins/condition/components/inspector/StylesView.vue';
import Vue from 'vue';
export default {
@ -46,7 +45,6 @@ export default {
methods: {
updateSelection(selection) {
if (selection.length > 0 && selection[0].length > 0) {
let template = selection.length > 1 ? '<multi-select-styles-view></multi-select-styles-view>' : '<conditional-styles-view></conditional-styles-view>';
if (this.component) {
this.component.$destroy();
this.component = undefined;
@ -61,10 +59,9 @@ export default {
},
el: viewContainer,
components: {
ConditionalStylesView,
MultiSelectStylesView
StylesView
},
template: template
template: '<styles-view/>'
});
}
}

View File

@ -267,7 +267,7 @@ export default {
});
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.openmct.router.path, event.clientX, event.clientY);
this.openmct.menus._showObjectMenu(this.openmct.router.path, event.clientX, event.clientY);
},
goToParent() {
window.location.hash = this.parentUrl;

View File

@ -91,6 +91,7 @@ export default {
},
mounted() {
this.openmct.notifications.on('notification', this.showNotification);
this.openmct.notifications.on('dismiss-all', this.clearModel);
},
methods: {
showNotification(notification) {

View File

@ -30,7 +30,7 @@ export default {
showContextMenu(event) {
event.preventDefault();
event.stopPropagation();
this.openmct.contextMenu._showContextMenuForObjectPath(this.objectPath, event.clientX, event.clientY);
this.openmct.menus._showObjectMenu(this.objectPath, event.clientX, event.clientY);
}
}
};

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.menus.registerObjectAction(new PreviewAction(openmct));
openmct.menus.registerObjectAction(new ViewHistoricalDataAction(openmct));
};
}