mirror of
https://github.com/nasa/openmct.git
synced 2025-06-06 09:21:43 +00:00
Merged in conditional stuff and whatnot
This commit is contained in:
commit
cbc04c3fa9
@ -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: [
|
||||||
|
@ -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({
|
||||||
|
@ -116,6 +116,22 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the properties of an OpenMCT domain object by its identifier.
|
||||||
|
*
|
||||||
|
* @param {import('@playwright/test').Page} page - The Playwright page object.
|
||||||
|
* @param {string | identifier - The identifier or UUID of the domain object.
|
||||||
|
* @returns {Promise<Object>} An object containing the properties of the domain object.
|
||||||
|
*/
|
||||||
|
async function getDomainObject(page, identifier) {
|
||||||
|
const domainObject = await page.evaluate(async (objIdentifier) => {
|
||||||
|
const object = await window.openmct.objects.get(objIdentifier);
|
||||||
|
return object;
|
||||||
|
}, identifier);
|
||||||
|
|
||||||
|
return domainObject;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a notification with the given options.
|
* Generate a notification with the given options.
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
@ -705,6 +721,7 @@ export {
|
|||||||
createStableStateTelemetry,
|
createStableStateTelemetry,
|
||||||
expandEntireTree,
|
expandEntireTree,
|
||||||
getCanvasPixels,
|
getCanvasPixels,
|
||||||
|
getDomainObject,
|
||||||
linkParameterToObject,
|
linkParameterToObject,
|
||||||
navigateToObjectWithFixedTimeBounds,
|
navigateToObjectWithFixedTimeBounds,
|
||||||
navigateToObjectWithRealTime,
|
navigateToObjectWithRealTime,
|
||||||
|
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$/);
|
||||||
|
});
|
||||||
|
});
|
@ -29,7 +29,8 @@ import { fileURLToPath } from 'url';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
createExampleTelemetryObject
|
createExampleTelemetryObject,
|
||||||
|
getDomainObject
|
||||||
} from '../../../../appActions.js';
|
} from '../../../../appActions.js';
|
||||||
import { expect, test } from '../../../../pluginFixtures.js';
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
@ -468,6 +469,34 @@ test.describe('Basic Condition Set Use', () => {
|
|||||||
description: 'https://github.com/nasa/openmct/issues/7484'
|
description: 'https://github.com/nasa/openmct/issues/7484'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should toggle shouldFetchHistorical property in inspector', async ({ page }) => {
|
||||||
|
await page.goto(conditionSet.url);
|
||||||
|
await page.getByLabel('Edit Object').click();
|
||||||
|
await page.getByRole('tab', { name: 'Config' }).click();
|
||||||
|
let toggleSwitch = page.getByLabel('condition-historical-toggle');
|
||||||
|
const initialState = await toggleSwitch.isChecked();
|
||||||
|
expect(initialState).toBe(false);
|
||||||
|
|
||||||
|
await toggleSwitch.click();
|
||||||
|
let toggledState = await toggleSwitch.isChecked();
|
||||||
|
expect(toggledState).toBe(true);
|
||||||
|
await page.click('button[title="Save"]');
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
let conditionSetObject = await getDomainObject(page, conditionSet.uuid);
|
||||||
|
expect(conditionSetObject.configuration.shouldFetchHistorical).toBe(true);
|
||||||
|
|
||||||
|
await page.getByLabel('Edit Object').click();
|
||||||
|
await page.getByRole('tab', { name: 'Config' }).click();
|
||||||
|
toggleSwitch = page.getByLabel('condition-historical-toggle');
|
||||||
|
await toggleSwitch.click();
|
||||||
|
toggledState = await toggleSwitch.isChecked();
|
||||||
|
expect(toggledState).toBe(false);
|
||||||
|
await page.click('button[title="Save"]');
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
conditionSetObject = await getDomainObject(page, conditionSet.uuid);
|
||||||
|
expect(conditionSetObject.configuration.shouldFetchHistorical).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Condition Set Composition', () => {
|
test.describe('Condition Set Composition', () => {
|
||||||
|
@ -66,6 +66,10 @@ module.exports = async (config) => {
|
|||||||
{
|
{
|
||||||
pattern: 'dist/generatorWorker.js*',
|
pattern: 'dist/generatorWorker.js*',
|
||||||
included: false
|
included: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: 'dist/historicalTelemetryWorker.js*',
|
||||||
|
included: false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
port: 9876,
|
port: 9876,
|
||||||
|
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.
|
||||||
|
@ -760,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
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));
|
||||||
|
};
|
||||||
|
}
|
53
src/plugins/condition/ConditionInspectorViewProvider.js
Normal file
53
src/plugins/condition/ConditionInspectorViewProvider.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// src/plugins/condition/ConditionInspectorView.js
|
||||||
|
|
||||||
|
import mount from 'utils/mount';
|
||||||
|
|
||||||
|
import ConditionConfigView from './components/ConditionInspectorConfigView.vue';
|
||||||
|
|
||||||
|
export default function ConditionInspectorView(openmct) {
|
||||||
|
return {
|
||||||
|
key: 'condition-config',
|
||||||
|
name: 'Config',
|
||||||
|
canView: function (selection) {
|
||||||
|
return selection.length > 0 && selection[0][0].context.item.type === 'conditionSet';
|
||||||
|
},
|
||||||
|
view: function (selection) {
|
||||||
|
let _destroy = null;
|
||||||
|
const domainObject = selection[0][0].context.item;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
const { destroy } = mount(
|
||||||
|
{
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
ConditionConfigView: ConditionConfigView
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject
|
||||||
|
},
|
||||||
|
template: '<condition-config-view></condition-config-view>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
app: openmct.app,
|
||||||
|
element
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_destroy = destroy;
|
||||||
|
},
|
||||||
|
showTab: function (isEditing) {
|
||||||
|
return isEditing;
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
if (_destroy) {
|
||||||
|
_destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -24,6 +24,7 @@ import { EventEmitter } from 'eventemitter3';
|
|||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import Condition from './Condition.js';
|
import Condition from './Condition.js';
|
||||||
|
import HistoricalTelemetryProvider from './HistoricalTelemetryProvider.js';
|
||||||
import { getLatestTimestamp } from './utils/time.js';
|
import { getLatestTimestamp } from './utils/time.js';
|
||||||
|
|
||||||
export default class ConditionManager extends EventEmitter {
|
export default class ConditionManager extends EventEmitter {
|
||||||
@ -46,6 +47,8 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
applied: false
|
applied: false
|
||||||
};
|
};
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
this.telemetryBuffer = [];
|
||||||
|
this.isProcessing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeToTelemetry(telemetryObject) {
|
subscribeToTelemetry(telemetryObject) {
|
||||||
@ -320,69 +323,18 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
return currentCondition;
|
return currentCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentConditionLAD(conditionResults) {
|
async getHistoricalData(options) {
|
||||||
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
|
if (!this.conditionSetDomainObject.configuration.shouldFetchHistorical) {
|
||||||
let currentCondition = conditionCollection[conditionCollection.length - 1];
|
|
||||||
|
|
||||||
for (let i = 0; i < conditionCollection.length - 1; i++) {
|
|
||||||
if (conditionResults[conditionCollection[i].id]) {
|
|
||||||
//first condition to be true wins
|
|
||||||
currentCondition = conditionCollection[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentCondition;
|
|
||||||
}
|
|
||||||
|
|
||||||
async requestLADConditionSetOutput(options) {
|
|
||||||
if (!this.conditions.length) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
const historicalTelemetry = new HistoricalTelemetryProvider(
|
||||||
await this.compositionLoad;
|
this.openmct,
|
||||||
|
this.conditions,
|
||||||
let latestTimestamp;
|
this.conditionSetDomainObject,
|
||||||
let conditionResults = {};
|
options
|
||||||
let nextLegOptions = { ...options };
|
|
||||||
delete nextLegOptions.onPartialResponse;
|
|
||||||
|
|
||||||
const results = await Promise.all(
|
|
||||||
this.conditions.map((condition) => condition.requestLADConditionResult(nextLegOptions))
|
|
||||||
);
|
);
|
||||||
|
const historicalData = await historicalTelemetry.getHistoricalData();
|
||||||
results.forEach((resultObj) => {
|
return historicalData;
|
||||||
const {
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
data: { result }
|
|
||||||
} = resultObj;
|
|
||||||
|
|
||||||
if (this.findConditionById(id)) {
|
|
||||||
conditionResults[id] = Boolean(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
latestTimestamp = getLatestTimestamp(
|
|
||||||
latestTimestamp,
|
|
||||||
data,
|
|
||||||
this.timeSystems,
|
|
||||||
this.openmct.time.getTimeSystem()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!Object.values(latestTimestamp).some((timeSystem) => timeSystem)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentCondition = this.getCurrentConditionLAD(conditionResults);
|
|
||||||
const currentOutput = {
|
|
||||||
output: currentCondition.configuration.output,
|
|
||||||
id: this.conditionSetDomainObject.identifier,
|
|
||||||
conditionId: currentCondition.id,
|
|
||||||
...latestTimestamp
|
|
||||||
};
|
|
||||||
|
|
||||||
return [currentOutput];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isTelemetryUsed(endpoint) {
|
isTelemetryUsed(endpoint) {
|
||||||
@ -409,13 +361,16 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
const datum = data[0];
|
const datum = data[0];
|
||||||
|
|
||||||
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
||||||
const timeSystemKey = this.openmct.time.getTimeSystem().key;
|
const keyString = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||||
let timestamp = {};
|
const associatedTelemetryCollection = this.telemetryCollections[keyString];
|
||||||
const currentTimestamp = normalizedDatum[timeSystemKey];
|
const timeKey = associatedTelemetryCollection.timeKey;
|
||||||
timestamp[timeSystemKey] = currentTimestamp;
|
const formattedTimeStamp = associatedTelemetryCollection.parseTime(datum);
|
||||||
if (this.shouldEvaluateNewTelemetry(currentTimestamp)) {
|
const rawTimestamp = {
|
||||||
|
[timeKey]: datum[timeKey]
|
||||||
|
};
|
||||||
|
if (this.shouldEvaluateNewTelemetry(formattedTimeStamp)) {
|
||||||
this.updateConditionResults(normalizedDatum);
|
this.updateConditionResults(normalizedDatum);
|
||||||
this.updateCurrentCondition(timestamp);
|
this.updateCurrentCondition(rawTimestamp, endpoint, datum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,22 +383,77 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCurrentCondition(timestamp) {
|
emitConditionSetResult(currentCondition, timestamp, outputValue, result) {
|
||||||
const currentCondition = this.getCurrentCondition();
|
const conditionSetResult = {
|
||||||
|
|
||||||
this.emit(
|
|
||||||
'conditionSetResultUpdated',
|
|
||||||
Object.assign(
|
|
||||||
{
|
|
||||||
output: currentCondition.configuration.output,
|
|
||||||
id: this.conditionSetDomainObject.identifier,
|
id: this.conditionSetDomainObject.identifier,
|
||||||
conditionId: currentCondition.id
|
conditionId: currentCondition.id,
|
||||||
},
|
value: outputValue,
|
||||||
|
result,
|
||||||
|
...timestamp
|
||||||
|
};
|
||||||
|
this.emit('conditionSetResultUpdated', conditionSetResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCurrentCondition(timestamp, telemetryObject, telemetryData) {
|
||||||
|
this.telemetryBuffer.push({ timestamp, telemetryObject, telemetryData });
|
||||||
|
|
||||||
|
if (!this.isProcessing) {
|
||||||
|
this.processBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async processBuffer() {
|
||||||
|
this.isProcessing = true;
|
||||||
|
|
||||||
|
while (this.telemetryBuffer.length > 0) {
|
||||||
|
const { timestamp, telemetryObject, telemetryData } = this.telemetryBuffer.shift();
|
||||||
|
await this.processCondition(timestamp, telemetryObject, telemetryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isProcessing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchUnderlyingTelemetry(currentCondition, telemetryObject, telemetryData, timestamp) {
|
||||||
|
let telemetryValue;
|
||||||
|
const selectedOutputIdentifier = currentCondition?.configuration?.outputTelemetry;
|
||||||
|
const outputMetadata = currentCondition?.configuration?.outputMetadata;
|
||||||
|
const telemetryKeystring = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
|
|
||||||
|
if (selectedOutputIdentifier === telemetryKeystring) {
|
||||||
|
telemetryValue = telemetryData[outputMetadata];
|
||||||
|
} else {
|
||||||
|
// grab it from the associated telemetry collection
|
||||||
|
const outputTelemetryCollection = this.telemetryCollections[selectedOutputIdentifier];
|
||||||
|
const latestTelemetryData = outputTelemetryCollection.getAll();
|
||||||
|
const lastValue = latestTelemetryData[latestTelemetryData.length - 1];
|
||||||
|
if (lastValue && lastValue?.[outputMetadata]) {
|
||||||
|
telemetryValue = lastValue?.[outputMetadata];
|
||||||
|
} else {
|
||||||
|
telemetryValue = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return telemetryValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
processCondition(timestamp, telemetryObject, telemetryData) {
|
||||||
|
const currentCondition = this.getCurrentCondition();
|
||||||
|
const conditionDetails = this.conditions.filter(
|
||||||
|
(condition) => condition.id === currentCondition.id
|
||||||
|
)?.[0];
|
||||||
|
const conditionResult = currentCondition?.isDefault ? false : conditionDetails?.result;
|
||||||
|
let telemetryValue = currentCondition.configuration.output;
|
||||||
|
if (currentCondition?.configuration?.outputTelemetry) {
|
||||||
|
telemetryValue = this.fetchUnderlyingTelemetry(
|
||||||
|
currentCondition,
|
||||||
|
telemetryObject,
|
||||||
|
telemetryData,
|
||||||
timestamp
|
timestamp
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.emitConditionSetResult(currentCondition, timestamp, telemetryValue, conditionResult);
|
||||||
|
}
|
||||||
|
|
||||||
getTestData(metadatum) {
|
getTestData(metadatum) {
|
||||||
let data = undefined;
|
let data = undefined;
|
||||||
if (this.testData.applied) {
|
if (this.testData.applied) {
|
||||||
@ -460,18 +470,7 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
|
|
||||||
createNormalizedDatum(telemetryDatum, endpoint) {
|
createNormalizedDatum(telemetryDatum, endpoint) {
|
||||||
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||||
const metadata = this.openmct.telemetry.getMetadata(endpoint).valueMetadatas;
|
const normalizedDatum = { ...telemetryDatum };
|
||||||
|
|
||||||
const normalizedDatum = Object.values(metadata).reduce((datum, metadatum) => {
|
|
||||||
const testValue = this.getTestData(metadatum);
|
|
||||||
const formatter = this.openmct.telemetry.getValueFormatter(metadatum);
|
|
||||||
datum[metadatum.key] =
|
|
||||||
testValue !== undefined
|
|
||||||
? formatter.parse(testValue)
|
|
||||||
: formatter.parse(telemetryDatum[metadatum.source]);
|
|
||||||
|
|
||||||
return datum;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
normalizedDatum.id = id;
|
normalizedDatum.id = id;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ export default class ConditionSetMetadataProvider {
|
|||||||
return domainObject.type === 'conditionSet';
|
return domainObject.type === 'conditionSet';
|
||||||
}
|
}
|
||||||
|
|
||||||
getDomains(domainObject) {
|
getDefaultDomains(domainObject) {
|
||||||
return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
|
return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
|
||||||
return {
|
return {
|
||||||
key: ts.key,
|
key: ts.key,
|
||||||
@ -43,34 +43,59 @@ export default class ConditionSetMetadataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMetadata(domainObject) {
|
getMetadata(domainObject) {
|
||||||
const enumerations = domainObject.configuration.conditionCollection.map((condition, index) => {
|
const format = {};
|
||||||
return {
|
domainObject.configuration.conditionCollection.forEach((condition, index) => {
|
||||||
string: condition.configuration.output,
|
if (condition?.configuration?.valueMetadata?.enumerations) {
|
||||||
value: index
|
delete format.formatString;
|
||||||
};
|
format.format = 'enum';
|
||||||
|
format.enumerations = condition?.configuration?.valueMetadata?.enumerations;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
const resultEnum = [
|
||||||
values: this.getDomains().concat([
|
|
||||||
{
|
{
|
||||||
key: 'state',
|
string: 'true',
|
||||||
source: 'output',
|
value: true
|
||||||
name: 'State',
|
},
|
||||||
format: 'enum',
|
{
|
||||||
enumerations: enumerations,
|
string: 'false',
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const metaDataToReturn = {
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
key: 'value',
|
||||||
|
name: 'Value',
|
||||||
hints: {
|
hints: {
|
||||||
range: 1
|
range: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'output',
|
key: 'result',
|
||||||
name: 'Value',
|
source: 'result',
|
||||||
format: 'string',
|
name: 'Result',
|
||||||
|
format: 'enum',
|
||||||
|
enumerations: resultEnum,
|
||||||
hints: {
|
hints: {
|
||||||
range: 2
|
range: 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// if there are any parameters, grab the first one's timeMetaData
|
||||||
|
const timeMetaData =
|
||||||
|
domainObject?.configuration?.conditionCollection[0]?.configuration.timeMetadata;
|
||||||
|
|
||||||
|
if (timeMetaData) {
|
||||||
|
metaDataToReturn.values.push(timeMetaData);
|
||||||
|
} else {
|
||||||
|
const defaultDomains = this.getDefaultDomains(domainObject);
|
||||||
|
metaDataToReturn.values.push(...defaultDomains);
|
||||||
|
}
|
||||||
|
|
||||||
|
return metaDataToReturn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,15 +42,17 @@ export default class ConditionSetTelemetryProvider {
|
|||||||
|
|
||||||
async request(domainObject, options) {
|
async request(domainObject, options) {
|
||||||
let conditionManager = this.getConditionManager(domainObject);
|
let conditionManager = this.getConditionManager(domainObject);
|
||||||
let latestOutput = await conditionManager.requestLADConditionSetOutput(options);
|
const formattedHistoricalData = await conditionManager.getHistoricalData(options);
|
||||||
return latestOutput;
|
return formattedHistoricalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(domainObject, callback) {
|
subscribe(domainObject, callback) {
|
||||||
let conditionManager = this.getConditionManager(domainObject);
|
let conditionManager = this.getConditionManager(domainObject);
|
||||||
|
|
||||||
conditionManager.on('conditionSetResultUpdated', (data) => {
|
conditionManager.on('conditionSetResultUpdated', (data) => {
|
||||||
|
if (data?.result) {
|
||||||
callback(data);
|
callback(data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.destroyConditionManager.bind(
|
return this.destroyConditionManager.bind(
|
||||||
|
285
src/plugins/condition/HistoricalTelemetryProvider.js
Normal file
285
src/plugins/condition/HistoricalTelemetryProvider.js
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
import { evaluateResults } from './utils/evaluator.js';
|
||||||
|
|
||||||
|
export default class HistoricalTelemetryProvider {
|
||||||
|
#telemetryOptions;
|
||||||
|
#telemetryObjects = {};
|
||||||
|
#conditions;
|
||||||
|
#conditionSetDomainObject;
|
||||||
|
#openmct;
|
||||||
|
#telemetryCollections = {};
|
||||||
|
#composition;
|
||||||
|
#outputTelemetryDetails = {};
|
||||||
|
|
||||||
|
constructor(openmct, conditions, conditionSetDomainObject, telemetryOptions) {
|
||||||
|
this.#openmct = openmct;
|
||||||
|
this.#conditions = conditions;
|
||||||
|
this.#conditionSetDomainObject = conditionSetDomainObject;
|
||||||
|
this.#telemetryOptions = telemetryOptions;
|
||||||
|
this.addTelemetryObject = this.addTelemetryObject.bind(this);
|
||||||
|
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#evaluateTrueCondition() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#getInputTelemetry(conditionCriteria, dataFrame, timestamp) {
|
||||||
|
if (conditionCriteria?.telemetry === 'all') {
|
||||||
|
// if the criteria is 'all', return all telemetry data
|
||||||
|
const allTelemetry = [];
|
||||||
|
Object.keys(dataFrame).forEach((key) => {
|
||||||
|
const telemetryData = dataFrame[key][timestamp];
|
||||||
|
telemetryData.id = key;
|
||||||
|
if (telemetryData) {
|
||||||
|
allTelemetry.push(telemetryData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return allTelemetry;
|
||||||
|
}
|
||||||
|
if (!conditionCriteria?.telemetry) {
|
||||||
|
console.debug('🚨 Missing telemetry key in condition criteria - are we the default?');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const conditionInputTelemetryKeyString = this.#openmct.objects.makeKeyString(
|
||||||
|
conditionCriteria?.telemetry
|
||||||
|
);
|
||||||
|
|
||||||
|
const inputTelemetryByDate = dataFrame[conditionInputTelemetryKeyString];
|
||||||
|
if (!inputTelemetryByDate) {
|
||||||
|
console.debug(`🚨 Missing ALL data for ${conditionInputTelemetryKeyString}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const specificDatum = inputTelemetryByDate[timestamp];
|
||||||
|
|
||||||
|
if (!specificDatum) {
|
||||||
|
console.debug(`🚨 Missing data for ${conditionInputTelemetryKeyString} at ${timestamp}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
specificDatum.id = conditionInputTelemetryKeyString;
|
||||||
|
return specificDatum;
|
||||||
|
}
|
||||||
|
|
||||||
|
#formatDatumForOutput(datum, metadata, result) {
|
||||||
|
const formattedDatum = {
|
||||||
|
...datum,
|
||||||
|
value: datum[metadata],
|
||||||
|
result
|
||||||
|
};
|
||||||
|
return formattedDatum;
|
||||||
|
}
|
||||||
|
|
||||||
|
#computeHistoricalDatum(timestamp, dataFrame, timekey) {
|
||||||
|
for (let conditionIndex = 0; conditionIndex < this.#conditions.length; conditionIndex++) {
|
||||||
|
const condition = this.#conditions[conditionIndex];
|
||||||
|
const { id } = condition;
|
||||||
|
const conditionCriteria = condition?.criteria.length > 0;
|
||||||
|
let result = false;
|
||||||
|
let defaultHit = false;
|
||||||
|
if (conditionCriteria) {
|
||||||
|
result = evaluateResults(
|
||||||
|
condition.criteria.map((criterion) => {
|
||||||
|
const inputTelemetry = this.#getInputTelemetry(criterion, dataFrame, timestamp);
|
||||||
|
return criterion.computeResult({ id, ...inputTelemetry });
|
||||||
|
}),
|
||||||
|
condition?.trigger
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// default criteria is 'all'
|
||||||
|
defaultHit = true;
|
||||||
|
}
|
||||||
|
if (result || defaultHit) {
|
||||||
|
// generate the output telemetry object if available
|
||||||
|
const outputTelmetryDetail = this.#outputTelemetryDetails[id];
|
||||||
|
if (
|
||||||
|
outputTelmetryDetail?.outputTelemetryKeyString &&
|
||||||
|
outputTelmetryDetail?.outputMetadata
|
||||||
|
) {
|
||||||
|
const outputTelmetryDatum =
|
||||||
|
dataFrame[outputTelmetryDetail.outputTelemetryKeyString][timestamp];
|
||||||
|
const formattedDatum = this.#formatDatumForOutput(
|
||||||
|
outputTelmetryDatum,
|
||||||
|
outputTelmetryDetail.outputMetadata,
|
||||||
|
result
|
||||||
|
);
|
||||||
|
return formattedDatum;
|
||||||
|
} else if (outputTelmetryDetail?.staticOutputValue) {
|
||||||
|
const staticOutput = {
|
||||||
|
output: outputTelmetryDetail?.staticOutputValue,
|
||||||
|
[timekey]: timestamp,
|
||||||
|
result
|
||||||
|
};
|
||||||
|
return staticOutput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #loadTelemetryCollections() {
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(this.#telemetryObjects).map(async ([keystring, telemetryObject]) => {
|
||||||
|
// clone telemetry options without size as we need to load all data
|
||||||
|
const telemetryOptionsWithoutSize = { ...this.#telemetryOptions };
|
||||||
|
delete telemetryOptionsWithoutSize.size;
|
||||||
|
const telemetryCollection = this.#openmct.telemetry.requestCollection(
|
||||||
|
telemetryObject,
|
||||||
|
telemetryOptionsWithoutSize
|
||||||
|
);
|
||||||
|
await telemetryCollection.load();
|
||||||
|
this.#telemetryCollections[keystring] = telemetryCollection;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#computeHistoricalData(dataFrame) {
|
||||||
|
const historicalData = [];
|
||||||
|
if (Object.keys(dataFrame).length === 0) {
|
||||||
|
// if we have no telemetry data, return an empty object
|
||||||
|
return historicalData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the first telemetry collection as the reference for the frame
|
||||||
|
const referenceTelemetryKeyString = Object.keys(dataFrame)[0];
|
||||||
|
const referenceTelemetryCollection = this.#telemetryCollections[referenceTelemetryKeyString];
|
||||||
|
const referenceTelemetryData = referenceTelemetryCollection.getAll();
|
||||||
|
referenceTelemetryData.forEach((datum) => {
|
||||||
|
const timestamp = datum[referenceTelemetryCollection.timeKey];
|
||||||
|
const historicalDatum = this.#computeHistoricalDatum(
|
||||||
|
timestamp,
|
||||||
|
dataFrame,
|
||||||
|
referenceTelemetryCollection.timeKey
|
||||||
|
);
|
||||||
|
if (historicalDatum) {
|
||||||
|
historicalData.push(historicalDatum);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return historicalData;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#createDataFrame() {
|
||||||
|
// Step 1: Collect all unique timestamps from all telemetry collections
|
||||||
|
const allTimestampsSet = new Set();
|
||||||
|
|
||||||
|
Object.values(this.#telemetryCollections).forEach((collection) => {
|
||||||
|
collection.getAll().forEach((dataPoint) => {
|
||||||
|
const timeKey = collection.timeKey;
|
||||||
|
allTimestampsSet.add(dataPoint[timeKey]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert the set to a sorted array
|
||||||
|
const allTimestamps = Array.from(allTimestampsSet).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
// Step 2: Initialize the result object
|
||||||
|
const dataFrame = {};
|
||||||
|
|
||||||
|
// 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[timestamp] = imputedDatum;
|
||||||
|
} else {
|
||||||
|
console.debug(`🚨 Missing data for ${keyString} at ${timestamp}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dataFrame[keyString] = alignedValues;
|
||||||
|
});
|
||||||
|
return dataFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTelemetryObject(telemetryObjectToAdd) {
|
||||||
|
const keyString = this.#openmct.objects.makeKeyString(telemetryObjectToAdd.identifier);
|
||||||
|
this.#telemetryObjects[keyString] = telemetryObjectToAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTelemetryObject(telemetryIdentifierToRemove) {
|
||||||
|
const keyStringToRemove = this.#openmct.objects.makeKeyString(telemetryIdentifierToRemove);
|
||||||
|
this.#telemetryObjects = this.#telemetryObjects.filter((existingTelemetryObject) => {
|
||||||
|
const existingKeyString = this.#openmct.objects.makeKeyString(
|
||||||
|
existingTelemetryObject.identifier
|
||||||
|
);
|
||||||
|
return keyStringToRemove !== existingKeyString;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.#composition.off('add', this.addTelemetryObject);
|
||||||
|
this.#composition.off('remove', this.removeTelemetryObject);
|
||||||
|
Object.keys(this.#telemetryCollections).forEach((key) => {
|
||||||
|
this.#telemetryCollections[key]?.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async #loadComposition() {
|
||||||
|
// load the composition of the condition set
|
||||||
|
this.#composition = this.#openmct.composition.get(this.#conditionSetDomainObject);
|
||||||
|
if (this.#composition) {
|
||||||
|
this.#composition.on('add', this.addTelemetryObject);
|
||||||
|
this.#composition.on('remove', this.removeTelemetryObject);
|
||||||
|
await this.#composition.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#processConditionSet() {
|
||||||
|
const conditionCollection = this.#conditionSetDomainObject.configuration.conditionCollection;
|
||||||
|
conditionCollection.forEach((condition, index) => {
|
||||||
|
const { outputTelemetry, outputMetadata, output } = condition.configuration;
|
||||||
|
if (outputTelemetry && outputMetadata) {
|
||||||
|
this.#outputTelemetryDetails[condition.id] = {
|
||||||
|
outputTelemetryKeyString: outputTelemetry,
|
||||||
|
outputMetadata
|
||||||
|
};
|
||||||
|
} else if (output) {
|
||||||
|
this.#outputTelemetryDetails[condition.id] = {
|
||||||
|
staticOutputValue: output
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHistoricalData() {
|
||||||
|
console.debug('🍯 Getting historical data');
|
||||||
|
// get the telemetry objects from the condition set
|
||||||
|
await this.#loadComposition();
|
||||||
|
console.debug('🍯 Loaded telemetry objects', this.#telemetryObjects);
|
||||||
|
if (!this.#telemetryObjects) {
|
||||||
|
console.debug('🚨 No telemetry objects found in condition set');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
console.debug('🍯 Processed Condition Set', this.#outputTelemetryDetails);
|
||||||
|
this.#processConditionSet();
|
||||||
|
// load telemetry collections for each telemetry object
|
||||||
|
await this.#loadTelemetryCollections();
|
||||||
|
console.debug('🍯 Loaded Telemetry Collections', this.#telemetryCollections);
|
||||||
|
// create data frame from telemetry collections
|
||||||
|
const dataFrame = this.#createDataFrame();
|
||||||
|
console.debug('🍯 Created data frame', dataFrame);
|
||||||
|
// compute historical data from data frame
|
||||||
|
const computedHistoricalData = this.#computeHistoricalData(dataFrame);
|
||||||
|
console.debug('🍯 Computed historical data', computedHistoricalData);
|
||||||
|
return computedHistoricalData;
|
||||||
|
}
|
||||||
|
}
|
@ -235,10 +235,11 @@ export default {
|
|||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
},
|
},
|
||||||
addTelemetryObject(domainObject) {
|
async addTelemetryObject(domainObject) {
|
||||||
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
const telemetryPath = await this.getFullTelemetryPath(domainObject);
|
||||||
|
|
||||||
this.telemetryObjs.push(domainObject);
|
this.telemetryObjs.push({ ...domainObject, path: telemetryPath });
|
||||||
this.$emit('telemetry-updated', this.telemetryObjs);
|
this.$emit('telemetry-updated', this.telemetryObjs);
|
||||||
|
|
||||||
this.subscribeToStaleness(domainObject, (stalenessResponse) => {
|
this.subscribeToStaleness(domainObject, (stalenessResponse) => {
|
||||||
@ -248,6 +249,19 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
async getFullTelemetryPath(telemetry) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(telemetry.identifier);
|
||||||
|
const originalPathObjects = await this.openmct.objects.getOriginalPath(keyString, []);
|
||||||
|
|
||||||
|
const telemetryPath = originalPathObjects.reverse().map((pathObject) => {
|
||||||
|
if (pathObject.type !== 'root') {
|
||||||
|
return pathObject.name;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
return telemetryPath.join('/');
|
||||||
|
},
|
||||||
removeTelemetryObject(identifier) {
|
removeTelemetryObject(identifier) {
|
||||||
const keyString = this.openmct.objects.makeKeyString(identifier);
|
const keyString = this.openmct.objects.makeKeyString(identifier);
|
||||||
const index = this.telemetryObjs.findIndex((obj) => {
|
const index = this.telemetryObjs.findIndex((obj) => {
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-inspect-properties">
|
||||||
|
<h2>Configuration</h2>
|
||||||
|
<section>
|
||||||
|
<div class="c-form-row">
|
||||||
|
<label for="historical-toggle">Enable Historical: </label>
|
||||||
|
<ToggleSwitch
|
||||||
|
id="historical-toggle"
|
||||||
|
class="c-toggle-switch"
|
||||||
|
:checked="historicalEnabled"
|
||||||
|
name="condition-historical-toggle"
|
||||||
|
@change="onToggleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ToggleSwitch
|
||||||
|
},
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
historicalEnabled: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.historicalEnabled = this.domainObject.configuration.shouldFetchHistorical;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onToggleChange() {
|
||||||
|
this.historicalEnabled = !this.historicalEnabled;
|
||||||
|
this.openmct.objects.mutate(
|
||||||
|
this.domainObject,
|
||||||
|
'configuration.shouldFetchHistorical',
|
||||||
|
this.historicalEnabled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.c-inspect-properties {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-form-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -99,13 +99,13 @@
|
|||||||
@change="setOutputValue"
|
@change="setOutputValue"
|
||||||
>
|
>
|
||||||
<option v-for="option in outputOptions" :key="option" :value="option">
|
<option v-for="option in outputOptions" :key="option" :value="option">
|
||||||
{{ initCap(option) }}
|
{{ option }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
<span class="c-cdef__control">
|
<span class="c-cdef__control">
|
||||||
<input
|
<input
|
||||||
v-if="selectedOutputSelection === outputOptions[2]"
|
v-if="selectedOutputSelection === outputOptions[3]"
|
||||||
v-model="condition.configuration.output"
|
v-model="condition.configuration.output"
|
||||||
aria-label="Condition Output String"
|
aria-label="Condition Output String"
|
||||||
class="t-condition-name-input"
|
class="t-condition-name-input"
|
||||||
@ -113,8 +113,41 @@
|
|||||||
@change="persist"
|
@change="persist"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="selectedOutputSelection === telemetryValueString" class="c-cdef__control">
|
||||||
|
<select
|
||||||
|
v-model="condition.configuration.outputTelemetry"
|
||||||
|
aria-label="Output Telemetry Selection"
|
||||||
|
@change="persist"
|
||||||
|
>
|
||||||
|
<option value="">- Select Telemetry -</option>
|
||||||
|
<option
|
||||||
|
v-for="telemetryOption in telemetry"
|
||||||
|
:key="openmct.objects.makeKeyString(telemetryOption.identifier)"
|
||||||
|
:value="openmct.objects.makeKeyString(telemetryOption.identifier)"
|
||||||
|
>
|
||||||
|
{{ telemetryOption.path }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
<span v-if="condition.configuration.outputTelemetry" class="c-cdef__control">
|
||||||
|
<select
|
||||||
|
v-model="condition.configuration.outputMetadata"
|
||||||
|
aria-label="Output Telemetry Metadata Selection"
|
||||||
|
@change="persist"
|
||||||
|
>
|
||||||
|
<option value="">- Select Field -</option>
|
||||||
|
<option
|
||||||
|
v-for="(option, index) in telemetryMetadataOptions[
|
||||||
|
condition.configuration.outputTelemetry
|
||||||
|
]"
|
||||||
|
:key="index"
|
||||||
|
:value="option.key"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div v-if="!condition.isDefault" class="c-cdef__match-and-criteria">
|
<div v-if="!condition.isDefault" class="c-cdef__match-and-criteria">
|
||||||
<span class="c-cdef__separator c-row-separator"></span>
|
<span class="c-cdef__separator c-row-separator"></span>
|
||||||
<span class="c-cdef__label">Match</span>
|
<span class="c-cdef__label">Match</span>
|
||||||
@ -183,7 +216,12 @@
|
|||||||
<span class="c-condition__name">
|
<span class="c-condition__name">
|
||||||
{{ condition.configuration.name }}
|
{{ condition.configuration.name }}
|
||||||
</span>
|
</span>
|
||||||
<span class="c-condition__output"> Output: {{ condition.configuration.output }} </span>
|
<span class="c-condition__output">
|
||||||
|
Output:
|
||||||
|
{{
|
||||||
|
condition.configuration.output === undefined ? 'none' : condition.configuration.output
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-condition__summary">
|
<div class="c-condition__summary">
|
||||||
<ConditionDescription :show-label="false" :condition="condition" />
|
<ConditionDescription :show-label="false" :condition="condition" />
|
||||||
@ -197,6 +235,7 @@ import { v4 as uuid } from 'uuid';
|
|||||||
|
|
||||||
import { TRIGGER, TRIGGER_LABEL } from '@/plugins/condition/utils/constants';
|
import { TRIGGER, TRIGGER_LABEL } from '@/plugins/condition/utils/constants';
|
||||||
|
|
||||||
|
import { TELEMETRY_VALUE } from '../utils/constants.js';
|
||||||
import ConditionDescription from './ConditionDescription.vue';
|
import ConditionDescription from './ConditionDescription.vue';
|
||||||
import Criterion from './CriterionItem.vue';
|
import Criterion from './CriterionItem.vue';
|
||||||
|
|
||||||
@ -252,10 +291,13 @@ export default {
|
|||||||
expanded: true,
|
expanded: true,
|
||||||
trigger: 'all',
|
trigger: 'all',
|
||||||
selectedOutputSelection: '',
|
selectedOutputSelection: '',
|
||||||
outputOptions: ['false', 'true', 'string'],
|
telemetryValueString: TELEMETRY_VALUE,
|
||||||
|
outputOptions: ['none', 'false', 'true', 'string', TELEMETRY_VALUE],
|
||||||
criterionIndex: 0,
|
criterionIndex: 0,
|
||||||
draggingOver: false,
|
draggingOver: false,
|
||||||
isDefault: this.condition.isDefault
|
isDefault: this.condition.isDefault,
|
||||||
|
telemetryMetadataOptions: {},
|
||||||
|
telemetryFormats: new Map()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -289,32 +331,64 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
condition: {
|
||||||
|
handler() {
|
||||||
|
const config = this.condition?.configuration;
|
||||||
|
if (config?.output !== TELEMETRY_VALUE) {
|
||||||
|
config.outputTelemetry = null;
|
||||||
|
config.outputMetadata = null;
|
||||||
|
config.timeMetadata = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
telemetry: {
|
||||||
|
handler() {
|
||||||
|
this.initializeMetadata();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.setOutputSelection();
|
this.setOutputSelection();
|
||||||
|
this.initializeMetadata();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setOutputSelection() {
|
setOutputSelection() {
|
||||||
let conditionOutput = this.condition.configuration.output;
|
let conditionOutput = this.condition.configuration.output;
|
||||||
if (conditionOutput) {
|
if (conditionOutput === undefined) {
|
||||||
if (conditionOutput !== 'false' && conditionOutput !== 'true') {
|
this.selectedOutputSelection = 'none';
|
||||||
this.selectedOutputSelection = 'string';
|
} else if (['false', 'true', TELEMETRY_VALUE].includes(conditionOutput)) {
|
||||||
} else {
|
|
||||||
this.selectedOutputSelection = conditionOutput;
|
this.selectedOutputSelection = conditionOutput;
|
||||||
}
|
} else {
|
||||||
|
this.selectedOutputSelection = 'string';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setOutputValue() {
|
setOutputValue() {
|
||||||
if (this.selectedOutputSelection === 'string') {
|
if (this.selectedOutputSelection === 'string') {
|
||||||
this.condition.configuration.output = '';
|
this.condition.configuration.output = '';
|
||||||
|
} else if (this.selectedOutputSelection === 'none') {
|
||||||
|
this.condition.configuration.output = undefined;
|
||||||
} else {
|
} else {
|
||||||
this.condition.configuration.output = this.selectedOutputSelection;
|
this.condition.configuration.output = this.selectedOutputSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.persist();
|
this.persist();
|
||||||
},
|
},
|
||||||
|
getOutputMetadata() {
|
||||||
|
const config = this.condition.configuration;
|
||||||
|
let valueMetadata;
|
||||||
|
if (config?.outputTelemetry && config?.outputMetadata) {
|
||||||
|
valueMetadata = this.telemetryFormats.get(
|
||||||
|
`${config?.outputTelemetry}_${config?.outputMetadata}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return valueMetadata;
|
||||||
|
},
|
||||||
addCriteria() {
|
addCriteria() {
|
||||||
const criteriaObject = {
|
const criteriaObject = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
@ -396,13 +470,56 @@ export default {
|
|||||||
this.condition.configuration.criteria.splice(index + 1, 0, clonedCriterion);
|
this.condition.configuration.criteria.splice(index + 1, 0, clonedCriterion);
|
||||||
this.persist();
|
this.persist();
|
||||||
},
|
},
|
||||||
|
persistTimeMetadata() {
|
||||||
|
if (!this.condition.configuration.outputTelemetry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const outputTelemetryObject = this.telemetry.find(
|
||||||
|
(telemetryItem) =>
|
||||||
|
this.openmct.objects.makeKeyString(telemetryItem.identifier) ===
|
||||||
|
this.condition.configuration.outputTelemetry
|
||||||
|
);
|
||||||
|
const timeSystem = this.openmct.time.getTimeSystem();
|
||||||
|
const telemetryMetadata = this.openmct.telemetry.getMetadata(outputTelemetryObject);
|
||||||
|
const domains = telemetryMetadata?.valuesForHints(['domain']);
|
||||||
|
const timeMetaData = domains.find((d) => d.key === timeSystem.key);
|
||||||
|
if (telemetryMetadata) {
|
||||||
|
this.condition.configuration.timeMetadata = timeMetaData;
|
||||||
|
}
|
||||||
|
},
|
||||||
persist() {
|
persist() {
|
||||||
|
const valueMetadata = this.getOutputMetadata();
|
||||||
|
if (valueMetadata) {
|
||||||
|
this.condition.configuration.valueMetadata = valueMetadata;
|
||||||
|
this.persistTimeMetadata();
|
||||||
|
}
|
||||||
this.$emit('update-condition', {
|
this.$emit('update-condition', {
|
||||||
condition: this.condition
|
condition: this.condition
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
initCap(str) {
|
initCap(str) {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
|
},
|
||||||
|
initializeMetadata() {
|
||||||
|
this.telemetry.forEach((telemetryObject) => {
|
||||||
|
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
|
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||||
|
if (telemetryMetadata) {
|
||||||
|
this.telemetryMetadataOptions[id] = telemetryMetadata.values().slice();
|
||||||
|
telemetryMetadata.values().forEach((telemetryValue) => {
|
||||||
|
this.telemetryFormats.set(`${id}_${telemetryValue.key}`, telemetryValue);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.telemetryMetadataOptions[id] = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getId(identifier) {
|
||||||
|
if (identifier) {
|
||||||
|
return this.openmct.objects.makeKeyString(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
@ -86,7 +86,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateCurrentOutput(currentConditionResult) {
|
updateCurrentOutput(currentConditionResult) {
|
||||||
this.currentConditionOutput = currentConditionResult.output;
|
this.currentConditionOutput = currentConditionResult.value;
|
||||||
},
|
},
|
||||||
updateDefaultOutput(output) {
|
updateDefaultOutput(output) {
|
||||||
this.currentConditionOutput = output;
|
this.currentConditionOutput = output;
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
:key="telemetryOption.identifier.key"
|
:key="telemetryOption.identifier.key"
|
||||||
:value="telemetryOption.identifier"
|
:value="telemetryOption.identifier"
|
||||||
>
|
>
|
||||||
{{ telemetryOption.name }}
|
{{ telemetryOption.path }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
:key="index"
|
:key="index"
|
||||||
:value="telemetryOption.identifier"
|
:value="telemetryOption.identifier"
|
||||||
>
|
>
|
||||||
{{ telemetryOption.name }}
|
{{ telemetryOption.path || telemetryOption.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
@ -147,7 +147,8 @@ export default {
|
|||||||
expanded: true,
|
expanded: true,
|
||||||
isApplied: false,
|
isApplied: false,
|
||||||
testInputs: [],
|
testInputs: [],
|
||||||
telemetryMetadataOptions: {}
|
telemetryMetadataOptions: {},
|
||||||
|
telemetryPaths: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -244,6 +245,22 @@ export default {
|
|||||||
applied: this.isApplied,
|
applied: this.isApplied,
|
||||||
conditionTestInputs: this.testInputs
|
conditionTestInputs: this.testInputs
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
async getFullTelemetryPath(telemetry) {
|
||||||
|
const keyStringForObject = this.openmct.objects.makeKeyString(telemetry.identifier);
|
||||||
|
const originalPathObjects = await this.openmct.objects.getOriginalPath(
|
||||||
|
keyStringForObject,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const telemetryPath = originalPathObjects.reverse().map((pathObject) => {
|
||||||
|
if (pathObject.type !== 'root') {
|
||||||
|
return pathObject.name;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
return telemetryPath.join('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
import ConditionInspectorViewProvider from './ConditionInspectorViewProvider.js';
|
||||||
import ConditionSetCompositionPolicy from './ConditionSetCompositionPolicy.js';
|
import ConditionSetCompositionPolicy from './ConditionSetCompositionPolicy.js';
|
||||||
import ConditionSetMetadataProvider from './ConditionSetMetadataProvider.js';
|
import ConditionSetMetadataProvider from './ConditionSetMetadataProvider.js';
|
||||||
import ConditionSetTelemetryProvider from './ConditionSetTelemetryProvider.js';
|
import ConditionSetTelemetryProvider from './ConditionSetTelemetryProvider.js';
|
||||||
@ -37,6 +38,7 @@ export default function ConditionPlugin() {
|
|||||||
cssClass: 'icon-conditional',
|
cssClass: 'icon-conditional',
|
||||||
initialize: function (domainObject) {
|
initialize: function (domainObject) {
|
||||||
domainObject.configuration = {
|
domainObject.configuration = {
|
||||||
|
shouldFetchHistorical: false,
|
||||||
conditionTestData: [],
|
conditionTestData: [],
|
||||||
conditionCollection: [
|
conditionCollection: [
|
||||||
{
|
{
|
||||||
@ -61,5 +63,6 @@ export default function ConditionPlugin() {
|
|||||||
openmct.telemetry.addProvider(new ConditionSetMetadataProvider(openmct));
|
openmct.telemetry.addProvider(new ConditionSetMetadataProvider(openmct));
|
||||||
openmct.telemetry.addProvider(new ConditionSetTelemetryProvider(openmct));
|
openmct.telemetry.addProvider(new ConditionSetTelemetryProvider(openmct));
|
||||||
openmct.objectViews.addProvider(new ConditionSetViewProvider(openmct));
|
openmct.objectViews.addProvider(new ConditionSetViewProvider(openmct));
|
||||||
|
openmct.inspectorViews.addProvider(new ConditionInspectorViewProvider(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -62,3 +62,5 @@ export const ERROR = {
|
|||||||
|
|
||||||
export const IS_OLD_KEY = 'isStale';
|
export const IS_OLD_KEY = 'isStale';
|
||||||
export const IS_STALE_KEY = 'isStale.new';
|
export const IS_STALE_KEY = 'isStale.new';
|
||||||
|
|
||||||
|
export const TELEMETRY_VALUE = 'telemetry value';
|
||||||
|
@ -123,7 +123,7 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.conditionalLabel = latestDatum.output || '';
|
this.conditionalLabel = latestDatum.value || '';
|
||||||
},
|
},
|
||||||
async showToolTip() {
|
async showToolTip() {
|
||||||
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
||||||
|
@ -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)
|
||||||
|
@ -243,13 +243,15 @@ export default {
|
|||||||
domainObject: {
|
domainObject: {
|
||||||
...this.childObject,
|
...this.childObject,
|
||||||
configuration: {
|
configuration: {
|
||||||
|
...this.childObject.configuration,
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
identifier: this.childObject.identifier,
|
identifier: this.childObject.identifier,
|
||||||
...persistedSeriesConfig.series
|
...persistedSeriesConfig.series
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
yAxis: persistedSeriesConfig.yAxis
|
yAxis: persistedSeriesConfig.yAxis,
|
||||||
|
...this.childObject.configuration
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openmct: this.openmct,
|
openmct: this.openmct,
|
||||||
|
@ -139,6 +139,7 @@ export default {
|
|||||||
styleObj.isStyleInvisible = null;
|
styleObj.isStyleInvisible = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
Object.entries(styleObj).forEach(([key, value]) => {
|
Object.entries(styleObj).forEach(([key, value]) => {
|
||||||
if (typeof value !== 'string' || !value.includes('__no_value')) {
|
if (typeof value !== 'string' || !value.includes('__no_value')) {
|
||||||
elemToStyle.style[key] = value;
|
elemToStyle.style[key] = value;
|
||||||
@ -146,6 +147,7 @@ export default {
|
|||||||
elemToStyle.style[key] = ''; // remove the property
|
elemToStyle.style[key] = ''; // remove the property
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -94,7 +94,7 @@ export function ticks(start, stop, count) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function commonPrefix(a, b) {
|
export function commonPrefix(a, b) {
|
||||||
const maxLen = Math.min(a.length, b.length);
|
const maxLen = Math.min(a.length, b?.length);
|
||||||
let breakpoint = 0;
|
let breakpoint = 0;
|
||||||
for (let i = 0; i < maxLen; i++) {
|
for (let i = 0; i < maxLen; i++) {
|
||||||
if (a[i] !== b[i]) {
|
if (a[i] !== b[i]) {
|
||||||
@ -110,7 +110,7 @@ export function commonPrefix(a, b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function commonSuffix(a, b) {
|
export function commonSuffix(a, b) {
|
||||||
const maxLen = Math.min(a.length, b.length);
|
const maxLen = Math.min(a.length, b?.length);
|
||||||
let breakpoint = 0;
|
let breakpoint = 0;
|
||||||
for (let i = 0; i <= maxLen; i++) {
|
for (let i = 0; i <= maxLen; i++) {
|
||||||
if (a[a.length - i] !== b[b.length - i]) {
|
if (a[a.length - i] !== b[b.length - i]) {
|
||||||
|
@ -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';
|
||||||
@ -178,5 +179,6 @@ plugins.Timelist = TimeList;
|
|||||||
plugins.InspectorViews = InspectorViews;
|
plugins.InspectorViews = InspectorViews;
|
||||||
plugins.InspectorDataVisualization = InspectorDataVisualization;
|
plugins.InspectorDataVisualization = InspectorDataVisualization;
|
||||||
plugins.EventTimestripPlugin = EventTimestripPlugin;
|
plugins.EventTimestripPlugin = EventTimestripPlugin;
|
||||||
|
plugins.Comps = CompsPlugin;
|
||||||
|
|
||||||
export default plugins;
|
export default plugins;
|
||||||
|
@ -353,7 +353,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");
|
||||||
|
@ -200,6 +200,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;
|
||||||
@ -314,6 +315,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.
|
||||||
@ -368,8 +370,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 {
|
||||||
@ -377,11 +380,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'] {
|
||||||
@ -405,6 +409,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;
|
||||||
}
|
}
|
||||||
@ -463,6 +468,11 @@ textarea {
|
|||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--md {
|
||||||
|
// Smallish inputs, like numerics or short text
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
&--autocomplete {
|
&--autocomplete {
|
||||||
&__wrapper {
|
&__wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -516,6 +526,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,8 +557,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;
|
||||||
@ -978,6 +988,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
|
||||||
}
|
}
|
||||||
@ -1095,6 +1106,7 @@ select {
|
|||||||
/******************************************************** SLIDERS */
|
/******************************************************** SLIDERS */
|
||||||
.c-slider {
|
.c-slider {
|
||||||
@include cControl();
|
@include cControl();
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-left: $interiorMargin;
|
margin-left: $interiorMargin;
|
||||||
}
|
}
|
||||||
@ -1137,10 +1149,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);
|
||||||
@ -1250,6 +1264,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>
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user