mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 10:44:21 +00:00
Compare commits
14 Commits
tc-size-li
...
troublesho
Author | SHA1 | Date | |
---|---|---|---|
98264f35c6 | |||
f42f291790 | |||
176e8167f0 | |||
87d58904b4 | |||
ae629a6c8b | |||
3f575f0ec0 | |||
b3ab56cb57 | |||
f6934a43c9 | |||
22a7537974 | |||
3620760991 | |||
88a94c80be | |||
2fc0d34b8f | |||
d53ca3ec9a | |||
86e5d10fc1 |
@ -2,7 +2,7 @@ version: 2.1
|
||||
executors:
|
||||
pw-focal-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:focal
|
||||
- image: mcr.microsoft.com/playwright:v1.17.2-focal
|
||||
environment:
|
||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||
parameters:
|
||||
@ -76,14 +76,14 @@ jobs:
|
||||
node-version: <<parameters.node-version>>
|
||||
- run: npm audit --audit-level=low
|
||||
- generate_and_store_version_and_filesystem_artifacts
|
||||
node10-lint:
|
||||
node14-lint:
|
||||
parameters:
|
||||
node-version:
|
||||
type: string
|
||||
executor: pw-focal-development
|
||||
steps:
|
||||
- checkout
|
||||
- node/install:
|
||||
install-npm: false #Cannot install latest npm version with node10.
|
||||
node-version: lts/dubnium
|
||||
- run: npm install
|
||||
- build_and_install:
|
||||
node-version: <<parameters.node-version>>
|
||||
- run: npm run lint
|
||||
- generate_and_store_version_and_filesystem_artifacts
|
||||
unit-test:
|
||||
@ -141,7 +141,8 @@ jobs:
|
||||
workflows:
|
||||
overall-circleci-commit-status: #These jobs run on every commit
|
||||
jobs:
|
||||
- node10-lint
|
||||
- node14-lint:
|
||||
node-version: lts/fermium
|
||||
- unit-test:
|
||||
name: node12-chrome
|
||||
node-version: lts/erbium
|
||||
@ -153,15 +154,11 @@ workflows:
|
||||
post-steps:
|
||||
- upload_code_covio
|
||||
- e2e-test:
|
||||
name: e2e-smoke
|
||||
name: e2e-ci
|
||||
node-version: lts/fermium
|
||||
suite: ci
|
||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||
jobs:
|
||||
- unit-test:
|
||||
name: node10-chrome-nightly
|
||||
node-version: lts/dubnium
|
||||
browser: ChromeHeadless
|
||||
- unit-test:
|
||||
name: node12-firefoxESR-nightly
|
||||
node-version: lts/erbium
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0) [](https://lgtm.com/projects/g/nasa/openmct/context:javascript) [](https://codecov.io/gh/nasa/openmct) [](https://percy.io/b2e34b17/openmct)
|
||||
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0) [](https://lgtm.com/projects/g/nasa/openmct/context:javascript) [](https://codecov.io/gh/nasa/openmct) [](https://percy.io/b2e34b17/openmct) [](https://www.npmjs.com/package/openmct)
|
||||
|
||||
Open MCT (Open Mission Control Technologies) is a next-generation mission control framework for visualization of data on desktop and mobile devices. It is developed at NASA's Ames Research Center, and is being used by NASA for data analysis of spacecraft missions, as well as planning and operation of experimental rover systems. As a generalizable and open source framework, Open MCT could be used as the basis for building applications for planning, operation, and analysis of any systems producing telemetry data.
|
||||
|
||||
|
@ -52,6 +52,8 @@
|
||||
- [The URL Status Indicator](#the-url-status-indicator)
|
||||
- [Creating a Simple Indicator](#creating-a-simple-indicator)
|
||||
- [Custom Indicators](#custom-indicators)
|
||||
- [Priority API](#priority-api)
|
||||
- [Priority Types](#priority-types)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
@ -247,16 +249,24 @@ To do so, use the `addRoot` method of the object API.
|
||||
eg.
|
||||
```javascript
|
||||
openmct.objects.addRoot({
|
||||
namespace: "example.namespace",
|
||||
key: "my-key"
|
||||
});
|
||||
namespace: "example.namespace",
|
||||
key: "my-key"
|
||||
},
|
||||
openmct.priority.HIGH);
|
||||
```
|
||||
|
||||
The `addRoot` function takes a single [object identifier](#domain-objects-and-identifiers)
|
||||
as an argument.
|
||||
The `addRoot` function takes a two arguments, the first can be an [object identifier](#domain-objects-and-identifiers) for a root level object, or an array of identifiers for root
|
||||
level objects, or a function that returns a promise for an identifier or an array of root level objects, the second is a [priority](#priority-api) or numeric value.
|
||||
|
||||
Root objects are loaded just like any other objects, i.e. via an object
|
||||
provider.
|
||||
When using the `getAll` method of the object API, they will be returned in order of priority.
|
||||
|
||||
eg.
|
||||
```javascript
|
||||
openmct.objects.addRoot(identifier, openmct.priority.LOW); // low = -1000, will appear last in composition or tree
|
||||
openmct.objects.addRoot(otherIdentifier, openmct.priority.HIGH); // high = 1000, will appear first in composition or tree
|
||||
```
|
||||
|
||||
Root objects are loaded just like any other objects, i.e. via an object provider.
|
||||
|
||||
## Object Providers
|
||||
|
||||
@ -1051,3 +1061,25 @@ A completely custom indicator can be added by simply providing a DOM element to
|
||||
element: domNode
|
||||
});
|
||||
```
|
||||
|
||||
## Priority API
|
||||
|
||||
Open MCT provides some built-in priority values that can be used in the application for view providers, indicators, root object order, and more.
|
||||
|
||||
### Priority Types
|
||||
|
||||
Currently, the Open MCT Priority API provides (type: numeric value):
|
||||
- HIGH: 1000
|
||||
- Default: 0
|
||||
- LOW: -1000
|
||||
|
||||
View provider Example:
|
||||
|
||||
``` javascript
|
||||
class ViewProvider {
|
||||
...
|
||||
priority() {
|
||||
return openmct.priority.HIGH;
|
||||
}
|
||||
}
|
||||
```
|
1
docs/src/process/testing/automation.md
Normal file
1
docs/src/process/testing/automation.md
Normal file
@ -0,0 +1 @@
|
||||
## doc dump
|
@ -28,9 +28,11 @@ components. Tests are subject to code review along with the actual
|
||||
implementation, to ensure that tests are applicable and useful.
|
||||
|
||||
Unit tests should meet
|
||||
[test standards](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#test-standards)
|
||||
[test standards](CONTRIBUTING.md#test-standards)
|
||||
as described in the contributing guide.
|
||||
|
||||
More about unit testing
|
||||
|
||||
### User Testing
|
||||
|
||||
User testing is performed at scheduled times involving target users
|
||||
@ -93,7 +95,7 @@ Before changes are merged, the author of the changes must perform:
|
||||
|
||||
Changes are not merged until the author has affirmed that both
|
||||
forms of testing have been performed successfully; this is documented
|
||||
by the [Author Checklist](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#author-checklist).
|
||||
by the [Author Checklist](CONTRIBUTING.md#author-checklist).
|
||||
|
||||
### Per-sprint Testing
|
||||
|
||||
@ -135,7 +137,7 @@ For per-sprint testing, a testathon is typically performed at the beginning of t
|
||||
#### 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).
|
||||
2. For each issue, testers should review the associated PR for testing instructions. See the contributing guide for instructions on [pull requests](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.
|
||||
|
0
e2e/e2e.md
Normal file
0
e2e/e2e.md
Normal file
@ -13,6 +13,7 @@ const config = {
|
||||
timeout: 200 * 1000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
},
|
||||
workers: 2, //Limit to 2 for CircleCI Agent
|
||||
use: {
|
||||
browserName: "chromium",
|
||||
baseURL: 'http://localhost:8080/',
|
||||
|
20
package.json
20
package.json
@ -4,16 +4,16 @@
|
||||
"description": "The Open MCT core platform",
|
||||
"devDependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.2",
|
||||
"@percy/cli": "^1.0.0-beta.70",
|
||||
"@percy/cli": "^1.0.0-beta.71",
|
||||
"@percy/playwright": "^1.0.1",
|
||||
"@playwright/test": "^1.16.3",
|
||||
"@playwright/test": "^1.17.2",
|
||||
"allure-playwright": "^2.0.0-beta.14",
|
||||
"angular": ">=1.8.0",
|
||||
"angular-route": "1.4.14",
|
||||
"babel-eslint": "10.1.0",
|
||||
"comma-separated-values": "^3.6.4",
|
||||
"concurrently": "^3.6.1",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"cross-env": "^6.0.3",
|
||||
"css-loader": "^4.0.0",
|
||||
"d3-axis": "1.0.x",
|
||||
@ -36,7 +36,7 @@
|
||||
"istanbul-instrumenter-loader": "^3.0.1",
|
||||
"jasmine-core": "^4.0.0",
|
||||
"jsdoc": "^3.3.2",
|
||||
"karma": "6.3.9",
|
||||
"karma": "6.3.10",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-cli": "2.0.0",
|
||||
"karma-coverage": "2.1.0",
|
||||
@ -45,20 +45,20 @@
|
||||
"karma-jasmine": "4.0.1",
|
||||
"karma-junit-reporter": "2.0.1",
|
||||
"karma-sourcemap-loader": "0.3.8",
|
||||
"karma-spec-reporter": "0.0.32",
|
||||
"karma-spec-reporter": "0.0.33",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"location-bar": "^3.0.1",
|
||||
"lodash": "^4.17.12",
|
||||
"markdown-toc": "^0.11.7",
|
||||
"marked": "^0.3.5",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"mini-css-extract-plugin": "2.4.5",
|
||||
"minimist": "^1.2.5",
|
||||
"moment": "2.25.3",
|
||||
"moment-duration-format": "^2.2.2",
|
||||
"moment-timezone": "0.5.28",
|
||||
"node-bourbon": "^4.2.3",
|
||||
"painterro": "^1.2.56",
|
||||
"playwright": "^1.16.3",
|
||||
"playwright": "^1.17.2",
|
||||
"plotly.js-basic-dist": "^2.5.0",
|
||||
"plotly.js-gl2d-dist": "^2.5.0",
|
||||
"printj": "^1.2.1",
|
||||
@ -76,7 +76,7 @@
|
||||
"vue-eslint-parser": "8.0.1",
|
||||
"vue-loader": "15.9.8",
|
||||
"vue-template-compiler": "2.5.6",
|
||||
"webpack": "^5.53.0",
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-cli": "^4.0.0",
|
||||
"webpack-dev-middleware": "^3.1.3",
|
||||
"webpack-hot-middleware": "^2.22.3",
|
||||
@ -96,7 +96,7 @@
|
||||
"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:coverage:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
|
||||
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js smoke",
|
||||
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js smoke default condition.e2e",
|
||||
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js",
|
||||
"test:e2e:visual": "percy exec -- npx playwright test --config=e2e/playwright-visual.config.js default",
|
||||
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js",
|
||||
@ -112,7 +112,7 @@
|
||||
"url": "https://github.com/nasa/openmct.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.12.2 <16.0.0"
|
||||
"node": ">=12.0.1 <15.0.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -41,7 +41,7 @@ function ObjectAPI(typeRegistry, openmct) {
|
||||
this.typeRegistry = typeRegistry;
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.providers = {};
|
||||
this.rootRegistry = new RootRegistry();
|
||||
this.rootRegistry = new RootRegistry(openmct);
|
||||
this.inMemorySearchProvider = new InMemorySearchProvider(openmct);
|
||||
|
||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
||||
@ -367,14 +367,17 @@ ObjectAPI.prototype.endTransaction = function () {
|
||||
|
||||
/**
|
||||
* Add a root-level object.
|
||||
* @param {module:openmct.ObjectAPI~Identifier|function} an array of
|
||||
* identifiers for root level objects, or a function that returns a
|
||||
* @param {module:openmct.ObjectAPI~Identifier|array|function} identifier an identifier or
|
||||
* an array of identifiers for root level objects, or a function that returns a
|
||||
* promise for an identifier or an array of root level objects.
|
||||
* @param {module:openmct.PriorityAPI~priority|Number} priority a number representing
|
||||
* this item(s) position in the root object's composition (example: order in object tree).
|
||||
* For arrays, they are treated as blocks.
|
||||
* @method addRoot
|
||||
* @memberof module:openmct.ObjectAPI#
|
||||
*/
|
||||
ObjectAPI.prototype.addRoot = function (key) {
|
||||
this.rootRegistry.addRoot(key);
|
||||
ObjectAPI.prototype.addRoot = function (identifier, priority) {
|
||||
this.rootRegistry.addRoot(identifier, priority);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -20,39 +20,43 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'lodash'
|
||||
], function (
|
||||
_
|
||||
) {
|
||||
import utils from './object-utils';
|
||||
|
||||
function RootRegistry() {
|
||||
this.providers = [];
|
||||
export default class RootRegistry {
|
||||
|
||||
constructor(openmct) {
|
||||
this._rootItems = [];
|
||||
this._openmct = openmct;
|
||||
}
|
||||
|
||||
RootRegistry.prototype.getRoots = function () {
|
||||
const promises = this.providers.map(function (provider) {
|
||||
return provider();
|
||||
});
|
||||
getRoots() {
|
||||
const sortedItems = this._rootItems.sort((a, b) => b.priority - a.priority);
|
||||
const promises = sortedItems.map((rootItem) => rootItem.provider());
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(_.flatten);
|
||||
};
|
||||
|
||||
function isKey(key) {
|
||||
return _.isObject(key) && _.has(key, 'key') && _.has(key, 'namespace');
|
||||
return Promise.all(promises).then(rootItems => rootItems.flat());
|
||||
}
|
||||
|
||||
RootRegistry.prototype.addRoot = function (key) {
|
||||
if (isKey(key) || (Array.isArray(key) && key.every(isKey))) {
|
||||
this.providers.push(function () {
|
||||
return key;
|
||||
});
|
||||
} else if (typeof key === "function") {
|
||||
this.providers.push(key);
|
||||
addRoot(rootItem, priority) {
|
||||
|
||||
if (!this._isValid(rootItem)) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return RootRegistry;
|
||||
this._rootItems.push({
|
||||
priority: priority || this._openmct.priority.DEFAULT,
|
||||
provider: typeof rootItem === 'function' ? rootItem : () => rootItem
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
_isValid(rootItem) {
|
||||
if (utils.isIdentifier(rootItem) || typeof rootItem === 'function') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(rootItem)) {
|
||||
return rootItem.every(utils.isIdentifier);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -172,6 +172,7 @@ define([
|
||||
}
|
||||
|
||||
return {
|
||||
isIdentifier: isIdentifier,
|
||||
toOldFormat: toOldFormat,
|
||||
toNewFormat: toNewFormat,
|
||||
makeKeyString: makeKeyString,
|
||||
|
@ -19,83 +19,113 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define([
|
||||
'../RootRegistry'
|
||||
], function (
|
||||
RootRegistry
|
||||
) {
|
||||
describe('RootRegistry', function () {
|
||||
let idA;
|
||||
let idB;
|
||||
let idC;
|
||||
let registry;
|
||||
|
||||
beforeEach(function () {
|
||||
idA = {
|
||||
key: 'keyA',
|
||||
namespace: 'something'
|
||||
};
|
||||
idB = {
|
||||
key: 'keyB',
|
||||
namespace: 'something'
|
||||
};
|
||||
idC = {
|
||||
key: 'keyC',
|
||||
namespace: 'something'
|
||||
};
|
||||
registry = new RootRegistry();
|
||||
});
|
||||
import { createOpenMct, resetApplicationState } from '../../../utils/testing';
|
||||
|
||||
it('can register a root by key', function () {
|
||||
registry.addRoot(idA);
|
||||
describe('RootRegistry', () => {
|
||||
let openmct;
|
||||
let idA;
|
||||
let idB;
|
||||
let idC;
|
||||
let idD;
|
||||
|
||||
return registry.getRoots()
|
||||
.then(function (roots) {
|
||||
expect(roots).toEqual([idA]);
|
||||
});
|
||||
});
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
idA = {
|
||||
key: 'keyA',
|
||||
namespace: 'something'
|
||||
};
|
||||
idB = {
|
||||
key: 'keyB',
|
||||
namespace: 'something'
|
||||
};
|
||||
idC = {
|
||||
key: 'keyC',
|
||||
namespace: 'something'
|
||||
};
|
||||
idD = {
|
||||
key: 'keyD',
|
||||
namespace: 'something'
|
||||
};
|
||||
|
||||
it('can register multiple roots by key', function () {
|
||||
registry.addRoot([idA, idB]);
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
return registry.getRoots()
|
||||
.then(function (roots) {
|
||||
expect(roots).toEqual([idA, idB]);
|
||||
});
|
||||
});
|
||||
afterEach(async () => {
|
||||
await resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it('can register an asynchronous root ', function () {
|
||||
registry.addRoot(function () {
|
||||
return Promise.resolve(idA);
|
||||
it('can register a root by identifier', () => {
|
||||
openmct.objects.addRoot(idA);
|
||||
|
||||
return openmct.objects.getRoot()
|
||||
.then((rootObject) => {
|
||||
expect(rootObject.composition).toEqual([idA]);
|
||||
});
|
||||
});
|
||||
|
||||
return registry.getRoots()
|
||||
.then(function (roots) {
|
||||
expect(roots).toEqual([idA]);
|
||||
});
|
||||
});
|
||||
it('can register multiple roots by identifier', () => {
|
||||
openmct.objects.addRoot([idA, idB]);
|
||||
|
||||
it('can register multiple asynchronous roots', function () {
|
||||
registry.addRoot(function () {
|
||||
return Promise.resolve([idA, idB]);
|
||||
return openmct.objects.getRoot()
|
||||
.then((rootObject) => {
|
||||
expect(rootObject.composition).toEqual([idA, idB]);
|
||||
});
|
||||
});
|
||||
|
||||
return registry.getRoots()
|
||||
.then(function (roots) {
|
||||
expect(roots).toEqual([idA, idB]);
|
||||
});
|
||||
});
|
||||
it('can register an asynchronous root ', () => {
|
||||
openmct.objects.addRoot(() => Promise.resolve(idA));
|
||||
|
||||
it('can combine different types of registration', function () {
|
||||
registry.addRoot([idA, idB]);
|
||||
registry.addRoot(function () {
|
||||
return Promise.resolve([idC]);
|
||||
return openmct.objects.getRoot()
|
||||
.then((rootObject) => {
|
||||
expect(rootObject.composition).toEqual([idA]);
|
||||
});
|
||||
});
|
||||
|
||||
return registry.getRoots()
|
||||
.then(function (roots) {
|
||||
expect(roots).toEqual([idA, idB, idC]);
|
||||
});
|
||||
});
|
||||
it('can register multiple asynchronous roots', () => {
|
||||
openmct.objects.addRoot(() => Promise.resolve([idA, idB]));
|
||||
|
||||
return openmct.objects.getRoot()
|
||||
.then((rootObject) => {
|
||||
expect(rootObject.composition).toEqual([idA, idB]);
|
||||
});
|
||||
});
|
||||
|
||||
it('can combine different types of registration', () => {
|
||||
openmct.objects.addRoot([idA, idB]);
|
||||
openmct.objects.addRoot(() => Promise.resolve([idC]));
|
||||
|
||||
return openmct.objects.getRoot()
|
||||
.then((rootObject) => {
|
||||
expect(rootObject.composition).toEqual([idA, idB, idC]);
|
||||
});
|
||||
});
|
||||
|
||||
it('supports priority ordering for identifiers', () => {
|
||||
openmct.objects.addRoot(idA, openmct.priority.LOW);
|
||||
openmct.objects.addRoot(idB, openmct.priority.HIGH);
|
||||
openmct.objects.addRoot(idC); // DEFAULT
|
||||
|
||||
return openmct.objects.getRoot()
|
||||
.then((rootObject) => {
|
||||
expect(rootObject.composition[0]).toEqual(idB);
|
||||
expect(rootObject.composition[1]).toEqual(idC);
|
||||
expect(rootObject.composition[2]).toEqual(idA);
|
||||
});
|
||||
});
|
||||
|
||||
it('supports priority ordering for different types of registration', () => {
|
||||
openmct.objects.addRoot(() => Promise.resolve([idC]), openmct.priority.LOW);
|
||||
openmct.objects.addRoot(idB, openmct.priority.HIGH);
|
||||
openmct.objects.addRoot([idA, idD]); // default
|
||||
|
||||
return openmct.objects.getRoot()
|
||||
.then((rootObject) => {
|
||||
expect(rootObject.composition[0]).toEqual(idB);
|
||||
expect(rootObject.composition[1]).toEqual(idA);
|
||||
expect(rootObject.composition[2]).toEqual(idD);
|
||||
expect(rootObject.composition[3]).toEqual(idC);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -62,12 +62,14 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.refreshData = this.refreshData.bind(this);
|
||||
this.setTimeContext();
|
||||
|
||||
this.loadComposition();
|
||||
|
||||
this.openmct.time.on('bounds', this.refreshData);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.time.off('bounds', this.refreshData);
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
this.removeAllSubscriptions();
|
||||
|
||||
@ -79,6 +81,21 @@ export default {
|
||||
this.composition.off('remove', this.removeTelemetryObject);
|
||||
},
|
||||
methods: {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
||||
this.followTimeContext();
|
||||
|
||||
},
|
||||
followTimeContext() {
|
||||
this.timeContext.on('bounds', this.refreshData);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.refreshData);
|
||||
}
|
||||
},
|
||||
addTelemetryObject(telemetryObject) {
|
||||
// grab information we need from the added telmetry object
|
||||
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
@ -147,7 +164,7 @@ export default {
|
||||
};
|
||||
},
|
||||
getOptions() {
|
||||
const { start, end } = this.openmct.time.bounds();
|
||||
const { start, end } = this.timeContext.bounds();
|
||||
|
||||
return {
|
||||
end,
|
||||
@ -247,10 +264,10 @@ export default {
|
||||
this.addTrace(trace, key);
|
||||
},
|
||||
isDataInTimeRange(datum, key) {
|
||||
const timeSystemKey = this.openmct.time.timeSystem().key;
|
||||
const timeSystemKey = this.timeContext.timeSystem().key;
|
||||
let currentTimestamp = this.parse(key, timeSystemKey, datum);
|
||||
|
||||
return currentTimestamp && this.openmct.time.bounds().end >= currentTimestamp;
|
||||
return currentTimestamp && this.timeContext.bounds().end >= currentTimestamp;
|
||||
},
|
||||
format(telemetryObjectKey, metadataKey, data) {
|
||||
const formats = this.telemetryObjectFormats[telemetryObjectKey];
|
||||
|
@ -48,7 +48,7 @@ define([
|
||||
|
||||
let indicator = {
|
||||
element: component.$mount().$el,
|
||||
key: 'clear-data-indicator',
|
||||
key: 'global-clear-indicator',
|
||||
priority: openmct.priority.DEFAULT
|
||||
};
|
||||
|
||||
|
228
src/plugins/clearData/pluginSpec.js
Normal file
228
src/plugins/clearData/pluginSpec.js
Normal file
@ -0,0 +1,228 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 ClearDataPlugin from './plugin.js';
|
||||
import Vue from 'vue';
|
||||
import { createOpenMct, resetApplicationState, createMouseEvent } from 'utils/testing';
|
||||
|
||||
describe('The Clear Data Plugin:', () => {
|
||||
let clearDataPlugin;
|
||||
|
||||
describe('The clear data action:', () => {
|
||||
let openmct;
|
||||
let selection;
|
||||
let mockObjectPath;
|
||||
let clearDataAction;
|
||||
let testViewObject;
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
|
||||
clearDataPlugin = new ClearDataPlugin(
|
||||
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
|
||||
{indicator: true}
|
||||
);
|
||||
openmct.install(clearDataPlugin);
|
||||
|
||||
clearDataAction = openmct.actions.getAction('clear-data-action');
|
||||
testViewObject = [{
|
||||
identifier: {
|
||||
key: "foo-table",
|
||||
namespace: ''
|
||||
},
|
||||
type: "table"
|
||||
}];
|
||||
openmct.router.path = testViewObject;
|
||||
mockObjectPath = [
|
||||
{
|
||||
name: 'Mock Table',
|
||||
type: 'table',
|
||||
identifier: {
|
||||
key: "foo-table",
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
];
|
||||
selection = [
|
||||
{
|
||||
context: {
|
||||
item: mockObjectPath[0]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
openmct.selection.select(selection);
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.router.path = null;
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
it('is installed', () => {
|
||||
expect(clearDataAction).toBeDefined();
|
||||
});
|
||||
|
||||
it('is applicable on applicable objects', () => {
|
||||
const gatheredActions = openmct.actions.getActionsCollection(mockObjectPath);
|
||||
expect(gatheredActions.applicableActions['clear-data-action']).toBeDefined();
|
||||
});
|
||||
|
||||
it('is not applicable on inapplicable objects', () => {
|
||||
testViewObject = [{
|
||||
identifier: {
|
||||
key: "foo-widget",
|
||||
namespace: ''
|
||||
},
|
||||
type: "widget"
|
||||
}];
|
||||
mockObjectPath = [
|
||||
{
|
||||
name: 'Mock Widget',
|
||||
type: 'widget',
|
||||
identifier: {
|
||||
key: "foo-widget",
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
];
|
||||
selection = [
|
||||
{
|
||||
context: {
|
||||
item: mockObjectPath[0]
|
||||
}
|
||||
}
|
||||
];
|
||||
openmct.selection.select(selection);
|
||||
const gatheredActions = openmct.actions.getActionsCollection(mockObjectPath);
|
||||
expect(gatheredActions.applicableActions['clear-data-action']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('is not applicable if object not in the selection path and not a layout', () => {
|
||||
selection = [
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
name: 'Some Random Widget',
|
||||
type: 'not-in-path-widget',
|
||||
identifier: {
|
||||
key: "something-else-widget",
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
openmct.selection.select(selection);
|
||||
const gatheredActions = openmct.actions.getActionsCollection(mockObjectPath);
|
||||
expect(gatheredActions.applicableActions['clear-data-action']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('is applicable if object not in the selection path and is a layout', () => {
|
||||
selection = [
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
name: 'Some Random Widget',
|
||||
type: 'not-in-path-widget',
|
||||
identifier: {
|
||||
key: "something-else-widget",
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
openmct.selection.select(selection);
|
||||
|
||||
testViewObject = [{
|
||||
identifier: {
|
||||
key: "foo-layout",
|
||||
namespace: ''
|
||||
},
|
||||
type: "layout"
|
||||
}];
|
||||
openmct.router.path = testViewObject;
|
||||
const gatheredActions = openmct.actions.getActionsCollection(mockObjectPath);
|
||||
expect(gatheredActions.applicableActions['clear-data-action']).toBeDefined();
|
||||
});
|
||||
|
||||
it('fires an event upon invocation', (done) => {
|
||||
openmct.objectViews.on('clearData', (domainObject) => {
|
||||
expect(domainObject).toEqual(testViewObject[0]);
|
||||
done();
|
||||
});
|
||||
clearDataAction.invoke(testViewObject);
|
||||
});
|
||||
});
|
||||
|
||||
describe('The clear data indicator:', () => {
|
||||
let openmct;
|
||||
let appHolder;
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
|
||||
clearDataPlugin = new ClearDataPlugin(
|
||||
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
|
||||
{indicator: true}
|
||||
);
|
||||
openmct.install(clearDataPlugin);
|
||||
appHolder = document.createElement('div');
|
||||
document.body.appendChild(appHolder);
|
||||
openmct.on('start', done);
|
||||
openmct.start(appHolder);
|
||||
});
|
||||
|
||||
it('installs', () => {
|
||||
const globalClearIndicator = openmct.indicators.indicatorObjects
|
||||
.find(indicator => indicator.key === 'global-clear-indicator').element;
|
||||
expect(globalClearIndicator).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders its major elements", async () => {
|
||||
await Vue.nextTick();
|
||||
const indicatorClass = appHolder.querySelector('.c-indicator');
|
||||
const iconClass = appHolder.querySelector('.icon-clear-data');
|
||||
const indicatorLabel = appHolder.querySelector('.c-indicator__label');
|
||||
const buttonElement = indicatorLabel.querySelector('button');
|
||||
const hasMajorElements = Boolean(indicatorClass && iconClass && buttonElement);
|
||||
|
||||
expect(hasMajorElements).toBe(true);
|
||||
expect(buttonElement.innerText).toEqual('Clear Data');
|
||||
});
|
||||
|
||||
it("clicking the button fires the global clear", (done) => {
|
||||
const indicatorLabel = appHolder.querySelector('.c-indicator__label');
|
||||
const buttonElement = indicatorLabel.querySelector('button');
|
||||
const clickEvent = createMouseEvent('click');
|
||||
openmct.objectViews.on('clearData', () => {
|
||||
// when we click the button, this event should fire
|
||||
done();
|
||||
});
|
||||
buttonElement.dispatchEvent(clickEvent);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,143 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 ClearDataActionPlugin from '../plugin.js';
|
||||
import ClearDataAction from '../ClearDataAction.js';
|
||||
|
||||
describe('When the Clear Data Plugin is installed,', () => {
|
||||
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
|
||||
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
|
||||
const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
|
||||
const goodMockSelectionPath = [[{
|
||||
context: {
|
||||
item: {
|
||||
identifier: {
|
||||
key: 'apple',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}]];
|
||||
|
||||
const openmct = {
|
||||
objectViews: mockObjectViews,
|
||||
indicators: mockIndicatorProvider,
|
||||
priority: {
|
||||
DEFAULT: 0
|
||||
},
|
||||
actions: mockActionsProvider,
|
||||
install: function (plugin) {
|
||||
plugin(this);
|
||||
},
|
||||
selection: {
|
||||
get: function () {
|
||||
return goodMockSelectionPath;
|
||||
}
|
||||
},
|
||||
objects: {
|
||||
areIdsEqual: function (obj1, obj2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mockObjectPath = [
|
||||
{
|
||||
name: 'mockObject1',
|
||||
type: 'apple'
|
||||
},
|
||||
{
|
||||
name: 'mockObject2',
|
||||
type: 'banana'
|
||||
}
|
||||
];
|
||||
|
||||
it('Global Clear Indicator is installed', () => {
|
||||
openmct.install(ClearDataActionPlugin(openmct, {indicator: true}));
|
||||
|
||||
expect(mockIndicatorProvider.add).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Clear Data context menu action is installed', () => {
|
||||
openmct.install(ClearDataActionPlugin(openmct, []));
|
||||
|
||||
expect(mockActionsProvider.register).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clear data action emits a clearData event when invoked', () => {
|
||||
const action = new ClearDataAction(openmct);
|
||||
|
||||
action.invoke(mockObjectPath);
|
||||
|
||||
expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
|
||||
});
|
||||
|
||||
it('clears data on applicable objects', () => {
|
||||
let action = new ClearDataAction(openmct, ['apple']);
|
||||
|
||||
const actionApplies = action.appliesTo(mockObjectPath);
|
||||
|
||||
expect(actionApplies).toBe(true);
|
||||
});
|
||||
|
||||
it('does not clear data on inapplicable objects', () => {
|
||||
let action = new ClearDataAction(openmct, ['pineapple']);
|
||||
|
||||
const actionApplies = action.appliesTo(mockObjectPath);
|
||||
|
||||
expect(actionApplies).toBe(false);
|
||||
});
|
||||
|
||||
it('does not clear data if not in the selection path and not a layout', () => {
|
||||
openmct.objects = {
|
||||
areIdsEqual: function (obj1, obj2) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
openmct.router = {
|
||||
path: [{type: 'not-a-layout'}]
|
||||
};
|
||||
|
||||
let action = new ClearDataAction(openmct, ['apple']);
|
||||
|
||||
const actionApplies = action.appliesTo(mockObjectPath);
|
||||
|
||||
expect(actionApplies).toBe(false);
|
||||
});
|
||||
|
||||
it('does clear data if not in the selection path and is a layout', () => {
|
||||
openmct.objects = {
|
||||
areIdsEqual: function (obj1, obj2) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
openmct.router = {
|
||||
path: [{type: 'layout'}]
|
||||
};
|
||||
|
||||
let action = new ClearDataAction(openmct, ['apple']);
|
||||
|
||||
const actionApplies = action.appliesTo(mockObjectPath);
|
||||
|
||||
expect(actionApplies).toBe(true);
|
||||
});
|
||||
});
|
@ -109,7 +109,7 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
|
||||
this.initialize(styleConfiguration || {});
|
||||
this.applyStaticStyle();
|
||||
this.destroy();
|
||||
this.destroy(true);
|
||||
} else {
|
||||
let isNewConditionSet = !this.conditionSetIdentifier
|
||||
|| !this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, styleConfiguration.conditionSetIdentifier);
|
||||
@ -180,15 +180,17 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
this.updateDomainObjectStyle();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
destroy(skipEventListeners) {
|
||||
if (this.stopProvidingTelemetry) {
|
||||
|
||||
this.stopProvidingTelemetry();
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
|
||||
this.openmct.time.off("bounds", this.refreshData);
|
||||
this.openmct.editor.off('isEditing', this.toggleSubscription);
|
||||
if (!skipEventListeners) {
|
||||
this.openmct.time.off("bounds", this.refreshData);
|
||||
this.openmct.editor.off('isEditing', this.toggleSubscription);
|
||||
}
|
||||
|
||||
this.conditionSetIdentifier = undefined;
|
||||
}
|
||||
|
@ -1,475 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-inspector__styles c-inspect-styles">
|
||||
<template v-if="!conditionSetDomainObject">
|
||||
<div class="c-inspect-styles__header">
|
||||
Object Style
|
||||
</div>
|
||||
<div class="c-inspect-styles__content">
|
||||
<div v-if="staticStyle"
|
||||
class="c-inspect-styles__style"
|
||||
>
|
||||
<StyleEditor class="c-inspect-styles__editor"
|
||||
:style-item="staticStyle"
|
||||
:is-editing="isEditing"
|
||||
@persist="updateStaticStyle"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
id="addConditionSet"
|
||||
class="c-button c-button--major c-toggle-styling-button labeled"
|
||||
@click="addConditionSet"
|
||||
>
|
||||
<span class="c-cs-button__label">Use Conditional Styling...</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="c-inspect-styles__header">
|
||||
Conditional Object Styles
|
||||
</div>
|
||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||
<a v-if="conditionSetDomainObject"
|
||||
class="c-object-label icon-conditional"
|
||||
@click="navigateOrPreview"
|
||||
>
|
||||
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
||||
</a>
|
||||
<template v-if="isEditing">
|
||||
<button
|
||||
id="changeConditionSet"
|
||||
class="c-button labeled"
|
||||
@click="addConditionSet"
|
||||
>
|
||||
<span class="c-button__label">Change...</span>
|
||||
</button>
|
||||
|
||||
<button class="c-click-icon icon-x"
|
||||
title="Remove conditional styles"
|
||||
@click="removeConditionSet"
|
||||
></button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="conditionsLoaded"
|
||||
class="c-inspect-styles__conditions"
|
||||
>
|
||||
<div v-for="(conditionStyle, index) in conditionalStyles"
|
||||
:key="index"
|
||||
class="c-inspect-styles__condition"
|
||||
:class="{'is-current': conditionStyle.conditionId === selectedConditionId}"
|
||||
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
|
||||
>
|
||||
<condition-error :show-label="true"
|
||||
:condition="getCondition(conditionStyle.conditionId)"
|
||||
/>
|
||||
<condition-description :show-label="true"
|
||||
:condition="getCondition(conditionStyle.conditionId)"
|
||||
/>
|
||||
<StyleEditor class="c-inspect-styles__editor"
|
||||
:style-item="conditionStyle"
|
||||
:is-editing="isEditing"
|
||||
@persist="updateConditionalStyle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import StyleEditor from "./StyleEditor.vue";
|
||||
import SelectorDialogTree from '@/ui/components/SelectorDialogTree.vue';
|
||||
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
|
||||
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
|
||||
import Vue from 'vue';
|
||||
import PreviewAction from "@/ui/preview/PreviewAction.js";
|
||||
import { getApplicableStylesForItem } from "@/plugins/condition/utils/styleUtils";
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
export default {
|
||||
name: 'ConditionalStylesView',
|
||||
components: {
|
||||
ConditionDescription,
|
||||
ConditionError,
|
||||
StyleEditor
|
||||
},
|
||||
inject: [
|
||||
'openmct',
|
||||
'selection'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
conditionalStyles: [],
|
||||
staticStyle: undefined,
|
||||
conditionSetDomainObject: undefined,
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
conditions: undefined,
|
||||
conditionsLoaded: false,
|
||||
navigateToPath: '',
|
||||
selectedConditionId: ''
|
||||
};
|
||||
},
|
||||
destroyed() {
|
||||
this.removeListeners();
|
||||
},
|
||||
mounted() {
|
||||
this.itemId = '';
|
||||
this.getDomainObjectFromSelection();
|
||||
this.previewAction = new PreviewAction(this.openmct);
|
||||
if (this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
|
||||
let objectStyles = this.itemId ? this.domainObject.configuration.objectStyles[this.itemId] : this.domainObject.configuration.objectStyles;
|
||||
this.initializeStaticStyle(objectStyles);
|
||||
if (objectStyles && objectStyles.conditionSetIdentifier) {
|
||||
this.openmct.objects.get(objectStyles.conditionSetIdentifier).then(this.initialize);
|
||||
this.conditionalStyles = objectStyles.styles;
|
||||
}
|
||||
} else {
|
||||
this.initializeStaticStyle();
|
||||
}
|
||||
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
methods: {
|
||||
isItemType(type, item) {
|
||||
return item && (item.type === type);
|
||||
},
|
||||
getDomainObjectFromSelection() {
|
||||
let layoutItem;
|
||||
let domainObject;
|
||||
|
||||
if (this.selection[0].length > 1) {
|
||||
//If there are more than 1 items in the this.selection[0] list, the first one could either be a sub domain object OR a layout drawing control.
|
||||
//The second item in the this.selection[0] list is the container object (usually a layout)
|
||||
layoutItem = this.selection[0][0].context.layoutItem;
|
||||
const item = this.selection[0][0].context.item;
|
||||
this.canHide = true;
|
||||
if (item
|
||||
&& (!layoutItem || (this.isItemType('subobject-view', layoutItem)))) {
|
||||
domainObject = item;
|
||||
} else {
|
||||
domainObject = this.selection[0][1].context.item;
|
||||
if (layoutItem) {
|
||||
this.itemId = layoutItem.id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
domainObject = this.selection[0][0].context.item;
|
||||
}
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.initialStyles = getApplicableStylesForItem(domainObject, layoutItem);
|
||||
this.$nextTick(() => {
|
||||
this.removeListeners();
|
||||
if (this.domainObject) {
|
||||
this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
|
||||
this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
|
||||
}
|
||||
});
|
||||
},
|
||||
removeListeners() {
|
||||
if (this.stopObserving) {
|
||||
this.stopObserving();
|
||||
}
|
||||
|
||||
if (this.stopObservingItems) {
|
||||
this.stopObservingItems();
|
||||
}
|
||||
|
||||
if (this.stopProvidingTelemetry) {
|
||||
this.stopProvidingTelemetry();
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
},
|
||||
initialize(conditionSetDomainObject) {
|
||||
//If there are new conditions in the conditionSet we need to set those styles to default
|
||||
this.conditionSetDomainObject = conditionSetDomainObject;
|
||||
this.enableConditionSetNav();
|
||||
this.initializeConditionalStyles();
|
||||
},
|
||||
setEditState(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
if (this.isEditing) {
|
||||
if (this.stopProvidingTelemetry) {
|
||||
this.stopProvidingTelemetry();
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
} else {
|
||||
this.subscribeToConditionSet();
|
||||
}
|
||||
},
|
||||
addConditionSet() {
|
||||
let conditionSetDomainObject;
|
||||
let self = this;
|
||||
|
||||
function handleItemSelection({ item }) {
|
||||
if (item) {
|
||||
conditionSetDomainObject = item;
|
||||
}
|
||||
}
|
||||
|
||||
function dismissDialog(overlay, initialize) {
|
||||
overlay.dismiss();
|
||||
if (initialize && conditionSetDomainObject) {
|
||||
self.conditionSetDomainObject = conditionSetDomainObject;
|
||||
self.conditionalStyles = [];
|
||||
self.initializeConditionalStyles();
|
||||
}
|
||||
}
|
||||
|
||||
let vm = new Vue({
|
||||
components: { SelectorDialogTree },
|
||||
provide: {
|
||||
openmct: this.openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
handleItemSelection,
|
||||
title: 'Select Condition Set'
|
||||
};
|
||||
},
|
||||
template: '<selector-dialog-tree :title="title" @treeItemSelected="handleItemSelection"></selector-dialog-tree>'
|
||||
}).$mount();
|
||||
|
||||
let overlay = this.openmct.overlays.overlay({
|
||||
element: vm.$el,
|
||||
size: 'small',
|
||||
buttons: [
|
||||
{
|
||||
label: 'OK',
|
||||
emphasis: 'true',
|
||||
callback: () => dismissDialog(overlay, true)
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: () => dismissDialog(overlay, false)
|
||||
}
|
||||
],
|
||||
onDestroy: () => vm.$destroy()
|
||||
});
|
||||
},
|
||||
enableConditionSetNav() {
|
||||
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
|
||||
(objectPath) => {
|
||||
this.objectPath = objectPath;
|
||||
this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath);
|
||||
}
|
||||
);
|
||||
},
|
||||
navigateOrPreview(event) {
|
||||
// If editing, display condition set in Preview overlay; otherwise nav to it while browsing
|
||||
if (this.openmct.editor.isEditing()) {
|
||||
event.preventDefault();
|
||||
this.previewAction.invoke(this.objectPath);
|
||||
} else {
|
||||
this.openmct.router.navigate(this.navigateToPath);
|
||||
}
|
||||
},
|
||||
removeConditionSet() {
|
||||
this.conditionSetDomainObject = undefined;
|
||||
this.conditionalStyles = [];
|
||||
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
|
||||
if (this.itemId) {
|
||||
domainObjectStyles[this.itemId].conditionSetIdentifier = undefined;
|
||||
domainObjectStyles[this.itemId].selectedConditionId = undefined;
|
||||
domainObjectStyles[this.itemId].defaultConditionId = undefined;
|
||||
delete domainObjectStyles[this.itemId].conditionSetIdentifier;
|
||||
domainObjectStyles[this.itemId].styles = undefined;
|
||||
delete domainObjectStyles[this.itemId].styles;
|
||||
if (isEmpty(domainObjectStyles[this.itemId])) {
|
||||
delete domainObjectStyles[this.itemId];
|
||||
}
|
||||
} else {
|
||||
domainObjectStyles.conditionSetIdentifier = undefined;
|
||||
domainObjectStyles.selectedConditionId = undefined;
|
||||
domainObjectStyles.defaultConditionId = undefined;
|
||||
delete domainObjectStyles.conditionSetIdentifier;
|
||||
domainObjectStyles.styles = undefined;
|
||||
delete domainObjectStyles.styles;
|
||||
}
|
||||
|
||||
if (isEmpty(domainObjectStyles)) {
|
||||
domainObjectStyles = undefined;
|
||||
}
|
||||
|
||||
this.persist(domainObjectStyles);
|
||||
if (this.stopProvidingTelemetry) {
|
||||
this.stopProvidingTelemetry();
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
},
|
||||
updateDomainObjectItemStyles(newItems) {
|
||||
//check that all items that have been styles still exist. Otherwise delete those styles
|
||||
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
|
||||
let itemsToRemove = [];
|
||||
let keys = Object.keys(domainObjectStyles);
|
||||
//TODO: Need an easier way to find which properties are itemIds
|
||||
keys.forEach((key) => {
|
||||
const keyIsItemId = (key !== 'styles')
|
||||
&& (key !== 'staticStyle')
|
||||
&& (key !== 'defaultConditionId')
|
||||
&& (key !== 'selectedConditionId')
|
||||
&& (key !== 'conditionSetIdentifier');
|
||||
if (keyIsItemId) {
|
||||
if (!(newItems.find(item => item.id === key))) {
|
||||
itemsToRemove.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (itemsToRemove.length) {
|
||||
this.removeItemStyles(itemsToRemove, domainObjectStyles);
|
||||
}
|
||||
},
|
||||
removeItemStyles(itemIds, domainObjectStyles) {
|
||||
itemIds.forEach(itemId => {
|
||||
if (domainObjectStyles[itemId]) {
|
||||
domainObjectStyles[itemId] = undefined;
|
||||
delete domainObjectStyles[this.itemId];
|
||||
}
|
||||
});
|
||||
if (isEmpty(domainObjectStyles)) {
|
||||
domainObjectStyles = undefined;
|
||||
}
|
||||
|
||||
this.persist(domainObjectStyles);
|
||||
},
|
||||
initializeConditionalStyles() {
|
||||
if (!this.conditions) {
|
||||
this.conditions = {};
|
||||
}
|
||||
|
||||
let conditionalStyles = [];
|
||||
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
|
||||
if (conditionConfiguration.isDefault) {
|
||||
this.selectedConditionId = conditionConfiguration.id;
|
||||
}
|
||||
|
||||
this.conditions[conditionConfiguration.id] = conditionConfiguration;
|
||||
let foundStyle = this.findStyleByConditionId(conditionConfiguration.id);
|
||||
if (foundStyle) {
|
||||
foundStyle.style = Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, foundStyle.style);
|
||||
conditionalStyles.push(foundStyle);
|
||||
} else {
|
||||
conditionalStyles.splice(index, 0, {
|
||||
conditionId: conditionConfiguration.id,
|
||||
style: Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles)
|
||||
});
|
||||
}
|
||||
});
|
||||
//we're doing this so that we remove styles for any conditions that have been removed from the condition set
|
||||
this.conditionalStyles = conditionalStyles;
|
||||
this.conditionsLoaded = true;
|
||||
this.persist(this.getDomainObjectConditionalStyle(this.selectedConditionId));
|
||||
if (!this.isEditing) {
|
||||
this.subscribeToConditionSet();
|
||||
}
|
||||
},
|
||||
subscribeToConditionSet() {
|
||||
if (this.stopProvidingTelemetry) {
|
||||
this.stopProvidingTelemetry();
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
|
||||
if (this.conditionSetDomainObject) {
|
||||
this.openmct.telemetry.request(this.conditionSetDomainObject)
|
||||
.then(output => {
|
||||
if (output && output.length) {
|
||||
this.handleConditionSetResultUpdated(output[0]);
|
||||
}
|
||||
});
|
||||
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(this.conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this));
|
||||
}
|
||||
},
|
||||
handleConditionSetResultUpdated(resultData) {
|
||||
this.selectedConditionId = resultData ? resultData.conditionId : '';
|
||||
},
|
||||
initializeStaticStyle(objectStyles) {
|
||||
let staticStyle = objectStyles && objectStyles.staticStyle;
|
||||
if (staticStyle) {
|
||||
this.staticStyle = {
|
||||
style: Object.assign({}, this.initialStyles, staticStyle.style)
|
||||
};
|
||||
} else {
|
||||
this.staticStyle = {
|
||||
style: Object.assign({}, this.initialStyles)
|
||||
};
|
||||
}
|
||||
},
|
||||
findStyleByConditionId(id) {
|
||||
return this.conditionalStyles.find(conditionalStyle => conditionalStyle.conditionId === id);
|
||||
},
|
||||
updateStaticStyle(staticStyle) {
|
||||
this.staticStyle = staticStyle;
|
||||
this.persist(this.getDomainObjectConditionalStyle());
|
||||
},
|
||||
updateConditionalStyle(conditionStyle) {
|
||||
let found = this.findStyleByConditionId(conditionStyle.conditionId);
|
||||
if (found) {
|
||||
found.style = conditionStyle.style;
|
||||
this.selectedConditionId = found.conditionId;
|
||||
this.persist(this.getDomainObjectConditionalStyle());
|
||||
}
|
||||
},
|
||||
getDomainObjectConditionalStyle(defaultConditionId) {
|
||||
let objectStyle = {
|
||||
styles: this.conditionalStyles,
|
||||
staticStyle: this.staticStyle,
|
||||
selectedConditionId: this.selectedConditionId
|
||||
};
|
||||
if (defaultConditionId) {
|
||||
objectStyle.defaultConditionId = defaultConditionId;
|
||||
}
|
||||
|
||||
if (this.conditionSetDomainObject) {
|
||||
objectStyle.conditionSetIdentifier = this.conditionSetDomainObject.identifier;
|
||||
}
|
||||
|
||||
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
|
||||
|
||||
if (this.itemId) {
|
||||
domainObjectStyles[this.itemId] = objectStyle;
|
||||
} else {
|
||||
//we're deconstructing here to ensure that if an item within a domainObject already had a style we don't lose it
|
||||
domainObjectStyles = {
|
||||
...domainObjectStyles,
|
||||
...objectStyle
|
||||
};
|
||||
}
|
||||
|
||||
return domainObjectStyles;
|
||||
},
|
||||
getCondition(id) {
|
||||
return this.conditions ? this.conditions[id] : {};
|
||||
},
|
||||
applySelectedConditionStyle(conditionId) {
|
||||
this.selectedConditionId = conditionId;
|
||||
this.persist(this.getDomainObjectConditionalStyle());
|
||||
},
|
||||
persist(style) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.objectStyles', style);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -1,280 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-inspector__styles c-inspect-styles">
|
||||
<div class="c-inspect-styles__header">
|
||||
Object Style
|
||||
</div>
|
||||
<div class="c-inspect-styles__content">
|
||||
<div v-if="isStaticAndConditionalStyles"
|
||||
class="c-inspect-styles__mixed-static-and-conditional u-alert u-alert--block u-alert--with-icon"
|
||||
>
|
||||
Your selection includes one or more items that use Conditional Styling. Applying a static style below will replace any Conditional Styling with the new choice.
|
||||
</div>
|
||||
<div v-if="staticStyle"
|
||||
class="c-inspect-styles__style"
|
||||
>
|
||||
<style-editor class="c-inspect-styles__editor"
|
||||
:style-item="staticStyle"
|
||||
:is-editing="isEditing"
|
||||
:mixed-styles="mixedStyles"
|
||||
@persist="updateStaticStyle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import StyleEditor from "./StyleEditor.vue";
|
||||
import PreviewAction from "@/ui/preview/PreviewAction.js";
|
||||
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionalStyleForItem } from "@/plugins/condition/utils/styleUtils";
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
export default {
|
||||
name: 'MultiSelectStylesView',
|
||||
components: {
|
||||
StyleEditor
|
||||
},
|
||||
inject: [
|
||||
'openmct',
|
||||
'selection'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
staticStyle: undefined,
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
mixedStyles: [],
|
||||
isStaticAndConditionalStyles: false
|
||||
};
|
||||
},
|
||||
destroyed() {
|
||||
this.removeListeners();
|
||||
},
|
||||
mounted() {
|
||||
this.items = [];
|
||||
this.previewAction = new PreviewAction(this.openmct);
|
||||
this.getObjectsAndItemsFromSelection();
|
||||
this.initializeStaticStyle();
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
methods: {
|
||||
isItemType(type, item) {
|
||||
return item && (item.type === type);
|
||||
},
|
||||
hasConditionalStyles(domainObject, id) {
|
||||
return getConditionalStyleForItem(domainObject, id) !== undefined;
|
||||
},
|
||||
getObjectsAndItemsFromSelection() {
|
||||
let domainObject;
|
||||
let subObjects = [];
|
||||
|
||||
//multiple selection
|
||||
let itemInitialStyles = [];
|
||||
let itemStyle;
|
||||
this.selection.forEach((selectionItem) => {
|
||||
const item = selectionItem[0].context.item;
|
||||
const layoutItem = selectionItem[0].context.layoutItem;
|
||||
if (item && this.isItemType('subobject-view', layoutItem)) {
|
||||
subObjects.push(item);
|
||||
itemStyle = getApplicableStylesForItem(item);
|
||||
if (!this.isStaticAndConditionalStyles) {
|
||||
this.isStaticAndConditionalStyles = this.hasConditionalStyles(item);
|
||||
}
|
||||
} else {
|
||||
domainObject = selectionItem[1].context.item;
|
||||
itemStyle = getApplicableStylesForItem(domainObject, layoutItem || item);
|
||||
this.items.push({
|
||||
id: layoutItem.id,
|
||||
applicableStyles: itemStyle
|
||||
});
|
||||
if (!this.isStaticAndConditionalStyles) {
|
||||
this.isStaticAndConditionalStyles = this.hasConditionalStyles(domainObject, layoutItem.id);
|
||||
}
|
||||
}
|
||||
|
||||
itemInitialStyles.push(itemStyle);
|
||||
});
|
||||
const {styles, mixedStyles} = getConsolidatedStyleValues(itemInitialStyles);
|
||||
this.initialStyles = styles;
|
||||
this.mixedStyles = mixedStyles;
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.removeListeners();
|
||||
if (this.domainObject) {
|
||||
this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
|
||||
this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
|
||||
}
|
||||
|
||||
subObjects.forEach(this.registerListener);
|
||||
},
|
||||
updateDomainObjectItemStyles(newItems) {
|
||||
//check that all items that have been styles still exist. Otherwise delete those styles
|
||||
let keys = Object.keys(this.domainObject.configuration.objectStyles || {});
|
||||
keys.forEach((key) => {
|
||||
if ((key !== 'styles')
|
||||
&& (key !== 'staticStyle')
|
||||
&& (key !== 'conditionSetIdentifier')) {
|
||||
if (!(newItems.find(item => item.id === key))) {
|
||||
this.removeItemStyles(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
registerListener(domainObject) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
|
||||
if (!this.domainObjectsById) {
|
||||
this.domainObjectsById = {};
|
||||
}
|
||||
|
||||
if (!this.domainObjectsById[id]) {
|
||||
this.domainObjectsById[id] = domainObject;
|
||||
this.observeObject(domainObject, id);
|
||||
}
|
||||
},
|
||||
observeObject(domainObject, id) {
|
||||
let unobserveObject = this.openmct.objects.observe(domainObject, '*', function (newObject) {
|
||||
this.domainObjectsById[id] = JSON.parse(JSON.stringify(newObject));
|
||||
}.bind(this));
|
||||
this.unObserveObjects.push(unobserveObject);
|
||||
},
|
||||
removeListeners() {
|
||||
if (this.stopObserving) {
|
||||
this.stopObserving();
|
||||
}
|
||||
|
||||
if (this.stopObservingItems) {
|
||||
this.stopObservingItems();
|
||||
}
|
||||
|
||||
if (this.unObserveObjects) {
|
||||
this.unObserveObjects.forEach((unObserveObject) => {
|
||||
unObserveObject();
|
||||
});
|
||||
}
|
||||
|
||||
this.unObserveObjects = [];
|
||||
},
|
||||
removeItemStyles(itemId) {
|
||||
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
|
||||
if (itemId && domainObjectStyles[itemId]) {
|
||||
domainObjectStyles[itemId] = undefined;
|
||||
delete domainObjectStyles[this.itemId];
|
||||
|
||||
if (isEmpty(domainObjectStyles)) {
|
||||
domainObjectStyles = undefined;
|
||||
}
|
||||
|
||||
this.persist(this.domainObject, domainObjectStyles);
|
||||
}
|
||||
},
|
||||
removeConditionalStyles(domainObjectStyles, itemId) {
|
||||
if (itemId) {
|
||||
domainObjectStyles[itemId].conditionSetIdentifier = undefined;
|
||||
delete domainObjectStyles[itemId].conditionSetIdentifier;
|
||||
domainObjectStyles[itemId].styles = undefined;
|
||||
delete domainObjectStyles[itemId].styles;
|
||||
} else {
|
||||
domainObjectStyles.conditionSetIdentifier = undefined;
|
||||
delete domainObjectStyles.conditionSetIdentifier;
|
||||
domainObjectStyles.styles = undefined;
|
||||
delete domainObjectStyles.styles;
|
||||
}
|
||||
},
|
||||
setEditState(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
},
|
||||
initializeStaticStyle() {
|
||||
this.staticStyle = {
|
||||
style: Object.assign({}, this.initialStyles)
|
||||
};
|
||||
},
|
||||
updateStaticStyle(staticStyle, property) {
|
||||
//update the static style for each of the layoutItems as well as each sub object item
|
||||
this.staticStyle = staticStyle;
|
||||
this.persist(this.domainObject, this.getDomainObjectStyle(this.domainObject, property, this.items));
|
||||
if (this.domainObjectsById) {
|
||||
const keys = Object.keys(this.domainObjectsById);
|
||||
keys.forEach(key => {
|
||||
let domainObject = this.domainObjectsById[key];
|
||||
this.persist(domainObject, this.getDomainObjectStyle(domainObject, property));
|
||||
});
|
||||
}
|
||||
|
||||
this.isStaticAndConditionalStyles = false;
|
||||
let foundIndex = this.mixedStyles.indexOf(property);
|
||||
if (foundIndex > -1) {
|
||||
this.mixedStyles.splice(foundIndex, 1);
|
||||
}
|
||||
},
|
||||
getDomainObjectStyle(domainObject, property, items) {
|
||||
let domainObjectStyles = (domainObject.configuration && domainObject.configuration.objectStyles) || {};
|
||||
|
||||
if (items) {
|
||||
items.forEach(item => {
|
||||
let itemStaticStyle = {};
|
||||
if (domainObjectStyles[item.id] && domainObjectStyles[item.id].staticStyle) {
|
||||
itemStaticStyle = domainObjectStyles[item.id].staticStyle.style;
|
||||
}
|
||||
|
||||
Object.keys(item.applicableStyles).forEach(key => {
|
||||
if (property === key) {
|
||||
itemStaticStyle[key] = this.staticStyle.style[key];
|
||||
}
|
||||
});
|
||||
if (this.isStaticAndConditionalStyles) {
|
||||
this.removeConditionalStyles(domainObjectStyles, item.id);
|
||||
}
|
||||
|
||||
if (isEmpty(itemStaticStyle)) {
|
||||
itemStaticStyle = undefined;
|
||||
domainObjectStyles[item.id] = undefined;
|
||||
} else {
|
||||
domainObjectStyles[item.id] = Object.assign({}, { staticStyle: { style: itemStaticStyle } });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!domainObjectStyles.staticStyle) {
|
||||
domainObjectStyles.staticStyle = {
|
||||
style: {}
|
||||
};
|
||||
}
|
||||
|
||||
if (this.isStaticAndConditionalStyles) {
|
||||
this.removeConditionalStyles(domainObjectStyles);
|
||||
}
|
||||
|
||||
domainObjectStyles.staticStyle.style[property] = this.staticStyle.style[property];
|
||||
}
|
||||
|
||||
return domainObjectStyles;
|
||||
},
|
||||
|
||||
persist(domainObject, style) {
|
||||
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -63,7 +63,7 @@
|
||||
<div class="c-inspect-styles__header">
|
||||
Conditional Object Styles
|
||||
</div>
|
||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set c-inspect-styles__elem">
|
||||
<a v-if="conditionSetDomainObject"
|
||||
class="c-object-label"
|
||||
@click="navigateOrPreview"
|
||||
@ -87,6 +87,27 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="isConditionWidget && allowEditing"
|
||||
class="c-inspect-styles__elem c-inspect-styles__output-label-toggle"
|
||||
>
|
||||
<label class="c-toggle-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="useConditionSetOutputAsLabel"
|
||||
@change="updateConditionSetOutputLabel"
|
||||
>
|
||||
<span class="c-toggle-switch__slider"></span>
|
||||
<span class="c-toggle-switch__label">Use Condition Set output as label</span>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="isConditionWidget && !allowEditing"
|
||||
class="c-inspect-styles__elem"
|
||||
>
|
||||
<span class="c-toggle-switch__label">Condition Set output as label:
|
||||
<span v-if="useConditionSetOutputAsLabel"> Yes</span><span v-else> No</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<FontStyleEditor
|
||||
v-if="canStyleFont"
|
||||
:font-style="consolidatedFontStyle"
|
||||
@ -172,7 +193,8 @@ export default {
|
||||
selectedConditionId: '',
|
||||
items: [],
|
||||
domainObject: undefined,
|
||||
consolidatedFontStyle: {}
|
||||
consolidatedFontStyle: {},
|
||||
useConditionSetOutputAsLabel: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -187,6 +209,11 @@ export default {
|
||||
allowEditing() {
|
||||
return this.isEditing && !this.locked;
|
||||
},
|
||||
isConditionWidget() {
|
||||
const hasConditionWidgetObjects = this.domainObjectsById && Object.values(this.domainObjectsById).some((object) => object.type === 'conditionWidget');
|
||||
|
||||
return (hasConditionWidgetObjects || (this.domainObject && this.domainObject.type === 'conditionWidget'));
|
||||
},
|
||||
styleableFontItems() {
|
||||
return this.selection.filter(selectionPath => {
|
||||
const item = selectionPath[0].context.item;
|
||||
@ -205,28 +232,6 @@ export default {
|
||||
return true;
|
||||
});
|
||||
},
|
||||
computedconsolidatedFontStyle() {
|
||||
let consolidatedFontStyle;
|
||||
const styles = [];
|
||||
|
||||
this.styleableFontItems.forEach(styleable => {
|
||||
const fontStyle = this.getFontStyle(styleable[0]);
|
||||
|
||||
styles.push(fontStyle);
|
||||
});
|
||||
|
||||
if (styles.length) {
|
||||
const hasConsolidatedFontSize = styles.length && styles.every((fontStyle, i, arr) => fontStyle.fontSize === arr[0].fontSize);
|
||||
const hasConsolidatedFont = styles.length && styles.every((fontStyle, i, arr) => fontStyle.font === arr[0].font);
|
||||
|
||||
consolidatedFontStyle = {
|
||||
fontSize: hasConsolidatedFontSize ? styles[0].fontSize : NON_SPECIFIC,
|
||||
font: hasConsolidatedFont ? styles[0].font : NON_SPECIFIC
|
||||
};
|
||||
}
|
||||
|
||||
return consolidatedFontStyle;
|
||||
},
|
||||
nonSpecificFontProperties() {
|
||||
if (!this.consolidatedFontStyle) {
|
||||
return [];
|
||||
@ -247,6 +252,8 @@ export default {
|
||||
this.previewAction = new PreviewAction(this.openmct);
|
||||
this.isMultipleSelection = this.selection.length > 1;
|
||||
this.getObjectsAndItemsFromSelection();
|
||||
this.useConditionSetOutputAsLabel = this.getConfigurationForLabel();
|
||||
|
||||
if (!this.isMultipleSelection) {
|
||||
let objectStyles = this.getObjectStyles();
|
||||
this.initializeStaticStyle(objectStyles);
|
||||
@ -264,6 +271,12 @@ export default {
|
||||
this.stylesManager.on('styleSelected', this.applyStyleToSelection);
|
||||
},
|
||||
methods: {
|
||||
getConfigurationForLabel() {
|
||||
const childObjectUsesLabels = Object.values(this.domainObjectsById || {}).some((object) => object.configuration && object.configuration.useConditionSetOutputAsLabel);
|
||||
const domainObjectUsesLabels = this.domainObject && this.domainObject.configuration && this.domainObject.configuration.useConditionSetOutputAsLabel;
|
||||
|
||||
return childObjectUsesLabels || domainObjectUsesLabels;
|
||||
},
|
||||
getObjectStyles() {
|
||||
let objectStyles;
|
||||
if (this.domainObjectsById) {
|
||||
@ -487,13 +500,14 @@ export default {
|
||||
|
||||
this.conditions[conditionConfiguration.id] = conditionConfiguration;
|
||||
let foundStyle = this.findStyleByConditionId(conditionConfiguration.id);
|
||||
let output = { output: conditionConfiguration.configuration.output };
|
||||
if (foundStyle) {
|
||||
foundStyle.style = Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, foundStyle.style);
|
||||
foundStyle.style = Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, foundStyle.style, output);
|
||||
conditionalStyles.push(foundStyle);
|
||||
} else {
|
||||
conditionalStyles.splice(index, 0, {
|
||||
conditionId: conditionConfiguration.id,
|
||||
style: Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles)
|
||||
style: Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, output)
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -715,6 +729,12 @@ export default {
|
||||
} else {
|
||||
objectStyle.styles.forEach((conditionalStyle, index) => {
|
||||
let style = {};
|
||||
if (domainObject.configuration.useConditionSetOutputAsLabel) {
|
||||
style.output = conditionalStyle.style.output;
|
||||
} else {
|
||||
style.output = '';
|
||||
}
|
||||
|
||||
Object.keys(item.applicableStyles).concat(['isStyleInvisible']).forEach(key => {
|
||||
style[key] = conditionalStyle.style[key];
|
||||
});
|
||||
@ -731,10 +751,21 @@ export default {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
domainObjectStyles = {
|
||||
...domainObjectStyles,
|
||||
...objectStyle
|
||||
};
|
||||
if (domainObject.configuration.useConditionSetOutputAsLabel !== true) {
|
||||
let objectConditionStyle = JSON.parse(JSON.stringify(objectStyle));
|
||||
objectConditionStyle.styles.forEach((conditionalStyle) => {
|
||||
conditionalStyle.style.output = '';
|
||||
});
|
||||
domainObjectStyles = {
|
||||
...domainObjectStyles,
|
||||
...objectConditionStyle
|
||||
};
|
||||
} else {
|
||||
domainObjectStyles = {
|
||||
...domainObjectStyles,
|
||||
...objectStyle
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return domainObjectStyles;
|
||||
@ -743,6 +774,17 @@ export default {
|
||||
this.selectedConditionId = conditionId;
|
||||
this.getAndPersistStyles();
|
||||
},
|
||||
persistLabelConfiguration() {
|
||||
if (this.domainObjectsById) {
|
||||
Object.values(this.domainObjectsById).forEach((object) => {
|
||||
this.openmct.objects.mutate(object, 'configuration.useConditionSetOutputAsLabel', this.useConditionSetOutputAsLabel);
|
||||
});
|
||||
} else {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.useConditionSetOutputAsLabel', this.useConditionSetOutputAsLabel);
|
||||
}
|
||||
|
||||
this.getAndPersistStyles();
|
||||
},
|
||||
persist(domainObject, style) {
|
||||
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
|
||||
},
|
||||
@ -863,6 +905,10 @@ export default {
|
||||
const layoutItemType = selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type;
|
||||
|
||||
return layoutItemType && layoutItemType !== 'subobject-view';
|
||||
},
|
||||
updateConditionSetOutputLabel(event) {
|
||||
this.useConditionSetOutputAsLabel = event.target.checked === true;
|
||||
this.persistLabelConfiguration();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -39,12 +39,15 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__elem {
|
||||
border-bottom: 1px solid $colorInteriorBorder;
|
||||
padding-bottom: $interiorMargin;
|
||||
}
|
||||
|
||||
&__condition-set {
|
||||
align-items: baseline;
|
||||
border-bottom: 1px solid $colorInteriorBorder;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-bottom: $interiorMargin;
|
||||
|
||||
.c-object-label {
|
||||
flex: 1 1 auto;
|
||||
|
@ -133,6 +133,168 @@ describe('the plugin', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('the condition set usage for condition widgets', () => {
|
||||
let conditionWidgetItem;
|
||||
let selection;
|
||||
let component;
|
||||
let styleViewComponentObject;
|
||||
const conditionSetDomainObject = {
|
||||
"configuration": {
|
||||
"conditionTestData": [
|
||||
{
|
||||
"telemetry": "",
|
||||
"metadata": "",
|
||||
"input": ""
|
||||
}
|
||||
],
|
||||
"conditionCollection": [
|
||||
{
|
||||
"id": "39584410-cbf9-499e-96dc-76f27e69885d",
|
||||
"configuration": {
|
||||
"name": "Unnamed Condition",
|
||||
"output": "Sine > 0",
|
||||
"trigger": "all",
|
||||
"criteria": [
|
||||
{
|
||||
"id": "85fbb2f7-7595-42bd-9767-a932266c5225",
|
||||
"telemetry": {
|
||||
"namespace": "",
|
||||
"key": "be0ba97f-b510-4f40-a18d-4ff121d5ea1a"
|
||||
},
|
||||
"operation": "greaterThan",
|
||||
"input": [
|
||||
"0"
|
||||
],
|
||||
"metadata": "sin"
|
||||
},
|
||||
{
|
||||
"id": "35400132-63b0-425c-ac30-8197df7d5862",
|
||||
"telemetry": "any",
|
||||
"operation": "enumValueIs",
|
||||
"input": [
|
||||
"0"
|
||||
],
|
||||
"metadata": "state"
|
||||
}
|
||||
]
|
||||
},
|
||||
"summary": "Match if all criteria are met: Sine Wave Generator Sine > 0 and any telemetry State is OFF "
|
||||
},
|
||||
{
|
||||
"isDefault": true,
|
||||
"id": "2532d90a-e0d6-4935-b546-3123522da2de",
|
||||
"configuration": {
|
||||
"name": "Default",
|
||||
"output": "Default",
|
||||
"trigger": "all",
|
||||
"criteria": [
|
||||
]
|
||||
},
|
||||
"summary": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"composition": [
|
||||
{
|
||||
"namespace": "",
|
||||
"key": "be0ba97f-b510-4f40-a18d-4ff121d5ea1a"
|
||||
},
|
||||
{
|
||||
"namespace": "",
|
||||
"key": "077ffa67-e78f-4e99-80e0-522ac33a3888"
|
||||
}
|
||||
],
|
||||
"telemetry": {
|
||||
},
|
||||
"name": "Condition Set",
|
||||
"type": "conditionSet",
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "863012c1-f6ca-4ab0-aed7-fd43d5e4cd12"
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
conditionWidgetItem = {
|
||||
"label": "Condition Widget",
|
||||
"conditionalLabel": "",
|
||||
"configuration": {
|
||||
},
|
||||
"name": "Condition Widget",
|
||||
"type": "conditionWidget",
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "c5e636c1-6771-4c9c-b933-8665cab189b3"
|
||||
}
|
||||
};
|
||||
selection = [
|
||||
[{
|
||||
context: {
|
||||
"item": conditionWidgetItem,
|
||||
"supportsMultiSelect": false
|
||||
}
|
||||
}]
|
||||
];
|
||||
let viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
StylesView
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
selection: selection,
|
||||
stylesManager
|
||||
},
|
||||
template: '<styles-view/>'
|
||||
});
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
styleViewComponentObject = component.$root.$children[0];
|
||||
styleViewComponentObject.setEditState(true);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
component.$destroy();
|
||||
});
|
||||
|
||||
it('does not include the output label when the flag is disabled', () => {
|
||||
styleViewComponentObject.conditionSetDomainObject = conditionSetDomainObject;
|
||||
styleViewComponentObject.conditionalStyles = [];
|
||||
styleViewComponentObject.initializeConditionalStyles();
|
||||
expect(styleViewComponentObject.conditionalStyles.length).toBe(2);
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
const hasNoOutput = styleViewComponentObject.domainObject.configuration.objectStyles.styles.every((style) => {
|
||||
return style.style.output === '' || style.style.output === undefined;
|
||||
});
|
||||
|
||||
expect(hasNoOutput).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
it('includes the output label when the flag is enabled', () => {
|
||||
styleViewComponentObject.conditionSetDomainObject = conditionSetDomainObject;
|
||||
styleViewComponentObject.conditionalStyles = [];
|
||||
styleViewComponentObject.initializeConditionalStyles();
|
||||
expect(styleViewComponentObject.conditionalStyles.length).toBe(2);
|
||||
|
||||
styleViewComponentObject.useConditionSetOutputAsLabel = true;
|
||||
styleViewComponentObject.persistLabelConfiguration();
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
const outputs = styleViewComponentObject.domainObject.configuration.objectStyles.styles.map((style) => {
|
||||
return style.style.output;
|
||||
});
|
||||
expect(outputs.join(',')).toEqual('Sine > 0,Default');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('the condition set usage for multiple display layout items', () => {
|
||||
let displayLayoutItem;
|
||||
let lineLayoutItem;
|
||||
@ -449,6 +611,10 @@ describe('the plugin', function () {
|
||||
const applicableStyles = getApplicableStylesForItem(styleViewComponentObject.domainObject, item);
|
||||
const applicableStylesKeys = Object.keys(applicableStyles).concat(['isStyleInvisible']);
|
||||
Object.keys(foundStyle.style).forEach((key) => {
|
||||
if (key === 'output') {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(applicableStylesKeys.indexOf(key)).toBeGreaterThan(-1);
|
||||
expect(foundStyle.style[key]).toEqual(conditionalStyle.style[key]);
|
||||
});
|
||||
|
@ -26,7 +26,7 @@
|
||||
:href="url"
|
||||
>
|
||||
<div class="c-condition-widget__label">
|
||||
{{ internalDomainObject.label }}
|
||||
{{ internalDomainObject.conditionalLabel || internalDomainObject.label }}
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
@ -27,12 +27,15 @@ export default function plugin() {
|
||||
openmct.objectViews.addProvider(new ConditionWidgetViewProvider(openmct));
|
||||
|
||||
openmct.types.addType('conditionWidget', {
|
||||
key: 'conditionWidget',
|
||||
name: "Condition Widget",
|
||||
description: "A button that can be used on its own, or dynamically styled with a Condition Set.",
|
||||
creatable: true,
|
||||
cssClass: 'icon-condition-widget',
|
||||
initialize(domainObject) {
|
||||
domainObject.configuration = {};
|
||||
domainObject.label = 'Condition Widget';
|
||||
domainObject.conditionalLabel = '';
|
||||
},
|
||||
form: [
|
||||
{
|
||||
|
195
src/plugins/conditionWidget/pluginSpec.js
Normal file
195
src/plugins/conditionWidget/pluginSpec.js
Normal file
@ -0,0 +1,195 @@
|
||||
import { createOpenMct, resetApplicationState } from "utils/testing";
|
||||
import ConditionWidgetPlugin from "./plugin";
|
||||
import Vue from 'vue';
|
||||
|
||||
describe('the plugin', function () {
|
||||
const CONDITION_WIDGET_KEY = 'conditionWidget';
|
||||
let objectDef;
|
||||
let element;
|
||||
let child;
|
||||
let openmct;
|
||||
let mockConditionObjectDefinition;
|
||||
let mockConditionObject;
|
||||
let mockConditionObjectPath;
|
||||
|
||||
beforeEach((done) => {
|
||||
mockConditionObjectPath = [
|
||||
{
|
||||
name: 'mock folder',
|
||||
type: 'fake-folder',
|
||||
identifier: {
|
||||
key: 'mock-folder',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'mock parent folder',
|
||||
type: 'conditionWidget',
|
||||
identifier: {
|
||||
key: 'mock-parent-folder',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
mockConditionObjectDefinition = {
|
||||
name: 'Condition Widget',
|
||||
key: 'conditionWidget',
|
||||
creatable: true
|
||||
};
|
||||
|
||||
mockConditionObject = {
|
||||
"conditionWidget": {
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "condition-widget-object"
|
||||
},
|
||||
"url": "https://nasa.github.io/openmct/",
|
||||
"label": "Foo Widget",
|
||||
"type": "conditionWidget",
|
||||
"composition": []
|
||||
},
|
||||
"telemetry": {
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "telemetry-object"
|
||||
},
|
||||
"type": "test-telemetry-object",
|
||||
"name": "Test Telemetry Object",
|
||||
"telemetry": {
|
||||
"values": [
|
||||
{
|
||||
"key": "name",
|
||||
"name": "Name",
|
||||
"format": "string"
|
||||
},
|
||||
{
|
||||
"key": "utc",
|
||||
"name": "Time",
|
||||
"format": "utc",
|
||||
"hints": {
|
||||
"domain": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Some attribute 1",
|
||||
"key": "some-key-1",
|
||||
"hints": {
|
||||
"range": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Some attribute 2",
|
||||
"key": "some-key-2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const timeSystem = {
|
||||
timeSystemKey: 'utc',
|
||||
bounds: {
|
||||
start: 1597160002854,
|
||||
end: 1597181232854
|
||||
}
|
||||
};
|
||||
|
||||
openmct = createOpenMct(timeSystem);
|
||||
openmct.install(new ConditionWidgetPlugin());
|
||||
|
||||
objectDef = openmct.types.get('conditionWidget').definition;
|
||||
|
||||
element = document.createElement('div');
|
||||
element.style.width = '640px';
|
||||
element.style.height = '480px';
|
||||
child = document.createElement('div');
|
||||
child.style.width = '640px';
|
||||
child.style.height = '480px';
|
||||
element.appendChild(child);
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it('defines a conditionWidget object type with the correct key', () => {
|
||||
expect(objectDef.key).toEqual(mockConditionObjectDefinition.key);
|
||||
});
|
||||
|
||||
describe('the conditionWidget object', () => {
|
||||
it('is creatable', () => {
|
||||
expect(objectDef.creatable).toEqual(mockConditionObjectDefinition.creatable);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the view', () => {
|
||||
let conditionWidgetView;
|
||||
let testViewObject;
|
||||
|
||||
beforeEach(() => {
|
||||
testViewObject = {
|
||||
id: "test-object",
|
||||
identifier: {
|
||||
key: "test-object",
|
||||
namespace: ''
|
||||
},
|
||||
type: "conditionWidget"
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testViewObject, mockConditionObjectPath);
|
||||
conditionWidgetView = applicableViews.find((viewProvider) => viewProvider.key === 'conditionWidget');
|
||||
let view = conditionWidgetView.view(testViewObject, element);
|
||||
view.show(child, true);
|
||||
|
||||
return Vue.nextTick();
|
||||
});
|
||||
|
||||
it('provides a view', () => {
|
||||
expect(conditionWidgetView).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should have a view provider for condition widget objects", () => {
|
||||
const applicableViews = openmct.objectViews.get(mockConditionObject[CONDITION_WIDGET_KEY], []);
|
||||
|
||||
const conditionWidgetViewProvider = applicableViews.find(
|
||||
(viewProvider) => viewProvider.key === CONDITION_WIDGET_KEY
|
||||
);
|
||||
|
||||
expect(applicableViews.length).toEqual(1);
|
||||
expect(conditionWidgetViewProvider).toBeDefined();
|
||||
});
|
||||
|
||||
it("should render a view with a URL and label", async () => {
|
||||
const urlParent = document.createElement('div');
|
||||
const urlChild = document.createElement('div');
|
||||
urlParent.appendChild(urlChild);
|
||||
|
||||
const applicableViews = openmct.objectViews.get(mockConditionObject[CONDITION_WIDGET_KEY], []);
|
||||
|
||||
const conditionWidgetViewProvider = applicableViews.find(
|
||||
(viewProvider) => viewProvider.key === CONDITION_WIDGET_KEY
|
||||
);
|
||||
|
||||
const conditionWidgetView = conditionWidgetViewProvider.view(mockConditionObject[CONDITION_WIDGET_KEY], [mockConditionObject[CONDITION_WIDGET_KEY]]);
|
||||
conditionWidgetView.show(urlChild);
|
||||
|
||||
await Vue.nextTick();
|
||||
|
||||
const domainUrl = mockConditionObject[CONDITION_WIDGET_KEY].url;
|
||||
expect(urlParent.innerHTML).toContain(`<a href="${domainUrl}"`);
|
||||
|
||||
const conditionWidgetRender = urlParent.querySelector('.c-condition-widget');
|
||||
expect(conditionWidgetRender).toBeDefined();
|
||||
expect(conditionWidgetRender.innerHTML).toContain('<div class="c-condition-widget__label">');
|
||||
|
||||
const conditionWidgetLabel = conditionWidgetRender.querySelector('.c-condition-widget__label');
|
||||
expect(conditionWidgetLabel).toBeDefined();
|
||||
const domainLabel = mockConditionObject[CONDITION_WIDGET_KEY].label;
|
||||
expect(conditionWidgetLabel.textContent).toContain(domainLabel);
|
||||
});
|
||||
});
|
@ -38,10 +38,14 @@ export default {
|
||||
this.objectStyle = this.getObjectStyleForItem(this.parentDomainObject.configuration.objectStyles);
|
||||
this.initObjectStyles();
|
||||
},
|
||||
destroyed() {
|
||||
beforeDestroy() {
|
||||
if (this.stopListeningObjectStyles) {
|
||||
this.stopListeningObjectStyles();
|
||||
}
|
||||
|
||||
if (this.styleRuleManager) {
|
||||
this.styleRuleManager.destroy();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getObjectStyleForItem(objectStyle) {
|
||||
|
@ -25,11 +25,15 @@ import myItemsInterceptor from "./myItemsInterceptor";
|
||||
|
||||
const MY_ITEMS_DEFAULT_NAME = 'My Items';
|
||||
|
||||
export default function MyItemsPlugin(name = MY_ITEMS_DEFAULT_NAME, namespace = '') {
|
||||
export default function MyItemsPlugin(name = MY_ITEMS_DEFAULT_NAME, namespace = '', priority = undefined) {
|
||||
return function install(openmct) {
|
||||
const identifier = createMyItemsIdentifier(namespace);
|
||||
|
||||
if (priority === undefined) {
|
||||
priority = openmct.priority.LOW;
|
||||
}
|
||||
|
||||
openmct.objects.addGetInterceptor(myItemsInterceptor(openmct, identifier, name));
|
||||
openmct.objects.addRoot(identifier);
|
||||
openmct.objects.addRoot(identifier, priority);
|
||||
};
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ export default {
|
||||
focusEntryId: null,
|
||||
search: '',
|
||||
searchResults: [],
|
||||
showTime: 0,
|
||||
showTime: this.domainObject.configuration.showTime || 0,
|
||||
showNav: false,
|
||||
sidebarCoversEntries: false
|
||||
};
|
||||
@ -239,6 +239,12 @@ export default {
|
||||
watch: {
|
||||
search() {
|
||||
this.getSearchResults();
|
||||
},
|
||||
defaultSort() {
|
||||
mutateObject(this.openmct, this.domainObject, 'configuration.defaultSort', this.defaultSort);
|
||||
},
|
||||
showTime() {
|
||||
mutateObject(this.openmct, this.domainObject, 'configuration.showTime', this.showTime);
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
|
@ -29,10 +29,7 @@ define(
|
||||
}
|
||||
|
||||
SummaryWidgetsCompositionPolicy.prototype.allow = function (parent, child) {
|
||||
const parentType = parent.getCapability('type');
|
||||
const newStyleChild = child.useCapability('adapter');
|
||||
|
||||
if (parentType.instanceOf('summary-widget') && !this.openmct.telemetry.isTelemetryObject(newStyleChild)) {
|
||||
if (parent.type === 'summary-widget' && !this.openmct.telemetry.isTelemetryObject(child)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -185,6 +185,11 @@
|
||||
&__inputs,
|
||||
&__time-bounds {
|
||||
display: flex;
|
||||
|
||||
.c-toggle-switch {
|
||||
// Used in independent Time Conductor
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__inputs {
|
||||
|
@ -229,7 +229,7 @@ body.desktop .has-local-controls {
|
||||
@include abs();
|
||||
}
|
||||
|
||||
.c-grid {
|
||||
.c-grid .c-grid {
|
||||
pointer-events: none;
|
||||
|
||||
&__x { @include bgTicks($editUIGridColorFg, 'x'); }
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
export const COLOR_PALETTE = [
|
||||
[0x00, 0x37, 0xFF],
|
||||
[0x43, 0xB0, 0xFF],
|
||||
[0xF0, 0x60, 0x00],
|
||||
[0x00, 0x70, 0x40],
|
||||
[0xFB, 0x49, 0x49],
|
||||
@ -30,25 +30,25 @@ export const COLOR_PALETTE = [
|
||||
[0xFF, 0xA6, 0x3D],
|
||||
[0x05, 0xA3, 0x00],
|
||||
[0xF0, 0x00, 0x6C],
|
||||
[0x77, 0x17, 0x7A],
|
||||
[0xAC, 0x54, 0xAE],
|
||||
[0x23, 0xA9, 0xDB],
|
||||
[0xFA, 0xF0, 0x6F],
|
||||
[0x4E, 0xF0, 0x48],
|
||||
[0xC7, 0xBE, 0x52],
|
||||
[0x5A, 0xBD, 0x56],
|
||||
[0xAD, 0x50, 0x72],
|
||||
[0x94, 0x25, 0xEA],
|
||||
[0x21, 0x87, 0x82],
|
||||
[0x8F, 0x6E, 0x47],
|
||||
[0xf0, 0x59, 0xcb],
|
||||
[0x34, 0xB6, 0x7D],
|
||||
[0x6A, 0x36, 0xFF],
|
||||
[0x56, 0xF0, 0xE8],
|
||||
[0x7F, 0x52, 0xFF],
|
||||
[0x46, 0xC7, 0xC0],
|
||||
[0xA1, 0x8C, 0x1C],
|
||||
[0xCB, 0xE1, 0x44],
|
||||
[0x95, 0xB1, 0x26],
|
||||
[0xFF, 0x84, 0x9E],
|
||||
[0xB7, 0x79, 0xE7],
|
||||
[0x8C, 0xC9, 0xFD],
|
||||
[0xDB, 0xAA, 0x6E],
|
||||
[0xB8, 0xDF, 0x97],
|
||||
[0x93, 0xB5, 0x77],
|
||||
[0xFF, 0xBC, 0xDA],
|
||||
[0xD3, 0xB6, 0xDE]
|
||||
];
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="domainObject && domainObject.type === 'time-strip'"
|
||||
<div v-if="supportsIndependentTime"
|
||||
class="c-conductor-holder--compact l-shell__main-independent-time-conductor"
|
||||
>
|
||||
<independent-time-conductor :domain-object="domainObject"
|
||||
@ -20,6 +20,12 @@ import StyleRuleManager from "@/plugins/condition/StyleRuleManager";
|
||||
import {STYLE_CONSTANTS} from "@/plugins/condition/utils/constants";
|
||||
import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue';
|
||||
|
||||
const SupportedViewTypes = [
|
||||
'plot-stacked',
|
||||
'plot-overlay',
|
||||
'bar-graph.view',
|
||||
'time-strip.view'
|
||||
];
|
||||
export default {
|
||||
components: {
|
||||
IndependentTimeConductor
|
||||
@ -64,6 +70,11 @@ export default {
|
||||
},
|
||||
font() {
|
||||
return this.objectFontStyle ? this.objectFontStyle.font : this.layoutFont;
|
||||
},
|
||||
supportsIndependentTime() {
|
||||
const viewKey = this.getViewKey();
|
||||
|
||||
return this.domainObject && SupportedViewTypes.includes(viewKey);
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
@ -191,6 +202,12 @@ export default {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this.domainObject && this.domainObject.type === 'conditionWidget' && keys.includes('output')) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'conditionalLabel', styleObj.output);
|
||||
} else {
|
||||
this.openmct.objects.mutate(this.domainObject, 'conditionalLabel', '');
|
||||
}
|
||||
},
|
||||
updateView(immediatelySelect) {
|
||||
this.clear();
|
||||
|
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
/* Note: Open MCT does not intend to support the entire Typescript ecosystem at this time.
|
||||
* This file is intended to add Intellisense for IDEs like VSCode. For more information
|
||||
* about Typescript, please discuss in https://github.com/nasa/openmct/discussions/4693
|
||||
*/
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"strict": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
/* global __dirname */
|
||||
|
||||
const path = require('path');
|
||||
const packageDefinition = require('./package.json');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
@ -12,7 +14,8 @@ const gitBranch = require('child_process')
|
||||
.execSync('git rev-parse --abbrev-ref HEAD')
|
||||
.toString().trim();
|
||||
|
||||
module.exports = {
|
||||
/** @type {import('webpack').Configuration} */
|
||||
const config = {
|
||||
entry: {
|
||||
openmct: './openmct.js',
|
||||
couchDBChangesFeed: './src/plugins/persistence/couch/CouchChangesFeed.js',
|
||||
@ -27,6 +30,7 @@ module.exports = {
|
||||
library: '[name]',
|
||||
libraryTarget: 'umd',
|
||||
publicPath: '',
|
||||
hashFunction: 'xxhash64',
|
||||
clean: true
|
||||
},
|
||||
resolve: {
|
||||
@ -129,3 +133,5 @@ module.exports = {
|
||||
},
|
||||
stats: 'errors-warnings'
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
Reference in New Issue
Block a user