Compare commits

..

35 Commits

Author SHA1 Message Date
charlesh88
d0bdd1f1b6 Range sliders fixed for Chrome and Firefox
- New cross-browser approach to range track / knob alignment;
- Markup mods to support "type" icons for each range;
- Moved $sliderTrack mixin from _mixins to _controls, modded for new
track / knob approach;
2020-06-09 17:28:41 -07:00
Joshi
89e464431b Persist layers on page refresh since vue's destroy method is not called in this case 2020-06-08 10:23:26 -07:00
Joshi
6cd0e70b93 Merge branch 'master' of https://github.com/nasa/openmct into imagery-view-layers 2020-06-08 10:07:05 -07:00
charlesh88
25598663f0 Styling for Imagery controls and layers menus
- Added new glyph 'icon-image-telemetry';
- Note that this may conflict with changes from branch
`display-layout-improvements-52820` - be sure to merge changes from
both!;
2020-06-05 15:02:35 -07:00
Joshi
499bb77bc8 Reset layer visibility if object is not creatable 2020-06-05 14:11:06 -07:00
Joshi
66719bd182 Add more tests for not creatable imagery objects 2020-06-05 13:50:34 -07:00
Joshi
24d19cedbf Use domainObject configuration to identify if we should save layers 2020-06-05 13:28:30 -07:00
Joshi
61d5d05045 Merge branch 'imagery-view-layers' of https://github.com/nasa/openmct into imagery-view-layers 2020-06-05 12:04:48 -07:00
Joshi
bea52b8a4c Removing logging 2020-06-05 12:04:33 -07:00
charlesh88
61de8c0bfb Merge branch 'imagery-view-layers' of https://github.com/nasa/openmct into imagery-view-layers 2020-06-05 12:02:47 -07:00
charlesh88
edaee545b9 Styling for Imagery controls and layers menus WIP
- Fixed menu color constants, now using filter instead of colors;
- Fixed coloring in menus, unit tested in Snow and Espresso themes;
- Normalized and fixed overlay colors;
- Scrollbar colors tweaked for improved legibility;
2020-06-05 12:02:29 -07:00
Joshi
d44f05d117 Updating logging 2020-06-05 11:59:39 -07:00
Joshi
a82b7abc08 Adds some logging for debugging 2020-06-05 11:54:11 -07:00
Joshi
c9988148f2 Fixes failing tests and linting issues 2020-06-05 11:49:35 -07:00
charlesh88
0909f7c6db Styling for Imagery controls and layers menus WIP
- Merge latest changes;
2020-06-04 15:13:24 -07:00
charlesh88
2ad4af71c6 Merge branch 'imagery-view-layers' of https://github.com/nasa/openmct into imagery-view-layers 2020-06-04 15:09:36 -07:00
Joshi
a81df8dae8 Reset visible layers on destroy 2020-06-04 15:09:20 -07:00
charlesh88
d4dc75d5ae Styling for Imagery controls and layers menus WIP
- Synced spec.js file CSS classname refs with related markup, utilizing
js* class naming strategy;
2020-06-04 15:08:15 -07:00
Joshi
92bdc56d46 Use object type to determine if the layers should be persisted. 2020-06-04 15:04:19 -07:00
charlesh88
11fc87eecb Styling for Imagery controls and layers menus WIP
- Merge latest and resolve conflicts;
2020-06-04 14:48:23 -07:00
Joshi
5943393b23 Show only visible layers 2020-06-04 14:20:16 -07:00
Joshi
0085616189 Fixes persisted layer matching 2020-06-04 14:16:23 -07:00
Joshi
d4b6cf70b5 Persist layer visibility 2020-06-04 14:02:02 -07:00
Joshi
86984eb3e9 Persist image layers for creatable domain objects 2020-06-04 10:37:17 -07:00
charlesh88
4cc4898c3c Styling for Imagery controls and layers menus WIP
- Significant mods to markup and CSS;
- Removed legacy CSS from markup;
2020-06-03 22:43:52 -07:00
Joshi
f1570a2897 Merge branch 'master' of https://github.com/nasa/openmct into imagery-view-layers 2020-06-02 14:36:20 -07:00
Joshi
300eac03ba Removes fdescribe and updates one test 2020-06-02 14:27:22 -07:00
Joshi
3f5b937873 Add imagery for layers 2020-06-02 14:15:42 -07:00
Joshi
3407c3bb7a Adds tests for image layers and filters menus 2020-06-02 14:06:14 -07:00
Joshi
1812b77826 Adds new files that were missed in previous commit 2020-06-02 10:59:10 -07:00
Joshi
3cfe3255f3 Merge branch 'imagery-view-layers' of https://github.com/nasa/openmct into imagery-view-layers 2020-06-02 10:58:27 -07:00
Joshi
a35b15f535 Fixes layers metadata 2020-06-02 10:58:06 -07:00
Shefali Joshi
153cf6095a Merge branch 'master' into imagery-view-layers 2020-06-01 12:29:16 -07:00
Shefali Joshi
3d853b1c6e Merge branch 'master' into imagery-view-layers 2020-05-29 16:09:22 -07:00
Joshi
0d0ec298ab Adds 3 new components: imagery-layers, imagery-settings and imagery-view-menu-switcher.
ImageViewLayout now shows the new menu switchers to display the filters menu and layers menu
For example imagery, the layers come from telemetry metadata (hardcoded to show images from the dist/images folder)
2020-05-29 16:04:43 -07:00
133 changed files with 3107 additions and 5407 deletions

View File

@@ -2,7 +2,7 @@ version: 2
jobs:
build:
docker:
- image: circleci/node:13-browsers
- image: circleci/node:8-browsers
environment:
CHROME_BIN: "/usr/bin/google-chrome"
steps:
@@ -11,12 +11,12 @@ jobs:
name: Update npm
command: 'sudo npm install -g npm@latest'
- restore_cache:
key: dependency-cache-13-{{ checksum "package.json" }}
key: dependency-cache-{{ checksum "package.json" }}
- run:
name: Installing dependencies (npm install)
command: npm install
- save_cache:
key: dependency-cache-13-{{ checksum "package.json" }}
key: dependency-cache-{{ checksum "package.json" }}
paths:
- node_modules
- run:

View File

@@ -136,7 +136,7 @@ this repository. This is verified by the command line build.
#### Code Guidelines
The following guidelines are provided for anyone contributing source code to the Open MCT project:
JavaScript sources in Open MCT should:
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).
@@ -178,7 +178,7 @@ The following guidelines are provided for anyone contributing source code to the
code, and present these in the following order:
* First, variable declarations and initialization.
* Secondly, imperative statements.
* Finally, the returned value. A single return statement at the end of the function should be used, except where an early return would improve code clarity.
* Finally, the returned value. Functions should only have a single return statement.
1. Avoid the use of "magic" values.
eg.
```JavaScript
@@ -189,7 +189,7 @@ The following guidelines are provided for anyone contributing source code to the
```JavaScript
if (responseCode === 401)
```
1. Use the ternary operator only for simple cases such as variable assignment. Nested ternaries should be avoided in all cases.
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.
@@ -226,9 +226,9 @@ typically from the author of the change and its reviewer.
Automated testing shall occur whenever changes are merged into the main
development branch and must be confirmed alongside any pull request.
Automated tests are tests which exercise plugins, API, and utility classes.
Tests are subject to code review along with the actual implementation, to
ensure that tests are applicable and useful.
Automated tests are typically unit tests which exercise individual software
components. Tests are subject to code review along with the actual
implementation, to ensure that tests are applicable and useful.
Examples of useful tests:
* Tests which replicate bugs (or their root causes) to verify their
@@ -238,26 +238,8 @@ Examples of useful tests:
* Tests which verify expected interactions with other components in the
system.
#### Guidelines
* 100% statement coverage is achievable and desirable.
* Do blackbox testing. Test external behaviors, not internal details. Write tests that describe what your plugin is supposed to do. How it does this doesn't matter, so don't test it.
* Unit test specs for plugins should be defined at the plugin level. Start with one test spec per plugin named pluginSpec.js, and as this test spec grows too big, break it up into multiple test specs that logically group related tests.
* Unit tests for API or for utility functions and classes may be defined at a per-source file level.
* Wherever possible only use and mock public API, builtin functions, and UI in your test specs. Do not directly invoke any private functions. ie. only call or mock functions and objects exposed by openmct.* (eg. openmct.telemetry, openmct.objectView, etc.), and builtin browser functions (fetch, requestAnimationFrame, setTimeout, etc.).
* Where builtin functions have been mocked, be sure to clear them between tests.
* Test at an appropriate level of isolation. Eg.
* If youre testing a view, you do not need to test the whole application UI, you can just fetch the view provider using the public API and render the view into an element that you have created.
* You do not need to test that the view switcher works, there should be separate tests for that.
* You do not need to test that telemetry providers work, you can mock openmct.telemetry.request() to feed test data to the view.
* Use your best judgement when deciding on appropriate scope.
* Automated tests for plugins should start by actually installing the plugin being tested, and then test that installing the plugin adds the desired features and behavior to Open MCT, observing the above rules.
* All variables used in a test spec, including any instances of the Open MCT API should be declared inside of an appropriate block scope (not at the root level of the source file), and should be initialized in the relevant beforeEach block. `beforeEach` is preferable to `beforeAll` to avoid leaking of state between tests.
* A `afterEach` or `afterAll` should be used to do any clean up necessary to prevent leakage of state between test specs. This can happen when functions on `window` are wrapped, or when the URL is changed. [A convenience function](https://github.com/nasa/openmct/blob/master/src/utils/testing.js#L59) is provided for resetting the URL and clearing builtin spies between tests.
* If writing unit tests for legacy Angular code be sure to follow [best practices in order to avoid memory leaks](https://www.thecodecampus.de/blog/avoid-memory-leaks-angularjs-unit-tests/).
#### Examples
* [Example of an automated test spec for an object view plugin](https://github.com/nasa/openmct/blob/master/src/plugins/telemetryTable/pluginSpec.js)
* [Example of an automated test spec for API](https://github.com/nasa/openmct/blob/master/src/api/time/TimeAPISpec.js)
During automated testing, code coverage metrics will be reported. Line
coverage must remain at or above 80%.
### Commit Message Standards

View File

@@ -1,6 +1,6 @@
# Open MCT License
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, Copyright (c) 2014-2019, 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.

View File

@@ -125,22 +125,3 @@ A release is not closed until both categories have been performed on
the latest snapshot of the software, _and_ no issues labelled as
["blocker" or "critical"](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
remain open.
### Testathons
Testathons can be used as a means of performing per-sprint and per-release testing.
#### Timing
For per-sprint testing, a testathon is typically performed at the beginning of the third week of a sprint, and again later that week to verify any fixes. For per-release testing, a testathon is typically performed prior to any formal testing processes that are applicable to that release.
#### Process
1. Prior to the scheduled testathon, a list will be compiled of all issues that are closed and unverified.
2. For each issue, testers should review the associated PR for testing instructions. See the contributing guide for instructions on [pull requests](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md#merging).
3. As each issue is verified via testing, any team members testing it should leave a comment on that issue indicating that it has been verified fixed.
4. If a bug is found that relates to an issue being tested, notes should be included on the associated issue, and the issue should be reopened. Bug notes should include reproduction steps.
5. For any bugs that are not obviously related to any of the issues under test, a new issue should be created with details about the bug, including reproduction steps. If unsure about whether a bug relates to an issue being tested, just create a new issue.
6. At the end of the testathon, triage will take place, where all tested issues will be reviewed.
7. If verified fixed, an issue will remain closed, and will have the “unverified” label removed.
8. For any bugs found, a severity will be assigned.
9. A second testathon will be scheduled for later in the week that will aim to address all issues identified as blockers, as well as any other issues scoped by the team during triage.
10. Any issues that were not tested will remain "unverified" and will be picked up in the next testathon.

View File

@@ -28,16 +28,6 @@ define([
domain: 2
}
},
// Need to enable "LocalTimeSystem" plugin to make use of this
// {
// key: "local",
// name: "Time",
// format: "local-format",
// source: "utc",
// hints: {
// domain: 3
// }
// },
{
key: "sin",
name: "Sine",
@@ -71,15 +61,6 @@ define([
domain: 1
}
},
{
key: "local",
name: "Time",
format: "utc",
source: "utc",
hints: {
domain: 2
}
},
{
key: "state",
source: "value",

View File

@@ -135,6 +135,20 @@ define([
name: 'Image',
key: 'url',
format: 'image',
layers: [
{
source: 'dist/imagery/example-imagery-layer-16x9.png',
name: '16:9'
},
{
source: 'dist/imagery/example-imagery-layer-safe.png',
name: 'Safe'
},
{
source: 'dist/imagery/example-imagery-layer-scale.png',
name: 'Scale'
}
],
hints: {
image: 1
}

View File

@@ -34,8 +34,8 @@
<body>
</body>
<script>
const THIRTY_SECONDS = 30 * 1000;
const THIRTY_MINUTES = THIRTY_SECONDS * 60;
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_MINUTES = 30 * 60 * 1000;
[
'example/eventGenerator'
@@ -63,39 +63,7 @@
bounds: {
start: Date.now() - THIRTY_MINUTES,
end: Date.now()
},
// commonly used bounds can be stored in history
// bounds (start and end) can accept either a milliseconds number
// or a callback function returning a milliseconds number
// a function is useful for invoking Date.now() at exact moment of preset selection
presets: [
{
label: 'Last Day',
bounds: {
start: () => Date.now() - 1000 * 60 * 60 * 24,
end: () => Date.now()
}
},
{
label: 'Last 2 hours',
bounds: {
start: () => Date.now() - 1000 * 60 * 60 * 2,
end: () => Date.now()
}
},
{
label: 'Last hour',
bounds: {
start: () => Date.now() - 1000 * 60 * 60,
end: () => Date.now()
}
}
],
// maximum recent bounds to retain in conductor history
records: 10,
// maximum duration between start and end bounds
// for utc-based time systems this is in milliseconds
limit: 1000 * 60 * 60 * 24
}
},
{
name: "Realtime",
@@ -103,7 +71,7 @@
clock: 'local',
clockOffsets: {
start: - THIRTY_MINUTES,
end: THIRTY_SECONDS
end: FIVE_MINUTES
}
}
]

View File

@@ -4,7 +4,7 @@
"description": "The Open MCT core platform",
"dependencies": {},
"devDependencies": {
"angular": ">=1.8.0",
"angular": "1.7.9",
"angular-route": "1.4.14",
"babel-eslint": "8.2.6",
"comma-separated-values": "^3.6.4",
@@ -84,10 +84,10 @@
"build:prod": "cross-env NODE_ENV=production webpack",
"build:dev": "webpack",
"build:watch": "webpack --watch",
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
"test": "karma start --single-run",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run",
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
"test:coverage": "./scripts/test-coverage.sh",
"test:watch": "karma start --no-single-run",
"verify": "concurrently 'npm:test' 'npm:lint'",
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
"otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",

View File

@@ -81,15 +81,10 @@ define(
* context.
*/
PropertiesAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject,
type = domainObject && domainObject.getCapability('type'),
creatable = type && type.hasFeature('creation');
if (domainObject && domainObject.model && domainObject.model.locked) {
return false;
}
// Only allow creatable types to be edited
return domainObject && creatable;
};

View File

@@ -40,18 +40,7 @@ define(
}
MoveAction.prototype = Object.create(AbstractComposeAction.prototype);
MoveAction.appliesTo = function (context) {
var applicableObject =
context.selectedObject || context.domainObject;
if (applicableObject && applicableObject.model.locked) {
return false;
}
return Boolean(applicableObject &&
applicableObject.hasCapability('context'));
};
MoveAction.appliesTo = AbstractComposeAction.appliesTo;
return MoveAction;
}

View File

@@ -216,14 +216,8 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
};
ImportAsJSONAction.appliesTo = function (context) {
let domainObject = context.domainObject;
if (domainObject && domainObject.model.locked) {
return false;
}
return domainObject !== undefined &&
domainObject.hasCapability("composition");
return context.domainObject !== undefined &&
context.domainObject.hasCapability("composition");
};
return ImportAsJSONAction;

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"
]
}

2
scripts/test-coverage.sh Executable file
View File

@@ -0,0 +1,2 @@
export NODE_OPTIONS=--max_old_space_size=4096
cross-env COVERAGE=true karma start --single-run

View File

@@ -266,9 +266,7 @@ define([
this.install(this.plugins.WebPage());
this.install(this.plugins.Condition());
this.install(this.plugins.ConditionWidget());
this.install(this.plugins.URLTimeSettingsSynchronizer());
this.install(this.plugins.NotificationIndicator());
this.install(this.plugins.NewFolderAction());
}
MCT.prototype = Object.create(EventEmitter.prototype);
@@ -435,10 +433,6 @@ define([
plugin(this);
};
MCT.prototype.destroy = function () {
this.emit('destroy');
};
MCT.prototype.plugins = plugins;
return MCT;

View File

@@ -23,7 +23,7 @@
define([
'./plugins/plugins',
'legacyRegistry',
'utils/testing'
'testUtils'
], function (plugins, legacyRegistry, testUtils) {
describe("MCT", function () {
var openmct;
@@ -32,10 +32,6 @@ define([
var mockListener;
var oldBundles;
beforeAll(() => {
testUtils.resetApplicationState();
});
beforeEach(function () {
mockPlugin = jasmine.createSpy('plugin');
mockPlugin2 = jasmine.createSpy('plugin2');
@@ -56,7 +52,6 @@ define([
legacyRegistry.delete(bundle);
}
});
testUtils.resetApplicationState(openmct);
});
it("exposes plugins", function () {

View File

@@ -29,6 +29,7 @@ define([
'./capabilities/APICapabilityDecorator',
'./policies/AdaptedViewPolicy',
'./runs/AlternateCompositionInitializer',
'./runs/TimeSettingsURLHandler',
'./runs/TypeDeprecationChecker',
'./runs/LegacyTelemetryProvider',
'./runs/RegisterLegacyTypes',
@@ -45,6 +46,7 @@ define([
APICapabilityDecorator,
AdaptedViewPolicy,
AlternateCompositionInitializer,
TimeSettingsURLHandler,
TypeDeprecationChecker,
LegacyTelemetryProvider,
RegisterLegacyTypes,
@@ -132,6 +134,16 @@ define([
implementation: AlternateCompositionInitializer,
depends: ["openmct"]
},
{
implementation: function (openmct, $location, $rootScope) {
return new TimeSettingsURLHandler(
openmct.time,
$location,
$rootScope
);
},
depends: ["openmct", "$location", "$rootScope"]
},
{
implementation: LegacyTelemetryProvider,
depends: [

View File

@@ -0,0 +1,150 @@
/*****************************************************************************
* 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([
'lodash'
], function (
_
) {
// Parameter names in query string
var SEARCH = {
MODE: 'tc.mode',
TIME_SYSTEM: 'tc.timeSystem',
START_BOUND: 'tc.startBound',
END_BOUND: 'tc.endBound',
START_DELTA: 'tc.startDelta',
END_DELTA: 'tc.endDelta'
};
var TIME_EVENTS = ['bounds', 'timeSystem', 'clock', 'clockOffsets'];
// Used to shorthand calls to $location, which clears null parameters
var NULL_PARAMETERS = { key: null, start: null, end: null };
/**
* Communicates settings from the URL to the time API,
* and vice versa.
*/
function TimeSettingsURLHandler(time, $location, $rootScope) {
this.time = time;
this.$location = $location;
$rootScope.$on('$locationChangeSuccess', this.updateTime.bind(this));
TIME_EVENTS.forEach(function (event) {
this.time.on(event, this.updateQueryParams.bind(this));
}, this);
this.updateTime(); // Initialize
}
TimeSettingsURLHandler.prototype.updateQueryParams = function () {
var clock = this.time.clock();
var fixed = !clock;
var mode = fixed ? 'fixed' : clock.key;
var timeSystem = this.time.timeSystem() || NULL_PARAMETERS;
var bounds = fixed ? this.time.bounds() : NULL_PARAMETERS;
var deltas = fixed ? NULL_PARAMETERS : this.time.clockOffsets();
bounds = bounds || NULL_PARAMETERS;
deltas = deltas || NULL_PARAMETERS;
if (deltas.start) {
deltas = { start: -deltas.start, end: deltas.end };
}
this.$location.search(SEARCH.MODE, mode);
this.$location.search(SEARCH.TIME_SYSTEM, timeSystem.key);
this.$location.search(SEARCH.START_BOUND, bounds.start);
this.$location.search(SEARCH.END_BOUND, bounds.end);
this.$location.search(SEARCH.START_DELTA, deltas.start);
this.$location.search(SEARCH.END_DELTA, deltas.end);
};
TimeSettingsURLHandler.prototype.parseQueryParams = function () {
var searchParams = _.pick(this.$location.search(), Object.values(SEARCH));
var parsedParams = {
clock: searchParams[SEARCH.MODE],
timeSystem: searchParams[SEARCH.TIME_SYSTEM]
};
if (!isNaN(parseInt(searchParams[SEARCH.START_DELTA], 0xA)) &&
!isNaN(parseInt(searchParams[SEARCH.END_DELTA], 0xA))) {
parsedParams.clockOffsets = {
start: -searchParams[SEARCH.START_DELTA],
end: +searchParams[SEARCH.END_DELTA]
};
}
if (!isNaN(parseInt(searchParams[SEARCH.START_BOUND], 0xA)) &&
!isNaN(parseInt(searchParams[SEARCH.END_BOUND], 0xA))) {
parsedParams.bounds = {
start: +searchParams[SEARCH.START_BOUND],
end: +searchParams[SEARCH.END_BOUND]
};
}
return parsedParams;
};
TimeSettingsURLHandler.prototype.updateTime = function () {
var params = this.parseQueryParams();
if (_.isEqual(params, this.last)) {
return; // Do nothing;
}
this.last = params;
if (!params.timeSystem) {
this.updateQueryParams();
} else if (params.clock === 'fixed' && params.bounds) {
if (!this.time.timeSystem() ||
this.time.timeSystem().key !== params.timeSystem) {
this.time.timeSystem(
params.timeSystem,
params.bounds
);
} else if (!_.isEqual(this.time.bounds(), params.bounds)) {
this.time.bounds(params.bounds);
}
if (this.time.clock()) {
this.time.stopClock();
}
} else if (params.clockOffsets) {
if (params.clock === 'fixed') {
this.time.stopClock();
return;
}
if (!this.time.clock() ||
this.time.clock().key !== params.clock) {
this.time.clock(params.clock, params.clockOffsets);
} else if (!_.isEqual(this.time.clockOffsets(), params.clockOffsets)) {
this.time.clockOffsets(params.clockOffsets);
}
if (!this.time.timeSystem() ||
this.time.timeSystem().key !== params.timeSystem) {
this.time.timeSystem(params.timeSystem);
}
} else {
// Neither found, update from timeSystem.
this.updateQueryParams();
}
};
return TimeSettingsURLHandler;
});

View File

@@ -0,0 +1,576 @@
/*****************************************************************************
* 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([
'./TimeSettingsURLHandler',
'../../api/time/TimeAPI'
], function (
TimeSettingsURLHandler,
TimeAPI
) {
describe("TimeSettingsURLHandler", function () {
var time;
var $location;
var $rootScope;
var search;
var handler; // eslint-disable-line
var clockA;
var clockB;
var timeSystemA;
var timeSystemB;
var boundsA;
var boundsB;
var offsetsA;
var offsetsB;
var initialize;
var triggerLocationChange;
beforeEach(function () {
clockA = jasmine.createSpyObj('clockA', ['on', 'off']);
clockA.key = 'clockA';
clockA.currentValue = function () {
return 1000;
};
clockB = jasmine.createSpyObj('clockB', ['on', 'off']);
clockB.key = 'clockB';
clockB.currentValue = function () {
return 2000;
};
timeSystemA = {key: 'timeSystemA'};
timeSystemB = {key: 'timeSystemB'};
boundsA = {
start: 10,
end: 20
};
boundsB = {
start: 120,
end: 360
};
offsetsA = {
start: -100,
end: 0
};
offsetsB = {
start: -50,
end: 50
};
time = new TimeAPI();
[
'on',
'bounds',
'clockOffsets',
'timeSystem',
'clock',
'stopClock'
].forEach(function (method) {
spyOn(time, method).and.callThrough();
});
time.addTimeSystem(timeSystemA);
time.addTimeSystem(timeSystemB);
time.addClock(clockA);
time.addClock(clockB);
$location = jasmine.createSpyObj('$location', [
'search'
]);
$rootScope = jasmine.createSpyObj('$rootScope', [
'$on'
]);
search = {};
$location.search.and.callFake(function (key, value) {
if (arguments.length === 0) {
return search;
}
if (value === null) {
delete search[key];
} else {
search[key] = String(value);
}
return this;
});
expect(time.timeSystem()).toBeUndefined();
expect(time.bounds()).toEqual({});
expect(time.clockOffsets()).toBeUndefined();
expect(time.clock()).toBeUndefined();
initialize = function () {
handler = new TimeSettingsURLHandler(
time,
$location,
$rootScope
);
expect($rootScope.$on).toHaveBeenCalledWith(
'$locationChangeSuccess',
jasmine.any(Function)
);
triggerLocationChange = $rootScope.$on.calls.mostRecent().args[1];
};
});
it("initializes with missing time system", function () {
// This handles an odd transitory case where a url does not include
// a timeSystem. It's generally only experienced by those who
// based their code on the tutorial before it specified a time
// system.
search['tc.mode'] = 'clockA';
search['tc.timeSystem'] = undefined;
search['tc.startDelta'] = '123';
search['tc.endDelta'] = '456';
// We don't specify behavior right now other than "don't break."
expect(initialize).not.toThrow();
});
it("can initalize fixed mode from location", function () {
search['tc.mode'] = 'fixed';
search['tc.timeSystem'] = 'timeSystemA';
search['tc.startBound'] = '123';
search['tc.endBound'] = '456';
initialize();
expect(time.timeSystem).toHaveBeenCalledWith(
'timeSystemA',
{
start: 123,
end: 456
}
);
});
it("can initialize clock mode from location", function () {
search['tc.mode'] = 'clockA';
search['tc.timeSystem'] = 'timeSystemA';
search['tc.startDelta'] = '123';
search['tc.endDelta'] = '456';
initialize();
expect(time.clock).toHaveBeenCalledWith(
'clockA',
{
start: -123,
end: 456
}
);
expect(time.timeSystem).toHaveBeenCalledWith(
'timeSystemA'
);
});
it("can initialize fixed mode from time API", function () {
time.timeSystem(timeSystemA.key, boundsA);
initialize();
expect($location.search)
.toHaveBeenCalledWith('tc.mode', 'fixed');
expect($location.search)
.toHaveBeenCalledWith('tc.timeSystem', 'timeSystemA');
expect($location.search)
.toHaveBeenCalledWith('tc.startBound', 10);
expect($location.search)
.toHaveBeenCalledWith('tc.endBound', 20);
expect($location.search)
.toHaveBeenCalledWith('tc.startDelta', null);
expect($location.search)
.toHaveBeenCalledWith('tc.endDelta', null);
});
it("can initialize clock mode from time API", function () {
time.clock(clockA.key, offsetsA);
time.timeSystem(timeSystemA.key);
initialize();
expect($location.search)
.toHaveBeenCalledWith('tc.mode', 'clockA');
expect($location.search)
.toHaveBeenCalledWith('tc.timeSystem', 'timeSystemA');
expect($location.search)
.toHaveBeenCalledWith('tc.startBound', null);
expect($location.search)
.toHaveBeenCalledWith('tc.endBound', null);
expect($location.search)
.toHaveBeenCalledWith('tc.startDelta', 100);
expect($location.search)
.toHaveBeenCalledWith('tc.endDelta', 0);
});
describe('location changes in fixed mode', function () {
beforeEach(function () {
time.timeSystem(timeSystemA.key, boundsA);
initialize();
time.timeSystem.calls.reset();
time.bounds.calls.reset();
time.clock.calls.reset();
time.stopClock.calls.reset();
});
it("does not change on spurious location change", function () {
triggerLocationChange();
expect(time.timeSystem).not.toHaveBeenCalledWith(
'timeSystemA',
jasmine.any(Object)
);
expect(time.bounds).not.toHaveBeenCalledWith(
jasmine.any(Object)
);
expect(time.stopClock).not.toHaveBeenCalled();
});
it("updates timeSystem changes", function () {
search['tc.timeSystem'] = 'timeSystemB';
triggerLocationChange();
expect(time.timeSystem).toHaveBeenCalledWith(
'timeSystemB',
{
start: 10,
end: 20
}
);
});
it("updates bounds changes", function () {
search['tc.startBound'] = '100';
search['tc.endBound'] = '200';
triggerLocationChange();
expect(time.timeSystem).not.toHaveBeenCalledWith(
jasmine.anything(), jasmine.anything()
);
expect(time.bounds).toHaveBeenCalledWith({
start: 100,
end: 200
});
search['tc.endBound'] = '300';
triggerLocationChange();
expect(time.timeSystem).not.toHaveBeenCalledWith(
jasmine.anything(), jasmine.anything()
);
expect(time.bounds).toHaveBeenCalledWith({
start: 100,
end: 300
});
});
it("updates clock mode w/o timeSystem change", function () {
search['tc.mode'] = 'clockA';
search['tc.startDelta'] = '50';
search['tc.endDelta'] = '50';
delete search['tc.endBound'];
delete search['tc.startBound'];
triggerLocationChange();
expect(time.clock).toHaveBeenCalledWith(
'clockA',
{
start: -50,
end: 50
}
);
expect(time.timeSystem).not.toHaveBeenCalledWith(
jasmine.anything(), jasmine.anything()
);
});
it("updates clock mode and timeSystem", function () {
search['tc.mode'] = 'clockA';
search['tc.startDelta'] = '50';
search['tc.endDelta'] = '50';
search['tc.timeSystem'] = 'timeSystemB';
delete search['tc.endBound'];
delete search['tc.startBound'];
triggerLocationChange();
expect(time.clock).toHaveBeenCalledWith(
'clockA',
{
start: -50,
end: 50
}
);
expect(time.timeSystem).toHaveBeenCalledWith('timeSystemB');
});
});
describe('location changes in clock mode', function () {
beforeEach(function () {
time.clock(clockA.key, offsetsA);
time.timeSystem(timeSystemA.key);
initialize();
time.timeSystem.calls.reset();
time.bounds.calls.reset();
time.clock.calls.reset();
time.clockOffsets.calls.reset();
time.stopClock.calls.reset();
});
it("does not change on spurious location change", function () {
triggerLocationChange();
expect(time.timeSystem).not.toHaveBeenCalledWith(
'timeSystemA',
jasmine.any(Object)
);
expect(time.clockOffsets).not.toHaveBeenCalledWith(
jasmine.any(Object)
);
expect(time.clock).not.toHaveBeenCalledWith(
jasmine.any(Object)
);
expect(time.bounds).not.toHaveBeenCalledWith(
jasmine.any(Object)
);
});
it("changes time system", function () {
search['tc.timeSystem'] = 'timeSystemB';
triggerLocationChange();
expect(time.timeSystem).toHaveBeenCalledWith(
'timeSystemB'
);
expect(time.clockOffsets).not.toHaveBeenCalledWith(
jasmine.any(Object)
);
expect(time.clock).not.toHaveBeenCalledWith(
jasmine.any(Object)
);
expect(time.stopClock).not.toHaveBeenCalled();
expect(time.bounds).not.toHaveBeenCalledWith(
jasmine.any(Object)
);
});
it("changes offsets", function () {
search['tc.startDelta'] = '50';
search['tc.endDelta'] = '50';
triggerLocationChange();
expect(time.timeSystem).not.toHaveBeenCalledWith(
'timeSystemA',
jasmine.any(Object)
);
expect(time.clockOffsets).toHaveBeenCalledWith(
{
start: -50,
end: 50
}
);
expect(time.clock).not.toHaveBeenCalledWith(
jasmine.any(Object)
);
});
it("updates to fixed w/o timeSystem change", function () {
search['tc.mode'] = 'fixed';
search['tc.startBound'] = '234';
search['tc.endBound'] = '567';
delete search['tc.endDelta'];
delete search['tc.startDelta'];
triggerLocationChange();
expect(time.stopClock).toHaveBeenCalled();
expect(time.bounds).toHaveBeenCalledWith({
start: 234,
end: 567
});
expect(time.timeSystem).not.toHaveBeenCalledWith(
jasmine.anything(), jasmine.anything()
);
});
it("updates fixed and timeSystem", function () {
search['tc.mode'] = 'fixed';
search['tc.startBound'] = '234';
search['tc.endBound'] = '567';
search['tc.timeSystem'] = 'timeSystemB';
delete search['tc.endDelta'];
delete search['tc.startDelta'];
triggerLocationChange();
expect(time.stopClock).toHaveBeenCalled();
expect(time.timeSystem).toHaveBeenCalledWith(
'timeSystemB',
{
start: 234,
end: 567
}
);
});
it("updates clock", function () {
search['tc.mode'] = 'clockB';
triggerLocationChange();
expect(time.clock).toHaveBeenCalledWith(
'clockB',
{
start: -100,
end: 0
}
);
expect(time.timeSystem).not.toHaveBeenCalledWith(jasmine.anything());
});
it("updates clock and timeSystem", function () {
search['tc.mode'] = 'clockB';
search['tc.timeSystem'] = 'timeSystemB';
triggerLocationChange();
expect(time.clock).toHaveBeenCalledWith(
'clockB',
{
start: -100,
end: 0
}
);
expect(time.timeSystem).toHaveBeenCalledWith(
'timeSystemB'
);
});
it("updates clock and timeSystem and offsets", function () {
search['tc.mode'] = 'clockB';
search['tc.timeSystem'] = 'timeSystemB';
search['tc.startDelta'] = '50';
search['tc.endDelta'] = '50';
triggerLocationChange();
expect(time.clock).toHaveBeenCalledWith(
'clockB',
{
start: -50,
end: 50
}
);
expect(time.timeSystem).toHaveBeenCalledWith(
'timeSystemB'
);
});
it("stops the clock", function () {
// this is a robustness test, unsure if desired, requires
// user to be manually editing location strings.
search['tc.mode'] = 'fixed';
triggerLocationChange();
expect(time.stopClock).toHaveBeenCalled();
});
});
describe("location updates from time API in fixed", function () {
beforeEach(function () {
time.timeSystem(timeSystemA.key, boundsA);
initialize();
});
it("updates on bounds change", function () {
time.bounds(boundsB);
expect(search).toEqual({
'tc.mode': 'fixed',
'tc.startBound': '120',
'tc.endBound': '360',
'tc.timeSystem': 'timeSystemA'
});
});
it("updates on timeSystem change", function () {
time.timeSystem(timeSystemB, boundsA);
expect(search).toEqual({
'tc.mode': 'fixed',
'tc.startBound': '10',
'tc.endBound': '20',
'tc.timeSystem': 'timeSystemB'
});
time.timeSystem(timeSystemA, boundsB);
expect(search).toEqual({
'tc.mode': 'fixed',
'tc.startBound': '120',
'tc.endBound': '360',
'tc.timeSystem': 'timeSystemA'
});
});
it("Updates to clock", function () {
time.clock(clockA, offsetsA);
expect(search).toEqual({
'tc.mode': 'clockA',
'tc.startDelta': '100',
'tc.endDelta': '0',
'tc.timeSystem': 'timeSystemA'
});
});
});
describe("location updates from time API in fixed", function () {
beforeEach(function () {
time.clock(clockA.key, offsetsA);
time.timeSystem(timeSystemA.key);
initialize();
});
it("updates offsets", function () {
time.clockOffsets(offsetsB);
expect(search).toEqual({
'tc.mode': 'clockA',
'tc.startDelta': '50',
'tc.endDelta': '50',
'tc.timeSystem': 'timeSystemA'
});
});
it("updates clocks", function () {
time.clock(clockB, offsetsA);
expect(search).toEqual({
'tc.mode': 'clockB',
'tc.startDelta': '100',
'tc.endDelta': '0',
'tc.timeSystem': 'timeSystemA'
});
time.clock(clockA, offsetsB);
expect(search).toEqual({
'tc.mode': 'clockA',
'tc.startDelta': '50',
'tc.endDelta': '50',
'tc.timeSystem': 'timeSystemA'
});
});
it("updates timesystems", function () {
time.timeSystem(timeSystemB);
expect(search).toEqual({
'tc.mode': 'clockA',
'tc.startDelta': '100',
'tc.endDelta': '0',
'tc.timeSystem': 'timeSystemB'
});
});
it("stops the clock", function () {
time.stopClock();
expect(search).toEqual({
'tc.mode': 'fixed',
'tc.startBound': '900',
'tc.endBound': '1000',
'tc.timeSystem': 'timeSystemA'
});
});
});
});
});

View File

@@ -82,11 +82,6 @@
margin-left: $interiorMargin;
}
}
.c-button,
.c-click-icon {
filter: $overlayBrightnessAdjust;
}
}
body.desktop {
@@ -139,4 +134,4 @@ body.desktop {
min-width: 20%;
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,32 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function ladTableCompositionPolicy(openmct) {
return function (parent, child) {
if(parent.type === 'LadTable') {
return openmct.telemetry.isTelemetryObject(child);
} else if(parent.type === 'LadTableSet') {
return child.type === 'LadTable';
}
return true;
}
}

View File

@@ -19,46 +19,53 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import LadTableSet from './components/LadTableSet.vue';
import Vue from 'vue';
export default function LADTableSetViewProvider(openmct) {
return {
key: 'LadTableSet',
name: 'LAD Table Set',
cssClass: 'icon-tabular-lad-set',
canView: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
view: function (domainObject, objectPath) {
let component;
define([
'./components/LadTableSet.vue',
'vue'
], function (
LadTableSet,
Vue
) {
function LADTableSetViewProvider(openmct) {
return {
key: 'LadTableSet',
name: 'LAD Table Set',
cssClass: 'icon-tabular-lad-set',
canView: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
view: function (domainObject, objectPath) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
LadTableSet: LadTableSet
},
provide: {
openmct,
domainObject,
objectPath
},
template: '<lad-table-set></lad-table-set>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}
return {
show: function (element) {
component = new Vue({
el: element,
components: {
LadTableSet: LadTableSet.default
},
provide: {
openmct,
domainObject,
objectPath
},
template: '<lad-table-set></lad-table-set>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}
return LADTableSetViewProvider;
});

View File

@@ -19,46 +19,53 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import LadTable from './components/LADTable.vue';
import Vue from 'vue';
export default function LADTableViewProvider(openmct) {
return {
key: 'LadTable',
name: 'LAD Table',
cssClass: 'icon-tabular-lad',
canView: function (domainObject) {
return domainObject.type === 'LadTable';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTable';
},
view: function (domainObject, objectPath) {
let component;
define([
'./components/LADTable.vue',
'vue'
], function (
LadTableComponent,
Vue
) {
function LADTableViewProvider(openmct) {
return {
key: 'LadTable',
name: 'LAD Table',
cssClass: 'icon-tabular-lad',
canView: function (domainObject) {
return domainObject.type === 'LadTable';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTable';
},
view: function (domainObject, objectPath) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
LadTableComponent: LadTable
},
provide: {
openmct,
domainObject,
objectPath
},
template: '<lad-table-component></lad-table-component>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}
return {
show: function (element) {
component = new Vue({
el: element,
components: {
LadTableComponent: LadTableComponent.default
},
provide: {
openmct,
domainObject,
objectPath
},
template: '<lad-table-component></lad-table-component>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}
return LADTableViewProvider;
});

View File

@@ -1,6 +1,6 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -22,16 +22,12 @@
*****************************************************************************/
<template>
<tr
class="js-lad-table__body__row"
@contextmenu.prevent="showContextMenu"
>
<td class="js-first-data">{{ name }}</td>
<td class="js-second-data">{{ formattedTimestamp }}</td>
<td
class="js-third-data"
:class="valueClass"
>{{ value }}</td>
<tr @contextmenu.prevent="showContextMenu">
<td>{{ name }}</td>
<td>{{ timestamp }}</td>
<td :class="valueClass">
{{ value }}
</td>
</tr>
</template>
@@ -56,22 +52,16 @@ export default {
return {
name: this.domainObject.name,
timestamp: undefined,
timestamp: '---',
value: '---',
valueClass: '',
currentObjectPath
}
},
computed: {
formattedTimestamp() {
return this.timestamp !== undefined ? this.getFormattedTimestamp(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
@@ -86,7 +76,6 @@ export default {
);
this.openmct.time.on('timeSystem', this.updateTimeSystem);
this.openmct.time.on('bounds', this.updateBounds);
this.timestampKey = this.openmct.time.timeSystem().key;
@@ -100,91 +89,46 @@ export default {
.telemetry
.subscribe(this.domainObject, this.updateValues);
this.requestHistory();
this.openmct
.telemetry
.request(this.domainObject, {strategy: 'latest'})
.then((array) => this.updateValues(array[array.length - 1]));
},
destroyed() {
this.stopWatchingMutation();
this.unsubscribe();
this.openmct.time.off('timeSystem', this.updateTimeSystem);
this.openmct.time.off('bounds', this.updateBounds);
this.openmct.off('timeSystem', this.updateTimeSystem);
},
methods: {
updateValues(datum) {
let newTimestamp = this.getParsedTimestamp(datum),
limit;
this.timestamp = this.formats[this.timestampKey].format(datum);
this.value = this.formats[this.valueKey].format(datum);
if(this.shouldUpdate(newTimestamp)) {
this.timestamp = newTimestamp;
this.value = this.formats[this.valueKey].format(datum);
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
}
var limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
}
},
shouldUpdate(newTimestamp) {
let newTimestampInBounds = this.inBounds(newTimestamp),
noExistingTimestamp = this.timestamp === undefined,
newTimestampIsLatest = newTimestamp > this.timestamp;
return newTimestampInBounds &&
(noExistingTimestamp || newTimestampIsLatest);
},
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.resetValues();
this.requestHistory();
}
},
inBounds(timestamp) {
return timestamp >= this.bounds.start && timestamp <= this.bounds.end;
},
updateTimeSystem(timeSystem) {
this.resetValues();
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);
},
resetValues() {
this.value = '---';
this.timestamp = undefined;
this.valueClass = '';
},
getParsedTimestamp(timestamp) {
if(this.timeSystemFormat()) {
return this.formats[this.timestampKey].parse(timestamp);
}
},
getFormattedTimestamp(timestamp) {
if(this.timeSystemFormat()) {
return this.formats[this.timestampKey].format(timestamp);
}
},
timeSystemFormat() {
if(this.formats[this.timestampKey]) {
return true;
} else {
console.warn(`No formatter for ${this.timestampKey} time system for ${this.domainObject.name}.`);
return false;
}
}
}
}

View File

@@ -88,3 +88,4 @@ export default {
}
}
</script>

View File

@@ -35,7 +35,7 @@
>
<tr
:key="primary.key"
class="c-table__group-header js-lad-table-set__table-headers"
class="c-table__group-header"
>
<td colspan="10">
{{ primary.domainObject.name }}

View File

@@ -19,36 +19,38 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import LADTableViewProvider from './LADTableViewProvider';
import LADTableSetViewProvider from './LADTableSetViewProvider';
import ladTableCompositionPolicy from './LADTableCompositionPolicy';
export default function plugin() {
return function install(openmct) {
define([
'./LADTableViewProvider',
'./LADTableSetViewProvider'
], function (
LADTableViewProvider,
LADTableSetViewProvider
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new LADTableViewProvider(openmct));
openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct));
openmct.objectViews.addProvider(new LADTableViewProvider(openmct));
openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct));
openmct.types.addType('LadTable', {
name: "LAD Table",
creatable: true,
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
cssClass: 'icon-tabular-lad',
initialize(domainObject) {
domainObject.composition = [];
}
});
openmct.types.addType('LadTable', {
name: "LAD Table",
creatable: true,
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
cssClass: 'icon-tabular-lad',
initialize(domainObject) {
domainObject.composition = [];
}
});
openmct.types.addType('LadTableSet', {
name: "LAD Table Set",
creatable: true,
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
cssClass: 'icon-tabular-lad-set',
initialize(domainObject) {
domainObject.composition = [];
}
});
openmct.composition.addPolicy(ladTableCompositionPolicy(openmct));
openmct.types.addType('LadTableSet', {
name: "LAD Table Set",
creatable: true,
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
cssClass: 'icon-tabular-lad-set',
initialize(domainObject) {
domainObject.composition = [];
}
});
};
};
}
});

View File

@@ -1,365 +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.
*****************************************************************************/
import LadPlugin from './plugin.js';
import Vue from 'vue';
import {
createOpenMct,
getMockObjects,
getMockTelemetry,
getLatestTelemetry,
resetApplicationState
} from 'utils/testing';
const TABLE_BODY_ROWS = '.js-lad-table__body__row';
const TABLE_BODY_FIRST_ROW = TABLE_BODY_ROWS + ':first-child';
const TABLE_BODY_FIRST_ROW_FIRST_DATA = TABLE_BODY_FIRST_ROW + ' .js-first-data';
const TABLE_BODY_FIRST_ROW_SECOND_DATA = TABLE_BODY_FIRST_ROW + ' .js-second-data';
const TABLE_BODY_FIRST_ROW_THIRD_DATA = TABLE_BODY_FIRST_ROW + ' .js-third-data';
const LAD_SET_TABLE_HEADERS = '.js-lad-table-set__table-headers';
function utcTimeFormat(value) {
return new Date(value).toISOString().replace('T', ' ')
}
describe("The LAD Table", () => {
const ladTableKey = 'LadTable';
let openmct,
ladPlugin,
parent,
child,
telemetryCount = 3,
timeFormat = 'utc',
mockTelemetry = getMockTelemetry({ count: telemetryCount, format: timeFormat }),
mockObj = getMockObjects({
objectKeyStrings: ['ladTable', 'telemetry'],
format: timeFormat
}),
bounds = {
start: 0,
end: 4
};
// add telemetry object as composition in lad table
mockObj.ladTable.composition.push(mockObj.telemetry.identifier);
// this setups up the app
beforeEach((done) => {
const appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
parent = document.createElement('div');
child = document.createElement('div');
parent.appendChild(child);
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
ladPlugin = new LadPlugin();
openmct.install(ladPlugin);
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
openmct.time.bounds({ start: bounds.start, end: bounds.end });
openmct.on('start', done);
openmct.startHeadless(appHolder);
});
afterEach(() => {
resetApplicationState(openmct);
});
it("should provide a table view only for lad table objects", () => {
let applicableViews = openmct.objectViews.get(mockObj.ladTable),
ladTableView = applicableViews.find(
(viewProvider) => viewProvider.key === ladTableKey
);
expect(applicableViews.length).toEqual(1);
expect(ladTableView).toBeDefined();
});
describe('composition', () => {
let ladTableCompositionCollection;
beforeEach(() => {
ladTableCompositionCollection = openmct.composition.get(mockObj.ladTable);
ladTableCompositionCollection.load();
});
it("should accept telemetry producing objects", () => {
expect(() => {
ladTableCompositionCollection.add(mockObj.telemetry);
}).not.toThrow();
});
it("should reject non-telemtry producing objects", () => {
expect(()=> {
ladTableCompositionCollection.add(mockObj.ladTable);
}).toThrow();
});
});
describe("table view", () => {
let applicableViews,
ladTableViewProvider,
ladTableView,
anotherTelemetryObj = getMockObjects({
objectKeyStrings: ['telemetry'],
overwrite: {
telemetry: {
name: "New Telemetry Object",
identifier: { namespace: "", key: "another-telemetry-object" }
}
}
}).telemetry;
// add another telemetry object as composition in lad table to test multi rows
mockObj.ladTable.composition.push(anotherTelemetryObj.identifier);
beforeEach(async () => {
let telemetryRequestResolve,
telemetryObjectResolve,
anotherTelemetryObjectResolve;
let telemetryRequestPromise = new Promise((resolve) => {
telemetryRequestResolve = resolve;
}),
telemetryObjectPromise = new Promise((resolve) => {
telemetryObjectResolve = resolve;
}),
anotherTelemetryObjectPromise = new Promise((resolve) => {
anotherTelemetryObjectResolve = resolve;
})
openmct.telemetry.request.and.callFake(() => {
telemetryRequestResolve(mockTelemetry);
return telemetryRequestPromise;
});
openmct.objects.get.and.callFake((obj) => {
if(obj.key === 'telemetry-object') {
telemetryObjectResolve(mockObj.telemetry);
return telemetryObjectPromise;
} else {
anotherTelemetryObjectResolve(anotherTelemetryObj);
return anotherTelemetryObjectPromise;
}
});
openmct.time.bounds({ start: bounds.start, end: bounds.end });
applicableViews = openmct.objectViews.get(mockObj.ladTable);
ladTableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === ladTableKey);
ladTableView = ladTableViewProvider.view(mockObj.ladTable, [mockObj.ladTable]);
ladTableView.show(child, true);
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
await Vue.nextTick();
return;
});
it("should show one row per object in the composition", () => {
const rowCount = parent.querySelectorAll(TABLE_BODY_ROWS).length;
expect(rowCount).toBe(mockObj.ladTable.composition.length);
});
it("should show the most recent datum from the telemetry producing object", async () => {
const latestDatum = getLatestTelemetry(mockTelemetry, { timeFormat });
const expectedDate = utcTimeFormat(latestDatum[timeFormat]);
await Vue.nextTick();
const latestDate = parent.querySelector(TABLE_BODY_FIRST_ROW_SECOND_DATA).innerText;
expect(latestDate).toBe(expectedDate);
});
it("should show the name provided for the the telemetry producing object", () => {
const rowName = parent.querySelector(TABLE_BODY_FIRST_ROW_FIRST_DATA).innerText,
expectedName = mockObj.telemetry.name;
expect(rowName).toBe(expectedName);
});
it("should show the correct values for the datum based on domain and range hints", async () => {
const range = mockObj.telemetry.telemetry.values.find((val) => {
return val.hints && val.hints.range !== undefined;
}).key;
const domain = mockObj.telemetry.telemetry.values.find((val) => {
return val.hints && val.hints.domain !== undefined;
}).key;
const mostRecentTelemetry = getLatestTelemetry(mockTelemetry, { timeFormat });
const rangeValue = mostRecentTelemetry[range];
const domainValue = utcTimeFormat(mostRecentTelemetry[domain]);
await Vue.nextTick();
const actualDomainValue = parent.querySelector(TABLE_BODY_FIRST_ROW_SECOND_DATA).innerText;
const actualRangeValue = parent.querySelector(TABLE_BODY_FIRST_ROW_THIRD_DATA).innerText;
expect(actualRangeValue).toBe(rangeValue);
expect(actualDomainValue).toBe(domainValue);
});
});
});
describe("The LAD Table Set", () => {
const ladTableSetKey = 'LadTableSet';
let openmct,
ladPlugin,
parent,
child,
telemetryCount = 3,
timeFormat = 'utc',
mockTelemetry = getMockTelemetry({ count: telemetryCount, format: timeFormat }),
mockObj = getMockObjects({
objectKeyStrings: ['ladTable', 'ladTableSet', 'telemetry']
}),
bounds = {
start: 0,
end: 4
};
// add mock telemetry to lad table and lad table to lad table set (composition)
mockObj.ladTable.composition.push(mockObj.telemetry.identifier);
mockObj.ladTableSet.composition.push(mockObj.ladTable.identifier);
beforeEach((done) => {
const appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
parent = document.createElement('div');
child = document.createElement('div');
parent.appendChild(child);
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
ladPlugin = new LadPlugin();
openmct.install(ladPlugin);
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
openmct.time.bounds({ start: bounds.start, end: bounds.end });
openmct.on('start', done);
openmct.start(appHolder);
});
afterEach(() => {
resetApplicationState(openmct);
});
it("should provide a lad table set view only for lad table set objects", () => {
let applicableViews = openmct.objectViews.get(mockObj.ladTableSet),
ladTableSetView = applicableViews.find(
(viewProvider) => viewProvider.key === ladTableSetKey
);
expect(applicableViews.length).toEqual(1);
expect(ladTableSetView).toBeDefined();
});
describe('composition', () => {
let ladTableSetCompositionCollection;
beforeEach(() => {
ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet);
ladTableSetCompositionCollection.load();
});
it("should accept lad table objects", () => {
expect(() => {
ladTableSetCompositionCollection.add(mockObj.ladTable);
}).not.toThrow();
});
it("should reject non lad table objects", () => {
expect(()=> {
ladTableSetCompositionCollection.add(mockObj.telemetry);
}).toThrow();
});
});
describe("table view", () => {
let applicableViews,
ladTableSetViewProvider,
ladTableSetView,
otherObj = getMockObjects({
objectKeyStrings: ['ladTable'],
overwrite: {
ladTable: {
name: "New LAD Table Object",
identifier: { namespace: "", key: "another-lad-object" }
}
}
});
// add another lad table (with telemetry object) object to the lad table set for multi row test
otherObj.ladTable.composition.push(mockObj.telemetry.identifier);
mockObj.ladTableSet.composition.push(otherObj.ladTable.identifier);
beforeEach(async () => {
let telemetryRequestResolve,
ladObjectResolve,
anotherLadObjectResolve;
let telemetryRequestPromise = new Promise((resolve) => {
telemetryRequestResolve = resolve;
}),
ladObjectPromise = new Promise((resolve) => {
ladObjectResolve = resolve;
}),
anotherLadObjectPromise = new Promise((resolve) => {
anotherLadObjectResolve = resolve;
})
openmct.telemetry.request.and.callFake(() => {
telemetryRequestResolve(mockTelemetry);
return telemetryRequestPromise;
});
openmct.objects.get.and.callFake((obj) => {
if(obj.key === 'lad-object') {
ladObjectResolve(mockObj.ladObject);
return ladObjectPromise;
} else if(obj.key === 'another-lad-object') {
anotherLadObjectResolve(otherObj.ladObject);
return anotherLadObjectPromise;
}
return Promise.resolve({});
});
openmct.time.bounds({ start: bounds.start, end: bounds.end });
applicableViews = openmct.objectViews.get(mockObj.ladTableSet);
ladTableSetViewProvider = applicableViews.find((viewProvider) => viewProvider.key === ladTableSetKey);
ladTableSetView = ladTableSetViewProvider.view(mockObj.ladTableSet, [mockObj.ladTableSet]);
ladTableSetView.show(child, true);
await Promise.all([telemetryRequestPromise, ladObjectPromise, anotherLadObjectPromise]);
await Vue.nextTick();
return;
});
it("should show one row per lad table object in the composition", () => {
const rowCount = parent.querySelectorAll(LAD_SET_TABLE_HEADERS).length;
expect(rowCount).toBe(mockObj.ladTableSet.composition.length);
pending();
});
});
});

View File

@@ -1,237 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
getAllSearchParams,
setAllSearchParams
} from 'utils/openmctLocation';
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
const SEARCH_MODE = 'tc.mode';
const SEARCH_TIME_SYSTEM = 'tc.timeSystem';
const SEARCH_START_BOUND = 'tc.startBound';
const SEARCH_END_BOUND = 'tc.endBound';
const SEARCH_START_DELTA = 'tc.startDelta';
const SEARCH_END_DELTA = 'tc.endDelta';
const MODE_FIXED = 'fixed';
export default class URLTimeSettingsSynchronizer {
constructor(openmct) {
this.openmct = openmct;
this.isUrlUpdateInProgress = false;
this.initialize = this.initialize.bind(this);
this.destroy = this.destroy.bind(this);
this.updateTimeSettings = this.updateTimeSettings.bind(this);
this.setUrlFromTimeApi = this.setUrlFromTimeApi.bind(this);
this.updateBounds = this.updateBounds.bind(this);
openmct.on('start', this.initialize);
openmct.on('destroy', this.destroy);
}
initialize() {
this.updateTimeSettings();
window.addEventListener('hashchange', this.updateTimeSettings);
TIME_EVENTS.forEach(event => {
this.openmct.time.on(event, this.setUrlFromTimeApi);
});
this.openmct.time.on('bounds', this.updateBounds);
}
destroy() {
window.removeEventListener('hashchange', this.updateTimeSettings);
this.openmct.off('start', this.initialize);
this.openmct.off('destroy', this.destroy);
TIME_EVENTS.forEach(event => {
this.openmct.time.off(event, this.setUrlFromTimeApi);
});
this.openmct.time.on('bounds', this.updateBounds);
}
updateTimeSettings() {
// Prevent from triggering self
if (!this.isUrlUpdateInProgress) {
let timeParameters = this.parseParametersFromUrl();
if (this.areTimeParametersValid(timeParameters)) {
this.setTimeApiFromUrl(timeParameters);
} else {
this.setUrlFromTimeApi();
}
} else {
this.isUrlUpdateInProgress = false;
}
}
parseParametersFromUrl() {
let searchParams = getAllSearchParams();
let mode = searchParams.get(SEARCH_MODE);
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
let startBound = parseInt(searchParams.get(SEARCH_START_BOUND), 10);
let endBound = parseInt(searchParams.get(SEARCH_END_BOUND), 10);
let bounds = {
start: startBound,
end: endBound
};
let startOffset = parseInt(searchParams.get(SEARCH_START_DELTA), 10);
let endOffset = parseInt(searchParams.get(SEARCH_END_DELTA), 10);
let clockOffsets = {
start: 0 - startOffset,
end: endOffset
};
return {
mode,
timeSystem,
bounds,
clockOffsets
};
}
setTimeApiFromUrl(timeParameters) {
if (timeParameters.mode === 'fixed') {
if (this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
this.openmct.time.timeSystem(
timeParameters.timeSystem,
timeParameters.bounds
);
} else if (!this.areStartAndEndEqual(this.openmct.time.bounds(), timeParameters.bounds)) {
this.openmct.time.bounds(timeParameters.bounds);
}
if (this.openmct.time.clock()) {
this.openmct.time.stopClock();
}
} else {
if (!this.openmct.time.clock() ||
this.openmct.time.clock().key !== timeParameters.mode) {
this.openmct.time.clock(timeParameters.mode, timeParameters.clockOffsets);
} else if (!this.areStartAndEndEqual(this.openmct.time.clockOffsets(), timeParameters.clockOffsets)) {
this.openmct.time.clockOffsets(timeParameters.clockOffsets);
}
if (!this.openmct.time.timeSystem() ||
this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
this.openmct.time.timeSystem(timeParameters.timeSystem);
}
}
}
updateBounds(bounds, isTick) {
if (!isTick) {
this.setUrlFromTimeApi();
}
}
setUrlFromTimeApi() {
let searchParams = getAllSearchParams();
let clock = this.openmct.time.clock();
let bounds = this.openmct.time.bounds();
let clockOffsets = this.openmct.time.clockOffsets();
if (clock === undefined) {
searchParams.set(SEARCH_MODE, MODE_FIXED);
searchParams.set(SEARCH_START_BOUND, bounds.start);
searchParams.set(SEARCH_END_BOUND, bounds.end);
searchParams.delete(SEARCH_START_DELTA);
searchParams.delete(SEARCH_END_DELTA);
} else {
searchParams.set(SEARCH_MODE, clock.key);
if (clockOffsets !== undefined) {
searchParams.set(SEARCH_START_DELTA, 0 - clockOffsets.start);
searchParams.set(SEARCH_END_DELTA, clockOffsets.end);
} else {
searchParams.delete(SEARCH_START_DELTA);
searchParams.delete(SEARCH_END_DELTA);
}
searchParams.delete(SEARCH_START_BOUND);
searchParams.delete(SEARCH_END_BOUND);
}
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
this.isUrlUpdateInProgress = true;
setAllSearchParams(searchParams);
}
areTimeParametersValid(timeParameters) {
let isValid = false;
if (this.isModeValid(timeParameters.mode) &&
this.isTimeSystemValid(timeParameters.timeSystem)) {
if (timeParameters.mode === 'fixed') {
isValid = this.areStartAndEndValid(timeParameters.bounds);
} else {
isValid = this.areStartAndEndValid(timeParameters.clockOffsets);
}
}
return isValid;
}
areStartAndEndValid(bounds) {
return bounds !== undefined &&
bounds.start !== undefined &&
bounds.start !== null &&
bounds.end !== undefined &&
bounds.start !== null &&
!isNaN(bounds.start) &&
!isNaN(bounds.end);
}
isTimeSystemValid(timeSystem) {
let isValid = timeSystem !== undefined;
if (isValid) {
let timeSystemObject = this.openmct.time.timeSystems.get(timeSystem);
isValid = timeSystemObject !== undefined;
}
return isValid;
}
isModeValid(mode) {
let isValid = false;
if (mode !== undefined &&
mode !== null) {
isValid = true;
}
if (isValid) {
if (mode.toLowerCase() === MODE_FIXED) {
isValid = true;
} else {
isValid = this.openmct.time.clocks.get(mode) !== undefined;
}
}
return isValid;
}
areStartAndEndEqual(firstBounds, secondBounds) {
return firstBounds.start === secondBounds.start &&
firstBounds.end === secondBounds.end;
}
}

View File

@@ -1,28 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import URLTimeSettingsSynchronizer from "./URLTimeSettingsSynchronizer.js";
export default function () {
return function install(openmct) {
return new URLTimeSettingsSynchronizer(openmct);
}
}

View File

@@ -1,307 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
describe("The URLTimeSettingsSynchronizer", () => {
let openmct;
let testClock;
beforeAll(() => resetApplicationState());
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(openmct.plugins.LocalTimeSystem());
testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
testClock.key = "test-clock";
testClock.currentValue.and.returnValue(0);
openmct.time.addClock(testClock);
openmct.on('start', done);
openmct.startHeadless();
});
afterEach(() => resetApplicationState(openmct));
describe("realtime mode", () => {
it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
//Test expected initial conditions
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
openmct.time.clock('local', {start: -1000, end: 100});
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
});
it("when offsets are set via the time API, they are immediately reflected in the URL", () => {
//Test expected initial conditions
expect(window.location.hash.includes('tc.startDelta')).toBe(false);
expect(window.location.hash.includes('tc.endDelta')).toBe(false);
openmct.time.clock('local', {start: -1000, end: 100});
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
openmct.time.clockOffsets({start: -2000, end: 200});
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
});
describe("when set in the url", () => {
it("will change from fixed to realtime mode when the mode changes", () => {
expectLocationToBeInFixedMode();
return switchToRealtimeMode().then(() => {
let clock = openmct.time.clock();
expect(clock).toBeDefined();
expect(clock.key).toBe('local');
});
});
it("the clock is correctly set in the API from the URL parameters", () => {
return switchToRealtimeMode().then(() => {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('clock', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.mode=local', 'tc.mode=test-clock');
window.location.hash = hash;
}).then(() => {
let clock = openmct.time.clock();
expect(clock).toBeDefined();
expect(clock.key).toBe('test-clock');
openmct.time.off('clock', resolveFunction);
});
});
});
it("the clock offsets are correctly set in the API from the URL parameters", () => {
return switchToRealtimeMode().then(() => {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('clockOffsets', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.startDelta=1000', 'tc.startDelta=2000');
hash = hash.replace('tc.endDelta=100', 'tc.endDelta=200');
window.location.hash = hash;
}).then(() => {
let clockOffsets = openmct.time.clockOffsets();
expect(clockOffsets).toBeDefined();
expect(clockOffsets.start).toBe(-2000);
expect(clockOffsets.end).toBe(200);
openmct.time.off('clockOffsets', resolveFunction);
});
});
});
it("the time system is correctly set in the API from the URL parameters", () => {
return switchToRealtimeMode().then(() => {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('timeSystem', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
window.location.hash = hash;
}).then(() => {
let timeSystem = openmct.time.timeSystem();
expect(timeSystem).toBeDefined();
expect(timeSystem.key).toBe('local');
openmct.time.off('timeSystem', resolveFunction);
});
});
});
});
});
describe("fixed timespan mode", () => {
beforeEach(() => {
openmct.time.stopClock();
openmct.time.timeSystem('utc', {start: 0, end: 1});
});
it("when bounds are set via the time API, they are immediately reflected in the URL", ()=>{
//Test expected initial conditions
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
openmct.time.bounds({start: 10, end: 20});
expect(window.location.hash.includes('tc.startBound=10')).toBe(true);
expect(window.location.hash.includes('tc.endBound=20')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.startBound=0')).toBe(false);
expect(window.location.hash.includes('tc.endBound=1')).toBe(false);
});
it("when time system is set via the time API, it is immediately reflected in the URL", ()=>{
//Test expected initial conditions
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(true);
openmct.time.timeSystem('local', {start: 20, end: 30});
expect(window.location.hash.includes('tc.timeSystem=local')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
});
describe("when set in the url", () => {
it("time system changes are reflected in the API", () => {
let resolveFunction;
return new Promise((resolve) => {
let timeSystem = openmct.time.timeSystem();
resolveFunction = resolve;
expect(timeSystem.key).toBe('utc');
window.location.hash = window.location.hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
openmct.time.on('timeSystem', resolveFunction);
}).then(() => {
let timeSystem = openmct.time.timeSystem();
expect(timeSystem.key).toBe('local');
openmct.time.off('timeSystem', resolveFunction);
});
});
it("mode can be changed from realtime to fixed", () => {
return switchToRealtimeMode().then(() => {
expectLocationToBeInRealtimeMode();
expect(openmct.time.clock()).toBeDefined();
}).then(switchToFixedMode).then(() => {
let clock = openmct.time.clock();
expect(clock).not.toBeDefined();
});
});
it("bounds are correctly set in the API from the URL parameters", () => {
let resolveFunction;
expectLocationToBeInFixedMode();
return new Promise((resolve) => {
resolveFunction = resolve;
openmct.time.on('bounds', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.startBound=0', 'tc.startBound=222')
.replace('tc.endBound=1', 'tc.endBound=333');
window.location.hash = hash;
}).then(() => {
let bounds = openmct.time.bounds();
expect(bounds).toBeDefined();
expect(bounds.start).toBe(222);
expect(bounds.end).toBe(333);
});
});
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => {
let resolveFunction;
expectLocationToBeInFixedMode();
return new Promise((resolve) => {
resolveFunction = resolve;
openmct.time.on('bounds', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.endBound=1', 'tc.endBound=333');
window.location.hash = hash;
}).then(() => {
let bounds = openmct.time.bounds();
expect(bounds).toBeDefined();
expect(bounds.start).toBe(0);
expect(bounds.end).toBe(333);
});
});
});
});
function setRealtimeLocationParameters() {
let hash = window.location.hash.toString()
.replace('tc.mode=fixed', 'tc.mode=local')
.replace('tc.startBound=0', 'tc.startDelta=1000')
.replace('tc.endBound=1', 'tc.endDelta=100');
window.location.hash = hash;
}
function setFixedLocationParameters() {
let hash = window.location.hash.toString()
.replace('tc.mode=local', 'tc.mode=fixed')
.replace('tc.timeSystem=utc', 'tc.timeSystem=local')
.replace('tc.startDelta=1000', 'tc.startBound=50')
.replace('tc.endDelta=100', 'tc.endBound=60');
window.location.hash = hash;
}
function switchToRealtimeMode() {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
openmct.time.on('clock', resolveFunction);
setRealtimeLocationParameters();
}).then(() => {
openmct.time.off('clock', resolveFunction);
});
}
function switchToFixedMode() {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('clock', resolveFunction);
setFixedLocationParameters();
}).then(() => {
openmct.time.off('clock', resolveFunction);
});
}
function expectLocationToBeInRealtimeMode() {
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
}
function expectLocationToBeInFixedMode() {
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
expect(window.location.hash.includes('tc.mode=local')).toBe(false);
}
});

View File

@@ -26,7 +26,6 @@ import TelemetryCriterion from "./criterion/TelemetryCriterion";
import { evaluateResults } from './utils/evaluator';
import { getLatestTimestamp } from './utils/time';
import AllTelemetryCriterion from "./criterion/AllTelemetryCriterion";
import {TRIGGER_CONJUNCTION, TRIGGER_LABEL} from "./utils/constants";
/*
* conditionConfiguration = {
@@ -42,14 +41,13 @@ import {TRIGGER_CONJUNCTION, TRIGGER_LABEL} from "./utils/constants";
* ]
* }
*/
export default class Condition extends EventEmitter {
export default class ConditionClass extends EventEmitter {
/**
* Manages criteria and emits the result of - true or false - based on criteria evaluated.
* @constructor
* @param conditionConfiguration: {id: uuid,trigger: enum, criteria: Array of {id: uuid, operation: enum, input: Array, metaDataKey: string, key: {domainObject.identifier} }
* @param openmct
* @param conditionManager
*/
constructor(conditionConfiguration, openmct, conditionManager) {
super();
@@ -64,7 +62,6 @@ export default class Condition extends EventEmitter {
this.createCriteria(conditionConfiguration.configuration.criteria);
}
this.trigger = conditionConfiguration.configuration.trigger;
this.description = '';
}
getResult(datum) {
@@ -112,7 +109,7 @@ export default class Condition extends EventEmitter {
return {
id: criterionConfiguration.id || uuid(),
telemetry: criterionConfiguration.telemetry || '',
telemetryObjects: this.conditionManager.telemetryObjects,
telemetryObject: this.conditionManager.telemetryObjects[this.openmct.objects.makeKeyString(criterionConfiguration.telemetry)],
operation: criterionConfiguration.operation || '',
input: criterionConfiguration.input === undefined ? [] : criterionConfiguration.input,
metadata: criterionConfiguration.metadata || ''
@@ -123,7 +120,6 @@ export default class Condition extends EventEmitter {
criterionConfigurations.forEach((criterionConfiguration) => {
this.addCriterion(criterionConfiguration);
});
this.updateDescription();
}
updateCriteria(criterionConfigurations) {
@@ -131,11 +127,10 @@ export default class Condition extends EventEmitter {
this.createCriteria(criterionConfigurations);
}
updateTelemetryObjects() {
updateTelemetry() {
this.criteria.forEach((criterion) => {
criterion.updateTelemetryObjects(this.conditionManager.telemetryObjects);
criterion.updateTelemetry(this.conditionManager.telemetryObjects);
});
this.updateDescription();
}
/**
@@ -150,7 +145,6 @@ export default class Condition extends EventEmitter {
criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
}
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
if (!this.criteria) {
this.criteria = [];
}
@@ -179,14 +173,11 @@ export default class Condition extends EventEmitter {
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
newCriterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
let criterion = found.item;
criterion.unsubscribe();
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.off('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
this.criteria.splice(found.index, 1, newCriterion);
this.updateDescription();
}
}
@@ -197,12 +188,8 @@ export default class Condition extends EventEmitter {
criterion.off('criterionUpdated', (obj) => {
this.handleCriterionUpdated(obj);
});
criterion.off('telemetryIsStale', (obj) => {
this.handleStaleCriterion(obj);
});
criterion.destroy();
this.criteria.splice(found.index, 1);
this.updateDescription();
return true;
}
@@ -213,42 +200,9 @@ export default class Condition extends EventEmitter {
let found = this.findCriterion(criterion.id);
if (found) {
this.criteria[found.index] = criterion.data;
this.updateDescription();
}
}
handleStaleCriterion(updatedCriterion) {
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
let latestTimestamp = {};
latestTimestamp = getLatestTimestamp(
latestTimestamp,
updatedCriterion.data,
this.timeSystems,
this.openmct.time.timeSystem()
);
this.conditionManager.updateCurrentCondition(latestTimestamp);
}
updateDescription() {
const triggerDescription = this.getTriggerDescription();
let description = '';
this.criteria.forEach((criterion, index) => {
if (!index) {
description = `Match if ${triggerDescription.prefix}`;
}
description = `${description} ${criterion.getDescription()} ${(index < this.criteria.length - 1) ? triggerDescription.conjunction : ''}`;
});
this.description = description;
this.conditionManager.updateConditionDescription(this);
}
getTriggerDescription() {
return {
conjunction: TRIGGER_CONJUNCTION[this.trigger],
prefix: `${TRIGGER_LABEL[this.trigger]}: `
};
}
requestLADConditionResult() {
let latestTimestamp;
let criteriaResults = {};

View File

@@ -57,7 +57,7 @@ export default class ConditionManager extends EventEmitter {
endpoint,
this.telemetryReceived.bind(this, endpoint)
);
this.updateConditionTelemetryObjects();
this.updateConditionTelemetry();
}
unsubscribeFromTelemetry(endpointIdentifier) {
@@ -70,11 +70,11 @@ export default class ConditionManager extends EventEmitter {
this.subscriptions[id]();
delete this.subscriptions[id];
delete this.telemetryObjects[id];
this.removeConditionTelemetryObjects();
this.removeConditionTelemetry();
}
initialize() {
this.conditions = [];
this.conditionClassCollection = [];
if (this.conditionSetDomainObject.configuration.conditionCollection.length) {
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
this.initCondition(conditionConfiguration, index);
@@ -82,14 +82,13 @@ export default class ConditionManager extends EventEmitter {
}
}
updateConditionTelemetryObjects() {
this.conditions.forEach((condition) => condition.updateTelemetryObjects());
updateConditionTelemetry() {
this.conditionClassCollection.forEach((condition) => condition.updateTelemetry());
}
removeConditionTelemetryObjects() {
removeConditionTelemetry() {
let conditionsChanged = false;
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, conditionIndex) => {
let conditionChanged = false;
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration) => {
conditionConfiguration.configuration.criteria.forEach((criterion, index) => {
const isAnyAllTelemetry = criterion.telemetry && (criterion.telemetry === 'any' || criterion.telemetry === 'all');
if (!isAnyAllTelemetry) {
@@ -101,16 +100,10 @@ export default class ConditionManager extends EventEmitter {
criterion.metadata = '';
criterion.input = [];
criterion.operation = '';
conditionChanged = true;
conditionsChanged = true;
}
} else {
conditionChanged = true;
}
});
if (conditionChanged) {
this.updateCondition(conditionConfiguration, conditionIndex);
conditionsChanged = true;
}
});
if (conditionsChanged) {
this.persistConditions();
@@ -118,24 +111,18 @@ export default class ConditionManager extends EventEmitter {
}
updateCondition(conditionConfiguration, index) {
let condition = this.conditions[index];
this.conditionSetDomainObject.configuration.conditionCollection[index] = conditionConfiguration;
let condition = this.conditionClassCollection[index];
condition.update(conditionConfiguration);
this.persistConditions();
}
updateConditionDescription(condition) {
const found = this.conditionSetDomainObject.configuration.conditionCollection.find(conditionConfiguration => (conditionConfiguration.id === condition.id));
found.summary = condition.description;
this.conditionSetDomainObject.configuration.conditionCollection[index] = conditionConfiguration;
this.persistConditions();
}
initCondition(conditionConfiguration, index) {
let condition = new Condition(conditionConfiguration, this.openmct, this);
if (index !== undefined) {
this.conditions.splice(index + 1, 0, condition);
this.conditionClassCollection.splice(index + 1, 0, condition);
} else {
this.conditions.unshift(condition);
this.conditionClassCollection.unshift(condition);
}
}
@@ -194,15 +181,15 @@ export default class ConditionManager extends EventEmitter {
}
removeCondition(index) {
let condition = this.conditions[index];
let condition = this.conditionClassCollection[index];
condition.destroy();
this.conditions.splice(index, 1);
this.conditionClassCollection.splice(index, 1);
this.conditionSetDomainObject.configuration.conditionCollection.splice(index, 1);
this.persistConditions();
}
findConditionById(id) {
return this.conditions.find(condition => condition.id === id);
return this.conditionClassCollection.find(conditionClass => conditionClass.id === id);
}
reorderConditions(reorderPlan) {
@@ -247,14 +234,14 @@ export default class ConditionManager extends EventEmitter {
}
requestLADConditionSetOutput() {
if (!this.conditions.length) {
if (!this.conditionClassCollection.length) {
return Promise.resolve([]);
}
return this.compositionLoad.then(() => {
let latestTimestamp;
let conditionResults = {};
const conditionRequests = this.conditions
const conditionRequests = this.conditionClassCollection
.map(condition => condition.requestLADConditionResult());
return Promise.all(conditionRequests)
@@ -294,7 +281,7 @@ export default class ConditionManager extends EventEmitter {
isTelemetryUsed(endpoint) {
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
for(const condition of this.conditions) {
for(const condition of this.conditionClassCollection) {
if (condition.isTelemetryUsed(id)) {
return true;
}
@@ -313,14 +300,10 @@ export default class ConditionManager extends EventEmitter {
let timestamp = {};
timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
this.conditions.forEach(condition => {
this.conditionClassCollection.forEach(condition => {
condition.getResult(normalizedDatum);
});
this.updateCurrentCondition(timestamp);
}
updateCurrentCondition(timestamp) {
const currentCondition = this.getCurrentCondition();
this.emit('conditionSetResultUpdated',
@@ -381,7 +364,7 @@ export default class ConditionManager extends EventEmitter {
this.stopObservingForChanges();
}
this.conditions.forEach((condition) => {
this.conditionClassCollection.forEach((condition) => {
condition.destroy();
})
}

View File

@@ -126,7 +126,7 @@ describe('ConditionManager', () => {
it('creates a conditionCollection with a default condition', function () {
expect(conditionMgr.conditionSetDomainObject.configuration.conditionCollection.length).toEqual(1);
let defaultConditionId = conditionMgr.conditions[0].id;
let defaultConditionId = conditionMgr.conditionClassCollection[0].id;
expect(defaultConditionId).toEqual(mockCondition.id);
});

View File

@@ -36,20 +36,19 @@ describe("The condition", function () {
beforeEach (() => {
conditionManager = jasmine.createSpyObj('conditionManager',
['on', 'updateConditionDescription']
['on']
);
mockTelemetryReceived = jasmine.createSpy('listener');
conditionManager.on('telemetryReceived', mockTelemetryReceived);
conditionManager.updateConditionDescription.and.returnValue(function () {});
testTelemetryObject = {
identifier:{ namespace: "", key: "test-object"},
type: "test-object",
name: "Test Object",
telemetry: {
valueMetadatas: [{
key: "some-key",
name: "Some attribute",
values: [{
key: "value",
name: "Value",
hints: {
range: 2
}
@@ -79,7 +78,7 @@ describe("The condition", function () {
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', 'subscribe', 'getMetadata']);
openmct.telemetry.isTelemetryObject.and.returnValue(true);
openmct.telemetry.subscribe.and.returnValue(function () {});
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values);
mockTimeSystems = {
key: 'utc'

View File

@@ -50,7 +50,7 @@
<span class="c-condition__name">{{ condition.configuration.name }}</span>
<span class="c-condition__summary">
<template v-if="!condition.isDefault && !canEvaluateCriteria">
<template v-if="!canEvaluateCriteria">
Define criteria
</template>
<span v-else>
@@ -250,7 +250,7 @@ export default {
keys.forEach((trigger) => {
triggerOptions.push({
value: TRIGGER[trigger],
label: `when ${TRIGGER_LABEL[TRIGGER[trigger]]}`
label: TRIGGER_LABEL[TRIGGER[trigger]]
});
});
return triggerOptions;

View File

@@ -152,8 +152,7 @@ export default {
},
observeForChanges() {
this.stopObservingForChanges = this.openmct.objects.observe(this.domainObject, 'configuration.conditionCollection', (newConditionCollection) => {
//this forces children to re-render
this.conditionCollection = newConditionCollection.map(condition => condition);
this.conditionCollection = newConditionCollection;
this.updateDefaultCondition();
});
},

View File

@@ -27,20 +27,20 @@
>
{{ condition.configuration.name }}
</span>
<span v-if="!condition.isDefault"
<span v-for="(criterionDescription, index) in criterionDescriptions"
:key="criterionDescription"
class="c-style__condition-desc__text"
>
{{ description }}
</span>
<span v-else
class="c-style__condition-desc__text"
>
Match if no other condition is matched
<template v-if="!index">When</template>
{{ criterionDescription }}
<template v-if="index < (criterionDescriptions.length-1)">{{ triggerDescription }}</template>
</span>
</div>
</template>
<script>
import { TRIGGER } from "@/plugins/condition/utils/constants";
import { OPERATIONS } from "@/plugins/condition/utils/operations";
export default {
name: 'ConditionDescription',
@@ -59,9 +59,95 @@ export default {
}
}
},
computed: {
description() {
return this.condition ? this.condition.summary : '';
data() {
return {
criterionDescriptions: [],
triggerDescription: ''
}
},
watch: {
condition: {
handler(val) {
this.getConditionDescription();
},
deep: true
}
},
mounted() {
this.getConditionDescription();
},
methods: {
getTriggerDescription(trigger) {
let description = '';
switch(trigger) {
case TRIGGER.ANY:
case TRIGGER.XOR:
description = 'or';
break;
case TRIGGER.ALL:
case TRIGGER.NOT: description = 'and';
break;
}
return description;
},
getConditionDescription() {
if (this.condition) {
this.triggerDescription = this.getTriggerDescription(this.condition.configuration.trigger);
this.criterionDescriptions = [];
this.condition.configuration.criteria.forEach((criterion, index) => {
this.getCriterionDescription(criterion, index);
});
if (this.condition.isDefault) {
this.criterionDescriptions.splice(0, 0, 'all else fails');
}
} else {
this.criterionDescriptions = [];
}
},
getCriterionDescription(criterion, index) {
if (!criterion.telemetry) {
let description = `Unknown ${criterion.metadata} ${this.getOperatorText(criterion.operation, criterion.input)}`;
this.criterionDescriptions.splice(index, 0, description);
} else if (criterion.telemetry === 'all' || criterion.telemetry === 'any') {
const telemetryDescription = criterion.telemetry === 'all' ? 'All telemetry' : 'Any telemetry';
let description = `${telemetryDescription} ${criterion.metadata} ${this.getOperatorText(criterion.operation, criterion.input)}`;
this.criterionDescriptions.splice(index, 0, description);
} else {
this.openmct.objects.get(criterion.telemetry).then((telemetryObject) => {
if (telemetryObject.type === 'unknown') {
let description = `Unknown ${criterion.metadata} ${this.getOperatorText(criterion.operation, criterion.input)}`;
this.criterionDescriptions.splice(index, 0, description);
} else {
let metadataValue = criterion.metadata;
let inputValue = criterion.input;
if (criterion.metadata) {
this.telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
const metadataObj = this.telemetryMetadata.valueMetadatas.find((metadata) => metadata.key === criterion.metadata);
if (metadataObj) {
if (metadataObj.name) {
metadataValue = metadataObj.name;
}
if(metadataObj.enumerations && inputValue.length) {
if (metadataObj.enumerations[inputValue[0]] && metadataObj.enumerations[inputValue[0]].string) {
inputValue = [metadataObj.enumerations[inputValue[0]].string];
}
}
}
}
let description = `${telemetryObject.name} ${metadataValue} ${this.getOperatorText(criterion.operation, inputValue)}`;
if (this.criterionDescriptions[index]) {
this.criterionDescriptions[index] = description;
} else {
this.criterionDescriptions.splice(index, 0, description);
}
}
});
}
},
getOperatorText(operationName, values) {
const found = OPERATIONS.find((operation) => operation.name === operationName);
return found ? found.getDescription(values) : '';
}
}
}

View File

@@ -66,13 +66,22 @@ export default {
}
},
getCriterionErrors(criterion, index) {
//It is sufficient to check for absence of telemetry here since the condition manager ensures that telemetry for a criterion is set if it exists
const isInvalidTelemetry = !criterion.telemetry && (criterion.telemetry !== 'all' && criterion.telemetry !== 'any');
if (isInvalidTelemetry) {
if (!criterion.telemetry) {
this.conditionErrors.push({
message: ERROR.TELEMETRY_NOT_FOUND,
additionalInfo: ''
});
} else {
if (criterion.telemetry !== 'all' && criterion.telemetry !== 'any') {
this.openmct.objects.get(criterion.telemetry).then((telemetryObject) => {
if (telemetryObject.type === 'unknown') {
this.conditionErrors.push({
message: ERROR.TELEMETRY_NOT_FOUND,
additionalInfo: criterion.telemetry ? `Key: ${this.openmct.objects.makeKeyString(criterion.telemetry)}` : ''
});
}
});
}
}
}
}

View File

@@ -55,7 +55,6 @@
>
{{ option.name }}
</option>
<option value="dataReceived">any data received</option>
</select>
</span>
<span v-if="criterion.telemetry && criterion.metadata"
@@ -80,11 +79,10 @@
<input v-model="criterion.input[inputIndex]"
class="c-cdef__control__input"
:type="setInputType"
@change="persist"
@blur="persist"
>
<span v-if="inputIndex < inputCount-1">and</span>
</span>
<span v-if="criterion.metadata === 'dataReceived'">seconds</span>
</template>
<span v-else>
<span v-if="inputCount && criterion.operation"
@@ -110,7 +108,6 @@
<script>
import { OPERATIONS } from '../utils/operations';
import { INPUT_TYPES } from '../utils/operations';
import {TRIGGER_CONJUNCTION} from "../utils/constants";
export default {
inject: ['openmct'],
@@ -146,15 +143,11 @@ export default {
},
computed: {
setRowLabel: function () {
let operator = TRIGGER_CONJUNCTION[this.trigger];
return (this.index !== 0 ? operator : '') + ' when';
let operator = this.trigger === 'all' ? 'and ': 'or ';
return (this.index !== 0 ? operator : '') + 'when';
},
filteredOps: function () {
if (this.criterion.metadata === 'dataReceived') {
return this.operations.filter(op => op.name === 'isStale');
} else {
return this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1);
}
return this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1);
},
setInputType: function () {
let type = '';
@@ -185,18 +178,17 @@ export default {
methods: {
checkTelemetry() {
if(this.criterion.telemetry) {
const isAnyAllTelemetry = this.criterion.telemetry === 'any' || this.criterion.telemetry === 'all';
const telemetryForCriterionExists = this.telemetry.find((telemetryObj) => this.openmct.objects.areIdsEqual(this.criterion.telemetry, telemetryObj.identifier));
if (!isAnyAllTelemetry &&
!telemetryForCriterionExists) {
//telemetry being used was removed. So reset this criterion.
this.criterion.telemetry = '';
this.criterion.metadata = '';
this.criterion.input = [];
this.criterion.operation = '';
this.persist();
} else {
if (this.criterion.telemetry === 'any' || this.criterion.telemetry === 'all') {
this.updateMetadataOptions();
} else {
if (!this.telemetry.find((telemetryObj) => this.openmct.objects.areIdsEqual(this.criterion.telemetry, telemetryObj.identifier))) {
//telemetry being used was removed. So reset this criterion.
this.criterion.telemetry = '';
this.criterion.metadata = '';
this.criterion.input = [];
this.criterion.operation = '';
this.persist();
}
}
}
},
@@ -220,8 +212,6 @@ export default {
} else {
this.operationFormat = 'number';
}
} else if (this.criterion.metadata === 'dataReceived') {
this.operationFormat = 'number';
}
this.updateInputVisibilityAndValues();
},
@@ -231,17 +221,19 @@ export default {
this.persist();
}
if (this.criterion.telemetry) {
let telemetryObjects = this.telemetry;
if (this.criterion.telemetry !== 'all' && this.criterion.telemetry !== 'any') {
const found = this.telemetry.find(telemetryObj => (this.openmct.objects.areIdsEqual(telemetryObj.identifier, this.criterion.telemetry)));
telemetryObjects = found ? [found] : [];
}
this.telemetryMetadataOptions = [];
telemetryObjects.forEach(telemetryObject => {
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
this.addMetaDataOptions(telemetryMetadata.values());
const telemetry = (this.criterion.telemetry === 'all' || this.criterion.telemetry === 'any') ? this.telemetry : [{
identifier: this.criterion.telemetry
}];
let telemetryPromises = telemetry.map((telemetryObject) => this.openmct.objects.get(telemetryObject.identifier));
Promise.all(telemetryPromises).then(telemetryObjects => {
this.telemetryMetadataOptions = [];
telemetryObjects.forEach(telemetryObject => {
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
this.addMetaDataOptions(telemetryMetadata.values());
});
this.updateOperations();
});
this.updateOperations();
}
},
addMetaDataOptions(options) {

View File

@@ -37,13 +37,12 @@
>
<style-editor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="allowEditing"
:is-editing="isEditing"
:mixed-styles="mixedStyles"
@persist="updateStaticStyle"
/>
</div>
<button
v-if="allowEditing"
id="addConditionSet"
class="c-button c-button--major c-toggle-styling-button labeled"
@click="addConditionSet"
@@ -64,7 +63,7 @@
>
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
</a>
<template v-if="allowEditing">
<template v-if="isEditing">
<button
id="changeConditionSet"
class="c-button labeled"
@@ -97,7 +96,7 @@
/>
<style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:is-editing="allowEditing"
:is-editing="isEditing"
@persist="updateConditionalStyle"
/>
</div>
@@ -138,13 +137,7 @@ export default {
conditions: undefined,
conditionsLoaded: false,
navigateToPath: '',
selectedConditionId: '',
locked: false
}
},
computed: {
allowEditing() {
return this.isEditing && !this.locked;
selectedConditionId: ''
}
},
destroyed() {
@@ -231,13 +224,7 @@ export default {
this.selection.forEach((selectionItem) => {
const item = selectionItem[0].context.item;
const layoutItem = selectionItem[0].context.layoutItem;
const layoutDomainObject = selectionItem[0].context.item;
const isChildItem = selectionItem.length > 1;
if (layoutDomainObject && layoutDomainObject.locked) {
this.locked = true;
}
if (!isChildItem) {
domainObject = item;
itemStyle = getApplicableStylesForItem(item);

View File

@@ -22,8 +22,7 @@
import TelemetryCriterion from './TelemetryCriterion';
import { evaluateResults } from "../utils/evaluator";
import {getLatestTimestamp, subscribeForStaleness} from '../utils/time';
import { getOperatorText } from "@/plugins/condition/utils/operations";
import { getLatestTimestamp } from '../utils/time';
export default class AllTelemetryCriterion extends TelemetryCriterion {
@@ -41,44 +40,15 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
initialize() {
this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects };
this.telemetryDataCache = {};
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData(this.telemetryObjects || {});
}
}
subscribeForStaleData(telemetryObjects) {
if (!this.stalenessSubscription) {
this.stalenessSubscription = {};
}
Object.values(telemetryObjects).forEach((telemetryObject) => {
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (!this.stalenessSubscription[id]) {
this.stalenessSubscription[id] = subscribeForStaleness((data) => {
this.handleStaleTelemetry(id, data);
}, this.input[0]*1000);
}
})
}
handleStaleTelemetry(id, data) {
if (this.telemetryDataCache) {
this.telemetryDataCache[id] = true;
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
}
this.emitEvent('telemetryIsStale', data);
}
isValid() {
return (this.telemetry === 'any' || this.telemetry === 'all') && this.metadata && this.operation;
}
updateTelemetryObjects(telemetryObjects) {
updateTelemetry(telemetryObjects) {
this.telemetryObjects = { ...telemetryObjects };
this.removeTelemetryDataCache();
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData(this.telemetryObjects || {});
}
}
removeTelemetryDataCache() {
@@ -92,7 +62,6 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
});
telemetryCacheIds.forEach(id => {
delete (this.telemetryDataCache[id]);
delete (this.stalenessSubscription[id]);
});
}
@@ -126,14 +95,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
const validatedData = this.isValid() ? data : {};
if (validatedData) {
if (this.isStalenessCheck()) {
if (this.stalenessSubscription[validatedData.id]) {
this.stalenessSubscription[validatedData.id].update(validatedData);
}
this.telemetryDataCache[validatedData.id] = false;
} else {
this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
}
this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
}
Object.values(telemetryObjects).forEach(telemetryObject => {
@@ -197,31 +159,8 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
});
}
getDescription() {
const telemetryDescription = this.telemetry === 'all' ? 'all telemetry' : 'any telemetry';
let metadataValue = (this.metadata === 'dataReceived' ? '' : this.metadata);
let inputValue = this.input;
if (this.metadata) {
const telemetryObjects = Object.values(this.telemetryObjects);
for (let i=0; i < telemetryObjects.length; i++) {
const telemetryObject = telemetryObjects[i];
const metadataObject = this.getMetaDataObject(telemetryObject, this.metadata);
if (metadataObject) {
metadataValue = this.getMetadataValueFromMetaData(metadataObject) || this.metadata;
inputValue = this.getInputValueFromMetaData(metadataObject, this.input) || this.input;
break;
}
}
}
return `${telemetryDescription} ${metadataValue} ${getOperatorText(this.operation, inputValue)}`;
}
destroy() {
delete this.telemetryObjects;
delete this.telemetryDataCache;
if (this.stalenessSubscription) {
Object.values(this.stalenessSubscription).forEach((subscription) => subscription.clear);
delete this.stalenessSubscription;
}
}
}

View File

@@ -21,8 +21,7 @@
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import { OPERATIONS, getOperatorText } from '../utils/operations';
import { subscribeForStaleness } from "../utils/time";
import { OPERATIONS } from '../utils/operations';
export default class TelemetryCriterion extends EventEmitter {
@@ -44,49 +43,22 @@ export default class TelemetryCriterion extends EventEmitter {
this.input = telemetryDomainObjectDefinition.input;
this.metadata = telemetryDomainObjectDefinition.metadata;
this.result = undefined;
this.stalenessSubscription = undefined;
this.initialize();
this.emitEvent('criterionUpdated', this);
}
initialize() {
this.telemetryObject = this.telemetryDomainObjectDefinition.telemetryObject;
this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
this.updateTelemetryObjects(this.telemetryDomainObjectDefinition.telemetryObjects);
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData()
}
}
subscribeForStaleData() {
if (this.stalenessSubscription) {
this.stalenessSubscription.clear();
}
this.stalenessSubscription = subscribeForStaleness(this.handleStaleTelemetry.bind(this), this.input[0]*1000);
}
handleStaleTelemetry(data) {
this.result = true;
this.emitEvent('telemetryIsStale', data);
}
isValid() {
return this.telemetryObject && this.metadata && this.operation;
}
isStalenessCheck() {
return this.metadata && this.metadata === 'dataReceived';
}
isValidInput() {
return this.input instanceof Array && this.input.length;
}
updateTelemetryObjects(telemetryObjects) {
updateTelemetry(telemetryObjects) {
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
this.subscribeForStaleData()
}
}
createNormalizedDatum(telemetryDatum, endpoint) {
@@ -119,14 +91,7 @@ export default class TelemetryCriterion extends EventEmitter {
getResult(data) {
const validatedData = this.isValid() ? data : {};
if (this.isStalenessCheck()) {
if (this.stalenessSubscription) {
this.stalenessSubscription.update(validatedData);
}
this.result = false;
} else {
this.result = this.computeResult(validatedData);
}
this.result = this.computeResult(validatedData);
}
requestLAD() {
@@ -171,7 +136,7 @@ export default class TelemetryCriterion extends EventEmitter {
let comparator = this.findOperation(this.operation);
let params = [];
params.push(data[this.metadata]);
if (this.isValidInput()) {
if (this.input instanceof Array && this.input.length) {
this.input.forEach(input => params.push(input));
}
if (typeof comparator === 'function') {
@@ -188,57 +153,9 @@ export default class TelemetryCriterion extends EventEmitter {
});
}
getMetaDataObject(telemetryObject, metadata) {
let metadataObject;
if (metadata) {
const telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
metadataObject = telemetryMetadata.valueMetadatas.find((valueMetadata) => valueMetadata.key === metadata);
}
return metadataObject;
}
getInputValueFromMetaData(metadataObject, input) {
let inputValue;
if (metadataObject) {
if(metadataObject.enumerations && input.length) {
const enumeration = metadataObject.enumerations[input[0]];
if (enumeration !== undefined && enumeration.string) {
inputValue = [enumeration.string];
}
}
}
return inputValue;
}
getMetadataValueFromMetaData(metadataObject) {
let metadataValue;
if (metadataObject) {
if (metadataObject.name) {
metadataValue = metadataObject.name;
}
}
return metadataValue;
}
getDescription(criterion, index) {
let description;
if (!this.telemetry || !this.telemetryObject || (this.telemetryObject.type === 'unknown')) {
description = `Unknown ${this.metadata} ${getOperatorText(this.operation, this.input)}`;
} else {
const metadataObject = this.getMetaDataObject(this.telemetryObject, this.metadata);
const metadataValue = this.getMetadataValueFromMetaData(metadataObject) || (this.metadata === 'dataReceived' ? '' : this.metadata);
const inputValue = this.getInputValueFromMetaData(metadataObject, this.input) || this.input;
description = `${this.telemetryObject.name} ${metadataValue} ${getOperatorText(this.operation, inputValue)}`;
}
return description;
}
destroy() {
delete this.telemetryObject;
delete this.telemetryObjectIdAsString;
if (this.stalenessSubscription) {
delete this.stalenessSubscription;
}
}
}

View File

@@ -83,7 +83,7 @@ describe("The telemetry criterion", function () {
operation: 'textContains',
metadata: 'value',
input: ['Hell'],
telemetryObjects: {[testTelemetryObject.identifier.key]: testTelemetryObject}
telemetryObject: testTelemetryObject
};
mockListener = jasmine.createSpy('listener');
@@ -109,4 +109,13 @@ describe("The telemetry criterion", function () {
});
expect(telemetryCriterion.result).toBeTrue();
});
// it("does not return a result on new data from irrelavant telemetry providers", function () {
// telemetryCriterion.getResult({
// value: 'Hello',
// utc: 'Hi',
// id: '1234'
// });
// expect(telemetryCriterion.result).toBeFalse();
// });
});

View File

@@ -20,55 +20,20 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, resetApplicationState } from "utils/testing";
import { createOpenMct } from "testUtils";
import ConditionPlugin from "./plugin";
import StylesView from "./components/inspector/StylesView.vue";
import Vue from 'vue';
import {getApplicableStylesForItem} from "./utils/styleUtils";
import ConditionManager from "@/plugins/condition/ConditionManager";
describe('the plugin', function () {
let conditionSetDefinition;
let mockConditionSetDomainObject;
let mockListener;
let element;
let child;
let openmct;
let testTelemetryObject;
beforeAll(() => {
resetApplicationState(openmct);
});
beforeEach((done) => {
testTelemetryObject = {
identifier:{ namespace: "", key: "test-object"},
type: "test-object",
name: "Test Object",
telemetry: {
valueMetadatas: [{
key: "some-key",
name: "Some attribute",
hints: {
range: 2
}
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
}, {
key: "testSource",
source: "value",
name: "Test",
format: "string"
}]
}
};
openmct = createOpenMct();
openmct.install(new ConditionPlugin());
@@ -86,18 +51,12 @@ describe('the plugin', function () {
type: 'conditionSet'
};
mockListener = jasmine.createSpy('mockListener');
conditionSetDefinition.initialize(mockConditionSetDomainObject);
openmct.on('start', done);
openmct.startHeadless();
});
afterEach(() => {
resetApplicationState(openmct);
});
let mockConditionSetObject = {
name: 'Condition Set',
key: 'conditionSet',
@@ -389,113 +348,4 @@ describe('the plugin', function () {
});
});
describe('the condition check for staleness', () => {
let conditionSetDomainObject;
beforeEach(()=>{
conditionSetDomainObject = {
"configuration":{
"conditionTestData":[
{
"telemetry":"",
"metadata":"",
"input":""
}
],
"conditionCollection":[
{
"id":"39584410-cbf9-499e-96dc-76f27e69885d",
"configuration":{
"name":"Unnamed Condition",
"output":"Any stale telemetry",
"trigger":"all",
"criteria":[
{
"id":"35400132-63b0-425c-ac30-8197df7d5862",
"telemetry":"any",
"operation":"isStale",
"input":[
"1"
],
"metadata":"dataReceived"
}
]
},
"summary":"Match if all criteria are met: Any telemetry is stale after 5 seconds"
},
{
"isDefault":true,
"id":"2532d90a-e0d6-4935-b546-3123522da2de",
"configuration":{
"name":"Default",
"output":"Default",
"trigger":"all",
"criteria":[
]
},
"summary":""
}
]
},
"composition":[
{
"namespace":"",
"key":"test-object"
}
],
"telemetry":{
},
"name":"Condition Set",
"type":"conditionSet",
"identifier":{
"namespace":"",
"key":"cf4456a9-296a-4e6b-b182-62ed29cd15b9"
}
};
});
it('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
conditionMgr.on('conditionSetResultUpdated', mockListener);
conditionMgr.telemetryObjects = {
"test-object": testTelemetryObject
};
conditionMgr.updateConditionTelemetryObjects();
setTimeout(() => {
expect(mockListener).toHaveBeenCalledWith({
output: 'Any stale telemetry',
id: { namespace: '', key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9' },
conditionId: '39584410-cbf9-499e-96dc-76f27e69885d',
utc: undefined
});
done();
}, 1500);
});
it('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
const date = Date.now();
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["2"];
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
conditionMgr.on('conditionSetResultUpdated', mockListener);
conditionMgr.telemetryObjects = {
"test-object": testTelemetryObject
};
conditionMgr.updateConditionTelemetryObjects();
conditionMgr.telemetryReceived(testTelemetryObject, {
utc: date
});
setTimeout(() => {
expect(mockListener).toHaveBeenCalledWith({
output: 'Default',
id: { namespace: '', key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9' },
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
utc: undefined
});
done();
}, 1500);
});
});
});

View File

@@ -28,17 +28,10 @@ export const TRIGGER = {
};
export const TRIGGER_LABEL = {
'any': 'any criteria are met',
'all': 'all criteria are met',
'not': 'no criteria are met',
'xor': 'only one criterion is met'
};
export const TRIGGER_CONJUNCTION = {
'any': 'or',
'all': 'and',
'not': 'and',
'xor': 'or'
'any': 'when any criteria are met',
'all': 'when all criteria are met',
'not': 'when no criteria are met',
'xor': 'when only one criteria is met'
};
export const STYLE_CONSTANTS = {

View File

@@ -35,7 +35,7 @@ export const evaluateResults = (results, trigger) => {
function matchAll(results) {
for (const result of results) {
if (result !== true) {
if (!result) {
return false;
}
}
@@ -45,7 +45,7 @@ function matchAll(results) {
function matchAny(results) {
for (const result of results) {
if (result === true) {
if (result) {
return true;
}
}
@@ -56,7 +56,7 @@ function matchAny(results) {
function matchExact(results, target) {
let matches = 0;
for (const result of results) {
if (result === true) {
if (result) {
matches++;
}
if (matches > target) {

View File

@@ -250,12 +250,12 @@ export const OPERATIONS = [
}
},
{
name: 'isOneOf',
name: 'valueIs',
operation: function (input) {
const lhsValue = input[0] !== undefined ? input[0].toString() : '';
if (input[1]) {
const values = input[1].split(',');
return values.some((value) => lhsValue === value.toString().trim());
return values.find((value) => lhsValue === value.toString().trim());
}
return false;
},
@@ -267,12 +267,12 @@ export const OPERATIONS = [
}
},
{
name: 'isNotOneOf',
name: 'valueIsNot',
operation: function (input) {
const lhsValue = input[0] !== undefined ? input[0].toString() : '';
if (input[1]) {
const values = input[1].split(',');
const found = values.some((value) => lhsValue === value.toString().trim());
const found = values.find((value) => lhsValue === value.toString().trim());
return !found;
}
return false;
@@ -283,18 +283,6 @@ export const OPERATIONS = [
getDescription: function (values) {
return ' is not one of ' + values[0];
}
},
{
name: 'isStale',
operation: function () {
return false;
},
text: 'is older than',
appliesTo: ["number"],
inputCount: 1,
getDescription: function (values) {
return ` is older than ${values[0] || ''} seconds`;
}
}
];
@@ -302,8 +290,3 @@ export const INPUT_TYPES = {
'string': 'text',
'number': 'number'
};
export const getOperatorText = (operationName, values) => {
const found = OPERATIONS.find((operation) => operation.name === operationName);
return found ? found.getDescription(values) : '';
};

View File

@@ -21,8 +21,8 @@
*****************************************************************************/
import { OPERATIONS } from "./operations";
let isOneOfOperation = OPERATIONS.find((operation) => operation.name === 'isOneOf');
let isNotOneOfOperation = OPERATIONS.find((operation) => operation.name === 'isNotOneOf');
let isOneOfOperation = OPERATIONS.find((operation) => operation.name === 'valueIs');
let isNotOneOfOperation = OPERATIONS.find((operation) => operation.name === 'valueIsNot');
let isBetween = OPERATIONS.find((operation) => operation.name === 'between');
let isNotBetween = OPERATIONS.find((operation) => operation.name === 'notBetween');
let enumIsOperation = OPERATIONS.find((operation) => operation.name === 'enumValueIs');

View File

@@ -50,26 +50,3 @@ function updateLatestTimeStamp(timestamp, timeSystems) {
return latest;
}
export const subscribeForStaleness = (callback, timeout) => {
let stalenessTimer = setTimeout(() => {
clearTimeout(stalenessTimer);
callback();
}, timeout);
return {
update: (data) => {
if (stalenessTimer) {
clearTimeout(stalenessTimer);
}
stalenessTimer = setTimeout(() => {
clearTimeout(stalenessTimer);
callback(data);
}, timeout);
},
clear: () => {
if (stalenessTimer) {
clearTimeout(stalenessTimer);
}
}
}
};

View File

@@ -1,64 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { subscribeForStaleness } from "./time";
describe('time related utils', () => {
let subscription;
let mockListener;
beforeEach(() => {
mockListener = jasmine.createSpy('listener');
subscription = subscribeForStaleness(mockListener, 100);
});
describe('subscribe for staleness', () => {
it('should call listeners when stale', (done) => {
setTimeout(() => {
expect(mockListener).toHaveBeenCalled();
done();
}, 200);
});
it('should update the subscription', (done) => {
function updated() {
setTimeout(() => {
expect(mockListener).not.toHaveBeenCalled();
done();
}, 50);
}
setTimeout(() => {
subscription.update();
updated();
}, 50);
});
it('should clear the subscription', (done) => {
subscription.clear();
setTimeout(() => {
expect(mockListener).not.toHaveBeenCalled();
done();
}, 200);
});
});
});

View File

@@ -29,15 +29,11 @@ define([
function isTelemetryObject(selectionPath) {
let selectedObject = selectionPath[0].context.item;
let parentObject = selectionPath[1].context.item;
let selectedLayoutItem = selectionPath[0].context.layoutItem;
return parentObject &&
parentObject.type === 'layout' &&
selectedObject &&
selectedLayoutItem &&
selectedLayoutItem.type === 'telemetry-view' &&
openmct.telemetry.isTelemetryObject(selectedObject) &&
!options.showAsView.includes(selectedObject.type);
!options.showAsView.includes(selectedObject.type)
}
return {

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -41,90 +41,38 @@ define(['lodash'], function (_) {
},
toolbar: function (selectedObjects) {
const DIALOG_FORM = {
'text': {
name: "Text Element Properties",
sections: [
{
rows: [
{
key: "text",
control: "textfield",
name: "Text",
required: true
}
]
}
]
},
'image': {
name: "Image Properties",
sections: [
{
rows: [
{
key: "url",
control: "textfield",
name: "Image URL",
"cssClass": "l-input-lg",
required: true
}
]
}
]
}
},
VIEW_TYPES = {
'telemetry-view': {
value: 'telemetry-view',
name: 'Alphanumeric',
class: 'icon-alphanumeric'
},
'telemetry.plot.overlay': {
value: 'telemetry.plot.overlay',
name: 'Overlay Plot',
class: "icon-plot-overlay"
},
'telemetry.plot.stacked': {
value: "telemetry.plot.stacked",
name: "Stacked Plot",
class: "icon-plot-stacked"
},
'table': {
value: 'table',
name: 'Table',
class: 'icon-tabular-realtime'
}
},
APPLICABLE_VIEWS = {
'telemetry-view': [
VIEW_TYPES['telemetry.plot.overlay'],
VIEW_TYPES['telemetry.plot.stacked'],
VIEW_TYPES.table
],
'telemetry.plot.overlay': [
VIEW_TYPES['telemetry.plot.stacked'],
VIEW_TYPES.table,
VIEW_TYPES['telemetry-view']
],
'telemetry.plot.stacked': [
VIEW_TYPES['telemetry.plot.overlay'],
VIEW_TYPES.table,
VIEW_TYPES['telemetry-view']
],
'table': [
VIEW_TYPES['telemetry.plot.overlay'],
VIEW_TYPES['telemetry.plot.stacked'],
VIEW_TYPES['telemetry-view']
],
'telemetry-view-multi': [
VIEW_TYPES['telemetry.plot.overlay'],
VIEW_TYPES['telemetry.plot.stacked'],
VIEW_TYPES.table
],
'telemetry.plot.overlay-multi': [
VIEW_TYPES['telemetry.plot.stacked']
'text': {
name: "Text Element Properties",
sections: [
{
rows: [
{
key: "text",
control: "textfield",
name: "Text",
required: true
}
]
}
]
};
},
'image': {
name: "Image Properties",
sections: [
{
rows: [
{
key: "url",
control: "textfield",
name: "Image URL",
"cssClass": "l-input-lg",
required: true
}
]
}
]
}
};
function getUserInput(form) {
return openmct.$injector.get('dialogService').getUserInput(form, {});
@@ -467,100 +415,6 @@ define(['lodash'], function (_) {
}
}
function getDuplicateButton(selectedParent, selectionPath, selection) {
return {
control: "button",
domainObject: selectedParent,
icon: "icon-duplicate",
title: "Duplicate the selected object",
method: function () {
let duplicateItem = selectionPath[1].context.duplicateItem;
duplicateItem(selection);
}
};
}
function getPropertyFromPath(object, path) {
let splitPath = path.split('.'),
property = Object.assign({}, object);
while (splitPath.length && property) {
property = property[splitPath.shift()];
}
return property;
}
function areAllViews(type, path, selection) {
let allTelemetry = true;
selection.forEach(selectedItem => {
let selectedItemContext = selectedItem[0].context;
if (getPropertyFromPath(selectedItemContext, path) !== type) {
allTelemetry = false;
}
});
return allTelemetry;
}
function getViewSwitcherMenu(selectedParent, selectionPath, selection) {
if (selection.length === 1) {
let displayLayoutContext = selectionPath[1].context,
selectedItemContext = selectionPath[0].context,
selectedItemType = selectedItemContext.item.type;
if (selectedItemContext.layoutItem.type === 'telemetry-view') {
selectedItemType = 'telemetry-view';
}
let viewOptions = APPLICABLE_VIEWS[selectedItemType];
if (viewOptions) {
return {
control: "menu",
domainObject: selectedParent,
icon: "icon-object",
title: "Switch the way this telemetry is displayed",
options: viewOptions,
method: function (option) {
displayLayoutContext.switchViewType(selectedItemContext, option.value, selection);
}
};
}
} else if (selection.length > 1) {
if (areAllViews('telemetry-view', 'layoutItem.type', selection)) {
let displayLayoutContext = selectionPath[1].context;
return {
control: "menu",
domainObject: selectedParent,
icon: "icon-object",
title: "Merge into a telemetry table or plot",
options: APPLICABLE_VIEWS['telemetry-view-multi'],
method: function (option) {
displayLayoutContext.mergeMultipleTelemetryViews(selection, option.value);
}
};
} else if (areAllViews('telemetry.plot.overlay', 'item.type', selection)) {
let displayLayoutContext = selectionPath[1].context;
return {
control: "menu",
domainObject: selectedParent,
icon: "icon-object",
title: "Merge into a stacked plot",
options: APPLICABLE_VIEWS['telemetry.plot.overlay-multi'],
method: function (option) {
displayLayoutContext.mergeMultipleOverlayPlots(selection, option.value);
}
};
}
}
}
function getSeparator() {
return {
control: "separator"
@@ -581,14 +435,12 @@ define(['lodash'], function (_) {
'add-menu': [],
'text': [],
'url': [],
'viewSwitcher': [],
'toggle-frame': [],
'display-mode': [],
'telemetry-value': [],
'style': [],
'text-style': [],
'position': [],
'duplicate': [],
'remove': []
};
@@ -596,7 +448,7 @@ define(['lodash'], function (_) {
let selectedParent = selectionPath[1].context.item;
let layoutItem = selectionPath[0].context.layoutItem;
if (!layoutItem || selectedParent.locked) {
if (!layoutItem) {
return;
}
@@ -619,9 +471,6 @@ define(['lodash'], function (_) {
if (toolbar.remove.length === 0) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
if (toolbar.viewSwitcher.length === 0) {
toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'telemetry-view') {
if (toolbar['display-mode'].length === 0) {
toolbar['display-mode'] = [getDisplayModeMenu(selectedParent, selectedObjects)];
@@ -646,9 +495,6 @@ define(['lodash'], function (_) {
if (toolbar.remove.length === 0) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
if (toolbar.viewSwitcher.length === 0) {
toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'text-view') {
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
@@ -710,9 +556,6 @@ define(['lodash'], function (_) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
}
if(toolbar.duplicate.length === 0) {
toolbar.duplicate = [getDuplicateButton(selectedParent, selectionPath, selectedObjects)];
}
});
let toolbarArray = Object.values(toolbar);

View File

@@ -24,7 +24,6 @@
<layout-frame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
@@ -71,11 +70,7 @@ export default {
type: Number,
required: true
},
initSelect: Boolean,
isEditing: {
type: Boolean,
required: true
}
initSelect: Boolean
},
computed: {
style() {
@@ -96,13 +91,6 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {

View File

@@ -24,18 +24,14 @@
<div
class="l-layout"
:class="{
'is-multi-selected': selectedLayoutItems.length > 1,
'allow-editing': isEditing
'is-multi-selected': selectedLayoutItems.length > 1
}"
@dragover="handleDragOver"
@click.capture="bypassSelection"
@drop="handleDrop"
>
<!-- Background grid -->
<div
v-if="isEditing"
class="l-layout__grid-holder c-grid"
>
<div class="l-layout__grid-holder c-grid">
<div
v-if="gridSize[0] >= 3"
class="c-grid__x l-grid l-grid-x"
@@ -51,13 +47,11 @@
:is="item.type"
v-for="(item, index) in layoutItems"
:key="item.id"
:ref="`layout-item-${item.id}`"
:item="item"
:grid-size="gridSize"
:init-select="initSelectIndex === index"
:index="index"
:multi-select="selectedLayoutItems.length > 1"
:is-editing="isEditing"
@move="move"
@endMove="endMove"
@endLineResize="endLineResize"
@@ -83,30 +77,6 @@ import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue'
import _ from 'lodash'
const TELEMETRY_IDENTIFIER_FUNCTIONS = {
'table': (domainObject) => {
return Promise.resolve(domainObject.composition);
},
'telemetry.plot.overlay': (domainObject) => {
return Promise.resolve(domainObject.composition);
},
'telemetry.plot.stacked': (domainObject, openmct) => {
let composition = openmct.composition.get(domainObject);
return composition.load().then((objects) => {
let identifiers = [];
objects.forEach(object => {
if (object.type === 'telemetry.plot.overlay') {
identifiers.push(...object.composition);
} else {
identifiers.push(object.identifier);
}
});
return Promise.resolve(identifiers);
});
}
}
const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView,
'telemetry-view': TelemetryView,
@@ -122,7 +92,6 @@ const ORDERS = {
bottom: Number.NEGATIVE_INFINITY
};
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
const DUPLICATE_OFFSET = 3;
let components = ITEM_TYPE_VIEW_MAP;
components['edit-marquee'] = EditMarquee;
@@ -143,10 +112,6 @@ export default {
domainObject: {
type: Object,
required: true
},
isEditing: {
type: Boolean,
required: true
}
},
data() {
@@ -173,7 +138,7 @@ export default {
let selectionPath = this.selection[0];
let singleSelectedLine = this.selection.length === 1 &&
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view';
return this.isEditing && selectionPath && selectionPath.length > 1 && !singleSelectedLine;
return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
}
},
inject: ['openmct', 'options', 'objectPath'],
@@ -336,9 +301,9 @@ export default {
if (this.isTelemetry(domainObject)) {
this.addItem('telemetry-view', domainObject, droppedObjectPosition);
} else {
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
let identifier = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.objectViewMap[keyString]) {
if (!this.objectViewMap[identifier]) {
this.addItem('subobject-view', domainObject, droppedObjectPosition);
} else {
let prompt = this.openmct.overlays.dialog({
@@ -361,9 +326,6 @@ export default {
.some(childId => this.openmct.objects.areIdsEqual(childId, identifier));
},
handleDragOver($event) {
if (this.internalDomainObject.locked) {
return;
}
// Get the ID of the dragged object
let draggedKeyString = $event.dataTransfer.types
.filter(type => type.startsWith(DRAG_OBJECT_TRANSFER_PREFIX))
@@ -403,8 +365,7 @@ export default {
let count = this.telemetryViewMap[keyString] || 0;
this.telemetryViewMap[keyString] = ++count;
} else if (item.type === "subobject-view") {
let count = this.objectViewMap[keyString] || 0;
this.objectViewMap[keyString] = ++count;
this.objectViewMap[keyString] = true;
}
},
removeItem(selectedItems) {
@@ -423,25 +384,17 @@ export default {
return;
}
let keyString = this.openmct.objects.makeKeyString(item.identifier),
telemetryViewCount = this.telemetryViewMap[keyString],
objectViewCount = this.objectViewMap[keyString];
let keyString = this.openmct.objects.makeKeyString(item.identifier);
if (item.type === 'telemetry-view') {
telemetryViewCount = --this.telemetryViewMap[keyString];
let count = --this.telemetryViewMap[keyString];
if (telemetryViewCount === 0) {
if (count === 0) {
delete this.telemetryViewMap[keyString];
this.removeFromComposition(keyString);
}
} else if (item.type === 'subobject-view') {
objectViewCount = --this.objectViewMap[keyString];
if (objectViewCount === 0) {
delete this.objectViewMap[keyString];
}
}
if (!telemetryViewCount && !objectViewCount) {
delete this.objectViewMap[keyString];
this.removeFromComposition(keyString);
}
},
@@ -457,43 +410,15 @@ export default {
this.objectViewMap = {};
this.layoutItems.forEach(this.trackItem);
},
isItemAlreadyTracked(child) {
let found = false,
keyString = this.openmct.objects.makeKeyString(child.identifier);
this.layoutItems.forEach(item => {
if (item.identifier) {
let itemKeyString = this.openmct.objects.makeKeyString(item.identifier);
if (itemKeyString === keyString) {
found = true;
return;
}
}
});
if (found) {
return true;
} else if (this.isTelemetry(child)) {
return this.telemetryViewMap[keyString] && this.objectViewMap[keyString];
} else {
return this.objectViewMap[keyString];
}
},
addChild(child) {
if (this.isItemAlreadyTracked(child)) {
return;
}
let type;
let identifier = this.openmct.objects.makeKeyString(child.identifier);
if (this.isTelemetry(child)) {
type = 'telemetry-view';
} else {
type = 'subobject-view';
if (!this.telemetryViewMap[identifier]) {
this.addItem('telemetry-view', child);
}
} else if (!this.objectViewMap[identifier]) {
this.addItem('subobject-view', child);
}
this.addItem(type, child);
},
removeChild(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
@@ -590,191 +515,6 @@ export default {
let index = this.layoutItems.findIndex(item);
item.format = format;
this.mutate(`configuration.items[${index}]`, item);
},
createNewDomainObject(domainObject, composition, viewType, nameExtension, model) {
let identifier = {
key: uuid(),
namespace: this.internalDomainObject.identifier.namespace
},
type = this.openmct.types.get(viewType),
parentKeyString = this.openmct.objects.makeKeyString(this.internalDomainObject.identifier),
objectName = nameExtension ? `${domainObject.name}-${nameExtension}` : domainObject.name,
object = {};
if (model) {
object = _.cloneDeep(model);
} else {
object.type = viewType;
type.definition.initialize(object);
object.composition.push(...composition);
}
object.name = objectName;
object.identifier = identifier;
object.location = parentKeyString;
this.openmct.objects.mutate(object, 'created', Date.now());
return object;
},
convertToTelemetryView(identifier, position) {
this.openmct.objects.get(identifier).then((domainObject) => {
this.composition.add(domainObject);
this.addItem('telemetry-view', domainObject, position);
});
},
dispatchMultipleSelection(selectItemsArray) {
let event = new MouseEvent('click', {
bubbles: true,
shiftKey: true,
cancelable: true,
view: window
})
selectItemsArray.forEach((id) => {
let refId = `layout-item-${id}`,
component = this.$refs[refId] && this.$refs[refId][0];
if (component) {
component.immediatelySelect = event;
component.$el.dispatchEvent(event);
}
});
},
duplicateItem(selectedItems) {
let objectStyles = this.internalDomainObject.configuration.objectStyles || {},
selectItemsArray = [],
newDomainObjectsArray = [];
selectedItems.forEach(selectedItem => {
let layoutItem = selectedItem[0].context.layoutItem,
domainObject = selectedItem[0].context.item,
layoutItemStyle = objectStyles[layoutItem.id],
copy = _.cloneDeep(layoutItem);
copy.id = uuid();
selectItemsArray.push(copy.id);
let offsetKeys = ['x', 'y'];
if (copy.type === 'line-view') {
offsetKeys = offsetKeys.concat(['x2', 'y2']);
}
if (copy.type === 'subobject-view') {
let newDomainObject = this.createNewDomainObject(domainObject, domainObject.composition, domainObject.type, 'duplicate', domainObject);
newDomainObjectsArray.push(newDomainObject);
copy.identifier = newDomainObject.identifier;
}
offsetKeys.forEach(key => {
copy[key] += DUPLICATE_OFFSET
});
if (layoutItemStyle) {
objectStyles[copy.id] = layoutItemStyle;
}
this.trackItem(copy);
this.layoutItems.push(copy);
});
this.$nextTick(() => {
this.openmct.objects.mutate(this.internalDomainObject, "configuration.items", this.layoutItems);
this.openmct.objects.mutate(this.internalDomainObject, "configuration.objectStyles", objectStyles);
this.$el.click(); //clear selection;
newDomainObjectsArray.forEach(domainObject => {
this.composition.add(domainObject);
});
this.dispatchMultipleSelection(selectItemsArray);
});
},
mergeMultipleTelemetryViews(selection, viewType) {
let identifiers = selection.map(selectedItem => {
return selectedItem[0].context.layoutItem.identifier;
}),
firstDomainObject = selection[0][0].context.item,
firstLayoutItem = selection[0][0].context.layoutItem,
position = [firstLayoutItem.x, firstLayoutItem.y],
mockDomainObject = {
name: 'Merged Telemetry Views',
identifier: firstDomainObject.identifier
},
newDomainObject = this.createNewDomainObject(mockDomainObject, identifiers, viewType);
this.composition.add(newDomainObject);
this.addItem('subobject-view', newDomainObject, position);
this.removeItem(selection);
this.initSelectIndex = this.layoutItems.length - 1;
},
mergeMultipleOverlayPlots(selection, viewType) {
let overlayPlots = selection.map(selectedItem => selectedItem[0].context.item),
overlayPlotIdentifiers = overlayPlots.map(overlayPlot => overlayPlot.identifier),
firstOverlayPlot = overlayPlots[0],
firstLayoutItem = selection[0][0].context.layoutItem,
position = [firstLayoutItem.x, firstLayoutItem.y],
mockDomainObject = {
name: 'Merged Overlay Plots',
identifier: firstOverlayPlot.identifier
},
newDomainObject = this.createNewDomainObject(mockDomainObject, overlayPlotIdentifiers, viewType),
newDomainObjectKeyString = this.openmct.objects.makeKeyString(newDomainObject.identifier),
internalDomainObjectKeyString = this.openmct.objects.makeKeyString(this.internalDomainObject.identifier);
this.composition.add(newDomainObject);
this.addItem('subobject-view', newDomainObject, position);
overlayPlots.forEach(overlayPlot => {
if (overlayPlot.location === internalDomainObjectKeyString) {
this.openmct.objects.mutate(overlayPlot, 'location', newDomainObjectKeyString);
}
});
this.removeItem(selection);
this.initSelectIndex = this.layoutItems.length - 1;
},
getTelemetryIdentifiers(domainObject) {
let method = TELEMETRY_IDENTIFIER_FUNCTIONS[domainObject.type];
if (method) {
return method(domainObject, this.openmct);
} else {
throw 'No method identified for domainObject type';
}
},
switchViewType(context, viewType, selection) {
let domainObject = context.item,
layoutItem = context.layoutItem,
position = [layoutItem.x, layoutItem.y],
layoutType = 'subobject-view';
if (layoutItem.type === 'telemetry-view') {
let newDomainObject = this.createNewDomainObject(domainObject, [domainObject.identifier], viewType);
this.composition.add(newDomainObject);
this.addItem(layoutType, newDomainObject, position);
} else {
this.getTelemetryIdentifiers(domainObject).then((identifiers) => {
if (viewType === 'telemetry-view') {
identifiers.forEach((identifier, index) => {
let positionX = position[0] + (index * DUPLICATE_OFFSET),
positionY = position[1] + (index * DUPLICATE_OFFSET);
this.convertToTelemetryView(identifier, [positionX, positionY]);
});
} else {
let newDomainObject = this.createNewDomainObject(domainObject, identifiers, viewType);
this.composition.add(newDomainObject);
this.addItem(layoutType, newDomainObject, position);
}
});
}
this.removeItem(selection);
this.initSelectIndex = this.layoutItems.length - 1; //restore selection
}
}
}

View File

@@ -24,7 +24,6 @@
<layout-frame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
@@ -71,11 +70,7 @@ export default {
type: Number,
required: true
},
initSelect: Boolean,
isEditing: {
type: Boolean,
required: true
}
initSelect: Boolean
},
computed: {
style() {
@@ -100,13 +95,6 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {

View File

@@ -33,7 +33,7 @@
<div
class="c-frame-edit__move"
@mousedown="isEditing ? startMove([1,1], [0,0], $event) : null"
@mousedown="startMove([1,1], [0,0], $event)"
></div>
</div>
</template>
@@ -54,10 +54,6 @@ export default {
required: true,
validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number')
},
isEditing: {
type: Boolean,
required: true
}
},
computed: {

View File

@@ -194,13 +194,6 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {

View File

@@ -24,7 +24,6 @@
:item="item"
:grid-size="gridSize"
:title="domainObject && domainObject.name"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
@@ -62,7 +61,7 @@ function hasFrameByDefault(type) {
}
export default {
makeDefinition(openmct, gridSize, domainObject, position, viewKey) {
makeDefinition(openmct, gridSize, domainObject, position) {
let defaultDimensions = getDefaultDimensions(gridSize);
position = position || DEFAULT_POSITION;
@@ -72,8 +71,7 @@ export default {
x: position[0],
y: position[1],
identifier: domainObject.identifier,
hasFrame: hasFrameByDefault(domainObject.type),
viewKey
hasFrame: hasFrameByDefault(domainObject.type)
};
},
inject: ['openmct', 'objectPath'],
@@ -96,10 +94,6 @@ export default {
index: {
type: Number,
required: true
},
isEditing: {
type: Boolean,
required: true
}
},
data() {
@@ -115,13 +109,6 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {
@@ -144,8 +131,7 @@ export default {
childContext.index = this.index;
this.context = childContext;
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.immediatelySelect || this.initSelect);
delete this.immediatelySelect;
this.$el, this.context, this.initSelect);
});
}
}

View File

@@ -24,7 +24,6 @@
<layout-frame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
@@ -106,10 +105,6 @@ export default {
index: {
type: Number,
required: true
},
isEditing: {
type: Boolean,
required: true
}
},
data() {
@@ -173,10 +168,6 @@ export default {
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
@@ -251,8 +242,7 @@ export default {
updateTelemetryFormat: this.updateTelemetryFormat
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.immediatelySelect || this.initSelect);
delete this.immediatelySelect;
this.$el, this.context, this.initSelect);
},
updateTelemetryFormat(format) {
this.$emit('formatChanged', this.item, format);

View File

@@ -24,7 +24,6 @@
<layout-frame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
@@ -76,11 +75,7 @@ export default {
type: Number,
required: true
},
initSelect: Boolean,
isEditing: {
type: Boolean,
required: true
}
initSelect: Boolean
},
computed: {
style() {
@@ -96,13 +91,6 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {

View File

@@ -45,7 +45,8 @@
&[s-selected],
&[s-selected-parent] {
// Display grid and allow edit marquee to display in nested layouts when editing
> * > * > .l-layout + .allow-editing {
> * > * > .l-layout {
background: $editUIGridColorBg;
box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
> [class*='grid-holder'] {

View File

@@ -54,11 +54,10 @@ export default function DisplayLayoutPlugin(options) {
},
data() {
return {
domainObject: domainObject,
isEditing: openmct.editor.isEditing()
domainObject: domainObject
};
},
template: '<layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></layout>'
template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>'
});
},
getSelectionContext() {
@@ -67,15 +66,8 @@ export default function DisplayLayoutPlugin(options) {
supportsMultiSelect: true,
addElement: component && component.$refs.displayLayout.addElement,
removeItem: component && component.$refs.displayLayout.removeItem,
orderItem: component && component.$refs.displayLayout.orderItem,
duplicateItem: component && component.$refs.displayLayout.duplicateItem,
switchViewType: component && component.$refs.displayLayout.switchViewType,
mergeMultipleTelemetryViews: component && component.$refs.displayLayout.mergeMultipleTelemetryViews,
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots
};
},
onEditModeChange: function (isEditing) {
component.isEditing = isEditing;
orderItem: component && component.$refs.displayLayout.orderItem
}
},
destroy() {
component.$destroy();

View File

@@ -53,7 +53,6 @@
:index="i"
:container-index="index"
:is-editing="isEditing"
:object-path="objectPath"
/>
<drop-hint
@@ -106,14 +105,6 @@ export default {
isEditing: {
type: Boolean,
default: false
},
locked: {
type: Boolean,
default: false
},
objectPath: {
type: Array,
required: true
}
},
computed: {
@@ -139,10 +130,6 @@ export default {
},
methods: {
allowDrop(event, index) {
if (this.locked) {
return false;
}
if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
return true;
}

View File

@@ -57,8 +57,6 @@
:container="container"
:rows-layout="rowsLayout"
:is-editing="isEditing"
:locked="domainObject.locked"
:object-path="objectPath"
@move-frame="moveFrame"
@new-frame="setFrameLocation"
@persist="persist"
@@ -138,7 +136,7 @@ function sizeToFill(items) {
}
export default {
inject: ['openmct', 'objectPath', 'layoutObject'],
inject: ['openmct', 'layoutObject'],
components: {
ContainerComponent,
ResizeHandle,

View File

@@ -37,7 +37,7 @@
v-if="domainObject"
ref="objectFrame"
:domain-object="domainObject"
:object-path="currentObjectPath"
:object-path="objectPath"
:has-frame="hasFrame"
:show-edit-view="false"
/>
@@ -77,16 +77,12 @@ export default {
isEditing: {
type: Boolean,
default: false
},
objectPath: {
type: Array,
required: true
}
},
data() {
return {
domainObject: undefined,
currentObjectPath: undefined
objectPath: undefined
}
},
computed: {
@@ -111,7 +107,7 @@ export default {
methods: {
setDomainObject(object) {
this.domainObject = object;
this.currentObjectPath = [object].concat(this.objectPath);
this.objectPath = [object];
this.setSelection();
},
setSelection() {

View File

@@ -38,7 +38,7 @@ define([
canEdit: function (domainObject) {
return domainObject.type === 'flexible-layout';
},
view: function (domainObject, objectPath) {
view: function (domainObject) {
let component;
return {
@@ -46,7 +46,6 @@ define([
component = new Vue({
provide: {
openmct,
objectPath,
layoutObject: domainObject
},
el: element,

View File

@@ -70,10 +70,6 @@ function ToolbarProvider(openmct) {
}
if (primary.context.type === 'frame') {
if (secondary.context.item.locked) {
return [];
}
let frameId = primary.context.frameId;
let layoutObject = tertiary.context.item;
let containers = layoutObject
@@ -147,9 +143,6 @@ function ToolbarProvider(openmct) {
toggleContainer.domainObject = secondary.context.item;
} else if (primary.context.type === 'container') {
if (primary.context.item.locked) {
return [];
}
deleteContainer = {
control: "button",
@@ -194,9 +187,6 @@ function ToolbarProvider(openmct) {
};
} else if (primary.context.type === 'flexible-layout') {
if (primary.context.item.locked) {
return [];
}
addContainer = {
control: "button",

View File

@@ -0,0 +1,47 @@
<template>
<div class="c-checkbox-menu js-checkbox-menu c-menu--to-left c-menu--has-close-btn">
<ul
@click="$event.stopPropagation()"
>
<li
v-for="(layer, index) in layers"
:key="index"
>
<input v-if="layer.visible"
:id="index + 'LayerControl'"
checked
type="checkbox"
@change="toggleLayerVisibility(index)"
>
<input v-else
:id="index + 'LayerControl'"
type="checkbox"
@change="toggleLayerVisibility(index)"
>
<label :for="index + 'LayerControl'">{{ layer.name }}</label>
</li>
</ul>
<a class="s-icon-button icon-x t-btn-close c-switcher-menu__close-button"></a>
</div>
</template>
<script>
export default {
inject: ['openmct'],
props: {
layers: {
type: Array,
default() {
return []
}
}
},
methods: {
toggleLayerVisibility(index) {
this.$emit('toggleLayerVisibility', index);
}
}
}
</script>

View File

@@ -0,0 +1,62 @@
<template>
<div class="c-control-menu c-menu--to-left c-menu--has-close-btn c-image-controls">
<div class="c-image-controls__controls"
@click="$event.stopPropagation()"
>
<span class="c-image-controls__sliders">
<div class="c-image-controls__slider-wrapper icon-brightness">
<input v-model="filters.brightness"
type="range"
min="0"
max="500"
@change="notifyFiltersChanged"
@input="notifyFiltersChanged"
>
</div>
<div class="c-image-controls__slider-wrapper icon-contrast">
<input v-model="filters.contrast"
type="range"
min="0"
max="500"
@change="notifyFiltersChanged"
@input="notifyFiltersChanged"
>
</div>
</span>
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
<a class="s-icon-button icon-reset t-btn-reset"
@click="resetFilters"
></a>
</span>
</div>
<a class="s-icon-button icon-x t-btn-close c-switcher-menu__close-button"></a>
</div>
</template>
<script>
export default {
inject: ['openmct'],
data() {
return {
filters: {
brightness: 100,
contrast: 100
}
}
},
methods: {
notifyFiltersChanged() {
this.$emit('filterChanged', this.filters);
},
resetFilters() {
this.filters = {
brightness: 100,
contrast: 100
};
this.notifyFiltersChanged();
}
}
}
</script>

View File

@@ -1,40 +1,39 @@
<template>
<div class="c-imagery">
<div class="c-imagery__main-image-wrapper has-local-controls">
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
<span class="holder flex-elem grows c-imagery__lc__sliders">
<input v-model="filters.brightness"
class="icon-brightness"
type="range"
min="0"
max="500"
>
<input v-model="filters.contrast"
class="icon-contrast"
type="range"
min="0"
max="500"
>
</span>
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
<a class="s-icon-button icon-reset t-btn-reset"
@click="filters={brightness: 100, contrast: 100}"
></a>
</span>
<div class="h-local-controls h-local-controls--horz h-local-controls--overlay-content c-local-controls--show-on-hover c-imagery__lc">
<imagery-view-menu-switcher :icon-class="'icon-brightness'"
:title="'Filters menu'"
>
<imagery-settings @filterChanged="updateFilterValues" />
</imagery-view-menu-switcher>
<imagery-view-menu-switcher v-if="layers.length"
:icon-class="'icon-layers'"
:title="'Layers menu'"
>
<imagery-layers :layers="layers"
@toggleLayerVisibility="toggleLayerVisibility"
/>
</imagery-view-menu-switcher>
</div>
<div class="main-image s-image-main c-imagery__main-image"
:class="{'paused unnsynced': paused(),'stale':false }"
:style="{'background-image': getImageUrl() ? `url(${getImageUrl()})` : 'none',
:style="{'background-image': `url(${getImageUrl()})`,
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
>
</div>
<div v-for="(layer, index) in visibleLayers"
:key="index"
class="layer-image s-image-layer c-imagery__layer-image"
:style="{'background-image': `url(${layer.source})`}"
>
</div>
<div class="c-imagery__control-bar">
<div class="c-imagery__timestamp">{{ getTime() }}</div>
<div class="h-local-controls flex-elem">
<a
class="c-button icon-pause pause-play"
:class="{'is-paused': paused()}"
@click="paused(!paused())"
<a class="c-button icon-pause pause-play"
:class="{'is-paused': paused()}"
@click="paused(!paused())"
></a>
</div>
</div>
@@ -61,9 +60,17 @@
<script>
import _ from 'lodash';
import ImagerySettings from "./ImagerySettings.vue";
import ImageryLayers from "./ImageryLayers.vue";
import ImageryViewMenuSwitcher from "./ImageryViewMenuSwitcher.vue";
export default {
inject: ['openmct', 'domainObject'],
components: {
ImagerySettings,
ImageryLayers,
ImageryViewMenuSwitcher
},
data() {
return {
autoScroll: true,
@@ -80,20 +87,25 @@ export default {
isPaused: false,
metadata: {},
requestCount: 0,
timeFormat: ''
timeFormat: '',
layers: [],
visibleLayers: []
}
},
mounted() {
// set
this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.imageFormat = this.openmct.telemetry.getValueFormatter(this.metadata.valuesForHints(['image'])[0]);
const metaDataValues = this.metadata.valuesForHints(['image'])[0];
this.imageFormat = this.openmct.telemetry.getValueFormatter(metaDataValues);
this.loadVisibleLayers(metaDataValues);
// 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);
window.addEventListener('beforeunload', this.persistVisibleLayers);
// kickoff
this.subscribe();
this.requestHistory();
@@ -102,12 +114,14 @@ export default {
this.scrollToRight();
},
beforeDestroy() {
this.persistVisibleLayers();
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
}
this.openmct.time.off('bounds', this.boundsChange);
this.openmct.time.off('timeSystem', this.timeSystemChange);
window.removeEventListener('beforeunload', this.persistVisibleLayers);
},
methods: {
datumIsNotValid(datum) {
@@ -150,6 +164,27 @@ export default {
|| (scrollHeight - scrollTop) > 2 * clientHeight;
this.autoScroll = !disableScroll;
},
loadVisibleLayers(metaDataValues) {
let layersMetadata = metaDataValues.layers;
if (layersMetadata) {
this.layers = layersMetadata;
if (this.domainObject.configuration) {
let persistedLayers = this.domainObject.configuration.layers;
layersMetadata.forEach((layer) => {
const persistedLayer = persistedLayers.find((object) =>{ return object.name === layer.name; });
if (persistedLayer) {
layer.visible = persistedLayer.visible === true;
}
});
this.visibleLayers = this.layers.filter((layer) => { return layer.visible === true; });
} else {
this.visibleLayers = [];
this.layers.forEach((layer) => {
layer.visible = false;
})
}
}
},
paused(state) {
if (arguments.length > 0 && state !== this.isPaused) {
this.unselectAllImages();
@@ -171,6 +206,13 @@ export default {
return this.isPaused;
},
persistVisibleLayers() {
if (this.domainObject.configuration) {
this.openmct.objects.mutate(this.domainObject, 'configuration.layers', this.layers);
}
this.visibleLayers = [];
this.layers = [];
},
scrollToRight() {
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
return;
@@ -186,10 +228,6 @@ export default {
setSelectedImage(image) {
// If we are paused and the current image IS selected, unpause
// Otherwise, set current image and pause
if (!image) {
return;
}
if (this.isPaused && image.selected) {
this.paused(false);
this.unselectAllImages();
@@ -202,7 +240,7 @@ export default {
}
},
boundsChange(bounds, isTick) {
if (!isTick) {
if(!isTick) {
this.requestHistory();
}
},
@@ -261,6 +299,14 @@ export default {
this.time = this.timeFormat.format(datum);
this.imageUrl = this.imageFormat.format(datum);
},
updateFilterValues(filters) {
this.filters = filters;
},
toggleLayerVisibility(index) {
let isVisible = this.layers[index].visible === true;
this.layers[index].visible = !isVisible;
this.visibleLayers = this.layers.filter((layer) => { return layer.visible; });
}
}
}

View File

@@ -0,0 +1,58 @@
<template>
<div class="c-switcher-menu">
<button
class="c-button c-button--menu c-switcher-menu__button"
:class="iconClass"
:title="title"
@click.stop="toggleMenu"
>
<span class="c-button__label"></span>
</button>
<div
v-show="showMenu"
class="c-switcher-menu__content"
>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
inject: ['openmct'],
props: {
iconClass: {
type: String,
default() {
return '';
}
},
title: {
type: String,
default() {
return '';
}
}
},
data() {
return {
showMenu: false
}
},
mounted() {
document.addEventListener('click', this.hideMenu);
},
destroyed() {
document.removeEventListener('click', this.hideMenu);
},
methods: {
toggleMenu() {
this.showMenu = !this.showMenu;
},
hideMenu() {
this.showMenu = false;
}
}
}
</script>

View File

@@ -14,7 +14,7 @@
flex: 1 1 auto;
}
&__main-image {
&__main-image, &__layer-image {
background-position: center;
background-repeat: no-repeat;
background-size: contain;
@@ -95,34 +95,31 @@
border: 1px solid transparent;
}
.s-image-layer {
position: absolute;
height: 100%;
width: 100%;
opacity: 0.5;
}
/*************************************** IMAGERY LOCAL CONTROLS*/
.c-imagery {
.h-local-controls--overlay-content {
// Outer holder, holds menu buttons
//@include test();
position: absolute;
right: $interiorMargin; top: $interiorMargin;
z-index: 2;
background: $colorLocalControlOvrBg;
border-radius: $basicCr;
max-width: 200px;
min-width: 100px;
width: 35%;
align-items: center;
padding: $interiorMargin $interiorMarginLg;
input[type="range"] {
display: block;
width: 100%;
&:not(:first-child) {
margin-top: $interiorMarginLg;
}
&:before {
margin-right: $interiorMarginSm;
}
}
//background: $colorLocalControlOvrBg;
//border-radius: $basicCr;
//max-width: 200px;
//min-width: 100px;
//width: 35%;
//padding: $interiorMargin $interiorMarginLg;
}
&__lc {
// Local Controls
&__reset-btn {
$bc: $scrollbarTrackColorBg;
&:before,
@@ -145,6 +142,47 @@
}
}
}
&__lc {
&__close-btn {
$bc: $scrollbarTrackColorBg;
}
}
}
.c-image-controls {
// Brightness/contrast
&__controls {
// Sliders and reset element
display: flex;
align-items: center;
margin-right: $interiorMargin; // Need some extra space due to proximity to close button
}
&__sliders {
display: flex;
flex-direction: column;
> * + * {
margin-top: 11px;
}
}
&__slider-wrapper {
// A wrapper is needed to add the type icon to left of each range input
display: flex;
align-items: center;
&:before {
color: rgba($colorMenuFg, 0.5);
margin-right: $interiorMarginSm;
}
input[type='range'] {
width: 100px;
}
}
}
/*************************************** BUTTONS */

View File

@@ -0,0 +1,380 @@
/*****************************************************************************
* 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 {createMouseEvent, createOpenMct} from "testUtils";
import ImageryPlugin from "./plugin";
import Vue from 'vue';
describe('the plugin', function () {
let element;
let child;
let openmct;
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(new ImageryPlugin());
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct.on('start', done);
openmct.startHeadless();
});
it('provides an imagery view for telemetry with images', () => {
const testObject = {
id:"test-object",
type: "example.imagery",
telemetry: {
values: [
{
name: 'Name',
key: 'name'
},
{
name: 'Time',
key: 'utc',
format: 'utc',
hints: {
domain: 1
}
},
{
name: 'Image',
key: 'url',
format: 'image',
layers: [
{
source: 'dist/images/bg-splash.jpg',
name: 'Big Splash'
},
{
source: 'dist/images/logo-nasa.svg',
name: 'Nasa Logo'
}
],
hints: {
image: 1
}
}
]
}
};
const applicableViews = openmct.objectViews.get(testObject);
let imageryView = applicableViews.find((viewProvider) => viewProvider.key === 'example.imagery');
expect(imageryView).toBeDefined();
});
describe("The imagery view", () => {
let testTelemetryObject;
let applicableViews;
let imageryViewProvider;
let imageryView;
beforeEach(() => {
testTelemetryObject = {
identifier:{ namespace: "", key: "test-object"},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "some-key",
name: "Some attribute",
hints: {
domain: 1
}
},
{
name: 'Time',
key: 'utc',
format: 'utc',
hints: {
domain: 1
}
},
{
key: "some-other-key",
name: "Another attribute",
format: 'image',
hints: {
image: 1
},
layers: [
{
source: 'dist/imagery/example-imagery-layer-16x9.png',
name: '16:9'
},
{
source: 'dist/imagery/example-imagery-layer-safe.png',
name: 'Safe'
}
]
}]
}
};
applicableViews = openmct.objectViews.get(testTelemetryObject);
imageryViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'example.imagery');
imageryView = imageryViewProvider.view(testTelemetryObject, true, [testTelemetryObject]);
imageryView.show(child, true);
return Vue.nextTick();
});
it("Renders an image filters menu button",() => {
let filtersMenuSwitcher = element.querySelectorAll('button.c-button--menu.icon-brightness');
expect(filtersMenuSwitcher.length).toBe(1);
expect(filtersMenuSwitcher[0].title).toBe('Filters menu');
});
it("Shows the filters controls",() => {
let filtersMenuSwitcher = element.querySelectorAll('button.c-button--menu.icon-brightness');
expect(filtersMenuSwitcher.length).toBe(1);
expect(filtersMenuSwitcher[0].title).toBe('Filters menu');
let event = createMouseEvent('click');
filtersMenuSwitcher[0].dispatchEvent(event);
return Vue.nextTick().then(() => {
let filtersMenu = element.querySelectorAll('button.c-button--menu.icon-brightness + .c-switcher-menu__content');
expect(filtersMenu.length).toBe(1);
let filtersSliders = element.querySelectorAll('.c-image-controls__sliders');
expect(filtersSliders.length).toBe(1);
});
});
it("Shows the layers controls",() => {
let layersMenuSwitcher = element.querySelectorAll('button.c-button--menu.icon-layers');
expect(layersMenuSwitcher.length).toBe(1);
expect(layersMenuSwitcher[0].title).toBe('Layers menu');
let event = createMouseEvent('click');
layersMenuSwitcher[0].dispatchEvent(event);
return Vue.nextTick().then(() => {
let layersMenu = element.querySelectorAll('button.c-button--menu.icon-layers + .c-switcher-menu__content');
expect(layersMenu.length).toBe(1);
let layers = element.querySelectorAll('.js-checkbox-menu li');
expect(layers.length).toBe(2);
});
});
});
describe("The imagery view for creatable objects", () => {
let testCreatableImageryObject;
let applicableViews;
let imageryViewProvider;
let imageryView;
beforeEach(() => {
testCreatableImageryObject = {
identifier:{ namespace: "", key: "test-object"},
type: "example.imagery",
name: "Test Object",
configuration: {
layers: [
{
source: 'dist/imagery/example-imagery-layer-16x9.png',
name: '16:9',
visible: true
},
{
source: 'dist/imagery/example-imagery-layer-safe.png',
name: 'Safe'
}
]
},
telemetry: {
values: [{
key: "some-key",
name: "Some attribute",
hints: {
domain: 1
}
},
{
name: 'Time',
key: 'utc',
format: 'utc',
hints: {
domain: 1
}
},
{
key: "some-other-key",
name: "Another attribute",
format: 'image',
hints: {
image: 1
},
layers: [
{
source: 'dist/imagery/example-imagery-layer-16x9.png',
name: '16:9'
},
{
source: 'dist/imagery/example-imagery-layer-safe.png',
name: 'Safe'
}
]
}]
}
};
applicableViews = openmct.objectViews.get(testCreatableImageryObject);
imageryViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'example.imagery');
imageryView = imageryViewProvider.view(testCreatableImageryObject, true, [testCreatableImageryObject]);
imageryView.show(child, true);
return Vue.nextTick();
});
it("shows previously visible layers",() => {
let layersMenuSwitcher = element.querySelectorAll('button.c-button--menu.icon-layers');
expect(layersMenuSwitcher.length).toBe(1);
expect(layersMenuSwitcher[0].title).toBe('Layers menu');
let event = createMouseEvent('click');
layersMenuSwitcher[0].dispatchEvent(event);
return Vue.nextTick().then(() => {
let layersMenu = element.querySelectorAll('button.c-button--menu.icon-layers + .c-switcher-menu__content');
expect(layersMenu.length).toBe(1);
let layers = element.querySelectorAll('.js-checkbox-menu input[checked]');
expect(layers.length).toBe(1);
});
});
it("saves visible layers",() => {
let layersMenuSwitcher = element.querySelectorAll('button.c-button--menu.icon-layers');
expect(layersMenuSwitcher.length).toBe(1);
expect(layersMenuSwitcher[0].title).toBe('Layers menu');
let event = createMouseEvent('click');
layersMenuSwitcher[0].dispatchEvent(event);
return Vue.nextTick().then(() => {
let layersMenu = element.querySelectorAll('button.c-button--menu.icon-layers + .c-switcher-menu__content');
expect(layersMenu.length).toBe(1);
let checkedlayers = element.querySelectorAll('.js-checkbox-menu input[checked]');
expect(checkedlayers.length).toBe(1);
let uncheckedLayers = element.querySelectorAll('.js-checkbox-menu input:not([checked])');
expect(uncheckedLayers.length).toBe(1);
uncheckedLayers[0].dispatchEvent(event);
return Vue.nextTick().then(() => {
imageryView.destroy();
return Vue.nextTick().then(() => {
const visibleLayers = testCreatableImageryObject.configuration.layers.filter((layer) => {
return layer.visible === true;
});
expect(visibleLayers.length).toBe(2);
});
});
});
});
});
describe("The imagery view for objects that are not creatable", () => {
let testImageryObject;
let applicableViews;
let imageryViewProvider;
let imageryView;
beforeEach(() => {
testImageryObject = {
identifier:{ namespace: "", key: "test-object"},
type: "example.imagery",
name: "Test Object",
telemetry: {
values: [{
key: "some-key",
name: "Some attribute",
hints: {
domain: 1
}
},
{
name: 'Time',
key: 'utc',
format: 'utc',
hints: {
domain: 1
}
},
{
key: "some-other-key",
name: "Another attribute",
format: 'image',
hints: {
image: 1
},
layers: [
{
source: 'dist/imagery/example-imagery-layer-16x9.png',
name: '16:9'
},
{
source: 'dist/imagery/example-imagery-layer-safe.png',
name: 'Safe'
}
]
}]
}
};
applicableViews = openmct.objectViews.get(testImageryObject);
imageryViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'example.imagery');
imageryView = imageryViewProvider.view(testImageryObject, true, [testImageryObject]);
imageryView.show(child, true);
return Vue.nextTick();
});
it("does not show previously visible layers on load",() => {
let layersMenuSwitcher = element.querySelectorAll('button.c-button--menu.icon-layers');
expect(layersMenuSwitcher.length).toBe(1);
expect(layersMenuSwitcher[0].title).toBe('Layers menu');
let event = createMouseEvent('click');
layersMenuSwitcher[0].dispatchEvent(event);
return Vue.nextTick().then(() => {
let layersMenu = element.querySelectorAll('button.c-button--menu.icon-layers + .c-switcher-menu__content');
expect(layersMenu.length).toBe(1);
let layers = element.querySelectorAll('.js-checkbox-menu input[checked]');
expect(layers.length).toBe(0);
});
});
it("does not save visible layers",() => {
let layersMenuSwitcher = element.querySelectorAll('button.c-button--menu.icon-layers');
expect(layersMenuSwitcher.length).toBe(1);
expect(layersMenuSwitcher[0].title).toBe('Layers menu');
let event = createMouseEvent('click');
layersMenuSwitcher[0].dispatchEvent(event);
return Vue.nextTick().then(() => {
let layersMenu = element.querySelectorAll('button.c-button--menu.icon-layers + .c-switcher-menu__content');
expect(layersMenu.length).toBe(1);
let checkedLayers = element.querySelectorAll('.js-checkbox-menu input[checked]');
expect(checkedLayers.length).toBe(0);
let uncheckedLayers = element.querySelectorAll('.js-checkbox-menu input:not([checked])');
expect(uncheckedLayers.length).toBe(2);
uncheckedLayers[0].dispatchEvent(event);
return Vue.nextTick().then(() => {
imageryView.destroy();
return Vue.nextTick().then(() => {
expect(testImageryObject.configuration).toBeUndefined();
});
});
});
});
});
});

View File

@@ -41,7 +41,7 @@ define([], function () {
this.timeFormat = 'local-format';
this.durationFormat = 'duration';
this.isUTCBased = true;
this.isUTCBased = false;
}
return LocalTimeSystem;

View File

@@ -1,82 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import uuid from 'uuid';
export default class NewFolderAction {
constructor(openmct) {
this.name = 'Add New Folder';
this.key = 'newFolder';
this.description = 'Create a new folder';
this.cssClass = 'icon-folder-new';
this._openmct = openmct;
this._dialogForm = {
name: "Add New Folder",
sections: [
{
rows: [
{
key: "name",
control: "textfield",
name: "Folder Name",
pattern: "\\S+",
required: true,
cssClass: "l-input-lg"
}
]
}
]
};
}
invoke(objectPath) {
let domainObject = objectPath[0],
parentKeystring = this._openmct.objects.makeKeyString(domainObject.identifier),
composition = this._openmct.composition.get(domainObject),
dialogService = this._openmct.$injector.get('dialogService'),
folderType = this._openmct.types.get('folder');
dialogService.getUserInput(this._dialogForm, {name: 'Unnamed Folder'}).then((userInput) => {
let name = userInput.name,
identifier = {
key: uuid(),
namespace: domainObject.identifier.namespace
},
objectModel = {
identifier,
type: 'folder',
location: parentKeystring
};
folderType.definition.initialize(objectModel);
objectModel.name = name || 'New Folder';
this._openmct.objects.mutate(objectModel, 'created', Date.now());
composition.add(objectModel);
});
}
appliesTo(objectPath) {
let domainObject = objectPath[0];
return domainObject.type === 'folder';
}
}

View File

@@ -1,28 +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.
*****************************************************************************/
import NewFolderAction from './newFolderAction';
export default function () {
return function (openmct) {
openmct.contextMenu.registerAction(new NewFolderAction(openmct));
};
}

View File

@@ -1,99 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
describe("the plugin", () => {
let openmct,
compositionAPI,
newFolderAction,
mockObjectPath,
mockDialogService,
mockComposition,
mockPromise,
newFolderName = 'New Folder';
beforeEach((done) => {
openmct = createOpenMct();
openmct.on('start', done);
openmct.startHeadless();
newFolderAction = openmct.contextMenu._allActions.filter(action => {
return action.key === 'newFolder';
})[0];
});
afterEach(() => {
resetApplicationState(openmct);
});
it('installs the new folder action', () => {
expect(newFolderAction).toBeDefined();
});
describe('when invoked', () => {
beforeEach((done) => {
compositionAPI = openmct.composition;
mockObjectPath = [{
name: 'mock folder',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
}];
mockPromise = {
then: (callback) => {
callback({name: newFolderName});
done();
}
};
mockDialogService = jasmine.createSpyObj('dialogService', ['getUserInput']);
mockComposition = jasmine.createSpyObj('composition', ['add']);
mockDialogService.getUserInput.and.returnValue(mockPromise);
spyOn(openmct.$injector, 'get').and.returnValue(mockDialogService);
spyOn(compositionAPI, 'get').and.returnValue(mockComposition);
spyOn(openmct.objects, 'mutate');
newFolderAction.invoke(mockObjectPath);
});
it('gets user input for folder name', () => {
expect(mockDialogService.getUserInput).toHaveBeenCalled();
});
it('creates a new folder object', () => {
expect(openmct.objects.mutate).toHaveBeenCalled();
});
it('adds new folder object to parent composition', () => {
expect(mockComposition.add).toHaveBeenCalled();
});
});
});

View File

@@ -60,7 +60,6 @@ export default {
},
mounted() {
this.addPopupMenuItems();
this.exportImageService = this.openmct.$injector.get('exportImageService');
},
methods: {
addPopupMenuItems() {
@@ -206,7 +205,7 @@ export default {
},
openSnapshot() {
const self = this;
this.snapshot = new Vue({
const snapshot = new Vue({
data: () => {
return {
embed: self.embed
@@ -214,15 +213,14 @@ export default {
},
methods: {
formatTime: self.formatTime,
annotateSnapshot: self.annotateSnapshot,
exportImage: self.exportImage
annotateSnapshot: self.annotateSnapshot
},
template: SnapshotTemplate
});
const snapshotOverlay = this.openmct.overlays.overlay({
element: this.snapshot.$mount().$el,
onDestroy: () => { this.snapshot.$destroy(true) },
element: snapshot.$mount().$el,
onDestroy: () => { snapshot.$destroy(true) },
size: 'large',
dismissable: true,
buttons: [
@@ -236,15 +234,6 @@ export default {
]
});
},
exportImage(type) {
let element = this.snapshot.$refs['snapshot-image'];
if (type === 'png') {
this.exportImageService.exportPNG(element, this.embed.name);
} else {
this.exportImageService.exportJPG(element, this.embed.name);
}
},
previewEmbed() {
const self = this;
const previewAction = new PreviewAction(self.openmct);

View File

@@ -15,32 +15,14 @@
<div class="l-browse-bar__snapshot-datetime">
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div>
<span class="c-button-set c-button-set--strip-h">
<button
class="c-button icon-download"
title="Export This View's Data as PNG"
@click="exportImage('png')"
>
<span class="c-button__label">PNG</span>
</button>
<button
class="c-button"
title="Export This View's Data as JPG"
@click="exportImage('jpg')"
>
<span class="c-button__label">JPG</span>
</button>
</span>
<a class="l-browse-bar__annotate-button c-button icon-pencil" title="Annotate" @click="annotateSnapshot">
<span class="title-label">Annotate</span>
</a>
</div>
</div>
<div
ref="snapshot-image"
class="c-notebook-snapshot__image"
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
<div class="c-notebook-snapshot__image"
:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"
>
</div>
</div>

View File

@@ -23,9 +23,8 @@
import NotificationIndicatorPlugin from './plugin.js';
import Vue from 'vue';
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
createOpenMct
} from 'testUtils';
describe('the plugin', () => {
let notificationIndicatorPlugin,
@@ -35,10 +34,6 @@ describe('the plugin', () => {
parentElement,
mockMessages = ['error', 'test', 'notifications'];
beforeAll(() => {
resetApplicationState();
});
beforeEach((done) => {
openmct = createOpenMct();
@@ -60,10 +55,6 @@ describe('the plugin', () => {
openmct.startHeadless();
});
afterEach(() => {
resetApplicationState(openmct);
});
describe('the indicator plugin element', () => {
beforeEach(() => {
parentElement.append(indicatorElement);

View File

@@ -44,16 +44,19 @@
</li>
<li class="grid-row">
<div class="grid-cell label"
title="The line style and rendering method to join lines for this series.">Line Method and Style</div>
<div class="grid-cell value">
{{ series.lineOptionsDisplayText() }}
title="The line rendering style for this series.">Line Style</div>
<div class="grid-cell value">{{ {
'none': 'None',
'linear': 'Linear interpolation',
'stepAfter': 'Step After'
}[series.get('interpolate')] }}
</div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Whether markers are displayed, and their size.">Markers</div>
<div class="grid-cell value">
{{ series.markerOptionsDisplayText() }}
{{series.get('markers') ? "Enabled: " + series.get('markerSize') + "px" : "Disabled"}}
</div>
</li>
<li class="grid-row">

View File

@@ -52,7 +52,7 @@
</li>
<li class="grid-row">
<div class="grid-cell label"
title="The rendering method to join lines for this series.">Line Method</div>
title="The line rendering style for this series.">Line Style</div>
<div class="grid-cell value">
<select ng-model="form.interpolate">
<option value="none">None</option>
@@ -61,45 +61,15 @@
</select>
</div>
</li>
<li class="grid-row" ng-show="form.interpolate !== 'none'">
<div class="grid-cell label"
title="The line style for this series.">Line Style</div>
<div class="grid-cell value">
<select ng-model="form.lineStyle">
<option
ng-repeat="option in lineStyleOptions"
value="{{ option.value }}"
ng-selected="option.value === form.lineStyle"
>
{{ option.name }}
</option>
</select>
</div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Whether markers are displayed.">Markers</div>
<div class="grid-cell value">
<input type="checkbox" ng-model="form.markers"/>
<select
ng-show="form.markers"
ng-model="form.markerShape">
<option
ng-repeat="option in markerShapeOptions"
value="{{ option.value }}"
ng-selected="option.value == form.markerShape"
>
{{ option.name }}
</option>
</select>
</div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.markers"/></div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Display markers visually denoting points in alarm.">Alarm Markers</div>
<div class="grid-cell value">
<input type="checkbox" ng-model="form.alarmMarkers"/>
</div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.alarmMarkers"/></div>
</li>
<li class="grid-row" ng-show="form.markers || form.alarmMarkers">
<div class="grid-cell label"

View File

@@ -19,12 +19,12 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-if="!domainObject.model.locked && domainObject.getCapability('editor').inEditContext()">
<div ng-if="domainObject.getCapability('editor').inEditContext()">
<mct-representation key="'plot-options-edit'"
mct-object="domainObject">
</mct-representation>
</div>
<div ng-if="domainObject.model.locked || !domainObject.getCapability('editor').inEditContext()">
<div ng-if="!domainObject.getCapability('editor').inEditContext()">
<mct-representation key="'plot-options-browse'"
mct-object="domainObject">
</mct-representation>

View File

@@ -30,7 +30,8 @@ define([
'./MCTChartPointSet',
'./MCTChartAlarmPointSet',
'../draw/DrawLoader',
'../lib/eventHelpers'
'../lib/eventHelpers',
'lodash'
],
function (
MCTChartLineLinear,
@@ -38,7 +39,8 @@ function (
MCTChartPointSet,
MCTChartAlarmPointSet,
DrawLoader,
eventHelpers
eventHelpers,
_
) {
var MARKER_SIZE = 6.0,
@@ -371,8 +373,7 @@ function (
chartElement.getBuffer(),
chartElement.color().asRGBAArray(),
chartElement.count,
chartElement.series.get('markerSize'),
chartElement.series.get('markerShape')
chartElement.series.get('markerSize')
);
};
@@ -380,8 +381,7 @@ function (
this.drawAPI.drawLine(
chartElement.getBuffer(),
chartElement.color().asRGBAArray(),
chartElement.count,
chartElement.series.get('lineStyle')
chartElement.count
);
};
@@ -397,10 +397,9 @@ function (
this.offset.yVal(highlight.point, highlight.series)
]),
color = highlight.series.get('color').asRGBAArray(),
pointCount = 1,
shape = highlight.series.get('markerShape');
pointCount = 1;
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE);
};
MCTChartController.prototype.drawRectangles = function () {

View File

@@ -22,11 +22,13 @@
/*global define*/
define([
'lodash',
'EventEmitter',
'./Model',
'../lib/extend',
'../lib/eventHelpers'
], function (
_,
EventEmitter,
Model,
extend,

View File

@@ -25,16 +25,12 @@ define([
'lodash',
'../configuration/Model',
'../lib/extend',
'EventEmitter',
'../draw/LineStyles',
'../draw/MarkerShapes'
'EventEmitter'
], function (
_,
Model,
extend,
EventEmitter,
LINE_STYLES,
MARKER_SHAPES
EventEmitter
) {
/**
@@ -59,9 +55,7 @@ define([
* `interpolate`: interpolate method, either `undefined` (no interpolation),
* `linear` (points are connected via straight lines), or
* `stepAfter` (points are connected by steps).
* `lineStyle`: string, style of line.
* `markers`: boolean, whether or not this series should render with markers.
* `markerShape`: string, shape of markers.
* `markerSize`: number, size in pixels of markers for this series.
* `alarmMarkers`: whether or not to display alarm markers for this series.
* `stats`: An object that tracks the min and max y values observed in this
@@ -107,10 +101,8 @@ define([
xKey: options.collection.plot.xAxis.get('key'),
yKey: range.key,
markers: true,
markerShape: 'point',
markerSize: 2.0,
alarmMarkers: true,
lineStyle: 'solid'
alarmMarkers: true
};
},
@@ -418,36 +410,6 @@ define([
} else {
this.filters = deepCopiedFilters;
}
},
lineOptionsDisplayText: function () {
const lineMethods = {
'none': 'None',
'linear': 'Linear interpolation',
'stepAfter': 'Step After'
}
const lineMethodKey = this.get('interpolate');
const lineMethod = lineMethods[lineMethodKey];
if (lineMethod === 'None') {
return lineMethod;
}
const lineStyleKey = this.get('lineStyle');
const lineStyle = LINE_STYLES[lineStyleKey].label;
return `${lineMethod}: ${lineStyle}`;
},
markerOptionsDisplayText: function () {
const showMarkers = this.get('markers');
if (!showMarkers) {
return "Disabled";
}
const markerShapeKey = this.get('markerShape');
const markerShape = MARKER_SHAPES[markerShapeKey].label;
const markerSize = this.get('markerSize');
return `${markerShape}: ${markerSize}px`;
}
});

View File

@@ -28,10 +28,8 @@ define([
}
ConfigStore.prototype.deleteStore = function (id) {
if (this.store[id]) {
this.store[id].destroy();
delete this.store[id];
}
this.store[id].destroy();
delete this.store[id];
};
ConfigStore.prototype.add = function (id, config) {

View File

@@ -22,15 +22,13 @@
define([
'lodash',
'EventEmitter',
'../lib/eventHelpers',
'./LineStyles',
'./MarkerShapes'
'../lib/eventHelpers'
], function (
_,
EventEmitter,
eventHelpers,
LINE_STYLES,
MARKER_SHAPES
eventHelpers
) {
/**
@@ -88,8 +86,9 @@ define([
this.origin = newOrigin;
};
Draw2D.prototype.drawLine = function (buf, color, points, style) {
const pattern = LINE_STYLES[style].pattern;
Draw2D.prototype.drawLine = function (buf, color, points) {
var i;
this.setColor(color);
// Configure context to draw two-pixel-thick lines
@@ -98,12 +97,11 @@ define([
// Start a new path...
if (buf.length > 1) {
this.c2d.beginPath();
this.c2d.setLineDash(pattern);
this.c2d.moveTo(this.x(buf[0]), this.y(buf[1]));
}
// ...and add points to it...
for (let i = 2; i < points * 2; i = i + 2) {
for (i = 2; i < points * 2; i = i + 2) {
this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1]));
}
@@ -125,17 +123,18 @@ define([
buf,
color,
points,
pointSize,
shape
pointSize
) {
const drawC2DShape = MARKER_SHAPES[shape].drawC2D.bind(this);
var i = 0,
offset = pointSize / 2;
this.setColor(color);
for (let i = 0; i < points; i++) {
drawC2DShape(
this.x(buf[i * 2]),
this.y(buf[i * 2 + 1]),
for (; i < points; i++) {
this.c2d.fillRect(
this.x(buf[i * 2]) - offset,
this.y(buf[i * 2 + 1]) - offset,
pointSize,
pointSize
);
}

View File

@@ -22,59 +22,33 @@
define([
'lodash',
'EventEmitter',
'../lib/eventHelpers',
'./LineStyles',
'./MarkerShapes'
'../lib/eventHelpers'
], function (
_,
EventEmitter,
eventHelpers,
LINE_STYLES,
MARKER_SHAPES
eventHelpers
) {
// WebGL shader sources (for drawing plain colors)
const FRAGMENT_SHADER = `
precision mediump float;
uniform vec4 uColor;
uniform int uLineStyle;
uniform int uMarkerShape;
uniform sampler2D uPattern;
uniform float uPointCount;
varying float vLengthSoFar;
void main(void) {
if (uMarkerShape == 2) {
float distance = length(2.0 * gl_PointCoord - 1.0);
if (distance > 1.0) {
discard;
}
}
gl_FragColor = uColor;
if (uLineStyle == 2) {
gl_FragColor = texture2D(
uPattern,
vec2(fract(vLengthSoFar * 10.0))
);
}
}
`;
const VERTEX_SHADER = `
attribute vec2 aVertexPosition;
attribute float aLengthSoFar;
uniform vec2 uDimensions;
uniform vec2 uOrigin;
uniform float uPointSize;
varying float vLengthSoFar;
void main(void) {
gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);
gl_PointSize = uPointSize;
vLengthSoFar = aLengthSoFar;
}
`;
var FRAGMENT_SHADER = [
"precision mediump float;",
"uniform vec4 uColor;",
"void main(void) {",
"gl_FragColor = uColor;",
"}"
].join('\n'),
VERTEX_SHADER = [
"attribute vec2 aVertexPosition;",
"uniform vec2 uDimensions;",
"uniform vec2 uOrigin;",
"uniform float uPointSize;",
"void main(void) {",
"gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);",
"gl_PointSize = uPointSize;",
"}"
].join('\n');
/**
* Create a draw api utilizing WebGL.
@@ -118,7 +92,6 @@ define([
this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(this.vertexShader, VERTEX_SHADER);
this.gl.compileShader(this.vertexShader);
this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.shaderSource(this.fragmentShader, FRAGMENT_SHADER);
this.gl.compileShader(this.fragmentShader);
@@ -133,21 +106,19 @@ define([
// Get locations for attribs/uniforms from the
// shader programs (to pass values into shaders at draw-time)
this.aVertexPosition = this.gl.getAttribLocation(this.program, "aVertexPosition");
this.aLengthSoFar = this.gl.getAttribLocation(this.program, "aLengthSoFar");
this.uColor = this.gl.getUniformLocation(this.program, "uColor");
this.uLineStyle = this.gl.getUniformLocation(this.program, "uLineStyle");
this.uMarkerShape = this.gl.getUniformLocation(this.program, "uMarkerShape");
this.uDimensions = this.gl.getUniformLocation(this.program, "uDimensions");
this.uOrigin = this.gl.getUniformLocation(this.program, "uOrigin");
this.uPointSize = this.gl.getUniformLocation(this.program, "uPointSize");
this.uPointCount = this.gl.getUniformLocation(this.program, "uPointCount");
this.gl.enableVertexAttribArray(this.aVertexPosition);
this.gl.enableVertexAttribArray(this.aLengthSoFar);
// Create a buffer to holds points which will be drawn
this.buffer = this.gl.createBuffer();
// Use a line width of 2.0 for legibility
this.gl.lineWidth(2.0);
// Enable blending, for smoothness
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
@@ -169,62 +140,14 @@ define([
((v - this.origin[1]) / this.dimensions[1]) * this.height;
};
DrawWebGL.prototype.doDraw = function (drawType, buf, color, points, shape) {
DrawWebGL.prototype.doDraw = function (drawType, buf, color, points) {
if (this.isContextLost) {
return;
}
const lineStyle = LINE_STYLES[shape] ? LINE_STYLES[shape].drawWebGL : 0;
const shapeCode = MARKER_SHAPES[shape] ? MARKER_SHAPES[shape].drawWebGL : 0;
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW);
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
this.gl.uniform4fv(this.uColor, color);
this.gl.uniform1i(this.uLineStyle, lineStyle);
this.gl.uniform1i(this.uMarkerShape, shapeCode);
this.gl.uniform1f(this.uPointCount, points);
const lengthSoFar = [0];
for (let i = 1; i < points; i++) {
const lastX = (i - 1) * 2;
const currentX = i * 2;
const xDelta = this.x(buf[lastX]) - this.x(buf[currentX]);
const yDelta = this.y(buf[lastX + 1]) - this.y(buf[currentX + 1]);
const distance = Math.sqrt(
Math.pow(xDelta, 2)
+ Math.pow(yDelta, 2)
);
lengthSoFar.push(lengthSoFar[i - 1] + distance);
}
let lineBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, lineBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(lengthSoFar), this.gl.DYNAMIC_DRAW);
this.gl.vertexAttribPointer(this.aLengthSoFar, 1, this.gl.FLOAT, false, 0, 0);
const mappedColor = color.map(function (c, i) {
return Math.floor(c * 255);
});
const dots = [
...mappedColor,
0,0,0,0,
0,0,0,0,
0,0,0,0,
];
var texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
this.gl.texImage2D(
this.gl.TEXTURE_2D, 0, this.gl.RGBA, dots.length / 4, 1, 0,
this.gl.RGBA, this.gl.UNSIGNED_BYTE, new Uint8Array(dots));
this.gl.texParameteri(
this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
this.gl.texParameteri(
this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
this.gl.drawArrays(drawType, 0, points);
};
@@ -277,26 +200,24 @@ define([
* the line, as an RGBA color where each element
* is in the range of 0.0-1.0
* @param {number} points the number of points to draw
* @param {string} style the line style
*/
DrawWebGL.prototype.drawLine = function (buf, color, points, style) {
DrawWebGL.prototype.drawLine = function (buf, color, points) {
if (this.isContextLost) {
return;
}
this.doDraw(this.gl.LINE_STRIP, buf, color, points, style);
this.doDraw(this.gl.LINE_STRIP, buf, color, points);
};
/**
* Draw the buffer as points.
*
*/
DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize, shape) {
DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize) {
if (this.isContextLost) {
return;
}
this.gl.uniform1f(this.uPointSize, pointSize);
this.doDraw(this.gl.POINTS, buf, color, points, shape);
this.doDraw(this.gl.POINTS, buf, color, points);
};
/**

View File

@@ -1,53 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* @label string (required) display name of shape
* @drawWebGL integer (unique, required) index provided to WebGL Fragment Shader
* @pattern array
*/
const LINE_STYLES = {
solid: {
label: 'Solid',
drawWebGL: 1,
drawC2D: []
},
dot: {
label: 'Dot',
drawWebGL: 2,
drawC2D: [2, 2]
}
// dash: {
// label: 'Dash',
// drawWebGL: 3,
// drawC2D: [5, 2]
// }
// dotDashDot: {
// label: 'Dot Dash Dot',
// drawWebGL: 4,
// drawC2D: [5, 2, 2]
// }
};
return LINE_STYLES;
});

View File

@@ -1,53 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* @label string (required) display name of shape
* @drawWebGL integer (unique, required) index provided to WebGL Fragment Shader
* @drawC2D function (required) canvas2d draw function
*/
const MARKER_SHAPES = {
point: {
label: 'Point',
drawWebGL: 1,
drawC2D: function (x, y, size) {
const offset = size / 2;
this.c2d.fillRect(x - offset, y - offset, size, size);
}
},
circle: {
label: 'Circle',
drawWebGL: 2,
drawC2D: function (x, y, size) {
const radius = size / 2;
this.c2d.beginPath();
this.c2d.arc(x, y, radius, 0, 2 * Math.PI, false);
this.c2d.closePath();
this.c2d.fill();
}
}
};
return MARKER_SHAPES;
});

View File

@@ -23,11 +23,13 @@
define([
'../configuration/configStore',
'../lib/eventHelpers',
'objectUtils'
'objectUtils',
'lodash'
], function (
configStore,
eventHelpers,
objectUtils
objectUtils,
_
) {
function PlotOptionsController($scope, openmct, $timeout) {

View File

@@ -22,13 +22,9 @@
define([
'./PlotModelFormController',
'../draw/LineStyles',
'../draw/MarkerShapes',
'lodash'
], function (
PlotModelFormController,
LINE_STYLES,
MARKER_SHAPES,
_
) {
@@ -97,20 +93,6 @@ define([
value: o.key
};
});
this.$scope.lineStyleOptions = Object.entries(LINE_STYLES)
.map(([key, obj]) => {
return {
name: obj.label,
value: key
};
});
this.$scope.markerShapeOptions = Object.entries(MARKER_SHAPES)
.map(([key, obj]) => {
return {
name: obj.label,
value: key
};
});
},
fields: [
@@ -122,18 +104,10 @@ define([
modelProp: 'interpolate',
objectPath: dynamicPathForKey('interpolate')
},
{
modelProp: 'lineStyle',
objectPath: dynamicPathForKey('lineStyle')
},
{
modelProp: 'markers',
objectPath: dynamicPathForKey('markers')
},
{
modelProp: 'markerShape',
objectPath: dynamicPathForKey('markerShape')
},
{
modelProp: 'markerSize',
coerce: Number,

View File

@@ -21,9 +21,11 @@
*****************************************************************************/
define([
'./PlotModelFormController'
'./PlotModelFormController',
'lodash'
], function (
PlotModelFormController
PlotModelFormController,
_
) {
var PlotYAxisFormController = PlotModelFormController.extend({

View File

@@ -20,7 +20,12 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
define([
'lodash'
], function (
_
) {
function StackedPlotController($scope, openmct, objectService, $element, exportImageService) {
var tickWidth = 0,
composition,
@@ -120,13 +125,12 @@ define([], function () {
$scope.$watch('domainObject.getModel().composition', onCompositionChange);
$scope.$on('plot:tickWidth', function ($e, width) {
const plotId = $e.targetScope.domainObject.getId();
var plotId = $e.targetScope.domainObject.getId();
if (!tickWidthMap.hasOwnProperty(plotId)) {
return;
}
tickWidthMap[plotId] = Math.max(width, tickWidthMap[plotId]);
const newTickWidth = Math.max(...Object.values(tickWidthMap));
var newTickWidth = _.max(tickWidthMap);
if (newTickWidth !== tickWidth || width !== tickWidth) {
tickWidth = newTickWidth;
$scope.$broadcast('plot:tickWidth', tickWidth);

View File

@@ -52,9 +52,7 @@ define([
'./themes/espresso',
'./themes/maelstrom',
'./themes/snow',
'./URLTimeSettingsSynchronizer/plugin',
'./notificationIndicator/plugin',
'./newFolderAction/plugin'
'./notificationIndicator/plugin'
], function (
_,
UTCTimeSystem,
@@ -87,9 +85,7 @@ define([
Espresso,
Maelstrom,
Snow,
URLTimeSettingsSynchronizer,
NotificationIndicator,
NewFolderAction
NotificationIndicator
) {
var bundleMap = {
LocalStorage: 'platform/persistence/local',
@@ -187,7 +183,7 @@ define([
plugins.FolderView = FolderView;
plugins.Tabs = Tabs;
plugins.FlexibleLayout = FlexibleLayout;
plugins.LADTable = LADTable.default;
plugins.LADTable = LADTable;
plugins.Filters = Filters;
plugins.ObjectMigration = ObjectMigration.default;
plugins.GoToOriginalAction = GoToOriginalAction.default;
@@ -198,9 +194,7 @@ define([
plugins.Snow = Snow.default;
plugins.Condition = ConditionPlugin.default;
plugins.ConditionWidget = ConditionWidgetPlugin.default;
plugins.URLTimeSettingsSynchronizer = URLTimeSettingsSynchronizer.default;
plugins.NotificationIndicator = NotificationIndicator.default;
plugins.NewFolderAction = NewFolderAction.default;
return plugins;
});

View File

@@ -101,12 +101,6 @@ export default class RemoveAction {
appliesTo(objectPath) {
let parent = objectPath[1];
let parentType = parent && this.openmct.types.get(parent.type);
let child = objectPath[0];
let locked = child.locked ? child.locked : parent && parent.locked;
if (locked) {
return false;
}
return parentType &&
parentType.definition.creatable &&

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