mirror of
https://github.com/nasa/openmct.git
synced 2025-07-03 05:22:52 +00:00
Compare commits
118 Commits
fix-plot-z
...
telemetry-
Author | SHA1 | Date | |
---|---|---|---|
67703902ec | |||
28b5d7c41c | |||
ecd120387c | |||
a6517bb33e | |||
1fde0d9e38 | |||
5e15af120b | |||
5be103ea72 | |||
d74e1b19b6 | |||
5bb6a18cd4 | |||
14b947c101 | |||
61b982ab99 | |||
ba4d8a428b | |||
5683320bea | |||
64ae96e602 | |||
55063a0045 | |||
9ca489cf12 | |||
4a4dabf7f0 | |||
96b1ef0db5 | |||
1516524f0b | |||
51f73bbfbe | |||
00c8c0e095 | |||
7d46afbced | |||
6710ad0d4c | |||
d5ee430f8b | |||
3b78d4d2bf | |||
4de03542f8 | |||
3b06c7749e | |||
3be414e2e7 | |||
85a77699e6 | |||
d6f50567bc | |||
bf9d5ef4a7 | |||
7c01a5e0d7 | |||
ba7f2919c0 | |||
2de628adac | |||
8e7bfd080d | |||
c6806944eb | |||
3f92deb896 | |||
395436a361 | |||
d859322a47 | |||
58d6cdb574 | |||
f718ccdf4e | |||
38316bd2f5 | |||
4bbbd17b61 | |||
154e8c695d | |||
8e5ac68360 | |||
1a9401039e | |||
5e013b6aa8 | |||
eb5d32c2a0 | |||
4a301a15d2 | |||
dfcfa47237 | |||
ee612a6b5a | |||
c1a361db5f | |||
e02217ad63 | |||
60e07e642b | |||
8b4eed938a | |||
dd57d78c4f | |||
89e18d482d | |||
b251fde1fc | |||
1b186d7596 | |||
59461d6b06 | |||
1615c36b7e | |||
80d8babb61 | |||
fdcece8e0e | |||
cde34e3c6d | |||
dde0d1a64d | |||
1996e66891 | |||
06e916ea20 | |||
fb8730cf22 | |||
9aeb454fb6 | |||
02cf70135e | |||
eac4676cad | |||
e1f50feda0 | |||
f8ceaa5a83 | |||
1d686c2926 | |||
2793da40ec | |||
cce30084ee | |||
4260cc3603 | |||
3ad21ee01e | |||
ccc12deacd | |||
40a95cfa08 | |||
5894363ba5 | |||
1c68c7e044 | |||
5af6413e01 | |||
386c3b4131 | |||
28ec13a532 | |||
d96c3fc537 | |||
58568b849e | |||
e4dcda80f1 | |||
a9d63b9272 | |||
d4b2986651 | |||
d7d79130ac | |||
d6c8beeeac | |||
4d546fb63f | |||
aa8750eb97 | |||
446c8119c3 | |||
484a81b370 | |||
df3ca84fda | |||
87dc272a5a | |||
dcc893880b | |||
9e3e7394d2 | |||
f174515c9e | |||
413338d3e2 | |||
0326e38f5d | |||
e5719fc71b | |||
a0a2ead5e7 | |||
0113ec08db | |||
a94c752616 | |||
55c8609953 | |||
37d222fd87 | |||
90a24b380f | |||
69db534042 | |||
ce5e435ae0 | |||
74c3a95ca3 | |||
ff814544c6 | |||
0dd04426f5 | |||
f5d7a33915 | |||
6c74e84e11 | |||
431600342f |
@ -483,7 +483,9 @@
|
|||||||
"countup",
|
"countup",
|
||||||
"darkmatter",
|
"darkmatter",
|
||||||
"Undeletes",
|
"Undeletes",
|
||||||
"SSSZ"
|
"SSSZ",
|
||||||
|
"LOCF",
|
||||||
|
"pageerror"
|
||||||
],
|
],
|
||||||
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"],
|
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"],
|
||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
|
@ -14,7 +14,8 @@ const config = {
|
|||||||
__OPENMCT_VERSION__: 'readonly',
|
__OPENMCT_VERSION__: 'readonly',
|
||||||
__OPENMCT_BUILD_DATE__: 'readonly',
|
__OPENMCT_BUILD_DATE__: 'readonly',
|
||||||
__OPENMCT_REVISION__: 'readonly',
|
__OPENMCT_REVISION__: 'readonly',
|
||||||
__OPENMCT_BUILD_BRANCH__: 'readonly'
|
__OPENMCT_BUILD_BRANCH__: 'readonly',
|
||||||
|
__OPENMCT_ROOT_RELATIVE__: 'readonly'
|
||||||
},
|
},
|
||||||
plugins: ['prettier', 'unicorn', 'simple-import-sort'],
|
plugins: ['prettier', 'unicorn', 'simple-import-sort'],
|
||||||
extends: [
|
extends: [
|
||||||
|
8
.github/workflows/e2e-couchdb.yml
vendored
8
.github/workflows/e2e-couchdb.yml
vendored
@ -66,15 +66,19 @@ jobs:
|
|||||||
|
|
||||||
- name: Archive test results
|
- name: Archive test results
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
name: e2e-couchdb-test-results
|
||||||
path: test-results
|
path: test-results
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
- name: Archive html test results
|
- name: Archive html test results
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
name: e2e-couchdb-html-test-results
|
||||||
path: html-test-results
|
path: html-test-results
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
- name: Remove pr:e2e:couchdb label (if present)
|
- name: Remove pr:e2e:couchdb label (if present)
|
||||||
if: always()
|
if: always()
|
||||||
|
4
.github/workflows/e2e-flakefinder.yml
vendored
4
.github/workflows/e2e-flakefinder.yml
vendored
@ -38,9 +38,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Archive test results
|
- name: Archive test results
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
name: e2e-flakefinder-test-results
|
||||||
path: test-results
|
path: test-results
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
- name: Remove pr:e2e:flakefinder label (if present)
|
- name: Remove pr:e2e:flakefinder label (if present)
|
||||||
if: always()
|
if: always()
|
||||||
|
4
.github/workflows/e2e-perf.yml
vendored
4
.github/workflows/e2e-perf.yml
vendored
@ -35,9 +35,11 @@ jobs:
|
|||||||
- run: npm run test:perf:memory
|
- run: npm run test:perf:memory
|
||||||
- name: Archive test results
|
- name: Archive test results
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
name: e2e-perf-test-results
|
||||||
path: test-results
|
path: test-results
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
- name: Remove pr:e2e:perf label (if present)
|
- name: Remove pr:e2e:perf label (if present)
|
||||||
if: always()
|
if: always()
|
||||||
|
4
.github/workflows/e2e-pr.yml
vendored
4
.github/workflows/e2e-pr.yml
vendored
@ -45,9 +45,11 @@ jobs:
|
|||||||
npm run cov:e2e:full:publish
|
npm run cov:e2e:full:publish
|
||||||
- name: Archive test results
|
- name: Archive test results
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
name: e2e-pr-test-results
|
||||||
path: test-results
|
path: test-results
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
- name: Remove pr:e2e label (if present)
|
- name: Remove pr:e2e label (if present)
|
||||||
if: always()
|
if: always()
|
||||||
|
@ -48,6 +48,7 @@ const config = {
|
|||||||
generatorWorker: './example/generator/generatorWorker.js',
|
generatorWorker: './example/generator/generatorWorker.js',
|
||||||
couchDBChangesFeed: './src/plugins/persistence/couch/CouchChangesFeed.js',
|
couchDBChangesFeed: './src/plugins/persistence/couch/CouchChangesFeed.js',
|
||||||
inMemorySearchWorker: './src/api/objects/InMemorySearchWorker.js',
|
inMemorySearchWorker: './src/api/objects/InMemorySearchWorker.js',
|
||||||
|
compsMathWorker: './src/plugins/comps/CompsMathWorker.js',
|
||||||
espressoTheme: './src/plugins/themes/espresso-theme.scss',
|
espressoTheme: './src/plugins/themes/espresso-theme.scss',
|
||||||
snowTheme: './src/plugins/themes/snow-theme.scss',
|
snowTheme: './src/plugins/themes/snow-theme.scss',
|
||||||
darkmatterTheme: './src/plugins/themes/darkmatter-theme.scss'
|
darkmatterTheme: './src/plugins/themes/darkmatter-theme.scss'
|
||||||
@ -89,7 +90,8 @@ const config = {
|
|||||||
__OPENMCT_REVISION__: `'${gitRevision}'`,
|
__OPENMCT_REVISION__: `'${gitRevision}'`,
|
||||||
__OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`,
|
__OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`,
|
||||||
__VUE_OPTIONS_API__: true, // enable/disable Options API support, default: true
|
__VUE_OPTIONS_API__: true, // enable/disable Options API support, default: true
|
||||||
__VUE_PROD_DEVTOOLS__: false // enable/disable devtools support in production, default: false
|
__VUE_PROD_DEVTOOLS__: false, // enable/disable devtools support in production, default: false
|
||||||
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, // enable/disable hydration mismatch details in production, default: false
|
||||||
}),
|
}),
|
||||||
new VueLoaderPlugin(),
|
new VueLoaderPlugin(),
|
||||||
new CopyWebpackPlugin({
|
new CopyWebpackPlugin({
|
||||||
|
@ -682,6 +682,21 @@ async function linkParameterToObject(page, parameterName, objectName) {
|
|||||||
await page.getByLabel('Save').click();
|
await page.getByLabel('Save').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename the currently viewed `domainObject` from the browse bar.
|
||||||
|
*
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {string} newName
|
||||||
|
*/
|
||||||
|
async function renameCurrentObjectFromBrowseBar(page, newName) {
|
||||||
|
const nameInput = page.getByLabel('Browse bar object name');
|
||||||
|
await nameInput.click();
|
||||||
|
await nameInput.fill('');
|
||||||
|
await nameInput.fill(newName);
|
||||||
|
// Click the browse bar container to save changes
|
||||||
|
await page.getByLabel('Browse bar', { exact: true }).click();
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
createExampleTelemetryObject,
|
createExampleTelemetryObject,
|
||||||
@ -693,6 +708,7 @@ export {
|
|||||||
linkParameterToObject,
|
linkParameterToObject,
|
||||||
navigateToObjectWithFixedTimeBounds,
|
navigateToObjectWithFixedTimeBounds,
|
||||||
navigateToObjectWithRealTime,
|
navigateToObjectWithRealTime,
|
||||||
|
renameCurrentObjectFromBrowseBar,
|
||||||
setEndOffset,
|
setEndOffset,
|
||||||
setFixedIndependentTimeConductorBounds,
|
setFixedIndependentTimeConductorBounds,
|
||||||
setFixedTimeMode,
|
setFixedTimeMode,
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../appActions.js';
|
||||||
|
import { MISSION_TIME } from '../../../constants.js';
|
||||||
|
import { expect, test } from '../../../pluginFixtures.js';
|
||||||
|
|
||||||
|
const TELEMETRY_RATE = 2500;
|
||||||
|
|
||||||
|
test.describe('Example Event Generator Acknowledge with Controlled Clock @clock', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.clock.install({ time: MISSION_TIME });
|
||||||
|
await page.clock.resume();
|
||||||
|
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Event Message Generator with Acknowledge'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rows are updatable in place', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7938'
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('First telemetry datum gets added as new row', async () => {
|
||||||
|
await page.clock.fastForward(TELEMETRY_RATE);
|
||||||
|
const rows = page.getByLabel('table content').getByLabel('Table Row');
|
||||||
|
const acknowledgeCell = rows.first().getByLabel('acknowledge table cell');
|
||||||
|
|
||||||
|
await expect(rows).toHaveCount(1);
|
||||||
|
await expect(acknowledgeCell).not.toHaveAttribute('title', 'OK');
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Incoming Telemetry datum matching an existing rows in place update key has data merged to existing row', async () => {
|
||||||
|
await page.clock.fastForward(TELEMETRY_RATE * 2);
|
||||||
|
const rows = page.getByLabel('table content').getByLabel('Table Row');
|
||||||
|
const acknowledgeCell = rows.first().getByLabel('acknowledge table cell');
|
||||||
|
|
||||||
|
await expect(rows).toHaveCount(1);
|
||||||
|
await expect(acknowledgeCell).toHaveAttribute('title', 'OK');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -24,7 +24,9 @@ import {
|
|||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
createPlanFromJSON,
|
createPlanFromJSON,
|
||||||
navigateToObjectWithFixedTimeBounds,
|
navigateToObjectWithFixedTimeBounds,
|
||||||
setFixedIndependentTimeConductorBounds
|
setFixedIndependentTimeConductorBounds,
|
||||||
|
setFixedTimeMode,
|
||||||
|
setTimeConductorBounds
|
||||||
} from '../../../appActions.js';
|
} from '../../../appActions.js';
|
||||||
import { expect, test } from '../../../pluginFixtures.js';
|
import { expect, test } from '../../../pluginFixtures.js';
|
||||||
|
|
||||||
@ -74,21 +76,14 @@ const testPlan = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
test.describe('Time Strip', () => {
|
test.describe('Time Strip', () => {
|
||||||
test('Create two Time Strips, add a single Plan to both, and verify they can have separate Independent Time Contexts', async ({
|
let timestrip;
|
||||||
page
|
let plan;
|
||||||
}) => {
|
|
||||||
test.info().annotations.push({
|
|
||||||
type: 'issue',
|
|
||||||
description: 'https://github.com/nasa/openmct/issues/5627'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Constant locators
|
|
||||||
const activityBounds = page.locator('.activity-bounds');
|
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
// Goto baseURL
|
// Goto baseURL
|
||||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
const timestrip = await test.step('Create a Time Strip', async () => {
|
timestrip = await test.step('Create a Time Strip', async () => {
|
||||||
const createdTimeStrip = await createDomainObjectWithDefaults(page, { type: 'Time Strip' });
|
const createdTimeStrip = await createDomainObjectWithDefaults(page, { type: 'Time Strip' });
|
||||||
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
|
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
|
||||||
expect(objectName).toBe(createdTimeStrip.name);
|
expect(objectName).toBe(createdTimeStrip.name);
|
||||||
@ -96,7 +91,7 @@ test.describe('Time Strip', () => {
|
|||||||
return createdTimeStrip;
|
return createdTimeStrip;
|
||||||
});
|
});
|
||||||
|
|
||||||
const plan = await test.step('Create a Plan and add it to the timestrip', async () => {
|
plan = await test.step('Create a Plan and add it to the timestrip', async () => {
|
||||||
const createdPlan = await createPlanFromJSON(page, {
|
const createdPlan = await createPlanFromJSON(page, {
|
||||||
name: 'Test Plan',
|
name: 'Test Plan',
|
||||||
json: testPlan
|
json: testPlan
|
||||||
@ -110,6 +105,22 @@ test.describe('Time Strip', () => {
|
|||||||
.dragTo(page.getByLabel('Object View'));
|
.dragTo(page.getByLabel('Object View'));
|
||||||
await page.getByLabel('Save').click();
|
await page.getByLabel('Save').click();
|
||||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
|
return createdPlan;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Create two Time Strips, add a single Plan to both, and verify they can have separate Independent Time Contexts', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/5627'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Constant locators
|
||||||
|
const activityBounds = page.locator('.activity-bounds');
|
||||||
|
|
||||||
|
await test.step('Set time strip to fixed timespan mode and verify activities', async () => {
|
||||||
const startBound = testPlan.TEST_GROUP[0].start;
|
const startBound = testPlan.TEST_GROUP[0].start;
|
||||||
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
|
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
|
||||||
|
|
||||||
@ -119,8 +130,6 @@ test.describe('Time Strip', () => {
|
|||||||
// Verify all events are displayed
|
// Verify all events are displayed
|
||||||
const eventCount = await page.locator('.activity-bounds').count();
|
const eventCount = await page.locator('.activity-bounds').count();
|
||||||
expect(eventCount).toEqual(testPlan.TEST_GROUP.length);
|
expect(eventCount).toEqual(testPlan.TEST_GROUP.length);
|
||||||
|
|
||||||
return createdPlan;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('TimeStrip can use the Independent Time Conductor', async () => {
|
await test.step('TimeStrip can use the Independent Time Conductor', async () => {
|
||||||
@ -177,4 +186,48 @@ test.describe('Time Strip', () => {
|
|||||||
expect(await activityBounds.count()).toEqual(1);
|
expect(await activityBounds.count()).toEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Time strip now line', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7817'
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Is displayed in realtime mode', async () => {
|
||||||
|
await expect(page.getByLabel('Now Marker')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Is hidden when out of bounds of the time axis', async () => {
|
||||||
|
// Switch to fixed timespan mode
|
||||||
|
await setFixedTimeMode(page);
|
||||||
|
// Get the end bounds
|
||||||
|
const endBounds = await page.getByLabel('End bounds').textContent();
|
||||||
|
|
||||||
|
// Add 2 minutes to end bound datetime and use it as the new end time
|
||||||
|
let endTimeStamp = new Date(endBounds);
|
||||||
|
endTimeStamp.setUTCMinutes(endTimeStamp.getUTCMinutes() + 2);
|
||||||
|
const endDate = endTimeStamp.toISOString().split('T')[0];
|
||||||
|
const milliseconds = endTimeStamp.getMilliseconds();
|
||||||
|
const endTime = endTimeStamp.toISOString().split('T')[1].replace(`.${milliseconds}Z`, '');
|
||||||
|
|
||||||
|
// Subtract 1 minute from the end bound and use it as the new start time
|
||||||
|
let startTimeStamp = new Date(endBounds);
|
||||||
|
startTimeStamp.setUTCMinutes(startTimeStamp.getUTCMinutes() + 1);
|
||||||
|
const startDate = startTimeStamp.toISOString().split('T')[0];
|
||||||
|
const startMilliseconds = startTimeStamp.getMilliseconds();
|
||||||
|
const startTime = startTimeStamp
|
||||||
|
.toISOString()
|
||||||
|
.split('T')[1]
|
||||||
|
.replace(`.${startMilliseconds}Z`, '');
|
||||||
|
// Set fixed timespan mode to the future so that "now" is out of bounds.
|
||||||
|
await setTimeConductorBounds(page, {
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
startTime,
|
||||||
|
endTime
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByLabel('Now Marker')).toBeHidden();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
111
e2e/tests/functional/plugins/comps/comps.e2e.spec.js
Normal file
111
e2e/tests/functional/plugins/comps/comps.e2e.spec.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import {
|
||||||
|
createDomainObjectWithDefaults,
|
||||||
|
createExampleTelemetryObject,
|
||||||
|
setRealTimeMode
|
||||||
|
} from '../../../../appActions.js';
|
||||||
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
|
test.describe('Comps', () => {
|
||||||
|
test.use({ failOnConsoleError: false });
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Open a browser, navigate to the main page, and wait until all networkevents to resolve
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Basic Functionality Works', async ({ page, openmctConfig }) => {
|
||||||
|
const folder = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Folder'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the comps with defaults
|
||||||
|
const comp = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Derived Telemetry',
|
||||||
|
parent: folder.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
const telemetryObject = await createExampleTelemetryObject(page, comp.uuid);
|
||||||
|
|
||||||
|
// Check that expressions can be edited
|
||||||
|
await page.goto(comp.url);
|
||||||
|
await page.getByLabel('Edit Object').click();
|
||||||
|
await page.getByPlaceholder('Enter an expression').fill('a*2');
|
||||||
|
await page.getByText('Current Output').click();
|
||||||
|
await expect(page.getByText('Expression valid')).toBeVisible();
|
||||||
|
|
||||||
|
// Check that expressions are marked invalid
|
||||||
|
await page.getByLabel('Reference Name Input for a').fill('b');
|
||||||
|
await page.getByText('Current Output').click();
|
||||||
|
await expect(page.getByText('Invalid: Undefined symbol a')).toBeVisible();
|
||||||
|
|
||||||
|
// Check that test data works
|
||||||
|
await page.getByPlaceholder('Enter an expression').fill('b*2');
|
||||||
|
await page.getByLabel('Reference Test Value for b').fill('5');
|
||||||
|
await page.getByLabel('Apply Test Data').click();
|
||||||
|
let testValue = await page.getByLabel('Current Output Value').textContent();
|
||||||
|
expect(testValue).toBe('10');
|
||||||
|
|
||||||
|
// Check that real data works
|
||||||
|
await page.getByLabel('Apply Test Data').click();
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
testValue = await page.getByLabel('Current Output Value').textContent();
|
||||||
|
expect(testValue).not.toBe('10');
|
||||||
|
// should be a number
|
||||||
|
expect(parseFloat(testValue)).not.toBeNaN();
|
||||||
|
|
||||||
|
// Check that object path is correct
|
||||||
|
const { myItemsFolderName } = openmctConfig;
|
||||||
|
let objectPath = await page.getByLabel(`${telemetryObject.name} Object Path`).textContent();
|
||||||
|
const expectedObjectPath = `/${myItemsFolderName}/${folder.name}/${comp.name}/${telemetryObject.name}`;
|
||||||
|
expect(objectPath).toBe(expectedObjectPath);
|
||||||
|
|
||||||
|
// Check that the comps are saved
|
||||||
|
await page.getByLabel('Save').click();
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
const expression = await page.getByLabel('Expression', { exact: true }).textContent();
|
||||||
|
expect(expression).toBe('b*2');
|
||||||
|
|
||||||
|
// Check that object path is still correct after save
|
||||||
|
objectPath = await page.getByLabel(`${telemetryObject.name} Object Path`).textContent();
|
||||||
|
expect(objectPath).toBe(expectedObjectPath);
|
||||||
|
|
||||||
|
// Check that comps work after being saved
|
||||||
|
testValue = await page.getByLabel('Current Output Value').textContent();
|
||||||
|
expect(testValue).not.toBe('10');
|
||||||
|
// should be a number
|
||||||
|
expect(parseFloat(testValue)).not.toBeNaN();
|
||||||
|
|
||||||
|
// Check that output format can be changed
|
||||||
|
await page.getByLabel('Edit Object').click();
|
||||||
|
await page.getByRole('tab', { name: 'Config' }).click();
|
||||||
|
await page.getByLabel('Output Format').click();
|
||||||
|
await page.getByLabel('Output Format').fill('%d');
|
||||||
|
await page.getByRole('tab', { name: 'Config' }).click();
|
||||||
|
// Ensure we only have one digit
|
||||||
|
await expect(page.getByLabel('Current Output Value')).toHaveText(/^-1$|^0$|^1$/);
|
||||||
|
// And that it persists post save
|
||||||
|
await page.getByLabel('Save').click();
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
await expect(page.getByLabel('Current Output Value')).toHaveText(/^-1$|^0$|^1$/);
|
||||||
|
});
|
||||||
|
});
|
@ -507,8 +507,140 @@ test.describe('Display Layout', () => {
|
|||||||
// In real time mode, we don't fetch annotations at all
|
// In real time mode, we don't fetch annotations at all
|
||||||
await expect.poll(() => networkRequests, { timeout: 10000 }).toHaveLength(0);
|
await expect.poll(() => networkRequests, { timeout: 10000 }).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Same objects with different request options have unique subscriptions', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
// Expand My Items
|
||||||
|
await page.getByLabel('Expand My Items folder').click();
|
||||||
|
|
||||||
|
// Create a Display Layout
|
||||||
|
const displayLayout = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Display Layout',
|
||||||
|
name: 'Test Display'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a State Generator, set to higher frequency updates
|
||||||
|
const stateGenerator = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'State Generator',
|
||||||
|
name: 'State Generator'
|
||||||
|
});
|
||||||
|
const stateGeneratorTreeItem = page.getByRole('treeitem', {
|
||||||
|
name: stateGenerator.name
|
||||||
|
});
|
||||||
|
await stateGeneratorTreeItem.click({ button: 'right' });
|
||||||
|
await page.getByLabel('Edit Properties...').click();
|
||||||
|
await page.getByLabel('State Duration (seconds)', { exact: true }).fill('0.1');
|
||||||
|
await page.getByLabel('Save').click();
|
||||||
|
|
||||||
|
// Create a Table for filtering ON values
|
||||||
|
const tableFilterOnValue = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Telemetry Table',
|
||||||
|
name: 'Table Filter On Value'
|
||||||
|
});
|
||||||
|
const tableFilterOnTreeItem = page.getByRole('treeitem', {
|
||||||
|
name: tableFilterOnValue.name
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a Table for filtering OFF values
|
||||||
|
const tableFilterOffValue = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Telemetry Table',
|
||||||
|
name: 'Table Filter Off Value'
|
||||||
|
});
|
||||||
|
const tableFilterOffTreeItem = page.getByRole('treeitem', {
|
||||||
|
name: tableFilterOffValue.name
|
||||||
|
});
|
||||||
|
|
||||||
|
// Navigate to ON filtering table and add state generator and setup filters
|
||||||
|
await page.goto(tableFilterOnValue.url);
|
||||||
|
await stateGeneratorTreeItem.dragTo(page.getByLabel('Object View'));
|
||||||
|
await selectFilterOption(page, '1');
|
||||||
|
await page.getByLabel('Save').click();
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
|
// Navigate to OFF filtering table and add state generator and setup filters
|
||||||
|
await page.goto(tableFilterOffValue.url);
|
||||||
|
await stateGeneratorTreeItem.dragTo(page.getByLabel('Object View'));
|
||||||
|
await selectFilterOption(page, '0');
|
||||||
|
await page.getByLabel('Save').click();
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
|
// Navigate to the display layout and edit it
|
||||||
|
await page.goto(displayLayout.url);
|
||||||
|
|
||||||
|
// Add the tables to the display layout
|
||||||
|
await page.getByLabel('Edit Object').click();
|
||||||
|
await tableFilterOffTreeItem.dragTo(page.getByLabel('Layout Grid'), {
|
||||||
|
targetPosition: { x: 10, y: 300 }
|
||||||
|
});
|
||||||
|
await page.locator('.c-frame-edit > div:nth-child(4)').dragTo(page.getByLabel('Layout Grid'), {
|
||||||
|
targetPosition: { x: 400, y: 500 },
|
||||||
|
// eslint-disable-next-line playwright/no-force-option
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
await tableFilterOnTreeItem.dragTo(page.getByLabel('Layout Grid'), {
|
||||||
|
targetPosition: { x: 10, y: 100 }
|
||||||
|
});
|
||||||
|
await page.locator('.c-frame-edit > div:nth-child(4)').dragTo(page.getByLabel('Layout Grid'), {
|
||||||
|
targetPosition: { x: 400, y: 300 },
|
||||||
|
// eslint-disable-next-line playwright/no-force-option
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
await page.getByLabel('Save').click();
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
|
// Get the tables so we can verify filtering is working as expected
|
||||||
|
const tableFilterOn = page.getByLabel(`${tableFilterOnValue.name} Frame`, {
|
||||||
|
exact: true
|
||||||
|
});
|
||||||
|
const tableFilterOff = page.getByLabel(`${tableFilterOffValue.name} Frame`, {
|
||||||
|
exact: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify filtering is working correctly
|
||||||
|
|
||||||
|
// Check that no filtered values appear for at least 2 seconds
|
||||||
|
const VERIFICATION_TIME = 2000; // 2 seconds
|
||||||
|
const CHECK_INTERVAL = 100; // Check every 100ms
|
||||||
|
|
||||||
|
// Create a promise that will check for filtered values periodically
|
||||||
|
const checkForCorrectValues = new Promise((resolve, reject) => {
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
const offCount = await tableFilterOn.locator('td[title="OFF"]').count();
|
||||||
|
const onCount = await tableFilterOff.locator('td[title="ON"]').count();
|
||||||
|
if (offCount > 0 || onCount > 0) {
|
||||||
|
clearInterval(interval);
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
`Found ${offCount} OFF and ${onCount} ON values when expecting 0 OFF and 0 ON`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, CHECK_INTERVAL);
|
||||||
|
|
||||||
|
// After VERIFICATION_TIME, if no filtered values were found, resolve successfully
|
||||||
|
setTimeout(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
resolve();
|
||||||
|
}, VERIFICATION_TIME);
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(checkForCorrectValues).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function selectFilterOption(page, filterOption) {
|
||||||
|
await page.getByRole('tab', { name: 'Filters' }).click();
|
||||||
|
await page
|
||||||
|
.getByLabel('Inspector Views')
|
||||||
|
.locator('li')
|
||||||
|
.filter({ hasText: 'State Generator' })
|
||||||
|
.locator('span')
|
||||||
|
.click();
|
||||||
|
await page.getByRole('switch').click();
|
||||||
|
await page.selectOption('select[name="setSelectionThreshold"]', filterOption);
|
||||||
|
}
|
||||||
|
|
||||||
async function addAndRemoveDrawingObjectAndAssert(page, layoutObject, DISPLAY_LAYOUT_NAME) {
|
async function addAndRemoveDrawingObjectAndAssert(page, layoutObject, DISPLAY_LAYOUT_NAME) {
|
||||||
await expect(page.getByLabel(layoutObject, { exact: true })).toHaveCount(0);
|
await expect(page.getByLabel(layoutObject, { exact: true })).toHaveCount(0);
|
||||||
await addLayoutObject(page, DISPLAY_LAYOUT_NAME, layoutObject);
|
await addLayoutObject(page, DISPLAY_LAYOUT_NAME, layoutObject);
|
||||||
|
@ -28,7 +28,9 @@ import { v4 as uuid } from 'uuid';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
createExampleTelemetryObject
|
createExampleTelemetryObject,
|
||||||
|
setRealTimeMode,
|
||||||
|
setStartOffset
|
||||||
} from '../../../../appActions.js';
|
} from '../../../../appActions.js';
|
||||||
import { expect, test } from '../../../../pluginFixtures.js';
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
@ -166,6 +168,57 @@ test.describe('Gauge', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Gauge does not break when an object is missing', async ({ page }) => {
|
||||||
|
// Set up error listeners
|
||||||
|
const pageErrors = [];
|
||||||
|
|
||||||
|
// Listen for uncaught exceptions
|
||||||
|
page.on('pageerror', (err) => {
|
||||||
|
pageErrors.push(err.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
|
||||||
|
// Create a Gauge
|
||||||
|
const gauge = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Gauge',
|
||||||
|
name: 'Gauge with missing object'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a Sine Wave Generator in the Gauge with a loading delay
|
||||||
|
const missingSWG = await createExampleTelemetryObject(page, gauge.uuid);
|
||||||
|
|
||||||
|
// Remove the object from local storage
|
||||||
|
await page.evaluate(
|
||||||
|
([missingObject]) => {
|
||||||
|
const mct = localStorage.getItem('mct');
|
||||||
|
const mctObjects = JSON.parse(mct);
|
||||||
|
delete mctObjects[missingObject.uuid];
|
||||||
|
localStorage.setItem('mct', JSON.stringify(mctObjects));
|
||||||
|
},
|
||||||
|
[missingSWG]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify start bounds
|
||||||
|
await expect(page.getByLabel('Start offset: 00:30:00')).toBeVisible();
|
||||||
|
|
||||||
|
// Nav to the Gauge
|
||||||
|
await page.goto(gauge.url, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
// adjust time bounds and ensure they are updated
|
||||||
|
await setStartOffset(page, {
|
||||||
|
startHours: '00',
|
||||||
|
startMins: '45',
|
||||||
|
startSecs: '00'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify start bounds changed
|
||||||
|
await expect(page.getByLabel('Start offset: 00:45:00')).toBeVisible();
|
||||||
|
|
||||||
|
// // Verify no errors were thrown
|
||||||
|
expect(pageErrors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
test('Gauge enforces composition policy', async ({ page }) => {
|
test('Gauge enforces composition policy', async ({ page }) => {
|
||||||
// Create a Gauge
|
// Create a Gauge
|
||||||
await createDomainObjectWithDefaults(page, {
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
@ -26,7 +26,10 @@ This test suite is dedicated to tests which verify the basic operations surround
|
|||||||
|
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
import {
|
||||||
|
createDomainObjectWithDefaults,
|
||||||
|
renameCurrentObjectFromBrowseBar
|
||||||
|
} from '../../../../appActions.js';
|
||||||
import { copy, paste, selectAll } from '../../../../helper/hotkeys/hotkeys.js';
|
import { copy, paste, selectAll } from '../../../../helper/hotkeys/hotkeys.js';
|
||||||
import * as nbUtils from '../../../../helper/notebookUtils.js';
|
import * as nbUtils from '../../../../helper/notebookUtils.js';
|
||||||
import { expect, streamToString, test } from '../../../../pluginFixtures.js';
|
import { expect, streamToString, test } from '../../../../pluginFixtures.js';
|
||||||
@ -596,4 +599,61 @@ test.describe('Notebook entry tests', () => {
|
|||||||
await expect(await page.locator(`text="${TEST_TEXT.repeat(1)}"`).count()).toEqual(1);
|
await expect(await page.locator(`text="${TEST_TEXT.repeat(1)}"`).count()).toEqual(1);
|
||||||
await expect(await page.locator(`text="${TEST_TEXT.repeat(2)}"`).count()).toEqual(0);
|
await expect(await page.locator(`text="${TEST_TEXT.repeat(2)}"`).count()).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('When changing the name of a notebook in the browse bar, new notebook changes are not lost', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
const TEST_TEXT = 'Do not lose me!';
|
||||||
|
const FIRST_NEW_NAME = 'New Name';
|
||||||
|
const SECOND_NEW_NAME = 'Second New Name';
|
||||||
|
|
||||||
|
await page.goto(notebookObject.url);
|
||||||
|
|
||||||
|
await page.getByLabel('Expand My Items folder').click();
|
||||||
|
|
||||||
|
await renameCurrentObjectFromBrowseBar(page, FIRST_NEW_NAME);
|
||||||
|
|
||||||
|
// verify the name change in tree and browse bar
|
||||||
|
await verifyNameChange(page, FIRST_NEW_NAME);
|
||||||
|
|
||||||
|
// enter one entry
|
||||||
|
await enterAndCommitTextEntry(page, TEST_TEXT);
|
||||||
|
|
||||||
|
// verify the entry is present
|
||||||
|
await expect(await page.locator(`text="${TEST_TEXT}"`).count()).toEqual(1);
|
||||||
|
|
||||||
|
// change the name
|
||||||
|
await renameCurrentObjectFromBrowseBar(page, SECOND_NEW_NAME);
|
||||||
|
|
||||||
|
// verify the name change in tree and browse bar
|
||||||
|
await verifyNameChange(page, SECOND_NEW_NAME);
|
||||||
|
|
||||||
|
// verify the entry is still present
|
||||||
|
await expect(await page.locator(`text="${TEST_TEXT}"`).count()).toEqual(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enter text into the last notebook entry and commit it.
|
||||||
|
*
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
async function enterAndCommitTextEntry(page, text) {
|
||||||
|
await nbUtils.addNotebookEntry(page);
|
||||||
|
await nbUtils.enterTextInLastEntry(page, text);
|
||||||
|
await nbUtils.commitEntry(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the name change in the tree and browse bar.
|
||||||
|
*
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {string} newName
|
||||||
|
*/
|
||||||
|
async function verifyNameChange(page, newName) {
|
||||||
|
await expect(
|
||||||
|
page.getByRole('treeitem').locator('.is-navigated-object .c-tree__item__name')
|
||||||
|
).toHaveText(newName);
|
||||||
|
await expect(page.getByLabel('Browse bar object name')).toHaveText(newName);
|
||||||
|
}
|
||||||
|
@ -108,4 +108,42 @@ test.describe('Plot Controls', () => {
|
|||||||
// Expect before and after plot points to match
|
// Expect before and after plot points to match
|
||||||
await expect(plotPixelSizeAtPause).toEqual(plotPixelSizeAfterWait);
|
await expect(plotPixelSizeAtPause).toEqual(plotPixelSizeAfterWait);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Test to verify that switching a plot's time context from global to
|
||||||
|
its own independent time context and then back to global context works correctly.
|
||||||
|
|
||||||
|
After switching from fixed time mode (ITC) to real time mode (global context),
|
||||||
|
the pause control for the plot should be available, indicating that it is following the right context.
|
||||||
|
*/
|
||||||
|
test('Plots follow the right time context', async ({ page }) => {
|
||||||
|
// Set global time conductor to real-time mode
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
|
||||||
|
// hover over plot for plot controls
|
||||||
|
await page.getByLabel('Plot Canvas').hover();
|
||||||
|
// Ensure pause control is visible since global time conductor is in Real time mode.
|
||||||
|
await expect(page.getByTitle('Pause incoming real-time data')).toBeVisible();
|
||||||
|
|
||||||
|
// Toggle independent time conductor ON
|
||||||
|
await page.getByLabel('Enable Independent Time Conductor').click();
|
||||||
|
|
||||||
|
// Bring up the independent time conductor popup and switch to fixed time mode
|
||||||
|
await page.getByLabel('Independent Time Conductor Settings').click();
|
||||||
|
await page.getByLabel('Independent Time Conductor Mode Menu').click();
|
||||||
|
await page.getByRole('menuitem', { name: /Fixed Timespan/ }).click();
|
||||||
|
|
||||||
|
// hover over plot for plot controls
|
||||||
|
await page.getByLabel('Plot Canvas').hover();
|
||||||
|
// Ensure pause control is no longer visible since the plot is following the independent time context
|
||||||
|
await expect(page.getByTitle('Pause incoming real-time data')).toBeHidden();
|
||||||
|
|
||||||
|
// Toggle independent time conductor OFF - Note that the global time conductor is still in Real time mode
|
||||||
|
await page.getByLabel('Disable Independent Time Conductor').click();
|
||||||
|
|
||||||
|
// hover over plot for plot controls
|
||||||
|
await page.getByLabel('Plot Canvas').hover();
|
||||||
|
// Ensure pause control is visible since the global time conductor is in real time mode
|
||||||
|
await expect(page.getByTitle('Pause incoming real-time data')).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2025, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This test suite is dedicated to testing the rendering and interaction of plots.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||||
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
|
test.describe('Plot Controls in compact mode', () => {
|
||||||
|
let timeStrip;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Open a browser, navigate to the main page, and wait until all networkevents to resolve
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
timeStrip = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Time Strip'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create an overlay plot with a sine wave generator
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
parent: timeStrip.uuid
|
||||||
|
});
|
||||||
|
await page.goto(`${timeStrip.url}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Plots show cursor guides', async ({ page }) => {
|
||||||
|
// hover over plot for plot controls
|
||||||
|
await page.getByLabel('Plot Canvas').hover();
|
||||||
|
// click on cursor guides control
|
||||||
|
await page.getByTitle('Toggle cursor guides').click();
|
||||||
|
await page.getByLabel('Plot Canvas').hover();
|
||||||
|
await expect(page.getByLabel('Vertical cursor guide')).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Horizontal cursor guide')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
@ -46,6 +46,24 @@ class EventMetadataProvider {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const inPlaceUpdateMetadataValue = {
|
||||||
|
key: 'messageId',
|
||||||
|
name: 'row identifier',
|
||||||
|
format: 'string',
|
||||||
|
useToUpdateInPlace: true
|
||||||
|
};
|
||||||
|
const eventAcknowledgeMetadataValue = {
|
||||||
|
key: 'acknowledge',
|
||||||
|
name: 'Acknowledge',
|
||||||
|
format: 'string'
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventGeneratorWithAcknowledge = structuredClone(this.METADATA_BY_TYPE.eventGenerator);
|
||||||
|
eventGeneratorWithAcknowledge.values.push(inPlaceUpdateMetadataValue);
|
||||||
|
eventGeneratorWithAcknowledge.values.push(eventAcknowledgeMetadataValue);
|
||||||
|
|
||||||
|
this.METADATA_BY_TYPE.eventGeneratorWithAcknowledge = eventGeneratorWithAcknowledge;
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsMetadata(domainObject) {
|
supportsMetadata(domainObject) {
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining EventTelemetryProvider. Created by chacskaylo on 06/18/2015.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import EventTelemetryProvider from './EventTelemetryProvider.js';
|
||||||
|
|
||||||
|
class EventWithAcknowledgeTelemetryProvider extends EventTelemetryProvider {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.unAcknowledgedData = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateData(firstObservedTime, count, startTime, duration, name) {
|
||||||
|
if (this.unAcknowledgedData === undefined) {
|
||||||
|
const unAcknowledgedData = super.generateData(
|
||||||
|
firstObservedTime,
|
||||||
|
count,
|
||||||
|
startTime,
|
||||||
|
duration,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
unAcknowledgedData.messageId = unAcknowledgedData.message;
|
||||||
|
this.unAcknowledgedData = unAcknowledgedData;
|
||||||
|
|
||||||
|
return this.unAcknowledgedData;
|
||||||
|
} else {
|
||||||
|
const acknowledgedData = {
|
||||||
|
...this.unAcknowledgedData,
|
||||||
|
acknowledge: 'OK'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unAcknowledgedData = undefined;
|
||||||
|
|
||||||
|
return acknowledgedData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsRequest(domainObject) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsSubscribe(domainObject) {
|
||||||
|
return domainObject.type === 'eventGeneratorWithAcknowledge';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EventWithAcknowledgeTelemetryProvider;
|
@ -21,6 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import EventMetadataProvider from './EventMetadataProvider.js';
|
import EventMetadataProvider from './EventMetadataProvider.js';
|
||||||
import EventTelemetryProvider from './EventTelemetryProvider.js';
|
import EventTelemetryProvider from './EventTelemetryProvider.js';
|
||||||
|
import EventWithAcknowledgeTelemetryProvider from './EventWithAcknowledgeTelemetryProvider.js';
|
||||||
|
|
||||||
export default function EventGeneratorPlugin(options) {
|
export default function EventGeneratorPlugin(options) {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
@ -38,5 +39,20 @@ export default function EventGeneratorPlugin(options) {
|
|||||||
});
|
});
|
||||||
openmct.telemetry.addProvider(new EventTelemetryProvider());
|
openmct.telemetry.addProvider(new EventTelemetryProvider());
|
||||||
openmct.telemetry.addProvider(new EventMetadataProvider());
|
openmct.telemetry.addProvider(new EventMetadataProvider());
|
||||||
|
|
||||||
|
openmct.types.addType('eventGeneratorWithAcknowledge', {
|
||||||
|
name: 'Event Message Generator with Acknowledge',
|
||||||
|
description:
|
||||||
|
'For development use. Creates sample event message data stream and updates the event row with an acknowledgement.',
|
||||||
|
cssClass: 'icon-generator-events',
|
||||||
|
creatable: true,
|
||||||
|
initialize: function (object) {
|
||||||
|
object.telemetry = {
|
||||||
|
duration: 2.5
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.telemetry.addProvider(new EventWithAcknowledgeTelemetryProvider());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -108,6 +108,16 @@ const METADATA_BY_TYPE = {
|
|||||||
string: 'ON'
|
string: 'ON'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
singleSelectionThreshold: true,
|
||||||
|
comparator: 'equals',
|
||||||
|
possibleValues: [
|
||||||
|
{ label: 'OFF', value: 0 },
|
||||||
|
{ label: 'ON', value: 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
hints: {
|
hints: {
|
||||||
range: 1
|
range: 1
|
||||||
}
|
}
|
||||||
|
@ -34,14 +34,16 @@ StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
|
|||||||
return domainObject.type === 'example.state-generator';
|
return domainObject.type === 'example.state-generator';
|
||||||
};
|
};
|
||||||
|
|
||||||
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback, options) {
|
||||||
var duration = domainObject.telemetry.duration * 1000;
|
var duration = domainObject.telemetry.duration * 1000;
|
||||||
|
|
||||||
var interval = setInterval(function () {
|
var interval = setInterval(() => {
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
var datum = pointForTimestamp(now, duration, domainObject.name);
|
var datum = pointForTimestamp(now, duration, domainObject.name);
|
||||||
|
if (!this.shouldBeFiltered(datum, options)) {
|
||||||
datum.value = String(datum.value);
|
datum.value = String(datum.value);
|
||||||
callback(datum);
|
callback(datum);
|
||||||
|
}
|
||||||
}, duration);
|
}, duration);
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
@ -63,9 +65,25 @@ StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
|||||||
|
|
||||||
var data = [];
|
var data = [];
|
||||||
while (start <= end && data.length < 5000) {
|
while (start <= end && data.length < 5000) {
|
||||||
data.push(pointForTimestamp(start, duration, domainObject.name));
|
const point = pointForTimestamp(start, duration, domainObject.name);
|
||||||
|
|
||||||
|
if (!this.shouldBeFiltered(point, options)) {
|
||||||
|
data.push(point);
|
||||||
|
}
|
||||||
start += duration;
|
start += duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(data);
|
return Promise.resolve(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
StateGeneratorProvider.prototype.shouldBeFiltered = function (point, options) {
|
||||||
|
const valueToFilter = options?.filters?.state?.equals?.[0];
|
||||||
|
|
||||||
|
if (!valueToFilter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value } = point;
|
||||||
|
|
||||||
|
return value !== Number(valueToFilter);
|
||||||
|
};
|
||||||
|
101
package-lock.json
generated
101
package-lock.json
generated
@ -62,6 +62,7 @@
|
|||||||
"location-bar": "3.0.1",
|
"location-bar": "3.0.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"marked": "12.0.0",
|
"marked": "12.0.0",
|
||||||
|
"mathjs": "13.1.1",
|
||||||
"mini-css-extract-plugin": "2.7.6",
|
"mini-css-extract-plugin": "2.7.6",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"moment-duration-format": "2.3.2",
|
"moment-duration-format": "2.3.2",
|
||||||
@ -643,6 +644,18 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.25.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
|
||||||
|
"integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.24.0",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
|
||||||
@ -3088,6 +3101,19 @@
|
|||||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/complex.js": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/infusion"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/compressible": {
|
"node_modules/compressible": {
|
||||||
"version": "2.0.18",
|
"version": "2.0.18",
|
||||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||||
@ -4033,6 +4059,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decimal.js": {
|
||||||
|
"version": "10.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
|
||||||
|
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@ -4483,6 +4515,12 @@
|
|||||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/escape-latex": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
@ -5817,6 +5855,19 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fraction.js": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://github.com/sponsors/rawify"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
@ -7063,6 +7114,12 @@
|
|||||||
"integrity": "sha512-UrzO3fL7nnxlQXlvTynNAenL+21oUQRlzqQFsA2U11ryb4+NLOCOePZ70PTojEaUKhiFugh7dG0Q+I58xlPdWg==",
|
"integrity": "sha512-UrzO3fL7nnxlQXlvTynNAenL+21oUQRlzqQFsA2U11ryb4+NLOCOePZ70PTojEaUKhiFugh7dG0Q+I58xlPdWg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/javascript-natural-sort": {
|
||||||
|
"version": "0.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||||
|
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/jest-worker": {
|
"node_modules/jest-worker": {
|
||||||
"version": "27.5.1",
|
"version": "27.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||||
@ -7708,6 +7765,29 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mathjs": {
|
||||||
|
"version": "13.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-13.1.1.tgz",
|
||||||
|
"integrity": "sha512-duaSAy7m4F+QtP1Dyv8MX2XuxcqpNDDlGly0SdVTCqpAmwdOFWilDdQKbLdo9RfD6IDNMOdo9tIsEaTXkconlQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.25.4",
|
||||||
|
"complex.js": "^2.1.1",
|
||||||
|
"decimal.js": "^10.4.3",
|
||||||
|
"escape-latex": "^1.2.0",
|
||||||
|
"fraction.js": "^4.3.7",
|
||||||
|
"javascript-natural-sort": "^0.7.1",
|
||||||
|
"seedrandom": "^3.0.5",
|
||||||
|
"tiny-emitter": "^2.1.0",
|
||||||
|
"typed-function": "^4.2.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mathjs": "bin/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@ -9491,6 +9571,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/regex-parser": {
|
"node_modules/regex-parser": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz",
|
||||||
@ -9847,6 +9933,12 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/seedrandom": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/select-hose": {
|
"node_modules/select-hose": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||||
@ -10833,6 +10925,15 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typed-function": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typedarray-to-buffer": {
|
"node_modules/typedarray-to-buffer": {
|
||||||
"version": "3.1.5",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"location-bar": "3.0.1",
|
"location-bar": "3.0.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"marked": "12.0.0",
|
"marked": "12.0.0",
|
||||||
|
"mathjs": "13.1.1",
|
||||||
"mini-css-extract-plugin": "2.7.6",
|
"mini-css-extract-plugin": "2.7.6",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"moment-duration-format": "2.3.2",
|
"moment-duration-format": "2.3.2",
|
||||||
|
@ -306,6 +306,7 @@ export class MCT extends EventEmitter {
|
|||||||
this.install(this.plugins.UserIndicator());
|
this.install(this.plugins.UserIndicator());
|
||||||
this.install(this.plugins.Gauge());
|
this.install(this.plugins.Gauge());
|
||||||
this.install(this.plugins.InspectorViews());
|
this.install(this.plugins.InspectorViews());
|
||||||
|
this.install(this.plugins.Comps());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Set path to where assets are hosted. This should be the path to main.js.
|
* Set path to where assets are hosted. This should be the path to main.js.
|
||||||
|
@ -250,6 +250,90 @@ export default class TelemetryAPI {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes objects for consistent serialization by:
|
||||||
|
* 1. Removing non-plain objects (class instances) and functions
|
||||||
|
* 2. Sorting object keys alphabetically to ensure consistent ordering
|
||||||
|
*/
|
||||||
|
sanitizeForSerialization(key, value) {
|
||||||
|
// Handle null and primitives directly
|
||||||
|
if (value === null || typeof value !== 'object') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove functions and non-plain objects (except arrays)
|
||||||
|
if (
|
||||||
|
typeof value === 'function' ||
|
||||||
|
(Object.getPrototypeOf(value) !== Object.prototype && !Array.isArray(value))
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For plain objects, just sort the keys
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
const sortedObject = {};
|
||||||
|
const sortedKeys = Object.keys(value).sort();
|
||||||
|
|
||||||
|
sortedKeys.forEach((objectKey) => {
|
||||||
|
sortedObject[objectKey] = value[objectKey];
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a numeric hash value for an options object. The hash is consistent
|
||||||
|
* for equivalent option objects regardless of property order.
|
||||||
|
*
|
||||||
|
* This is used to create compact, unique cache keys for telemetry subscriptions with
|
||||||
|
* different options configurations. The hash function ensures that identical options
|
||||||
|
* objects will always generate the same hash value, while different options objects
|
||||||
|
* (even with small differences) will generate different hash values.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} options The options object to hash
|
||||||
|
* @returns {number} A positive integer hash of the options object
|
||||||
|
*/
|
||||||
|
#hashOptions(options) {
|
||||||
|
const sanitizedOptionsString = JSON.stringify(
|
||||||
|
options,
|
||||||
|
this.sanitizeForSerialization.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
let hash = 0;
|
||||||
|
const prime = 31;
|
||||||
|
const modulus = 1e9 + 9; // Large prime number
|
||||||
|
|
||||||
|
for (let i = 0; i < sanitizedOptionsString.length; i++) {
|
||||||
|
const char = sanitizedOptionsString.charCodeAt(i);
|
||||||
|
// Calculate new hash value while keeping numbers manageable
|
||||||
|
hash = Math.floor((hash * prime + char) % modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.abs(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique cache key for a telemetry subscription based on the
|
||||||
|
* domain object identifier and options (which includes strategy).
|
||||||
|
*
|
||||||
|
* Uses a hash of the options object to create compact cache keys while still
|
||||||
|
* ensuring unique keys for different subscription configurations.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {import('openmct').DomainObject} domainObject The domain object being subscribed to
|
||||||
|
* @param {Object} options The subscription options object (including strategy)
|
||||||
|
* @returns {string} A unique key string for caching the subscription
|
||||||
|
*/
|
||||||
|
#getSubscriptionCacheKey(domainObject, options) {
|
||||||
|
const keyString = makeKeyString(domainObject.identifier);
|
||||||
|
|
||||||
|
return `${keyString}:${this.#hashOptions(options)}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a request interceptor that transforms a request via module:openmct.TelemetryAPI.request
|
* Register a request interceptor that transforms a request via module:openmct.TelemetryAPI.request
|
||||||
* The request will be modified when it is received and will be returned in it's modified state
|
* The request will be modified when it is received and will be returned in it's modified state
|
||||||
@ -418,16 +502,14 @@ export default class TelemetryAPI {
|
|||||||
this.#subscribeCache = {};
|
this.#subscribeCache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyString = makeKeyString(domainObject.identifier);
|
|
||||||
const supportedStrategy = supportsBatching ? requestedStrategy : SUBSCRIBE_STRATEGY.LATEST;
|
const supportedStrategy = supportsBatching ? requestedStrategy : SUBSCRIBE_STRATEGY.LATEST;
|
||||||
// Override the requested strategy with the strategy supported by the provider
|
// Override the requested strategy with the strategy supported by the provider
|
||||||
const optionsWithSupportedStrategy = {
|
const optionsWithSupportedStrategy = {
|
||||||
...options,
|
...options,
|
||||||
strategy: supportedStrategy
|
strategy: supportedStrategy
|
||||||
};
|
};
|
||||||
// If batching is supported, we need to cache a subscription for each strategy -
|
|
||||||
// latest and batched.
|
const cacheKey = this.#getSubscriptionCacheKey(domainObject, optionsWithSupportedStrategy);
|
||||||
const cacheKey = `${keyString}:${supportedStrategy}`;
|
|
||||||
let subscriber = this.#subscribeCache[cacheKey];
|
let subscriber = this.#subscribeCache[cacheKey];
|
||||||
|
|
||||||
if (!subscriber) {
|
if (!subscriber) {
|
||||||
@ -678,6 +760,15 @@ export default class TelemetryAPI {
|
|||||||
return this.metadataCache.get(domainObject);
|
return this.metadataCache.get(domainObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a domain object from the telemetry metadata cache.
|
||||||
|
* @param {import('openmct').DomainObject} domainObject
|
||||||
|
*/
|
||||||
|
|
||||||
|
removeMetadataFromCache(domainObject) {
|
||||||
|
this.metadataCache.delete(domainObject);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a value formatter for a given valueMetadata.
|
* Get a value formatter for a given valueMetadata.
|
||||||
*
|
*
|
||||||
|
@ -86,14 +86,23 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this._setTimeSystem(this.options.timeContext.getTimeSystem());
|
this._setTimeSystem(this.options.timeContext.getTimeSystem());
|
||||||
this.lastBounds = this.options.timeContext.getBounds();
|
this.lastBounds = this.options.timeContext.getBounds();
|
||||||
|
// prioritize passed options over time bounds
|
||||||
|
if (this.options.start) {
|
||||||
|
this.lastBounds.start = this.options.start;
|
||||||
|
}
|
||||||
|
if (this.options.end) {
|
||||||
|
this.lastBounds.end = this.options.end;
|
||||||
|
}
|
||||||
this._watchBounds();
|
this._watchBounds();
|
||||||
this._watchTimeSystem();
|
this._watchTimeSystem();
|
||||||
this._watchTimeModeChange();
|
this._watchTimeModeChange();
|
||||||
|
|
||||||
this._requestHistoricalTelemetry();
|
const historicalTelemetryLoadedPromise = this._requestHistoricalTelemetry();
|
||||||
this._initiateSubscriptionTelemetry();
|
this._initiateSubscriptionTelemetry();
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
|
||||||
|
return historicalTelemetryLoadedPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,6 +122,7 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.removeAllListeners();
|
this.removeAllListeners();
|
||||||
|
this.loaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,7 +178,7 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._processNewTelemetry(historicalData);
|
this._processNewTelemetry(historicalData, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -182,10 +192,9 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
const options = { ...this.options };
|
const options = { ...this.options };
|
||||||
//We always want to receive all available values in telemetry tables.
|
//We always want to receive all available values in telemetry tables.
|
||||||
options.strategy = this.openmct.telemetry.SUBSCRIBE_STRATEGY.BATCH;
|
options.strategy = this.openmct.telemetry.SUBSCRIBE_STRATEGY.BATCH;
|
||||||
|
|
||||||
this.unsubscribe = this.openmct.telemetry.subscribe(
|
this.unsubscribe = this.openmct.telemetry.subscribe(
|
||||||
this.domainObject,
|
this.domainObject,
|
||||||
(datum) => this._processNewTelemetry(datum),
|
(datum) => this._processNewTelemetry(datum, true),
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -196,9 +205,10 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {(Object|Object[])} telemetryData - telemetry data object or
|
* @param {(Object|Object[])} telemetryData - telemetry data object or
|
||||||
* array of telemetry data objects
|
* array of telemetry data objects
|
||||||
|
* @param {boolean} isSubscriptionData - `true` if the telemetry data is new subscription data,
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_processNewTelemetry(telemetryData) {
|
_processNewTelemetry(telemetryData, isSubscriptionData = false) {
|
||||||
if (telemetryData === undefined) {
|
if (telemetryData === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -213,12 +223,19 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
let hasDataBeforeStartBound = false;
|
let hasDataBeforeStartBound = false;
|
||||||
let size = this.options.size;
|
let size = this.options.size;
|
||||||
let enforceSize = size !== undefined && this.options.enforceSize;
|
let enforceSize = size !== undefined && this.options.enforceSize;
|
||||||
|
const boundsToUse = this.lastBounds;
|
||||||
|
if (!isSubscriptionData && this.options.start) {
|
||||||
|
boundsToUse.start = this.options.start;
|
||||||
|
}
|
||||||
|
if (!isSubscriptionData && this.options.end) {
|
||||||
|
boundsToUse.end = this.options.end;
|
||||||
|
}
|
||||||
|
|
||||||
// loop through, sort and dedupe
|
// loop through, sort and dedupe
|
||||||
for (let datum of data) {
|
for (let datum of data) {
|
||||||
parsedValue = this.parseTime(datum);
|
parsedValue = this.parseTime(datum);
|
||||||
beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
beforeStartOfBounds = parsedValue < boundsToUse.start;
|
||||||
afterEndOfBounds = parsedValue > this.lastBounds.end;
|
afterEndOfBounds = parsedValue > boundsToUse.end;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!afterEndOfBounds &&
|
!afterEndOfBounds &&
|
||||||
@ -397,7 +414,10 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
this.emit('add', added, [this.boundedTelemetry.length]);
|
this.emit('add', added, [this.boundedTelemetry.length]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// user bounds change, reset
|
// user bounds change, reset and remove initial requested bounds (we're using new bounds)
|
||||||
|
delete this.options?.start;
|
||||||
|
delete this.options?.end;
|
||||||
|
this.lastBounds = bounds;
|
||||||
this._reset();
|
this._reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -477,9 +497,9 @@ export default class TelemetryCollection extends EventEmitter {
|
|||||||
this.boundedTelemetry = [];
|
this.boundedTelemetry = [];
|
||||||
this.futureBuffer = [];
|
this.futureBuffer = [];
|
||||||
|
|
||||||
this.emit('clear');
|
const telemetryLoadPromise = this._requestHistoricalTelemetry();
|
||||||
|
|
||||||
this._requestHistoricalTelemetry();
|
this.emit('clear', telemetryLoadPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -359,6 +359,18 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean}
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
isFixed() {
|
||||||
|
if (this.upstreamTimeContext) {
|
||||||
|
return this.upstreamTimeContext.isFixed(...arguments);
|
||||||
|
} else {
|
||||||
|
return super.isFixed(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
* @override
|
* @override
|
||||||
@ -400,7 +412,7 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the time context to the global time context
|
* Reset the time context from the global time context
|
||||||
*/
|
*/
|
||||||
resetContext() {
|
resetContext() {
|
||||||
if (this.upstreamTimeContext) {
|
if (this.upstreamTimeContext) {
|
||||||
@ -428,6 +440,10 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
// Emit bounds so that views that are changing context get the upstream bounds
|
// Emit bounds so that views that are changing context get the upstream bounds
|
||||||
this.emit('bounds', this.getBounds());
|
this.emit('bounds', this.getBounds());
|
||||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
|
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
|
||||||
|
// Also emit the mode in case it's different from previous time context
|
||||||
|
if (this.getMode()) {
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.getMode()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -502,6 +518,10 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
// Emit bounds so that views that are changing context get the upstream bounds
|
// Emit bounds so that views that are changing context get the upstream bounds
|
||||||
this.emit('bounds', this.getBounds());
|
this.emit('bounds', this.getBounds());
|
||||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
|
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
|
||||||
|
// Also emit the mode in case it's different from the global time context
|
||||||
|
if (this.getMode()) {
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.getMode()));
|
||||||
|
}
|
||||||
// now that the view's context is set, tell others to check theirs in case they were following this view's context.
|
// now that the view's context is set, tell others to check theirs in case they were following this view's context.
|
||||||
this.globalTimeContext.emit('refreshContext', viewKey);
|
this.globalTimeContext.emit('refreshContext', viewKey);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '@/api/time/constants';
|
import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '@/api/time/constants';
|
||||||
import IndependentTimeContext from '@/api/time/IndependentTimeContext';
|
import IndependentTimeContext from '@/api/time/IndependentTimeContext';
|
||||||
|
|
||||||
|
import { TIME_CONTEXT_EVENTS } from './constants';
|
||||||
import GlobalTimeContext from './GlobalTimeContext.js';
|
import GlobalTimeContext from './GlobalTimeContext.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,7 +143,7 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
addIndependentContext(key, value, clockKey) {
|
addIndependentContext(key, value, clockKey) {
|
||||||
let timeContext = this.getIndependentContext(key);
|
let timeContext = this.getIndependentContext(key);
|
||||||
|
|
||||||
//stop following upstream time context since the view has it's own
|
//stop following upstream time context since the view has its own
|
||||||
timeContext.resetContext();
|
timeContext.resetContext();
|
||||||
|
|
||||||
if (clockKey) {
|
if (clockKey) {
|
||||||
@ -152,6 +153,9 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
timeContext.setMode(FIXED_MODE_KEY, value);
|
timeContext.setMode(FIXED_MODE_KEY, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also emit the mode in case it's different from the previous time context
|
||||||
|
timeContext.emit(TIME_CONTEXT_EVENTS.modeChanged, structuredClone(timeContext.getMode()));
|
||||||
|
|
||||||
// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
|
// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
|
||||||
this.emit('refreshContext', key);
|
this.emit('refreshContext', key);
|
||||||
|
|
||||||
|
85
src/plugins/comps/CompsInspectorViewProvider.js
Normal file
85
src/plugins/comps/CompsInspectorViewProvider.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import mount from 'utils/mount';
|
||||||
|
|
||||||
|
import CompsInspectorView from './components/CompsInspectorView.vue';
|
||||||
|
|
||||||
|
export default class ConditionSetViewProvider {
|
||||||
|
constructor(openmct, compsManagerPool) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.name = 'Config';
|
||||||
|
this.key = 'comps-configuration';
|
||||||
|
this.compsManagerPool = compsManagerPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
canView(selection) {
|
||||||
|
if (selection.length !== 1 || selection[0].length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let object = selection[0][0].context.item;
|
||||||
|
return object && object.type === 'comps';
|
||||||
|
}
|
||||||
|
|
||||||
|
view(selection) {
|
||||||
|
let _destroy = null;
|
||||||
|
const domainObject = selection[0][0].context.item;
|
||||||
|
const openmct = this.openmct;
|
||||||
|
const compsManagerPool = this.compsManagerPool;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
const { destroy } = mount(
|
||||||
|
{
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
CompsInspectorView: CompsInspectorView
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject,
|
||||||
|
compsManagerPool
|
||||||
|
},
|
||||||
|
template: '<comps-inspector-view></comps-inspector-view>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
app: openmct.app,
|
||||||
|
element
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_destroy = destroy;
|
||||||
|
},
|
||||||
|
showTab: function (isEditing) {
|
||||||
|
return isEditing;
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
if (_destroy) {
|
||||||
|
_destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
379
src/plugins/comps/CompsManager.js
Normal file
379
src/plugins/comps/CompsManager.js
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
|
||||||
|
export default class CompsManager extends EventEmitter {
|
||||||
|
#openmct;
|
||||||
|
#domainObject;
|
||||||
|
#composition;
|
||||||
|
#telemetryObjects = {};
|
||||||
|
#telemetryCollections = {};
|
||||||
|
#telemetryLoadedPromises = [];
|
||||||
|
#telemetryOptions = {};
|
||||||
|
#loaded = false;
|
||||||
|
#compositionLoaded = false;
|
||||||
|
#telemetryProcessors = {};
|
||||||
|
#loadVersion = 0;
|
||||||
|
#currentLoadPromise = null;
|
||||||
|
|
||||||
|
constructor(openmct, domainObject) {
|
||||||
|
super();
|
||||||
|
this.#openmct = openmct;
|
||||||
|
this.#domainObject = domainObject;
|
||||||
|
this.clearData = this.clearData.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#getNextAlphabeticalParameterName() {
|
||||||
|
const parameters = this.#domainObject.configuration.comps.parameters;
|
||||||
|
const existingNames = new Set(parameters.map((p) => p.name));
|
||||||
|
const alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
let suffix = '';
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
for (let letter of alphabet) {
|
||||||
|
const proposedName = letter + suffix;
|
||||||
|
if (!existingNames.has(proposedName)) {
|
||||||
|
return proposedName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Increment suffix after exhausting the alphabet
|
||||||
|
suffix = (parseInt(suffix, 10) || 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addParameter(telemetryObject) {
|
||||||
|
const keyString = this.#openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
|
const metaData = this.#openmct.telemetry.getMetadata(telemetryObject);
|
||||||
|
const timeSystem = this.#openmct.time.getTimeSystem();
|
||||||
|
const domains = metaData?.valuesForHints(['domain']);
|
||||||
|
const timeMetaData = domains.find((d) => d.key === timeSystem.key);
|
||||||
|
// in the valuesMetadata, find the first numeric data type
|
||||||
|
const rangeItems = metaData.valueMetadatas.filter(
|
||||||
|
(metaDatum) => metaDatum.hints && metaDatum.hints.range
|
||||||
|
);
|
||||||
|
rangeItems.sort((a, b) => a.hints.range - b.hints.range);
|
||||||
|
let valueToUse = rangeItems[0]?.key;
|
||||||
|
if (!valueToUse) {
|
||||||
|
// if no numeric data type, just use the first one
|
||||||
|
valueToUse = metaData.valueMetadatas[0]?.key;
|
||||||
|
}
|
||||||
|
this.#domainObject.configuration.comps.parameters.push({
|
||||||
|
keyString,
|
||||||
|
name: `${this.#getNextAlphabeticalParameterName()}`,
|
||||||
|
valueToUse,
|
||||||
|
testValue: 0,
|
||||||
|
timeMetaData,
|
||||||
|
accumulateValues: false,
|
||||||
|
sampleSize: 10
|
||||||
|
});
|
||||||
|
this.emit('parameterAdded', this.#domainObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
getParameters() {
|
||||||
|
const parameters = this.#domainObject.configuration.comps.parameters;
|
||||||
|
const parametersWithTimeKey = parameters.map((parameter) => {
|
||||||
|
return {
|
||||||
|
...parameter,
|
||||||
|
timeKey: this.#telemetryCollections[parameter.keyString]?.timeKey
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return parametersWithTimeKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTelemetryObjectForParameter(keyString) {
|
||||||
|
return this.#telemetryObjects[keyString];
|
||||||
|
}
|
||||||
|
|
||||||
|
getMetaDataValuesForParameter(keyString) {
|
||||||
|
const telemetryObject = this.getTelemetryObjectForParameter(keyString);
|
||||||
|
const metaData = this.#openmct.telemetry.getMetadata(telemetryObject);
|
||||||
|
return metaData.valueMetadatas;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteParameter(keyString) {
|
||||||
|
this.#domainObject.configuration.comps.parameters =
|
||||||
|
this.#domainObject.configuration.comps.parameters.filter(
|
||||||
|
(parameter) => parameter.keyString !== keyString
|
||||||
|
);
|
||||||
|
// if there are no parameters referencing this parameter keyString, remove the telemetry object too
|
||||||
|
const parameterExists = this.#domainObject.configuration.comps.parameters.some(
|
||||||
|
(parameter) => parameter.keyString === keyString
|
||||||
|
);
|
||||||
|
if (!parameterExists) {
|
||||||
|
this.emit('parameterRemoved', this.#domainObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDomainObject(passedDomainObject) {
|
||||||
|
this.#domainObject = passedDomainObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
isReady() {
|
||||||
|
return this.#loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(telemetryOptions) {
|
||||||
|
// Increment the load version to mark a new load operation
|
||||||
|
const loadVersion = ++this.#loadVersion;
|
||||||
|
|
||||||
|
if (!_.isEqual(this.#telemetryOptions, telemetryOptions)) {
|
||||||
|
this.#destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#telemetryOptions = telemetryOptions;
|
||||||
|
|
||||||
|
// Start the load process and store the promise
|
||||||
|
this.#currentLoadPromise = (async () => {
|
||||||
|
// Load composition if not already loaded
|
||||||
|
if (!this.#compositionLoaded) {
|
||||||
|
await this.#loadComposition();
|
||||||
|
// Check if a newer load has been initiated
|
||||||
|
if (loadVersion !== this.#loadVersion) {
|
||||||
|
await this.#currentLoadPromise;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#compositionLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening to telemetry if not already done
|
||||||
|
if (!this.#loaded) {
|
||||||
|
await this.#startListeningToUnderlyingTelemetry();
|
||||||
|
// Check again for newer load
|
||||||
|
if (loadVersion !== this.#loadVersion) {
|
||||||
|
await this.#currentLoadPromise;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#loaded = true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Await the load process
|
||||||
|
await this.#currentLoadPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #startListeningToUnderlyingTelemetry() {
|
||||||
|
Object.keys(this.#telemetryCollections).forEach((keyString) => {
|
||||||
|
if (!this.#telemetryCollections[keyString].loaded) {
|
||||||
|
this.#telemetryCollections[keyString].on('add', this.#getTelemetryProcessor(keyString));
|
||||||
|
this.#telemetryCollections[keyString].on('clear', this.clearData);
|
||||||
|
const telemetryLoadedPromise = this.#telemetryCollections[keyString].load();
|
||||||
|
this.#telemetryLoadedPromises.push(telemetryLoadedPromise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(this.#telemetryLoadedPromises);
|
||||||
|
this.#telemetryLoadedPromises = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
#destroy() {
|
||||||
|
this.stopListeningToUnderlyingTelemetry();
|
||||||
|
this.#composition = null;
|
||||||
|
this.#telemetryCollections = {};
|
||||||
|
this.#compositionLoaded = false;
|
||||||
|
this.#loaded = false;
|
||||||
|
this.#telemetryObjects = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
stopListeningToUnderlyingTelemetry() {
|
||||||
|
this.#loaded = false;
|
||||||
|
Object.keys(this.#telemetryCollections).forEach((keyString) => {
|
||||||
|
const specificTelemetryProcessor = this.#telemetryProcessors[keyString];
|
||||||
|
delete this.#telemetryProcessors[keyString];
|
||||||
|
this.#telemetryCollections[keyString].off('add', specificTelemetryProcessor);
|
||||||
|
this.#telemetryCollections[keyString].off('clear', this.clearData);
|
||||||
|
this.#telemetryCollections[keyString].destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getTelemetryObjects() {
|
||||||
|
return this.#telemetryObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #loadComposition() {
|
||||||
|
this.#composition = this.#openmct.composition.get(this.#domainObject);
|
||||||
|
if (this.#composition) {
|
||||||
|
this.#composition.on('add', this.#addTelemetryObject);
|
||||||
|
this.#composition.on('remove', this.#removeTelemetryObject);
|
||||||
|
await this.#composition.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#getParameterForKeyString(keyString) {
|
||||||
|
return this.#domainObject.configuration.comps.parameters.find(
|
||||||
|
(parameter) => parameter.keyString === keyString
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#getImputedDataUsingLOCF(datum, telemetryCollection) {
|
||||||
|
const telemetryCollectionData = telemetryCollection.getAll();
|
||||||
|
let insertionPointForNewData = telemetryCollection._sortedIndex(datum);
|
||||||
|
if (insertionPointForNewData && insertionPointForNewData >= telemetryCollectionData.length) {
|
||||||
|
insertionPointForNewData = telemetryCollectionData.length - 1;
|
||||||
|
}
|
||||||
|
// get the closest datum to the new datum
|
||||||
|
const closestDatum = telemetryCollectionData[insertionPointForNewData];
|
||||||
|
// clone the closest datum and replace the time key with the new time
|
||||||
|
const imputedData = {
|
||||||
|
...closestDatum,
|
||||||
|
[telemetryCollection.timeKey]: datum[telemetryCollection.timeKey]
|
||||||
|
};
|
||||||
|
return imputedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataFrameForRequest() {
|
||||||
|
// Step 1: Collect all unique timestamps from all telemetry collections
|
||||||
|
const allTimestampsSet = new Set();
|
||||||
|
|
||||||
|
Object.values(this.#telemetryCollections).forEach((collection) => {
|
||||||
|
collection.getAll().forEach((dataPoint) => {
|
||||||
|
allTimestampsSet.add(dataPoint.timestamp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert the set to a sorted array
|
||||||
|
const allTimestamps = Array.from(allTimestampsSet).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
// Step 2: Initialize the result object
|
||||||
|
const telemetryForComps = {};
|
||||||
|
|
||||||
|
// Step 3: Iterate through each telemetry collection to align data
|
||||||
|
Object.keys(this.#telemetryCollections).forEach((keyString) => {
|
||||||
|
const telemetryCollection = this.#telemetryCollections[keyString];
|
||||||
|
const alignedValues = [];
|
||||||
|
|
||||||
|
// Iterate through each common timestamp
|
||||||
|
allTimestamps.forEach((timestamp) => {
|
||||||
|
const timeKey = telemetryCollection.timeKey;
|
||||||
|
const fakeData = { [timeKey]: timestamp };
|
||||||
|
const imputedDatum = this.#getImputedDataUsingLOCF(fakeData, telemetryCollection);
|
||||||
|
if (imputedDatum) {
|
||||||
|
alignedValues.push(imputedDatum);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
telemetryForComps[keyString] = alignedValues;
|
||||||
|
});
|
||||||
|
|
||||||
|
return telemetryForComps;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataFrameForSubscription(newTelemetry) {
|
||||||
|
const telemetryForComps = {};
|
||||||
|
const newTelemetryKey = Object.keys(newTelemetry)[0];
|
||||||
|
const newTelemetryParameter = this.#getParameterForKeyString(newTelemetryKey);
|
||||||
|
const newTelemetryData = newTelemetry[newTelemetryKey];
|
||||||
|
const otherTelemetryKeys = Object.keys(this.#telemetryCollections).slice(0);
|
||||||
|
if (newTelemetryParameter.accumulateValues) {
|
||||||
|
telemetryForComps[newTelemetryKey] = this.#telemetryCollections[newTelemetryKey].getAll();
|
||||||
|
} else {
|
||||||
|
telemetryForComps[newTelemetryKey] = newTelemetryData;
|
||||||
|
}
|
||||||
|
otherTelemetryKeys.forEach((keyString) => {
|
||||||
|
telemetryForComps[keyString] = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const otherTelemetryKeysNotAccumulating = otherTelemetryKeys.filter(
|
||||||
|
(keyString) => !this.#getParameterForKeyString(keyString).accumulateValues
|
||||||
|
);
|
||||||
|
const otherTelemetryKeysAccumulating = otherTelemetryKeys.filter(
|
||||||
|
(keyString) => this.#getParameterForKeyString(keyString).accumulateValues
|
||||||
|
);
|
||||||
|
|
||||||
|
// if we're accumulating, just add all the data
|
||||||
|
otherTelemetryKeysAccumulating.forEach((keyString) => {
|
||||||
|
telemetryForComps[keyString] = this.#telemetryCollections[keyString].getAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
// for the others, march through the new telemetry data and add data to the frame from the other telemetry objects
|
||||||
|
// using LOCF
|
||||||
|
newTelemetryData.forEach((newDatum) => {
|
||||||
|
otherTelemetryKeysNotAccumulating.forEach((otherKeyString) => {
|
||||||
|
const otherCollection = this.#telemetryCollections[otherKeyString];
|
||||||
|
const imputedDatum = this.#getImputedDataUsingLOCF(newDatum, otherCollection);
|
||||||
|
if (imputedDatum) {
|
||||||
|
telemetryForComps[otherKeyString].push(imputedDatum);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return telemetryForComps;
|
||||||
|
}
|
||||||
|
|
||||||
|
#removeTelemetryObject = (telemetryObjectIdentifier) => {
|
||||||
|
const keyString = this.#openmct.objects.makeKeyString(telemetryObjectIdentifier);
|
||||||
|
delete this.#telemetryObjects[keyString];
|
||||||
|
this.#telemetryCollections[keyString]?.destroy();
|
||||||
|
delete this.#telemetryCollections[keyString];
|
||||||
|
// remove all parameters that reference this telemetry object
|
||||||
|
this.deleteParameter(keyString);
|
||||||
|
};
|
||||||
|
|
||||||
|
#requestUnderlyingTelemetry() {
|
||||||
|
const underlyingTelemetry = {};
|
||||||
|
Object.keys(this.#telemetryCollections).forEach((collectionKey) => {
|
||||||
|
const collection = this.#telemetryCollections[collectionKey];
|
||||||
|
underlyingTelemetry[collectionKey] = collection.getAll();
|
||||||
|
});
|
||||||
|
return underlyingTelemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
#getTelemetryProcessor(keyString) {
|
||||||
|
if (this.#telemetryProcessors[keyString]) {
|
||||||
|
return this.#telemetryProcessors[keyString];
|
||||||
|
}
|
||||||
|
|
||||||
|
const telemetryProcessor = (newTelemetry) => {
|
||||||
|
this.emit('underlyingTelemetryUpdated', { [keyString]: newTelemetry });
|
||||||
|
};
|
||||||
|
this.#telemetryProcessors[keyString] = telemetryProcessor;
|
||||||
|
return telemetryProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
#telemetryProcessor = (newTelemetry, keyString) => {
|
||||||
|
this.emit('underlyingTelemetryUpdated', { [keyString]: newTelemetry });
|
||||||
|
};
|
||||||
|
|
||||||
|
clearData(telemetryLoadedPromise) {
|
||||||
|
this.#loaded = false;
|
||||||
|
if (telemetryLoadedPromise) {
|
||||||
|
this.#telemetryLoadedPromises.push(telemetryLoadedPromise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setOutputFormat(outputFormat) {
|
||||||
|
this.#domainObject.configuration.comps.outputFormat = outputFormat;
|
||||||
|
this.emit('outputFormatChanged', outputFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutputFormat() {
|
||||||
|
return this.#domainObject.configuration.comps.outputFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
getExpression() {
|
||||||
|
return this.#domainObject.configuration.comps.expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addTelemetryObject = (telemetryObject) => {
|
||||||
|
const keyString = this.#openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
|
this.#telemetryObjects[keyString] = telemetryObject;
|
||||||
|
this.#telemetryCollections[keyString] = this.#openmct.telemetry.requestCollection(
|
||||||
|
telemetryObject,
|
||||||
|
this.#telemetryOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
// check to see if we have a corresponding parameter
|
||||||
|
// if not, add one
|
||||||
|
const parameterExists = this.#domainObject.configuration.comps.parameters.some(
|
||||||
|
(parameter) => parameter.keyString === keyString
|
||||||
|
);
|
||||||
|
if (!parameterExists) {
|
||||||
|
this.addParameter(telemetryObject);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static getCompsManager(domainObject, openmct, compsManagerPool) {
|
||||||
|
const id = openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
|
||||||
|
if (!compsManagerPool[id]) {
|
||||||
|
compsManagerPool[id] = new CompsManager(openmct, domainObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compsManagerPool[id];
|
||||||
|
}
|
||||||
|
}
|
139
src/plugins/comps/CompsMathWorker.js
Normal file
139
src/plugins/comps/CompsMathWorker.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { evaluate } from 'mathjs';
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
onconnect = function (e) {
|
||||||
|
const port = e.ports[0];
|
||||||
|
|
||||||
|
port.onmessage = function (event) {
|
||||||
|
const { type, callbackID, telemetryForComps, expression, parameters, newTelemetry } =
|
||||||
|
event.data;
|
||||||
|
let responseType = 'unknown';
|
||||||
|
let error = null;
|
||||||
|
let result = [];
|
||||||
|
try {
|
||||||
|
if (type === 'calculateRequest') {
|
||||||
|
responseType = 'calculationRequestResult';
|
||||||
|
console.debug(`📫 Received new calculation request with callback ID ${callbackID}`);
|
||||||
|
result = calculateRequest(telemetryForComps, parameters, expression);
|
||||||
|
} else if (type === 'calculateSubscription') {
|
||||||
|
responseType = 'calculationSubscriptionResult';
|
||||||
|
result = calculateSubscription(telemetryForComps, newTelemetry, parameters, expression);
|
||||||
|
} else if (type === 'init') {
|
||||||
|
port.postMessage({ type: 'ready' });
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid message type');
|
||||||
|
}
|
||||||
|
} catch (errorInCalculation) {
|
||||||
|
error = errorInCalculation;
|
||||||
|
}
|
||||||
|
console.debug(`📭 Sending response for callback ID ${callbackID}`, result);
|
||||||
|
port.postMessage({ type: responseType, callbackID, result, error });
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function getFullDataFrame(telemetryForComps, parameters) {
|
||||||
|
const dataFrame = {};
|
||||||
|
Object.keys(telemetryForComps)?.forEach((key) => {
|
||||||
|
const parameter = parameters.find((p) => p.keyString === key);
|
||||||
|
const dataSet = telemetryForComps[key];
|
||||||
|
const telemetryMap = new Map(dataSet.map((item) => [item[parameter.timeKey], item]));
|
||||||
|
dataFrame[key] = telemetryMap;
|
||||||
|
});
|
||||||
|
return dataFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateSubscription(telemetryForComps, newTelemetry, parameters, expression) {
|
||||||
|
const dataFrame = getFullDataFrame(telemetryForComps, parameters);
|
||||||
|
const calculation = calculate(dataFrame, parameters, expression);
|
||||||
|
const newTelemetryKey = Object.keys(newTelemetry)[0];
|
||||||
|
const newTelemetrySize = newTelemetry[newTelemetryKey].length;
|
||||||
|
let trimmedCalculation = calculation;
|
||||||
|
if (calculation.length > newTelemetrySize) {
|
||||||
|
trimmedCalculation = calculation.slice(calculation.length - newTelemetrySize);
|
||||||
|
}
|
||||||
|
return trimmedCalculation;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateRequest(telemetryForComps, parameters, expression) {
|
||||||
|
const dataFrame = getFullDataFrame(telemetryForComps, parameters);
|
||||||
|
return calculate(dataFrame, parameters, expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculate(dataFrame, parameters, expression) {
|
||||||
|
const sumResults = [];
|
||||||
|
// ensure all parameter keyStrings have corresponding telemetry data
|
||||||
|
if (!expression) {
|
||||||
|
return sumResults;
|
||||||
|
}
|
||||||
|
// set up accumulated data structure
|
||||||
|
const accumulatedData = {};
|
||||||
|
parameters.forEach((parameter) => {
|
||||||
|
if (parameter.accumulateValues) {
|
||||||
|
accumulatedData[parameter.name] = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// take the first parameter keyString as the reference
|
||||||
|
const referenceParameter = parameters[0];
|
||||||
|
const otherParameters = parameters.slice(1);
|
||||||
|
// iterate over the reference telemetry data
|
||||||
|
const referenceTelemetry = dataFrame[referenceParameter.keyString];
|
||||||
|
referenceTelemetry?.forEach((referenceTelemetryItem) => {
|
||||||
|
let referenceValue = referenceTelemetryItem[referenceParameter.valueToUse];
|
||||||
|
if (referenceParameter.accumulateValues) {
|
||||||
|
accumulatedData[referenceParameter.name].push(referenceValue);
|
||||||
|
referenceValue = accumulatedData[referenceParameter.name];
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
referenceParameter.accumulateValues &&
|
||||||
|
referenceParameter.sampleSize &&
|
||||||
|
referenceParameter.sampleSize > 0
|
||||||
|
) {
|
||||||
|
// enforce sample size by ensuring referenceValue has the latest n elements
|
||||||
|
// if we don't have at least the sample size, skip this iteration
|
||||||
|
if (!referenceValue.length || referenceValue.length < referenceParameter.sampleSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
referenceValue = referenceValue.slice(-referenceParameter.sampleSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scope = {
|
||||||
|
[referenceParameter.name]: referenceValue
|
||||||
|
};
|
||||||
|
const referenceTime = referenceTelemetryItem[referenceParameter.timeKey];
|
||||||
|
// iterate over the other parameters to set the scope
|
||||||
|
let missingData = false;
|
||||||
|
otherParameters.forEach((parameter) => {
|
||||||
|
const otherDataFrame = dataFrame[parameter.keyString];
|
||||||
|
const otherTelemetry = otherDataFrame.get(referenceTime);
|
||||||
|
if (otherTelemetry === undefined || otherTelemetry === null) {
|
||||||
|
missingData = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let otherValue = otherTelemetry[parameter.valueToUse];
|
||||||
|
if (parameter.accumulateValues) {
|
||||||
|
accumulatedData[parameter.name].push(referenceValue);
|
||||||
|
otherValue = accumulatedData[referenceParameter.name];
|
||||||
|
}
|
||||||
|
scope[parameter.name] = otherValue;
|
||||||
|
});
|
||||||
|
if (missingData) {
|
||||||
|
console.debug('🤦♂️ Missing data for some parameters, skipping calculation');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rawComputedValue = evaluate(expression, scope);
|
||||||
|
let computedValue = rawComputedValue;
|
||||||
|
if (computedValue.entries) {
|
||||||
|
// if there aren't any entries, return with nothing
|
||||||
|
if (computedValue.entries.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.debug('📊 Computed value is an array of entries', computedValue.entries);
|
||||||
|
// make array of arrays of entries
|
||||||
|
computedValue = computedValue.entries?.[0];
|
||||||
|
}
|
||||||
|
sumResults.push({ [referenceParameter.timeKey]: referenceTime, value: computedValue });
|
||||||
|
});
|
||||||
|
return sumResults;
|
||||||
|
}
|
79
src/plugins/comps/CompsMetadataProvider.js
Normal file
79
src/plugins/comps/CompsMetadataProvider.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import CompsManager from './CompsManager.js';
|
||||||
|
|
||||||
|
export default class CompsMetadataProvider {
|
||||||
|
#openmct = null;
|
||||||
|
#compsManagerPool = null;
|
||||||
|
|
||||||
|
constructor(openmct, compsManagerPool) {
|
||||||
|
this.#openmct = openmct;
|
||||||
|
this.#compsManagerPool = compsManagerPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsMetadata(domainObject) {
|
||||||
|
return domainObject.type === 'comps';
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultDomains(domainObject) {
|
||||||
|
return this.#openmct.time.getAllTimeSystems().map(function (ts, i) {
|
||||||
|
return {
|
||||||
|
key: ts.key,
|
||||||
|
name: ts.name,
|
||||||
|
format: ts.timeFormat,
|
||||||
|
hints: {
|
||||||
|
domain: i
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMetadata(domainObject) {
|
||||||
|
const specificCompsManager = CompsManager.getCompsManager(
|
||||||
|
domainObject,
|
||||||
|
this.#openmct,
|
||||||
|
this.#compsManagerPool
|
||||||
|
);
|
||||||
|
// if there are any parameters, grab the first one's timeMetaData
|
||||||
|
const timeMetaData = specificCompsManager?.getParameters()[0]?.timeMetaData;
|
||||||
|
const metaDataToReturn = {
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
key: 'value',
|
||||||
|
name: 'Value',
|
||||||
|
derived: true,
|
||||||
|
formatString: specificCompsManager.getOutputFormat(),
|
||||||
|
hints: {
|
||||||
|
range: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
if (timeMetaData) {
|
||||||
|
metaDataToReturn.values.push(timeMetaData);
|
||||||
|
} else {
|
||||||
|
const defaultDomains = this.getDefaultDomains(domainObject);
|
||||||
|
metaDataToReturn.values.push(...defaultDomains);
|
||||||
|
}
|
||||||
|
return metaDataToReturn;
|
||||||
|
}
|
||||||
|
}
|
175
src/plugins/comps/CompsTelemetryProvider.js
Normal file
175
src/plugins/comps/CompsTelemetryProvider.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import CompsManager from './CompsManager.js';
|
||||||
|
|
||||||
|
export default class CompsTelemetryProvider {
|
||||||
|
#openmct = null;
|
||||||
|
#sharedWorker = null;
|
||||||
|
#compsManagerPool = null;
|
||||||
|
#lastUniqueID = 1;
|
||||||
|
#requestPromises = {};
|
||||||
|
#subscriptionCallbacks = {};
|
||||||
|
// id is random 4 digit number
|
||||||
|
#id = Math.floor(Math.random() * 9000) + 1000;
|
||||||
|
|
||||||
|
constructor(openmct, compsManagerPool) {
|
||||||
|
this.#openmct = openmct;
|
||||||
|
this.#compsManagerPool = compsManagerPool;
|
||||||
|
this.#openmct.on('start', this.#startSharedWorker.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
isTelemetryObject(domainObject) {
|
||||||
|
return domainObject.type === 'comps';
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsRequest(domainObject) {
|
||||||
|
return domainObject.type === 'comps';
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsSubscribe(domainObject) {
|
||||||
|
return domainObject.type === 'comps';
|
||||||
|
}
|
||||||
|
|
||||||
|
#getCallbackID() {
|
||||||
|
return this.#lastUniqueID++;
|
||||||
|
}
|
||||||
|
|
||||||
|
request(domainObject, options) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const specificCompsManager = CompsManager.getCompsManager(
|
||||||
|
domainObject,
|
||||||
|
this.#openmct,
|
||||||
|
this.#compsManagerPool
|
||||||
|
);
|
||||||
|
specificCompsManager.load(options).then(() => {
|
||||||
|
const callbackID = this.#getCallbackID();
|
||||||
|
const telemetryForComps = JSON.parse(
|
||||||
|
JSON.stringify(specificCompsManager.getDataFrameForRequest())
|
||||||
|
);
|
||||||
|
const expression = specificCompsManager.getExpression();
|
||||||
|
const parameters = JSON.parse(JSON.stringify(specificCompsManager.getParameters()));
|
||||||
|
if (!expression || !parameters) {
|
||||||
|
resolve([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#requestPromises[callbackID] = { resolve, reject };
|
||||||
|
const payload = {
|
||||||
|
type: 'calculateRequest',
|
||||||
|
telemetryForComps,
|
||||||
|
expression,
|
||||||
|
parameters,
|
||||||
|
callbackID
|
||||||
|
};
|
||||||
|
this.#sharedWorker.port.postMessage(payload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#computeOnNewTelemetry(specificCompsManager, callbackID, newTelemetry) {
|
||||||
|
if (!specificCompsManager.isReady()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const expression = specificCompsManager.getExpression();
|
||||||
|
const telemetryForComps = specificCompsManager.getDataFrameForSubscription(newTelemetry);
|
||||||
|
const parameters = JSON.parse(JSON.stringify(specificCompsManager.getParameters()));
|
||||||
|
if (!expression || !parameters) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const payload = {
|
||||||
|
type: 'calculateSubscription',
|
||||||
|
telemetryForComps,
|
||||||
|
newTelemetry,
|
||||||
|
expression,
|
||||||
|
parameters,
|
||||||
|
callbackID
|
||||||
|
};
|
||||||
|
this.#sharedWorker.port.postMessage(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(domainObject, callback) {
|
||||||
|
const specificCompsManager = CompsManager.getCompsManager(
|
||||||
|
domainObject,
|
||||||
|
this.#openmct,
|
||||||
|
this.#compsManagerPool
|
||||||
|
);
|
||||||
|
const callbackID = this.#getCallbackID();
|
||||||
|
this.#subscriptionCallbacks[callbackID] = callback;
|
||||||
|
const boundComputeOnNewTelemetry = this.#computeOnNewTelemetry.bind(
|
||||||
|
this,
|
||||||
|
specificCompsManager,
|
||||||
|
callbackID
|
||||||
|
);
|
||||||
|
specificCompsManager.on('underlyingTelemetryUpdated', boundComputeOnNewTelemetry);
|
||||||
|
const telemetryOptions = {
|
||||||
|
strategy: 'latest',
|
||||||
|
size: 1
|
||||||
|
};
|
||||||
|
specificCompsManager.load(telemetryOptions);
|
||||||
|
return () => {
|
||||||
|
delete this.#subscriptionCallbacks[callbackID];
|
||||||
|
specificCompsManager.stopListeningToUnderlyingTelemetry();
|
||||||
|
specificCompsManager.off('underlyingTelemetryUpdated', boundComputeOnNewTelemetry);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#startSharedWorker() {
|
||||||
|
if (this.#sharedWorker) {
|
||||||
|
throw new Error('Shared worker already started');
|
||||||
|
}
|
||||||
|
const sharedWorkerURL = `${this.#openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}compsMathWorker.js`;
|
||||||
|
|
||||||
|
this.#sharedWorker = new SharedWorker(sharedWorkerURL, `Comps Math Worker`);
|
||||||
|
this.#sharedWorker.port.onmessage = this.onSharedWorkerMessage.bind(this);
|
||||||
|
this.#sharedWorker.port.onmessageerror = this.onSharedWorkerMessageError.bind(this);
|
||||||
|
this.#sharedWorker.port.start();
|
||||||
|
|
||||||
|
this.#sharedWorker.port.postMessage({ type: 'init' });
|
||||||
|
|
||||||
|
this.#openmct.on('destroy', () => {
|
||||||
|
this.#sharedWorker.port.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSharedWorkerMessage(event) {
|
||||||
|
const { type, result, callbackID, error } = event.data;
|
||||||
|
if (
|
||||||
|
type === 'calculationSubscriptionResult' &&
|
||||||
|
this.#subscriptionCallbacks[callbackID] &&
|
||||||
|
result.length
|
||||||
|
) {
|
||||||
|
this.#subscriptionCallbacks[callbackID](result);
|
||||||
|
} else if (type === 'calculationRequestResult' && this.#requestPromises[callbackID]) {
|
||||||
|
if (error) {
|
||||||
|
console.error('📝 Error calculating request:', event.data);
|
||||||
|
this.#requestPromises[callbackID].resolve([]);
|
||||||
|
} else {
|
||||||
|
this.#requestPromises[callbackID].resolve(result);
|
||||||
|
}
|
||||||
|
delete this.#requestPromises[callbackID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSharedWorkerMessageError(event) {
|
||||||
|
console.error('❌ Shared worker message error:', event);
|
||||||
|
}
|
||||||
|
}
|
98
src/plugins/comps/CompsViewProvider.js
Normal file
98
src/plugins/comps/CompsViewProvider.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import mount from 'utils/mount';
|
||||||
|
|
||||||
|
import CompsView from './components/CompsView.vue';
|
||||||
|
|
||||||
|
const DEFAULT_VIEW_PRIORITY = 100;
|
||||||
|
|
||||||
|
export default class ConditionSetViewProvider {
|
||||||
|
constructor(openmct, compsManagerPool) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.name = 'Comps View';
|
||||||
|
this.key = 'comps.view';
|
||||||
|
this.cssClass = 'icon-derived-telemetry';
|
||||||
|
this.compsManagerPool = compsManagerPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
canView(domainObject, objectPath) {
|
||||||
|
return domainObject.type === 'comps' && this.openmct.router.isNavigatedObject(objectPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
canEdit(domainObject, objectPath) {
|
||||||
|
return domainObject.type === 'comps' && this.openmct.router.isNavigatedObject(objectPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
view(domainObject, objectPath) {
|
||||||
|
let _destroy = null;
|
||||||
|
let component = null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: (container, isEditing) => {
|
||||||
|
const { vNode, destroy } = mount(
|
||||||
|
{
|
||||||
|
el: container,
|
||||||
|
components: {
|
||||||
|
CompsView
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: this.openmct,
|
||||||
|
domainObject,
|
||||||
|
objectPath,
|
||||||
|
compsManagerPool: this.compsManagerPool
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isEditing
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: '<CompsView :isEditing="isEditing"></CompsView>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
app: this.openmct.app,
|
||||||
|
element: container
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_destroy = destroy;
|
||||||
|
component = vNode.componentInstance;
|
||||||
|
},
|
||||||
|
onEditModeChange: (isEditing) => {
|
||||||
|
component.isEditing = isEditing;
|
||||||
|
},
|
||||||
|
destroy: () => {
|
||||||
|
if (_destroy) {
|
||||||
|
_destroy();
|
||||||
|
}
|
||||||
|
component = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
priority(domainObject) {
|
||||||
|
if (domainObject.type === 'comps') {
|
||||||
|
return Number.MAX_VALUE;
|
||||||
|
} else {
|
||||||
|
return DEFAULT_VIEW_PRIORITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
src/plugins/comps/components/CompsInspectorView.vue
Normal file
77
src/plugins/comps/components/CompsInspectorView.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<!--
|
||||||
|
Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
Administration. All rights reserved.
|
||||||
|
|
||||||
|
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
Open MCT includes source code licensed under additional open source
|
||||||
|
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
this source code distribution or the Licensing information page available
|
||||||
|
at runtime from the About dialog for additional information.
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="c-inspect-properties">
|
||||||
|
<template v-if="isEditing">
|
||||||
|
<ul class="c-inspect-properties__section">
|
||||||
|
<li class="c-inspect-properties__row">
|
||||||
|
<div class="c-inspect-properties__label" title="Output Format">
|
||||||
|
<label for="OutputFormatControl">Output Format</label>
|
||||||
|
</div>
|
||||||
|
<div class="c-inspect-properties__value">
|
||||||
|
<input
|
||||||
|
id="OutputFormatControl"
|
||||||
|
v-model="inputFormatValue"
|
||||||
|
type="text"
|
||||||
|
class="c-input--flex"
|
||||||
|
placeholder="e.g. %0.2f"
|
||||||
|
@change="changeInputFormat()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { inject, onBeforeMount, onBeforeUnmount, ref } from 'vue';
|
||||||
|
|
||||||
|
import CompsManager from '../CompsManager';
|
||||||
|
|
||||||
|
const isEditing = ref(false);
|
||||||
|
const inputFormatValue = ref('');
|
||||||
|
|
||||||
|
const openmct = inject('openmct');
|
||||||
|
const domainObject = inject('domainObject');
|
||||||
|
const compsManagerPool = inject('compsManagerPool');
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
isEditing.value = openmct.editor.isEditing();
|
||||||
|
openmct.editor.on('isEditing', toggleEdit);
|
||||||
|
inputFormatValue.value = domainObject.configuration.comps.outputFormat;
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
openmct.editor.off('isEditing', toggleEdit);
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleEdit(passedIsEditing) {
|
||||||
|
isEditing.value = passedIsEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeInputFormat() {
|
||||||
|
openmct.objects.mutate(domainObject, `configuration.comps.outputFormat`, inputFormatValue.value);
|
||||||
|
const compsManager = CompsManager.getCompsManager(domainObject, openmct, compsManagerPool);
|
||||||
|
compsManager.setOutputFormat(inputFormatValue.value);
|
||||||
|
}
|
||||||
|
</script>
|
399
src/plugins/comps/components/CompsView.vue
Normal file
399
src/plugins/comps/components/CompsView.vue
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
<!--
|
||||||
|
Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
Administration. All rights reserved.
|
||||||
|
|
||||||
|
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
Open MCT includes source code licensed under additional open source
|
||||||
|
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
this source code distribution or the Licensing information page available
|
||||||
|
at runtime from the About dialog for additional information.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="c-comps" aria-label="Derived Telemetry">
|
||||||
|
<section class="c-section c-comps-output">
|
||||||
|
<div class="c-output-featured">
|
||||||
|
<span class="c-output-featured__label">Current Output</span>
|
||||||
|
<span class="c-output-featured__value" aria-label="Current Output Value">
|
||||||
|
<template
|
||||||
|
v-if="testDataApplied && currentTestOutput !== undefined && currentTestOutput !== null"
|
||||||
|
>
|
||||||
|
{{ currentTestOutput }}
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-else-if="
|
||||||
|
!testDataApplied && currentCompOutput !== undefined && currentCompOutput !== null
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ currentCompOutput }}
|
||||||
|
</template>
|
||||||
|
<template v-else> --- </template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section
|
||||||
|
id="telemetryReferenceSection"
|
||||||
|
class="c-comps__section c-comps__refs-and-controls"
|
||||||
|
aria-describedby="telemetryReferences"
|
||||||
|
>
|
||||||
|
<div class="c-cs__header c-section__header">
|
||||||
|
<div id="telemetryReferences" class="c-cs__header-label c-section__label">
|
||||||
|
Telemetry References
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
|
class="c-comps__apply-test-data-control"
|
||||||
|
:class="['c-comps__refs-controls c-cdef__controls', { disabled: !parameters?.length }]"
|
||||||
|
>
|
||||||
|
<label class="c-toggle-switch">
|
||||||
|
<input type="checkbox" :checked="testDataApplied" @change="toggleTestData" />
|
||||||
|
<span class="c-toggle-switch__slider" aria-label="Apply Test Data"></span>
|
||||||
|
<span class="c-toggle-switch__label">Apply Test Values</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="c-comps__refs">
|
||||||
|
<div v-for="parameter in parameters" :key="parameter.keyString" class="c-comps__ref">
|
||||||
|
<div class="c-comps__ref-section">
|
||||||
|
<div class="c-comps__ref-sub-section ref-and-path">
|
||||||
|
<span class="c-test-datum__string">Reference</span>
|
||||||
|
<input
|
||||||
|
v-if="isEditing"
|
||||||
|
v-model="parameter.name"
|
||||||
|
:aria-label="`Reference Name Input for ${parameter.name}`"
|
||||||
|
type="text"
|
||||||
|
class="c-input--md"
|
||||||
|
@change="updateParameters"
|
||||||
|
/>
|
||||||
|
<div v-else class="--em">{{ parameter.name }}</div>
|
||||||
|
<span class="c-test-datum__string">=</span>
|
||||||
|
<span
|
||||||
|
class="c-comps__path-and-field"
|
||||||
|
:aria-label="`Reference ${parameter.name} Object Path`"
|
||||||
|
>
|
||||||
|
<ObjectPathString
|
||||||
|
:domain-object="compsManager.getTelemetryObjectForParameter(parameter.keyString)"
|
||||||
|
:show-object-itself="true"
|
||||||
|
class="c-comp__ref-path --em"
|
||||||
|
/>
|
||||||
|
<!-- drop down to select value from telemetry -->
|
||||||
|
<select
|
||||||
|
v-if="isEditing"
|
||||||
|
v-model="parameter.valueToUse"
|
||||||
|
class="c-comp__ref-field"
|
||||||
|
@change="updateParameters"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="parameterValueOption in compsManager.getMetaDataValuesForParameter(
|
||||||
|
parameter.keyString
|
||||||
|
)"
|
||||||
|
:key="parameterValueOption.key"
|
||||||
|
:value="parameterValueOption.key"
|
||||||
|
>
|
||||||
|
{{ parameterValueOption.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div v-else class="c-comp__ref-field">{{ parameter.valueToUse }}</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
|
class="c-comps__ref-sub-section accum-vals"
|
||||||
|
:class="['c-comps__refs-controls', { disabled: !parameters?.length }]"
|
||||||
|
>
|
||||||
|
<label class="c-toggle-switch">
|
||||||
|
<span class="c-toggle-switch__label">Accumulate Values</span>
|
||||||
|
<input
|
||||||
|
v-model="parameter.accumulateValues"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateAccumulateValues(parameter)"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="c-toggle-switch__slider"
|
||||||
|
aria-label="Toggle Parameter Accumulation"
|
||||||
|
></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span v-if="isEditing && parameter.accumulateValues" class="c-comps__label"
|
||||||
|
>Sample Size</span
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-if="isEditing && parameter.accumulateValues"
|
||||||
|
v-model="parameter.sampleSize"
|
||||||
|
:aria-label="`Sample Size for ${parameter.name}`"
|
||||||
|
type="number"
|
||||||
|
class="c-input--sm c-comps__value"
|
||||||
|
@change="updateParameters"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="!isEditing && parameter.accumulateValues"
|
||||||
|
class="c-comps__ref-sub-section accum-vals"
|
||||||
|
>
|
||||||
|
Accumulating values with sample size {{ parameter.sampleSize }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isEditing" class="c-comps__ref-section">
|
||||||
|
<span class="c-comps__label">Test value</span>
|
||||||
|
<input
|
||||||
|
v-if="isEditing"
|
||||||
|
v-model="parameter.testValue"
|
||||||
|
:aria-label="`Reference Test Value for ${parameter.name}`"
|
||||||
|
type="text"
|
||||||
|
class="c-input--md c-comps__value"
|
||||||
|
@change="updateTestValue(parameter)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="expressionSection" class="c-comps__section c-comps__expression">
|
||||||
|
<div class="c-cs__header c-section__header">
|
||||||
|
<div class="c-cs__header-label c-section__label">Expression</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!parameters?.length && isEditing" class="hint">
|
||||||
|
Drag in telemetry to add references for an expression.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
v-if="parameters?.length && isEditing"
|
||||||
|
v-model="expression"
|
||||||
|
class="c-comps__expression-value"
|
||||||
|
placeholder="Enter an expression"
|
||||||
|
@change="updateExpression"
|
||||||
|
></textarea>
|
||||||
|
<div v-else>
|
||||||
|
<div class="c-comps__expression-value" aria-label="Expression">
|
||||||
|
{{ expression }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-if="expression && expressionOutput"
|
||||||
|
class="icon-alert-triangle c-comps__expression-msg --bad"
|
||||||
|
>
|
||||||
|
Invalid: {{ expressionOutput }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else-if="expression && !expressionOutput && isEditing"
|
||||||
|
class="c-comps__expression-msg --good"
|
||||||
|
>
|
||||||
|
Expression valid
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { evaluate } from 'mathjs';
|
||||||
|
import { inject, onBeforeMount, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import ObjectPathString from '../../../ui/components/ObjectPathString.vue';
|
||||||
|
import CompsManager from '../CompsManager';
|
||||||
|
|
||||||
|
const openmct = inject('openmct');
|
||||||
|
const domainObject = inject('domainObject');
|
||||||
|
const compsManagerPool = inject('compsManagerPool');
|
||||||
|
const compsManager = CompsManager.getCompsManager(domainObject, openmct, compsManagerPool);
|
||||||
|
const currentCompOutput = ref(null);
|
||||||
|
const currentTestOutput = ref(null);
|
||||||
|
const testDataApplied = ref(false);
|
||||||
|
const parameters = ref(null);
|
||||||
|
const expression = ref(null);
|
||||||
|
const expressionOutput = ref(null);
|
||||||
|
const outputFormat = ref(null);
|
||||||
|
|
||||||
|
let outputTelemetryCollection;
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isEditing: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
let maxSampleSize = 20;
|
||||||
|
if (parameters.value) {
|
||||||
|
maxSampleSize =
|
||||||
|
parameters.value.reduce((max, param) => {
|
||||||
|
if (param.accumulateValues) {
|
||||||
|
return Math.max(max, param.sampleSize);
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}, 0) + 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
const telemetryOptions = {
|
||||||
|
strategy: 'minmax',
|
||||||
|
size: maxSampleSize
|
||||||
|
};
|
||||||
|
// TODO: we should dynamically set size to the largest comp input window
|
||||||
|
outputTelemetryCollection = openmct.telemetry.requestCollection(domainObject, telemetryOptions);
|
||||||
|
outputTelemetryCollection.on('add', telemetryProcessor);
|
||||||
|
outputTelemetryCollection.on('clear', clearData);
|
||||||
|
compsManager.on('parameterAdded', reloadParameters);
|
||||||
|
compsManager.on('parameterRemoved', reloadParameters);
|
||||||
|
compsManager.on('outputFormatChanged', updateOutputFormat);
|
||||||
|
await outputTelemetryCollection.load(telemetryOptions); // will implicitly load compsManager
|
||||||
|
parameters.value = compsManager.getParameters();
|
||||||
|
expression.value = compsManager.getExpression();
|
||||||
|
outputFormat.value = compsManager.getOutputFormat();
|
||||||
|
applyTestData();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
outputTelemetryCollection.off('add', telemetryProcessor);
|
||||||
|
outputTelemetryCollection.off('clear', clearData);
|
||||||
|
compsManager.off('parameterAdded', reloadParameters);
|
||||||
|
compsManager.off('parameterRemoved', reloadParameters);
|
||||||
|
compsManager.off('outputFormatChanged', updateOutputFormat);
|
||||||
|
outputTelemetryCollection.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.isEditing,
|
||||||
|
(editMode) => {
|
||||||
|
if (!editMode) {
|
||||||
|
testDataApplied.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function updateOutputFormat() {
|
||||||
|
outputFormat.value = compsManager.getOutputFormat();
|
||||||
|
// delete the metadata cache so that the new output format is used
|
||||||
|
openmct.telemetry.removeMetadataFromCache(domainObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadParameters(passedDomainObject) {
|
||||||
|
// Because this is triggered by a composition change, we have
|
||||||
|
// to defer mutation of our domain object, otherwise we might
|
||||||
|
// mutate an outdated version of the domain object.
|
||||||
|
setTimeout(function () {
|
||||||
|
domainObject.configuration.comps.parameters = passedDomainObject.configuration.comps.parameters;
|
||||||
|
parameters.value = domainObject.configuration.comps.parameters;
|
||||||
|
openmct.objects.mutate(domainObject, `configuration.comps.parameters`, parameters.value);
|
||||||
|
compsManager.setDomainObject(domainObject);
|
||||||
|
applyTestData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateParameters() {
|
||||||
|
openmct.objects.mutate(domainObject, `configuration.comps.parameters`, parameters.value);
|
||||||
|
compsManager.setDomainObject(domainObject);
|
||||||
|
applyTestData();
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAccumulateValues(parameter) {
|
||||||
|
if (parameter.accumulateValues) {
|
||||||
|
parameter.testValue = [''];
|
||||||
|
} else {
|
||||||
|
parameter.testValue = '';
|
||||||
|
}
|
||||||
|
updateParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTestValue(parameter) {
|
||||||
|
if (parameter.accumulateValues && parameter.testValue === '') {
|
||||||
|
parameter.testValue = [];
|
||||||
|
}
|
||||||
|
updateParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTestData() {
|
||||||
|
testDataApplied.value = !testDataApplied.value;
|
||||||
|
if (testDataApplied.value) {
|
||||||
|
applyTestData();
|
||||||
|
} else {
|
||||||
|
clearData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateExpression() {
|
||||||
|
openmct.objects.mutate(domainObject, `configuration.comps.expression`, expression.value);
|
||||||
|
compsManager.setDomainObject(domainObject);
|
||||||
|
applyTestData();
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValueFormatter() {
|
||||||
|
const metaData = openmct.telemetry.getMetadata(domainObject);
|
||||||
|
const outputMetaDatum = metaData.values().find((metaDatum) => metaDatum.key === 'value');
|
||||||
|
return openmct.telemetry.getValueFormatter(outputMetaDatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTestData() {
|
||||||
|
if (!expression.value || !parameters.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const scope = parameters.value.reduce((acc, parameter) => {
|
||||||
|
// try to parse the test value as JSON
|
||||||
|
try {
|
||||||
|
const parsedValue = JSON.parse(parameter.testValue);
|
||||||
|
acc[parameter.name] = parsedValue;
|
||||||
|
} catch (error) {
|
||||||
|
acc[parameter.name] = parameter.testValue;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// see which parameters are misconfigured as non-arrays
|
||||||
|
const misconfiguredParameterNames = parameters.value
|
||||||
|
.filter((parameter) => {
|
||||||
|
return parameter.accumulateValues && !Array.isArray(scope[parameter.name]);
|
||||||
|
})
|
||||||
|
.map((parameter) => parameter.name);
|
||||||
|
if (misconfiguredParameterNames.length) {
|
||||||
|
const misconfiguredParameterNamesString = misconfiguredParameterNames.join(', ');
|
||||||
|
currentTestOutput.value = null;
|
||||||
|
expressionOutput.value = `Reference "${misconfiguredParameterNamesString}" set to accumulating, but test values aren't arrays.`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const testOutput = evaluate(expression.value, scope);
|
||||||
|
const formattedData = getValueFormatter().format(testOutput);
|
||||||
|
currentTestOutput.value = formattedData;
|
||||||
|
expressionOutput.value = null;
|
||||||
|
} catch (error) {
|
||||||
|
currentTestOutput.value = null;
|
||||||
|
expressionOutput.value = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function telemetryProcessor(data) {
|
||||||
|
if (testDataApplied.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// new data will come in as array, so just take the last element
|
||||||
|
const currentOutput = data[data.length - 1]?.value;
|
||||||
|
const formattedOutput = getValueFormatter().format(currentOutput);
|
||||||
|
currentCompOutput.value = formattedOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
clearData();
|
||||||
|
outputTelemetryCollection._requestHistoricalTelemetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearData() {
|
||||||
|
currentCompOutput.value = null;
|
||||||
|
}
|
||||||
|
</script>
|
153
src/plugins/comps/components/comps.scss
Normal file
153
src/plugins/comps/components/comps.scss
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
@mixin expressionMsg($fg, $bg) {
|
||||||
|
$op: 0.4;
|
||||||
|
color: rgba($fg, $op * 1.5);
|
||||||
|
background: rgba($bg, $op);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-comps {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $interiorMarginLg;
|
||||||
|
|
||||||
|
.is-editing & {
|
||||||
|
padding: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__output {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: $interiorMargin;
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section,
|
||||||
|
&__refs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&__apply-test-data-control {
|
||||||
|
padding: $interiorMargin 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__refs {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__ref {
|
||||||
|
@include discreteItem();
|
||||||
|
align-items: start;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 $interiorMargin;
|
||||||
|
line-height: 170%; // Aligns text with controls like selects
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
border-top: 1px solid $colorInteriorBorder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__ref-section {
|
||||||
|
align-items: baseline;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: $interiorMargin;
|
||||||
|
padding: $interiorMargin 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__ref-sub-section {
|
||||||
|
align-items: baseline;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
gap: $interiorMargin;
|
||||||
|
|
||||||
|
&.ref-and-path {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__path-and-field {
|
||||||
|
align-items: start;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: $interiorMargin;
|
||||||
|
|
||||||
|
.c-comp__ref-path {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label,
|
||||||
|
&__value {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__expression {
|
||||||
|
*[class*=value] {
|
||||||
|
font-family: monospace;
|
||||||
|
resize: vertical; // Only applies to textarea
|
||||||
|
}
|
||||||
|
div[class*=value] {
|
||||||
|
padding: $interiorMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__expression-msg {
|
||||||
|
@include expressionMsg($colorOkFg, $colorOk);
|
||||||
|
border-radius: $basicCr;
|
||||||
|
display: flex; // Creates hanging indent from :before icon
|
||||||
|
padding: $interiorMarginSm $interiorMarginLg $interiorMarginSm $interiorMargin;
|
||||||
|
max-width: max-content;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: $glyph-icon-check;
|
||||||
|
font-family: symbolsfont;
|
||||||
|
margin-right: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.--bad {
|
||||||
|
@include expressionMsg($colorErrorFg, $colorError);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: $glyph-icon-alert-triangle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.--em {
|
||||||
|
color: $colorBodyFgEm;
|
||||||
|
}
|
||||||
|
}
|
60
src/plugins/comps/plugin.js
Normal file
60
src/plugins/comps/plugin.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import CompsInspectorViewProvider from './CompsInspectorViewProvider.js';
|
||||||
|
import CompsMetadataProvider from './CompsMetadataProvider.js';
|
||||||
|
import CompsTelemetryProvider from './CompsTelemetryProvider.js';
|
||||||
|
import CompsViewProvider from './CompsViewProvider.js';
|
||||||
|
|
||||||
|
export default function CompsPlugin() {
|
||||||
|
const compsManagerPool = {};
|
||||||
|
|
||||||
|
return function install(openmct) {
|
||||||
|
openmct.types.addType('comps', {
|
||||||
|
name: 'Derived Telemetry',
|
||||||
|
key: 'comps',
|
||||||
|
description:
|
||||||
|
'Add one or more telemetry end points, apply a mathematical operation to them, and output the result as new telemetry.',
|
||||||
|
creatable: true,
|
||||||
|
cssClass: 'icon-derived-telemetry',
|
||||||
|
initialize: function (domainObject) {
|
||||||
|
domainObject.configuration = {
|
||||||
|
comps: {
|
||||||
|
expression: '',
|
||||||
|
parameters: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
domainObject.composition = [];
|
||||||
|
domainObject.telemetry = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
openmct.composition.addPolicy((parent, child) => {
|
||||||
|
if (parent.type === 'comps' && !openmct.telemetry.isTelemetryObject(child)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
openmct.telemetry.addProvider(new CompsMetadataProvider(openmct, compsManagerPool));
|
||||||
|
openmct.telemetry.addProvider(new CompsTelemetryProvider(openmct, compsManagerPool));
|
||||||
|
openmct.objectViews.addProvider(new CompsViewProvider(openmct, compsManagerPool));
|
||||||
|
openmct.inspectorViews.addProvider(new CompsInspectorViewProvider(openmct, compsManagerPool));
|
||||||
|
};
|
||||||
|
}
|
@ -23,9 +23,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-cs" :class="{ 'is-stale': isStale }" aria-label="Condition Set">
|
<div class="c-cs" :class="{ 'is-stale': isStale }" aria-label="Condition Set">
|
||||||
<section class="c-cs__current-output c-section">
|
<section class="c-cs__current-output c-section">
|
||||||
<div class="c-cs__content c-cs__current-output-value">
|
<div class="c-output-featured">
|
||||||
<span class="c-cs__current-output-value__label">Current Output</span>
|
<span class="c-output-featured__label">Current Output</span>
|
||||||
<span class="c-cs__current-output-value__value" aria-label="Current Output Value">
|
<span class="c-output-featured__value" aria-label="Current Output Value">
|
||||||
<template v-if="currentConditionOutput">
|
<template v-if="currentConditionOutput">
|
||||||
{{ currentConditionOutput }}
|
{{ currentConditionOutput }}
|
||||||
</template>
|
</template>
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
gap: $interiorMargin;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@ -89,23 +90,24 @@
|
|||||||
&__conditions {
|
&__conditions {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
> * + * {
|
//> * + * {
|
||||||
margin-top: $interiorMarginSm;
|
// margin-top: $interiorMarginSm;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: $interiorMarginSm;
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
+ * {
|
//+ * {
|
||||||
margin-top: $interiorMarginSm;
|
// margin-top: $interiorMarginSm;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-button {
|
.c-button {
|
||||||
@ -121,6 +123,7 @@
|
|||||||
section {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: $interiorMargin;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,9 +27,9 @@ To define a filter, you'll need to add a new `filter` property to the domain obj
|
|||||||
singleSelectionThreshold: true,
|
singleSelectionThreshold: true,
|
||||||
comparator: 'equals',
|
comparator: 'equals',
|
||||||
possibleValues: [
|
possibleValues: [
|
||||||
{ name: 'Apple', value: 'apple' },
|
{ label: 'Apple', value: 'apple' },
|
||||||
{ name: 'Banana', value: 'banana' },
|
{ label: 'Banana', value: 'banana' },
|
||||||
{ name: 'Orange', value: 'orange' }
|
{ label: 'Orange', value: 'orange' }
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
@ -649,6 +649,11 @@ export default {
|
|||||||
},
|
},
|
||||||
request(domainObject = this.telemetryObject) {
|
request(domainObject = this.telemetryObject) {
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||||
|
|
||||||
|
if (!this.metadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||||
const LimitEvaluator = this.openmct.telemetry.getLimits(domainObject);
|
const LimitEvaluator = this.openmct.telemetry.getLimits(domainObject);
|
||||||
LimitEvaluator.limits().then(this.updateLimits);
|
LimitEvaluator.limits().then(this.updateLimits);
|
||||||
|
@ -164,11 +164,13 @@
|
|||||||
<div
|
<div
|
||||||
v-show="cursorGuide"
|
v-show="cursorGuide"
|
||||||
ref="cursorGuideVertical"
|
ref="cursorGuideVertical"
|
||||||
|
aria-label="Vertical cursor guide"
|
||||||
class="c-cursor-guide--v js-cursor-guide--v"
|
class="c-cursor-guide--v js-cursor-guide--v"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
v-show="cursorGuide"
|
v-show="cursorGuide"
|
||||||
ref="cursorGuideHorizontal"
|
ref="cursorGuideHorizontal"
|
||||||
|
aria-label="Horizontal cursor guide"
|
||||||
class="c-cursor-guide--h js-cursor-guide--h"
|
class="c-cursor-guide--h js-cursor-guide--h"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@ -537,6 +539,7 @@ export default {
|
|||||||
this.followTimeContext();
|
this.followTimeContext();
|
||||||
},
|
},
|
||||||
followTimeContext() {
|
followTimeContext() {
|
||||||
|
this.updateMode();
|
||||||
this.updateDisplayBounds(this.timeContext.getBounds());
|
this.updateDisplayBounds(this.timeContext.getBounds());
|
||||||
this.timeContext.on('modeChanged', this.updateMode);
|
this.timeContext.on('modeChanged', this.updateMode);
|
||||||
this.timeContext.on('boundsChanged', this.updateDisplayBounds);
|
this.timeContext.on('boundsChanged', this.updateDisplayBounds);
|
||||||
@ -854,13 +857,11 @@ export default {
|
|||||||
|
|
||||||
this.canvas = this.$refs.chartContainer.querySelectorAll('canvas')[1];
|
this.canvas = this.$refs.chartContainer.querySelectorAll('canvas')[1];
|
||||||
|
|
||||||
if (!this.options.compact) {
|
|
||||||
this.listenTo(this.canvas, 'mousemove', this.trackMousePosition, this);
|
this.listenTo(this.canvas, 'mousemove', this.trackMousePosition, this);
|
||||||
this.listenTo(this.canvas, 'mouseleave', this.untrackMousePosition, this);
|
this.listenTo(this.canvas, 'mouseleave', this.untrackMousePosition, this);
|
||||||
this.listenTo(this.canvas, 'mousedown', this.onMouseDown, this);
|
this.listenTo(this.canvas, 'mousedown', this.onMouseDown, this);
|
||||||
this.listenTo(this.canvas, 'click', this.selectNearbyAnnotations, this);
|
this.listenTo(this.canvas, 'click', this.selectNearbyAnnotations, this);
|
||||||
this.listenTo(this.canvas, 'wheel', this.wheelZoom, this);
|
this.listenTo(this.canvas, 'wheel', this.wheelZoom, this);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
marqueeAnnotations(annotationsToSelect) {
|
marqueeAnnotations(annotationsToSelect) {
|
||||||
@ -1115,6 +1116,7 @@ export default {
|
|||||||
this.listenTo(window, 'mouseup', this.onMouseUp, this);
|
this.listenTo(window, 'mouseup', this.onMouseUp, this);
|
||||||
this.listenTo(window, 'mousemove', this.trackMousePosition, this);
|
this.listenTo(window, 'mousemove', this.trackMousePosition, this);
|
||||||
|
|
||||||
|
if (!this.options.compact) {
|
||||||
// track frozen state on mouseDown to be read on mouseUp
|
// track frozen state on mouseDown to be read on mouseUp
|
||||||
const isFrozen =
|
const isFrozen =
|
||||||
this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
|
this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
|
||||||
@ -1129,6 +1131,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return this.startMarquee(event, false);
|
return this.startMarquee(event, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseUp(event) {
|
onMouseUp(event) {
|
||||||
@ -1158,11 +1161,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
isMouseClick() {
|
isMouseClick() {
|
||||||
if (!this.marquee) {
|
// We may not have a marquee if we've disabled pan/zoom, but we still need to know if it's a mouse click for highlights and lock points.
|
||||||
|
if (!this.marquee && !this.positionOverPlot) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { start, end } = this.marquee;
|
const { start, end } = this.marquee ?? {
|
||||||
|
start: this.positionOverPlot,
|
||||||
|
end: this.positionOverPlot
|
||||||
|
};
|
||||||
const someYPositionOverPlot = start.y.some((y) => y);
|
const someYPositionOverPlot = start.y.some((y) => y);
|
||||||
|
|
||||||
return start.x === end.x && someYPositionOverPlot;
|
return start.x === end.x && someYPositionOverPlot;
|
||||||
|
@ -162,14 +162,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
gridLines(newGridLines) {
|
|
||||||
this.gridLines = newGridLines;
|
|
||||||
},
|
|
||||||
cursorGuide(newCursorGuide) {
|
|
||||||
this.cursorGuide = newCursorGuide;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
created() {
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
this.imageExporter = new ImageExporter(this.openmct);
|
this.imageExporter = new ImageExporter(this.openmct);
|
||||||
|
@ -225,7 +225,13 @@ export default class PlotSeries extends Model {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const points = await this.openmct.telemetry.request(this.domainObject, options);
|
const points = await this.openmct.telemetry.request(this.domainObject, options);
|
||||||
const data = this.getSeriesData();
|
// if derived, we can't use the old data
|
||||||
|
let data = this.getSeriesData();
|
||||||
|
|
||||||
|
if (this.metadata.value(this.get('yKey')).derived) {
|
||||||
|
data = [];
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line you-dont-need-lodash-underscore/concat
|
// eslint-disable-next-line you-dont-need-lodash-underscore/concat
|
||||||
const newPoints = _(data)
|
const newPoints = _(data)
|
||||||
.concat(points)
|
.concat(points)
|
||||||
|
@ -249,7 +249,8 @@ export default {
|
|||||||
...persistedSeriesConfig.series
|
...persistedSeriesConfig.series
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
yAxis: persistedSeriesConfig.yAxis
|
yAxis: persistedSeriesConfig.yAxis,
|
||||||
|
...this.childObject.configuration
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openmct: this.openmct,
|
openmct: this.openmct,
|
||||||
|
@ -32,6 +32,7 @@ import BarChartPlugin from './charts/bar/plugin.js';
|
|||||||
import ScatterPlotPlugin from './charts/scatter/plugin.js';
|
import ScatterPlotPlugin from './charts/scatter/plugin.js';
|
||||||
import ClearData from './clearData/plugin.js';
|
import ClearData from './clearData/plugin.js';
|
||||||
import Clock from './clock/plugin.js';
|
import Clock from './clock/plugin.js';
|
||||||
|
import CompsPlugin from './comps/plugin.js';
|
||||||
import ConditionPlugin from './condition/plugin.js';
|
import ConditionPlugin from './condition/plugin.js';
|
||||||
import ConditionWidgetPlugin from './conditionWidget/plugin.js';
|
import ConditionWidgetPlugin from './conditionWidget/plugin.js';
|
||||||
import CouchDBSearchFolder from './CouchDBSearchFolder/plugin.js';
|
import CouchDBSearchFolder from './CouchDBSearchFolder/plugin.js';
|
||||||
@ -176,5 +177,6 @@ plugins.Gauge = GaugePlugin;
|
|||||||
plugins.Timelist = TimeList;
|
plugins.Timelist = TimeList;
|
||||||
plugins.InspectorViews = InspectorViews;
|
plugins.InspectorViews = InspectorViews;
|
||||||
plugins.InspectorDataVisualization = InspectorDataVisualization;
|
plugins.InspectorDataVisualization = InspectorDataVisualization;
|
||||||
|
plugins.Comps = CompsPlugin;
|
||||||
|
|
||||||
export default plugins;
|
export default plugins;
|
||||||
|
@ -91,15 +91,19 @@ export default class TelemetryTableRow {
|
|||||||
return [VIEW_DATUM_ACTION_KEY, VIEW_HISTORICAL_DATA_ACTION_KEY];
|
return [VIEW_DATUM_ACTION_KEY, VIEW_HISTORICAL_DATA_ACTION_KEY];
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWithDatum(updatesToDatum) {
|
/**
|
||||||
const normalizedUpdatesToDatum = createNormalizedDatum(updatesToDatum, this.columns);
|
* Merges the row parameter's datum with the current row datum
|
||||||
|
* @param {TelemetryTableRow} row
|
||||||
|
*/
|
||||||
|
updateWithDatum(row) {
|
||||||
this.datum = {
|
this.datum = {
|
||||||
...this.datum,
|
...this.datum,
|
||||||
...normalizedUpdatesToDatum
|
...row.datum
|
||||||
};
|
};
|
||||||
|
|
||||||
this.fullDatum = {
|
this.fullDatum = {
|
||||||
...this.fullDatum,
|
...this.fullDatum,
|
||||||
...updatesToDatum
|
...row.fullDatum
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,11 @@ import { EventEmitter } from 'eventemitter3';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { ORDER } from '../constants.js';
|
import { ORDER } from '../constants.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('.TelemetryTableRow.js').default} TelemetryTableRow
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
@ -124,10 +129,22 @@ export default class TableRowCollection extends EventEmitter {
|
|||||||
return foundIndex;
|
return foundIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRowInPlace(row, index) {
|
/**
|
||||||
const foundRow = this.rows[index];
|
* `incomingRow` exists in the collection,
|
||||||
foundRow.updateWithDatum(row.datum);
|
* so merge existing and incoming row properties
|
||||||
this.rows[index] = foundRow;
|
*
|
||||||
|
* Do to reactivity of Vue, we want to replace the existing row with the updated row
|
||||||
|
* @param {TelemetryTableRow} incomingRow to update
|
||||||
|
* @param {number} index of the existing row in the collection to update
|
||||||
|
*/
|
||||||
|
updateRowInPlace(incomingRow, index) {
|
||||||
|
// Update the incoming row, not the existing row
|
||||||
|
const existingRow = this.rows[index];
|
||||||
|
incomingRow.updateWithDatum(existingRow);
|
||||||
|
|
||||||
|
// Replacing the existing row with the updated, incoming row will trigger Vue reactivity
|
||||||
|
// because the reference to the row has changed
|
||||||
|
this.rows.splice(index, 1, incomingRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLimit(rowLimit) {
|
setLimit(rowLimit) {
|
||||||
|
@ -373,7 +373,6 @@ export default {
|
|||||||
configuredColumnWidths: configuration.columnWidths,
|
configuredColumnWidths: configuration.columnWidths,
|
||||||
sizingRows: {},
|
sizingRows: {},
|
||||||
rowHeight: ROW_HEIGHT,
|
rowHeight: ROW_HEIGHT,
|
||||||
scrollOffset: 0,
|
|
||||||
totalHeight: 0,
|
totalHeight: 0,
|
||||||
totalWidth: 0,
|
totalWidth: 0,
|
||||||
rowOffset: 0,
|
rowOffset: 0,
|
||||||
@ -552,6 +551,7 @@ export default {
|
|||||||
//Default sort
|
//Default sort
|
||||||
this.sortOptions = this.table.tableRows.sortBy();
|
this.sortOptions = this.table.tableRows.sortBy();
|
||||||
this.scrollable = this.$refs.scrollable;
|
this.scrollable = this.$refs.scrollable;
|
||||||
|
this.lastScrollLeft = this.scrollable.scrollLeft;
|
||||||
this.contentTable = this.$refs.contentTable;
|
this.contentTable = this.$refs.contentTable;
|
||||||
this.sizingTable = this.$refs.sizingTable;
|
this.sizingTable = this.$refs.sizingTable;
|
||||||
this.headersHolderEl = this.$refs.headersHolderEl;
|
this.headersHolderEl = this.$refs.headersHolderEl;
|
||||||
@ -740,7 +740,9 @@ export default {
|
|||||||
this.table.sortBy(this.sortOptions);
|
this.table.sortBy(this.sortOptions);
|
||||||
},
|
},
|
||||||
scroll() {
|
scroll() {
|
||||||
|
if (this.lastScrollLeft === this.scrollable.scrollLeft) {
|
||||||
this.throttledUpdateVisibleRows();
|
this.throttledUpdateVisibleRows();
|
||||||
|
}
|
||||||
this.synchronizeScrollX();
|
this.synchronizeScrollX();
|
||||||
|
|
||||||
if (this.shouldAutoScroll()) {
|
if (this.shouldAutoScroll()) {
|
||||||
@ -765,6 +767,8 @@ export default {
|
|||||||
this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
|
this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
|
||||||
},
|
},
|
||||||
synchronizeScrollX() {
|
synchronizeScrollX() {
|
||||||
|
this.lastScrollLeft = this.scrollable.scrollLeft;
|
||||||
|
|
||||||
if (this.$refs.headersHolderEl && this.scrollable) {
|
if (this.$refs.headersHolderEl && this.scrollable) {
|
||||||
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
||||||
}
|
}
|
||||||
|
@ -243,12 +243,20 @@ export default {
|
|||||||
this.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this.setTimeOptionsMode);
|
this.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this.setTimeOptionsMode);
|
||||||
},
|
},
|
||||||
setTimeOptionsClock(clock) {
|
setTimeOptionsClock(clock) {
|
||||||
|
// If the user has persisted any time options, then don't override them with global settings.
|
||||||
|
if (this.independentTCEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.setTimeOptionsOffsets();
|
this.setTimeOptionsOffsets();
|
||||||
this.timeOptions.clock = clock.key;
|
this.timeOptions.clock = clock.key;
|
||||||
},
|
},
|
||||||
setTimeOptionsMode(mode) {
|
setTimeOptionsMode(mode) {
|
||||||
|
// If the user has persisted any time options, then don't override them with global settings.
|
||||||
|
if (this.independentTCEnabled) {
|
||||||
this.setTimeOptionsOffsets();
|
this.setTimeOptionsOffsets();
|
||||||
this.timeOptions.mode = mode;
|
this.timeOptions.mode = mode;
|
||||||
|
this.isFixed = this.timeOptions.mode === FIXED_MODE_KEY;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setTimeOptionsOffsets() {
|
setTimeOptionsOffsets() {
|
||||||
this.timeOptions.clockOffsets =
|
this.timeOptions.clockOffsets =
|
||||||
|
@ -436,6 +436,9 @@ export default {
|
|||||||
|
|
||||||
return startInBounds || endInBounds || middleInBounds;
|
return startInBounds || endInBounds || middleInBounds;
|
||||||
},
|
},
|
||||||
|
isActivityInProgress(activity) {
|
||||||
|
return this.persistedActivityStates[activity.id] === 'in-progress';
|
||||||
|
},
|
||||||
filterActivities(activity) {
|
filterActivities(activity) {
|
||||||
if (this.isEditing) {
|
if (this.isEditing) {
|
||||||
return true;
|
return true;
|
||||||
@ -460,7 +463,8 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isActivityInBounds(activity)) {
|
// An activity may be out of bounds, but if it is in-progress, we show it.
|
||||||
|
if (!this.isActivityInBounds(activity) && !this.isActivityInProgress(activity)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//current event or future start event or past end event
|
//current event or future start event or past end event
|
||||||
|
@ -356,7 +356,7 @@ $colorInspectorBg: $colorBodyBg;
|
|||||||
$colorInspectorFg: $colorBodyFg;
|
$colorInspectorFg: $colorBodyFg;
|
||||||
$colorInspectorPropName: $colorBodyFgSubtle;
|
$colorInspectorPropName: $colorBodyFgSubtle;
|
||||||
$colorInspectorPropVal: $colorBodyFgEm;
|
$colorInspectorPropVal: $colorBodyFgEm;
|
||||||
$colorInspectorSectionHeaderBg: pullForward($colorInspectorBg, 5%);
|
$colorInspectorSectionHeaderBg: pullForward($colorInspectorBg, 10%);
|
||||||
$colorInspectorSectionHeaderFg: #bfbfbf;
|
$colorInspectorSectionHeaderFg: #bfbfbf;
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
|
@ -277,8 +277,9 @@ $glyph-icon-bar-chart: '\eb2c';
|
|||||||
$glyph-icon-map: '\eb2d';
|
$glyph-icon-map: '\eb2d';
|
||||||
$glyph-icon-plan: '\eb2e';
|
$glyph-icon-plan: '\eb2e';
|
||||||
$glyph-icon-timelist: '\eb2f';
|
$glyph-icon-timelist: '\eb2f';
|
||||||
$glyph-icon-notebook-shift-log: '\eb31';
|
|
||||||
$glyph-icon-plot-scatter: '\eb30';
|
$glyph-icon-plot-scatter: '\eb30';
|
||||||
|
$glyph-icon-notebook-shift-log: '\eb31';
|
||||||
|
$glyph-icon-derived-telemetry: '\eb32';
|
||||||
|
|
||||||
/************************** GLYPHS AS DATA URI */
|
/************************** GLYPHS AS DATA URI */
|
||||||
// Only objects have been converted, for use in Create menu and folder views
|
// Only objects have been converted, for use in Create menu and folder views
|
||||||
@ -335,3 +336,4 @@ $bg-icon-telemetry-aggregate: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns
|
|||||||
$bg-icon-trash: url("data:image/svg+xml;charset=UTF-8,%3csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='512px' height='512px' viewBox='0 0 512 512' enable-background='new 0 0 512 512' xml:space='preserve'%3e%3cpath d='M416,64h-96.18V32c0-17.6-14.4-32-32-32h-64c-17.6,0-32,14.4-32,32v32H96c-52.8,0-96,36-96,80s0,80,0,80h32v192 c0,52.8,43.2,96,96,96h256c52.8,0,96-43.2,96-96V224h32c0,0,0-36,0-80S468.8,64,416,64z M160,416H96V224h64V416z M288,416h-64V224 h64V416z M416,416h-64V224h64V416z'/%3e%3c/svg%3e");
|
$bg-icon-trash: url("data:image/svg+xml;charset=UTF-8,%3csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='512px' height='512px' viewBox='0 0 512 512' enable-background='new 0 0 512 512' xml:space='preserve'%3e%3cpath d='M416,64h-96.18V32c0-17.6-14.4-32-32-32h-64c-17.6,0-32,14.4-32,32v32H96c-52.8,0-96,36-96,80s0,80,0,80h32v192 c0,52.8,43.2,96,96,96h256c52.8,0,96-43.2,96-96V224h32c0,0,0-36,0-80S468.8,64,416,64z M160,416H96V224h64V416z M288,416h-64V224 h64V416z M416,416h-64V224h64V416z'/%3e%3c/svg%3e");
|
||||||
$bg-icon-eye-open: url("data:image/svg+xml;charset=UTF-8,%3csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 512 512' style='enable-background:new 0 0 512 512;' xml:space='preserve'%3e%3cstyle type='text/css'%3e .st0%7bfill:%2300A14B;%7d %3c/style%3e%3ctitle%3eicon-eye-open-v2%3c/title%3e%3cg%3e%3cpath class='st0' d='M256,58.2c-122.9,0-226.1,84-255.4,197.8C29.9,369.7,133.1,453.8,256,453.8s226.1-84,255.4-197.8 C482.1,142.3,378.9,58.2,256,58.2z M414.6,294.2c-11.3,17.2-25.3,32.4-41.5,45.2c-16.4,12.9-34.5,22.8-54,29.7 c-20.2,7.1-41.4,10.7-63,10.7s-42.9-3.6-63-10.7c-19.5-6.9-37.7-16.9-54-29.7c-16.2-12.8-30.2-27.9-41.5-45.2 c-7.9-12-14.4-24.8-19.3-38.2c5-13.4,11.5-26.2,19.3-38.2c11.3-17.2,25.3-32.4,41.5-45.2c16.4-12.9,34.5-22.8,54-29.7 c20.2-7.1,41.4-10.7,63-10.7s42.9,3.6,63,10.7c19.5,6.9,37.7,16.9,54,29.7c16.2,12.8,30.2,27.9,41.5,45.2 c7.9,12,14.4,24.8,19.3,38.2C429,269.4,422.5,282.2,414.6,294.2z'/%3e%3ccircle class='st0' cx='256' cy='256' r='96'/%3e%3c/g%3e%3c/svg%3e");
|
$bg-icon-eye-open: url("data:image/svg+xml;charset=UTF-8,%3csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 512 512' style='enable-background:new 0 0 512 512;' xml:space='preserve'%3e%3cstyle type='text/css'%3e .st0%7bfill:%2300A14B;%7d %3c/style%3e%3ctitle%3eicon-eye-open-v2%3c/title%3e%3cg%3e%3cpath class='st0' d='M256,58.2c-122.9,0-226.1,84-255.4,197.8C29.9,369.7,133.1,453.8,256,453.8s226.1-84,255.4-197.8 C482.1,142.3,378.9,58.2,256,58.2z M414.6,294.2c-11.3,17.2-25.3,32.4-41.5,45.2c-16.4,12.9-34.5,22.8-54,29.7 c-20.2,7.1-41.4,10.7-63,10.7s-42.9-3.6-63-10.7c-19.5-6.9-37.7-16.9-54-29.7c-16.2-12.8-30.2-27.9-41.5-45.2 c-7.9-12-14.4-24.8-19.3-38.2c5-13.4,11.5-26.2,19.3-38.2c11.3-17.2,25.3-32.4,41.5-45.2c16.4-12.9,34.5-22.8,54-29.7 c20.2-7.1,41.4-10.7,63-10.7s42.9,3.6,63,10.7c19.5,6.9,37.7,16.9,54,29.7c16.2,12.8,30.2,27.9,41.5,45.2 c7.9,12,14.4,24.8,19.3,38.2C429,269.4,422.5,282.2,414.6,294.2z'/%3e%3ccircle class='st0' cx='256' cy='256' r='96'/%3e%3c/g%3e%3c/svg%3e");
|
||||||
$bg-icon-camera: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3ctitle%3eicon-camera-v2%3c/title%3e%3cpath d='M448,128H384L320,0H192L128,128H64A64.2,64.2,0,0,0,0,192V448a64.2,64.2,0,0,0,64,64H448a64.2,64.2,0,0,0,64-64V192A64.2,64.2,0,0,0,448,128ZM256,432A128,128,0,1,1,384,304,128,128,0,0,1,256,432Z'/%3e%3c/svg%3e");
|
$bg-icon-camera: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3ctitle%3eicon-camera-v2%3c/title%3e%3cpath d='M448,128H384L320,0H192L128,128H64A64.2,64.2,0,0,0,0,192V448a64.2,64.2,0,0,0,64,64H448a64.2,64.2,0,0,0,64-64V192A64.2,64.2,0,0,0,448,128ZM256,432A128,128,0,1,1,384,304,128,128,0,0,1,256,432Z'/%3e%3c/svg%3e");
|
||||||
|
$bg-icon-derived-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M66.1 166c20.2 24.3 35.1 54.6 44 75.7 10 23.6 21.7 44 33.1 57.7 11.1 13.3 18.5 16.3 20.2 16.3s9.1-3 20.2-16.3c11.4-13.7 23.1-34.2 33.1-57.7 8.9-21.1 23.8-51.4 44-75.7 23.3-28.1 48.7-42.3 75.6-42.3s52.2 14.2 75.6 42.3c20.2 24.3 35.1 54.6 44 75.7 10 23.6 21.7 44 33.1 57.7 11.1 13.3 18.5 16.3 20.2 16.3s1.6-.3 3.2-1.1v-58.9c-.2-141.3-114.9-256-256.2-256H.2v124.6c23.3 3 45.4 17 66 41.7Z'/%3e%3cpath d='M509 387.7c-26.8 0-52.2-14.2-75.6-42.3-20.2-24.3-35.1-54.6-44-75.7-10-23.6-21.7-44-33.1-57.7-11.1-13.3-18.5-16.3-20.2-16.3s-9.1 3-20.2 16.3c-11.4 13.7-23.1 34.2-33.1 57.7-8.9 21.1-23.8 51.4-44 75.7-23.3 28.1-48.7 42.3-75.6 42.3s-52.2-14.2-75.6-42.3c-20.2-24.3-35.1-54.6-44-75.7-10-23.6-21.7-44-33.1-57.7-4.1-4.9-7.6-8.4-10.6-10.8v54.5c.3 141.4 114.9 256 256.3 256h256V387.6H509Z'/%3e%3c/svg%3e");
|
||||||
|
@ -154,6 +154,7 @@ button {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
display: block;
|
display: block;
|
||||||
transition: transform $transOutTime;
|
transition: transform $transOutTime;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: $glyph-icon-arrow-down;
|
content: $glyph-icon-arrow-down;
|
||||||
font-family: symbolsfont;
|
font-family: symbolsfont;
|
||||||
@ -268,6 +269,7 @@ button {
|
|||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/********* Disclosure Triangle */
|
/********* Disclosure Triangle */
|
||||||
// Provides an arrow icon that when clicked expands an element to reveal its contents.
|
// Provides an arrow icon that when clicked expands an element to reveal its contents.
|
||||||
// Used in tree items, plot legends. Always placed BEFORE an element.
|
// Used in tree items, plot legends. Always placed BEFORE an element.
|
||||||
@ -322,8 +324,9 @@ button {
|
|||||||
section {
|
section {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
+ section {
|
+ section {
|
||||||
margin-top: $interiorMargin;
|
//margin-top: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-section__header {
|
.c-section__header {
|
||||||
@ -331,11 +334,12 @@ section {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: $interiorMargin;
|
gap: $interiorMarginSm;
|
||||||
|
//margin-bottom: $interiorMargin;
|
||||||
> * + * {
|
//
|
||||||
margin-left: $interiorMarginSm;
|
//> * + * {
|
||||||
}
|
// margin-left: $interiorMarginSm;
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
> [class*='__label'] {
|
> [class*='__label'] {
|
||||||
@ -359,6 +363,7 @@ input[type='password'],
|
|||||||
input[type='date'],
|
input[type='date'],
|
||||||
textarea {
|
textarea {
|
||||||
@include reactive-input();
|
@include reactive-input();
|
||||||
|
|
||||||
&.numeric {
|
&.numeric {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@ -417,6 +422,11 @@ textarea {
|
|||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--md {
|
||||||
|
// Smallish inputs, like numerics or short text
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
&--autocomplete {
|
&--autocomplete {
|
||||||
&__wrapper {
|
&__wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -470,6 +480,7 @@ input[type='number'].c-input-number--no-spinners {
|
|||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,8 +511,7 @@ select {
|
|||||||
color: $colorSelectFg;
|
color: $colorSelectFg;
|
||||||
box-shadow: $shdwSelect;
|
box-shadow: $shdwSelect;
|
||||||
background-repeat: no-repeat, no-repeat;
|
background-repeat: no-repeat, no-repeat;
|
||||||
background-position:
|
background-position: right 0.4em top 80%,
|
||||||
right 0.4em top 80%,
|
|
||||||
0 0;
|
0 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: $controlCr;
|
border-radius: $controlCr;
|
||||||
@ -932,6 +942,7 @@ select {
|
|||||||
|
|
||||||
.c-labeled-input {
|
.c-labeled-input {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
input[type='number'] {
|
input[type='number'] {
|
||||||
width: 40px; // Number input sucks and must have size set using this method
|
width: 40px; // Number input sucks and must have size set using this method
|
||||||
}
|
}
|
||||||
@ -1049,6 +1060,7 @@ select {
|
|||||||
/******************************************************** SLIDERS */
|
/******************************************************** SLIDERS */
|
||||||
.c-slider {
|
.c-slider {
|
||||||
@include cControl();
|
@include cControl();
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-left: $interiorMargin;
|
margin-left: $interiorMargin;
|
||||||
}
|
}
|
||||||
@ -1091,10 +1103,12 @@ input[type='range'] {
|
|||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
@include sliderKnobRound($knobH);
|
@include sliderKnobRound($knobH);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-moz-range-thumb {
|
&::-moz-range-thumb {
|
||||||
border: none;
|
border: none;
|
||||||
@include sliderKnobRound($knobH);
|
@include sliderKnobRound($knobH);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-ms-thumb {
|
&::-ms-thumb {
|
||||||
border: none;
|
border: none;
|
||||||
@include sliderKnobRound($knobH);
|
@include sliderKnobRound($knobH);
|
||||||
@ -1204,6 +1218,34 @@ input[type='range'] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***************************************************** DISCRETE ITEMS */
|
||||||
|
// OUTPUT
|
||||||
|
// Element that showcases an output value. Used in Condition Sets and Derived Telemetry
|
||||||
|
|
||||||
|
.c-output-featured {
|
||||||
|
display: flex;
|
||||||
|
gap: $interiorMargin;
|
||||||
|
padding: 0 $interiorMargin;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
padding: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
$p: $interiorMargin * 2;
|
||||||
|
//font-size: 1.25em;
|
||||||
|
padding-left: $p;
|
||||||
|
padding-right: $p;
|
||||||
|
background: rgba(black, 0.2);
|
||||||
|
border-radius: $basicCr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/***************************************************** LEGACY */
|
/***************************************************** LEGACY */
|
||||||
.l-btn-set {
|
.l-btn-set {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -42,503 +42,673 @@
|
|||||||
.icon-alert-rect {
|
.icon-alert-rect {
|
||||||
@include glyphBefore($glyph-icon-alert-rect);
|
@include glyphBefore($glyph-icon-alert-rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-alert-triangle {
|
.icon-alert-triangle {
|
||||||
@include glyphBefore($glyph-icon-alert-triangle);
|
@include glyphBefore($glyph-icon-alert-triangle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-up {
|
.icon-arrow-up {
|
||||||
@include glyphBefore($glyph-icon-arrow-up);
|
@include glyphBefore($glyph-icon-arrow-up);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-double-up {
|
.icon-arrow-double-up {
|
||||||
@include glyphBefore($glyph-icon-arrow-double-up);
|
@include glyphBefore($glyph-icon-arrow-double-up);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-tall-up {
|
.icon-arrow-tall-up {
|
||||||
@include glyphBefore($glyph-icon-arrow-tall-up);
|
@include glyphBefore($glyph-icon-arrow-tall-up);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-right {
|
.icon-arrow-right {
|
||||||
@include glyphBefore($glyph-icon-arrow-right);
|
@include glyphBefore($glyph-icon-arrow-right);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-right-equilateral {
|
.icon-arrow-right-equilateral {
|
||||||
@include glyphBefore($glyph-icon-arrow-right-equilateral);
|
@include glyphBefore($glyph-icon-arrow-right-equilateral);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-down {
|
.icon-arrow-down {
|
||||||
@include glyphBefore($glyph-icon-arrow-down);
|
@include glyphBefore($glyph-icon-arrow-down);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-double-down {
|
.icon-arrow-double-down {
|
||||||
@include glyphBefore($glyph-icon-arrow-double-down);
|
@include glyphBefore($glyph-icon-arrow-double-down);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-tall-down {
|
.icon-arrow-tall-down {
|
||||||
@include glyphBefore($glyph-icon-arrow-tall-down);
|
@include glyphBefore($glyph-icon-arrow-tall-down);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-left {
|
.icon-arrow-left {
|
||||||
@include glyphBefore($glyph-icon-arrow-left);
|
@include glyphBefore($glyph-icon-arrow-left);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-asterisk {
|
.icon-asterisk {
|
||||||
@include glyphBefore($glyph-icon-asterisk);
|
@include glyphBefore($glyph-icon-asterisk);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-bell {
|
.icon-bell {
|
||||||
@include glyphBefore($glyph-icon-bell);
|
@include glyphBefore($glyph-icon-bell);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-box-round-corners {
|
.icon-box-round-corners {
|
||||||
@include glyphBefore($glyph-icon-box-round-corners);
|
@include glyphBefore($glyph-icon-box-round-corners);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-box-with-arrow {
|
.icon-box-with-arrow {
|
||||||
@include glyphBefore($glyph-icon-box-with-arrow);
|
@include glyphBefore($glyph-icon-box-with-arrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-check {
|
.icon-check {
|
||||||
@include glyphBefore($glyph-icon-check);
|
@include glyphBefore($glyph-icon-check);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-connectivity {
|
.icon-connectivity {
|
||||||
@include glyphBefore($glyph-icon-connectivity);
|
@include glyphBefore($glyph-icon-connectivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-database-in-brackets {
|
.icon-database-in-brackets {
|
||||||
@include glyphBefore($glyph-icon-database-in-brackets);
|
@include glyphBefore($glyph-icon-database-in-brackets);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-eye-open {
|
.icon-eye-open {
|
||||||
@include glyphBefore($glyph-icon-eye-open);
|
@include glyphBefore($glyph-icon-eye-open);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-gear {
|
.icon-gear {
|
||||||
@include glyphBefore($glyph-icon-gear);
|
@include glyphBefore($glyph-icon-gear);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-gear-after {
|
.icon-gear-after {
|
||||||
@include glyphAfter($glyph-icon-gear);
|
@include glyphAfter($glyph-icon-gear);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-hourglass {
|
.icon-hourglass {
|
||||||
@include glyphBefore($glyph-icon-hourglass);
|
@include glyphBefore($glyph-icon-hourglass);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-info {
|
.icon-info {
|
||||||
@include glyphBefore($glyph-icon-info);
|
@include glyphBefore($glyph-icon-info);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-link {
|
.icon-link {
|
||||||
@include glyphBefore($glyph-icon-link);
|
@include glyphBefore($glyph-icon-link);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-lock {
|
.icon-lock {
|
||||||
@include glyphBefore($glyph-icon-lock);
|
@include glyphBefore($glyph-icon-lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-minus {
|
.icon-minus {
|
||||||
@include glyphBefore($glyph-icon-minus);
|
@include glyphBefore($glyph-icon-minus);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-people {
|
.icon-people {
|
||||||
@include glyphBefore($glyph-icon-people);
|
@include glyphBefore($glyph-icon-people);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-person {
|
.icon-person {
|
||||||
@include glyphBefore($glyph-icon-person);
|
@include glyphBefore($glyph-icon-person);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-plus {
|
.icon-plus {
|
||||||
@include glyphBefore($glyph-icon-plus);
|
@include glyphBefore($glyph-icon-plus);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-plus-in-rect {
|
.icon-plus-in-rect {
|
||||||
@include glyphBefore($glyph-icon-plus-in-rect);
|
@include glyphBefore($glyph-icon-plus-in-rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-trash {
|
.icon-trash {
|
||||||
@include glyphBefore($glyph-icon-trash);
|
@include glyphBefore($glyph-icon-trash);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-x {
|
.icon-x {
|
||||||
@include glyphBefore($glyph-icon-x);
|
@include glyphBefore($glyph-icon-x);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-brackets {
|
.icon-brackets {
|
||||||
@include glyphBefore($glyph-icon-brackets);
|
@include glyphBefore($glyph-icon-brackets);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-crosshair {
|
.icon-crosshair {
|
||||||
@include glyphBefore($glyph-icon-crosshair);
|
@include glyphBefore($glyph-icon-crosshair);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-grippy {
|
.icon-grippy {
|
||||||
@include glyphBefore($glyph-icon-grippy);
|
@include glyphBefore($glyph-icon-grippy);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-grid {
|
.icon-grid {
|
||||||
@include glyphBefore($glyph-icon-grid);
|
@include glyphBefore($glyph-icon-grid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-grippy-ew {
|
.icon-grippy-ew {
|
||||||
@include glyphBefore($glyph-icon-grippy-ew);
|
@include glyphBefore($glyph-icon-grippy-ew);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-columns {
|
.icon-columns {
|
||||||
@include glyphBefore($glyph-icon-columns);
|
@include glyphBefore($glyph-icon-columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-rows {
|
.icon-rows {
|
||||||
@include glyphBefore($glyph-icon-rows);
|
@include glyphBefore($glyph-icon-rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-filter {
|
.icon-filter {
|
||||||
@include glyphBefore($glyph-icon-filter);
|
@include glyphBefore($glyph-icon-filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-filter-outline {
|
.icon-filter-outline {
|
||||||
@include glyphBefore($glyph-icon-filter-outline);
|
@include glyphBefore($glyph-icon-filter-outline);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-suitcase {
|
.icon-suitcase {
|
||||||
@include glyphBefore($glyph-icon-suitcase);
|
@include glyphBefore($glyph-icon-suitcase);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-cursor-lock {
|
.icon-cursor-lock {
|
||||||
@include glyphBefore($glyph-icon-cursor-lock);
|
@include glyphBefore($glyph-icon-cursor-lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-flag {
|
.icon-flag {
|
||||||
@include glyphBefore($glyph-icon-flag);
|
@include glyphBefore($glyph-icon-flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-eye-disabled {
|
.icon-eye-disabled {
|
||||||
@include glyphBefore($glyph-icon-eye-disabled);
|
@include glyphBefore($glyph-icon-eye-disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-notebook-page {
|
.icon-notebook-page {
|
||||||
@include glyphBefore($glyph-icon-notebook-page);
|
@include glyphBefore($glyph-icon-notebook-page);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-unlocked {
|
.icon-unlocked {
|
||||||
@include glyphBefore($glyph-icon-unlocked);
|
@include glyphBefore($glyph-icon-unlocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-circle {
|
.icon-circle {
|
||||||
@include glyphBefore($glyph-icon-circle);
|
@include glyphBefore($glyph-icon-circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-draft {
|
.icon-draft {
|
||||||
@include glyphBefore($glyph-icon-draft);
|
@include glyphBefore($glyph-icon-draft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-question-mark {
|
.icon-question-mark {
|
||||||
@include glyphBefore($glyph-icon-question-mark);
|
@include glyphBefore($glyph-icon-question-mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-circle-slash {
|
.icon-circle-slash {
|
||||||
@include glyphBefore($glyph-icon-circle-slash);
|
@include glyphBefore($glyph-icon-circle-slash);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-status-poll-check {
|
.icon-status-poll-check {
|
||||||
@include glyphBefore($glyph-icon-status-poll-check);
|
@include glyphBefore($glyph-icon-status-poll-check);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-status-poll-caution {
|
.icon-status-poll-caution {
|
||||||
@include glyphBefore($glyph-icon-status-poll-caution);
|
@include glyphBefore($glyph-icon-status-poll-caution);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-status-poll-circle-slash {
|
.icon-status-poll-circle-slash {
|
||||||
@include glyphBefore($glyph-icon-status-poll-circle-slash);
|
@include glyphBefore($glyph-icon-status-poll-circle-slash);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-status-poll-question-mark {
|
.icon-status-poll-question-mark {
|
||||||
@include glyphBefore($glyph-icon-status-poll-question-mark);
|
@include glyphBefore($glyph-icon-status-poll-question-mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-status-poll-edit {
|
.icon-status-poll-edit {
|
||||||
@include glyphBefore($glyph-icon-status-poll-edit);
|
@include glyphBefore($glyph-icon-status-poll-edit);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-stale {
|
.icon-stale {
|
||||||
@include glyphBefore($glyph-icon-stale);
|
@include glyphBefore($glyph-icon-stale);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrows-right-left {
|
.icon-arrows-right-left {
|
||||||
@include glyphBefore($glyph-icon-arrows-right-left);
|
@include glyphBefore($glyph-icon-arrows-right-left);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrows-up-down {
|
.icon-arrows-up-down {
|
||||||
@include glyphBefore($glyph-icon-arrows-up-down);
|
@include glyphBefore($glyph-icon-arrows-up-down);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-bullet {
|
.icon-bullet {
|
||||||
@include glyphBefore($glyph-icon-bullet);
|
@include glyphBefore($glyph-icon-bullet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-calendar {
|
.icon-calendar {
|
||||||
@include glyphBefore($glyph-icon-calendar);
|
@include glyphBefore($glyph-icon-calendar);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-chain-links {
|
.icon-chain-links {
|
||||||
@include glyphBefore($glyph-icon-chain-links);
|
@include glyphBefore($glyph-icon-chain-links);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-download {
|
.icon-download {
|
||||||
@include glyphBefore($glyph-icon-download);
|
@include glyphBefore($glyph-icon-download);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-duplicate {
|
.icon-duplicate {
|
||||||
@include glyphBefore($glyph-icon-duplicate);
|
@include glyphBefore($glyph-icon-duplicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-folder-new {
|
.icon-folder-new {
|
||||||
@include glyphBefore($glyph-icon-folder-new);
|
@include glyphBefore($glyph-icon-folder-new);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-fullscreen-collapse {
|
.icon-fullscreen-collapse {
|
||||||
@include glyphBefore($glyph-icon-fullscreen-collapse);
|
@include glyphBefore($glyph-icon-fullscreen-collapse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-fullscreen-expand {
|
.icon-fullscreen-expand {
|
||||||
@include glyphBefore($glyph-icon-fullscreen-expand);
|
@include glyphBefore($glyph-icon-fullscreen-expand);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-layers {
|
.icon-layers {
|
||||||
@include glyphBefore($glyph-icon-layers);
|
@include glyphBefore($glyph-icon-layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-line-horz {
|
.icon-line-horz {
|
||||||
@include glyphBefore($glyph-icon-line-horz);
|
@include glyphBefore($glyph-icon-line-horz);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-magnify {
|
.icon-magnify {
|
||||||
@include glyphBefore($glyph-icon-magnify);
|
@include glyphBefore($glyph-icon-magnify);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-magnify-in {
|
.icon-magnify-in {
|
||||||
@include glyphBefore($glyph-icon-magnify-in);
|
@include glyphBefore($glyph-icon-magnify-in);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-magnify-out {
|
.icon-magnify-out {
|
||||||
@include glyphBefore($glyph-icon-magnify-out);
|
@include glyphBefore($glyph-icon-magnify-out);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-menu-hamburger {
|
.icon-menu-hamburger {
|
||||||
@include glyphBefore($glyph-icon-menu-hamburger);
|
@include glyphBefore($glyph-icon-menu-hamburger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-move {
|
.icon-move {
|
||||||
@include glyphBefore($glyph-icon-move);
|
@include glyphBefore($glyph-icon-move);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-new-window {
|
.icon-new-window {
|
||||||
@include glyphBefore($glyph-icon-new-window);
|
@include glyphBefore($glyph-icon-new-window);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-paint-bucket {
|
.icon-paint-bucket {
|
||||||
@include glyphBefore($glyph-icon-paint-bucket);
|
@include glyphBefore($glyph-icon-paint-bucket);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-pencil {
|
.icon-pencil {
|
||||||
@include glyphBefore($glyph-icon-pencil);
|
@include glyphBefore($glyph-icon-pencil);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-pencil-in-brackets {
|
.icon-pencil-in-brackets {
|
||||||
@include glyphBefore($glyph-icon-pencil-in-brackets);
|
@include glyphBefore($glyph-icon-pencil-in-brackets);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-play {
|
.icon-play {
|
||||||
@include glyphBefore($glyph-icon-play);
|
@include glyphBefore($glyph-icon-play);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-pause {
|
.icon-pause {
|
||||||
@include glyphBefore($glyph-icon-pause);
|
@include glyphBefore($glyph-icon-pause);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-plot-resource {
|
.icon-plot-resource {
|
||||||
@include glyphBefore($glyph-icon-plot-resource);
|
@include glyphBefore($glyph-icon-plot-resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-pointer-left {
|
.icon-pointer-left {
|
||||||
@include glyphBefore($glyph-icon-pointer-left);
|
@include glyphBefore($glyph-icon-pointer-left);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-pointer-right {
|
.icon-pointer-right {
|
||||||
@include glyphBefore($glyph-icon-pointer-right);
|
@include glyphBefore($glyph-icon-pointer-right);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-refresh {
|
.icon-refresh {
|
||||||
@include glyphBefore($glyph-icon-refresh);
|
@include glyphBefore($glyph-icon-refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-save {
|
.icon-save {
|
||||||
@include glyphBefore($glyph-icon-save);
|
@include glyphBefore($glyph-icon-save);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-save-as {
|
.icon-save-as {
|
||||||
@include glyphBefore($glyph-icon-save-as);
|
@include glyphBefore($glyph-icon-save-as);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-sine {
|
.icon-sine {
|
||||||
@include glyphBefore($glyph-icon-sine);
|
@include glyphBefore($glyph-icon-sine);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-font {
|
.icon-font {
|
||||||
@include glyphBefore($glyph-icon-font);
|
@include glyphBefore($glyph-icon-font);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-thumbs-strip {
|
.icon-thumbs-strip {
|
||||||
@include glyphBefore($glyph-icon-thumbs-strip);
|
@include glyphBefore($glyph-icon-thumbs-strip);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-two-parts-both {
|
.icon-two-parts-both {
|
||||||
@include glyphBefore($glyph-icon-two-parts-both);
|
@include glyphBefore($glyph-icon-two-parts-both);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-two-parts-one-only {
|
.icon-two-parts-one-only {
|
||||||
@include glyphBefore($glyph-icon-two-parts-one-only);
|
@include glyphBefore($glyph-icon-two-parts-one-only);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-resync {
|
.icon-resync {
|
||||||
@include glyphBefore($glyph-icon-resync);
|
@include glyphBefore($glyph-icon-resync);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-reset {
|
.icon-reset {
|
||||||
@include glyphBefore($glyph-icon-reset);
|
@include glyphBefore($glyph-icon-reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-x-in-circle {
|
.icon-x-in-circle {
|
||||||
@include glyphBefore($glyph-icon-x-in-circle);
|
@include glyphBefore($glyph-icon-x-in-circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-brightness {
|
.icon-brightness {
|
||||||
@include glyphBefore($glyph-icon-brightness);
|
@include glyphBefore($glyph-icon-brightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-contrast {
|
.icon-contrast {
|
||||||
@include glyphBefore($glyph-icon-contrast);
|
@include glyphBefore($glyph-icon-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-expand {
|
.icon-expand {
|
||||||
@include glyphBefore($glyph-icon-expand);
|
@include glyphBefore($glyph-icon-expand);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-list-view {
|
.icon-list-view {
|
||||||
@include glyphBefore($glyph-icon-list-view);
|
@include glyphBefore($glyph-icon-list-view);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-grid-snap-to {
|
.icon-grid-snap-to {
|
||||||
@include glyphBefore($glyph-icon-grid-snap-to);
|
@include glyphBefore($glyph-icon-grid-snap-to);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-grid-snap-no {
|
.icon-grid-snap-no {
|
||||||
@include glyphBefore($glyph-icon-grid-snap-no);
|
@include glyphBefore($glyph-icon-grid-snap-no);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-frame-show {
|
.icon-frame-show {
|
||||||
@include glyphBefore($glyph-icon-frame-show);
|
@include glyphBefore($glyph-icon-frame-show);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-frame-hide {
|
.icon-frame-hide {
|
||||||
@include glyphBefore($glyph-icon-frame-hide);
|
@include glyphBefore($glyph-icon-frame-hide);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-import {
|
.icon-import {
|
||||||
@include glyphBefore($glyph-icon-import);
|
@include glyphBefore($glyph-icon-import);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-export {
|
.icon-export {
|
||||||
@include glyphBefore($glyph-icon-export);
|
@include glyphBefore($glyph-icon-export);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-font-size {
|
.icon-font-size {
|
||||||
@include glyphBefore($glyph-icon-font-size);
|
@include glyphBefore($glyph-icon-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-clear-data {
|
.icon-clear-data {
|
||||||
@include glyphBefore($glyph-icon-clear-data);
|
@include glyphBefore($glyph-icon-clear-data);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-history {
|
.icon-history {
|
||||||
@include glyphBefore($glyph-icon-history);
|
@include glyphBefore($glyph-icon-history);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-arrow-nav-to-parent {
|
.icon-arrow-nav-to-parent {
|
||||||
@include glyphBefore($glyph-icon-arrow-nav-to-parent);
|
@include glyphBefore($glyph-icon-arrow-nav-to-parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-crosshair-in-circle {
|
.icon-crosshair-in-circle {
|
||||||
@include glyphBefore($glyph-icon-crosshair-in-circle);
|
@include glyphBefore($glyph-icon-crosshair-in-circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-target {
|
.icon-target {
|
||||||
@include glyphBefore($glyph-icon-target);
|
@include glyphBefore($glyph-icon-target);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-items-collapse {
|
.icon-items-collapse {
|
||||||
@include glyphBefore($glyph-icon-items-collapse);
|
@include glyphBefore($glyph-icon-items-collapse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-items-expand {
|
.icon-items-expand {
|
||||||
@include glyphBefore($glyph-icon-items-expand);
|
@include glyphBefore($glyph-icon-items-expand);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-3-dots {
|
.icon-3-dots {
|
||||||
@include glyphBefore($glyph-icon-3-dots);
|
@include glyphBefore($glyph-icon-3-dots);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-grid-on {
|
.icon-grid-on {
|
||||||
@include glyphBefore($glyph-icon-grid-on);
|
@include glyphBefore($glyph-icon-grid-on);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-grid-off {
|
.icon-grid-off {
|
||||||
@include glyphBefore($glyph-icon-grid-off);
|
@include glyphBefore($glyph-icon-grid-off);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-camera {
|
.icon-camera {
|
||||||
@include glyphBefore($glyph-icon-camera);
|
@include glyphBefore($glyph-icon-camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-folders-collapse {
|
.icon-folders-collapse {
|
||||||
@include glyphBefore($glyph-icon-folders-collapse);
|
@include glyphBefore($glyph-icon-folders-collapse);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-activity {
|
.icon-activity {
|
||||||
@include glyphBefore($glyph-icon-activity);
|
@include glyphBefore($glyph-icon-activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-activity-mode {
|
.icon-activity-mode {
|
||||||
@include glyphBefore($glyph-icon-activity-mode);
|
@include glyphBefore($glyph-icon-activity-mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-autoflow-tabular {
|
.icon-autoflow-tabular {
|
||||||
@include glyphBefore($glyph-icon-autoflow-tabular);
|
@include glyphBefore($glyph-icon-autoflow-tabular);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-clock {
|
.icon-clock {
|
||||||
@include glyphBefore($glyph-icon-clock);
|
@include glyphBefore($glyph-icon-clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-database {
|
.icon-database {
|
||||||
@include glyphBefore($glyph-icon-database);
|
@include glyphBefore($glyph-icon-database);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-database-query {
|
.icon-database-query {
|
||||||
@include glyphBefore($glyph-icon-database-query);
|
@include glyphBefore($glyph-icon-database-query);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-dataset {
|
.icon-dataset {
|
||||||
@include glyphBefore($glyph-icon-dataset);
|
@include glyphBefore($glyph-icon-dataset);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-datatable {
|
.icon-datatable {
|
||||||
@include glyphBefore($glyph-icon-datatable);
|
@include glyphBefore($glyph-icon-datatable);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-dictionary {
|
.icon-dictionary {
|
||||||
@include glyphBefore($glyph-icon-dictionary);
|
@include glyphBefore($glyph-icon-dictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-folder {
|
.icon-folder {
|
||||||
@include glyphBefore($glyph-icon-folder);
|
@include glyphBefore($glyph-icon-folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-image {
|
.icon-image {
|
||||||
@include glyphBefore($glyph-icon-image);
|
@include glyphBefore($glyph-icon-image);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-layout {
|
.icon-layout {
|
||||||
@include glyphBefore($glyph-icon-layout);
|
@include glyphBefore($glyph-icon-layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-object {
|
.icon-object {
|
||||||
@include glyphBefore($glyph-icon-object);
|
@include glyphBefore($glyph-icon-object);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-object-unknown {
|
.icon-object-unknown {
|
||||||
@include glyphBefore($glyph-icon-object-unknown);
|
@include glyphBefore($glyph-icon-object-unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-packet {
|
.icon-packet {
|
||||||
@include glyphBefore($glyph-icon-packet);
|
@include glyphBefore($glyph-icon-packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-page {
|
.icon-page {
|
||||||
@include glyphBefore($glyph-icon-page);
|
@include glyphBefore($glyph-icon-page);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-plot-overlay {
|
.icon-plot-overlay {
|
||||||
@include glyphBefore($glyph-icon-plot-overlay);
|
@include glyphBefore($glyph-icon-plot-overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-plot-stacked {
|
.icon-plot-stacked {
|
||||||
@include glyphBefore($glyph-icon-plot-stacked);
|
@include glyphBefore($glyph-icon-plot-stacked);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-session {
|
.icon-session {
|
||||||
@include glyphBefore($glyph-icon-session);
|
@include glyphBefore($glyph-icon-session);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tabular {
|
.icon-tabular {
|
||||||
@include glyphBefore($glyph-icon-tabular);
|
@include glyphBefore($glyph-icon-tabular);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tabular-lad {
|
.icon-tabular-lad {
|
||||||
@include glyphBefore($glyph-icon-tabular-lad);
|
@include glyphBefore($glyph-icon-tabular-lad);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tabular-lad-set {
|
.icon-tabular-lad-set {
|
||||||
@include glyphBefore($glyph-icon-tabular-lad-set);
|
@include glyphBefore($glyph-icon-tabular-lad-set);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tabular-realtime {
|
.icon-tabular-realtime {
|
||||||
@include glyphBefore($glyph-icon-tabular-realtime);
|
@include glyphBefore($glyph-icon-tabular-realtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tabular-scrolling {
|
.icon-tabular-scrolling {
|
||||||
@include glyphBefore($glyph-icon-tabular-scrolling);
|
@include glyphBefore($glyph-icon-tabular-scrolling);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-telemetry {
|
.icon-telemetry {
|
||||||
@include glyphBefore($glyph-icon-telemetry);
|
@include glyphBefore($glyph-icon-telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-timeline {
|
.icon-timeline {
|
||||||
@include glyphBefore($glyph-icon-timeline);
|
@include glyphBefore($glyph-icon-timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-timer {
|
.icon-timer {
|
||||||
@include glyphBefore($glyph-icon-timer);
|
@include glyphBefore($glyph-icon-timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-topic {
|
.icon-topic {
|
||||||
@include glyphBefore($glyph-icon-topic);
|
@include glyphBefore($glyph-icon-topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-box-with-dashed-lines {
|
.icon-box-with-dashed-lines {
|
||||||
@include glyphBefore($glyph-icon-box-with-dashed-lines);
|
@include glyphBefore($glyph-icon-box-with-dashed-lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-summary-widget {
|
.icon-summary-widget {
|
||||||
@include glyphBefore($glyph-icon-summary-widget);
|
@include glyphBefore($glyph-icon-summary-widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-notebook {
|
.icon-notebook {
|
||||||
@include glyphBefore($glyph-icon-notebook);
|
@include glyphBefore($glyph-icon-notebook);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tabs-view {
|
.icon-tabs-view {
|
||||||
@include glyphBefore($glyph-icon-tabs-view);
|
@include glyphBefore($glyph-icon-tabs-view);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-flexible-layout {
|
.icon-flexible-layout {
|
||||||
@include glyphBefore($glyph-icon-flexible-layout);
|
@include glyphBefore($glyph-icon-flexible-layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-generator-telemetry {
|
.icon-generator-telemetry {
|
||||||
@include glyphBefore($glyph-icon-generator-telemetry);
|
@include glyphBefore($glyph-icon-generator-telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-generator-events {
|
.icon-generator-events {
|
||||||
@include glyphBefore($glyph-icon-generator-events);
|
@include glyphBefore($glyph-icon-generator-events);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-gauge {
|
.icon-gauge {
|
||||||
@include glyphBefore($glyph-icon-gauge);
|
@include glyphBefore($glyph-icon-gauge);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-spectra {
|
.icon-spectra {
|
||||||
@include glyphBefore($glyph-icon-spectra);
|
@include glyphBefore($glyph-icon-spectra);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-spectra-telemetry {
|
.icon-spectra-telemetry {
|
||||||
@include glyphBefore($glyph-icon-spectra-telemetry);
|
@include glyphBefore($glyph-icon-spectra-telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-command {
|
.icon-command {
|
||||||
@include glyphBefore($glyph-icon-command);
|
@include glyphBefore($glyph-icon-command);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-conditional {
|
.icon-conditional {
|
||||||
@include glyphBefore($glyph-icon-conditional);
|
@include glyphBefore($glyph-icon-conditional);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-condition-widget {
|
.icon-condition-widget {
|
||||||
@include glyphBefore($glyph-icon-condition-widget);
|
@include glyphBefore($glyph-icon-condition-widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-alphanumeric {
|
.icon-alphanumeric {
|
||||||
@include glyphBefore($glyph-icon-alphanumeric);
|
@include glyphBefore($glyph-icon-alphanumeric);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-image-telemetry {
|
.icon-image-telemetry {
|
||||||
@include glyphBefore($glyph-icon-image-telemetry);
|
@include glyphBefore($glyph-icon-image-telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-telemetry-aggregate {
|
.icon-telemetry-aggregate {
|
||||||
@include glyphBefore($glyph-icon-telemetry-aggregate);
|
@include glyphBefore($glyph-icon-telemetry-aggregate);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-bar-chart {
|
.icon-bar-chart {
|
||||||
@include glyphBefore($glyph-icon-bar-chart);
|
@include glyphBefore($glyph-icon-bar-chart);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-map {
|
.icon-map {
|
||||||
@include glyphBefore($glyph-icon-map);
|
@include glyphBefore($glyph-icon-map);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-plan {
|
.icon-plan {
|
||||||
@include glyphBefore($glyph-icon-plan);
|
@include glyphBefore($glyph-icon-plan);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-timelist {
|
.icon-timelist {
|
||||||
@include glyphBefore($glyph-icon-timelist);
|
@include glyphBefore($glyph-icon-timelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-plot-scatter {
|
||||||
|
@include glyphBefore($glyph-icon-plot-scatter);
|
||||||
|
}
|
||||||
|
|
||||||
.icon-notebook-shift-log {
|
.icon-notebook-shift-log {
|
||||||
@include glyphBefore($glyph-icon-notebook-shift-log);
|
@include glyphBefore($glyph-icon-notebook-shift-log);
|
||||||
}
|
}
|
||||||
.icon-plot-scatter {
|
|
||||||
@include glyphBefore($glyph-icon-plot-scatter);
|
.icon-derived-telemetry {
|
||||||
|
@include glyphBefore($glyph-icon-derived-telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************** 12 PX CLASSES */
|
/************************** 12 PX CLASSES */
|
||||||
@ -546,18 +716,23 @@
|
|||||||
.icon-filter-12px {
|
.icon-filter-12px {
|
||||||
@include glyphBefore($glyph-icon-filter, 'symbolsfont-12px');
|
@include glyphBefore($glyph-icon-filter, 'symbolsfont-12px');
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-filter-outline-12px {
|
.icon-filter-outline-12px {
|
||||||
@include glyphBefore($glyph-icon-filter-outline, 'symbolsfont-12px');
|
@include glyphBefore($glyph-icon-filter-outline, 'symbolsfont-12px');
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-crosshair-12px {
|
.icon-crosshair-12px {
|
||||||
@include glyphBefore($glyph-icon-crosshair, 'symbolsfont-12px');
|
@include glyphBefore($glyph-icon-crosshair, 'symbolsfont-12px');
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-folder-12px {
|
.icon-folder-12px {
|
||||||
@include glyphBefore($glyph-icon-folder, 'symbolsfont-12px');
|
@include glyphBefore($glyph-icon-folder, 'symbolsfont-12px');
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-list-view-12px {
|
.icon-list-view-12px {
|
||||||
@include glyphBefore($glyph-icon-list-view, 'symbolsfont-12px');
|
@include glyphBefore($glyph-icon-list-view, 'symbolsfont-12px');
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-grippy-12px {
|
.icon-grippy-12px {
|
||||||
@include glyphBefore($glyph-icon-grippy, 'symbolsfont-12px');
|
@include glyphBefore($glyph-icon-grippy, 'symbolsfont-12px');
|
||||||
}
|
}
|
||||||
@ -566,159 +741,215 @@
|
|||||||
.bg-icon-alert-rect {
|
.bg-icon-alert-rect {
|
||||||
@include glyphBg($bg-icon-alert-rect);
|
@include glyphBg($bg-icon-alert-rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-alert-triangle {
|
.bg-icon-alert-triangle {
|
||||||
@include glyphBg($bg-icon-alert-triangle);
|
@include glyphBg($bg-icon-alert-triangle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-bell {
|
.bg-icon-bell {
|
||||||
@include glyphBg($bg-icon-bell);
|
@include glyphBg($bg-icon-bell);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-info {
|
.bg-icon-info {
|
||||||
@include glyphBg($bg-icon-info);
|
@include glyphBg($bg-icon-info);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-plus {
|
.bg-icon-plus {
|
||||||
@include glyphBg($bg-icon-plus);
|
@include glyphBg($bg-icon-plus);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-grippy-ew {
|
.bg-icon-grippy-ew {
|
||||||
@include glyphBg($bg-icon-grippy-ew);
|
@include glyphBg($bg-icon-grippy-ew);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-chain-links {
|
.bg-icon-chain-links {
|
||||||
@include glyphBg($bg-icon-chain-links);
|
@include glyphBg($bg-icon-chain-links);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-clock {
|
.bg-icon-clock {
|
||||||
@include glyphBg($bg-icon-clock);
|
@include glyphBg($bg-icon-clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-database {
|
.bg-icon-database {
|
||||||
@include glyphBg($bg-icon-database);
|
@include glyphBg($bg-icon-database);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-database-query {
|
.bg-icon-database-query {
|
||||||
@include glyphBg($bg-icon-database-query);
|
@include glyphBg($bg-icon-database-query);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-dataset {
|
.bg-icon-dataset {
|
||||||
@include glyphBg($bg-icon-dataset);
|
@include glyphBg($bg-icon-dataset);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-datatable {
|
.bg-icon-datatable {
|
||||||
@include glyphBg($bg-icon-datatable);
|
@include glyphBg($bg-icon-datatable);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-dictionary {
|
.bg-icon-dictionary {
|
||||||
@include glyphBg($bg-icon-dictionary);
|
@include glyphBg($bg-icon-dictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-folder {
|
.bg-icon-folder {
|
||||||
@include glyphBg($bg-icon-folder);
|
@include glyphBg($bg-icon-folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-image {
|
.bg-icon-image {
|
||||||
@include glyphBg($bg-icon-image);
|
@include glyphBg($bg-icon-image);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-layout {
|
.bg-icon-layout {
|
||||||
@include glyphBg($bg-icon-layout);
|
@include glyphBg($bg-icon-layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-object {
|
.bg-icon-object {
|
||||||
@include glyphBg($bg-icon-object);
|
@include glyphBg($bg-icon-object);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-object-unknown {
|
.bg-icon-object-unknown {
|
||||||
@include glyphBg($bg-icon-object-unknown);
|
@include glyphBg($bg-icon-object-unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-packet {
|
.bg-icon-packet {
|
||||||
@include glyphBg($bg-icon-packet);
|
@include glyphBg($bg-icon-packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-page {
|
.bg-icon-page {
|
||||||
@include glyphBg($bg-icon-page);
|
@include glyphBg($bg-icon-page);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-plot-overlay {
|
.bg-icon-plot-overlay {
|
||||||
@include glyphBg($bg-icon-plot-overlay);
|
@include glyphBg($bg-icon-plot-overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-plot-stacked {
|
.bg-icon-plot-stacked {
|
||||||
@include glyphBg($bg-icon-plot-stacked);
|
@include glyphBg($bg-icon-plot-stacked);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-session {
|
.bg-icon-session {
|
||||||
@include glyphBg($bg-icon-session);
|
@include glyphBg($bg-icon-session);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-tabular {
|
.bg-icon-tabular {
|
||||||
@include glyphBg($bg-icon-tabular);
|
@include glyphBg($bg-icon-tabular);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-tabular-lad {
|
.bg-icon-tabular-lad {
|
||||||
@include glyphBg($bg-icon-tabular-lad);
|
@include glyphBg($bg-icon-tabular-lad);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-tabular-lad-set {
|
.bg-icon-tabular-lad-set {
|
||||||
@include glyphBg($bg-icon-tabular-lad-set);
|
@include glyphBg($bg-icon-tabular-lad-set);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-tabular-scrolling {
|
.bg-icon-tabular-scrolling {
|
||||||
@include glyphBg($bg-icon-tabular-scrolling);
|
@include glyphBg($bg-icon-tabular-scrolling);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-telemetry {
|
.bg-icon-telemetry {
|
||||||
@include glyphBg($bg-icon-telemetry);
|
@include glyphBg($bg-icon-telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-timeline {
|
.bg-icon-timeline {
|
||||||
@include glyphBg($bg-icon-timeline);
|
@include glyphBg($bg-icon-timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-timer {
|
.bg-icon-timer {
|
||||||
@include glyphBg($bg-icon-timer);
|
@include glyphBg($bg-icon-timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-box-with-dashed-lines {
|
.bg-icon-box-with-dashed-lines {
|
||||||
@include glyphBg($bg-icon-box-with-dashed-lines);
|
@include glyphBg($bg-icon-box-with-dashed-lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-summary-widget {
|
.bg-icon-summary-widget {
|
||||||
@include glyphBg($bg-icon-summary-widget);
|
@include glyphBg($bg-icon-summary-widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-notebook {
|
.bg-icon-notebook {
|
||||||
@include glyphBg($bg-icon-notebook);
|
@include glyphBg($bg-icon-notebook);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-tabs-view {
|
.bg-icon-tabs-view {
|
||||||
@include glyphBg($bg-icon-tabs-view);
|
@include glyphBg($bg-icon-tabs-view);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-flexible-layout {
|
.bg-icon-flexible-layout {
|
||||||
@include glyphBg($bg-icon-flexible-layout);
|
@include glyphBg($bg-icon-flexible-layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-generator-telemetry {
|
.bg-icon-generator-telemetry {
|
||||||
@include glyphBg($bg-icon-generator-telemetry);
|
@include glyphBg($bg-icon-generator-telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-generator-events {
|
.bg-icon-generator-events {
|
||||||
@include glyphBg($bg-icon-generator-events);
|
@include glyphBg($bg-icon-generator-events);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-gauge {
|
.bg-icon-gauge {
|
||||||
@include glyphBg($bg-icon-gauge);
|
@include glyphBg($bg-icon-gauge);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-spectra {
|
.bg-icon-spectra {
|
||||||
@include glyphBg($bg-icon-spectra);
|
@include glyphBg($bg-icon-spectra);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-spectra-telemetry {
|
.bg-icon-spectra-telemetry {
|
||||||
@include glyphBg($bg-icon-spectra-telemetry);
|
@include glyphBg($bg-icon-spectra-telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-command {
|
.bg-icon-command {
|
||||||
@include glyphBg($bg-icon-command);
|
@include glyphBg($bg-icon-command);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-conditional {
|
.bg-icon-conditional {
|
||||||
@include glyphBg($bg-icon-conditional);
|
@include glyphBg($bg-icon-conditional);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-condition-widget {
|
.bg-icon-condition-widget {
|
||||||
@include glyphBg($bg-icon-condition-widget);
|
@include glyphBg($bg-icon-condition-widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-bar-chart {
|
.bg-icon-bar-chart {
|
||||||
@include glyphBg($bg-icon-bar-chart);
|
@include glyphBg($bg-icon-bar-chart);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-map {
|
.bg-icon-map {
|
||||||
@include glyphBg($bg-icon-map);
|
@include glyphBg($bg-icon-map);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-plan {
|
.bg-icon-plan {
|
||||||
@include glyphBg($bg-icon-plan);
|
@include glyphBg($bg-icon-plan);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-timelist {
|
.bg-icon-timelist {
|
||||||
@include glyphBg($bg-icon-timelist);
|
@include glyphBg($bg-icon-timelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-plot-scatter {
|
.bg-icon-plot-scatter {
|
||||||
@include glyphBg($bg-icon-plot-scatter);
|
@include glyphBg($bg-icon-plot-scatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-notebook-shift-log {
|
.bg-icon-notebook-shift-log {
|
||||||
@include glyphBg($bg-icon-notebook-shift-log);
|
@include glyphBg($bg-icon-notebook-shift-log);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-telemetry-aggregate {
|
.bg-icon-telemetry-aggregate {
|
||||||
@include glyphBg($bg-icon-telemetry-aggregate);
|
@include glyphBg($bg-icon-telemetry-aggregate);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-trash {
|
.bg-icon-trash {
|
||||||
@include glyphBg($bg-icon-trash);
|
@include glyphBg($bg-icon-trash);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-eye-open {
|
.bg-icon-eye-open {
|
||||||
@include glyphBg($bg-icon-eye-open);
|
@include glyphBg($bg-icon-eye-open);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-icon-camera {
|
.bg-icon-camera {
|
||||||
@include glyphBg($bg-icon-camera);
|
@include glyphBg($bg-icon-camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-icon-derived-telemetry {
|
||||||
|
@include glyphBg($bg-icon-derived-telemetry);
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -2,6 +2,7 @@
|
|||||||
@import '../api/overlays/components/overlay-component.scss';
|
@import '../api/overlays/components/overlay-component.scss';
|
||||||
@import '../api/tooltips/components/tooltip-component.scss';
|
@import '../api/tooltips/components/tooltip-component.scss';
|
||||||
@import '../plugins/condition/components/conditionals.scss';
|
@import '../plugins/condition/components/conditionals.scss';
|
||||||
|
@import '../plugins/comps/components/comps.scss';
|
||||||
@import '../plugins/conditionWidget/components/condition-widget.scss';
|
@import '../plugins/conditionWidget/components/condition-widget.scss';
|
||||||
@import '../plugins/condition/components/inspector/conditional-styles.scss';
|
@import '../plugins/condition/components/inspector/conditional-styles.scss';
|
||||||
@import '../plugins/displayLayout/components/box-and-line-views';
|
@import '../plugins/displayLayout/components/box-and-line-views';
|
||||||
|
168
src/ui/components/ObjectPathString.vue
Normal file
168
src/ui/components/ObjectPathString.vue
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
<!--
|
||||||
|
Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
Administration. All rights reserved.
|
||||||
|
|
||||||
|
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
Open MCT includes source code licensed under additional open source
|
||||||
|
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
this source code distribution or the Licensing information page available
|
||||||
|
at runtime from the About dialog for additional information.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="orderedPath.length"
|
||||||
|
class="c-object-path-string"
|
||||||
|
:aria-label="`${domainObject.name} Object Path`"
|
||||||
|
role="navigation"
|
||||||
|
>
|
||||||
|
{{ orderedPathStr }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
domainObject: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
readOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showObjectItself: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
objectPath: {
|
||||||
|
type: Array,
|
||||||
|
default() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
orderedPath: [],
|
||||||
|
orderedPathStr: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
this.nameChangeListeners = {};
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
|
||||||
|
if (keyString && this.keyString !== keyString) {
|
||||||
|
this.keyString = keyString;
|
||||||
|
this.originalPath = [];
|
||||||
|
|
||||||
|
let rawPath = null;
|
||||||
|
if (this.objectPath === null) {
|
||||||
|
try {
|
||||||
|
rawPath = await this.openmct.objects.getOriginalPath(
|
||||||
|
keyString,
|
||||||
|
[],
|
||||||
|
this.abortController.signal
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// aborting the search is ok, everything else should be thrown
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rawPath = this.objectPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathWithDomainObject = rawPath.map((domainObject, index, pathArray) => {
|
||||||
|
let key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
const objectPath = pathArray.slice(index);
|
||||||
|
|
||||||
|
return {
|
||||||
|
domainObject,
|
||||||
|
key,
|
||||||
|
objectPath
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (this.showObjectItself) {
|
||||||
|
// remove ROOT only
|
||||||
|
this.orderedPath = pathWithDomainObject.slice(0, pathWithDomainObject.length - 1).reverse();
|
||||||
|
} else {
|
||||||
|
// remove ROOT and object itself from path
|
||||||
|
this.orderedPath = pathWithDomainObject.slice(1, pathWithDomainObject.length - 1).reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.orderedPath.forEach((pathObject) => {
|
||||||
|
this.orderedPathStr = this.orderedPathStr.concat('/').concat(pathObject.domainObject.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
if (this.abortController) {
|
||||||
|
this.abortController.abort();
|
||||||
|
}
|
||||||
|
Object.values(this.nameChangeListeners).forEach((unlisten) => {
|
||||||
|
unlisten();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Generate the hash url for the given object path, removing the '/ROOT' prefix if present.
|
||||||
|
* @param {import('../../api/objects/ObjectAPI').DomainObject[]} objectPath
|
||||||
|
*/
|
||||||
|
navigateToPath(objectPath) {
|
||||||
|
/** @type {string} */
|
||||||
|
const path = `/browse/${this.openmct.objects.getRelativePath(objectPath)}`;
|
||||||
|
|
||||||
|
return path.replace('ROOT/', '');
|
||||||
|
},
|
||||||
|
updateObjectPathName(keyString, newName) {
|
||||||
|
this.orderedPath = this.orderedPath.map((pathObject) => {
|
||||||
|
if (this.openmct.objects.makeKeyString(pathObject.domainObject.identifier) === keyString) {
|
||||||
|
return {
|
||||||
|
...pathObject,
|
||||||
|
domainObject: { ...pathObject.domainObject, name: newName }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return pathObject;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeNameListenerFor(domainObject) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
if (this.nameChangeListeners[keyString]) {
|
||||||
|
this.nameChangeListeners[keyString]();
|
||||||
|
delete this.nameChangeListeners[keyString];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addNameListenerFor(domainObject) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
if (!this.nameChangeListeners[keyString]) {
|
||||||
|
this.nameChangeListeners[keyString] = this.openmct.objects.observe(
|
||||||
|
domainObject,
|
||||||
|
'name',
|
||||||
|
this.updateObjectPathName.bind(this, keyString)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -21,7 +21,9 @@
|
|||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div ref="axisHolder" class="c-timesystem-axis">
|
<div ref="axisHolder" class="c-timesystem-axis">
|
||||||
<div class="nowMarker" :style="nowMarkerStyle"><span class="icon-arrow-down"></span></div>
|
<div class="nowMarker" :style="nowMarkerStyle" aria-label="Now Marker">
|
||||||
|
<span class="icon-arrow-down"></span>
|
||||||
|
</div>
|
||||||
<svg :width="svgWidth" :height="svgHeight">
|
<svg :width="svgWidth" :height="svgHeight">
|
||||||
<g class="axis" font-size="1.3em" :transform="axisTransform"></g>
|
<g class="axis" font-size="1.3em" :transform="axisTransform"></g>
|
||||||
</svg>
|
</svg>
|
||||||
@ -116,8 +118,10 @@ export default {
|
|||||||
this.axisTransform = `translate(${this.alignmentData.leftWidth + leftOffset}, 20)`;
|
this.axisTransform = `translate(${this.alignmentData.leftWidth + leftOffset}, 20)`;
|
||||||
|
|
||||||
const rightOffset = this.alignmentData.rightWidth ? AXES_PADDING : 0;
|
const rightOffset = this.alignmentData.rightWidth ? AXES_PADDING : 0;
|
||||||
|
|
||||||
|
this.leftAlignmentOffset = this.alignmentData.leftWidth + leftOffset;
|
||||||
this.alignmentOffset =
|
this.alignmentOffset =
|
||||||
this.alignmentData.leftWidth + leftOffset + this.alignmentData.rightWidth + rightOffset;
|
this.leftAlignmentOffset + this.alignmentData.rightWidth + rightOffset;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
@ -175,8 +179,8 @@ export default {
|
|||||||
this.nowMarkerStyle.height = this.contentHeight + 'px';
|
this.nowMarkerStyle.height = this.contentHeight + 'px';
|
||||||
const nowTimeStamp = this.openmct.time.now();
|
const nowTimeStamp = this.openmct.time.now();
|
||||||
const now = this.xScale(nowTimeStamp);
|
const now = this.xScale(nowTimeStamp);
|
||||||
this.nowMarkerStyle.left = `${now + this.alignmentOffset}px`;
|
this.nowMarkerStyle.left = `${now + this.leftAlignmentOffset}px`;
|
||||||
if (now > this.width) {
|
if (now < 0 || now > this.width) {
|
||||||
nowMarker.classList.add('hidden');
|
nowMarker.classList.add('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
.c-toggle-switch {
|
.c-toggle-switch {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
gap: $interiorMarginSm;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
@ -55,7 +56,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
margin-left: $interiorMarginSm;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="l-browse-bar">
|
<div class="l-browse-bar" aria-label="Browse bar">
|
||||||
<div class="l-browse-bar__start">
|
<div class="l-browse-bar__start">
|
||||||
<button
|
<button
|
||||||
v-if="hasParent"
|
v-if="hasParent"
|
||||||
@ -35,6 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
ref="objectName"
|
ref="objectName"
|
||||||
|
aria-label="Browse bar object name"
|
||||||
class="l-browse-bar__object-name c-object-label__name"
|
class="l-browse-bar__object-name c-object-label__name"
|
||||||
:class="{ 'c-input-inline': isPersistable }"
|
:class="{ 'c-input-inline': isPersistable }"
|
||||||
:contenteditable="isNameEditable"
|
:contenteditable="isNameEditable"
|
||||||
|
@ -80,13 +80,11 @@ class Browse {
|
|||||||
this.#openmct.layout.$refs.browseBar.viewKey = viewProvider.key;
|
this.#openmct.layout.$refs.browseBar.viewKey = viewProvider.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateDocumentTitleOnNameMutation(newName) {
|
#handleBrowseObjectUpdate(newObject) {
|
||||||
if (typeof newName === 'string' && newName !== document.title) {
|
this.#openmct.layout.$refs.browseBar.domainObject = newObject;
|
||||||
document.title = newName;
|
|
||||||
this.#openmct.layout.$refs.browseBar.domainObject = {
|
if (typeof newObject.name === 'string' && newObject.name !== document.title) {
|
||||||
...this.#openmct.layout.$refs.browseBar.domainObject,
|
document.title = newObject.name;
|
||||||
name: newName
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,8 +118,8 @@ class Browse {
|
|||||||
document.title = this.#browseObject.name; //change document title to current object in main view
|
document.title = this.#browseObject.name; //change document title to current object in main view
|
||||||
this.#unobserve = this.#openmct.objects.observe(
|
this.#unobserve = this.#openmct.objects.observe(
|
||||||
this.#browseObject,
|
this.#browseObject,
|
||||||
'name',
|
'*',
|
||||||
this.#updateDocumentTitleOnNameMutation.bind(this)
|
this.#handleBrowseObjectUpdate.bind(this)
|
||||||
);
|
);
|
||||||
const currentProvider = this.#openmct.objectViews.getByProviderKey(currentViewKey);
|
const currentProvider = this.#openmct.objectViews.getByProviderKey(currentViewKey);
|
||||||
if (currentProvider && currentProvider.canView(this.#browseObject, this.#openmct.router.path)) {
|
if (currentProvider && currentProvider.canView(this.#browseObject, this.#openmct.router.path)) {
|
||||||
|
Reference in New Issue
Block a user