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'),
testUtils: path.join(projectRootDir, 'src/utils/testUtils.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: [

View File

@ -2,13 +2,16 @@
module.exports = {
extends: ['plugin:playwright/playwright-test'],
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: [
{
files: ['tests/visual/*.spec.js'],
files: ['tests/visual-a11y/*.spec.js'],
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?
- 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
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();
// 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'
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill('');
await nameInput.fill(name);
await page.getByLabel('Title', { exact: true }).fill(name);
if (page.testNotes) {
// Fill the "Notes" section with information about the

View File

@ -35,7 +35,7 @@
const fs = require('fs');
const path = require('path');
const { test, expect } = require('./pluginFixtures');
const { test, expect } = require('../pluginFixtures');
const AxeBuilder = require('@axe-core/playwright').default;
// 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
}
],
['junit', { outputFile: '../test-results/results.xml' }],
['@deploysentinel/playwright']
['junit', { outputFile: '../test-results/results.xml' }]
]
};

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.
*****************************************************************************/
const { test, expect } = require('../../pluginFixtures.js');
const { test, expect } = require('../../fixtures/pluginFixtures');
const {
createDomainObjectWithDefaults,
createNotification,

View File

@ -24,7 +24,7 @@
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('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.
*/
const { test, expect } = require('../../pluginFixtures');
const { createDomainObjectWithDefaults, expandEntireTree } = require('../../appActions');
const { test, expect } = require('../../../pluginFixtures');
const { createDomainObjectWithDefaults, expandEntireTree } = require('../../../appActions');
test.describe('Verify tooltips', () => {
let folder1;

View File

@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { createDomainObjectWithDefaults, waitForPlotsToRender } = require('../../appActions');
const { test, expect } = require('../../pluginFixtures');
const { createDomainObjectWithDefaults, waitForPlotsToRender } = require('../../../appActions');
const { test, expect } = require('../../../pluginFixtures');
test.describe('Tabs View', () => {
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.
*/
const { VISUAL_URL, MISSION_TIME } = require('../../constants');
const { test, expect } = require('../../pluginFixtures');
const { VISUAL_URL, MISSION_TIME } = require('../../../constants');
const { test, expect } = require('../../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
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' });
});
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,
theme
}) => {
// Create a clock domain object
await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'Default Clock'
type: 'Condition Widget',
name: 'Visual Condition Widget'
});
// Click on the div with role="alert" that has "Save successful" text
await page.locator('div[role="alert"]:has-text("Save successful")').click();

View File

@ -1,138 +1,134 @@
define(['lodash'], function (_) {
var METADATA_BY_TYPE = {
generator: {
values: [
{
key: 'name',
name: 'Name',
format: 'string'
},
{
key: 'utc',
name: 'Time',
format: 'utc',
hints: {
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
}
const METADATA_BY_TYPE = {
generator: {
values: [
{
key: 'name',
name: 'Name',
format: 'string'
},
{
key: 'utc',
name: 'Time',
format: 'utc',
hints: {
domain: 1
}
]
},
'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
}
},
{
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': {
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) {
return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type);
};
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type);
};
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return Object.assign({}, domainObject.telemetry, METADATA_BY_TYPE[domainObject.type]);
};
return GeneratorMetadataProvider;
});
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return Object.assign({}, domainObject.telemetry, METADATA_BY_TYPE[domainObject.type]);
};

View File

@ -20,86 +20,84 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./WorkerInterface'], function (WorkerInterface) {
var REQUEST_DEFAULTS = {
amplitude: 1,
period: 10,
offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0,
loadDelay: 0,
infinityValues: false,
exceedFloat32: false
};
import WorkerInterface from './WorkerInterface';
function GeneratorProvider(openmct, StalenessProvider) {
this.openmct = openmct;
this.workerInterface = new WorkerInterface(openmct, StalenessProvider);
}
const REQUEST_DEFAULTS = {
amplitude: 1,
period: 10,
offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0,
loadDelay: 0,
infinityValues: false,
exceedFloat32: false
};
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
return domainObject.type === 'generator';
};
export default function GeneratorProvider(openmct, StalenessProvider) {
this.openmct = openmct;
this.workerInterface = new WorkerInterface(openmct, StalenessProvider);
}
GeneratorProvider.prototype.supportsRequest = GeneratorProvider.prototype.supportsSubscribe =
GeneratorProvider.prototype.canProvideTelemetry;
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
return domainObject.type === 'generator';
};
GeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) {
var props = [
'amplitude',
'period',
'offset',
'dataRateInHz',
'randomness',
'phase',
'loadDelay',
'infinityValues',
'exceedFloat32'
];
GeneratorProvider.prototype.supportsRequest = GeneratorProvider.prototype.supportsSubscribe =
GeneratorProvider.prototype.canProvideTelemetry;
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) {
if (
domainObject.telemetry &&
Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)
) {
workerRequest[prop] = domainObject.telemetry[prop];
}
var workerRequest = {};
if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
workerRequest[prop] = request[prop];
}
props.forEach(function (prop) {
if (
domainObject.telemetry &&
Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)
) {
workerRequest[prop] = domainObject.telemetry[prop];
}
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
workerRequest[prop] = REQUEST_DEFAULTS[prop];
}
if (request && Object.prototype.hasOwnProperty.call(request, 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.name = domainObject.name;
workerRequest[prop] = Number(workerRequest[prop]);
});
return workerRequest;
};
workerRequest.id = this.openmct.objects.makeKeyString(domainObject.identifier);
workerRequest.name = domainObject.name;
GeneratorProvider.prototype.request = function (domainObject, request) {
var workerRequest = this.makeWorkerRequest(domainObject, request);
workerRequest.start = request.start;
workerRequest.end = request.end;
return workerRequest;
};
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) {
var workerRequest = this.makeWorkerRequest(domainObject, {});
return this.workerInterface.request(workerRequest);
};
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.
*****************************************************************************/
define([], function () {
var PURPLE = {
sin: 2.2,
cos: 2.2
var PURPLE = {
sin: 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 = {
sin: 0.9,
cos: 0.9
rl: {
cssClass: 'is-limit--lwr is-limit--red',
high: -RED,
low: Number.NEGATIVE_INFINITY,
name: 'Red Low'
},
ORANGE = {
sin: 0.7,
cos: 0.7
yh: {
cssClass: 'is-limit--upr is-limit--yellow',
low: YELLOW,
high: RED,
name: 'Yellow High'
},
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'
},
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';
yl: {
cssClass: 'is-limit--lwr is-limit--yellow',
low: -RED,
high: -YELLOW,
name: 'Yellow Low'
}
};
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
return {
evaluate: function (datum, valueMetadata) {
var range = valueMetadata && valueMetadata.key;
export default function SinewaveLimitProvider() {}
if (datum[range] > RED[range]) {
return LIMITS.rh;
}
SinewaveLimitProvider.prototype.supportsLimits = function (domainObject) {
return domainObject.type === 'generator';
};
if (datum[range] < -RED[range]) {
return LIMITS.rl;
}
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
return {
evaluate: function (datum, valueMetadata) {
var range = valueMetadata && valueMetadata.key;
if (datum[range] > YELLOW[range]) {
return LIMITS.yh;
}
if (datum[range] < -YELLOW[range]) {
return LIMITS.yl;
}
if (datum[range] > RED[range]) {
return LIMITS.rh;
}
};
};
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
return {
limits: function () {
return Promise.resolve({
WATCH: {
low: {
color: 'cyan',
sin: -CYAN.sin,
cos: -CYAN.cos
},
high: {
color: 'cyan',
...CYAN
}
if (datum[range] < -RED[range]) {
return LIMITS.rl;
}
if (datum[range] > YELLOW[range]) {
return LIMITS.yh;
}
if (datum[range] < -YELLOW[range]) {
return LIMITS.yl;
}
}
};
};
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
return {
limits: function () {
return Promise.resolve({
WATCH: {
low: {
color: 'cyan',
sin: -CYAN.sin,
cos: -CYAN.cos
},
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
}
high: {
color: 'cyan',
...CYAN
}
});
}
};
},
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.
*****************************************************************************/
define([], function () {
function StateGeneratorProvider() {}
export default function StateGeneratorProvider() {}
function pointForTimestamp(timestamp, duration, name) {
return {
name: name,
utc: Math.floor(timestamp / duration) * duration,
value: Math.floor(timestamp / duration) % 2
};
function pointForTimestamp(timestamp, duration, name) {
return {
name: name,
utc: Math.floor(timestamp / duration) * duration,
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) {
return domainObject.type === 'example.state-generator';
};
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, duration, domainObject.name));
start += duration;
}
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;
}
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, duration, domainObject.name));
start += duration;
}
return Promise.resolve(data);
};
return StateGeneratorProvider;
});
return Promise.resolve(data);
};

View File

@ -20,88 +20,86 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['uuid'], function ({ v4: 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 = {};
import { v4 as uuid } from 'uuid';
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.StalenessProvider.on('stalenessEvent', ({ id, isStale }) => {
this.staleTelemetryIds[id] = isStale;
});
};
this.worker.postMessage(message);
WorkerInterface.prototype.onMessage = function (message) {
message = message.data;
var callback = this.callbacks[message.id];
if (callback) {
callback(message);
}
};
return message.id;
};
WorkerInterface.prototype.dispatch = function (request, data, callback) {
var message = {
request: request,
data: data,
id: uuid()
};
WorkerInterface.prototype.request = function (request) {
var deferred = {};
var promise = new Promise(function (resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
var messageId;
if (callback) {
this.callbacks[message.id] = callback;
let self = this;
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) {
var deferred = {};
var promise = new Promise(function (resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
var messageId;
return promise;
};
let self = this;
function callback(message) {
if (message.error) {
deferred.reject(message.error);
} else {
deferred.resolve(message.data);
}
delete self.callbacks[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));
}
});
messageId = this.dispatch('request', request, callback.bind(this));
return promise;
};
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));
}
return function () {
this.dispatch('unsubscribe', {
id: messageId
});
return function () {
this.dispatch('unsubscribe', {
id: messageId
});
delete this.callbacks[messageId];
}.bind(this);
};
return WorkerInterface;
});
delete this.callbacks[messageId];
}.bind(this);
};

View File

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

View File

@ -32,7 +32,7 @@
"eslint-config-prettier": "9.0.0",
"eslint-plugin-compat": "4.2.0",
"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-simple-import-sort": "10.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: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: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: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",

View File

@ -20,72 +20,66 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* eslint-disable no-undef */
define([
'EventEmitter',
'./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 EventEmitter from 'EventEmitter';
import { createApp, markRaw } from 'vue';
/**
* The Open MCT application. This may be configured by installing plugins
* or registering extensions before the application is started.
* @constructor
* @memberof module:openmct
*/
function MCT() {
import ActionsAPI from './api/actions/ActionsAPI';
import AnnotationAPI from './api/annotation/AnnotationAPI';
import BrandingAPI from './api/Branding';
import CompositionAPI from './api/composition/CompositionAPI';
import EditorAPI from './api/Editor';
import FaultManagementAPI from './api/faultmanagement/FaultManagementAPI';
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);
this.buildInfo = {
version: __OPENMCT_VERSION__,
buildDate: __OPENMCT_BUILD_DATE__,
@ -95,169 +89,140 @@ define([
this.destroy = this.destroy.bind(this);
this.defaultClock = 'local';
[
/**
* Tracks current selection state of the application.
* @private
*/
['selection', () => new Selection.default(this)],
/**
* 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)],
this.plugins = plugins;
/**
* An interface for interacting with the composition of domain objects.
* The composition of a domain object is the list of other domain
* objects it "contains" (for instance, that should be displayed
* beneath it in the tree.)
*
* `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)],
/**
* Tracks current selection state of the application.
* @private
*/
this.selection = new Selection(this);
/**
* Registry for views of domain objects which should appear in the
* main viewing area.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name objectViews
*/
['objectViews', () => new ViewRegistry()],
/**
* 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
*/
this.time = new TimeAPI(this);
/**
* Registry for views which should appear in the Inspector area.
* These views will be chosen based on the selection state.
*
* @type {module:openmct.InspectorViewRegistry}
* @memberof module:openmct.MCT#
* @name inspectorViews
*/
['inspectorViews', () => new InspectorViewRegistry.default()],
/**
* An interface for interacting with the composition of domain objects.
* The composition of a domain object is the list of other domain
* objects it "contains" (for instance, that should be displayed
* beneath it in the tree.)
*
* `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
*/
this.composition = new CompositionAPI(this);
/**
* Registry for views which should appear in Edit Properties
* dialogs, and similar user interface elements used for
* modifying domain objects external to its regular views.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name propertyEditors
*/
['propertyEditors', () => new ViewRegistry()],
/**
* Registry for views of domain objects which should appear in the
* main viewing area.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name objectViews
*/
this.objectViews = new ViewRegistry();
/**
* Registry for views which should appear in the toolbar area while
* editing. These views will be chosen based on the selection state.
*
* @type {module:openmct.ToolbarRegistry}
* @memberof module:openmct.MCT#
* @name toolbars
*/
['toolbars', () => new ToolbarRegistry()],
/**
* Registry for views which should appear in the Inspector area.
* These views will be chosen based on the selection state.
*
* @type {module:openmct.InspectorViewRegistry}
* @memberof module:openmct.MCT#
* @name inspectorViews
*/
this.inspectorViews = new InspectorViewRegistry();
/**
* Registry for domain object types which may exist within this
* instance of Open MCT.
*
* @type {module:openmct.TypeRegistry}
* @memberof module:openmct.MCT#
* @name types
*/
['types', () => new api.TypeRegistry()],
/**
* Registry for views which should appear in Edit Properties
* dialogs, and similar user interface elements used for
* modifying domain objects external to its regular views.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name propertyEditors
*/
this.propertyEditors = new ViewRegistry();
/**
* An interface for interacting with domain objects and the domain
* object hierarchy.
*
* @type {module:openmct.ObjectAPI}
* @memberof module:openmct.MCT#
* @name objects
*/
['objects', () => new api.ObjectAPI.default(this.types, this)],
/**
* Registry for views which should appear in the toolbar area while
* editing. These views will be chosen based on the selection state.
*
* @type {module:openmct.ToolbarRegistry}
* @memberof module:openmct.MCT#
* @name toolbars
*/
this.toolbars = new ToolbarRegistry();
/**
* An interface for retrieving and interpreting telemetry data associated
* with a domain object.
*
* @type {module:openmct.TelemetryAPI}
* @memberof module:openmct.MCT#
* @name telemetry
*/
['telemetry', () => new api.TelemetryAPI.default(this)],
/**
* Registry for domain object types which may exist within this
* instance of Open MCT.
*
* @type {module:openmct.TypeRegistry}
* @memberof module:openmct.MCT#
* @name types
*/
this.types = new TypeRegistry();
/**
* An interface for creating new indicators and changing them dynamically.
*
* @type {module:openmct.IndicatorAPI}
* @memberof module:openmct.MCT#
* @name indicators
*/
['indicators', () => new api.IndicatorAPI(this)],
/**
* An interface for interacting with domain objects and the domain
* object hierarchy.
*
* @type {module:openmct.ObjectAPI}
* @memberof module:openmct.MCT#
* @name objects
*/
this.objects = new ObjectAPI(this.types, this);
/**
* MCT's user awareness management, to enable user and
* role specific functionality.
* @type {module:openmct.UserAPI}
* @memberof module:openmct.MCT#
* @name user
*/
['user', () => new api.UserAPI(this)],
/**
* An interface for retrieving and interpreting telemetry data associated
* with a domain object.
*
* @type {module:openmct.TelemetryAPI}
* @memberof module:openmct.MCT#
* @name telemetry
*/
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()],
['tooltips', () => new ToolTipAPI.default()],
['menus', () => new api.MenuAPI(this)],
['actions', () => new api.ActionsAPI(this)],
['status', () => new api.StatusAPI(this)],
['priority', () => api.PriorityAPI],
['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
});
});
this.notifications = new NotificationAPI();
this.editor = new EditorAPI(this);
this.overlays = new OverlayAPI();
this.tooltips = new ToolTipAPI();
this.menus = new MenuAPI(this);
this.actions = new ActionsAPI(this);
this.status = new StatusAPI(this);
this.priority = PriorityAPI;
this.router = new ApplicationRouter(this);
this.faults = new FaultManagementAPI(this);
this.forms = new FormsAPI(this);
this.branding = BrandingAPI;
/**
* MCT's annotation API that enables
@ -266,23 +231,23 @@ define([
* @memberof module:openmct.MCT#
* @name annotation
*/
this.annotation = new api.AnnotationAPI(this);
this.annotation = new AnnotationAPI(this);
// Plugins that are installed by default
this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable.default());
this.install(PreviewPlugin.default());
this.install(LicensesPlugin.default());
this.install(RemoveActionPlugin.default());
this.install(MoveActionPlugin.default());
this.install(LinkActionPlugin.default());
this.install(DuplicateActionPlugin.default());
this.install(ExportAsJSONAction.default());
this.install(ImportFromJSONAction.default());
this.install(this.plugins.FormActions.default());
this.install(this.plugins.TelemetryTable());
this.install(PreviewPlugin());
this.install(LicensesPlugin());
this.install(RemoveActionPlugin());
this.install(MoveActionPlugin());
this.install(LinkActionPlugin());
this.install(DuplicateActionPlugin());
this.install(ExportAsJSONAction());
this.install(ImportFromJSONAction());
this.install(this.plugins.FormActions());
this.install(this.plugins.FolderView());
this.install(this.plugins.Tabs());
this.install(ImageryPlugin.default());
this.install(ImageryPlugin());
this.install(this.plugins.FlexibleLayout());
this.install(this.plugins.GoToOriginalAction());
this.install(this.plugins.OpenInNewTabAction());
@ -300,26 +265,20 @@ define([
this.install(this.plugins.Gauge());
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.
* @memberof module:openmct.MCT#
* @method setAssetPath
*/
MCT.prototype.setAssetPath = function (assetPath) {
setAssetPath(assetPath) {
this._assetPath = assetPath;
};
}
/**
* Get path to where assets are hosted.
* @memberof module:openmct.MCT#
* @method getAssetPath
*/
MCT.prototype.getAssetPath = function () {
getAssetPath() {
const assetPathLength = this._assetPath && this._assetPath.length;
if (!assetPathLength) {
return '/';
@ -330,8 +289,7 @@ define([
}
return this._assetPath;
};
}
/**
* Start running Open MCT. This should be called only after any plugins
* have been installed.
@ -341,10 +299,7 @@ define([
* @param {HTMLElement} [domElement] the DOM element in which to run
* MCT; if undefined, MCT will be run in the body of the document
*/
MCT.prototype.start = function (
domElement = document.body.firstElementChild,
isHeadlessMode = false
) {
start(domElement = document.body.firstElementChild, isHeadlessMode = false) {
// Create element to mount Layout if it doesn't exist
if (domElement === null) {
domElement = document.createElement('div');
@ -376,20 +331,12 @@ define([
* @event start
* @memberof module:openmct.MCT~
*/
if (!isHeadlessMode) {
const appLayout = Vue.createApp({
components: {
Layout: Layout.default
},
provide: {
openmct: Vue.markRaw(this)
},
template: '<Layout ref="layout"></Layout>'
});
const appLayout = createApp(Layout);
appLayout.provide('openmct', markRaw(this));
const component = appLayout.mount(domElement);
component.$nextTick(() => {
this.layout = component.$refs.layout;
this.layout = component;
this.app = appLayout;
Browse(this);
window.addEventListener('beforeunload', this.destroy);
@ -402,14 +349,12 @@ define([
this.router.start();
this.emit('start');
}
};
MCT.prototype.startHeadless = function () {
}
startHeadless() {
let unreachableNode = document.createElement('div');
return this.start(unreachableNode, true);
};
}
/**
* Install a plugin in MCT.
*
@ -417,17 +362,13 @@ define([
* invoked with the mct instance.
* @memberof module:openmct.MCT#
*/
MCT.prototype.install = function (plugin) {
install(plugin) {
plugin(this);
};
}
MCT.prototype.destroy = function () {
destroy() {
window.removeEventListener('beforeunload', this.destroy);
this.emit('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.
*****************************************************************************/
define(['./plugins/plugins', 'utils/testing'], function (plugins, testUtils) {
describe('MCT', function () {
let openmct;
let mockPlugin;
let mockPlugin2;
let mockListener;
import * as testUtils from 'utils/testing';
beforeEach(function () {
mockPlugin = jasmine.createSpy('plugin');
mockPlugin2 = jasmine.createSpy('plugin2');
mockListener = jasmine.createSpy('listener');
import plugins from './plugins/plugins';
openmct = testUtils.createOpenMct();
describe('MCT', function () {
let openmct;
let mockPlugin;
let mockPlugin2;
let mockListener;
openmct.install(mockPlugin);
openmct.install(mockPlugin2);
openmct.on('start', mockListener);
beforeEach(function () {
mockPlugin = jasmine.createSpy('plugin');
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.
afterEach(function () {
return testUtils.resetApplicationState(openmct);
it('calls plugins for configuration', function () {
expect(mockPlugin).toHaveBeenCalledWith(openmct);
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
});
it('exposes plugins', function () {
expect(openmct.plugins).toEqual(plugins);
it('emits a start event', function () {
expect(mockListener).toHaveBeenCalled();
});
it('does not issue a start event before started', function () {
expect(mockListener).not.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 () {
beforeEach(function (done) {
openmct.on('start', done);
openmct.startHeadless();
});
describe('start', function () {
let appHolder;
beforeEach(function (done) {
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);
});
it('calls plugins for configuration', function () {
expect(mockPlugin).toHaveBeenCalledWith(openmct);
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
});
describe('startHeadless', function () {
beforeEach(function (done) {
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);
});
it('emits a start event', function () {
expect(mockListener).toHaveBeenCalled();
});
describe('setAssetPath', function () {
let testAssetPath;
it('Does not render Open MCT', function () {
let openMctShellElements = document.body.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(0);
});
});
it('configures the path for assets', function () {
testAssetPath = 'some/path/';
openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath);
});
describe('setAssetPath', function () {
let testAssetPath;
it('adds a trailing /', function () {
testAssetPath = 'some/path';
openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath + '/');
});
it('configures the path for assets', function () {
testAssetPath = 'some/path/';
openmct.setAssetPath(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.
*****************************************************************************/
define([
'./actions/ActionsAPI',
'./composition/CompositionAPI',
'./Editor',
'./faultmanagement/FaultManagementAPI',
'./forms/FormsAPI',
'./indicators/IndicatorAPI',
'./menu/MenuAPI',
'./notifications/NotificationAPI',
'./objects/ObjectAPI',
'./priority/PriorityAPI',
'./status/StatusAPI',
'./telemetry/TelemetryAPI',
'./time/TimeAPI',
'./types/TypeRegistry',
'./user/UserAPI',
'./annotation/AnnotationAPI'
], function (
import ActionsAPI from './actions/ActionsAPI';
import AnnotationAPI from './annotation/AnnotationAPI';
import CompositionAPI from './composition/CompositionAPI';
import EditorAPI from './Editor';
import FaultManagementAPI from './faultmanagement/FaultManagementAPI';
import FormsAPI from './forms/FormsAPI';
import IndicatorAPI from './indicators/IndicatorAPI';
import MenuAPI from './menu/MenuAPI';
import NotificationAPI from './notifications/NotificationAPI';
import ObjectAPI from './objects/ObjectAPI';
import PriorityAPI from './priority/PriorityAPI';
import StatusAPI from './status/StatusAPI';
import TelemetryAPI from './telemetry/TelemetryAPI';
import TimeAPI from './time/TimeAPI';
import TypeRegistry from './types/TypeRegistry';
import UserAPI from './user/UserAPI';
export default {
ActionsAPI,
CompositionAPI,
EditorAPI,
@ -54,23 +54,4 @@ define([
TypeRegistry,
UserAPI,
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
v-for="section in formSections"
:key="section.id"
:aria-labelledby="'sectionTitle'"
class="c-form__section"
: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 }}
</h2>
<FormRow

View File

@ -20,163 +20,161 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* Utility for checking if a thing is an Open MCT Identifier.
* @private
*/
function isIdentifier(thing) {
return (
typeof thing === 'object' &&
Object.prototype.hasOwnProperty.call(thing, 'key') &&
Object.prototype.hasOwnProperty.call(thing, 'namespace')
);
/**
* Utility for checking if a thing is an Open MCT Identifier.
* @private
*/
function isIdentifier(thing) {
return (
typeof thing === 'object' &&
Object.prototype.hasOwnProperty.call(thing, 'key') &&
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;
}
/**
* Utility for checking if a thing is a key string. Not perfect.
* @private
*/
function isKeyString(thing) {
return typeof thing === 'string';
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];
}
/**
* 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 = '';
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);
if (keyString === namespace) {
namespace = '';
}
return {
isIdentifier: isIdentifier,
toOldFormat: toOldFormat,
toNewFormat: toNewFormat,
makeKeyString: makeKeyString,
parseKeyString: parseKeyString,
equals: objectEquals,
identifierEquals: identifierEquals,
refresh: refresh
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);
}
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) {
describe('objectUtils', function () {
describe('keyString util', function () {
const EXPECTATIONS = {
ROOT: {
import objectUtils from 'objectUtils';
describe('objectUtils', function () {
describe('keyString util', function () {
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: '',
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'
key: 'objId'
}
};
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({
it('translates composition', function () {
expect(
objectUtils.toNewFormat(
{
prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
},
'objId'
)
).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',
identifier: {
namespace: '',
key: 'objId'
}
});
})
).toEqual({
prop: 'someValue'
});
});
it('translates composition', function () {
expect(
objectUtils.toNewFormat(
{
prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
},
'objId'
)
).toEqual({
it('translates composition', function () {
expect(
objectUtils.toOldFormat({
prop: 'someValue',
composition: [
{
@ -99,48 +137,10 @@ define(['objectUtils'], function (objectUtils) {
namespace: '',
key: 'objId'
}
});
});
});
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']
});
})
).toEqual({
prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
});
});
});

View File

@ -20,17 +20,19 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['lodash'], function (_) {
/**
* This is the default metadata provider; for any object with a "telemetry"
* 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
* depreciated in the future.
*/
function DefaultMetadataProvider(openmct) {
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
* telemetry metadata.
*
* This provider also implements legacy support for telemetry metadata
* defined on the type. Telemetry metadata definitions on type will be
* depreciated in the future.
*/
export default class DefaultMetadataProvider {
constructor(openmct) {
this.openmct = openmct;
}
@ -38,65 +40,14 @@ define(['lodash'], function (_) {
* Applies to any domain object with a telemetry property, or whose type
* definition has a telemetry property.
*/
DefaultMetadataProvider.prototype.supportsMetadata = function (domainObject) {
supportsMetadata(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.
*/
DefaultMetadataProvider.prototype.getMetadata = function (domainObject) {
getMetadata(domainObject) {
const metadata = domainObject.telemetry || {};
if (this.typeHasTelemetry(domainObject)) {
const typeMetadata = this.openmct.types.get(domainObject.type).definition.telemetry;
@ -109,16 +60,65 @@ define(['lodash'], function (_) {
}
return metadata;
};
}
/**
* @private
*/
DefaultMetadataProvider.prototype.typeHasTelemetry = function (domainObject) {
typeHasTelemetry(domainObject) {
const type = this.openmct.types.get(domainObject.type);
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.
*****************************************************************************/
define(['lodash'], function (_) {
function applyReasonableDefaults(valueMetadata, index) {
valueMetadata.source = valueMetadata.source || valueMetadata.key;
valueMetadata.hints = valueMetadata.hints || {};
import _ from 'lodash';
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
valueMetadata.hints.domain = valueMetadata.hints.x;
}
function applyReasonableDefaults(valueMetadata, index) {
valueMetadata.source = valueMetadata.source || valueMetadata.key;
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')) {
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;
delete valueMetadata.hints.x;
}
/**
* 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.
*/
function TelemetryMetadataManager(metadata) {
this.metadata = metadata;
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
valueMetadata.hints.range = valueMetadata.hints.y;
}
this.valueMetadatas = this.metadata.values
? this.metadata.values.map(applyReasonableDefaults)
: [];
delete valueMetadata.hints.y;
}
/**
* Get value metadata for a single key.
*/
TelemetryMetadataManager.prototype.value = function (key) {
return this.valueMetadatas.filter(function (metadata) {
return metadata.key === key;
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;
}
/**
* 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];
};
}
/**
* Returns all value metadatas, sorted by priority.
*/
TelemetryMetadataManager.prototype.values = function () {
return this.valuesForHints(['priority']);
};
if (valueMetadata === undefined) {
valueMetadata = this.values()[0];
}
/**
* 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;
});
return valueMetadata;
};

View File

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

View File

@ -19,15 +19,15 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./URLIndicator'], function URLIndicatorPlugin(URLIndicator) {
return function (opts) {
return function install(openmct) {
const simpleIndicator = openmct.indicators.simpleIndicator();
const urlIndicator = new URLIndicator(opts, simpleIndicator);
import URLIndicator from './URLIndicator';
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.
*****************************************************************************/
define(['utils/testing', './URLIndicator', './URLIndicatorPlugin', '../../MCT'], function (
testingUtils,
URLIndicator,
URLIndicatorPlugin,
MCT
) {
describe('The URLIndicator', function () {
let openmct;
let indicatorElement;
let pluginOptions;
let urlIndicator; // eslint-disable-line
let fetchSpy;
import * as testingUtils from 'utils/testing';
beforeEach(function () {
jasmine.clock().install();
openmct = new testingUtils.createOpenMct();
spyOn(openmct.indicators, 'add');
fetchSpy = spyOn(window, 'fetch').and.callFake(() =>
Promise.resolve({
ok: true
})
);
});
import URLIndicatorPlugin from './URLIndicatorPlugin';
afterEach(function () {
if (window.fetch.restore) {
window.fetch.restore();
}
describe('The URLIndicator', function () {
let openmct;
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 () {
describe('with default options', function () {
beforeEach(function () {
pluginOptions = {
url: 'someURL'
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
jasmine.clock().uninstall();
it('has a default icon class if none supplied', function () {
expect(indicatorElement.classList.contains('icon-chain-links')).toBe(true);
});
return testingUtils.resetApplicationState(openmct);
});
it('defaults to the URL if no label supplied', function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.url) >= 0).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('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 () {
describe('on initialization', function () {
describe('with default options', function () {
beforeEach(function () {
pluginOptions = {
url: 'someURL',
interval: 100
url: 'someURL'
};
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('has a default icon class if none supplied', function () {
expect(indicatorElement.classList.contains('icon-chain-links')).toBe(true);
});
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('defaults to the URL if no label supplied', function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.url) >= 0).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 () {
fetchSpy.and.callFake(() =>
Promise.resolve({
ok: false
})
);
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);
await urlIndicator.fetchUrl();
expect(indicatorElement.classList.contains('s-status-warning-hi')).toBe(true);
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 () {
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.
*****************************************************************************/
define([], function () {
/**
* Constant values used by the Autoflow Tabular View.
*/
return {
ROW_HEIGHT: 16,
SLIDER_HEIGHT: 10,
INITIAL_COLUMN_WIDTH: 225,
MAX_COLUMN_WIDTH: 525,
COLUMN_WIDTH_STEP: 25
};
});
/**
* Constant values used by the Autoflow Tabular View.
*/
export default {
ROW_HEIGHT: 16,
SLIDER_HEIGHT: 10,
INITIAL_COLUMN_WIDTH: 225,
MAX_COLUMN_WIDTH: 525,
COLUMN_WIDTH_STEP: 25
};

View File

@ -20,104 +20,102 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./AutoflowTabularRowController'], function (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;
import AutoflowTabularRowController from './AutoflowTabularRowController';
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.removeRow = this.removeRow.bind(this);
this.rows = {};
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.
* @param {String} value the value to display
* @private
*/
AutoflowTabularController.prototype.trackLastUpdated = function (value) {
this.data.updated = value;
};
/**
* 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(':');
/**
* 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]);
}
};
/**
* 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();
if (this.rows[id]) {
this.data.items = this.data.items.filter(
function (item) {
return item !== this.rows[id];
}.bind(this)
);
this.controllers = {};
this.composition.off('add', this.addRow);
this.composition.off('remove', this.removeRow);
};
this.controllers[id].destroy();
delete this.controllers[id];
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.
*****************************************************************************/
define(['./AutoflowTabularView'], function (AutoflowTabularView) {
return function (options) {
return function (openmct) {
const views = openmct.mainViews || openmct.objectViews;
import AutoflowTabularView from './AutoflowTabularView';
views.addProvider({
name: 'Autoflow Tabular',
key: 'autoflow',
cssClass: 'icon-packet',
description: 'A tabular view of packet contents.',
canView: function (d) {
return !options || options.type === d.type;
},
view: function (domainObject) {
return new AutoflowTabularView(domainObject, openmct, document);
}
});
};
export default function (options) {
return function (openmct) {
const views = openmct.mainViews || openmct.objectViews;
views.addProvider({
name: 'Autoflow Tabular',
key: 'autoflow',
cssClass: 'icon-packet',
description: 'A tabular view of packet contents.',
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.
*****************************************************************************/
define([], function () {
/**
* Controller for individual rows of an Autoflow Tabular View.
* Subscribes to telemetry and updates row data.
*
* @param {DomainObject} domainObject the object being viewed
* @param {*} data the view data
* @param openmct a reference to the openmct application
* @param {Function} callback a callback to invoke with "last updated" timestamps
*/
function AutoflowTabularRowController(domainObject, data, openmct, callback) {
this.domainObject = domainObject;
this.data = data;
this.openmct = openmct;
this.callback = callback;
/**
* Controller for individual rows of an Autoflow Tabular View.
* Subscribes to telemetry and updates row data.
*
* @param {DomainObject} domainObject the object being viewed
* @param {*} data the view data
* @param openmct a reference to the openmct application
* @param {Function} callback a callback to invoke with "last updated" timestamps
*/
export default function AutoflowTabularRowController(domainObject, data, openmct, callback) {
this.domainObject = domainObject;
this.data = data;
this.openmct = openmct;
this.callback = callback;
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.ranges = this.metadata.valuesForHints(['range']);
this.domains = this.metadata.valuesForHints(['domain']);
this.rangeFormatter = this.openmct.telemetry.getValueFormatter(this.ranges[0]);
this.domainFormatter = this.openmct.telemetry.getValueFormatter(this.domains[0]);
this.evaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.ranges = this.metadata.valuesForHints(['range']);
this.domains = this.metadata.valuesForHints(['domain']);
this.rangeFormatter = this.openmct.telemetry.getValueFormatter(this.ranges[0]);
this.domainFormatter = this.openmct.telemetry.getValueFormatter(this.domains[0]);
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.
*****************************************************************************/
define([
'./AutoflowTabularController',
'./AutoflowTabularConstants',
'./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;
import autoflowTemplate from './autoflow-tabular.html';
import AutoflowTabularConstants from './AutoflowTabularConstants';
import AutoflowTabularController from './AutoflowTabularController';
import VueView from './VueView';
/**
* Implements the Autoflow Tabular view of a domain object.
*/
function AutoflowTabularView(domainObject, openmct) {
const data = {
items: [],
columns: [],
width: INITIAL_COLUMN_WIDTH,
filter: '',
updated: 'No updates',
rowCount: 1
};
const controller = new AutoflowTabularController(domainObject, data, openmct);
let interval;
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;
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;
});
/**
* Implements the Autoflow Tabular view of a domain object.
*/
export default function AutoflowTabularView(domainObject, openmct) {
const data = {
items: [],
columns: [],
width: INITIAL_COLUMN_WIDTH,
filter: '',
updated: 'No updates',
rowCount: 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) {
if (column.length >= data.rowCount) {
data.columns.push(column);
column = [];
}
data.columns = [];
column.push(filteredItems[index]);
index += 1;
}
if (column.length > 0) {
while (index < filteredItems.length) {
if (column.length >= data.rowCount) {
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) {
clearInterval(interval);
interval = undefined;
if (column.length > 0) {
data.columns.push(column);
}
},
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';
export default function () {
return function VueView(options) {
const { vNode, destroy } = mount(options);
class VueView {
constructor(options) {
const { vNode, destroy } = mount(options);
this.show = function (container) {
container.appendChild(vNode.el);
};
this.destroy = destroy;
}
}
this.show = function (container) {
container.appendChild(vNode.el);
};
this.destroy = destroy;
};
return VueView;
}

View File

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

View File

@ -20,53 +20,49 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(function () {
function DisplayLayoutType() {
return {
name: 'Display Layout',
creatable: true,
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.',
cssClass: 'icon-layout',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
items: [],
layoutGrid: [10, 10]
};
export default function DisplayLayoutType() {
return {
name: 'Display Layout',
creatable: true,
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.',
cssClass: 'icon-layout',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
items: [],
layoutGrid: [10, 10]
};
},
form: [
{
name: 'Horizontal grid (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutGrid', 0],
required: true
},
form: [
{
name: 'Horizontal grid (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutGrid', 0],
required: true
},
{
name: 'Vertical grid (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutGrid', 1],
required: true
},
{
name: 'Horizontal size (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutDimensions', 0],
required: false
},
{
name: 'Vertical size (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutDimensions', 1],
required: false
}
]
};
}
return DisplayLayoutType;
});
{
name: 'Vertical grid (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutGrid', 1],
required: true
},
{
name: 'Horizontal size (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutDimensions', 0],
required: false
},
{
name: 'Vertical size (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutDimensions', 1],
required: false
}
]
};
}

View File

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

View File

@ -20,33 +20,31 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./flexibleLayoutViewProvider', './utils/container', './toolbarProvider'], function (
FlexibleLayoutViewProvider,
Container,
ToolBarProvider
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new FlexibleLayoutViewProvider.default(openmct));
import FlexibleLayoutViewProvider from './flexibleLayoutViewProvider';
import ToolBarProvider from './toolbarProvider';
import Container from './utils/container';
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.default(50), new Container.default(50)],
rowsLayout: false
};
domainObject.composition = [];
}
});
export default function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new FlexibleLayoutViewProvider(openmct));
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.
*****************************************************************************/
define([], function () {
const helperFunctions = {
listenTo: function (object, event, callback, context) {
if (!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;
const helperFunctions = {
listenTo: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
};
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.
*****************************************************************************/
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.
* This is for testing purposes only, and behaves identically to a local clock.
* It DOES NOT tick on receipt of data.
* @constructor
*/
function LADClock(period) {
LocalClock.call(this, period);
constructor(period) {
super(period);
this.key = 'test-lad';
this.mode = 'lad';
this.cssClass = 'icon-suitcase';
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);
return LADClock;
});
export default LADClock;

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