Compare commits

...

11 Commits

Author SHA1 Message Date
49cf23b7e9 form no work 2023-12-28 21:47:51 -08:00
37a2b05612 chore: first fixes 2023-12-28 21:47:43 -08:00
3c89236da2 fix import 2023-12-27 21:53:16 -08:00
827ec5690f use stable object 2023-12-27 21:53:07 -08:00
5c8355c7f8 remove deploysentinel 2023-12-27 21:52:48 -08:00
0f1d80c6b6 Merge branch 'master' of https://github.com/nasa/openmct into eslint-playwright-changes 2023-12-27 21:48:57 -08:00
5af491382e chore: watch mode improvements (#7326) 2023-12-27 21:48:14 -08:00
1e8bb5fd8b chore: add new rules and fix overrides 2023-12-27 20:56:23 -08:00
e1fffaf39a chore:bump eslint playwright 2023-12-27 20:56:06 -08:00
6d47b5bc14 chore: folder changes 2023-12-27 20:55:49 -08:00
2e03bc394c feat: AMD -> ES6 (#7029)
* feat: full amd -> es6 conversion

* fix: move MCT to ES6 class

* fix: default drop, correct imports

* fix: correct all imports

* fix: property typo

* fix: avoid anonymous functions

* fix: correct typo

scarily small - can see why this e2e coverage issue is high priority

* fix: use proper uuid format

* style: fmt

* fix: import vue correctly, get correct layout

* fix: createApp without JSON

fixes template issues

* fix: don't use default on InspectorDataVisualization

* fix: remove more .default calls

* Update src/api/api.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* Update src/plugins/plugins.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* Update src/plugins/plugins.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* fix: suggestions

* fix: drop unnecessary this.annotation initialization

* fix: move all initialization calls to constructor

* refactor: move vue dist import to webpack alias

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-12-27 12:15:51 -08:00
172 changed files with 11818 additions and 12040 deletions

View File

@ -76,7 +76,8 @@ const config = {
MCT: path.join(projectRootDir, 'src/MCT'), MCT: path.join(projectRootDir, 'src/MCT'),
testUtils: path.join(projectRootDir, 'src/utils/testUtils.js'), testUtils: path.join(projectRootDir, 'src/utils/testUtils.js'),
objectUtils: path.join(projectRootDir, 'src/api/objects/object-utils.js'), objectUtils: path.join(projectRootDir, 'src/api/objects/object-utils.js'),
utils: path.join(projectRootDir, 'src/utils') utils: path.join(projectRootDir, 'src/utils'),
vue: 'vue/dist/vue.esm-bundler'
} }
}, },
plugins: [ plugins: [

View File

@ -2,13 +2,16 @@
module.exports = { module.exports = {
extends: ['plugin:playwright/playwright-test'], extends: ['plugin:playwright/playwright-test'],
rules: { rules: {
'playwright/max-nested-describe': ['error', { max: 1 }] 'playwright/max-nested-describe': ['error', { max: 1 }],
'playwright/no-nth-methods': 'error',
'playwright/no-raw-locators': 'error'
}, },
overrides: [ overrides: [
{ {
files: ['tests/visual/*.spec.js'], files: ['tests/visual-a11y/*.spec.js'],
rules: { rules: {
'playwright/no-wait-for-timeout': 'off' 'playwright/no-wait-for-timeout': 'off',
'playwright/expect-expect': 'off'
} }
} }
] ]

View File

@ -574,6 +574,15 @@ A single e2e test in Open MCT is extended to run:
- How is Open MCT extending default Playwright functionality? - How is Open MCT extending default Playwright functionality?
- What about Component Testing? - What about Component Testing?
### Writing Tests
Playwright provides 3 supported methods of debugging and authoring tests:
- A 'watch mode' for running tests locally and debugging on the fly
- A 'debug mode' for debugging tests and writing assertions against tests
- A 'VSCode plugin' for debugging tests within the VSCode IDE.
Generally, we encourage folks to use the watch mode and provide a script `npm run test:e2e:watch` which launches the launch mode ui and enables hot reloading on the dev server.
### e2e Troubleshooting ### e2e Troubleshooting
Please follow the general guide troubleshooting in [the general troubleshooting doc](../TESTING.md#troubleshooting-ci) Please follow the general guide troubleshooting in [the general troubleshooting doc](../TESTING.md#troubleshooting-ci)

View File

@ -84,12 +84,10 @@ async function createDomainObjectWithDefaults(
await page.getByRole('button', { name: 'Create' }).click(); await page.getByRole('button', { name: 'Create' }).click();
// Click the object specified by 'type' // Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("${type}")`); await page.getByRole('menuitem', { name: new RegExp(`${type}`) }).click();
// Modify the name input field of the domain object to accept 'name' // Modify the name input field of the domain object to accept 'name'
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]'); await page.getByLabel('Title', { exact: true }).fill(name);
await nameInput.fill('');
await nameInput.fill(name);
if (page.testNotes) { if (page.testNotes) {
// Fill the "Notes" section with information about the // Fill the "Notes" section with information about the

View File

@ -35,7 +35,7 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { test, expect } = require('./pluginFixtures'); const { test, expect } = require('../pluginFixtures');
const AxeBuilder = require('@axe-core/playwright').default; const AxeBuilder = require('@axe-core/playwright').default;
// Constants for repeated values // Constants for repeated values

View File

@ -76,8 +76,7 @@ const config = {
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840 outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
} }
], ],
['junit', { outputFile: '../test-results/results.xml' }], ['junit', { outputFile: '../test-results/results.xml' }]
['@deploysentinel/playwright']
] ]
}; };

View File

@ -0,0 +1,54 @@
/* eslint-disable no-undef */
// playwright.config.js
// @ts-check
// eslint-disable-next-line no-unused-vars
const { devices } = require('@playwright/test');
const MAX_FAILURES = 5;
const NUM_WORKERS = 2;
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 0, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
testDir: 'tests',
timeout: 60 * 1000,
webServer: {
command: 'npm run start', //Start in dev mode for hot reloading
url: 'http://localhost:8080/#',
timeout: 200 * 1000,
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
},
maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste
workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent
use: {
baseURL: 'http://localhost:8080/',
headless: true,
ignoreHTTPSErrors: true,
screenshot: 'only-on-failure',
trace: 'on-first-retry',
video: 'off'
},
projects: [
{
name: 'chrome',
testMatch: '**/*.spec.js', // run all tests
use: {
browserName: 'chromium'
}
}
],
reporter: [
['list'],
[
'html',
{
open: 'never',
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
}
],
['junit', { outputFile: '../test-results/results.xml' }],
['@deploysentinel/playwright']
]
};
module.exports = config;

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../pluginFixtures.js'); const { test, expect } = require('../../fixtures/pluginFixtures');
const { const {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
createNotification, createNotification,

View File

@ -24,7 +24,7 @@
This test suite is dedicated to tests which verify branding related components. This test suite is dedicated to tests which verify branding related components.
*/ */
const { test, expect } = require('../../baseFixtures.js'); const { test, expect } = require('../../../baseFixtures.js');
test.describe('Branding tests', () => { test.describe('Branding tests', () => {
test('About Modal launches with basic branding properties', async ({ page }) => { test('About Modal launches with basic branding properties', async ({ page }) => {

View File

@ -33,8 +33,8 @@ comfortable running this test during a live mission?" Avoid creating or deleting
Make no assumptions about the order that elements appear in the DOM. Make no assumptions about the order that elements appear in the DOM.
*/ */
const { test, expect } = require('../../pluginFixtures'); const { test, expect } = require('../../../pluginFixtures');
const { createDomainObjectWithDefaults, expandEntireTree } = require('../../appActions'); const { createDomainObjectWithDefaults, expandEntireTree } = require('../../../appActions');
test.describe('Verify tooltips', () => { test.describe('Verify tooltips', () => {
let folder1; let folder1;

View File

@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { createDomainObjectWithDefaults, waitForPlotsToRender } = require('../../appActions'); const { createDomainObjectWithDefaults, waitForPlotsToRender } = require('../../../appActions');
const { test, expect } = require('../../pluginFixtures'); const { test, expect } = require('../../../pluginFixtures');
test.describe('Tabs View', () => { test.describe('Tabs View', () => {
test('Renders tabbed elements nicely', async ({ page }) => { test('Renders tabbed elements nicely', async ({ page }) => {

View File

@ -25,8 +25,8 @@ Collection of Visual Tests set to run with browser clock manipulate made possibl
clockOptions plugin fixture. clockOptions plugin fixture.
*/ */
const { VISUAL_URL, MISSION_TIME } = require('../../constants'); const { VISUAL_URL, MISSION_TIME } = require('../../../constants');
const { test, expect } = require('../../pluginFixtures'); const { test, expect } = require('../../../pluginFixtures');
const percySnapshot = require('@percy/playwright'); const percySnapshot = require('@percy/playwright');
test.describe('Visual - Controlled Clock', () => { test.describe('Visual - Controlled Clock', () => {

View File

@ -34,14 +34,14 @@ test.describe("Visual - Check Notification Info Banner of 'Save successful' @a11
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' }); await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
}); });
test("Create a clock, click on 'Save successful' banner and dismiss it", async ({ test("Create a folder, click on 'Save successful' banner and dismiss it", async ({
page, page,
theme theme
}) => { }) => {
// Create a clock domain object // Create a clock domain object
await createDomainObjectWithDefaults(page, { await createDomainObjectWithDefaults(page, {
type: 'Clock', type: 'Condition Widget',
name: 'Default Clock' name: 'Visual Condition Widget'
}); });
// Click on the div with role="alert" that has "Save successful" text // Click on the div with role="alert" that has "Save successful" text
await page.locator('div[role="alert"]:has-text("Save successful")').click(); await page.locator('div[role="alert"]:has-text("Save successful")').click();

View File

@ -1,138 +1,134 @@
define(['lodash'], function (_) { const METADATA_BY_TYPE = {
var METADATA_BY_TYPE = { generator: {
generator: { values: [
values: [ {
{ key: 'name',
key: 'name', name: 'Name',
name: 'Name', format: 'string'
format: 'string' },
}, {
{ key: 'utc',
key: 'utc', name: 'Time',
name: 'Time', format: 'utc',
format: 'utc', hints: {
hints: { domain: 1
domain: 1
}
},
{
key: 'yesterday',
name: 'Yesterday',
format: 'utc',
hints: {
domain: 2
}
},
{
key: 'wavelengths',
name: 'Wavelength',
unit: 'nm',
format: 'string[]',
hints: {
range: 4
}
},
// Need to enable "LocalTimeSystem" plugin to make use of this
// {
// key: "local",
// name: "Time",
// format: "local-format",
// source: "utc",
// hints: {
// domain: 3
// }
// },
{
key: 'sin',
name: 'Sine',
unit: 'Hz',
formatString: '%0.2f',
hints: {
range: 1
}
},
{
key: 'cos',
name: 'Cosine',
unit: 'deg',
formatString: '%0.2f',
hints: {
range: 2
}
},
{
key: 'intensities',
name: 'Intensities',
format: 'number[]',
hints: {
range: 3
}
} }
] },
}, {
'example.state-generator': { key: 'yesterday',
values: [ name: 'Yesterday',
{ format: 'utc',
key: 'name', hints: {
name: 'Name', domain: 2
format: 'string'
},
{
key: 'utc',
name: 'Time',
format: 'utc',
hints: {
domain: 1
}
},
{
key: 'local',
name: 'Time',
format: 'utc',
source: 'utc',
hints: {
domain: 2
}
},
{
key: 'state',
source: 'value',
name: 'State',
format: 'enum',
enumerations: [
{
value: 0,
string: 'OFF'
},
{
value: 1,
string: 'ON'
}
],
hints: {
range: 1
}
},
{
key: 'value',
name: 'Value',
hints: {
range: 2
}
} }
] },
} {
}; key: 'wavelengths',
name: 'Wavelength',
unit: 'nm',
format: 'string[]',
hints: {
range: 4
}
},
// Need to enable "LocalTimeSystem" plugin to make use of this
// {
// key: "local",
// name: "Time",
// format: "local-format",
// source: "utc",
// hints: {
// domain: 3
// }
// },
{
key: 'sin',
name: 'Sine',
unit: 'Hz',
formatString: '%0.2f',
hints: {
range: 1
}
},
{
key: 'cos',
name: 'Cosine',
unit: 'deg',
formatString: '%0.2f',
hints: {
range: 2
}
},
{
key: 'intensities',
name: 'Intensities',
format: 'number[]',
hints: {
range: 3
}
}
]
},
'example.state-generator': {
values: [
{
key: 'name',
name: 'Name',
format: 'string'
},
{
key: 'utc',
name: 'Time',
format: 'utc',
hints: {
domain: 1
}
},
{
key: 'local',
name: 'Time',
format: 'utc',
source: 'utc',
hints: {
domain: 2
}
},
{
key: 'state',
source: 'value',
name: 'State',
format: 'enum',
enumerations: [
{
value: 0,
string: 'OFF'
},
{
value: 1,
string: 'ON'
}
],
hints: {
range: 1
}
},
{
key: 'value',
name: 'Value',
hints: {
range: 2
}
}
]
}
};
function GeneratorMetadataProvider() {} export default function GeneratorMetadataProvider() {}
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) { GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type); return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type);
}; };
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) { GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return Object.assign({}, domainObject.telemetry, METADATA_BY_TYPE[domainObject.type]); return Object.assign({}, domainObject.telemetry, METADATA_BY_TYPE[domainObject.type]);
}; };
return GeneratorMetadataProvider;
});

View File

@ -20,86 +20,84 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['./WorkerInterface'], function (WorkerInterface) { import WorkerInterface from './WorkerInterface';
var REQUEST_DEFAULTS = {
amplitude: 1,
period: 10,
offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0,
loadDelay: 0,
infinityValues: false,
exceedFloat32: false
};
function GeneratorProvider(openmct, StalenessProvider) { const REQUEST_DEFAULTS = {
this.openmct = openmct; amplitude: 1,
this.workerInterface = new WorkerInterface(openmct, StalenessProvider); period: 10,
} offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0,
loadDelay: 0,
infinityValues: false,
exceedFloat32: false
};
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) { export default function GeneratorProvider(openmct, StalenessProvider) {
return domainObject.type === 'generator'; this.openmct = openmct;
}; this.workerInterface = new WorkerInterface(openmct, StalenessProvider);
}
GeneratorProvider.prototype.supportsRequest = GeneratorProvider.prototype.supportsSubscribe = GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
GeneratorProvider.prototype.canProvideTelemetry; return domainObject.type === 'generator';
};
GeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) { GeneratorProvider.prototype.supportsRequest = GeneratorProvider.prototype.supportsSubscribe =
var props = [ GeneratorProvider.prototype.canProvideTelemetry;
'amplitude',
'period',
'offset',
'dataRateInHz',
'randomness',
'phase',
'loadDelay',
'infinityValues',
'exceedFloat32'
];
request = request || {}; GeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) {
var props = [
'amplitude',
'period',
'offset',
'dataRateInHz',
'randomness',
'phase',
'loadDelay',
'infinityValues',
'exceedFloat32'
];
var workerRequest = {}; request = request || {};
props.forEach(function (prop) { var workerRequest = {};
if (
domainObject.telemetry &&
Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)
) {
workerRequest[prop] = domainObject.telemetry[prop];
}
if (request && Object.prototype.hasOwnProperty.call(request, prop)) { props.forEach(function (prop) {
workerRequest[prop] = request[prop]; if (
} domainObject.telemetry &&
Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)
) {
workerRequest[prop] = domainObject.telemetry[prop];
}
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) { if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
workerRequest[prop] = REQUEST_DEFAULTS[prop]; workerRequest[prop] = request[prop];
} }
workerRequest[prop] = Number(workerRequest[prop]); if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
}); workerRequest[prop] = REQUEST_DEFAULTS[prop];
}
workerRequest.id = this.openmct.objects.makeKeyString(domainObject.identifier); workerRequest[prop] = Number(workerRequest[prop]);
workerRequest.name = domainObject.name; });
return workerRequest; workerRequest.id = this.openmct.objects.makeKeyString(domainObject.identifier);
}; workerRequest.name = domainObject.name;
GeneratorProvider.prototype.request = function (domainObject, request) { return workerRequest;
var workerRequest = this.makeWorkerRequest(domainObject, request); };
workerRequest.start = request.start;
workerRequest.end = request.end;
return this.workerInterface.request(workerRequest); GeneratorProvider.prototype.request = function (domainObject, request) {
}; var workerRequest = this.makeWorkerRequest(domainObject, request);
workerRequest.start = request.start;
workerRequest.end = request.end;
GeneratorProvider.prototype.subscribe = function (domainObject, callback) { return this.workerInterface.request(workerRequest);
var workerRequest = this.makeWorkerRequest(domainObject, {}); };
return this.workerInterface.subscribe(workerRequest, callback); GeneratorProvider.prototype.subscribe = function (domainObject, callback) {
}; var workerRequest = this.makeWorkerRequest(domainObject, {});
return GeneratorProvider; return this.workerInterface.subscribe(workerRequest, callback);
}); };

View File

@ -20,147 +20,143 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { var PURPLE = {
var PURPLE = { sin: 2.2,
sin: 2.2, cos: 2.2
cos: 2.2 },
RED = {
sin: 0.9,
cos: 0.9
},
ORANGE = {
sin: 0.7,
cos: 0.7
},
YELLOW = {
sin: 0.5,
cos: 0.5
},
CYAN = {
sin: 0.45,
cos: 0.45
},
LIMITS = {
rh: {
cssClass: 'is-limit--upr is-limit--red',
low: RED,
high: Number.POSITIVE_INFINITY,
name: 'Red High'
}, },
RED = { rl: {
sin: 0.9, cssClass: 'is-limit--lwr is-limit--red',
cos: 0.9 high: -RED,
low: Number.NEGATIVE_INFINITY,
name: 'Red Low'
}, },
ORANGE = { yh: {
sin: 0.7, cssClass: 'is-limit--upr is-limit--yellow',
cos: 0.7 low: YELLOW,
high: RED,
name: 'Yellow High'
}, },
YELLOW = { yl: {
sin: 0.5, cssClass: 'is-limit--lwr is-limit--yellow',
cos: 0.5 low: -RED,
}, high: -YELLOW,
CYAN = { name: 'Yellow Low'
sin: 0.45, }
cos: 0.45
},
LIMITS = {
rh: {
cssClass: 'is-limit--upr is-limit--red',
low: RED,
high: Number.POSITIVE_INFINITY,
name: 'Red High'
},
rl: {
cssClass: 'is-limit--lwr is-limit--red',
high: -RED,
low: Number.NEGATIVE_INFINITY,
name: 'Red Low'
},
yh: {
cssClass: 'is-limit--upr is-limit--yellow',
low: YELLOW,
high: RED,
name: 'Yellow High'
},
yl: {
cssClass: 'is-limit--lwr is-limit--yellow',
low: -RED,
high: -YELLOW,
name: 'Yellow Low'
}
};
function SinewaveLimitProvider() {}
SinewaveLimitProvider.prototype.supportsLimits = function (domainObject) {
return domainObject.type === 'generator';
}; };
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) { export default function SinewaveLimitProvider() {}
return {
evaluate: function (datum, valueMetadata) {
var range = valueMetadata && valueMetadata.key;
if (datum[range] > RED[range]) { SinewaveLimitProvider.prototype.supportsLimits = function (domainObject) {
return LIMITS.rh; return domainObject.type === 'generator';
} };
if (datum[range] < -RED[range]) { SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
return LIMITS.rl; return {
} evaluate: function (datum, valueMetadata) {
var range = valueMetadata && valueMetadata.key;
if (datum[range] > YELLOW[range]) { if (datum[range] > RED[range]) {
return LIMITS.yh; return LIMITS.rh;
}
if (datum[range] < -YELLOW[range]) {
return LIMITS.yl;
}
} }
};
};
SinewaveLimitProvider.prototype.getLimits = function (domainObject) { if (datum[range] < -RED[range]) {
return { return LIMITS.rl;
limits: function () { }
return Promise.resolve({
WATCH: { if (datum[range] > YELLOW[range]) {
low: { return LIMITS.yh;
color: 'cyan', }
sin: -CYAN.sin,
cos: -CYAN.cos if (datum[range] < -YELLOW[range]) {
}, return LIMITS.yl;
high: { }
color: 'cyan', }
...CYAN };
} };
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
return {
limits: function () {
return Promise.resolve({
WATCH: {
low: {
color: 'cyan',
sin: -CYAN.sin,
cos: -CYAN.cos
}, },
WARNING: { high: {
low: { color: 'cyan',
color: 'yellow', ...CYAN
sin: -YELLOW.sin,
cos: -YELLOW.cos
},
high: {
color: 'yellow',
...YELLOW
}
},
DISTRESS: {
low: {
color: 'orange',
sin: -ORANGE.sin,
cos: -ORANGE.cos
},
high: {
color: 'orange',
...ORANGE
}
},
CRITICAL: {
low: {
color: 'red',
sin: -RED.sin,
cos: -RED.cos
},
high: {
color: 'red',
...RED
}
},
SEVERE: {
low: {
color: 'purple',
sin: -PURPLE.sin,
cos: -PURPLE.cos
},
high: {
color: 'purple',
...PURPLE
}
} }
}); },
} WARNING: {
}; low: {
color: 'yellow',
sin: -YELLOW.sin,
cos: -YELLOW.cos
},
high: {
color: 'yellow',
...YELLOW
}
},
DISTRESS: {
low: {
color: 'orange',
sin: -ORANGE.sin,
cos: -ORANGE.cos
},
high: {
color: 'orange',
...ORANGE
}
},
CRITICAL: {
low: {
color: 'red',
sin: -RED.sin,
cos: -RED.cos
},
high: {
color: 'red',
...RED
}
},
SEVERE: {
low: {
color: 'purple',
sin: -PURPLE.sin,
cos: -PURPLE.cos
},
high: {
color: 'purple',
...PURPLE
}
}
});
}
}; };
};
return SinewaveLimitProvider;
});

View File

@ -20,56 +20,52 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { export default function StateGeneratorProvider() {}
function StateGeneratorProvider() {}
function pointForTimestamp(timestamp, duration, name) { function pointForTimestamp(timestamp, duration, name) {
return { return {
name: name, name: name,
utc: Math.floor(timestamp / duration) * duration, utc: Math.floor(timestamp / duration) * duration,
value: Math.floor(timestamp / duration) % 2 value: Math.floor(timestamp / duration) % 2
}; };
}
StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var duration = domainObject.telemetry.duration * 1000;
var interval = setInterval(function () {
var now = Date.now();
var datum = pointForTimestamp(now, duration, domainObject.name);
datum.value = String(datum.value);
callback(datum);
}, duration);
return function () {
clearInterval(interval);
};
};
StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = Math.min(Date.now(), options.end); // no future values
var duration = domainObject.telemetry.duration * 1000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;
} }
StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) { var data = [];
return domainObject.type === 'example.state-generator'; while (start <= end && data.length < 5000) {
}; data.push(pointForTimestamp(start, duration, domainObject.name));
start += duration;
}
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) { return Promise.resolve(data);
var duration = domainObject.telemetry.duration * 1000; };
var interval = setInterval(function () {
var now = Date.now();
var datum = pointForTimestamp(now, duration, domainObject.name);
datum.value = String(datum.value);
callback(datum);
}, duration);
return function () {
clearInterval(interval);
};
};
StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = Math.min(Date.now(), options.end); // no future values
var duration = domainObject.telemetry.duration * 1000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;
}
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, duration, domainObject.name));
start += duration;
}
return Promise.resolve(data);
};
return StateGeneratorProvider;
});

View File

@ -20,88 +20,86 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['uuid'], function ({ v4: uuid }) { import { v4 as uuid } from 'uuid';
function WorkerInterface(openmct, StalenessProvider) {
// eslint-disable-next-line no-undef
const workerUrl = `${openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}generatorWorker.js`;
this.StalenessProvider = StalenessProvider;
this.worker = new Worker(workerUrl);
this.worker.onmessage = this.onMessage.bind(this);
this.callbacks = {};
this.staleTelemetryIds = {};
this.watchStaleness(); export default function WorkerInterface(openmct, StalenessProvider) {
// eslint-disable-next-line no-undef
const workerUrl = `${openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}generatorWorker.js`;
this.StalenessProvider = StalenessProvider;
this.worker = new Worker(workerUrl);
this.worker.onmessage = this.onMessage.bind(this);
this.callbacks = {};
this.staleTelemetryIds = {};
this.watchStaleness();
}
WorkerInterface.prototype.watchStaleness = function () {
this.StalenessProvider.on('stalenessEvent', ({ id, isStale }) => {
this.staleTelemetryIds[id] = isStale;
});
};
WorkerInterface.prototype.onMessage = function (message) {
message = message.data;
var callback = this.callbacks[message.id];
if (callback) {
callback(message);
}
};
WorkerInterface.prototype.dispatch = function (request, data, callback) {
var message = {
request: request,
data: data,
id: uuid()
};
if (callback) {
this.callbacks[message.id] = callback;
} }
WorkerInterface.prototype.watchStaleness = function () { this.worker.postMessage(message);
this.StalenessProvider.on('stalenessEvent', ({ id, isStale }) => {
this.staleTelemetryIds[id] = isStale;
});
};
WorkerInterface.prototype.onMessage = function (message) { return message.id;
message = message.data; };
var callback = this.callbacks[message.id];
if (callback) {
callback(message);
}
};
WorkerInterface.prototype.dispatch = function (request, data, callback) { WorkerInterface.prototype.request = function (request) {
var message = { var deferred = {};
request: request, var promise = new Promise(function (resolve, reject) {
data: data, deferred.resolve = resolve;
id: uuid() deferred.reject = reject;
}; });
var messageId;
if (callback) { let self = this;
this.callbacks[message.id] = callback; function callback(message) {
if (message.error) {
deferred.reject(message.error);
} else {
deferred.resolve(message.data);
} }
this.worker.postMessage(message); delete self.callbacks[messageId];
}
return message.id; messageId = this.dispatch('request', request, callback.bind(this));
};
WorkerInterface.prototype.request = function (request) { return promise;
var deferred = {}; };
var promise = new Promise(function (resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
var messageId;
let self = this; WorkerInterface.prototype.subscribe = function (request, cb) {
function callback(message) { const { id, loadDelay } = request;
if (message.error) { const messageId = this.dispatch('subscribe', request, (message) => {
deferred.reject(message.error); if (!this.staleTelemetryIds[id]) {
} else { setTimeout(() => cb(message.data), Math.max(loadDelay, 0));
deferred.resolve(message.data);
}
delete self.callbacks[messageId];
} }
});
messageId = this.dispatch('request', request, callback.bind(this)); return function () {
this.dispatch('unsubscribe', {
return promise; id: messageId
};
WorkerInterface.prototype.subscribe = function (request, cb) {
const { id, loadDelay } = request;
const messageId = this.dispatch('subscribe', request, (message) => {
if (!this.staleTelemetryIds[id]) {
setTimeout(() => cb(message.data), Math.max(loadDelay, 0));
}
}); });
delete this.callbacks[messageId];
return function () { }.bind(this);
this.dispatch('unsubscribe', { };
id: messageId
});
delete this.callbacks[messageId];
}.bind(this);
};
return WorkerInterface;
});

View File

@ -75,7 +75,7 @@ if (document.currentScript) {
* @property {OpenMCTComponent[]} components * @property {OpenMCTComponent[]} components
*/ */
const MCT = require('./src/MCT'); const { MCT } = require('./src/MCT');
/** @type {OpenMCT} */ /** @type {OpenMCT} */
const openmct = new MCT(); const openmct = new MCT();

View File

@ -32,7 +32,7 @@
"eslint-config-prettier": "9.0.0", "eslint-config-prettier": "9.0.0",
"eslint-plugin-compat": "4.2.0", "eslint-plugin-compat": "4.2.0",
"eslint-plugin-no-unsanitized": "4.0.2", "eslint-plugin-no-unsanitized": "4.0.2",
"eslint-plugin-playwright": "0.12.0", "eslint-plugin-playwright": "0.20.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "4.2.1",
"eslint-plugin-simple-import-sort": "10.0.0", "eslint-plugin-simple-import-sort": "10.0.0",
"eslint-plugin-unicorn": "49.0.0", "eslint-plugin-unicorn": "49.0.0",
@ -115,7 +115,7 @@
"test:e2e:visual:ci": "percy exec --config ./e2e/.percy.ci.yml --partial -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --project=chrome --grep-invert @unstable", "test:e2e:visual:ci": "percy exec --config ./e2e/.percy.ci.yml --partial -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --project=chrome --grep-invert @unstable",
"test:e2e:visual:full": "percy exec --config ./e2e/.percy.nightly.yml -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --grep-invert @unstable", "test:e2e:visual:full": "percy exec --config ./e2e/.percy.nightly.yml -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --grep-invert @unstable",
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js --grep-invert @couchdb", "test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js --grep-invert @couchdb",
"test:e2e:watch": "npx playwright test --ui --config=e2e/playwright-ci.config.js", "test:e2e:watch": "npx playwright test --ui --config=e2e/playwright-watch.config.js",
"test:perf:contract": "npx playwright test --config=e2e/playwright-performance-dev.config.js", "test:perf:contract": "npx playwright test --config=e2e/playwright-performance-dev.config.js",
"test:perf:localhost": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome", "test:perf:localhost": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome",
"test:perf:memory": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome-memory", "test:perf:memory": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome-memory",

View File

@ -20,72 +20,66 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* eslint-disable no-undef */ /* eslint-disable no-undef */
define([ import EventEmitter from 'EventEmitter';
'EventEmitter', import { createApp, markRaw } from 'vue';
'./api/api',
'./api/overlays/OverlayAPI',
'./api/tooltips/ToolTipAPI',
'./selection/Selection',
'./plugins/plugins',
'./ui/registries/ViewRegistry',
'./plugins/imagery/plugin',
'./ui/registries/InspectorViewRegistry',
'./ui/registries/ToolbarRegistry',
'./ui/router/ApplicationRouter',
'./ui/router/Browse',
'./ui/layout/AppLayout.vue',
'./ui/preview/plugin',
'./api/Branding',
'./plugins/licenses/plugin',
'./plugins/remove/plugin',
'./plugins/move/plugin',
'./plugins/linkAction/plugin',
'./plugins/duplicate/plugin',
'./plugins/importFromJSONAction/plugin',
'./plugins/exportAsJSONAction/plugin',
'vue'
], function (
EventEmitter,
api,
OverlayAPI,
ToolTipAPI,
Selection,
plugins,
ViewRegistry,
ImageryPlugin,
InspectorViewRegistry,
ToolbarRegistry,
ApplicationRouter,
Browse,
Layout,
PreviewPlugin,
BrandingAPI,
LicensesPlugin,
RemoveActionPlugin,
MoveActionPlugin,
LinkActionPlugin,
DuplicateActionPlugin,
ImportFromJSONAction,
ExportAsJSONAction,
Vue
) {
/**
* Open MCT is an extensible web application for building mission
* control user interfaces. This module is itself an instance of
* [MCT]{@link module:openmct.MCT}, which provides an interface for
* configuring and executing the application.
*
* @exports openmct
*/
/** import ActionsAPI from './api/actions/ActionsAPI';
* The Open MCT application. This may be configured by installing plugins import AnnotationAPI from './api/annotation/AnnotationAPI';
* or registering extensions before the application is started. import BrandingAPI from './api/Branding';
* @constructor import CompositionAPI from './api/composition/CompositionAPI';
* @memberof module:openmct import EditorAPI from './api/Editor';
*/ import FaultManagementAPI from './api/faultmanagement/FaultManagementAPI';
function MCT() { import FormsAPI from './api/forms/FormsAPI';
import IndicatorAPI from './api/indicators/IndicatorAPI';
import MenuAPI from './api/menu/MenuAPI';
import NotificationAPI from './api/notifications/NotificationAPI';
import ObjectAPI from './api/objects/ObjectAPI';
import OverlayAPI from './api/overlays/OverlayAPI';
import PriorityAPI from './api/priority/PriorityAPI';
import StatusAPI from './api/status/StatusAPI';
import TelemetryAPI from './api/telemetry/TelemetryAPI';
import TimeAPI from './api/time/TimeAPI';
import ToolTipAPI from './api/tooltips/ToolTipAPI';
import TypeRegistry from './api/types/TypeRegistry';
import UserAPI from './api/user/UserAPI';
import DuplicateActionPlugin from './plugins/duplicate/plugin';
import ExportAsJSONAction from './plugins/exportAsJSONAction/plugin';
import ImageryPlugin from './plugins/imagery/plugin';
import ImportFromJSONAction from './plugins/importFromJSONAction/plugin';
import LicensesPlugin from './plugins/licenses/plugin';
import LinkActionPlugin from './plugins/linkAction/plugin';
import MoveActionPlugin from './plugins/move/plugin';
import plugins from './plugins/plugins';
import RemoveActionPlugin from './plugins/remove/plugin';
import Selection from './selection/Selection';
import Layout from './ui/layout/AppLayout.vue';
import PreviewPlugin from './ui/preview/plugin';
import InspectorViewRegistry from './ui/registries/InspectorViewRegistry';
import ToolbarRegistry from './ui/registries/ToolbarRegistry';
import ViewRegistry from './ui/registries/ViewRegistry';
import ApplicationRouter from './ui/router/ApplicationRouter';
import Browse from './ui/router/Browse';
/**
* Open MCT is an extensible web application for building mission
* control user interfaces. This module is itself an instance of
* [MCT]{@link module:openmct.MCT}, which provides an interface for
* configuring and executing the application.
*
* @exports openmct
*/
/**
* The Open MCT application. This may be configured by installing plugins
* or registering extensions before the application is started.
* @constructor
* @memberof module:openmct
*/
export class MCT extends EventEmitter {
constructor() {
super();
EventEmitter.call(this); EventEmitter.call(this);
this.buildInfo = { this.buildInfo = {
version: __OPENMCT_VERSION__, version: __OPENMCT_VERSION__,
buildDate: __OPENMCT_BUILD_DATE__, buildDate: __OPENMCT_BUILD_DATE__,
@ -95,169 +89,140 @@ define([
this.destroy = this.destroy.bind(this); this.destroy = this.destroy.bind(this);
this.defaultClock = 'local'; this.defaultClock = 'local';
[
/**
* Tracks current selection state of the application.
* @private
*/
['selection', () => new Selection.default(this)],
/** this.plugins = plugins;
* MCT's time conductor, which may be used to synchronize view contents
* for telemetry- or time-based views.
* @type {module:openmct.TimeConductor}
* @memberof module:openmct.MCT#
* @name conductor
*/
['time', () => new api.TimeAPI(this)],
/** /**
* An interface for interacting with the composition of domain objects. * Tracks current selection state of the application.
* The composition of a domain object is the list of other domain * @private
* objects it "contains" (for instance, that should be displayed */
* beneath it in the tree.) this.selection = new Selection(this);
*
* `composition` may be called as a function, in which case it acts
* as [`composition.get`]{@link module:openmct.CompositionAPI#get}.
*
* @type {module:openmct.CompositionAPI}
* @memberof module:openmct.MCT#
* @name composition
*/
['composition', () => new api.CompositionAPI.default(this)],
/** /**
* Registry for views of domain objects which should appear in the * MCT's time conductor, which may be used to synchronize view contents
* main viewing area. * for telemetry- or time-based views.
* * @type {module:openmct.TimeConductor}
* @type {module:openmct.ViewRegistry} * @memberof module:openmct.MCT#
* @memberof module:openmct.MCT# * @name conductor
* @name objectViews */
*/ this.time = new TimeAPI(this);
['objectViews', () => new ViewRegistry()],
/** /**
* Registry for views which should appear in the Inspector area. * An interface for interacting with the composition of domain objects.
* These views will be chosen based on the selection state. * The composition of a domain object is the list of other domain
* * objects it "contains" (for instance, that should be displayed
* @type {module:openmct.InspectorViewRegistry} * beneath it in the tree.)
* @memberof module:openmct.MCT# *
* @name inspectorViews * `composition` may be called as a function, in which case it acts
*/ * as [`composition.get`]{@link module:openmct.CompositionAPI#get}.
['inspectorViews', () => new InspectorViewRegistry.default()], *
* @type {module:openmct.CompositionAPI}
* @memberof module:openmct.MCT#
* @name composition
*/
this.composition = new CompositionAPI(this);
/** /**
* Registry for views which should appear in Edit Properties * Registry for views of domain objects which should appear in the
* dialogs, and similar user interface elements used for * main viewing area.
* modifying domain objects external to its regular views. *
* * @type {module:openmct.ViewRegistry}
* @type {module:openmct.ViewRegistry} * @memberof module:openmct.MCT#
* @memberof module:openmct.MCT# * @name objectViews
* @name propertyEditors */
*/ this.objectViews = new ViewRegistry();
['propertyEditors', () => new ViewRegistry()],
/** /**
* Registry for views which should appear in the toolbar area while * Registry for views which should appear in the Inspector area.
* editing. These views will be chosen based on the selection state. * These views will be chosen based on the selection state.
* *
* @type {module:openmct.ToolbarRegistry} * @type {module:openmct.InspectorViewRegistry}
* @memberof module:openmct.MCT# * @memberof module:openmct.MCT#
* @name toolbars * @name inspectorViews
*/ */
['toolbars', () => new ToolbarRegistry()], this.inspectorViews = new InspectorViewRegistry();
/** /**
* Registry for domain object types which may exist within this * Registry for views which should appear in Edit Properties
* instance of Open MCT. * dialogs, and similar user interface elements used for
* * modifying domain objects external to its regular views.
* @type {module:openmct.TypeRegistry} *
* @memberof module:openmct.MCT# * @type {module:openmct.ViewRegistry}
* @name types * @memberof module:openmct.MCT#
*/ * @name propertyEditors
['types', () => new api.TypeRegistry()], */
this.propertyEditors = new ViewRegistry();
/** /**
* An interface for interacting with domain objects and the domain * Registry for views which should appear in the toolbar area while
* object hierarchy. * editing. These views will be chosen based on the selection state.
* *
* @type {module:openmct.ObjectAPI} * @type {module:openmct.ToolbarRegistry}
* @memberof module:openmct.MCT# * @memberof module:openmct.MCT#
* @name objects * @name toolbars
*/ */
['objects', () => new api.ObjectAPI.default(this.types, this)], this.toolbars = new ToolbarRegistry();
/** /**
* An interface for retrieving and interpreting telemetry data associated * Registry for domain object types which may exist within this
* with a domain object. * instance of Open MCT.
* *
* @type {module:openmct.TelemetryAPI} * @type {module:openmct.TypeRegistry}
* @memberof module:openmct.MCT# * @memberof module:openmct.MCT#
* @name telemetry * @name types
*/ */
['telemetry', () => new api.TelemetryAPI.default(this)], this.types = new TypeRegistry();
/** /**
* An interface for creating new indicators and changing them dynamically. * An interface for interacting with domain objects and the domain
* * object hierarchy.
* @type {module:openmct.IndicatorAPI} *
* @memberof module:openmct.MCT# * @type {module:openmct.ObjectAPI}
* @name indicators * @memberof module:openmct.MCT#
*/ * @name objects
['indicators', () => new api.IndicatorAPI(this)], */
this.objects = new ObjectAPI(this.types, this);
/** /**
* MCT's user awareness management, to enable user and * An interface for retrieving and interpreting telemetry data associated
* role specific functionality. * with a domain object.
* @type {module:openmct.UserAPI} *
* @memberof module:openmct.MCT# * @type {module:openmct.TelemetryAPI}
* @name user * @memberof module:openmct.MCT#
*/ * @name telemetry
['user', () => new api.UserAPI(this)], */
this.telemetry = new TelemetryAPI(this);
['notifications', () => new api.NotificationAPI()], /**
* An interface for creating new indicators and changing them dynamically.
*
* @type {module:openmct.IndicatorAPI}
* @memberof module:openmct.MCT#
* @name indicators
*/
this.indicators = new IndicatorAPI(this);
['editor', () => new api.EditorAPI.default(this)], /**
* MCT's user awareness management, to enable user and
* role specific functionality.
* @type {module:openmct.UserAPI}
* @memberof module:openmct.MCT#
* @name user
*/
this.user = new UserAPI(this);
['overlays', () => new OverlayAPI.default()], this.notifications = new NotificationAPI();
this.editor = new EditorAPI(this);
['tooltips', () => new ToolTipAPI.default()], this.overlays = new OverlayAPI();
this.tooltips = new ToolTipAPI();
['menus', () => new api.MenuAPI(this)], this.menus = new MenuAPI(this);
this.actions = new ActionsAPI(this);
['actions', () => new api.ActionsAPI(this)], this.status = new StatusAPI(this);
this.priority = PriorityAPI;
['status', () => new api.StatusAPI(this)], this.router = new ApplicationRouter(this);
this.faults = new FaultManagementAPI(this);
['priority', () => api.PriorityAPI], this.forms = new FormsAPI(this);
this.branding = BrandingAPI;
['router', () => new ApplicationRouter(this)],
['faults', () => new api.FaultManagementAPI.default(this)],
['forms', () => new api.FormsAPI.default(this)],
['branding', () => BrandingAPI.default],
/**
* MCT's annotation API that enables
* human-created comments and categorization linked to data products
* @type {module:openmct.AnnotationAPI}
* @memberof module:openmct.MCT#
* @name annotation
*/
['annotation', () => new api.AnnotationAPI(this)]
].forEach((apiEntry) => {
const apiName = apiEntry[0];
const apiObject = apiEntry[1]();
Object.defineProperty(this, apiName, {
value: apiObject,
enumerable: false,
configurable: false,
writable: true
});
});
/** /**
* MCT's annotation API that enables * MCT's annotation API that enables
@ -266,23 +231,23 @@ define([
* @memberof module:openmct.MCT# * @memberof module:openmct.MCT#
* @name annotation * @name annotation
*/ */
this.annotation = new api.AnnotationAPI(this); this.annotation = new AnnotationAPI(this);
// Plugins that are installed by default // Plugins that are installed by default
this.install(this.plugins.Plot()); this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable.default()); this.install(this.plugins.TelemetryTable());
this.install(PreviewPlugin.default()); this.install(PreviewPlugin());
this.install(LicensesPlugin.default()); this.install(LicensesPlugin());
this.install(RemoveActionPlugin.default()); this.install(RemoveActionPlugin());
this.install(MoveActionPlugin.default()); this.install(MoveActionPlugin());
this.install(LinkActionPlugin.default()); this.install(LinkActionPlugin());
this.install(DuplicateActionPlugin.default()); this.install(DuplicateActionPlugin());
this.install(ExportAsJSONAction.default()); this.install(ExportAsJSONAction());
this.install(ImportFromJSONAction.default()); this.install(ImportFromJSONAction());
this.install(this.plugins.FormActions.default()); this.install(this.plugins.FormActions());
this.install(this.plugins.FolderView()); this.install(this.plugins.FolderView());
this.install(this.plugins.Tabs()); this.install(this.plugins.Tabs());
this.install(ImageryPlugin.default()); this.install(ImageryPlugin());
this.install(this.plugins.FlexibleLayout()); this.install(this.plugins.FlexibleLayout());
this.install(this.plugins.GoToOriginalAction()); this.install(this.plugins.GoToOriginalAction());
this.install(this.plugins.OpenInNewTabAction()); this.install(this.plugins.OpenInNewTabAction());
@ -300,26 +265,20 @@ define([
this.install(this.plugins.Gauge()); this.install(this.plugins.Gauge());
this.install(this.plugins.InspectorViews()); this.install(this.plugins.InspectorViews());
} }
MCT.prototype = Object.create(EventEmitter.prototype);
MCT.prototype.MCT = MCT;
/** /**
* Set path to where assets are hosted. This should be the path to main.js. * Set path to where assets are hosted. This should be the path to main.js.
* @memberof module:openmct.MCT# * @memberof module:openmct.MCT#
* @method setAssetPath * @method setAssetPath
*/ */
MCT.prototype.setAssetPath = function (assetPath) { setAssetPath(assetPath) {
this._assetPath = assetPath; this._assetPath = assetPath;
}; }
/** /**
* Get path to where assets are hosted. * Get path to where assets are hosted.
* @memberof module:openmct.MCT# * @memberof module:openmct.MCT#
* @method getAssetPath * @method getAssetPath
*/ */
MCT.prototype.getAssetPath = function () { getAssetPath() {
const assetPathLength = this._assetPath && this._assetPath.length; const assetPathLength = this._assetPath && this._assetPath.length;
if (!assetPathLength) { if (!assetPathLength) {
return '/'; return '/';
@ -330,8 +289,7 @@ define([
} }
return this._assetPath; return this._assetPath;
}; }
/** /**
* Start running Open MCT. This should be called only after any plugins * Start running Open MCT. This should be called only after any plugins
* have been installed. * have been installed.
@ -341,10 +299,7 @@ define([
* @param {HTMLElement} [domElement] the DOM element in which to run * @param {HTMLElement} [domElement] the DOM element in which to run
* MCT; if undefined, MCT will be run in the body of the document * MCT; if undefined, MCT will be run in the body of the document
*/ */
MCT.prototype.start = function ( start(domElement = document.body.firstElementChild, isHeadlessMode = false) {
domElement = document.body.firstElementChild,
isHeadlessMode = false
) {
// Create element to mount Layout if it doesn't exist // Create element to mount Layout if it doesn't exist
if (domElement === null) { if (domElement === null) {
domElement = document.createElement('div'); domElement = document.createElement('div');
@ -376,20 +331,12 @@ define([
* @event start * @event start
* @memberof module:openmct.MCT~ * @memberof module:openmct.MCT~
*/ */
if (!isHeadlessMode) { if (!isHeadlessMode) {
const appLayout = Vue.createApp({ const appLayout = createApp(Layout);
components: { appLayout.provide('openmct', markRaw(this));
Layout: Layout.default
},
provide: {
openmct: Vue.markRaw(this)
},
template: '<Layout ref="layout"></Layout>'
});
const component = appLayout.mount(domElement); const component = appLayout.mount(domElement);
component.$nextTick(() => { component.$nextTick(() => {
this.layout = component.$refs.layout; this.layout = component;
this.app = appLayout; this.app = appLayout;
Browse(this); Browse(this);
window.addEventListener('beforeunload', this.destroy); window.addEventListener('beforeunload', this.destroy);
@ -402,14 +349,12 @@ define([
this.router.start(); this.router.start();
this.emit('start'); this.emit('start');
} }
}; }
startHeadless() {
MCT.prototype.startHeadless = function () {
let unreachableNode = document.createElement('div'); let unreachableNode = document.createElement('div');
return this.start(unreachableNode, true); return this.start(unreachableNode, true);
}; }
/** /**
* Install a plugin in MCT. * Install a plugin in MCT.
* *
@ -417,17 +362,13 @@ define([
* invoked with the mct instance. * invoked with the mct instance.
* @memberof module:openmct.MCT# * @memberof module:openmct.MCT#
*/ */
MCT.prototype.install = function (plugin) { install(plugin) {
plugin(this); plugin(this);
}; }
MCT.prototype.destroy = function () { destroy() {
window.removeEventListener('beforeunload', this.destroy); window.removeEventListener('beforeunload', this.destroy);
this.emit('destroy'); this.emit('destroy');
this.router.destroy(); this.router.destroy();
}; }
}
MCT.prototype.plugins = plugins;
return MCT;
});

View File

@ -20,96 +20,98 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['./plugins/plugins', 'utils/testing'], function (plugins, testUtils) { import * as testUtils from 'utils/testing';
describe('MCT', function () {
let openmct;
let mockPlugin;
let mockPlugin2;
let mockListener;
beforeEach(function () { import plugins from './plugins/plugins';
mockPlugin = jasmine.createSpy('plugin');
mockPlugin2 = jasmine.createSpy('plugin2');
mockListener = jasmine.createSpy('listener');
openmct = testUtils.createOpenMct(); describe('MCT', function () {
let openmct;
let mockPlugin;
let mockPlugin2;
let mockListener;
openmct.install(mockPlugin); beforeEach(function () {
openmct.install(mockPlugin2); mockPlugin = jasmine.createSpy('plugin');
openmct.on('start', mockListener); mockPlugin2 = jasmine.createSpy('plugin2');
mockListener = jasmine.createSpy('listener');
openmct = testUtils.createOpenMct();
openmct.install(mockPlugin);
openmct.install(mockPlugin2);
openmct.on('start', mockListener);
});
// Clean up the dirty singleton.
afterEach(function () {
return testUtils.resetApplicationState(openmct);
});
it('exposes plugins', function () {
expect(openmct.plugins).toEqual(plugins);
});
it('does not issue a start event before started', function () {
expect(mockListener).not.toHaveBeenCalled();
});
describe('start', function () {
let appHolder;
beforeEach(function (done) {
appHolder = document.createElement('div');
openmct.on('start', done);
openmct.start(appHolder);
}); });
// Clean up the dirty singleton. it('calls plugins for configuration', function () {
afterEach(function () { expect(mockPlugin).toHaveBeenCalledWith(openmct);
return testUtils.resetApplicationState(openmct); expect(mockPlugin2).toHaveBeenCalledWith(openmct);
}); });
it('exposes plugins', function () { it('emits a start event', function () {
expect(openmct.plugins).toEqual(plugins); expect(mockListener).toHaveBeenCalled();
}); });
it('does not issue a start event before started', function () { it('Renders the application into the provided container element', function () {
expect(mockListener).not.toHaveBeenCalled(); let openMctShellElements = appHolder.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(1);
});
});
describe('startHeadless', function () {
beforeEach(function (done) {
openmct.on('start', done);
openmct.startHeadless();
}); });
describe('start', function () { it('calls plugins for configuration', function () {
let appHolder; expect(mockPlugin).toHaveBeenCalledWith(openmct);
beforeEach(function (done) { expect(mockPlugin2).toHaveBeenCalledWith(openmct);
appHolder = document.createElement('div');
openmct.on('start', done);
openmct.start(appHolder);
});
it('calls plugins for configuration', function () {
expect(mockPlugin).toHaveBeenCalledWith(openmct);
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
});
it('emits a start event', function () {
expect(mockListener).toHaveBeenCalled();
});
it('Renders the application into the provided container element', function () {
let openMctShellElements = appHolder.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(1);
});
}); });
describe('startHeadless', function () { it('emits a start event', function () {
beforeEach(function (done) { expect(mockListener).toHaveBeenCalled();
openmct.on('start', done);
openmct.startHeadless();
});
it('calls plugins for configuration', function () {
expect(mockPlugin).toHaveBeenCalledWith(openmct);
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
});
it('emits a start event', function () {
expect(mockListener).toHaveBeenCalled();
});
it('Does not render Open MCT', function () {
let openMctShellElements = document.body.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(0);
});
}); });
describe('setAssetPath', function () { it('Does not render Open MCT', function () {
let testAssetPath; let openMctShellElements = document.body.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(0);
});
});
it('configures the path for assets', function () { describe('setAssetPath', function () {
testAssetPath = 'some/path/'; let testAssetPath;
openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath);
});
it('adds a trailing /', function () { it('configures the path for assets', function () {
testAssetPath = 'some/path'; testAssetPath = 'some/path/';
openmct.setAssetPath(testAssetPath); openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath + '/'); expect(openmct.getAssetPath()).toBe(testAssetPath);
}); });
it('adds a trailing /', function () {
testAssetPath = 'some/path';
openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath + '/');
}); });
}); });
}); });

View File

@ -20,24 +20,24 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([ import ActionsAPI from './actions/ActionsAPI';
'./actions/ActionsAPI', import AnnotationAPI from './annotation/AnnotationAPI';
'./composition/CompositionAPI', import CompositionAPI from './composition/CompositionAPI';
'./Editor', import EditorAPI from './Editor';
'./faultmanagement/FaultManagementAPI', import FaultManagementAPI from './faultmanagement/FaultManagementAPI';
'./forms/FormsAPI', import FormsAPI from './forms/FormsAPI';
'./indicators/IndicatorAPI', import IndicatorAPI from './indicators/IndicatorAPI';
'./menu/MenuAPI', import MenuAPI from './menu/MenuAPI';
'./notifications/NotificationAPI', import NotificationAPI from './notifications/NotificationAPI';
'./objects/ObjectAPI', import ObjectAPI from './objects/ObjectAPI';
'./priority/PriorityAPI', import PriorityAPI from './priority/PriorityAPI';
'./status/StatusAPI', import StatusAPI from './status/StatusAPI';
'./telemetry/TelemetryAPI', import TelemetryAPI from './telemetry/TelemetryAPI';
'./time/TimeAPI', import TimeAPI from './time/TimeAPI';
'./types/TypeRegistry', import TypeRegistry from './types/TypeRegistry';
'./user/UserAPI', import UserAPI from './user/UserAPI';
'./annotation/AnnotationAPI'
], function ( export default {
ActionsAPI, ActionsAPI,
CompositionAPI, CompositionAPI,
EditorAPI, EditorAPI,
@ -54,23 +54,4 @@ define([
TypeRegistry, TypeRegistry,
UserAPI, UserAPI,
AnnotationAPI AnnotationAPI
) { };
return {
ActionsAPI: ActionsAPI.default,
CompositionAPI: CompositionAPI,
EditorAPI: EditorAPI,
FaultManagementAPI: FaultManagementAPI,
FormsAPI: FormsAPI,
IndicatorAPI: IndicatorAPI.default,
MenuAPI: MenuAPI.default,
NotificationAPI: NotificationAPI.default,
ObjectAPI: ObjectAPI,
PriorityAPI: PriorityAPI.default,
StatusAPI: StatusAPI.default,
TelemetryAPI: TelemetryAPI,
TimeAPI: TimeAPI.default,
TypeRegistry: TypeRegistry.default,
UserAPI: UserAPI.default,
AnnotationAPI: AnnotationAPI.default
};
});

View File

@ -32,10 +32,11 @@
<div <div
v-for="section in formSections" v-for="section in formSections"
:key="section.id" :key="section.id"
:aria-labelledby="'sectionTitle'"
class="c-form__section" class="c-form__section"
:class="section.cssClass" :class="section.cssClass"
> >
<h2 v-if="section.name" class="c-form__section-header"> <h2 v-if="section.name" :id="'sectionTitle' + section.id" class="c-form__section-header">
{{ section.name }} {{ section.name }}
</h2> </h2>
<FormRow <FormRow

View File

@ -20,163 +20,161 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { /**
/** * Utility for checking if a thing is an Open MCT Identifier.
* Utility for checking if a thing is an Open MCT Identifier. * @private
* @private */
*/ function isIdentifier(thing) {
function isIdentifier(thing) { return (
return ( typeof thing === 'object' &&
typeof thing === 'object' && Object.prototype.hasOwnProperty.call(thing, 'key') &&
Object.prototype.hasOwnProperty.call(thing, 'key') && Object.prototype.hasOwnProperty.call(thing, 'namespace')
Object.prototype.hasOwnProperty.call(thing, 'namespace') );
); }
/**
* Utility for checking if a thing is a key string. Not perfect.
* @private
*/
function isKeyString(thing) {
return typeof thing === 'string';
}
/**
* Convert a keyString into an Open MCT Identifier, ex:
* 'scratch:root' ==> {namespace: 'scratch', key: 'root'}
*
* Idempotent.
*
* @param keyString
* @returns identifier
*/
function parseKeyString(keyString) {
if (isIdentifier(keyString)) {
return keyString;
} }
/** let namespace = '';
* Utility for checking if a thing is a key string. Not perfect. let key = keyString;
* @private for (let i = 0; i < key.length; i++) {
*/ if (key[i] === '\\' && key[i + 1] === ':') {
function isKeyString(thing) { i++; // skip escape character.
return typeof thing === 'string'; } else if (key[i] === ':') {
key = key.slice(i + 1);
break;
}
namespace += key[i];
} }
/** if (keyString === namespace) {
* Convert a keyString into an Open MCT Identifier, ex: namespace = '';
* 'scratch:root' ==> {namespace: 'scratch', key: 'root'}
*
* Idempotent.
*
* @param keyString
* @returns identifier
*/
function parseKeyString(keyString) {
if (isIdentifier(keyString)) {
return keyString;
}
let namespace = '';
let key = keyString;
for (let i = 0; i < key.length; i++) {
if (key[i] === '\\' && key[i + 1] === ':') {
i++; // skip escape character.
} else if (key[i] === ':') {
key = key.slice(i + 1);
break;
}
namespace += key[i];
}
if (keyString === namespace) {
namespace = '';
}
return {
namespace: namespace,
key: key
};
}
/**
* Convert an Open MCT Identifier into a keyString, ex:
* {namespace: 'scratch', key: 'root'} ==> 'scratch:root'
*
* Idempotent
*
* @param identifier
* @returns keyString
*/
function makeKeyString(identifier) {
if (!identifier) {
throw new Error('Cannot make key string from null identifier');
}
if (isKeyString(identifier)) {
return identifier;
}
if (!identifier.namespace) {
return identifier.key;
}
return [identifier.namespace.replace(/:/g, '\\:'), identifier.key].join(':');
}
/**
* Convert a new domain object into an old format model, removing the
* identifier and converting the composition array from Open MCT Identifiers
* to old format keyStrings.
*
* @param domainObject
* @returns oldFormatModel
*/
function toOldFormat(model) {
model = JSON.parse(JSON.stringify(model));
delete model.identifier;
if (model.composition) {
model.composition = model.composition.map(makeKeyString);
}
return model;
}
/**
* Convert an old format domain object model into a new format domain
* object. Adds an identifier using the provided keyString, and converts
* the composition array to utilize Open MCT Identifiers.
*
* @param model
* @param keyString
* @returns domainObject
*/
function toNewFormat(model, keyString) {
model = JSON.parse(JSON.stringify(model));
model.identifier = parseKeyString(keyString);
if (model.composition) {
model.composition = model.composition.map(parseKeyString);
}
return model;
}
/**
* Compare two Open MCT Identifiers, returning true if they are equal.
*
* @param identifier
* @param otherIdentifier
* @returns Boolean true if identifiers are equal.
*/
function identifierEquals(a, b) {
return a.key === b.key && a.namespace === b.namespace;
}
/**
* Compare two domain objects, return true if they're the same object.
* Equality is determined by identifier.
*
* @param domainObject
* @param otherDomainOBject
* @returns Boolean true if objects are equal.
*/
function objectEquals(a, b) {
return identifierEquals(a.identifier, b.identifier);
}
function refresh(oldObject, newObject) {
let deleted = _.difference(Object.keys(oldObject), Object.keys(newObject));
deleted.forEach((propertyName) => delete oldObject[propertyName]);
Object.assign(oldObject, newObject);
} }
return { return {
isIdentifier: isIdentifier, namespace: namespace,
toOldFormat: toOldFormat, key: key
toNewFormat: toNewFormat,
makeKeyString: makeKeyString,
parseKeyString: parseKeyString,
equals: objectEquals,
identifierEquals: identifierEquals,
refresh: refresh
}; };
}); }
/**
* Convert an Open MCT Identifier into a keyString, ex:
* {namespace: 'scratch', key: 'root'} ==> 'scratch:root'
*
* Idempotent
*
* @param identifier
* @returns keyString
*/
function makeKeyString(identifier) {
if (!identifier) {
throw new Error('Cannot make key string from null identifier');
}
if (isKeyString(identifier)) {
return identifier;
}
if (!identifier.namespace) {
return identifier.key;
}
return [identifier.namespace.replace(/:/g, '\\:'), identifier.key].join(':');
}
/**
* Convert a new domain object into an old format model, removing the
* identifier and converting the composition array from Open MCT Identifiers
* to old format keyStrings.
*
* @param domainObject
* @returns oldFormatModel
*/
function toOldFormat(model) {
model = JSON.parse(JSON.stringify(model));
delete model.identifier;
if (model.composition) {
model.composition = model.composition.map(makeKeyString);
}
return model;
}
/**
* Convert an old format domain object model into a new format domain
* object. Adds an identifier using the provided keyString, and converts
* the composition array to utilize Open MCT Identifiers.
*
* @param model
* @param keyString
* @returns domainObject
*/
function toNewFormat(model, keyString) {
model = JSON.parse(JSON.stringify(model));
model.identifier = parseKeyString(keyString);
if (model.composition) {
model.composition = model.composition.map(parseKeyString);
}
return model;
}
/**
* Compare two Open MCT Identifiers, returning true if they are equal.
*
* @param identifier
* @param otherIdentifier
* @returns Boolean true if identifiers are equal.
*/
function identifierEquals(a, b) {
return a.key === b.key && a.namespace === b.namespace;
}
/**
* Compare two domain objects, return true if they're the same object.
* Equality is determined by identifier.
*
* @param domainObject
* @param otherDomainOBject
* @returns Boolean true if objects are equal.
*/
function objectEquals(a, b) {
return identifierEquals(a.identifier, b.identifier);
}
function refresh(oldObject, newObject) {
let deleted = _.difference(Object.keys(oldObject), Object.keys(newObject));
deleted.forEach((propertyName) => delete oldObject[propertyName]);
Object.assign(oldObject, newObject);
}
export default {
isIdentifier: isIdentifier,
toOldFormat: toOldFormat,
toNewFormat: toNewFormat,
makeKeyString: makeKeyString,
parseKeyString: parseKeyString,
equals: objectEquals,
identifierEquals: identifierEquals,
refresh: refresh
};

View File

@ -1,89 +1,127 @@
define(['objectUtils'], function (objectUtils) { import objectUtils from 'objectUtils';
describe('objectUtils', function () {
describe('keyString util', function () { describe('objectUtils', function () {
const EXPECTATIONS = { describe('keyString util', function () {
ROOT: { const EXPECTATIONS = {
ROOT: {
namespace: '',
key: 'ROOT'
},
mine: {
namespace: '',
key: 'mine'
},
'extended:something:with:colons': {
key: 'something:with:colons',
namespace: 'extended'
},
'https\\://some/url:resourceId': {
key: 'resourceId',
namespace: 'https://some/url'
},
'scratch:root': {
namespace: 'scratch',
key: 'root'
},
'thingy\\:thing:abc123': {
namespace: 'thingy:thing',
key: 'abc123'
}
};
Object.keys(EXPECTATIONS).forEach(function (keyString) {
it('parses "' + keyString + '".', function () {
expect(objectUtils.parseKeyString(keyString)).toEqual(EXPECTATIONS[keyString]);
});
it('parses and re-encodes "' + keyString + '"', function () {
const identifier = objectUtils.parseKeyString(keyString);
expect(objectUtils.makeKeyString(identifier)).toEqual(keyString);
});
it('is idempotent for "' + keyString + '".', function () {
const identifier = objectUtils.parseKeyString(keyString);
let again = objectUtils.parseKeyString(identifier);
expect(identifier).toEqual(again);
again = objectUtils.parseKeyString(again);
again = objectUtils.parseKeyString(again);
expect(identifier).toEqual(again);
let againKeyString = objectUtils.makeKeyString(again);
expect(againKeyString).toEqual(keyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
expect(againKeyString).toEqual(keyString);
});
});
});
describe('old object conversions', function () {
it('translate ids', function () {
expect(
objectUtils.toNewFormat(
{
prop: 'someValue'
},
'objId'
)
).toEqual({
prop: 'someValue',
identifier: {
namespace: '', namespace: '',
key: 'ROOT' key: 'objId'
},
mine: {
namespace: '',
key: 'mine'
},
'extended:something:with:colons': {
key: 'something:with:colons',
namespace: 'extended'
},
'https\\://some/url:resourceId': {
key: 'resourceId',
namespace: 'https://some/url'
},
'scratch:root': {
namespace: 'scratch',
key: 'root'
},
'thingy\\:thing:abc123': {
namespace: 'thingy:thing',
key: 'abc123'
} }
};
Object.keys(EXPECTATIONS).forEach(function (keyString) {
it('parses "' + keyString + '".', function () {
expect(objectUtils.parseKeyString(keyString)).toEqual(EXPECTATIONS[keyString]);
});
it('parses and re-encodes "' + keyString + '"', function () {
const identifier = objectUtils.parseKeyString(keyString);
expect(objectUtils.makeKeyString(identifier)).toEqual(keyString);
});
it('is idempotent for "' + keyString + '".', function () {
const identifier = objectUtils.parseKeyString(keyString);
let again = objectUtils.parseKeyString(identifier);
expect(identifier).toEqual(again);
again = objectUtils.parseKeyString(again);
again = objectUtils.parseKeyString(again);
expect(identifier).toEqual(again);
let againKeyString = objectUtils.makeKeyString(again);
expect(againKeyString).toEqual(keyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
expect(againKeyString).toEqual(keyString);
});
}); });
}); });
describe('old object conversions', function () { it('translates composition', function () {
it('translate ids', function () { expect(
expect( objectUtils.toNewFormat(
objectUtils.toNewFormat( {
{ prop: 'someValue',
prop: 'someValue' composition: ['anotherObjectId', 'scratch:anotherObjectId']
}, },
'objId' 'objId'
) )
).toEqual({ ).toEqual({
prop: 'someValue',
composition: [
{
namespace: '',
key: 'anotherObjectId'
},
{
namespace: 'scratch',
key: 'anotherObjectId'
}
],
identifier: {
namespace: '',
key: 'objId'
}
});
});
});
describe('new object conversions', function () {
it('removes ids', function () {
expect(
objectUtils.toOldFormat({
prop: 'someValue', prop: 'someValue',
identifier: { identifier: {
namespace: '', namespace: '',
key: 'objId' key: 'objId'
} }
}); })
).toEqual({
prop: 'someValue'
}); });
});
it('translates composition', function () { it('translates composition', function () {
expect( expect(
objectUtils.toNewFormat( objectUtils.toOldFormat({
{
prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
},
'objId'
)
).toEqual({
prop: 'someValue', prop: 'someValue',
composition: [ composition: [
{ {
@ -99,48 +137,10 @@ define(['objectUtils'], function (objectUtils) {
namespace: '', namespace: '',
key: 'objId' key: 'objId'
} }
}); })
}); ).toEqual({
}); prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
describe('new object conversions', function () {
it('removes ids', function () {
expect(
objectUtils.toOldFormat({
prop: 'someValue',
identifier: {
namespace: '',
key: 'objId'
}
})
).toEqual({
prop: 'someValue'
});
});
it('translates composition', function () {
expect(
objectUtils.toOldFormat({
prop: 'someValue',
composition: [
{
namespace: '',
key: 'anotherObjectId'
},
{
namespace: 'scratch',
key: 'anotherObjectId'
}
],
identifier: {
namespace: '',
key: 'objId'
}
})
).toEqual({
prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
});
}); });
}); });
}); });

View File

@ -20,17 +20,19 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['lodash'], function (_) { import _ from 'lodash';
/**
* This is the default metadata provider; for any object with a "telemetry" /**
* property, this provider will return the value of that property as the * This is the default metadata provider; for any object with a "telemetry"
* telemetry metadata. * property, this provider will return the value of that property as the
* * telemetry metadata.
* This provider also implements legacy support for telemetry metadata *
* defined on the type. Telemetry metadata definitions on type will be * This provider also implements legacy support for telemetry metadata
* depreciated in the future. * defined on the type. Telemetry metadata definitions on type will be
*/ * depreciated in the future.
function DefaultMetadataProvider(openmct) { */
export default class DefaultMetadataProvider {
constructor(openmct) {
this.openmct = openmct; this.openmct = openmct;
} }
@ -38,65 +40,14 @@ define(['lodash'], function (_) {
* Applies to any domain object with a telemetry property, or whose type * Applies to any domain object with a telemetry property, or whose type
* definition has a telemetry property. * definition has a telemetry property.
*/ */
DefaultMetadataProvider.prototype.supportsMetadata = function (domainObject) { supportsMetadata(domainObject) {
return Boolean(domainObject.telemetry) || Boolean(this.typeHasTelemetry(domainObject)); return Boolean(domainObject.telemetry) || Boolean(this.typeHasTelemetry(domainObject));
};
/**
* Retrieves valueMetadata from legacy metadata.
* @private
*/
function valueMetadatasFromOldFormat(metadata) {
const valueMetadatas = [];
valueMetadatas.push({
key: 'name',
name: 'Name'
});
metadata.domains.forEach(function (domain, index) {
const valueMetadata = _.clone(domain);
valueMetadata.hints = {
domain: index + 1
};
valueMetadatas.push(valueMetadata);
});
metadata.ranges.forEach(function (range, index) {
const valueMetadata = _.clone(range);
valueMetadata.hints = {
range: index,
priority: index + metadata.domains.length + 1
};
if (valueMetadata.type === 'enum') {
valueMetadata.key = 'enum';
valueMetadata.hints.y -= 10;
valueMetadata.hints.range -= 10;
valueMetadata.enumerations = _.sortBy(
valueMetadata.enumerations.map(function (e) {
return {
string: e.string,
value: Number(e.value)
};
}),
'e.value'
);
valueMetadata.values = valueMetadata.enumerations.map((e) => e.value);
valueMetadata.max = Math.max(valueMetadata.values);
valueMetadata.min = Math.min(valueMetadata.values);
}
valueMetadatas.push(valueMetadata);
});
return valueMetadatas;
} }
/** /**
* Returns telemetry metadata for a given domain object. * Returns telemetry metadata for a given domain object.
*/ */
DefaultMetadataProvider.prototype.getMetadata = function (domainObject) { getMetadata(domainObject) {
const metadata = domainObject.telemetry || {}; const metadata = domainObject.telemetry || {};
if (this.typeHasTelemetry(domainObject)) { if (this.typeHasTelemetry(domainObject)) {
const typeMetadata = this.openmct.types.get(domainObject.type).definition.telemetry; const typeMetadata = this.openmct.types.get(domainObject.type).definition.telemetry;
@ -109,16 +60,65 @@ define(['lodash'], function (_) {
} }
return metadata; return metadata;
}; }
/** /**
* @private * @private
*/ */
DefaultMetadataProvider.prototype.typeHasTelemetry = function (domainObject) { typeHasTelemetry(domainObject) {
const type = this.openmct.types.get(domainObject.type); const type = this.openmct.types.get(domainObject.type);
return Boolean(type.definition.telemetry); return Boolean(type.definition.telemetry);
}; }
}
return DefaultMetadataProvider; /**
}); * Retrieves valueMetadata from legacy metadata.
* @private
*/
function valueMetadatasFromOldFormat(metadata) {
const valueMetadatas = [];
valueMetadatas.push({
key: 'name',
name: 'Name'
});
metadata.domains.forEach(function (domain, index) {
const valueMetadata = _.clone(domain);
valueMetadata.hints = {
domain: index + 1
};
valueMetadatas.push(valueMetadata);
});
metadata.ranges.forEach(function (range, index) {
const valueMetadata = _.clone(range);
valueMetadata.hints = {
range: index,
priority: index + metadata.domains.length + 1
};
if (valueMetadata.type === 'enum') {
valueMetadata.key = 'enum';
valueMetadata.hints.y -= 10;
valueMetadata.hints.range -= 10;
valueMetadata.enumerations = _.sortBy(
valueMetadata.enumerations.map(function (e) {
return {
string: e.string,
value: Number(e.value)
};
}),
'e.value'
);
valueMetadata.values = valueMetadata.enumerations.map((e) => e.value);
valueMetadata.max = Math.max(valueMetadata.values);
valueMetadata.min = Math.min(valueMetadata.values);
}
valueMetadatas.push(valueMetadata);
});
return valueMetadatas;
}

View File

@ -20,143 +20,141 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['lodash'], function (_) { import _ from 'lodash';
function applyReasonableDefaults(valueMetadata, index) {
valueMetadata.source = valueMetadata.source || valueMetadata.key;
valueMetadata.hints = valueMetadata.hints || {};
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) { function applyReasonableDefaults(valueMetadata, index) {
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) { valueMetadata.source = valueMetadata.source || valueMetadata.key;
valueMetadata.hints.domain = valueMetadata.hints.x; valueMetadata.hints = valueMetadata.hints || {};
}
delete valueMetadata.hints.x; if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
valueMetadata.hints.domain = valueMetadata.hints.x;
} }
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) { delete valueMetadata.hints.x;
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
valueMetadata.hints.range = valueMetadata.hints.y;
}
delete valueMetadata.hints.y;
}
if (valueMetadata.format === 'enum') {
if (!valueMetadata.values) {
valueMetadata.values = valueMetadata.enumerations.map((e) => e.value);
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata, 'max')) {
valueMetadata.max = Math.max(valueMetadata.values) + 1;
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata, 'min')) {
valueMetadata.min = Math.min(valueMetadata.values) - 1;
}
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'priority')) {
valueMetadata.hints.priority = index;
}
return valueMetadata;
} }
/** if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
* Utility class for handling and inspecting telemetry metadata. Applies if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
* reasonable defaults to simplify the task of providing metadata, while valueMetadata.hints.range = valueMetadata.hints.y;
* also providing methods for interrogating telemetry metadata. }
*/
function TelemetryMetadataManager(metadata) {
this.metadata = metadata;
this.valueMetadatas = this.metadata.values delete valueMetadata.hints.y;
? this.metadata.values.map(applyReasonableDefaults)
: [];
} }
/** if (valueMetadata.format === 'enum') {
* Get value metadata for a single key. if (!valueMetadata.values) {
*/ valueMetadata.values = valueMetadata.enumerations.map((e) => e.value);
TelemetryMetadataManager.prototype.value = function (key) { }
return this.valueMetadatas.filter(function (metadata) {
return metadata.key === key; if (!Object.prototype.hasOwnProperty.call(valueMetadata, 'max')) {
valueMetadata.max = Math.max(valueMetadata.values) + 1;
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata, 'min')) {
valueMetadata.min = Math.min(valueMetadata.values) - 1;
}
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'priority')) {
valueMetadata.hints.priority = index;
}
return valueMetadata;
}
/**
* Utility class for handling and inspecting telemetry metadata. Applies
* reasonable defaults to simplify the task of providing metadata, while
* also providing methods for interrogating telemetry metadata.
*/
export default function TelemetryMetadataManager(metadata) {
this.metadata = metadata;
this.valueMetadatas = this.metadata.values
? this.metadata.values.map(applyReasonableDefaults)
: [];
}
/**
* Get value metadata for a single key.
*/
TelemetryMetadataManager.prototype.value = function (key) {
return this.valueMetadatas.filter(function (metadata) {
return metadata.key === key;
})[0];
};
/**
* Returns all value metadatas, sorted by priority.
*/
TelemetryMetadataManager.prototype.values = function () {
return this.valuesForHints(['priority']);
};
/**
* Get an array of valueMetadatas that possess all hints requested.
* Array is sorted based on hint priority.
*
*/
TelemetryMetadataManager.prototype.valuesForHints = function (hints) {
function hasHint(hint) {
// eslint-disable-next-line no-invalid-this
return Object.prototype.hasOwnProperty.call(this.hints, hint);
}
function hasHints(metadata) {
return hints.every(hasHint, metadata);
}
const matchingMetadata = this.valueMetadatas.filter(hasHints);
let iteratees = hints.map((hint) => {
return (metadata) => {
return metadata.hints[hint];
};
});
return _.sortBy(matchingMetadata, ...iteratees);
};
/**
* check out of a given metadata has array values
*/
TelemetryMetadataManager.prototype.isArrayValue = function (metadata) {
const regex = /\[\]$/g;
if (!metadata.format && !metadata.formatString) {
return false;
}
return (metadata.format || metadata.formatString).match(regex) !== null;
};
TelemetryMetadataManager.prototype.getFilterableValues = function () {
return this.valueMetadatas.filter(
(metadatum) => metadatum.filters && metadatum.filters.length > 0
);
};
TelemetryMetadataManager.prototype.getUseToUpdateInPlaceValue = function () {
return this.valueMetadatas.find(this.isInPlaceUpdateValue);
};
TelemetryMetadataManager.prototype.isInPlaceUpdateValue = function (metadatum) {
return metadatum.useToUpdateInPlace === true;
};
TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
let valueMetadata = this.valuesForHints(['range'])[0];
if (valueMetadata === undefined) {
valueMetadata = this.values().filter((values) => {
return !values.hints.domain;
})[0]; })[0];
}; }
/** if (valueMetadata === undefined) {
* Returns all value metadatas, sorted by priority. valueMetadata = this.values()[0];
*/ }
TelemetryMetadataManager.prototype.values = function () {
return this.valuesForHints(['priority']);
};
/** return valueMetadata;
* Get an array of valueMetadatas that possess all hints requested. };
* Array is sorted based on hint priority.
*
*/
TelemetryMetadataManager.prototype.valuesForHints = function (hints) {
function hasHint(hint) {
// eslint-disable-next-line no-invalid-this
return Object.prototype.hasOwnProperty.call(this.hints, hint);
}
function hasHints(metadata) {
return hints.every(hasHint, metadata);
}
const matchingMetadata = this.valueMetadatas.filter(hasHints);
let iteratees = hints.map((hint) => {
return (metadata) => {
return metadata.hints[hint];
};
});
return _.sortBy(matchingMetadata, ...iteratees);
};
/**
* check out of a given metadata has array values
*/
TelemetryMetadataManager.prototype.isArrayValue = function (metadata) {
const regex = /\[\]$/g;
if (!metadata.format && !metadata.formatString) {
return false;
}
return (metadata.format || metadata.formatString).match(regex) !== null;
};
TelemetryMetadataManager.prototype.getFilterableValues = function () {
return this.valueMetadatas.filter(
(metadatum) => metadatum.filters && metadatum.filters.length > 0
);
};
TelemetryMetadataManager.prototype.getUseToUpdateInPlaceValue = function () {
return this.valueMetadatas.find(this.isInPlaceUpdateValue);
};
TelemetryMetadataManager.prototype.isInPlaceUpdateValue = function (metadatum) {
return metadatum.useToUpdateInPlace === true;
};
TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
let valueMetadata = this.valuesForHints(['range'])[0];
if (valueMetadata === undefined) {
valueMetadata = this.values().filter((values) => {
return !values.hints.domain;
})[0];
}
if (valueMetadata === undefined) {
valueMetadata = this.values()[0];
}
return valueMetadata;
};
return TelemetryMetadataManager;
});

View File

@ -20,96 +20,92 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { // Set of connection states; changing among these states will be
// Set of connection states; changing among these states will be // reflected in the indicator's appearance.
// reflected in the indicator's appearance. // CONNECTED: Everything nominal, expect to be able to read/write.
// CONNECTED: Everything nominal, expect to be able to read/write. // DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected. // PENDING: Still trying to connect, and haven't failed yet.
// PENDING: Still trying to connect, and haven't failed yet. const CONNECTED = {
const CONNECTED = { statusClass: 's-status-on'
statusClass: 's-status-on' };
}; const PENDING = {
const PENDING = { statusClass: 's-status-warning-lo'
statusClass: 's-status-warning-lo' };
}; const DISCONNECTED = {
const DISCONNECTED = { statusClass: 's-status-warning-hi'
statusClass: 's-status-warning-hi' };
}; export default function URLIndicator(options, simpleIndicator) {
function URLIndicator(options, simpleIndicator) { this.bindMethods();
this.bindMethods(); this.count = 0;
this.count = 0;
this.indicator = simpleIndicator; this.indicator = simpleIndicator;
this.setDefaultsFromOptions(options); this.setDefaultsFromOptions(options);
this.setIndicatorToState(PENDING); this.setIndicatorToState(PENDING);
this.fetchUrl(); this.fetchUrl();
setInterval(this.fetchUrl, this.interval); setInterval(this.fetchUrl, this.interval);
} }
URLIndicator.prototype.setIndicatorToState = function (state) { URLIndicator.prototype.setIndicatorToState = function (state) {
switch (state) { switch (state) {
case CONNECTED: { case CONNECTED: {
this.indicator.text(this.label + ' is connected'); this.indicator.text(this.label + ' is connected');
this.indicator.description( this.indicator.description(
this.label + ' is online, checking status every ' + this.interval + ' milliseconds.' this.label + ' is online, checking status every ' + this.interval + ' milliseconds.'
); );
break; break;
}
case PENDING: {
this.indicator.text('Checking status of ' + this.label + ' please stand by...');
this.indicator.description('Checking status of ' + this.label + ' please stand by...');
break;
}
case DISCONNECTED: {
this.indicator.text(this.label + ' is offline');
this.indicator.description(
this.label + ' is offline, checking status every ' + this.interval + ' milliseconds'
);
break;
}
} }
this.indicator.statusClass(state.statusClass); case PENDING: {
}; this.indicator.text('Checking status of ' + this.label + ' please stand by...');
this.indicator.description('Checking status of ' + this.label + ' please stand by...');
break;
}
URLIndicator.prototype.fetchUrl = function () { case DISCONNECTED: {
fetch(this.URLpath) this.indicator.text(this.label + ' is offline');
.then((response) => { this.indicator.description(
if (response.ok) { this.label + ' is offline, checking status every ' + this.interval + ' milliseconds'
this.handleSuccess(); );
} else { break;
this.handleError(); }
} }
})
.catch((error) => { this.indicator.statusClass(state.statusClass);
};
URLIndicator.prototype.fetchUrl = function () {
fetch(this.URLpath)
.then((response) => {
if (response.ok) {
this.handleSuccess();
} else {
this.handleError(); this.handleError();
}); }
}; })
.catch((error) => {
this.handleError();
});
};
URLIndicator.prototype.handleError = function (e) { URLIndicator.prototype.handleError = function (e) {
this.setIndicatorToState(DISCONNECTED); this.setIndicatorToState(DISCONNECTED);
}; };
URLIndicator.prototype.handleSuccess = function () { URLIndicator.prototype.handleSuccess = function () {
this.setIndicatorToState(CONNECTED); this.setIndicatorToState(CONNECTED);
}; };
URLIndicator.prototype.setDefaultsFromOptions = function (options) { URLIndicator.prototype.setDefaultsFromOptions = function (options) {
this.URLpath = options.url; this.URLpath = options.url;
this.label = options.label || options.url; this.label = options.label || options.url;
this.interval = options.interval || 10000; this.interval = options.interval || 10000;
this.indicator.iconClass(options.iconClass || 'icon-chain-links'); this.indicator.iconClass(options.iconClass || 'icon-chain-links');
}; };
URLIndicator.prototype.bindMethods = function () { URLIndicator.prototype.bindMethods = function () {
this.fetchUrl = this.fetchUrl.bind(this); this.fetchUrl = this.fetchUrl.bind(this);
this.handleSuccess = this.handleSuccess.bind(this); this.handleSuccess = this.handleSuccess.bind(this);
this.handleError = this.handleError.bind(this); this.handleError = this.handleError.bind(this);
this.setIndicatorToState = this.setIndicatorToState.bind(this); this.setIndicatorToState = this.setIndicatorToState.bind(this);
}; };
return URLIndicator;
});

View File

@ -19,15 +19,15 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['./URLIndicator'], function URLIndicatorPlugin(URLIndicator) { import URLIndicator from './URLIndicator';
return function (opts) {
return function install(openmct) {
const simpleIndicator = openmct.indicators.simpleIndicator();
const urlIndicator = new URLIndicator(opts, simpleIndicator);
openmct.indicators.add(simpleIndicator); export default function URLIndicatorPlugin(opts) {
return function install(openmct) {
const simpleIndicator = openmct.indicators.simpleIndicator();
const urlIndicator = new URLIndicator(opts, simpleIndicator);
return urlIndicator; openmct.indicators.add(simpleIndicator);
};
return urlIndicator;
}; };
}); }

View File

@ -20,118 +20,115 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['utils/testing', './URLIndicator', './URLIndicatorPlugin', '../../MCT'], function ( import * as testingUtils from 'utils/testing';
testingUtils,
URLIndicator,
URLIndicatorPlugin,
MCT
) {
describe('The URLIndicator', function () {
let openmct;
let indicatorElement;
let pluginOptions;
let urlIndicator; // eslint-disable-line
let fetchSpy;
beforeEach(function () { import URLIndicatorPlugin from './URLIndicatorPlugin';
jasmine.clock().install();
openmct = new testingUtils.createOpenMct();
spyOn(openmct.indicators, 'add');
fetchSpy = spyOn(window, 'fetch').and.callFake(() =>
Promise.resolve({
ok: true
})
);
});
afterEach(function () { describe('The URLIndicator', function () {
if (window.fetch.restore) { let openmct;
window.fetch.restore(); let indicatorElement;
} let pluginOptions;
let urlIndicator; // eslint-disable-line
let fetchSpy;
jasmine.clock().uninstall(); beforeEach(function () {
jasmine.clock().install();
openmct = new testingUtils.createOpenMct();
spyOn(openmct.indicators, 'add');
fetchSpy = spyOn(window, 'fetch').and.callFake(() =>
Promise.resolve({
ok: true
})
);
});
return testingUtils.resetApplicationState(openmct); afterEach(function () {
}); if (window.fetch.restore) {
window.fetch.restore();
}
describe('on initialization', function () { jasmine.clock().uninstall();
describe('with default options', function () {
beforeEach(function () {
pluginOptions = {
url: 'someURL'
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
it('has a default icon class if none supplied', function () { return testingUtils.resetApplicationState(openmct);
expect(indicatorElement.classList.contains('icon-chain-links')).toBe(true); });
});
it('defaults to the URL if no label supplied', function () { describe('on initialization', function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.url) >= 0).toBe(true); describe('with default options', function () {
});
});
describe('with custom options', function () {
beforeEach(function () {
pluginOptions = {
url: 'customURL',
interval: 1814,
iconClass: 'iconClass-checked',
label: 'custom label'
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
it('uses the custom iconClass', function () {
expect(indicatorElement.classList.contains('iconClass-checked')).toBe(true);
});
it('uses custom interval', function () {
expect(window.fetch).toHaveBeenCalledTimes(1);
jasmine.clock().tick(1);
expect(window.fetch).toHaveBeenCalledTimes(1);
jasmine.clock().tick(pluginOptions.interval + 1);
expect(window.fetch).toHaveBeenCalledTimes(2);
});
it('uses custom label if supplied in initialization', function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.label) >= 0).toBe(true);
});
});
});
describe('when running', function () {
beforeEach(function () { beforeEach(function () {
pluginOptions = { pluginOptions = {
url: 'someURL', url: 'someURL'
interval: 100
}; };
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct); urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element; indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
}); });
it('requests the provided URL', function () { it('has a default icon class if none supplied', function () {
jasmine.clock().tick(pluginOptions.interval + 1); expect(indicatorElement.classList.contains('icon-chain-links')).toBe(true);
expect(window.fetch).toHaveBeenCalledWith(pluginOptions.url);
}); });
it('indicates success if connection is nominal', async function () { it('defaults to the URL if no label supplied', function () {
jasmine.clock().tick(pluginOptions.interval + 1); expect(indicatorElement.textContent.indexOf(pluginOptions.url) >= 0).toBe(true);
await urlIndicator.fetchUrl(); });
expect(indicatorElement.classList.contains('s-status-on')).toBe(true); });
describe('with custom options', function () {
beforeEach(function () {
pluginOptions = {
url: 'customURL',
interval: 1814,
iconClass: 'iconClass-checked',
label: 'custom label'
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
}); });
it('indicates an error when the server cannot be reached', async function () { it('uses the custom iconClass', function () {
fetchSpy.and.callFake(() => expect(indicatorElement.classList.contains('iconClass-checked')).toBe(true);
Promise.resolve({ });
ok: false it('uses custom interval', function () {
}) expect(window.fetch).toHaveBeenCalledTimes(1);
); jasmine.clock().tick(1);
expect(window.fetch).toHaveBeenCalledTimes(1);
jasmine.clock().tick(pluginOptions.interval + 1); jasmine.clock().tick(pluginOptions.interval + 1);
await urlIndicator.fetchUrl(); expect(window.fetch).toHaveBeenCalledTimes(2);
expect(indicatorElement.classList.contains('s-status-warning-hi')).toBe(true); });
it('uses custom label if supplied in initialization', function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.label) >= 0).toBe(true);
}); });
}); });
}); });
describe('when running', function () {
beforeEach(function () {
pluginOptions = {
url: 'someURL',
interval: 100
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
it('requests the provided URL', function () {
jasmine.clock().tick(pluginOptions.interval + 1);
expect(window.fetch).toHaveBeenCalledWith(pluginOptions.url);
});
it('indicates success if connection is nominal', async function () {
jasmine.clock().tick(pluginOptions.interval + 1);
await urlIndicator.fetchUrl();
expect(indicatorElement.classList.contains('s-status-on')).toBe(true);
});
it('indicates an error when the server cannot be reached', async function () {
fetchSpy.and.callFake(() =>
Promise.resolve({
ok: false
})
);
jasmine.clock().tick(pluginOptions.interval + 1);
await urlIndicator.fetchUrl();
expect(indicatorElement.classList.contains('s-status-warning-hi')).toBe(true);
});
});
}); });

View File

@ -20,15 +20,13 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { /**
/** * Constant values used by the Autoflow Tabular View.
* Constant values used by the Autoflow Tabular View. */
*/ export default {
return { ROW_HEIGHT: 16,
ROW_HEIGHT: 16, SLIDER_HEIGHT: 10,
SLIDER_HEIGHT: 10, INITIAL_COLUMN_WIDTH: 225,
INITIAL_COLUMN_WIDTH: 225, MAX_COLUMN_WIDTH: 525,
MAX_COLUMN_WIDTH: 525, COLUMN_WIDTH_STEP: 25
COLUMN_WIDTH_STEP: 25 };
};
});

View File

@ -20,104 +20,102 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['./AutoflowTabularRowController'], function (AutoflowTabularRowController) { import AutoflowTabularRowController from './AutoflowTabularRowController';
/**
* Controller for an Autoflow Tabular View. Subscribes to telemetry
* associated with children of the domain object and passes that
* information on to the view.
*
* @param {DomainObject} domainObject the object being viewed
* @param {*} data the view data
* @param openmct a reference to the openmct application
*/
function AutoflowTabularController(domainObject, data, openmct) {
this.composition = openmct.composition.get(domainObject);
this.data = data;
this.openmct = openmct;
this.rows = {}; /**
this.controllers = {}; * Controller for an Autoflow Tabular View. Subscribes to telemetry
* associated with children of the domain object and passes that
* information on to the view.
*
* @param {DomainObject} domainObject the object being viewed
* @param {*} data the view data
* @param openmct a reference to the openmct application
*/
export default function AutoflowTabularController(domainObject, data, openmct) {
this.composition = openmct.composition.get(domainObject);
this.data = data;
this.openmct = openmct;
this.addRow = this.addRow.bind(this); this.rows = {};
this.removeRow = this.removeRow.bind(this); this.controllers = {};
this.addRow = this.addRow.bind(this);
this.removeRow = this.removeRow.bind(this);
}
/**
* Set the "Last Updated" value to be displayed.
* @param {String} value the value to display
* @private
*/
AutoflowTabularController.prototype.trackLastUpdated = function (value) {
this.data.updated = value;
};
/**
* Respond to an `add` event from composition by adding a new row.
* @private
*/
AutoflowTabularController.prototype.addRow = function (childObject) {
const identifier = childObject.identifier;
const id = [identifier.namespace, identifier.key].join(':');
if (!this.rows[id]) {
this.rows[id] = {
classes: '',
name: childObject.name,
value: undefined
};
this.controllers[id] = new AutoflowTabularRowController(
childObject,
this.rows[id],
this.openmct,
this.trackLastUpdated.bind(this)
);
this.controllers[id].activate();
this.data.items.push(this.rows[id]);
} }
};
/** /**
* Set the "Last Updated" value to be displayed. * Respond to an `remove` event from composition by removing any
* @param {String} value the value to display * related row.
* @private * @private
*/ */
AutoflowTabularController.prototype.trackLastUpdated = function (value) { AutoflowTabularController.prototype.removeRow = function (identifier) {
this.data.updated = value; const id = [identifier.namespace, identifier.key].join(':');
};
/** if (this.rows[id]) {
* Respond to an `add` event from composition by adding a new row. this.data.items = this.data.items.filter(
* @private function (item) {
*/ return item !== this.rows[id];
AutoflowTabularController.prototype.addRow = function (childObject) {
const identifier = childObject.identifier;
const id = [identifier.namespace, identifier.key].join(':');
if (!this.rows[id]) {
this.rows[id] = {
classes: '',
name: childObject.name,
value: undefined
};
this.controllers[id] = new AutoflowTabularRowController(
childObject,
this.rows[id],
this.openmct,
this.trackLastUpdated.bind(this)
);
this.controllers[id].activate();
this.data.items.push(this.rows[id]);
}
};
/**
* Respond to an `remove` event from composition by removing any
* related row.
* @private
*/
AutoflowTabularController.prototype.removeRow = function (identifier) {
const id = [identifier.namespace, identifier.key].join(':');
if (this.rows[id]) {
this.data.items = this.data.items.filter(
function (item) {
return item !== this.rows[id];
}.bind(this)
);
this.controllers[id].destroy();
delete this.controllers[id];
delete this.rows[id];
}
};
/**
* Activate this controller; begin listening for changes.
*/
AutoflowTabularController.prototype.activate = function () {
this.composition.on('add', this.addRow);
this.composition.on('remove', this.removeRow);
this.composition.load();
};
/**
* Destroy this controller; detach any associated resources.
*/
AutoflowTabularController.prototype.destroy = function () {
Object.keys(this.controllers).forEach(
function (id) {
this.controllers[id].destroy();
}.bind(this) }.bind(this)
); );
this.controllers = {}; this.controllers[id].destroy();
this.composition.off('add', this.addRow); delete this.controllers[id];
this.composition.off('remove', this.removeRow); delete this.rows[id];
}; }
};
return AutoflowTabularController; /**
}); * Activate this controller; begin listening for changes.
*/
AutoflowTabularController.prototype.activate = function () {
this.composition.on('add', this.addRow);
this.composition.on('remove', this.removeRow);
this.composition.load();
};
/**
* Destroy this controller; detach any associated resources.
*/
AutoflowTabularController.prototype.destroy = function () {
Object.keys(this.controllers).forEach(
function (id) {
this.controllers[id].destroy();
}.bind(this)
);
this.controllers = {};
this.composition.off('add', this.addRow);
this.composition.off('remove', this.removeRow);
};

View File

@ -20,23 +20,23 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['./AutoflowTabularView'], function (AutoflowTabularView) { import AutoflowTabularView from './AutoflowTabularView';
return function (options) {
return function (openmct) {
const views = openmct.mainViews || openmct.objectViews;
views.addProvider({ export default function (options) {
name: 'Autoflow Tabular', return function (openmct) {
key: 'autoflow', const views = openmct.mainViews || openmct.objectViews;
cssClass: 'icon-packet',
description: 'A tabular view of packet contents.', views.addProvider({
canView: function (d) { name: 'Autoflow Tabular',
return !options || options.type === d.type; key: 'autoflow',
}, cssClass: 'icon-packet',
view: function (domainObject) { description: 'A tabular view of packet contents.',
return new AutoflowTabularView(domainObject, openmct, document); canView: function (d) {
} return !options || options.type === d.type;
}); },
}; view: function (domainObject) {
return new AutoflowTabularView(domainObject, openmct, document);
}
});
}; };
}); }

View File

@ -20,76 +20,72 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { /**
/** * Controller for individual rows of an Autoflow Tabular View.
* Controller for individual rows of an Autoflow Tabular View. * Subscribes to telemetry and updates row data.
* Subscribes to telemetry and updates row data. *
* * @param {DomainObject} domainObject the object being viewed
* @param {DomainObject} domainObject the object being viewed * @param {*} data the view data
* @param {*} data the view data * @param openmct a reference to the openmct application
* @param openmct a reference to the openmct application * @param {Function} callback a callback to invoke with "last updated" timestamps
* @param {Function} callback a callback to invoke with "last updated" timestamps */
*/ export default function AutoflowTabularRowController(domainObject, data, openmct, callback) {
function AutoflowTabularRowController(domainObject, data, openmct, callback) { this.domainObject = domainObject;
this.domainObject = domainObject; this.data = data;
this.data = data; this.openmct = openmct;
this.openmct = openmct; this.callback = callback;
this.callback = callback;
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.ranges = this.metadata.valuesForHints(['range']); this.ranges = this.metadata.valuesForHints(['range']);
this.domains = this.metadata.valuesForHints(['domain']); this.domains = this.metadata.valuesForHints(['domain']);
this.rangeFormatter = this.openmct.telemetry.getValueFormatter(this.ranges[0]); this.rangeFormatter = this.openmct.telemetry.getValueFormatter(this.ranges[0]);
this.domainFormatter = this.openmct.telemetry.getValueFormatter(this.domains[0]); this.domainFormatter = this.openmct.telemetry.getValueFormatter(this.domains[0]);
this.evaluator = this.openmct.telemetry.limitEvaluator(this.domainObject); this.evaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
this.initialized = false; this.initialized = false;
}
/**
* Update row to reflect incoming telemetry data.
* @private
*/
AutoflowTabularRowController.prototype.updateRowData = function (datum) {
const violations = this.evaluator.evaluate(datum, this.ranges[0]);
this.initialized = true;
this.data.classes = violations ? violations.cssClass : '';
this.data.value = this.rangeFormatter.format(datum);
this.callback(this.domainFormatter.format(datum));
};
/**
* Activate this controller; begin listening for changes.
*/
AutoflowTabularRowController.prototype.activate = function () {
this.unsubscribe = this.openmct.telemetry.subscribe(
this.domainObject,
this.updateRowData.bind(this)
);
const options = {
size: 1,
strategy: 'latest',
timeContext: this.openmct.time.getContextForView([])
};
this.openmct.telemetry.request(this.domainObject, options).then(
function (history) {
if (!this.initialized && history.length > 0) {
this.updateRowData(history[history.length - 1]);
}
}.bind(this)
);
};
/**
* Destroy this controller; detach any associated resources.
*/
AutoflowTabularRowController.prototype.destroy = function () {
if (this.unsubscribe) {
this.unsubscribe();
} }
};
/**
* Update row to reflect incoming telemetry data.
* @private
*/
AutoflowTabularRowController.prototype.updateRowData = function (datum) {
const violations = this.evaluator.evaluate(datum, this.ranges[0]);
this.initialized = true;
this.data.classes = violations ? violations.cssClass : '';
this.data.value = this.rangeFormatter.format(datum);
this.callback(this.domainFormatter.format(datum));
};
/**
* Activate this controller; begin listening for changes.
*/
AutoflowTabularRowController.prototype.activate = function () {
this.unsubscribe = this.openmct.telemetry.subscribe(
this.domainObject,
this.updateRowData.bind(this)
);
const options = {
size: 1,
strategy: 'latest',
timeContext: this.openmct.time.getContextForView([])
};
this.openmct.telemetry.request(this.domainObject, options).then(
function (history) {
if (!this.initialized && history.length > 0) {
this.updateRowData(history[history.length - 1]);
}
}.bind(this)
);
};
/**
* Destroy this controller; detach any associated resources.
*/
AutoflowTabularRowController.prototype.destroy = function () {
if (this.unsubscribe) {
this.unsubscribe();
}
};
return AutoflowTabularRowController;
});

View File

@ -20,96 +20,92 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([ import autoflowTemplate from './autoflow-tabular.html';
'./AutoflowTabularController', import AutoflowTabularConstants from './AutoflowTabularConstants';
'./AutoflowTabularConstants', import AutoflowTabularController from './AutoflowTabularController';
'./VueView', import VueView from './VueView';
'./autoflow-tabular.html'
], function (AutoflowTabularController, AutoflowTabularConstants, VueView, autoflowTemplate) {
const ROW_HEIGHT = AutoflowTabularConstants.ROW_HEIGHT;
const SLIDER_HEIGHT = AutoflowTabularConstants.SLIDER_HEIGHT;
const INITIAL_COLUMN_WIDTH = AutoflowTabularConstants.INITIAL_COLUMN_WIDTH;
const MAX_COLUMN_WIDTH = AutoflowTabularConstants.MAX_COLUMN_WIDTH;
const COLUMN_WIDTH_STEP = AutoflowTabularConstants.COLUMN_WIDTH_STEP;
/** const ROW_HEIGHT = AutoflowTabularConstants.ROW_HEIGHT;
* Implements the Autoflow Tabular view of a domain object. const SLIDER_HEIGHT = AutoflowTabularConstants.SLIDER_HEIGHT;
*/ const INITIAL_COLUMN_WIDTH = AutoflowTabularConstants.INITIAL_COLUMN_WIDTH;
function AutoflowTabularView(domainObject, openmct) { const MAX_COLUMN_WIDTH = AutoflowTabularConstants.MAX_COLUMN_WIDTH;
const data = { const COLUMN_WIDTH_STEP = AutoflowTabularConstants.COLUMN_WIDTH_STEP;
items: [],
columns: [],
width: INITIAL_COLUMN_WIDTH,
filter: '',
updated: 'No updates',
rowCount: 1
};
const controller = new AutoflowTabularController(domainObject, data, openmct);
let interval;
VueView.call(this, { /**
data: data, * Implements the Autoflow Tabular view of a domain object.
methods: { */
increaseColumnWidth: function () { export default function AutoflowTabularView(domainObject, openmct) {
data.width += COLUMN_WIDTH_STEP; const data = {
data.width = data.width > MAX_COLUMN_WIDTH ? INITIAL_COLUMN_WIDTH : data.width; items: [],
}, columns: [],
reflow: function () { width: INITIAL_COLUMN_WIDTH,
let column = []; filter: '',
let index = 0; updated: 'No updates',
const filteredItems = data.items.filter(function (item) { rowCount: 1
return item.name.toLowerCase().indexOf(data.filter.toLowerCase()) !== -1; };
}); const controller = new AutoflowTabularController(domainObject, data, openmct);
let interval;
data.columns = []; VueView.call(this, {
data: data,
methods: {
increaseColumnWidth: function () {
data.width += COLUMN_WIDTH_STEP;
data.width = data.width > MAX_COLUMN_WIDTH ? INITIAL_COLUMN_WIDTH : data.width;
},
reflow: function () {
let column = [];
let index = 0;
const filteredItems = data.items.filter(function (item) {
return item.name.toLowerCase().indexOf(data.filter.toLowerCase()) !== -1;
});
while (index < filteredItems.length) { data.columns = [];
if (column.length >= data.rowCount) {
data.columns.push(column);
column = [];
}
column.push(filteredItems[index]); while (index < filteredItems.length) {
index += 1; if (column.length >= data.rowCount) {
}
if (column.length > 0) {
data.columns.push(column); data.columns.push(column);
column = [];
} }
column.push(filteredItems[index]);
index += 1;
} }
},
watch: {
filter: 'reflow',
items: 'reflow',
rowCount: 'reflow'
},
template: autoflowTemplate,
unmounted: function () {
controller.destroy();
if (interval) { if (column.length > 0) {
clearInterval(interval); data.columns.push(column);
interval = undefined;
} }
},
mounted: function () {
controller.activate();
const updateRowHeight = function () {
const tabularArea = this.$refs.autoflowItems;
const height = tabularArea ? tabularArea.clientHeight : 0;
const available = height - SLIDER_HEIGHT;
const rows = Math.max(1, Math.floor(available / ROW_HEIGHT));
data.rowCount = rows;
}.bind(this);
interval = setInterval(updateRowHeight, 50);
this.$nextTick(updateRowHeight);
} }
}); },
} watch: {
filter: 'reflow',
items: 'reflow',
rowCount: 'reflow'
},
template: autoflowTemplate,
unmounted: function () {
controller.destroy();
AutoflowTabularView.prototype = Object.create(VueView.default.prototype); if (interval) {
clearInterval(interval);
interval = undefined;
}
},
mounted: function () {
controller.activate();
return AutoflowTabularView; const updateRowHeight = function () {
}); const tabularArea = this.$refs.autoflowItems;
const height = tabularArea ? tabularArea.clientHeight : 0;
const available = height - SLIDER_HEIGHT;
const rows = Math.max(1, Math.floor(available / ROW_HEIGHT));
data.rowCount = rows;
}.bind(this);
interval = setInterval(updateRowHeight, 50);
this.$nextTick(updateRowHeight);
}
});
}
AutoflowTabularView.prototype = Object.create(VueView.prototype);

View File

@ -22,13 +22,15 @@
import mount from 'utils/mount'; import mount from 'utils/mount';
export default function () { export default function () {
return function VueView(options) { class VueView {
const { vNode, destroy } = mount(options); constructor(options) {
const { vNode, destroy } = mount(options);
this.show = function (container) {
container.appendChild(vNode.el);
};
this.destroy = destroy;
}
}
this.show = function (container) { return VueView;
container.appendChild(vNode.el);
};
this.destroy = destroy;
};
} }

View File

@ -20,44 +20,40 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { export default function DOMObserver(element) {
function DOMObserver(element) { this.element = element;
this.element = element; this.observers = [];
this.observers = []; }
}
DOMObserver.prototype.when = function (latchFunction) { DOMObserver.prototype.when = function (latchFunction) {
return new Promise( return new Promise(
function (resolve, reject) { function (resolve, reject) {
//Test latch function at least once //Test latch function at least once
if (latchFunction()) { if (latchFunction()) {
resolve(); resolve();
} else { } else {
//Latch condition not true yet, create observer on DOM and test again on change. //Latch condition not true yet, create observer on DOM and test again on change.
const config = { const config = {
attributes: true, attributes: true,
childList: true, childList: true,
subtree: true subtree: true
}; };
const observer = new MutationObserver(function () { const observer = new MutationObserver(function () {
if (latchFunction()) { if (latchFunction()) {
resolve(); resolve();
} }
}); });
observer.observe(this.element, config); observer.observe(this.element, config);
this.observers.push(observer); this.observers.push(observer);
} }
}.bind(this) }.bind(this)
); );
}; };
DOMObserver.prototype.destroy = function () { DOMObserver.prototype.destroy = function () {
this.observers.forEach( this.observers.forEach(
function (observer) { function (observer) {
observer.disconnect(); observer.disconnect();
}.bind(this) }.bind(this)
); );
}; };
return DOMObserver;
});

View File

@ -20,53 +20,49 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(function () { export default function DisplayLayoutType() {
function DisplayLayoutType() { return {
return { name: 'Display Layout',
name: 'Display Layout', creatable: true,
creatable: true, description:
description: 'Assemble other objects and components together into a reusable screen layout. Simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.',
'Assemble other objects and components together into a reusable screen layout. Simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.', cssClass: 'icon-layout',
cssClass: 'icon-layout', initialize(domainObject) {
initialize(domainObject) { domainObject.composition = [];
domainObject.composition = []; domainObject.configuration = {
domainObject.configuration = { items: [],
items: [], layoutGrid: [10, 10]
layoutGrid: [10, 10] };
}; },
form: [
{
name: 'Horizontal grid (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutGrid', 0],
required: true
}, },
form: [ {
{ name: 'Vertical grid (px)',
name: 'Horizontal grid (px)', control: 'numberfield',
control: 'numberfield', cssClass: 'l-input-sm l-numeric',
cssClass: 'l-input-sm l-numeric', property: ['configuration', 'layoutGrid', 1],
property: ['configuration', 'layoutGrid', 0], required: true
required: true },
}, {
{ name: 'Horizontal size (px)',
name: 'Vertical grid (px)', control: 'numberfield',
control: 'numberfield', cssClass: 'l-input-sm l-numeric',
cssClass: 'l-input-sm l-numeric', property: ['configuration', 'layoutDimensions', 0],
property: ['configuration', 'layoutGrid', 1], required: false
required: true },
}, {
{ name: 'Vertical size (px)',
name: 'Horizontal size (px)', control: 'numberfield',
control: 'numberfield', cssClass: 'l-input-sm l-numeric',
cssClass: 'l-input-sm l-numeric', property: ['configuration', 'layoutDimensions', 1],
property: ['configuration', 'layoutDimensions', 0], required: false
required: false }
}, ]
{ };
name: 'Vertical size (px)', }
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutDimensions', 1],
required: false
}
]
};
}
return DisplayLayoutType;
});

View File

@ -20,93 +20,89 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { /**
/** * Handles drag interactions on frames in layouts. This will
* Handles drag interactions on frames in layouts. This will * provides new positions/dimensions for frames based on
* provides new positions/dimensions for frames based on * relative pixel positions provided; these will take into account
* relative pixel positions provided; these will take into account * the grid size (in a snap-to sense) and will enforce some minimums
* the grid size (in a snap-to sense) and will enforce some minimums * on both position and dimensions.
* on both position and dimensions. *
* * The provided position and dimensions factors will determine
* The provided position and dimensions factors will determine * whether this is a move or a resize, and what type of resize it
* whether this is a move or a resize, and what type of resize it * will be. For instance, a position factor of [1, 1]
* will be. For instance, a position factor of [1, 1] * will move a frame along with the mouse as the drag
* will move a frame along with the mouse as the drag * proceeds, while a dimension factor of [0, 0] will leave
* proceeds, while a dimension factor of [0, 0] will leave * dimensions unchanged. Combining these in different
* dimensions unchanged. Combining these in different * ways results in different handles; a position factor of
* ways results in different handles; a position factor of * [1, 0] and a dimensions factor of [-1, 0] will implement
* [1, 0] and a dimensions factor of [-1, 0] will implement * a left-edge resize, as the horizontal position will move
* a left-edge resize, as the horizontal position will move * with the mouse while the horizontal dimensions shrink in
* with the mouse while the horizontal dimensions shrink in * kind (and vertical properties remain unmodified.)
* kind (and vertical properties remain unmodified.) *
* * @param {object} rawPosition the initial position/dimensions
* @param {object} rawPosition the initial position/dimensions * of the frame being interacted with
* of the frame being interacted with * @param {number[]} posFactor the position factor
* @param {number[]} posFactor the position factor * @param {number[]} dimFactor the dimensions factor
* @param {number[]} dimFactor the dimensions factor * @param {number[]} the size of each grid element, in pixels
* @param {number[]} the size of each grid element, in pixels * @constructor
* @constructor * @memberof platform/features/layout
* @memberof platform/features/layout */
*/ export default function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) {
function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) { this.rawPosition = rawPosition;
this.rawPosition = rawPosition; this.posFactor = posFactor;
this.posFactor = posFactor; this.dimFactor = dimFactor;
this.dimFactor = dimFactor; this.gridSize = gridSize;
this.gridSize = gridSize; }
}
// Convert a delta from pixel coordinates to grid coordinates, // Convert a delta from pixel coordinates to grid coordinates,
// rounding to whole-number grid coordinates. // rounding to whole-number grid coordinates.
function toGridDelta(gridSize, pixelDelta) { function toGridDelta(gridSize, pixelDelta) {
return pixelDelta.map(function (v, i) { return pixelDelta.map(function (v, i) {
return Math.round(v / gridSize[i]); return Math.round(v / gridSize[i]);
}); });
} }
// Utility function to perform element-by-element multiplication // Utility function to perform element-by-element multiplication
function multiply(array, factors) { function multiply(array, factors) {
return array.map(function (v, i) { return array.map(function (v, i) {
return v * factors[i]; return v * factors[i];
}); });
} }
// Utility function to perform element-by-element addition // Utility function to perform element-by-element addition
function add(array, other) { function add(array, other) {
return array.map(function (v, i) { return array.map(function (v, i) {
return v + other[i]; return v + other[i];
}); });
} }
// Utility function to perform element-by-element max-choosing // Utility function to perform element-by-element max-choosing
function max(array, other) { function max(array, other) {
return array.map(function (v, i) { return array.map(function (v, i) {
return Math.max(v, other[i]); return Math.max(v, other[i]);
}); });
} }
/** /**
* Get a new position object in grid coordinates, with * Get a new position object in grid coordinates, with
* position and dimensions both offset appropriately * position and dimensions both offset appropriately
* according to the factors supplied in the constructor. * according to the factors supplied in the constructor.
* @param {number[]} pixelDelta the offset from the * @param {number[]} pixelDelta the offset from the
* original position, in pixels * original position, in pixels
*/ */
LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) { LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) {
const gridDelta = toGridDelta(this.gridSize, pixelDelta); const gridDelta = toGridDelta(this.gridSize, pixelDelta);
return { return {
position: max(add(this.rawPosition.position, multiply(gridDelta, this.posFactor)), [0, 0]), position: max(add(this.rawPosition.position, multiply(gridDelta, this.posFactor)), [0, 0]),
dimensions: max(add(this.rawPosition.dimensions, multiply(gridDelta, this.dimFactor)), [1, 1]) dimensions: max(add(this.rawPosition.dimensions, multiply(gridDelta, this.dimFactor)), [1, 1])
};
}; };
};
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) { LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
const gridDelta = toGridDelta(this.gridSize, pixelDelta); const gridDelta = toGridDelta(this.gridSize, pixelDelta);
return { return {
position: max(add(this.rawPosition.position, multiply(gridDelta, this.posFactor)), [0, 0]) position: max(add(this.rawPosition.position, multiply(gridDelta, this.posFactor)), [0, 0])
};
}; };
};
return LayoutDrag;
});

View File

@ -20,12 +20,12 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['./FiltersInspectorViewProvider'], function (FiltersInspectorViewProvider) { import FiltersInspectorViewProvider from './FiltersInspectorViewProvider';
return function plugin(supportedObjectTypesArray) {
return function install(openmct) { export default function plugin(supportedObjectTypesArray) {
openmct.inspectorViews.addProvider( return function install(openmct) {
new FiltersInspectorViewProvider.default(openmct, supportedObjectTypesArray) openmct.inspectorViews.addProvider(
); new FiltersInspectorViewProvider(openmct, supportedObjectTypesArray)
}; );
}; };
}); }

View File

@ -20,33 +20,31 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['./flexibleLayoutViewProvider', './utils/container', './toolbarProvider'], function ( import FlexibleLayoutViewProvider from './flexibleLayoutViewProvider';
FlexibleLayoutViewProvider, import ToolBarProvider from './toolbarProvider';
Container, import Container from './utils/container';
ToolBarProvider
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new FlexibleLayoutViewProvider.default(openmct));
openmct.types.addType('flexible-layout', { export default function plugin() {
name: 'Flexible Layout', return function install(openmct) {
creatable: true, openmct.objectViews.addProvider(new FlexibleLayoutViewProvider(openmct));
description:
'A fluid, flexible layout canvas that can display multiple objects in rows or columns.',
cssClass: 'icon-flexible-layout',
initialize: function (domainObject) {
domainObject.configuration = {
containers: [new Container.default(50), new Container.default(50)],
rowsLayout: false
};
domainObject.composition = [];
}
});
let toolbar = ToolBarProvider.default(openmct); openmct.types.addType('flexible-layout', {
name: 'Flexible Layout',
creatable: true,
description:
'A fluid, flexible layout canvas that can display multiple objects in rows or columns.',
cssClass: 'icon-flexible-layout',
initialize: function (domainObject) {
domainObject.configuration = {
containers: [new Container(50), new Container(50)],
rowsLayout: false
};
domainObject.composition = [];
}
});
openmct.toolbars.addProvider(toolbar); let toolbar = ToolBarProvider(openmct);
};
openmct.toolbars.addProvider(toolbar);
}; };
}); }

View File

@ -20,80 +20,78 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([], function () { const helperFunctions = {
const helperFunctions = { listenTo: function (object, event, callback, context) {
listenTo: function (object, event, callback, context) { if (!this._listeningTo) {
if (!this._listeningTo) { this._listeningTo = [];
this._listeningTo = [];
}
const listener = {
object: object,
event: event,
callback: callback,
context: context,
_cb: context ? callback.bind(context) : callback
};
if (object.$watch && event.indexOf('change:') === 0) {
const scopePath = event.replace('change:', '');
listener.unlisten = object.$watch(scopePath, listener._cb, true);
} else if (object.$on) {
listener.unlisten = object.$on(event, listener._cb);
} else if (object.addEventListener) {
object.addEventListener(event, listener._cb);
} else {
object.on(event, listener._cb);
}
this._listeningTo.push(listener);
},
stopListening: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
this._listeningTo
.filter(function (listener) {
if (object && object !== listener.object) {
return false;
}
if (event && event !== listener.event) {
return false;
}
if (callback && callback !== listener.callback) {
return false;
}
if (context && context !== listener.context) {
return false;
}
return true;
})
.map(function (listener) {
if (listener.unlisten) {
listener.unlisten();
} else if (listener.object.removeEventListener) {
listener.object.removeEventListener(listener.event, listener._cb);
} else {
listener.object.off(listener.event, listener._cb);
}
return listener;
})
.forEach(function (listener) {
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
}, this);
},
extend: function (object) {
object.listenTo = helperFunctions.listenTo;
object.stopListening = helperFunctions.stopListening;
} }
};
return helperFunctions; const listener = {
}); object: object,
event: event,
callback: callback,
context: context,
_cb: context ? callback.bind(context) : callback
};
if (object.$watch && event.indexOf('change:') === 0) {
const scopePath = event.replace('change:', '');
listener.unlisten = object.$watch(scopePath, listener._cb, true);
} else if (object.$on) {
listener.unlisten = object.$on(event, listener._cb);
} else if (object.addEventListener) {
object.addEventListener(event, listener._cb);
} else {
object.on(event, listener._cb);
}
this._listeningTo.push(listener);
},
stopListening: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
this._listeningTo
.filter(function (listener) {
if (object && object !== listener.object) {
return false;
}
if (event && event !== listener.event) {
return false;
}
if (callback && callback !== listener.callback) {
return false;
}
if (context && context !== listener.context) {
return false;
}
return true;
})
.map(function (listener) {
if (listener.unlisten) {
listener.unlisten();
} else if (listener.object.removeEventListener) {
listener.object.removeEventListener(listener.event, listener._cb);
} else {
listener.object.off(listener.event, listener._cb);
}
return listener;
})
.forEach(function (listener) {
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
}, this);
},
extend: function (object) {
object.listenTo = helperFunctions.listenTo;
object.stopListening = helperFunctions.stopListening;
}
};
export default helperFunctions;

View File

@ -20,24 +20,24 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['../../../src/plugins/utcTimeSystem/LocalClock'], function (LocalClock) { import LocalClock from '../../../src/plugins/utcTimeSystem/LocalClock';
class LADClock extends LocalClock {
/** /**
* A {@link Clock} that mocks a "latest available data" type tick source. * A {@link Clock} that mocks a "latest available data" type tick source.
* This is for testing purposes only, and behaves identically to a local clock. * This is for testing purposes only, and behaves identically to a local clock.
* It DOES NOT tick on receipt of data. * It DOES NOT tick on receipt of data.
* @constructor * @constructor
*/ */
function LADClock(period) { constructor(period) {
LocalClock.call(this, period); super(period);
this.key = 'test-lad'; this.key = 'test-lad';
this.mode = 'lad'; this.mode = 'lad';
this.cssClass = 'icon-suitcase'; this.cssClass = 'icon-suitcase';
this.name = 'Latest available data'; this.name = 'Latest available data';
this.description = 'Updates when when new data is available'; this.description = 'Updates when new data is available';
} }
}
LADClock.prototype = Object.create(LocalClock.prototype); export default LADClock;
return LADClock;
});

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