mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
41 Commits
sim3
...
omm-large-
Author | SHA1 | Date | |
---|---|---|---|
fe9f740611 | |||
798eb4444d | |||
59ae7cddc5 | |||
321c7a3af5 | |||
609cf72bd1 | |||
a447b0ada8 | |||
5788f4cc69 | |||
f94b4e53c7 | |||
faf71f1e67 | |||
23310f85ae | |||
d80819634b | |||
483b62c152 | |||
1254279635 | |||
c768a71656 | |||
678a92bd29 | |||
34b488944a | |||
4d1dd2f51d | |||
080f7b8f4b | |||
483f2feac8 | |||
7ec2c4475b | |||
8f59b16465 | |||
36cfb1d515 | |||
2ff7132e90 | |||
d0ca398e01 | |||
59278e8a06 | |||
c8377f392b | |||
29df748f2b | |||
665ba6dae1 | |||
f39f8df4e2 | |||
4aa572d489 | |||
0b24c4f2c5 | |||
e4657f79cd | |||
f2059406e0 | |||
3e3dc7dd83 | |||
50742c4f82 | |||
2f04add2a3 | |||
0ce5060246 | |||
00353cdccf | |||
a1ac209d74 | |||
bdd8477b54 | |||
f690f36bfb |
@ -27,26 +27,29 @@ test.describe('Testing LAD table configuration', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Sine Wave Generator
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
name: "Test Sine Wave Generator"
|
||||
});
|
||||
|
||||
// Create LAD table
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
const ladTable = await createDomainObjectWithDefaults(page, {
|
||||
type: 'LAD Table',
|
||||
name: "Test LAD Table"
|
||||
});
|
||||
|
||||
// Create Sine Wave Generator
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
name: "Test Sine Wave Generator",
|
||||
parent: ladTable.uuid
|
||||
});
|
||||
|
||||
await page.goto(ladTable.url);
|
||||
});
|
||||
test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => {
|
||||
// Edit LAD table
|
||||
await page.locator('[title="Edit"]').click();
|
||||
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// Add the Sine Wave Generator to the LAD table and save changes
|
||||
await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
|
||||
// // Expand the 'My Items' folder in the left tree
|
||||
// await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// // Add the Sine Wave Generator to the LAD table and save changes
|
||||
// await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
|
||||
// select configuration tab in inspector
|
||||
await selectInspectorTab(page, 'LAD Table Configuration');
|
||||
|
||||
@ -113,6 +116,24 @@ test.describe('Testing LAD table configuration', () => {
|
||||
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
|
||||
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('LAD Tables don\'t allow selection of rows but does show context click menus', async ({ page }) => {
|
||||
const cell = await page.locator('.js-first-data');
|
||||
const userSelectable = await cell.evaluate((el) => {
|
||||
return window.getComputedStyle(el).getPropertyValue('user-select');
|
||||
});
|
||||
|
||||
expect(userSelectable).toBe('none');
|
||||
// Right-click on the LAD table row
|
||||
await cell.click({
|
||||
button: 'right'
|
||||
});
|
||||
const menuOptions = page.locator('.c-menu ul');
|
||||
await expect.soft(menuOptions).toContainText('View Full Datum');
|
||||
await expect.soft(menuOptions).toContainText('View Historical Data');
|
||||
await expect.soft(menuOptions).toContainText('Remove');
|
||||
// await page.locator('li[title="Remove this object from its containing object."]').click();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Testing LAD table @unstable', () => {
|
||||
|
@ -268,6 +268,9 @@ async function getCanvasPixelsWithData(page) {
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function assertLimitLinesExistAndAreVisible(page) {
|
||||
// Wait for plot series data to load
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
// Wait for limit lines to be created
|
||||
await page.waitForSelector('.js-limit-area', { state: 'attached' });
|
||||
const limitLineCount = await page.locator('.c-plot-limit-line').count();
|
||||
// There should be 10 limit lines created by default
|
||||
|
@ -28,6 +28,14 @@ const { test, expect } = require('../../../../pluginFixtures');
|
||||
const { createDomainObjectWithDefaults, setRealTimeMode, setFixedTimeMode } = require('../../../../appActions');
|
||||
|
||||
test.describe('Plot Tagging', () => {
|
||||
/**
|
||||
* Given a canvas and a set of points, tags the points on the canvas.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {HTMLCanvasElement} canvas a telemetry item with a plot
|
||||
* @param {Number} xEnd a telemetry item with a plot
|
||||
* @param {Number} yEnd a telemetry item with a plot
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function createTags({page, canvas, xEnd, yEnd}) {
|
||||
await canvas.hover({trial: true});
|
||||
|
||||
@ -64,12 +72,20 @@ test.describe('Plot Tagging', () => {
|
||||
await page.getByText('Science').click();
|
||||
}
|
||||
|
||||
async function testTelemetryItem(page, canvas, telemetryItem) {
|
||||
/**
|
||||
* Given a telemetry item (e.g., a Sine Wave Generator) with a plot, tests that the plot can be tagged.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {import('../../../../appActions').CreatedObjectInfo} telemetryItem a telemetry item with a plot
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function testTelemetryItem(page, telemetryItem) {
|
||||
// Check that telemetry item also received the tag
|
||||
await page.goto(telemetryItem.url);
|
||||
|
||||
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||
|
||||
const canvas = page.locator('canvas').nth(1);
|
||||
|
||||
//Wait for canvas to stablize.
|
||||
await canvas.hover({trial: true});
|
||||
|
||||
@ -85,19 +101,31 @@ test.describe('Plot Tagging', () => {
|
||||
await expect(page.getByText('Driving')).toBeHidden();
|
||||
}
|
||||
|
||||
async function basicTagsTests(page, canvas) {
|
||||
// Search for Science
|
||||
/**
|
||||
* Given a page, tests that tags are searchable, deletable, and persist across reloads.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function basicTagsTests(page) {
|
||||
// Search for Driving
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||
await expect(page.locator('[aria-label="Search Result"]').nth(0)).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Search Result"]').nth(0)).not.toContainText("Drilling");
|
||||
|
||||
// Clicking elsewhere should cause annotation selection to be cleared
|
||||
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('driv');
|
||||
// click on the search result
|
||||
await page.getByRole('searchbox', { name: 'OpenMCT Search' }).getByText(/Sine Wave/).first().click();
|
||||
|
||||
// Delete Driving
|
||||
await page.hover('[aria-label="Tag"]:has-text("Driving")');
|
||||
await page.locator('[aria-label="Remove tag Driving"]').click();
|
||||
|
||||
await expect(page.locator('[aria-label="Tags Inspector"]')).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Tags Inspector"]')).not.toContainText("Driving");
|
||||
// Search for Science
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||
await expect(page.locator('[aria-label="Search Result"]').nth(0)).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Search Result"]').nth(0)).not.toContainText("Drilling");
|
||||
|
||||
// Search for Driving
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
@ -109,12 +137,13 @@ test.describe('Plot Tagging', () => {
|
||||
page.reload(),
|
||||
page.waitForLoadState('networkidle')
|
||||
]);
|
||||
// wait for plot progress bar to disappear
|
||||
await page.locator('.l-view-section.c-progress-bar').waitFor({ state: 'detached' });
|
||||
// wait for plots to load
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
|
||||
await page.getByText('Annotations').click();
|
||||
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||
|
||||
const canvas = page.locator('canvas').nth(1);
|
||||
// click on the tagged plot point
|
||||
await canvas.click({
|
||||
position: {
|
||||
@ -171,8 +200,8 @@ test.describe('Plot Tagging', () => {
|
||||
// changing to fixed time mode rebuilds canvas?
|
||||
canvas = page.locator('canvas').nth(1);
|
||||
|
||||
await basicTagsTests(page, canvas);
|
||||
await testTelemetryItem(page, canvas, alphaSineWave);
|
||||
await basicTagsTests(page);
|
||||
await testTelemetryItem(page, alphaSineWave);
|
||||
|
||||
// set to real time mode
|
||||
await setRealTimeMode(page);
|
||||
@ -182,8 +211,8 @@ test.describe('Plot Tagging', () => {
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||
// click on the search result
|
||||
await page.getByRole('searchbox', { name: 'OpenMCT Search' }).getByText('Alpha Sine Wave').first().click();
|
||||
// wait for plot progress bar to disappear
|
||||
await page.locator('.l-view-section.c-progress-bar').waitFor({ state: 'detached' });
|
||||
// wait for plots to load
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
// expect plot to be paused
|
||||
await expect(page.locator('[title="Resume displaying real-time data"]')).toBeVisible();
|
||||
|
||||
@ -202,7 +231,7 @@ test.describe('Plot Tagging', () => {
|
||||
xEnd: 700,
|
||||
yEnd: 480
|
||||
});
|
||||
await basicTagsTests(page, canvas);
|
||||
await basicTagsTests(page);
|
||||
});
|
||||
|
||||
test('Tags work with Stacked Plots', async ({ page }) => {
|
||||
@ -232,7 +261,7 @@ test.describe('Plot Tagging', () => {
|
||||
xEnd: 700,
|
||||
yEnd: 215
|
||||
});
|
||||
await basicTagsTests(page, canvas);
|
||||
await testTelemetryItem(page, canvas, alphaSineWave);
|
||||
await basicTagsTests(page);
|
||||
await testTelemetryItem(page, alphaSineWave);
|
||||
});
|
||||
});
|
||||
|
@ -191,7 +191,7 @@ test.describe('Recent Objects', () => {
|
||||
expect(await clockBreadcrumbs.count()).toBe(2);
|
||||
expect(await clockBreadcrumbs.nth(0).innerText()).not.toEqual(await clockBreadcrumbs.nth(1).innerText());
|
||||
});
|
||||
test("Enforces a limit of 20 recent objects", async ({ page }) => {
|
||||
test("Enforces a limit of 20 recent objects and clears the recent objects", async ({ page }) => {
|
||||
// Creating 21 objects takes a while, so increase the timeout
|
||||
test.slow();
|
||||
|
||||
@ -242,6 +242,15 @@ test.describe('Recent Objects', () => {
|
||||
|
||||
// Assert that the Clock treeitem is no longer highlighted
|
||||
await expect(lastClockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
|
||||
|
||||
// Click the aria-label="Clear Recently Viewed" button
|
||||
await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
|
||||
|
||||
// Click on the "OK" button in the confirmation dialog
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
// Assert that the list is empty
|
||||
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
|
||||
});
|
||||
|
||||
function assertInitialRecentObjectsListState() {
|
||||
|
@ -1,150 +1,138 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
]
|
||||
define(['lodash'], function (_) {
|
||||
var METADATA_BY_TYPE = {
|
||||
generator: {
|
||||
values: [
|
||||
{
|
||||
key: 'name',
|
||||
name: 'Name',
|
||||
format: 'string'
|
||||
},
|
||||
'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: '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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function GeneratorMetadataProvider() {
|
||||
|
||||
]
|
||||
},
|
||||
'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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
|
||||
return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type);
|
||||
};
|
||||
function GeneratorMetadataProvider() {}
|
||||
|
||||
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
|
||||
return Object.assign(
|
||||
{},
|
||||
domainObject.telemetry,
|
||||
METADATA_BY_TYPE[domainObject.type]
|
||||
);
|
||||
};
|
||||
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
|
||||
return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type);
|
||||
};
|
||||
|
||||
return GeneratorMetadataProvider;
|
||||
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
|
||||
return Object.assign({}, domainObject.telemetry, METADATA_BY_TYPE[domainObject.type]);
|
||||
};
|
||||
|
||||
return GeneratorMetadataProvider;
|
||||
});
|
||||
|
@ -20,87 +20,86 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./WorkerInterface'
|
||||
], function (
|
||||
WorkerInterface
|
||||
) {
|
||||
define(['./WorkerInterface'], function (WorkerInterface) {
|
||||
var REQUEST_DEFAULTS = {
|
||||
amplitude: 1,
|
||||
period: 10,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
randomness: 0,
|
||||
phase: 0,
|
||||
loadDelay: 0,
|
||||
infinityValues: false,
|
||||
veryLargeValues: false
|
||||
};
|
||||
|
||||
var REQUEST_DEFAULTS = {
|
||||
amplitude: 1,
|
||||
period: 10,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
randomness: 0,
|
||||
phase: 0,
|
||||
loadDelay: 0,
|
||||
infinityValues: false
|
||||
};
|
||||
function GeneratorProvider(openmct, StalenessProvider) {
|
||||
this.openmct = openmct;
|
||||
this.workerInterface = new WorkerInterface(openmct, StalenessProvider);
|
||||
}
|
||||
|
||||
function GeneratorProvider(openmct, StalenessProvider) {
|
||||
this.openmct = openmct;
|
||||
this.workerInterface = new WorkerInterface(openmct, StalenessProvider);
|
||||
}
|
||||
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
|
||||
return domainObject.type === 'generator';
|
||||
};
|
||||
|
||||
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
|
||||
return domainObject.type === 'generator';
|
||||
};
|
||||
GeneratorProvider.prototype.supportsRequest = GeneratorProvider.prototype.supportsSubscribe =
|
||||
GeneratorProvider.prototype.canProvideTelemetry;
|
||||
|
||||
GeneratorProvider.prototype.supportsRequest =
|
||||
GeneratorProvider.prototype.supportsSubscribe =
|
||||
GeneratorProvider.prototype.canProvideTelemetry;
|
||||
GeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) {
|
||||
var props = [
|
||||
'amplitude',
|
||||
'period',
|
||||
'offset',
|
||||
'dataRateInHz',
|
||||
'randomness',
|
||||
'phase',
|
||||
'loadDelay',
|
||||
'infinityValues',
|
||||
'veryLargeValues'
|
||||
];
|
||||
|
||||
GeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) {
|
||||
var props = [
|
||||
'amplitude',
|
||||
'period',
|
||||
'offset',
|
||||
'dataRateInHz',
|
||||
'randomness',
|
||||
'phase',
|
||||
'loadDelay',
|
||||
'infinityValues'
|
||||
];
|
||||
request = request || {};
|
||||
|
||||
request = request || {};
|
||||
var workerRequest = {};
|
||||
|
||||
var workerRequest = {};
|
||||
props.forEach(function (prop) {
|
||||
if (
|
||||
domainObject.telemetry &&
|
||||
Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)
|
||||
) {
|
||||
workerRequest[prop] = domainObject.telemetry[prop];
|
||||
}
|
||||
|
||||
props.forEach(function (prop) {
|
||||
if (domainObject.telemetry && Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)) {
|
||||
workerRequest[prop] = domainObject.telemetry[prop];
|
||||
}
|
||||
if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
|
||||
workerRequest[prop] = request[prop];
|
||||
}
|
||||
|
||||
if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
|
||||
workerRequest[prop] = request[prop];
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
|
||||
workerRequest[prop] = REQUEST_DEFAULTS[prop];
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
|
||||
workerRequest[prop] = REQUEST_DEFAULTS[prop];
|
||||
}
|
||||
workerRequest[prop] = Number(workerRequest[prop]);
|
||||
});
|
||||
|
||||
workerRequest[prop] = Number(workerRequest[prop]);
|
||||
});
|
||||
workerRequest.id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
workerRequest.name = domainObject.name;
|
||||
|
||||
workerRequest.id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
workerRequest.name = domainObject.name;
|
||||
return workerRequest;
|
||||
};
|
||||
|
||||
return workerRequest;
|
||||
};
|
||||
GeneratorProvider.prototype.request = function (domainObject, request) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, request);
|
||||
workerRequest.start = request.start;
|
||||
workerRequest.end = request.end;
|
||||
|
||||
GeneratorProvider.prototype.request = function (domainObject, request) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, request);
|
||||
workerRequest.start = request.start;
|
||||
workerRequest.end = request.end;
|
||||
return this.workerInterface.request(workerRequest);
|
||||
};
|
||||
|
||||
return this.workerInterface.request(workerRequest);
|
||||
};
|
||||
GeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, {});
|
||||
|
||||
GeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, {});
|
||||
return this.workerInterface.subscribe(workerRequest, callback);
|
||||
};
|
||||
|
||||
return this.workerInterface.subscribe(workerRequest, callback);
|
||||
};
|
||||
|
||||
return GeneratorProvider;
|
||||
return GeneratorProvider;
|
||||
});
|
||||
|
@ -20,155 +20,147 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
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"
|
||||
},
|
||||
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';
|
||||
define([], function () {
|
||||
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'
|
||||
},
|
||||
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'
|
||||
}
|
||||
};
|
||||
|
||||
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
|
||||
return {
|
||||
evaluate: function (datum, valueMetadata) {
|
||||
var range = valueMetadata && valueMetadata.key;
|
||||
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] > RED[range]) {
|
||||
return LIMITS.rh;
|
||||
}
|
||||
|
||||
if (datum[range] < -YELLOW[range]) {
|
||||
return LIMITS.yl;
|
||||
}
|
||||
}
|
||||
};
|
||||
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
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
});
|
||||
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
|
||||
}
|
||||
};
|
||||
},
|
||||
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;
|
||||
return SinewaveLimitProvider;
|
||||
});
|
||||
|
@ -23,135 +23,135 @@
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export default class SinewaveLimitProvider extends EventEmitter {
|
||||
#openmct;
|
||||
#observingStaleness;
|
||||
#watchingTheClock;
|
||||
#isRealTime;
|
||||
#openmct;
|
||||
#observingStaleness;
|
||||
#watchingTheClock;
|
||||
#isRealTime;
|
||||
|
||||
constructor(openmct) {
|
||||
super();
|
||||
constructor(openmct) {
|
||||
super();
|
||||
|
||||
this.#openmct = openmct;
|
||||
this.#observingStaleness = {};
|
||||
this.#watchingTheClock = false;
|
||||
this.#isRealTime = undefined;
|
||||
this.#openmct = openmct;
|
||||
this.#observingStaleness = {};
|
||||
this.#watchingTheClock = false;
|
||||
this.#isRealTime = undefined;
|
||||
}
|
||||
|
||||
supportsStaleness(domainObject) {
|
||||
return domainObject.type === 'generator';
|
||||
}
|
||||
|
||||
isStale(domainObject, options) {
|
||||
if (!this.#providingStaleness(domainObject)) {
|
||||
return;
|
||||
}
|
||||
|
||||
supportsStaleness(domainObject) {
|
||||
return domainObject.type === 'generator';
|
||||
const id = this.#getObjectKeyString(domainObject);
|
||||
|
||||
if (!this.#observerExists(id)) {
|
||||
this.#createObserver(id);
|
||||
}
|
||||
|
||||
isStale(domainObject, options) {
|
||||
if (!this.#providingStaleness(domainObject)) {
|
||||
return;
|
||||
}
|
||||
return Promise.resolve({
|
||||
isStale: this.#observingStaleness[id].isStale,
|
||||
utc: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
const id = this.#getObjectKeyString(domainObject);
|
||||
subscribeToStaleness(domainObject, callback) {
|
||||
const id = this.#getObjectKeyString(domainObject);
|
||||
|
||||
if (!this.#observerExists(id)) {
|
||||
this.#createObserver(id);
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
isStale: this.#observingStaleness[id].isStale,
|
||||
utc: Date.now()
|
||||
});
|
||||
if (this.#isRealTime === undefined) {
|
||||
this.#updateRealTime(this.#openmct.time.getMode());
|
||||
}
|
||||
|
||||
subscribeToStaleness(domainObject, callback) {
|
||||
const id = this.#getObjectKeyString(domainObject);
|
||||
this.#handleClockUpdate();
|
||||
|
||||
if (this.#isRealTime === undefined) {
|
||||
this.#updateRealTime(this.#openmct.time.clock());
|
||||
}
|
||||
|
||||
this.#handleClockUpdate();
|
||||
|
||||
if (this.#observerExists(id)) {
|
||||
this.#addCallbackToObserver(id, callback);
|
||||
} else {
|
||||
this.#createObserver(id, callback);
|
||||
}
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
if (this.#providingStaleness(domainObject)) {
|
||||
this.#updateStaleness(id, !this.#observingStaleness[id].isStale);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
this.#updateStaleness(id, false);
|
||||
this.#handleClockUpdate();
|
||||
this.#destroyObserver(id);
|
||||
};
|
||||
if (this.#observerExists(id)) {
|
||||
this.#addCallbackToObserver(id, callback);
|
||||
} else {
|
||||
this.#createObserver(id, callback);
|
||||
}
|
||||
|
||||
#handleClockUpdate() {
|
||||
let observers = Object.values(this.#observingStaleness).length > 0;
|
||||
const intervalId = setInterval(() => {
|
||||
if (this.#providingStaleness(domainObject)) {
|
||||
this.#updateStaleness(id, !this.#observingStaleness[id].isStale);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
if (observers && !this.#watchingTheClock) {
|
||||
this.#watchingTheClock = true;
|
||||
this.#openmct.time.on('clock', this.#updateRealTime, this);
|
||||
} else if (!observers && this.#watchingTheClock) {
|
||||
this.#watchingTheClock = false;
|
||||
this.#openmct.time.off('clock', this.#updateRealTime, this);
|
||||
}
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
this.#updateStaleness(id, false);
|
||||
this.#handleClockUpdate();
|
||||
this.#destroyObserver(id);
|
||||
};
|
||||
}
|
||||
|
||||
#handleClockUpdate() {
|
||||
let observers = Object.values(this.#observingStaleness).length > 0;
|
||||
|
||||
if (observers && !this.#watchingTheClock) {
|
||||
this.#watchingTheClock = true;
|
||||
this.#openmct.time.on('modeChanged', this.#updateRealTime, this);
|
||||
} else if (!observers && this.#watchingTheClock) {
|
||||
this.#watchingTheClock = false;
|
||||
this.#openmct.time.off('modeChanged', this.#updateRealTime, this);
|
||||
}
|
||||
}
|
||||
|
||||
#updateRealTime(clock) {
|
||||
this.#isRealTime = clock !== undefined;
|
||||
#updateRealTime(mode) {
|
||||
this.#isRealTime = mode !== 'fixed';
|
||||
|
||||
if (!this.#isRealTime) {
|
||||
Object.keys(this.#observingStaleness).forEach((id) => {
|
||||
this.#updateStaleness(id, false);
|
||||
});
|
||||
}
|
||||
if (!this.#isRealTime) {
|
||||
Object.keys(this.#observingStaleness).forEach((id) => {
|
||||
this.#updateStaleness(id, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#updateStaleness(id, isStale) {
|
||||
this.#observingStaleness[id].isStale = isStale;
|
||||
this.#observingStaleness[id].utc = Date.now();
|
||||
this.#observingStaleness[id].callback({
|
||||
isStale: this.#observingStaleness[id].isStale,
|
||||
utc: this.#observingStaleness[id].utc
|
||||
});
|
||||
this.emit('stalenessEvent', {
|
||||
id,
|
||||
isStale: this.#observingStaleness[id].isStale
|
||||
});
|
||||
#updateStaleness(id, isStale) {
|
||||
this.#observingStaleness[id].isStale = isStale;
|
||||
this.#observingStaleness[id].utc = Date.now();
|
||||
this.#observingStaleness[id].callback({
|
||||
isStale: this.#observingStaleness[id].isStale,
|
||||
utc: this.#observingStaleness[id].utc
|
||||
});
|
||||
this.emit('stalenessEvent', {
|
||||
id,
|
||||
isStale: this.#observingStaleness[id].isStale
|
||||
});
|
||||
}
|
||||
|
||||
#createObserver(id, callback) {
|
||||
this.#observingStaleness[id] = {
|
||||
isStale: false,
|
||||
utc: Date.now()
|
||||
};
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
this.#addCallbackToObserver(id, callback);
|
||||
}
|
||||
}
|
||||
|
||||
#createObserver(id, callback) {
|
||||
this.#observingStaleness[id] = {
|
||||
isStale: false,
|
||||
utc: Date.now()
|
||||
};
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
this.#addCallbackToObserver(id, callback);
|
||||
}
|
||||
#destroyObserver(id) {
|
||||
if (this.#observingStaleness[id]) {
|
||||
delete this.#observingStaleness[id];
|
||||
}
|
||||
}
|
||||
|
||||
#destroyObserver(id) {
|
||||
if (this.#observingStaleness[id]) {
|
||||
delete this.#observingStaleness[id];
|
||||
}
|
||||
}
|
||||
#providingStaleness(domainObject) {
|
||||
return domainObject.telemetry?.staleness === true && this.#isRealTime;
|
||||
}
|
||||
|
||||
#providingStaleness(domainObject) {
|
||||
return domainObject.telemetry?.staleness === true && this.#isRealTime;
|
||||
}
|
||||
#getObjectKeyString(object) {
|
||||
return this.#openmct.objects.makeKeyString(object.identifier);
|
||||
}
|
||||
|
||||
#getObjectKeyString(object) {
|
||||
return this.#openmct.objects.makeKeyString(object.identifier);
|
||||
}
|
||||
#addCallbackToObserver(id, callback) {
|
||||
this.#observingStaleness[id].callback = callback;
|
||||
}
|
||||
|
||||
#addCallbackToObserver(id, callback) {
|
||||
this.#observingStaleness[id].callback = callback;
|
||||
}
|
||||
|
||||
#observerExists(id) {
|
||||
return this.#observingStaleness?.[id];
|
||||
}
|
||||
#observerExists(id) {
|
||||
return this.#observingStaleness?.[id];
|
||||
}
|
||||
}
|
||||
|
@ -20,64 +20,56 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
define([], function () {
|
||||
function StateGeneratorProvider() {}
|
||||
|
||||
], function (
|
||||
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';
|
||||
};
|
||||
|
||||
function StateGeneratorProvider() {
|
||||
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;
|
||||
}
|
||||
|
||||
function pointForTimestamp(timestamp, duration, name) {
|
||||
return {
|
||||
name: name,
|
||||
utc: Math.floor(timestamp / duration) * duration,
|
||||
value: Math.floor(timestamp / duration) % 2
|
||||
};
|
||||
var data = [];
|
||||
while (start <= end && data.length < 5000) {
|
||||
data.push(pointForTimestamp(start, duration, domainObject.name));
|
||||
start += duration;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
return StateGeneratorProvider;
|
||||
});
|
||||
|
@ -20,93 +20,88 @@
|
||||
* 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 = {};
|
||||
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 = {};
|
||||
|
||||
this.watchStaleness();
|
||||
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);
|
||||
}
|
||||
WorkerInterface.prototype.subscribe = function (request, cb) {
|
||||
const { id, loadDelay } = request;
|
||||
const messageId = this.dispatch('subscribe', request, (message) => {
|
||||
if (!this.staleTelemetryIds[id]) {
|
||||
setTimeout(() => cb(message.data), Math.max(loadDelay, 0));
|
||||
}
|
||||
});
|
||||
|
||||
delete self.callbacks[messageId];
|
||||
return function () {
|
||||
this.dispatch('unsubscribe', {
|
||||
id: messageId
|
||||
});
|
||||
delete this.callbacks[messageId];
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
messageId = this.dispatch('request', request, callback.bind(this));
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
WorkerInterface.prototype.subscribe = function (request, cb) {
|
||||
const id = request.id;
|
||||
const messageId = this.dispatch('subscribe', request, (message) => {
|
||||
if (!this.staleTelemetryIds[id]) {
|
||||
cb(message.data);
|
||||
}
|
||||
});
|
||||
|
||||
return function () {
|
||||
this.dispatch('unsubscribe', {
|
||||
id: messageId
|
||||
});
|
||||
delete this.callbacks[messageId];
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
return WorkerInterface;
|
||||
return WorkerInterface;
|
||||
});
|
||||
|
@ -21,204 +21,254 @@
|
||||
*****************************************************************************/
|
||||
|
||||
(function () {
|
||||
var FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||
|
||||
var FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||
var handlers = {
|
||||
subscribe: onSubscribe,
|
||||
unsubscribe: onUnsubscribe,
|
||||
request: onRequest
|
||||
};
|
||||
|
||||
var handlers = {
|
||||
subscribe: onSubscribe,
|
||||
unsubscribe: onUnsubscribe,
|
||||
request: onRequest
|
||||
};
|
||||
var subscriptions = {};
|
||||
|
||||
var subscriptions = {};
|
||||
|
||||
function workSubscriptions(timestamp) {
|
||||
var now = Date.now();
|
||||
var nextWork = Math.min.apply(Math, Object.values(subscriptions).map(function (subscription) {
|
||||
return subscription(now);
|
||||
}));
|
||||
var wait = nextWork - now;
|
||||
if (wait < 0) {
|
||||
wait = 0;
|
||||
}
|
||||
|
||||
if (Number.isFinite(wait)) {
|
||||
setTimeout(workSubscriptions, wait);
|
||||
}
|
||||
function workSubscriptions(timestamp) {
|
||||
var now = Date.now();
|
||||
var nextWork = Math.min.apply(
|
||||
Math,
|
||||
Object.values(subscriptions).map(function (subscription) {
|
||||
return subscription(now);
|
||||
})
|
||||
);
|
||||
var wait = nextWork - now;
|
||||
if (wait < 0) {
|
||||
wait = 0;
|
||||
}
|
||||
|
||||
function onSubscribe(message) {
|
||||
var data = message.data;
|
||||
|
||||
// Keep
|
||||
var start = Date.now();
|
||||
var step = 1000 / data.dataRateInHz;
|
||||
var nextStep = start - (start % step) + step;
|
||||
let work;
|
||||
if (data.spectra) {
|
||||
work = function (now) {
|
||||
while (nextStep < now) {
|
||||
const messageCopy = Object.create(message);
|
||||
message.data.start = nextStep - (60 * 1000);
|
||||
message.data.end = nextStep;
|
||||
onRequest(messageCopy);
|
||||
nextStep += step;
|
||||
}
|
||||
|
||||
return nextStep;
|
||||
};
|
||||
} else {
|
||||
work = function (now) {
|
||||
while (nextStep < now) {
|
||||
self.postMessage({
|
||||
id: message.id,
|
||||
data: {
|
||||
name: data.name,
|
||||
utc: nextStep,
|
||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness, data.infinityValues),
|
||||
wavelengths: wavelengths(),
|
||||
intensities: intensities(),
|
||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness, data.infinityValues)
|
||||
}
|
||||
});
|
||||
nextStep += step;
|
||||
}
|
||||
|
||||
return nextStep;
|
||||
};
|
||||
}
|
||||
|
||||
subscriptions[message.id] = work;
|
||||
workSubscriptions();
|
||||
if (Number.isFinite(wait)) {
|
||||
setTimeout(workSubscriptions, wait);
|
||||
}
|
||||
}
|
||||
|
||||
function onUnsubscribe(message) {
|
||||
delete subscriptions[message.data.id];
|
||||
}
|
||||
function onSubscribe(message) {
|
||||
var data = message.data;
|
||||
|
||||
function onRequest(message) {
|
||||
var request = message.data;
|
||||
if (request.end === undefined) {
|
||||
request.end = Date.now();
|
||||
// Keep
|
||||
var start = Date.now();
|
||||
var step = 1000 / data.dataRateInHz;
|
||||
var nextStep = start - (start % step) + step;
|
||||
let work;
|
||||
if (data.spectra) {
|
||||
work = function (now) {
|
||||
while (nextStep < now) {
|
||||
const messageCopy = Object.create(message);
|
||||
message.data.start = nextStep - 60 * 1000;
|
||||
message.data.end = nextStep;
|
||||
onRequest(messageCopy);
|
||||
nextStep += step;
|
||||
}
|
||||
|
||||
if (request.start === undefined) {
|
||||
request.start = request.end - FIFTEEN_MINUTES;
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
var start = request.start;
|
||||
var end = request.end > now ? now : request.end;
|
||||
var amplitude = request.amplitude;
|
||||
var period = request.period;
|
||||
var offset = request.offset;
|
||||
var dataRateInHz = request.dataRateInHz;
|
||||
var phase = request.phase;
|
||||
var randomness = request.randomness;
|
||||
var loadDelay = Math.max(request.loadDelay, 0);
|
||||
var infinityValues = request.infinityValues;
|
||||
|
||||
var step = 1000 / dataRateInHz;
|
||||
var nextStep = start - (start % step) + step;
|
||||
|
||||
var data = [];
|
||||
|
||||
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
||||
data.push({
|
||||
utc: nextStep,
|
||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||
sin: sin(nextStep, period, amplitude, offset, phase, randomness, infinityValues),
|
||||
wavelengths: wavelengths(),
|
||||
intensities: intensities(),
|
||||
cos: cos(nextStep, period, amplitude, offset, phase, randomness, infinityValues)
|
||||
});
|
||||
}
|
||||
|
||||
if (loadDelay === 0) {
|
||||
postOnRequest(message, request, data);
|
||||
} else {
|
||||
setTimeout(() => postOnRequest(message, request, data), loadDelay);
|
||||
}
|
||||
}
|
||||
|
||||
function postOnRequest(message, request, data) {
|
||||
self.postMessage({
|
||||
return nextStep;
|
||||
};
|
||||
} else {
|
||||
work = function (now) {
|
||||
while (nextStep < now) {
|
||||
self.postMessage({
|
||||
id: message.id,
|
||||
data: request.spectra ? {
|
||||
wavelength: data.map((item) => {
|
||||
return item.wavelength;
|
||||
}),
|
||||
cos: data.map((item) => {
|
||||
return item.cos;
|
||||
})
|
||||
} : data
|
||||
});
|
||||
data: {
|
||||
name: data.name,
|
||||
utc: nextStep,
|
||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||
sin: sin(
|
||||
nextStep,
|
||||
data.period,
|
||||
data.amplitude,
|
||||
data.offset,
|
||||
data.phase,
|
||||
data.randomness,
|
||||
data.infinityValues,
|
||||
data.veryLargeValues
|
||||
),
|
||||
wavelengths: wavelengths(),
|
||||
intensities: intensities(),
|
||||
cos: cos(
|
||||
nextStep,
|
||||
data.period,
|
||||
data.amplitude,
|
||||
data.offset,
|
||||
data.phase,
|
||||
data.randomness,
|
||||
data.infinityValues,
|
||||
data.veryLargeValues
|
||||
)
|
||||
}
|
||||
});
|
||||
nextStep += step;
|
||||
}
|
||||
|
||||
return nextStep;
|
||||
};
|
||||
}
|
||||
|
||||
function cos(timestamp, period, amplitude, offset, phase, randomness, infinityValues) {
|
||||
if (infinityValues && Math.random() > 0.5) {
|
||||
subscriptions[message.id] = work;
|
||||
workSubscriptions();
|
||||
}
|
||||
|
||||
function onUnsubscribe(message) {
|
||||
delete subscriptions[message.data.id];
|
||||
}
|
||||
|
||||
function onRequest(message) {
|
||||
var request = message.data;
|
||||
if (request.end === undefined) {
|
||||
request.end = Date.now();
|
||||
}
|
||||
|
||||
if (request.start === undefined) {
|
||||
request.start = request.end - FIFTEEN_MINUTES;
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
var start = request.start;
|
||||
var end = request.end > now ? now : request.end;
|
||||
var amplitude = request.amplitude;
|
||||
var period = request.period;
|
||||
var offset = request.offset;
|
||||
var dataRateInHz = request.dataRateInHz;
|
||||
var phase = request.phase;
|
||||
var randomness = request.randomness;
|
||||
var loadDelay = Math.max(request.loadDelay, 0);
|
||||
var infinityValues = request.infinityValues;
|
||||
var veryLargeValues = request.veryLargeValues;
|
||||
|
||||
var step = 1000 / dataRateInHz;
|
||||
var nextStep = start - (start % step) + step;
|
||||
|
||||
var data = [];
|
||||
|
||||
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
||||
data.push({
|
||||
utc: nextStep,
|
||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||
sin: sin(nextStep, period, amplitude, offset, phase, randomness, infinityValues, veryLargeValues),
|
||||
wavelengths: wavelengths(),
|
||||
intensities: intensities(),
|
||||
cos: cos(nextStep, period, amplitude, offset, phase, randomness, infinityValues, veryLargeValues)
|
||||
});
|
||||
}
|
||||
|
||||
if (loadDelay === 0) {
|
||||
postOnRequest(message, request, data);
|
||||
} else {
|
||||
setTimeout(() => postOnRequest(message, request, data), loadDelay);
|
||||
}
|
||||
}
|
||||
|
||||
function postOnRequest(message, request, data) {
|
||||
self.postMessage({
|
||||
id: message.id,
|
||||
data: request.spectra
|
||||
? {
|
||||
wavelength: data.map((item) => {
|
||||
return item.wavelength;
|
||||
}),
|
||||
cos: data.map((item) => {
|
||||
return item.cos;
|
||||
})
|
||||
}
|
||||
: data
|
||||
});
|
||||
}
|
||||
|
||||
function cos(timestamp, period, amplitude, offset, phase, randomness, infinityValues, veryLargeValues) {
|
||||
const randomValue = Math.random();
|
||||
|
||||
if (infinityValues && veryLargeValues) {
|
||||
if (randomValue < 0.33) return Number.POSITIVE_INFINITY;
|
||||
if (randomValue < 0.66) return (randomValue > 0.5 ? -1 : 1) * 1e+177;
|
||||
// if neither condition is met, it just proceeds to the normal return value
|
||||
} else {
|
||||
if (infinityValues && randomValue > 0.5) {
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
return amplitude
|
||||
* Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
||||
if (veryLargeValues && randomValue > 0.5) {
|
||||
return (randomValue > 0.5 ? -1 : 1) * 1e+177;
|
||||
}
|
||||
}
|
||||
|
||||
function sin(timestamp, period, amplitude, offset, phase, randomness, infinityValues) {
|
||||
if (infinityValues && Math.random() > 0.5) {
|
||||
return (
|
||||
amplitude * Math.cos(phase + (timestamp / period / 1000) * Math.PI * 2) +
|
||||
amplitude * Math.random() * randomness +
|
||||
offset
|
||||
);
|
||||
}
|
||||
|
||||
function sin(timestamp, period, amplitude, offset, phase, randomness, infinityValues, veryLargeValues) {
|
||||
const randomValue = Math.random();
|
||||
|
||||
if (infinityValues && veryLargeValues) {
|
||||
if (randomValue < 0.33) return Number.POSITIVE_INFINITY;
|
||||
if (randomValue < 0.66) return (randomValue > 0.5 ? -1 : 1) * 1e+177;
|
||||
// if neither condition is met, it just proceeds to the normal return value
|
||||
} else {
|
||||
if (infinityValues && randomValue > 0.5) {
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
return amplitude
|
||||
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
||||
}
|
||||
|
||||
function wavelengths() {
|
||||
let values = [];
|
||||
while (values.length < 5) {
|
||||
const randomValue = Math.random() * 100;
|
||||
if (!values.includes(randomValue)) {
|
||||
values.push(String(randomValue));
|
||||
}
|
||||
if (veryLargeValues && randomValue > 0.5) {
|
||||
return (randomValue > 0.5 ? -1 : 1) * 1e+177;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
function intensities() {
|
||||
let values = [];
|
||||
while (values.length < 5) {
|
||||
const randomValue = Math.random() * 10;
|
||||
if (!values.includes(randomValue)) {
|
||||
values.push(String(randomValue));
|
||||
}
|
||||
}
|
||||
return (
|
||||
amplitude * Math.sin(phase + (timestamp / period / 1000) * Math.PI * 2) +
|
||||
amplitude * Math.random() * randomness +
|
||||
offset
|
||||
);
|
||||
}
|
||||
|
||||
return values;
|
||||
function wavelengths() {
|
||||
let values = [];
|
||||
while (values.length < 5) {
|
||||
const randomValue = Math.random() * 100;
|
||||
if (!values.includes(randomValue)) {
|
||||
values.push(String(randomValue));
|
||||
}
|
||||
}
|
||||
|
||||
function sendError(error, message) {
|
||||
self.postMessage({
|
||||
error: error.name + ': ' + error.message,
|
||||
message: message,
|
||||
id: message.id
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
function intensities() {
|
||||
let values = [];
|
||||
while (values.length < 5) {
|
||||
const randomValue = Math.random() * 10;
|
||||
if (!values.includes(randomValue)) {
|
||||
values.push(String(randomValue));
|
||||
}
|
||||
}
|
||||
|
||||
self.onmessage = function handleMessage(event) {
|
||||
var message = event.data;
|
||||
var handler = handlers[message.request];
|
||||
return values;
|
||||
}
|
||||
|
||||
if (!handler) {
|
||||
sendError(new Error('unknown message type'), message);
|
||||
} else {
|
||||
try {
|
||||
handler(message);
|
||||
} catch (e) {
|
||||
sendError(e, message);
|
||||
}
|
||||
}
|
||||
};
|
||||
function sendError(error, message) {
|
||||
self.postMessage({
|
||||
error: error.name + ': ' + error.message,
|
||||
message: message,
|
||||
id: message.id
|
||||
});
|
||||
}
|
||||
|
||||
}());
|
||||
self.onmessage = function handleMessage(event) {
|
||||
var message = event.data;
|
||||
var handler = handlers[message.request];
|
||||
|
||||
if (!handler) {
|
||||
sendError(new Error('unknown message type'), message);
|
||||
} else {
|
||||
try {
|
||||
handler(message);
|
||||
} catch (e) {
|
||||
sendError(e, message);
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
@ -20,163 +20,142 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import GeneratorProvider from "./GeneratorProvider";
|
||||
import SinewaveLimitProvider from "./SinewaveLimitProvider";
|
||||
import SinewaveStalenessProvider from "./SinewaveStalenessProvider";
|
||||
import StateGeneratorProvider from "./StateGeneratorProvider";
|
||||
import GeneratorMetadataProvider from "./GeneratorMetadataProvider";
|
||||
import GeneratorMetadataProvider from './GeneratorMetadataProvider';
|
||||
import GeneratorProvider from './GeneratorProvider';
|
||||
import SinewaveLimitProvider from './SinewaveLimitProvider';
|
||||
import SinewaveStalenessProvider from './SinewaveStalenessProvider';
|
||||
import StateGeneratorProvider from './StateGeneratorProvider';
|
||||
|
||||
export default function (openmct) {
|
||||
openmct.types.addType('example.state-generator', {
|
||||
name: 'State Generator',
|
||||
description:
|
||||
'For development use. Generates example enumerated telemetry by cycling through a given set of states.',
|
||||
cssClass: 'icon-generator-telemetry',
|
||||
creatable: true,
|
||||
form: [
|
||||
{
|
||||
name: 'State Duration (seconds)',
|
||||
control: 'numberfield',
|
||||
cssClass: 'l-input-sm l-numeric',
|
||||
key: 'duration',
|
||||
required: true,
|
||||
property: ['telemetry', 'duration']
|
||||
}
|
||||
],
|
||||
initialize: function (object) {
|
||||
object.telemetry = {
|
||||
duration: 5
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
openmct.types.addType("example.state-generator", {
|
||||
name: "State Generator",
|
||||
description: "For development use. Generates example enumerated telemetry by cycling through a given set of states.",
|
||||
cssClass: "icon-generator-telemetry",
|
||||
creatable: true,
|
||||
form: [
|
||||
{
|
||||
name: "State Duration (seconds)",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "duration",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"duration"
|
||||
]
|
||||
}
|
||||
],
|
||||
initialize: function (object) {
|
||||
object.telemetry = {
|
||||
duration: 5
|
||||
};
|
||||
}
|
||||
});
|
||||
openmct.telemetry.addProvider(new StateGeneratorProvider());
|
||||
|
||||
openmct.telemetry.addProvider(new StateGeneratorProvider());
|
||||
openmct.types.addType('generator', {
|
||||
name: 'Sine Wave Generator',
|
||||
description:
|
||||
'For development use. Generates example streaming telemetry data using a simple sine wave algorithm.',
|
||||
cssClass: 'icon-generator-telemetry',
|
||||
creatable: true,
|
||||
form: [
|
||||
{
|
||||
name: 'Period',
|
||||
control: 'numberfield',
|
||||
cssClass: 'l-input-sm l-numeric',
|
||||
key: 'period',
|
||||
required: true,
|
||||
property: ['telemetry', 'period']
|
||||
},
|
||||
{
|
||||
name: 'Amplitude',
|
||||
control: 'numberfield',
|
||||
cssClass: 'l-numeric',
|
||||
key: 'amplitude',
|
||||
required: true,
|
||||
property: ['telemetry', 'amplitude']
|
||||
},
|
||||
{
|
||||
name: 'Offset',
|
||||
control: 'numberfield',
|
||||
cssClass: 'l-numeric',
|
||||
key: 'offset',
|
||||
required: true,
|
||||
property: ['telemetry', 'offset']
|
||||
},
|
||||
{
|
||||
name: 'Data Rate (hz)',
|
||||
control: 'numberfield',
|
||||
cssClass: 'l-input-sm l-numeric',
|
||||
key: 'dataRateInHz',
|
||||
required: true,
|
||||
property: ['telemetry', 'dataRateInHz']
|
||||
},
|
||||
{
|
||||
name: 'Phase (radians)',
|
||||
control: 'numberfield',
|
||||
cssClass: 'l-input-sm l-numeric',
|
||||
key: 'phase',
|
||||
required: true,
|
||||
property: ['telemetry', 'phase']
|
||||
},
|
||||
{
|
||||
name: 'Randomness',
|
||||
control: 'numberfield',
|
||||
cssClass: 'l-input-sm l-numeric',
|
||||
key: 'randomness',
|
||||
required: true,
|
||||
property: ['telemetry', 'randomness']
|
||||
},
|
||||
{
|
||||
name: 'Loading Delay (ms)',
|
||||
control: 'numberfield',
|
||||
cssClass: 'l-input-sm l-numeric',
|
||||
key: 'loadDelay',
|
||||
required: true,
|
||||
property: ['telemetry', 'loadDelay']
|
||||
},
|
||||
{
|
||||
name: 'Include Infinity Values',
|
||||
control: 'toggleSwitch',
|
||||
cssClass: 'l-input',
|
||||
key: 'infinityValues',
|
||||
property: ['telemetry', 'infinityValues']
|
||||
},
|
||||
{
|
||||
name: 'Include Very Large Values',
|
||||
control: 'toggleSwitch',
|
||||
cssClass: 'l-input',
|
||||
key: 'veryLargeValues',
|
||||
property: ['telemetry', 'veryLargeValues']
|
||||
},
|
||||
{
|
||||
name: 'Provide Staleness Updates',
|
||||
control: 'toggleSwitch',
|
||||
cssClass: 'l-input',
|
||||
key: 'staleness',
|
||||
property: ['telemetry', 'staleness']
|
||||
}
|
||||
],
|
||||
initialize: function (object) {
|
||||
object.telemetry = {
|
||||
period: 10,
|
||||
amplitude: 1,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
phase: 0,
|
||||
randomness: 0,
|
||||
loadDelay: 0,
|
||||
infinityValues: false,
|
||||
veryLargeValues: false,
|
||||
staleness: false
|
||||
};
|
||||
}
|
||||
});
|
||||
const stalenessProvider = new SinewaveStalenessProvider(openmct);
|
||||
|
||||
openmct.types.addType("generator", {
|
||||
name: "Sine Wave Generator",
|
||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||
cssClass: "icon-generator-telemetry",
|
||||
creatable: true,
|
||||
form: [
|
||||
{
|
||||
name: "Period",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "period",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"period"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Amplitude",
|
||||
control: "numberfield",
|
||||
cssClass: "l-numeric",
|
||||
key: "amplitude",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"amplitude"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Offset",
|
||||
control: "numberfield",
|
||||
cssClass: "l-numeric",
|
||||
key: "offset",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"offset"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Data Rate (hz)",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "dataRateInHz",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"dataRateInHz"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Phase (radians)",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "phase",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"phase"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Randomness",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "randomness",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"randomness"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Loading Delay (ms)",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "loadDelay",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"loadDelay"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Include Infinity Values",
|
||||
control: "toggleSwitch",
|
||||
cssClass: "l-input",
|
||||
key: "infinityValues",
|
||||
property: [
|
||||
"telemetry",
|
||||
"infinityValues"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Provide Staleness Updates",
|
||||
control: "toggleSwitch",
|
||||
cssClass: "l-input",
|
||||
key: "staleness",
|
||||
property: [
|
||||
"telemetry",
|
||||
"staleness"
|
||||
]
|
||||
}
|
||||
],
|
||||
initialize: function (object) {
|
||||
object.telemetry = {
|
||||
period: 10,
|
||||
amplitude: 1,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
phase: 0,
|
||||
randomness: 0,
|
||||
loadDelay: 0,
|
||||
infinityValues: false,
|
||||
staleness: false
|
||||
};
|
||||
}
|
||||
});
|
||||
const stalenessProvider = new SinewaveStalenessProvider(openmct);
|
||||
|
||||
openmct.telemetry.addProvider(new GeneratorProvider(openmct, stalenessProvider));
|
||||
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
|
||||
openmct.telemetry.addProvider(new SinewaveLimitProvider());
|
||||
openmct.telemetry.addProvider(stalenessProvider);
|
||||
openmct.telemetry.addProvider(new GeneratorProvider(openmct, stalenessProvider));
|
||||
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
|
||||
openmct.telemetry.addProvider(new SinewaveLimitProvider());
|
||||
openmct.telemetry.addProvider(stalenessProvider);
|
||||
}
|
||||
|
16
package.json
16
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "2.2.0-SNAPSHOT",
|
||||
"version": "2.2.1-SNAPSHOT",
|
||||
"description": "The Open MCT core platform",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.18.9",
|
||||
@ -10,7 +10,7 @@
|
||||
"@playwright/test": "1.29.0",
|
||||
"@types/eventemitter3": "1.2.0",
|
||||
"@types/jasmine": "4.3.1",
|
||||
"@types/lodash": "4.14.191",
|
||||
"@types/lodash": "4.14.192",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
"codecov": "3.8.3",
|
||||
@ -23,7 +23,7 @@
|
||||
"eslint": "8.36.0",
|
||||
"eslint-plugin-compat": "4.1.1",
|
||||
"eslint-plugin-playwright": "0.12.0",
|
||||
"eslint-plugin-vue": "9.9.0",
|
||||
"eslint-plugin-vue": "9.10.0",
|
||||
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
|
||||
"eventemitter3": "1.2.0",
|
||||
"file-saver": "2.0.5",
|
||||
@ -51,13 +51,13 @@
|
||||
"nyc": "15.1.0",
|
||||
"painterro": "1.2.78",
|
||||
"playwright-core": "1.29.0",
|
||||
"plotly.js-basic-dist": "2.17.0",
|
||||
"plotly.js-gl2d-dist": "2.17.1",
|
||||
"plotly.js-basic-dist": "2.20.0",
|
||||
"plotly.js-gl2d-dist": "2.20.0",
|
||||
"printj": "1.3.1",
|
||||
"resolve-url-loader": "5.0.0",
|
||||
"sanitize-html": "2.10.0",
|
||||
"sass": "1.57.1",
|
||||
"sass-loader": "13.2.0",
|
||||
"sass": "1.59.3",
|
||||
"sass-loader": "13.2.1",
|
||||
"sinon": "15.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"typescript": "4.9.5",
|
||||
@ -66,7 +66,7 @@
|
||||
"vue-eslint-parser": "9.1.0",
|
||||
"vue-loader": "15.9.8",
|
||||
"vue-template-compiler": "2.6.14",
|
||||
"webpack": "5.74.0",
|
||||
"webpack": "5.76.3",
|
||||
"webpack-cli": "5.0.0",
|
||||
"webpack-dev-server": "4.11.1",
|
||||
"webpack-merge": "5.8.0"
|
||||
|
@ -43,7 +43,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideOptions && filteredOptions.length > 0"
|
||||
class="c-menu c-input--autocomplete__options"
|
||||
class="c-menu c-input--autocomplete__options js-autocomplete-options"
|
||||
aria-label="Autocomplete Options"
|
||||
@blur="hideOptions = true"
|
||||
>
|
||||
|
@ -239,9 +239,15 @@ export default class ObjectAPI {
|
||||
|
||||
return domainObject;
|
||||
}).catch((error) => {
|
||||
console.warn(`Failed to retrieve ${keystring}:`, error);
|
||||
let result;
|
||||
|
||||
delete this.cache[keystring];
|
||||
const result = this.applyGetInterceptors(identifier);
|
||||
|
||||
// suppress abort errors
|
||||
if (error.name !== 'AbortError') {
|
||||
console.warn(`Failed to retrieve ${keystring}:`, error);
|
||||
result = this.applyGetInterceptors(identifier);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
@ -55,6 +55,13 @@ define([
|
||||
*/
|
||||
function parseKeyString(keyString) {
|
||||
if (isIdentifier(keyString)) {
|
||||
// TODO REMOVE FOR OMM-RELEASE-5.0
|
||||
if (!keyString.namespace && keyString.key.includes(':')) {
|
||||
console.warn(`smushed key: ${keyString.key}`);
|
||||
|
||||
return parseKeyString(keyString.key);
|
||||
}
|
||||
|
||||
return keyString;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ export default class TelemetryAPI {
|
||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||
*/
|
||||
canProvideTelemetry(domainObject) {
|
||||
return Boolean(this.#findSubscriptionProvider(domainObject))
|
||||
return Boolean(this.findSubscriptionProvider(domainObject))
|
||||
|| Boolean(this.findRequestProvider(domainObject));
|
||||
}
|
||||
|
||||
@ -123,9 +123,10 @@ export default class TelemetryAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Returns a telemetry subscription provider that supports
|
||||
* a given domain object and options.
|
||||
*/
|
||||
#findSubscriptionProvider() {
|
||||
findSubscriptionProvider() {
|
||||
const args = Array.prototype.slice.apply(arguments);
|
||||
function supportsDomainObject(provider) {
|
||||
return provider.supportsSubscribe.apply(provider, args);
|
||||
@ -348,7 +349,7 @@ export default class TelemetryAPI {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const provider = this.#findSubscriptionProvider(domainObject);
|
||||
const provider = this.findSubscriptionProvider(domainObject);
|
||||
|
||||
if (!this.subscribeCache) {
|
||||
this.subscribeCache = {};
|
||||
|
@ -21,15 +21,18 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="urlDefined ? 'a' : 'span'"
|
||||
<div
|
||||
ref="conditionWidgetElement"
|
||||
class="c-condition-widget u-style-receiver js-style-receiver"
|
||||
:href="url"
|
||||
>
|
||||
<div class="c-condition-widget__label">
|
||||
{{ label }}
|
||||
</div>
|
||||
</component>
|
||||
<component
|
||||
:is="urlDefined ? 'a' : 'div'"
|
||||
class="c-condition-widget__label-wrapper"
|
||||
:href="url"
|
||||
>
|
||||
<div class="c-condition-widget__label">{{ label }}</div>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -39,19 +42,26 @@ export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data: function () {
|
||||
return {
|
||||
conditionalLabel: '',
|
||||
conditionSetIdentifier: null,
|
||||
domainObjectLabel: '',
|
||||
url: null,
|
||||
urlDefined: false,
|
||||
useConditionSetOutputAsLabel: false
|
||||
conditionalLabel: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
urlDefined() {
|
||||
return this.domainObject.url?.length > 0;
|
||||
},
|
||||
url() {
|
||||
return this.urlDefined ? sanitizeUrl(this.domainObject.url) : null;
|
||||
},
|
||||
useConditionSetOutputAsLabel() {
|
||||
return this.conditionSetIdentifier && this.domainObject.configuration.useConditionSetOutputAsLabel;
|
||||
},
|
||||
conditionSetIdentifier() {
|
||||
return this.domainObject.configuration?.objectStyles?.conditionSetIdentifier;
|
||||
},
|
||||
label() {
|
||||
return this.useConditionSetOutputAsLabel
|
||||
? this.conditionalLabel
|
||||
: this.domainObjectLabel
|
||||
: this.domainObject.label
|
||||
;
|
||||
}
|
||||
},
|
||||
@ -68,20 +78,11 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
|
||||
|
||||
if (this.domainObject) {
|
||||
this.updateDomainObject(this.domainObject);
|
||||
this.listenToConditionSetChanges();
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.conditionSetIdentifier = null;
|
||||
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
|
||||
this.stopListeningToConditionSetChanges();
|
||||
},
|
||||
methods: {
|
||||
@ -120,31 +121,6 @@ export default {
|
||||
}
|
||||
|
||||
this.conditionalLabel = latestDatum.output || '';
|
||||
},
|
||||
updateDomainObject(domainObject) {
|
||||
if (this.domainObjectLabel !== domainObject.label) {
|
||||
this.domainObjectLabel = domainObject.label;
|
||||
}
|
||||
|
||||
const urlDefined = domainObject.url && domainObject.url.length > 0;
|
||||
if (this.urlDefined !== urlDefined) {
|
||||
this.urlDefined = urlDefined;
|
||||
}
|
||||
|
||||
const url = this.urlDefined ? sanitizeUrl(domainObject.url) : null;
|
||||
if (this.url !== url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
const conditionSetIdentifier = domainObject.configuration?.objectStyles?.conditionSetIdentifier;
|
||||
if (conditionSetIdentifier && this.conditionSetIdentifier !== conditionSetIdentifier) {
|
||||
this.conditionSetIdentifier = conditionSetIdentifier;
|
||||
}
|
||||
|
||||
const useConditionSetOutputAsLabel = this.conditionSetIdentifier && domainObject.configuration.useConditionSetOutputAsLabel;
|
||||
if (this.useConditionSetOutputAsLabel !== useConditionSetOutputAsLabel) {
|
||||
this.useConditionSetOutputAsLabel = useConditionSetOutputAsLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -26,31 +26,35 @@
|
||||
background-color: rgba($colorBodyFg, 0.1); // Give a little presence if the user hasn't defined a fill color
|
||||
border-radius: $basicCr;
|
||||
border: 1px solid transparent;
|
||||
display: inline-block;
|
||||
padding: $interiorMarginLg $interiorMarginLg * 2;
|
||||
display: block;
|
||||
max-width: max-content;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.c-condition-widget__label {
|
||||
padding: $interiorMargin;
|
||||
// Either a <div> or an <a> tag
|
||||
padding: $interiorMargin $interiorMargin * 1.5;
|
||||
text-align: center;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
a.c-condition-widget {
|
||||
// Widget is conditionally made into a <a> when URL property has been defined
|
||||
cursor: pointer !important;
|
||||
pointer-events: inherit;
|
||||
}
|
||||
|
||||
// Make Condition Widget expand when in a hidden frame Layout context
|
||||
// For both static and Flexible Layouts
|
||||
.c-so-view--conditionWidget.c-so-view--no-frame {
|
||||
.c-condition-widget {
|
||||
@include abs();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
max-width: unset;
|
||||
|
||||
&__label-wrapper {
|
||||
@include abs();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.c-so-view__frame-controls { display: none; }
|
||||
|
@ -36,6 +36,7 @@ export default function plugin() {
|
||||
domainObject.configuration = {};
|
||||
domainObject.label = 'Condition Widget';
|
||||
domainObject.conditionalLabel = '';
|
||||
domainObject.url = '';
|
||||
},
|
||||
form: [
|
||||
{
|
||||
|
@ -20,8 +20,6 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import JSONExporter from '/src/exporters/JSONExporter.js';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export default class ExportAsJSONAction {
|
||||
@ -35,10 +33,9 @@ export default class ExportAsJSONAction {
|
||||
this.group = "json";
|
||||
this.priority = 1;
|
||||
|
||||
this.externalIdentifiers = [];
|
||||
this.tree = {};
|
||||
this.calls = 0;
|
||||
this.idMap = {};
|
||||
this.tree = null;
|
||||
this.calls = null;
|
||||
this.idMap = null;
|
||||
|
||||
this.JSONExportService = new JSONExporter();
|
||||
}
|
||||
@ -60,21 +57,164 @@ export default class ExportAsJSONAction {
|
||||
*/
|
||||
invoke(objectpath) {
|
||||
this.tree = {};
|
||||
this.calls = 0;
|
||||
this.idMap = {};
|
||||
|
||||
const root = objectpath[0];
|
||||
this.root = JSON.parse(JSON.stringify(root));
|
||||
const rootId = this._getId(this.root);
|
||||
this.root = this._copy(root);
|
||||
|
||||
const rootId = this._getKeystring(this.root);
|
||||
this.tree[rootId] = this.root;
|
||||
|
||||
this._write(this.root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} parent
|
||||
*/
|
||||
async _write(parent) {
|
||||
this.calls++;
|
||||
|
||||
//conditional object styles are not saved on the composition, so we need to check for them
|
||||
const conditionSetIdentifier = this._getConditionSetIdentifier(parent);
|
||||
const hasItemConditionSetIdentifiers = this._hasItemConditionSetIdentifiers(parent);
|
||||
const composition = this.openmct.composition.get(parent);
|
||||
|
||||
if (composition) {
|
||||
const children = await composition.load();
|
||||
|
||||
children.forEach((child) => {
|
||||
this._exportObject(child, parent);
|
||||
});
|
||||
}
|
||||
|
||||
if (!conditionSetIdentifier && !hasItemConditionSetIdentifiers) {
|
||||
this._decrementCallsAndSave();
|
||||
} else {
|
||||
const conditionSetObjects = [];
|
||||
|
||||
// conditionSetIdentifiers directly in objectStyles object
|
||||
if (conditionSetIdentifier) {
|
||||
conditionSetObjects.push(await this.openmct.objects.get(conditionSetIdentifier));
|
||||
}
|
||||
|
||||
// conditionSetIdentifiers stored on item ids in the objectStyles object
|
||||
if (hasItemConditionSetIdentifiers) {
|
||||
const itemConditionSetIdentifiers = this._getItemConditionSetIdentifiers(parent);
|
||||
|
||||
for (const itemConditionSetIdentifier of itemConditionSetIdentifiers) {
|
||||
conditionSetObjects.push(await this.openmct.objects.get(itemConditionSetIdentifier));
|
||||
}
|
||||
}
|
||||
|
||||
for (const conditionSetObject of conditionSetObjects) {
|
||||
this._exportObject(conditionSetObject, parent);
|
||||
}
|
||||
|
||||
this._decrementCallsAndSave();
|
||||
}
|
||||
}
|
||||
|
||||
_exportObject(child, parent) {
|
||||
const originalKeyString = this._getKeystring(child);
|
||||
const createable = this._isCreatableAndPersistable(child);
|
||||
const isNotInfinite = !Object.prototype.hasOwnProperty.call(this.tree, originalKeyString);
|
||||
|
||||
if (createable && isNotInfinite) {
|
||||
// for external or linked objects we generate new keys, if they don't exist already
|
||||
if (this._isLinkedObject(child, parent)) {
|
||||
child = this._rewriteLink(child, parent);
|
||||
} else {
|
||||
this.tree[originalKeyString] = child;
|
||||
}
|
||||
|
||||
this._write(child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} child
|
||||
* @param {object} parent
|
||||
* @returns {object}
|
||||
*/
|
||||
_rewriteLink(child, parent) {
|
||||
const originalKeyString = this._getKeystring(child);
|
||||
const parentKeyString = this._getKeystring(parent);
|
||||
const conditionSetIdentifier = this._getConditionSetIdentifier(parent);
|
||||
const hasItemConditionSetIdentifiers = this._hasItemConditionSetIdentifiers(parent);
|
||||
const existingMappedKeyString = this.idMap[originalKeyString];
|
||||
let copy;
|
||||
|
||||
if (!existingMappedKeyString) {
|
||||
copy = this._copy(child);
|
||||
copy.identifier.key = uuid();
|
||||
|
||||
if (!conditionSetIdentifier && !hasItemConditionSetIdentifiers) {
|
||||
copy.location = parentKeyString;
|
||||
}
|
||||
|
||||
let newKeyString = this._getKeystring(copy);
|
||||
this.idMap[originalKeyString] = newKeyString;
|
||||
this.tree[newKeyString] = copy;
|
||||
} else {
|
||||
copy = this.tree[existingMappedKeyString];
|
||||
}
|
||||
|
||||
if (conditionSetIdentifier || hasItemConditionSetIdentifiers) {
|
||||
|
||||
// update objectStyle object
|
||||
if (conditionSetIdentifier) {
|
||||
const directObjectStylesIdentifier = this.openmct.objects.areIdsEqual(
|
||||
parent.configuration.objectStyles.conditionSetIdentifier,
|
||||
child.identifier
|
||||
);
|
||||
|
||||
if (directObjectStylesIdentifier) {
|
||||
parent.configuration.objectStyles.conditionSetIdentifier = copy.identifier;
|
||||
this.tree[parentKeyString].configuration.objectStyles.conditionSetIdentifier = copy.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
// update per item id on objectStyle object
|
||||
if (hasItemConditionSetIdentifiers) {
|
||||
for (const itemId in parent.configuration.objectStyles) {
|
||||
if (parent.configuration.objectStyles[itemId]) {
|
||||
const itemConditionSetIdentifier = parent.configuration.objectStyles[itemId].conditionSetIdentifier;
|
||||
|
||||
if (
|
||||
itemConditionSetIdentifier
|
||||
&& this.openmct.objects.areIdsEqual(itemConditionSetIdentifier, child.identifier)
|
||||
) {
|
||||
parent.configuration.objectStyles[itemId].conditionSetIdentifier = copy.identifier;
|
||||
this.tree[parentKeyString].configuration.objectStyles[itemId].conditionSetIdentifier = copy.identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// just update parent
|
||||
const index = parent.composition.findIndex(identifier => {
|
||||
return this.openmct.objects.areIdsEqual(child.identifier, identifier);
|
||||
});
|
||||
|
||||
parent.composition[index] = copy.identifier;
|
||||
this.tree[parentKeyString].composition[index] = copy.identifier;
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} domainObject
|
||||
* @returns {string} A string representation of the given identifier, including namespace and key
|
||||
*/
|
||||
_getId(domainObject) {
|
||||
_getKeystring(domainObject) {
|
||||
return this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} domainObject
|
||||
@ -86,6 +226,7 @@ export default class ExportAsJSONAction {
|
||||
|
||||
return type && type.definition.creatable && isPersistable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} child
|
||||
@ -93,74 +234,80 @@ export default class ExportAsJSONAction {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isLinkedObject(child, parent) {
|
||||
if (child.location !== this._getId(parent)
|
||||
&& !Object.keys(this.tree).includes(child.location)
|
||||
&& this._getId(child) !== this._getId(this.root)
|
||||
|| this.externalIdentifiers.includes(this._getId(child))) {
|
||||
const rootKeyString = this._getKeystring(this.root);
|
||||
const childKeyString = this._getKeystring(child);
|
||||
const parentKeyString = this._getKeystring(parent);
|
||||
|
||||
return true;
|
||||
return (child.location !== parentKeyString
|
||||
&& !Object.keys(this.tree).includes(child.location)
|
||||
&& childKeyString !== rootKeyString)
|
||||
|| this.idMap[childKeyString] !== undefined;
|
||||
}
|
||||
|
||||
_getConditionSetIdentifier(object) {
|
||||
return object.configuration?.objectStyles?.conditionSetIdentifier;
|
||||
}
|
||||
|
||||
_hasItemConditionSetIdentifiers(parent) {
|
||||
const objectStyles = parent.configuration?.objectStyles;
|
||||
|
||||
for (const itemId in objectStyles) {
|
||||
if (Object.prototype.hasOwnProperty.call(objectStyles[itemId], 'conditionSetIdentifier')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} child
|
||||
* @param {object} parent
|
||||
* @returns {object}
|
||||
*/
|
||||
_rewriteLink(child, parent) {
|
||||
this.externalIdentifiers.push(this._getId(child));
|
||||
const index = parent.composition.findIndex(id => {
|
||||
return _.isEqual(child.identifier, id);
|
||||
});
|
||||
const copyOfChild = JSON.parse(JSON.stringify(child));
|
||||
|
||||
copyOfChild.identifier.key = uuid();
|
||||
const newIdString = this._getId(copyOfChild);
|
||||
const parentId = this._getId(parent);
|
||||
_getItemConditionSetIdentifiers(parent) {
|
||||
const objectStyles = parent.configuration?.objectStyles;
|
||||
let identifiers = new Set();
|
||||
|
||||
this.idMap[this._getId(child)] = newIdString;
|
||||
copyOfChild.location = parentId;
|
||||
parent.composition[index] = copyOfChild.identifier;
|
||||
this.tree[newIdString] = copyOfChild;
|
||||
this.tree[parentId].composition[index] = copyOfChild.identifier;
|
||||
if (objectStyles) {
|
||||
Object.keys(objectStyles).forEach(itemId => {
|
||||
if (objectStyles[itemId].conditionSetIdentifier) {
|
||||
identifiers.add(objectStyles[itemId].conditionSetIdentifier);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return copyOfChild;
|
||||
return Array.from(identifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} child
|
||||
* @param {object} parent
|
||||
* @returns {object}
|
||||
*/
|
||||
_rewriteLinkForReference(child, parent) {
|
||||
const childId = this._getId(child);
|
||||
this.externalIdentifiers.push(childId);
|
||||
const copyOfChild = JSON.parse(JSON.stringify(child));
|
||||
|
||||
copyOfChild.identifier.key = uuid();
|
||||
const newIdString = this._getId(copyOfChild);
|
||||
const parentId = this._getId(parent);
|
||||
|
||||
this.idMap[childId] = newIdString;
|
||||
copyOfChild.location = null;
|
||||
parent.configuration.objectStyles.conditionSetIdentifier = copyOfChild.identifier;
|
||||
this.tree[newIdString] = copyOfChild;
|
||||
this.tree[parentId].configuration.objectStyles.conditionSetIdentifier = copyOfChild.identifier;
|
||||
|
||||
return copyOfChild;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_rewriteReferences() {
|
||||
const oldKeyStrings = Object.keys(this.idMap);
|
||||
let treeString = JSON.stringify(this.tree);
|
||||
Object.keys(this.idMap).forEach(function (oldId) {
|
||||
const newId = this.idMap[oldId];
|
||||
treeString = treeString.split(oldId).join(newId);
|
||||
}.bind(this));
|
||||
|
||||
oldKeyStrings.forEach((oldKeyString) => {
|
||||
// this will cover keyStrings, identifiers and identifiers created
|
||||
// by hand that may be structured differently from those created with 'makeKeyString'
|
||||
const newKeyString = this.idMap[oldKeyString];
|
||||
const newIdentifier = JSON.stringify(this.openmct.objects.parseKeyString(newKeyString));
|
||||
const oldIdentifier = this.openmct.objects.parseKeyString(oldKeyString);
|
||||
const oldIdentifierNamespaceFirst = JSON.stringify(oldIdentifier);
|
||||
const oldIdentifierKeyFirst = JSON.stringify({
|
||||
key: oldIdentifier.key,
|
||||
namespace: oldIdentifier.namespace
|
||||
});
|
||||
|
||||
// replace keyStrings
|
||||
treeString = treeString.split(oldKeyString).join(newKeyString);
|
||||
|
||||
// check for namespace first identifiers, replace if necessary
|
||||
if (treeString.includes(oldIdentifierNamespaceFirst)) {
|
||||
treeString = treeString.split(oldIdentifierNamespaceFirst).join(newIdentifier);
|
||||
}
|
||||
|
||||
// check for key first identifiers, replace if necessary
|
||||
if (treeString.includes(oldIdentifierKeyFirst)) {
|
||||
treeString = treeString.split(oldIdentifierKeyFirst).join(newIdentifier);
|
||||
}
|
||||
|
||||
});
|
||||
this.tree = JSON.parse(treeString);
|
||||
}
|
||||
/**
|
||||
@ -180,70 +327,10 @@ export default class ExportAsJSONAction {
|
||||
_wrapTree() {
|
||||
return {
|
||||
"openmct": this.tree,
|
||||
"rootId": this._getId(this.root)
|
||||
"rootId": this._getKeystring(this.root)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} parent
|
||||
*/
|
||||
_write(parent) {
|
||||
this.calls++;
|
||||
//conditional object styles are not saved on the composition, so we need to check for them
|
||||
let childObjectReferenceId = parent.configuration?.objectStyles?.conditionSetIdentifier;
|
||||
|
||||
const composition = this.openmct.composition.get(parent);
|
||||
if (composition !== undefined) {
|
||||
composition.load()
|
||||
.then((children) => {
|
||||
children.forEach((child, index) => {
|
||||
// Only export if object is creatable
|
||||
if (this._isCreatableAndPersistable(child)) {
|
||||
// Prevents infinite export of self-contained objs
|
||||
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
|
||||
// If object is a link to something absent from
|
||||
// tree, generate new id and treat as new object
|
||||
if (this._isLinkedObject(child, parent)) {
|
||||
child = this._rewriteLink(child, parent);
|
||||
} else {
|
||||
this.tree[this._getId(child)] = child;
|
||||
}
|
||||
|
||||
this._write(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
this._decrementCallsAndSave();
|
||||
});
|
||||
} else if (!childObjectReferenceId) {
|
||||
this._decrementCallsAndSave();
|
||||
}
|
||||
|
||||
if (childObjectReferenceId) {
|
||||
this.openmct.objects.get(childObjectReferenceId)
|
||||
.then((child) => {
|
||||
// Only export if object is creatable
|
||||
if (this._isCreatableAndPersistable(child)) {
|
||||
// Prevents infinite export of self-contained objs
|
||||
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
|
||||
// If object is a link to something absent from
|
||||
// tree, generate new id and treat as new object
|
||||
if (this._isLinkedObject(child, parent)) {
|
||||
child = this._rewriteLinkForReference(child, parent);
|
||||
} else {
|
||||
this.tree[this._getId(child)] = child;
|
||||
}
|
||||
|
||||
this._write(child);
|
||||
}
|
||||
}
|
||||
|
||||
this._decrementCallsAndSave();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_decrementCallsAndSave() {
|
||||
this.calls--;
|
||||
if (this.calls === 0) {
|
||||
@ -251,4 +338,8 @@ export default class ExportAsJSONAction {
|
||||
this._saveAs(this._wrapTree());
|
||||
}
|
||||
}
|
||||
|
||||
_copy(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,10 @@ define([
|
||||
});
|
||||
},
|
||||
showTab: function (isEditing) {
|
||||
if (isEditing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasPersistedFilters = Boolean(domainObject?.configuration?.filters);
|
||||
const hasGlobalFilters = Boolean(domainObject?.configuration?.globalFilters);
|
||||
|
||||
|
@ -64,6 +64,11 @@ export default class CreateAction extends PropertiesAction {
|
||||
|
||||
const parentDomainObject = this.openmct.objects.toMutable(parentDomainObjectPath[0]);
|
||||
|
||||
// TODO REMOVE FOR OMM-RELEASE-5.0
|
||||
if (!parentDomainObject.identifier.namespace && parentDomainObject.key) {
|
||||
console.error(`parent namespace in key: ${parentDomainObject.key}`);
|
||||
}
|
||||
|
||||
this.domainObject.modified = Date.now();
|
||||
this.domainObject.location = this.openmct.objects.makeKeyString(parentDomainObject.identifier);
|
||||
this.domainObject.identifier.namespace = parentDomainObject.identifier.namespace;
|
||||
|
@ -322,7 +322,7 @@ export default {
|
||||
rgba(125,125,125,.2) 8px
|
||||
)`
|
||||
) : ''}`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${this.imageTranslateY / 2}px)`,
|
||||
transition: `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`,
|
||||
width: `${this.sizedImageWidth}px`,
|
||||
height: `${this.sizedImageHeight}px`
|
||||
@ -709,7 +709,7 @@ export default {
|
||||
getVisibleLayerStyles(layer) {
|
||||
return {
|
||||
backgroundImage: `url(${layer.source})`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${this.imageTranslateY / 2}px)`,
|
||||
transition: `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`
|
||||
};
|
||||
},
|
||||
|
@ -195,7 +195,7 @@
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: $interiorMarginSm;
|
||||
&.animate-scroll {
|
||||
scroll-behavior: smooth;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,7 +320,7 @@
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
left: $interiorMargin; top: $interiorMargin;
|
||||
z-index: 70;
|
||||
z-index: 10;
|
||||
background: $colorLocalControlOvrBg;
|
||||
border-radius: $basicCr;
|
||||
align-items: center;
|
||||
|
@ -31,6 +31,7 @@ export default class ImportAsJSONAction {
|
||||
this.cssClass = "icon-import";
|
||||
this.group = "json";
|
||||
this.priority = 2;
|
||||
this.newObjects = [];
|
||||
|
||||
this.openmct = openmct;
|
||||
}
|
||||
@ -85,22 +86,25 @@ export default class ImportAsJSONAction {
|
||||
let objectIdentifiers = this._getObjectReferenceIds(parent);
|
||||
|
||||
if (objectIdentifiers.length) {
|
||||
let newObj;
|
||||
const parentId = this.openmct.objects.makeKeyString(parent.identifier);
|
||||
seen.push(parentId);
|
||||
|
||||
seen.push(parent.id);
|
||||
|
||||
objectIdentifiers.forEach(async (childId) => {
|
||||
for (const childId of objectIdentifiers) {
|
||||
const keystring = this.openmct.objects.makeKeyString(childId);
|
||||
if (!tree[keystring] || seen.includes(keystring)) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
const newModel = tree[keystring];
|
||||
delete newModel.persisted;
|
||||
|
||||
newObj = await this._instantiate(newModel);
|
||||
this._deepInstantiate(newObj, tree, seen);
|
||||
}, this);
|
||||
this.newObjects.push(newModel);
|
||||
|
||||
// make sure there weren't any errors saving
|
||||
if (newModel) {
|
||||
this._deepInstantiate(newModel, tree, seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -110,19 +114,32 @@ export default class ImportAsJSONAction {
|
||||
*/
|
||||
_getObjectReferenceIds(parent) {
|
||||
let objectIdentifiers = [];
|
||||
let itemObjectReferences = [];
|
||||
const objectStyles = parent?.configuration?.objectStyles;
|
||||
const parentComposition = this.openmct.composition.get(parent);
|
||||
|
||||
let parentComposition = this.openmct.composition.get(parent);
|
||||
if (parentComposition) {
|
||||
objectIdentifiers = Array.from(parentComposition.domainObject.composition);
|
||||
objectIdentifiers = Array.from(parent.composition);
|
||||
}
|
||||
|
||||
//conditional object styles are not saved on the composition, so we need to check for them
|
||||
let parentObjectReference = parent.configuration?.objectStyles?.conditionSetIdentifier;
|
||||
if (parentObjectReference) {
|
||||
objectIdentifiers.push(parentObjectReference);
|
||||
if (objectStyles) {
|
||||
const parentObjectReference = objectStyles.conditionSetIdentifier;
|
||||
|
||||
if (parentObjectReference) {
|
||||
objectIdentifiers.push(parentObjectReference);
|
||||
}
|
||||
|
||||
function hasConditionSetIdentifier(item) {
|
||||
return Boolean(item.conditionSetIdentifier);
|
||||
}
|
||||
|
||||
itemObjectReferences = Object.values(objectStyles)
|
||||
.filter(hasConditionSetIdentifier)
|
||||
.map(item => item.conditionSetIdentifier);
|
||||
}
|
||||
|
||||
return objectIdentifiers;
|
||||
return Array.from(new Set([...objectIdentifiers, ...itemObjectReferences]));
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
@ -155,13 +172,21 @@ export default class ImportAsJSONAction {
|
||||
const tree = this._generateNewIdentifiers(objTree, namespace);
|
||||
const rootId = tree.rootId;
|
||||
|
||||
const rootModel = tree.openmct[rootId];
|
||||
delete rootModel.persisted;
|
||||
const rootObj = tree.openmct[rootId];
|
||||
delete rootObj.persisted;
|
||||
this.newObjects.push(rootObj);
|
||||
|
||||
const rootObj = await this._instantiate(rootModel);
|
||||
if (this.openmct.composition.checkPolicy(domainObject, rootObj)) {
|
||||
this._deepInstantiate(rootObj, tree.openmct, []);
|
||||
|
||||
try {
|
||||
await Promise.all(this.newObjects.map(this._instantiate, this));
|
||||
} catch (error) {
|
||||
this.openmct.notifications.error('Error saving objects');
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const compositionCollection = this.openmct.composition.get(domainObject);
|
||||
let domainObjectKeyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString);
|
||||
@ -184,16 +209,11 @@ export default class ImportAsJSONAction {
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} rootModel
|
||||
* @param {object} model
|
||||
* @returns {object}
|
||||
*/
|
||||
async _instantiate(rootModel) {
|
||||
const success = await this.openmct.objects.save(rootModel);
|
||||
if (success) {
|
||||
return rootModel;
|
||||
}
|
||||
|
||||
this.openmct.notifications.error('Error saving objects');
|
||||
_instantiate(model) {
|
||||
return this.openmct.objects.save(model);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
|
@ -23,11 +23,17 @@
|
||||
import Annotations from './AnnotationsInspectorView.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function ElementsViewProvider(openmct) {
|
||||
export default function AnnotationsViewProvider(openmct) {
|
||||
return {
|
||||
key: 'annotationsView',
|
||||
name: 'Annotations',
|
||||
canView: function (selection) {
|
||||
const availableTags = openmct.annotation.getAvailableTags();
|
||||
|
||||
if (availableTags.length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selection.length;
|
||||
},
|
||||
view: function (selection) {
|
||||
|
@ -177,10 +177,9 @@ export default {
|
||||
if (this.$refs.TagEditor) {
|
||||
const clickedInsideTagEditor = this.$refs.TagEditor.contains(event.target);
|
||||
if (!clickedInsideTagEditor) {
|
||||
// Remove last tag when user clicks outside of TagSelection
|
||||
this.addedTags.pop();
|
||||
// Hide TagSelection and show "Add Tag" button
|
||||
this.userAddingTag = false;
|
||||
this.tagsChanged();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -19,7 +19,7 @@
|
||||
class="c-icon-button c-button--menu icon-font"
|
||||
@click.prevent.stop="showFontMenu"
|
||||
>
|
||||
<span class="c-button__label">{{ fontTypeLable }}</span>
|
||||
<span class="c-button__label">{{ fontTypeLabel }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,7 +43,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fontTypeLable() {
|
||||
fontTypeLabel() {
|
||||
const fontType = FONTS.find(f => f.value === this.fontStyle.font);
|
||||
if (!fontType) {
|
||||
return '??';
|
||||
|
@ -22,7 +22,9 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="loaded"
|
||||
ref="plot"
|
||||
class="gl-plot"
|
||||
:class="{ 'js-series-data-loaded' : seriesDataLoaded }"
|
||||
>
|
||||
<slot></slot>
|
||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
||||
@ -347,6 +349,9 @@ export default {
|
||||
const parentLeftTickWidth = this.parentYTickWidth.leftTickWidth;
|
||||
|
||||
return parentLeftTickWidth || leftTickWidth;
|
||||
},
|
||||
seriesDataLoaded() {
|
||||
return ((this.pending === 0) && this.loaded);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -412,6 +417,7 @@ export default {
|
||||
this.openmct.selection.off('change', this.updateSelection);
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
document.removeEventListener('keyup', this.handleKeyUp);
|
||||
document.body.removeEventListener('click', this.cancelSelection);
|
||||
this.destroy();
|
||||
},
|
||||
methods: {
|
||||
@ -444,6 +450,19 @@ export default {
|
||||
//This section is common to all entry points for annotation display
|
||||
this.prepareExistingAnnotationSelection(selectedAnnotations);
|
||||
},
|
||||
cancelSelection(event) {
|
||||
if (this.$refs?.plot) {
|
||||
const clickedInsidePlot = this.$refs.plot.contains(event.target);
|
||||
const clickedInsideInspector = event.target.closest('.js-inspector') !== null;
|
||||
const clickedOption = event.target.closest('.js-autocomplete-options') !== null;
|
||||
if (!clickedInsidePlot && !clickedInsideInspector && !clickedOption) {
|
||||
this.rectangles = [];
|
||||
this.annotationSelections = [];
|
||||
this.selectPlot();
|
||||
document.body.removeEventListener('click', this.cancelSelection);
|
||||
}
|
||||
}
|
||||
},
|
||||
waitForAxesToLoad() {
|
||||
return new Promise(resolve => {
|
||||
// When there is no plot data, the ranges can be undefined
|
||||
@ -1276,6 +1295,8 @@ export default {
|
||||
}
|
||||
|
||||
this.openmct.selection.select(selection, true);
|
||||
|
||||
document.body.addEventListener('click', this.cancelSelection);
|
||||
},
|
||||
selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event) {
|
||||
let targetDomainObjects = {};
|
||||
|
@ -603,19 +603,20 @@ export default {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
//There has to be at least one yAxis
|
||||
const yAxisIds = [mainYAxisId].concat(this.config.additionalYAxes.map(yAxis => yAxis.get('id')));
|
||||
// Repeat drawing for all yAxes
|
||||
yAxisIds.forEach((id) => {
|
||||
if (this.canDraw(id)) {
|
||||
this.updateViewport(id);
|
||||
this.drawSeries(id);
|
||||
this.drawRectangles(id);
|
||||
this.drawHighlights(id);
|
||||
|
||||
// only draw these in fixed time mode or plot is paused
|
||||
if (this.annotationViewingAndEditingAllowed) {
|
||||
this.drawAnnotatedPoints(id);
|
||||
this.drawAnnotationSelections(id);
|
||||
}
|
||||
// Repeat drawing for all yAxes
|
||||
yAxisIds.filter(this.canDraw).forEach((id, yAxisIndex) => {
|
||||
this.updateViewport(id);
|
||||
this.drawSeries(id);
|
||||
if (yAxisIndex === 0) {
|
||||
this.drawRectangles(id);
|
||||
}
|
||||
|
||||
this.drawHighlights(id);
|
||||
// only draw these in fixed time mode or plot is paused
|
||||
if (this.annotationViewingAndEditingAllowed) {
|
||||
this.drawAnnotatedPoints(id);
|
||||
this.drawAnnotationSelections(id);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -29,7 +29,7 @@ define([
|
||||
'./TelemetryTableColumn',
|
||||
'./TelemetryTableUnitColumn',
|
||||
'./TelemetryTableConfiguration',
|
||||
'@/utils/staleness'
|
||||
'../../utils/staleness'
|
||||
], function (
|
||||
EventEmitter,
|
||||
_,
|
||||
|
@ -88,7 +88,7 @@ define([], function () {
|
||||
}
|
||||
|
||||
getContextMenuActions() {
|
||||
return ['viewDatumAction'];
|
||||
return ['viewDatumAction', 'viewHistoricalData'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,14 +175,22 @@ export default {
|
||||
getDatum() {
|
||||
return this.row.fullDatum;
|
||||
},
|
||||
showContextMenu: function (event) {
|
||||
showContextMenu: async function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.updateViewContext();
|
||||
this.markRow(event);
|
||||
|
||||
const contextualDomainObject = await this.row.getContextualDomainObject?.(this.openmct, this.row.objectKeyString);
|
||||
|
||||
let objectPath = this.objectPath;
|
||||
if (contextualDomainObject) {
|
||||
objectPath = objectPath.slice();
|
||||
objectPath.unshift(contextualDomainObject);
|
||||
}
|
||||
|
||||
const actions = this.row.getContextMenuActions().map(key => this.openmct.actions.getAction(key));
|
||||
const menuItems = this.openmct.menus.actionsToMenuItems(actions, this.objectPath, this.currentView);
|
||||
const menuItems = this.openmct.menus.actionsToMenuItems(actions, objectPath, this.currentView);
|
||||
if (menuItems.length) {
|
||||
this.openmct.menus.showMenu(event.x, event.y, menuItems);
|
||||
}
|
||||
|
@ -212,7 +212,15 @@ div.c-table {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:hover {
|
||||
background: $colorItemTreeHoverBg;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
user-select: none; // Table supports context-click to display Actions menu, don't allow text selection.
|
||||
|
||||
&.is-stale {
|
||||
@include isStaleElement();
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ export default {
|
||||
return this.domainObject && (this.currentObjectPath || this.objectPath);
|
||||
},
|
||||
objectFontStyle() {
|
||||
return this.domainObject && this.domainObject.configuration && this.domainObject.configuration.fontStyle;
|
||||
return this.domainObject?.configuration?.fontStyle;
|
||||
},
|
||||
fontSize() {
|
||||
return this.objectFontStyle ? this.objectFontStyle.fontSize : this.layoutFontSize;
|
||||
@ -286,6 +286,9 @@ export default {
|
||||
this.openmct.objectViews.on('clearData', this.clearData);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.updateStyle(this.styleRuleManager?.currentStyle);
|
||||
this.setFontSize(this.fontSize);
|
||||
this.setFont(this.font);
|
||||
this.getActionCollection();
|
||||
});
|
||||
},
|
||||
@ -328,9 +331,9 @@ export default {
|
||||
},
|
||||
initObjectStyles() {
|
||||
if (!this.styleRuleManager) {
|
||||
this.styleRuleManager = new StyleRuleManager((this.domainObject.configuration && this.domainObject.configuration.objectStyles), this.openmct, this.updateStyle.bind(this), true);
|
||||
this.styleRuleManager = new StyleRuleManager((this.domainObject.configuration?.objectStyles), this.openmct, this.updateStyle.bind(this), true);
|
||||
} else {
|
||||
this.styleRuleManager.updateObjectStyleConfig(this.domainObject.configuration && this.domainObject.configuration.objectStyles);
|
||||
this.styleRuleManager.updateObjectStyleConfig(this.domainObject.configuration?.objectStyles);
|
||||
}
|
||||
|
||||
if (this.stopListeningStyles) {
|
||||
@ -342,9 +345,6 @@ export default {
|
||||
this.styleRuleManager.updateObjectStyleConfig(newObjectStyle);
|
||||
});
|
||||
|
||||
this.setFontSize(this.fontSize);
|
||||
this.setFont(this.font);
|
||||
|
||||
this.stopListeningFontStyles = this.openmct.objects.observe(this.domainObject, 'configuration.fontStyle', (newFontStyle) => {
|
||||
this.setFontSize(newFontStyle.fontSize);
|
||||
this.setFont(newFontStyle.font);
|
||||
|
@ -61,7 +61,7 @@
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: auto; left: 0;
|
||||
z-index: 2;
|
||||
z-index: 10;
|
||||
|
||||
.c-object-label {
|
||||
visibility: hidden;
|
||||
@ -99,6 +99,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.c-so-view--flexible-layout,
|
||||
&.c-so-view--layout {
|
||||
// For sub-layouts with hidden frames, completely hide the header to avoid overlapping buttons
|
||||
> .c-so-view__header {
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-inspector">
|
||||
<div class="c-inspector js-inspector">
|
||||
<object-name />
|
||||
<InspectorTabs
|
||||
:selection="selection"
|
||||
|
@ -93,9 +93,18 @@
|
||||
:persist-position="true"
|
||||
>
|
||||
<RecentObjectsList
|
||||
ref="recentObjectsList"
|
||||
class="l-shell__tree"
|
||||
@openAndScrollTo="openAndScrollTo($event)"
|
||||
/>
|
||||
<button
|
||||
slot="controls"
|
||||
class="c-icon-button icon-clear-data"
|
||||
aria-label="Clear Recently Viewed"
|
||||
title="Clear Recently Viewed"
|
||||
@click="handleClearRecentObjects"
|
||||
>
|
||||
</button>
|
||||
</pane>
|
||||
</multipane>
|
||||
</pane>
|
||||
@ -279,6 +288,9 @@ export default {
|
||||
handleTreeReset() {
|
||||
this.triggerReset = !this.triggerReset;
|
||||
},
|
||||
handleClearRecentObjects() {
|
||||
this.$refs.recentObjectsList.clearRecentObjects();
|
||||
},
|
||||
onStartResizing() {
|
||||
this.isResizing = true;
|
||||
},
|
||||
|
@ -191,6 +191,33 @@ export default {
|
||||
shouldTrackCompositionFor(domainObject, navigationPath) {
|
||||
return this.compositionCollections[navigationPath] === undefined
|
||||
&& this.openmct.composition.supportsComposition(domainObject);
|
||||
},
|
||||
/**
|
||||
* Clears the Recent Objects list in localStorage and in the component.
|
||||
* Before clearing, prompts the user to confirm the action with a dialog.
|
||||
*/
|
||||
clearRecentObjects() {
|
||||
const dialog = this.openmct.overlays.dialog({
|
||||
title: 'Clear Recently Viewed Objects',
|
||||
iconClass: 'alert',
|
||||
message: 'This action will clear the Recently Viewed Objects list. Are you sure you want to continue?',
|
||||
buttons: [
|
||||
{
|
||||
label: 'OK',
|
||||
callback: () => {
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEY__RECENT_OBJECTS);
|
||||
this.recents = [];
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -322,10 +322,14 @@ export default {
|
||||
},
|
||||
async openTreeItem(parentItem) {
|
||||
const parentPath = parentItem.navigationPath;
|
||||
const abortSignal = this.startItemLoad(parentPath);
|
||||
|
||||
this.startItemLoad(parentPath);
|
||||
// pass in abort signal when functional
|
||||
const childrenItems = await this.loadAndBuildTreeItemsFor(parentItem.object.identifier, parentItem.objectPath);
|
||||
const childrenItems = await this.loadAndBuildTreeItemsFor(
|
||||
parentItem.object.identifier,
|
||||
parentItem.objectPath,
|
||||
abortSignal
|
||||
);
|
||||
const parentIndex = this.treeItems.indexOf(parentItem);
|
||||
|
||||
// if it's not loading, it was aborted
|
||||
@ -355,17 +359,18 @@ export default {
|
||||
this.abortItemLoad(path);
|
||||
}
|
||||
|
||||
let pathIndex = this.openTreeItems.indexOf(path);
|
||||
const pathIndex = this.openTreeItems.indexOf(path);
|
||||
|
||||
if (pathIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.treeItems = this.treeItems.filter((checkItem) => {
|
||||
if (checkItem.navigationPath !== path
|
||||
&& checkItem.navigationPath.includes(path)) {
|
||||
this.destroyObserverByPath(checkItem.navigationPath);
|
||||
this.destroyMutableByPath(checkItem.navigationPath);
|
||||
this.treeItems = this.treeItems.filter((item) => {
|
||||
const otherPath = item.navigationPath;
|
||||
if (otherPath !== path
|
||||
&& this.isTreeItemAChildOf(otherPath, path)) {
|
||||
this.destroyObserverByPath(otherPath);
|
||||
this.destroyMutableByPath(otherPath);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -960,6 +965,24 @@ export default {
|
||||
isTreeItemPathOpen(path) {
|
||||
return this.openTreeItems.includes(path);
|
||||
},
|
||||
isTreeItemAChildOf(childNavigationPath, parentNavigationPath) {
|
||||
const childPathKeys = childNavigationPath.split('/');
|
||||
const parentPathKeys = parentNavigationPath.split('/');
|
||||
|
||||
// If child path is shorter than or same length as
|
||||
// the parent path, then it's not a child.
|
||||
if (childPathKeys.length <= parentPathKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < parentPathKeys.length; i++) {
|
||||
if (childPathKeys[i] !== parentPathKeys[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
getElementStyleValue(el, style) {
|
||||
if (!el) {
|
||||
return;
|
||||
|
@ -49,9 +49,19 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
highlightedText() {
|
||||
let regex = new RegExp(`(?<!<[^>]*)(${this.highlight})`, 'gi');
|
||||
const highlight = this.highlight;
|
||||
|
||||
return this.text.replace(regex, `<span class="${this.highlightClass}">${this.highlight}</span>`);
|
||||
const normalCharsRegex = /^[^A-Za-z0-9]+$/g;
|
||||
|
||||
const newHighLight = normalCharsRegex.test(highlight)
|
||||
? `\\${highlight}`
|
||||
: highlight;
|
||||
|
||||
const highlightRegex = new RegExp(`(?<!<[^>]*)(${newHighLight})`, 'gi');
|
||||
|
||||
const replacement = `<span class="${this.highlightClass}">${highlight}</span>`;
|
||||
|
||||
return this.text.replace(highlightRegex, replacement);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user