mirror of
https://github.com/nasa/openmct.git
synced 2025-06-28 03:42:21 +00:00
Compare commits
18 Commits
eval-sourc
...
release/2.
Author | SHA1 | Date | |
---|---|---|---|
e2863595d7 | |||
7cad3c01ff | |||
ae1b7520bf | |||
2a165a4549 | |||
6abd395605 | |||
610f78b6fb | |||
663f42ad2e | |||
f80a3c13c1 | |||
a94ec344ea | |||
e75befafbd | |||
d7d06b59ea | |||
8b4a55a7ec | |||
2519e601d7 | |||
b7b205621b | |||
3d2d932323 | |||
ce28dd2b9f | |||
286a533dad | |||
378a4ca282 |
@ -21,45 +21,163 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
|
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. Note: this
|
||||||
|
suite is sharing state between tests which is considered an anti-pattern. Implimenting in this way to
|
||||||
|
demonstrate some playwright for test developers. This pattern should not be re-used in other CRUD suites.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { test, expect } = require('@playwright/test');
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
test.describe('Condition Set Operations', () => {
|
let conditionSetUrl;
|
||||||
test('Create new button `condition set` creates new condition object', async ({ page }) => {
|
let getConditionSetIdentifierFromUrl;
|
||||||
//Go to baseURL
|
|
||||||
await page.goto('/', { waitUntil: 'networkidle' });
|
|
||||||
|
|
||||||
//Click the Create button
|
test('Create new Condition Set object and store @localStorage', async ({ page, context }) => {
|
||||||
await page.click('button:has-text("Create")');
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
// Click text=Condition Set
|
//Click the Create button
|
||||||
await page.click('text=Condition Set');
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
// Click text=OK
|
// Click text=Condition Set
|
||||||
|
await page.click('text=Condition Set');
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.click('text=OK')
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
||||||
|
//Save localStorage for future test execution
|
||||||
|
await context.storageState({ path: './e2e/tests/recycled_storage.json' });
|
||||||
|
|
||||||
|
//Set object identifier from url
|
||||||
|
conditionSetUrl = await page.url();
|
||||||
|
console.log('conditionSetUrl ' + conditionSetUrl);
|
||||||
|
|
||||||
|
getConditionSetIdentifierFromUrl = await conditionSetUrl.split('/').pop().split('?')[0];
|
||||||
|
console.log('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
||||||
|
//Load localStorage for subsequent tests
|
||||||
|
test.use({ storageState: './e2e/tests/recycled_storage.json' });
|
||||||
|
|
||||||
|
//Begin suite of tests again localStorage
|
||||||
|
test('Condition set object properties persist in main view and inspector', async ({ page }) => {
|
||||||
|
//Navigate to baseURL with injected localStorage
|
||||||
|
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
//Assertions on loaded Condition Set in main view
|
||||||
|
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
||||||
|
|
||||||
|
//Assertions on loaded Condition Set in Inspector
|
||||||
|
await expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy;
|
||||||
|
|
||||||
|
//Reload Page
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/dab945d4-5a84-480e-8180-222b4aa730fa?tc.mode=fixed&tc.startBound=1639696164435&tc.endBound=1639697964435&tc.timeSystem=utc&view=conditionSet.view' }*/),
|
page.reload(),
|
||||||
page.click('text=OK')
|
page.waitForLoadState('networkidle')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
//Re-verify after reload
|
||||||
|
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
||||||
|
//Assertions on loaded Condition Set in Inspector
|
||||||
|
await expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy;
|
||||||
|
|
||||||
});
|
});
|
||||||
test.fixme('condition set object properties exist', async ({ page }) => {
|
test('condition set object can be modified on @localStorage', async ({ page }) => {
|
||||||
//Go to object created in step one
|
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
|
||||||
//Verify the Condition Set properties persist on Save
|
|
||||||
//Verify the Condition Set properties persist on page.reload()
|
//Assertions on loaded Condition Set in main view
|
||||||
});
|
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
||||||
test.fixme('condition set object can be modified', async ({ page }) => {
|
|
||||||
//Go to object created in step one
|
|
||||||
//Update the Condition Set properties
|
//Update the Condition Set properties
|
||||||
//Verify the Condition Set properties persist on Save
|
// Click Edit Button
|
||||||
//Verify the Condition Set properties persist on page.reload()
|
await page.locator('text=Conditions View Snapshot >> button').nth(3).click();
|
||||||
|
|
||||||
|
//Edit Condition Set Name from main view
|
||||||
|
await page.locator('text=Unnamed Condition Set').first().fill('Renamed Condition Set');
|
||||||
|
await page.locator('text=Renamed Condition Set').first().press('Enter');
|
||||||
|
// Click Save Button
|
||||||
|
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||||
|
// Click Save and Finish Editing Option
|
||||||
|
await page.locator('text=Save and Finish Editing').click();
|
||||||
|
|
||||||
|
//Verify Main section reflects updated Name Property
|
||||||
|
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set');
|
||||||
|
|
||||||
|
// Verify Inspector properties
|
||||||
|
// Verify Inspector has updated Name property
|
||||||
|
await expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy();
|
||||||
|
// Verify Inspector Details has updated Name property
|
||||||
|
await expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy();
|
||||||
|
|
||||||
|
// Verify Tree reflects updated Name proprety
|
||||||
|
// Expand Tree
|
||||||
|
await page.locator('text=Open MCT My Items >> span >> nth=3').click();
|
||||||
|
// Verify Condition Set Object is renamed in Tree
|
||||||
|
await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
||||||
|
// Verify Search Tree reflects renamed Name property
|
||||||
|
await page.locator('input[type="search"]').fill('Renamed');
|
||||||
|
await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
||||||
|
|
||||||
|
//Reload Page
|
||||||
|
await Promise.all([
|
||||||
|
page.reload(),
|
||||||
|
page.waitForLoadState('networkidle')
|
||||||
|
]);
|
||||||
|
|
||||||
|
//Verify Main section reflects updated Name Property
|
||||||
|
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set');
|
||||||
|
|
||||||
|
// Verify Inspector properties
|
||||||
|
// Verify Inspector has updated Name property
|
||||||
|
await expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy();
|
||||||
|
// Verify Inspector Details has updated Name property
|
||||||
|
await expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy();
|
||||||
|
|
||||||
|
// Verify Tree reflects updated Name proprety
|
||||||
|
// Expand Tree
|
||||||
|
await page.locator('text=Open MCT My Items >> span >> nth=3').click();
|
||||||
|
// Verify Condition Set Object is renamed in Tree
|
||||||
|
await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
||||||
|
// Verify Search Tree reflects renamed Name property
|
||||||
|
await page.locator('input[type="search"]').fill('Renamed');
|
||||||
|
await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
||||||
});
|
});
|
||||||
test.fixme('condition set object can be deleted', async ({ page }) => {
|
test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => {
|
||||||
//Go to object created in step one
|
//Navigate to baseURL
|
||||||
//Verify that Condition Set object can be deleted
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
//Verify the Condition Set object does not exist in Tree
|
|
||||||
//Verify the Condition Set object does not exist with direct navigation to object's URL
|
//Expect Unnamed Condition Set to be visible in Main View
|
||||||
|
await expect(page.locator('a:has-text("Unnamed Condition Set Condition Set")')).toBeVisible();
|
||||||
|
|
||||||
|
// Search for Unnamed Condition Set
|
||||||
|
await page.locator('input[type="search"]').fill('Unnamed Condition Set');
|
||||||
|
// Right Click to Open Actions Menu
|
||||||
|
await page.locator('a:has-text("Unnamed Condition Set")').click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
|
// Click Remove Action
|
||||||
|
await page.locator('text=Remove').click();
|
||||||
|
|
||||||
|
await page.locator('text=OK').click();
|
||||||
|
|
||||||
|
//Expect Unnamed Condition Set to be removed in Main View
|
||||||
|
await expect(page.locator('a:has-text("Unnamed Condition Set Condition Set")')).not.toBeVisible();
|
||||||
|
|
||||||
|
await page.locator('.c-search__clear-input').click();
|
||||||
|
// Search for Unnamed Condition Set
|
||||||
|
await page.locator('input[type="search"]').fill('Unnamed Condition Set');
|
||||||
|
// Expect Unnamed Condition Set to be removed
|
||||||
|
await expect(page.locator('a:has-text("Unnamed Condition Set")')).not.toBeVisible();
|
||||||
|
|
||||||
|
//Feature?
|
||||||
|
//Domain Object is still available by direct URL after delete
|
||||||
|
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
|
||||||
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
22
e2e/tests/recycled_storage.json
Normal file
22
e2e/tests/recycled_storage.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"cookies": [],
|
||||||
|
"origins": [
|
||||||
|
{
|
||||||
|
"origin": "http://localhost:8080",
|
||||||
|
"localStorage": [
|
||||||
|
{
|
||||||
|
"name": "tcHistory",
|
||||||
|
"value": "{\"utc\":[{\"start\":1651513945533,\"end\":1651515745533}]}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mct",
|
||||||
|
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"c0f99e39-85e7-4ef7-99b1-ef52d4ed69b2\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1651515746374,\"modified\":1651515746374},\"c0f99e39-85e7-4ef7-99b1-ef52d4ed69b2\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"c0f99e39-85e7-4ef7-99b1-ef52d4ed69b2\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"e35a066b-eb0e-4b05-a4c9-cc31dc202572\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1651515746373,\"location\":\"mine\",\"persisted\":1651515746373}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mct-tree-expanded",
|
||||||
|
"value": "[]"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "2.0.3-SNAPSHOT",
|
"version": "2.0.4",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "7.16.3",
|
"@babel/eslint-parser": "7.16.3",
|
||||||
|
@ -241,8 +241,6 @@ define([
|
|||||||
this.branding = BrandingAPI.default;
|
this.branding = BrandingAPI.default;
|
||||||
|
|
||||||
// Plugins that are installed by default
|
// Plugins that are installed by default
|
||||||
|
|
||||||
this.install(this.plugins.Gauge());
|
|
||||||
this.install(this.plugins.Plot());
|
this.install(this.plugins.Plot());
|
||||||
this.install(this.plugins.Chart());
|
this.install(this.plugins.Chart());
|
||||||
this.install(this.plugins.TelemetryTable.default());
|
this.install(this.plugins.TelemetryTable.default());
|
||||||
|
@ -22,12 +22,14 @@
|
|||||||
|
|
||||||
export default class Transaction {
|
export default class Transaction {
|
||||||
constructor(objectAPI) {
|
constructor(objectAPI) {
|
||||||
this.dirtyObjects = new Set();
|
this.dirtyObjects = {};
|
||||||
this.objectAPI = objectAPI;
|
this.objectAPI = objectAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(object) {
|
add(object) {
|
||||||
this.dirtyObjects.add(object);
|
const key = this.objectAPI.makeKeyString(object.identifier);
|
||||||
|
|
||||||
|
this.dirtyObjects[key] = object;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
@ -37,7 +39,8 @@ export default class Transaction {
|
|||||||
commit() {
|
commit() {
|
||||||
const promiseArray = [];
|
const promiseArray = [];
|
||||||
const save = this.objectAPI.save.bind(this.objectAPI);
|
const save = this.objectAPI.save.bind(this.objectAPI);
|
||||||
this.dirtyObjects.forEach(object => {
|
|
||||||
|
Object.values(this.dirtyObjects).forEach(object => {
|
||||||
promiseArray.push(this.createDirtyObjectPromise(object, save));
|
promiseArray.push(this.createDirtyObjectPromise(object, save));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,7 +51,9 @@ export default class Transaction {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
action(object)
|
action(object)
|
||||||
.then((success) => {
|
.then((success) => {
|
||||||
this.dirtyObjects.delete(object);
|
const key = this.objectAPI.makeKeyString(object.identifier);
|
||||||
|
|
||||||
|
delete this.dirtyObjects[key];
|
||||||
resolve(success);
|
resolve(success);
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
@ -57,7 +62,8 @@ export default class Transaction {
|
|||||||
|
|
||||||
getDirtyObject(identifier) {
|
getDirtyObject(identifier) {
|
||||||
let dirtyObject;
|
let dirtyObject;
|
||||||
this.dirtyObjects.forEach(object => {
|
|
||||||
|
Object.values(this.dirtyObjects).forEach(object => {
|
||||||
const areIdsEqual = this.objectAPI.areIdsEqual(object.identifier, identifier);
|
const areIdsEqual = this.objectAPI.areIdsEqual(object.identifier, identifier);
|
||||||
if (areIdsEqual) {
|
if (areIdsEqual) {
|
||||||
dirtyObject = object;
|
dirtyObject = object;
|
||||||
@ -67,14 +73,11 @@ export default class Transaction {
|
|||||||
return dirtyObject;
|
return dirtyObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
|
||||||
this.dirtyObjects = new Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
_clear() {
|
_clear() {
|
||||||
const promiseArray = [];
|
const promiseArray = [];
|
||||||
const refresh = this.objectAPI.refresh.bind(this.objectAPI);
|
const refresh = this.objectAPI.refresh.bind(this.objectAPI);
|
||||||
this.dirtyObjects.forEach(object => {
|
|
||||||
|
Object.values(this.dirtyObjects).forEach(object => {
|
||||||
promiseArray.push(this.createDirtyObjectPromise(object, refresh));
|
promiseArray.push(this.createDirtyObjectPromise(object, refresh));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,24 +34,24 @@ describe("Transaction Class", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('has no dirty objects', () => {
|
it('has no dirty objects', () => {
|
||||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add(), adds object to dirtyObjects', () => {
|
it('add(), adds object to dirtyObjects', () => {
|
||||||
const mockDomainObjects = createMockDomainObjects();
|
const mockDomainObjects = createMockDomainObjects();
|
||||||
transaction.add(mockDomainObjects[0]);
|
transaction.add(mockDomainObjects[0]);
|
||||||
expect(transaction.dirtyObjects.size).toEqual(1);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cancel(), clears all dirtyObjects', (done) => {
|
it('cancel(), clears all dirtyObjects', (done) => {
|
||||||
const mockDomainObjects = createMockDomainObjects(3);
|
const mockDomainObjects = createMockDomainObjects(3);
|
||||||
mockDomainObjects.forEach(transaction.add.bind(transaction));
|
mockDomainObjects.forEach(transaction.add.bind(transaction));
|
||||||
|
|
||||||
expect(transaction.dirtyObjects.size).toEqual(3);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(3);
|
||||||
|
|
||||||
transaction.cancel()
|
transaction.cancel()
|
||||||
.then(success => {
|
.then(success => {
|
||||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||||
}).finally(done);
|
}).finally(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -59,12 +59,12 @@ describe("Transaction Class", () => {
|
|||||||
const mockDomainObjects = createMockDomainObjects(3);
|
const mockDomainObjects = createMockDomainObjects(3);
|
||||||
mockDomainObjects.forEach(transaction.add.bind(transaction));
|
mockDomainObjects.forEach(transaction.add.bind(transaction));
|
||||||
|
|
||||||
expect(transaction.dirtyObjects.size).toEqual(3);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(3);
|
||||||
spyOn(objectAPI, 'save').and.callThrough();
|
spyOn(objectAPI, 'save').and.callThrough();
|
||||||
|
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
.then(success => {
|
.then(success => {
|
||||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||||
expect(objectAPI.save.calls.count()).toEqual(3);
|
expect(objectAPI.save.calls.count()).toEqual(3);
|
||||||
}).finally(done);
|
}).finally(done);
|
||||||
});
|
});
|
||||||
@ -73,7 +73,7 @@ describe("Transaction Class", () => {
|
|||||||
const mockDomainObjects = createMockDomainObjects();
|
const mockDomainObjects = createMockDomainObjects();
|
||||||
transaction.add(mockDomainObjects[0]);
|
transaction.add(mockDomainObjects[0]);
|
||||||
|
|
||||||
expect(transaction.dirtyObjects.size).toEqual(1);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(1);
|
||||||
const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier);
|
const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier);
|
||||||
|
|
||||||
expect(dirtyObject).toEqual(mockDomainObjects[0]);
|
expect(dirtyObject).toEqual(mockDomainObjects[0]);
|
||||||
@ -82,7 +82,7 @@ describe("Transaction Class", () => {
|
|||||||
it('getDirtyObject(), returns empty dirtyObject for no active transaction', () => {
|
it('getDirtyObject(), returns empty dirtyObject for no active transaction', () => {
|
||||||
const mockDomainObjects = createMockDomainObjects();
|
const mockDomainObjects = createMockDomainObjects();
|
||||||
|
|
||||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||||
const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier);
|
const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier);
|
||||||
|
|
||||||
expect(dirtyObject).toEqual(undefined);
|
expect(dirtyObject).toEqual(undefined);
|
||||||
|
@ -185,8 +185,8 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
|
|
||||||
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 < this.lastBounds.start;
|
||||||
afterEndOfBounds = parsedValue >= this.lastBounds.end;
|
afterEndOfBounds = parsedValue > this.lastBounds.end;
|
||||||
|
|
||||||
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
||||||
let isDuplicate = false;
|
let isDuplicate = false;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
@ -114,14 +113,12 @@ export default {
|
|||||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
this.bounds = this.openmct.time.bounds();
|
|
||||||
|
|
||||||
this.limitEvaluator = this.openmct
|
this.limitEvaluator = this.openmct
|
||||||
.telemetry
|
.telemetry
|
||||||
.limitEvaluator(this.domainObject);
|
.limitEvaluator(this.domainObject);
|
||||||
|
|
||||||
this.openmct.time.on('timeSystem', this.updateTimeSystem);
|
this.openmct.time.on('timeSystem', this.updateTimeSystem);
|
||||||
this.openmct.time.on('bounds', this.updateBounds);
|
|
||||||
|
|
||||||
this.timestampKey = this.openmct.time.timeSystem().key;
|
this.timestampKey = this.openmct.time.timeSystem().key;
|
||||||
|
|
||||||
@ -135,72 +132,41 @@ export default {
|
|||||||
|
|
||||||
this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined;
|
this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined;
|
||||||
|
|
||||||
this.unsubscribe = this.openmct
|
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {
|
||||||
.telemetry
|
size: 1,
|
||||||
.subscribe(this.domainObject, this.setLatestValues);
|
strategy: 'latest'
|
||||||
|
});
|
||||||
this.requestHistory();
|
this.telemetryCollection.on('add', this.setLatestValues);
|
||||||
|
this.telemetryCollection.on('clear', this.resetValues);
|
||||||
|
this.telemetryCollection.load();
|
||||||
|
|
||||||
if (this.hasUnits) {
|
if (this.hasUnits) {
|
||||||
this.setUnit();
|
this.setUnit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.unsubscribe();
|
|
||||||
this.openmct.time.off('timeSystem', this.updateTimeSystem);
|
this.openmct.time.off('timeSystem', this.updateTimeSystem);
|
||||||
this.openmct.time.off('bounds', this.updateBounds);
|
this.telemetryCollection.off('add', this.setLatestValues);
|
||||||
|
this.telemetryCollection.off('clear', this.resetValues);
|
||||||
|
|
||||||
|
this.telemetryCollection.destroy();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateView() {
|
updateView() {
|
||||||
if (!this.updatingView) {
|
if (!this.updatingView) {
|
||||||
this.updatingView = true;
|
this.updatingView = true;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
let newTimestamp = this.getParsedTimestamp(this.latestDatum);
|
this.timestamp = this.getParsedTimestamp(this.latestDatum);
|
||||||
|
this.datum = this.latestDatum;
|
||||||
if (this.shouldUpdate(newTimestamp)) {
|
|
||||||
this.timestamp = newTimestamp;
|
|
||||||
this.datum = this.latestDatum;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updatingView = false;
|
this.updatingView = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setLatestValues(datum) {
|
setLatestValues(data) {
|
||||||
this.latestDatum = datum;
|
this.latestDatum = data[data.length - 1];
|
||||||
|
|
||||||
this.updateView();
|
this.updateView();
|
||||||
},
|
},
|
||||||
shouldUpdate(newTimestamp) {
|
|
||||||
return this.inBounds(newTimestamp)
|
|
||||||
&& (this.timestamp === undefined || newTimestamp > this.timestamp);
|
|
||||||
},
|
|
||||||
requestHistory() {
|
|
||||||
this.openmct
|
|
||||||
.telemetry
|
|
||||||
.request(this.domainObject, {
|
|
||||||
start: this.bounds.start,
|
|
||||||
end: this.bounds.end,
|
|
||||||
size: 1,
|
|
||||||
strategy: 'latest'
|
|
||||||
})
|
|
||||||
.then((array) => this.setLatestValues(array[array.length - 1]))
|
|
||||||
.catch((error) => {
|
|
||||||
console.warn('Error fetching data', error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
updateBounds(bounds, isTick) {
|
|
||||||
this.bounds = bounds;
|
|
||||||
if (!isTick) {
|
|
||||||
this.resetValues();
|
|
||||||
this.requestHistory();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inBounds(timestamp) {
|
|
||||||
return timestamp >= this.bounds.start && timestamp <= this.bounds.end;
|
|
||||||
},
|
|
||||||
updateTimeSystem(timeSystem) {
|
updateTimeSystem(timeSystem) {
|
||||||
this.resetValues();
|
|
||||||
this.timestampKey = timeSystem.key;
|
this.timestampKey = timeSystem.key;
|
||||||
},
|
},
|
||||||
updateViewContext() {
|
updateViewContext() {
|
||||||
@ -241,4 +207,3 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ describe("The LAD Table", () => {
|
|||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
let ladPlugin;
|
let ladPlugin;
|
||||||
|
let historicalProvider;
|
||||||
let parent;
|
let parent;
|
||||||
let child;
|
let child;
|
||||||
let telemetryCount = 3;
|
let telemetryCount = 3;
|
||||||
@ -81,6 +82,13 @@ describe("The LAD Table", () => {
|
|||||||
|
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
|
historicalProvider = {
|
||||||
|
request: () => {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider);
|
||||||
|
|
||||||
openmct.time.bounds({
|
openmct.time.bounds({
|
||||||
start: bounds.start,
|
start: bounds.start,
|
||||||
end: bounds.end
|
end: bounds.end
|
||||||
@ -147,7 +155,7 @@ describe("The LAD Table", () => {
|
|||||||
// add another telemetry object as composition in lad table to test multi rows
|
// add another telemetry object as composition in lad table to test multi rows
|
||||||
mockObj.ladTable.composition.push(anotherTelemetryObj.identifier);
|
mockObj.ladTable.composition.push(anotherTelemetryObj.identifier);
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async (done) => {
|
||||||
let telemetryRequestResolve;
|
let telemetryRequestResolve;
|
||||||
let telemetryObjectResolve;
|
let telemetryObjectResolve;
|
||||||
let anotherTelemetryObjectResolve;
|
let anotherTelemetryObjectResolve;
|
||||||
@ -166,11 +174,12 @@ describe("The LAD Table", () => {
|
|||||||
callBack();
|
callBack();
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.telemetry.request.and.callFake(() => {
|
historicalProvider.request = () => {
|
||||||
telemetryRequestResolve(mockTelemetry);
|
telemetryRequestResolve(mockTelemetry);
|
||||||
|
|
||||||
return telemetryRequestPromise;
|
return telemetryRequestPromise;
|
||||||
});
|
};
|
||||||
|
|
||||||
openmct.objects.get.and.callFake((obj) => {
|
openmct.objects.get.and.callFake((obj) => {
|
||||||
if (obj.key === 'telemetry-object') {
|
if (obj.key === 'telemetry-object') {
|
||||||
telemetryObjectResolve(mockObj.telemetry);
|
telemetryObjectResolve(mockObj.telemetry);
|
||||||
@ -195,6 +204,8 @@ describe("The LAD Table", () => {
|
|||||||
|
|
||||||
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
|
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
|
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show one row per object in the composition", () => {
|
it("should show one row per object in the composition", () => {
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
:href="url"
|
:href="url"
|
||||||
>
|
>
|
||||||
<div class="c-condition-widget__label">
|
<div class="c-condition-widget__label">
|
||||||
{{ internalDomainObject.conditionalLabel || internalDomainObject.label }}
|
{{ label }}
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
@ -39,28 +39,112 @@ export default {
|
|||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
internalDomainObject: this.domainObject
|
conditionalLabel: '',
|
||||||
|
conditionSetIdentifier: null,
|
||||||
|
domainObjectLabel: '',
|
||||||
|
url: null,
|
||||||
|
urlDefined: false,
|
||||||
|
useConditionSetOutputAsLabel: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
urlDefined() {
|
label() {
|
||||||
return this.internalDomainObject.url && this.internalDomainObject.url.length > 0;
|
return this.useConditionSetOutputAsLabel
|
||||||
},
|
? this.conditionalLabel
|
||||||
url() {
|
: this.domainObjectLabel
|
||||||
return this.urlDefined ? sanitizeUrl(this.internalDomainObject.url) : null;
|
;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
conditionSetIdentifier: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
if (!oldValue || !newValue || !this.openmct.objects.areIdsEqual(newValue, oldValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listenToConditionSetChanges();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
|
||||||
|
|
||||||
|
if (this.domainObject) {
|
||||||
|
this.updateDomainObject(this.domainObject);
|
||||||
|
this.listenToConditionSetChanges();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
this.conditionSetIdentifier = null;
|
||||||
|
|
||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.stopListeningToConditionSetChanges();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateInternalDomainObject(domainObject) {
|
async listenToConditionSetChanges() {
|
||||||
this.internalDomainObject = domainObject;
|
if (!this.conditionSetIdentifier) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionSetDomainObject = await this.openmct.objects.get(this.conditionSetIdentifier);
|
||||||
|
this.stopListeningToConditionSetChanges();
|
||||||
|
|
||||||
|
if (!conditionSetDomainObject) {
|
||||||
|
this.openmct.notifications.alert('Unable to find condition set');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.telemetryCollection = this.openmct.telemetry.requestCollection(conditionSetDomainObject, {
|
||||||
|
size: 1,
|
||||||
|
strategy: 'latest'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.telemetryCollection.on('add', this.updateConditionLabel, this);
|
||||||
|
this.telemetryCollection.load();
|
||||||
|
},
|
||||||
|
stopListeningToConditionSetChanges() {
|
||||||
|
if (this.telemetryCollection) {
|
||||||
|
this.telemetryCollection.off('add', this.updateConditionLabel, this);
|
||||||
|
this.telemetryCollection.destroy();
|
||||||
|
this.telemetryCollection = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateConditionLabel([latestDatum]) {
|
||||||
|
if (!this.conditionSetIdentifier) {
|
||||||
|
this.stopListeningToConditionSetChanges();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.conditionalLabel = latestDatum.output || '';
|
||||||
|
},
|
||||||
|
updateDomainObject(domainObject) {
|
||||||
|
if (this.domainObjectLabel !== domainObject.label) {
|
||||||
|
this.domainObjectLabel = domainObject.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlDefined = domainObject.url && domainObject.url.length > 0;
|
||||||
|
if (this.urlDefined !== urlDefined) {
|
||||||
|
this.urlDefined = urlDefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = this.urlDefined ? sanitizeUrl(domainObject.url) : null;
|
||||||
|
if (this.url !== url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionSetIdentifier = domainObject.configuration.objectStyles.conditionSetIdentifier;
|
||||||
|
if (this.conditionSetIdentifier !== conditionSetIdentifier) {
|
||||||
|
this.conditionSetIdentifier = conditionSetIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useConditionSetOutputAsLabel = this.conditionSetIdentifier && domainObject.configuration.useConditionSetOutputAsLabel;
|
||||||
|
if (this.useConditionSetOutputAsLabel !== useConditionSetOutputAsLabel) {
|
||||||
|
this.useConditionSetOutputAsLabel = useConditionSetOutputAsLabel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -222,20 +222,20 @@ export default {
|
|||||||
.then(this.setObject);
|
.then(this.setObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.time.on("bounds", this.refreshData);
|
|
||||||
|
|
||||||
this.status = this.openmct.status.get(this.item.identifier);
|
this.status = this.openmct.status.get(this.item.identifier);
|
||||||
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
|
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.removeSubscription();
|
|
||||||
this.removeStatusListener();
|
this.removeStatusListener();
|
||||||
|
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.time.off("bounds", this.refreshData);
|
this.telemetryCollection.off('add', this.setLatestValues);
|
||||||
|
this.telemetryCollection.off('clear', this.refreshData);
|
||||||
|
|
||||||
|
this.telemetryCollection.destroy();
|
||||||
|
|
||||||
if (this.mutablePromise) {
|
if (this.mutablePromise) {
|
||||||
this.mutablePromise.then(() => {
|
this.mutablePromise.then(() => {
|
||||||
@ -253,34 +253,9 @@ export default {
|
|||||||
|
|
||||||
return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue}${unit}`;
|
return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue}${unit}`;
|
||||||
},
|
},
|
||||||
requestHistoricalData() {
|
setLatestValues(data) {
|
||||||
let bounds = this.openmct.time.bounds();
|
this.latestDatum = data[data.length - 1];
|
||||||
let options = {
|
this.updateView();
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
size: 1,
|
|
||||||
strategy: 'latest'
|
|
||||||
};
|
|
||||||
this.openmct.telemetry.request(this.domainObject, options)
|
|
||||||
.then(data => {
|
|
||||||
if (data.length > 0) {
|
|
||||||
this.latestDatum = data[data.length - 1];
|
|
||||||
this.updateView();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
subscribeToObject() {
|
|
||||||
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
|
|
||||||
const key = this.openmct.time.timeSystem().key;
|
|
||||||
const datumTimeStamp = datum[key];
|
|
||||||
if (this.openmct.time.clock() !== undefined
|
|
||||||
|| (datumTimeStamp
|
|
||||||
&& (this.openmct.time.bounds().end >= datumTimeStamp))
|
|
||||||
) {
|
|
||||||
this.latestDatum = datum;
|
|
||||||
this.updateView();
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
},
|
},
|
||||||
updateView() {
|
updateView() {
|
||||||
if (!this.updatingView) {
|
if (!this.updatingView) {
|
||||||
@ -291,17 +266,10 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeSubscription() {
|
|
||||||
if (this.subscription) {
|
|
||||||
this.subscription();
|
|
||||||
this.subscription = undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
refreshData(bounds, isTick) {
|
refreshData(bounds, isTick) {
|
||||||
if (!isTick) {
|
if (!isTick) {
|
||||||
this.latestDatum = undefined;
|
this.latestDatum = undefined;
|
||||||
this.updateView();
|
this.updateView();
|
||||||
this.requestHistoricalData(this.domainObject);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setObject(domainObject) {
|
setObject(domainObject) {
|
||||||
@ -315,8 +283,13 @@ export default {
|
|||||||
const valueMetadata = this.metadata.value(this.item.value);
|
const valueMetadata = this.metadata.value(this.item.value);
|
||||||
this.customStringformatter = this.openmct.telemetry.customStringFormatter(valueMetadata, this.item.format);
|
this.customStringformatter = this.openmct.telemetry.customStringFormatter(valueMetadata, this.item.format);
|
||||||
|
|
||||||
this.requestHistoricalData();
|
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {
|
||||||
this.subscribeToObject();
|
size: 1,
|
||||||
|
strategy: 'latest'
|
||||||
|
});
|
||||||
|
this.telemetryCollection.on('add', this.setLatestValues);
|
||||||
|
this.telemetryCollection.on('clear', this.refreshData);
|
||||||
|
this.telemetryCollection.load();
|
||||||
|
|
||||||
this.currentObjectPath = this.objectPath.slice();
|
this.currentObjectPath = this.objectPath.slice();
|
||||||
this.currentObjectPath.unshift(this.domainObject);
|
this.currentObjectPath.unshift(this.domainObject);
|
||||||
|
@ -53,6 +53,8 @@ describe('Gauge plugin', () => {
|
|||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
|
|
||||||
|
openmct.install(openmct.plugins.Gauge());
|
||||||
|
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -190,28 +192,27 @@ describe('Gauge plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders gauge element', () => {
|
it('renders gauge element', () => {
|
||||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||||
expect(gaugeElement.length).toBe(1);
|
expect(gaugeElement.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders major elements', () => {
|
it('renders major elements', () => {
|
||||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
const rangeElement = gaugeHolder.querySelector('.js-gauge-dial-range');
|
||||||
const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
|
const valueElement = gaugeHolder.querySelector('.js-dial-current-value');
|
||||||
const dialElement = gaugeHolder.querySelector('.c-dial');
|
|
||||||
|
|
||||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||||
|
|
||||||
expect(hasMajorElements).toBe(true);
|
expect(hasMajorElements).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correct min max values', () => {
|
it('renders correct min max values', () => {
|
||||||
expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${minValue} ${maxValue}`);
|
expect(gaugeHolder.querySelector('.js-gauge-dial-range').textContent).toEqual(`${minValue} ${maxValue}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correct current value', (done) => {
|
it('renders correct current value', (done) => {
|
||||||
function WatchUpdateValue() {
|
function WatchUpdateValue() {
|
||||||
const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
|
const textElement = gaugeHolder.querySelector('.js-dial-current-value');
|
||||||
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -326,28 +327,27 @@ describe('Gauge plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders gauge element', () => {
|
it('renders gauge element', () => {
|
||||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||||
expect(gaugeElement.length).toBe(1);
|
expect(gaugeElement.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders major elements', () => {
|
it('renders major elements', () => {
|
||||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
const rangeElement = gaugeHolder.querySelector('.js-gauge-dial-range');
|
||||||
const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
|
const valueElement = gaugeHolder.querySelector('.js-dial-current-value');
|
||||||
const dialElement = gaugeHolder.querySelector('.c-dial');
|
|
||||||
|
|
||||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||||
|
|
||||||
expect(hasMajorElements).toBe(true);
|
expect(hasMajorElements).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correct min max values', () => {
|
it('renders correct min max values', () => {
|
||||||
expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${minValue} ${maxValue}`);
|
expect(gaugeHolder.querySelector('.js-gauge-dial-range').textContent).toEqual(`${minValue} ${maxValue}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correct current value', (done) => {
|
it('renders correct current value', (done) => {
|
||||||
function WatchUpdateValue() {
|
function WatchUpdateValue() {
|
||||||
const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
|
const textElement = gaugeHolder.querySelector('.js-dial-current-value');
|
||||||
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -462,28 +462,27 @@ describe('Gauge plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders gauge element', () => {
|
it('renders gauge element', () => {
|
||||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||||
expect(gaugeElement.length).toBe(1);
|
expect(gaugeElement.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders major elements', () => {
|
it('renders major elements', () => {
|
||||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range');
|
||||||
const curveElement = gaugeHolder.querySelector('.c-meter');
|
const valueElement = gaugeHolder.querySelector('.js-meter-current-value');
|
||||||
const dialElement = gaugeHolder.querySelector('.c-meter__bg');
|
|
||||||
|
|
||||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||||
|
|
||||||
expect(hasMajorElements).toBe(true);
|
expect(hasMajorElements).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correct min max values', () => {
|
it('renders correct min max values', () => {
|
||||||
expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${maxValue} ${minValue}`);
|
expect(gaugeHolder.querySelector('.js-gauge-meter-range').textContent).toEqual(`${maxValue} ${minValue}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correct current value', (done) => {
|
it('renders correct current value', (done) => {
|
||||||
function WatchUpdateValue() {
|
function WatchUpdateValue() {
|
||||||
const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
|
const textElement = gaugeHolder.querySelector('.js-meter-current-value');
|
||||||
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
@ -560,17 +559,16 @@ describe('Gauge plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders gauge element', () => {
|
it('renders gauge element', () => {
|
||||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||||
expect(gaugeElement.length).toBe(1);
|
expect(gaugeElement.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders major elements', () => {
|
it('renders major elements', () => {
|
||||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range');
|
||||||
const curveElement = gaugeHolder.querySelector('.c-meter');
|
const valueElement = gaugeHolder.querySelector('.js-meter-current-value');
|
||||||
const dialElement = gaugeHolder.querySelector('.c-meter__bg');
|
|
||||||
|
|
||||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||||
|
|
||||||
expect(hasMajorElements).toBe(true);
|
expect(hasMajorElements).toBe(true);
|
||||||
});
|
});
|
||||||
@ -643,17 +641,16 @@ describe('Gauge plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders gauge element', () => {
|
it('renders gauge element', () => {
|
||||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||||
expect(gaugeElement.length).toBe(1);
|
expect(gaugeElement.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders major elements', () => {
|
it('renders major elements', () => {
|
||||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||||
const curveElement = gaugeHolder.querySelector('.c-meter');
|
const curveElement = gaugeHolder.querySelector('.c-meter');
|
||||||
const dialElement = gaugeHolder.querySelector('.c-meter__bg');
|
|
||||||
|
|
||||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement);
|
||||||
|
|
||||||
expect(hasMajorElements).toBe(true);
|
expect(hasMajorElements).toBe(true);
|
||||||
});
|
});
|
||||||
@ -772,28 +769,27 @@ describe('Gauge plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders gauge element', () => {
|
it('renders gauge element', () => {
|
||||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||||
expect(gaugeElement.length).toBe(1);
|
expect(gaugeElement.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders major elements', () => {
|
it('renders major elements', () => {
|
||||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
const rangeElement = gaugeHolder.querySelector('.js-gauge-dial-range');
|
||||||
const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
|
const valueElement = gaugeHolder.querySelector('.js-dial-current-value');
|
||||||
const dialElement = gaugeHolder.querySelector('.c-dial');
|
|
||||||
|
|
||||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||||
|
|
||||||
expect(hasMajorElements).toBe(true);
|
expect(hasMajorElements).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correct min max values', () => {
|
it('renders correct min max values', () => {
|
||||||
expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${gaugeViewObject.configuration.gaugeController.min} ${gaugeViewObject.configuration.gaugeController.max}`);
|
expect(gaugeHolder.querySelector('.js-gauge-dial-range').textContent).toEqual(`${gaugeViewObject.configuration.gaugeController.min} ${gaugeViewObject.configuration.gaugeController.max}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correct current value', (done) => {
|
it('renders correct current value', (done) => {
|
||||||
function WatchUpdateValue() {
|
function WatchUpdateValue() {
|
||||||
const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
|
const textElement = gaugeHolder.querySelector('.js-dial-current-value');
|
||||||
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,9 @@ export default function GaugeViewProvider(openmct) {
|
|||||||
return domainObject.type === 'gauge';
|
return domainObject.type === 'gauge';
|
||||||
},
|
},
|
||||||
canEdit: function (domainObject) {
|
canEdit: function (domainObject) {
|
||||||
return false;
|
if (domainObject.type === 'gauge') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
view: function (domainObject) {
|
view: function (domainObject) {
|
||||||
let component;
|
let component;
|
||||||
|
@ -21,168 +21,259 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="c-gauge"
|
class="c-gauge__wrapper js-gauge-wrapper"
|
||||||
:class="`c-gauge--${gaugeType}`"
|
:class="`c-gauge--${gaugeType}`"
|
||||||
>
|
>
|
||||||
<div class="c-gauge__wrapper">
|
<template v-if="typeDial">
|
||||||
<template v-if="typeDial">
|
<svg
|
||||||
<svg
|
width="0"
|
||||||
class="c-gauge__range"
|
height="0"
|
||||||
viewBox="0 0 512 512"
|
class="c-dial__clip-paths"
|
||||||
>
|
>
|
||||||
<text
|
<defs>
|
||||||
v-if="displayMinMax"
|
<clipPath
|
||||||
font-size="35"
|
id="gaugeBgMask"
|
||||||
transform="translate(105 455) rotate(-45)"
|
clipPathUnits="objectBoundingBox"
|
||||||
>{{ rangeLow }}</text>
|
>
|
||||||
<text
|
<path d="M0.853553 0.853553C0.944036 0.763071 1 0.638071 1 0.5C1 0.223858 0.776142 0 0.5 0C0.223858 0 0 0.223858 0 0.5C0 0.638071 0.0559644 0.763071 0.146447 0.853553L0.285934 0.714066C0.23115 0.659281 0.197266 0.583598 0.197266 0.5C0.197266 0.332804 0.332804 0.197266 0.5 0.197266C0.667196 0.197266 0.802734 0.332804 0.802734 0.5C0.802734 0.583598 0.76885 0.659281 0.714066 0.714066L0.853553 0.853553Z" />
|
||||||
v-if="displayMinMax"
|
</clipPath>
|
||||||
font-size="35"
|
<clipPath
|
||||||
transform="translate(407 455) rotate(45)"
|
id="gaugeValueMask"
|
||||||
text-anchor="end"
|
clipPathUnits="objectBoundingBox"
|
||||||
>{{ rangeHigh }}</text>
|
>
|
||||||
</svg>
|
<path d="M0.18926 0.81074C0.109735 0.731215 0.0605469 0.621351 0.0605469 0.5C0.0605469 0.257298 0.257298 0.0605469 0.5 0.0605469C0.742702 0.0605469 0.939453 0.257298 0.939453 0.5C0.939453 0.621351 0.890265 0.731215 0.81074 0.81074L0.714066 0.714066C0.76885 0.659281 0.802734 0.583599 0.802734 0.5C0.802734 0.332804 0.667196 0.197266 0.5 0.197266C0.332804 0.197266 0.197266 0.332804 0.197266 0.5C0.197266 0.583599 0.23115 0.659281 0.285934 0.714066L0.18926 0.81074Z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class="c-dial__range c-gauge__range js-gauge-dial-range"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
v-if="displayMinMax"
|
||||||
|
font-size="35"
|
||||||
|
transform="translate(105 455) rotate(-45)"
|
||||||
|
>{{ rangeLow }}</text>
|
||||||
|
<text
|
||||||
|
v-if="displayMinMax"
|
||||||
|
font-size="35"
|
||||||
|
transform="translate(407 455) rotate(45)"
|
||||||
|
text-anchor="end"
|
||||||
|
>{{ rangeHigh }}</text>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class="c-dial__current-value-text-wrapper"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
v-if="displayCurVal"
|
v-if="displayCurVal"
|
||||||
class="c-gauge__curval"
|
class="c-dial__current-value-text-sizer"
|
||||||
:viewBox="curValViewBox"
|
:viewBox="curValViewBox"
|
||||||
>
|
>
|
||||||
<text
|
<text
|
||||||
class="c-gauge__curval-text"
|
class="c-dial__current-value-text js-dial-current-value"
|
||||||
lengthAdjust="spacing"
|
lengthAdjust="spacing"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
|
style="transform: translate(50%, 70%)"
|
||||||
>{{ curVal }}</text>
|
>{{ curVal }}</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
</svg>
|
||||||
|
|
||||||
<div class="c-dial">
|
<svg
|
||||||
<svg
|
class="c-dial__bg"
|
||||||
class="c-dial__bg"
|
viewBox="0 0 10 10"
|
||||||
viewBox="0 0 512 512"
|
>
|
||||||
>
|
|
||||||
<path d="M256,0C114.6,0,0,114.6,0,256S114.6,512,256,512,512,397.4,512,256,397.4,0,256,0Zm0,412A156,156,0,1,1,412,256,155.9,155.9,0,0,1,256,412Z" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<svg
|
<g
|
||||||
v-if="limitHigh && dialHighLimitDeg < 270"
|
v-if="limitLow !== null && dialLowLimitDeg < getLimitDegree('low', 'max')"
|
||||||
class="c-dial__limit-high"
|
class="c-dial__limit-low"
|
||||||
viewBox="0 0 512 512"
|
:style="`transform: rotate(${dialLowLimitDeg}deg)`"
|
||||||
:class="{
|
>
|
||||||
'c-high-limit-clip--90': dialHighLimitDeg > 90,
|
<rect
|
||||||
'c-high-limit-clip--180': dialHighLimitDeg >= 180
|
v-if="dialLowLimitDeg >= getLimitDegree('low', 'q1')"
|
||||||
}"
|
class="c-dial__low-limit__low"
|
||||||
>
|
x="5"
|
||||||
<path
|
y="5"
|
||||||
d="M100,256A156,156,0,1,1,366.3,366.3L437,437a255.2,255.2,0,0,0,75-181C512,114.6,397.4,0,256,0S0,114.6,0,256A255.2,255.2,0,0,0,75,437l70.7-70.7A155.5,155.5,0,0,1,100,256Z"
|
width="5"
|
||||||
:style="`transform: rotate(${dialHighLimitDeg}deg)`"
|
height="5"
|
||||||
/>
|
/>
|
||||||
</svg>
|
<rect
|
||||||
|
v-if="dialLowLimitDeg >= getLimitDegree('low', 'q2')"
|
||||||
|
class="c-dial__low-limit__mid"
|
||||||
|
x="5"
|
||||||
|
y="0"
|
||||||
|
width="5"
|
||||||
|
height="5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
v-if="dialLowLimitDeg >= getLimitDegree('low', 'q3')"
|
||||||
|
class="c-dial__low-limit__high"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="5"
|
||||||
|
height="5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
|
||||||
<svg
|
<g
|
||||||
v-if="limitLow && dialLowLimitDeg < 270"
|
v-if="limitHigh !== null && dialHighLimitDeg < getLimitDegree('high', 'max')"
|
||||||
class="c-dial__limit-low"
|
class="c-dial__limit-high"
|
||||||
viewBox="0 0 512 512"
|
:style="`transform: rotate(${dialHighLimitDeg}deg)`"
|
||||||
:class="{
|
>
|
||||||
'c-dial-clip--90': dialLowLimitDeg < 90,
|
<rect
|
||||||
'c-dial-clip--180': dialLowLimitDeg >= 90 && dialLowLimitDeg < 180
|
v-if="dialHighLimitDeg <= getLimitDegree('high', 'max')"
|
||||||
}"
|
class="c-dial__high-limit__low"
|
||||||
>
|
x="0"
|
||||||
<path
|
y="5"
|
||||||
d="M256,100c86.2,0,156,69.8,156,156s-69.8,156-156,156c-43.1,0-82.1-17.5-110.3-45.7L75,437 c46.3,46.3,110.3,75,181,75c141.4,0,256-114.6,256-256S397.4,0,256,0C185.3,0,121.3,28.7,75,75l70.7,70.7 C173.9,117.5,212.9,100,256,100z"
|
width="5"
|
||||||
:style="`transform: rotate(${dialLowLimitDeg}deg)`"
|
height="5"
|
||||||
/>
|
/>
|
||||||
</svg>
|
<rect
|
||||||
|
v-if="dialHighLimitDeg <= getLimitDegree('high', 'q2')"
|
||||||
|
class="c-dial__high-limit__mid"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="5"
|
||||||
|
height="5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
v-if="dialHighLimitDeg <= getLimitDegree('high', 'q3')"
|
||||||
|
class="c-dial__high-limit__high"
|
||||||
|
x="5"
|
||||||
|
y="0"
|
||||||
|
width="5"
|
||||||
|
height="5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="c-dial__value"
|
v-if="typeFilledDial"
|
||||||
viewBox="0 0 512 512"
|
class="c-dial__filled-value-wrapper"
|
||||||
:class="{
|
viewBox="0 0 10 10"
|
||||||
'c-dial-clip--90': degValue < 90 && typeFilledDial,
|
>
|
||||||
'c-dial-clip--180': degValue >= 90 && degValue < 180 && typeFilledDial
|
<g
|
||||||
}"
|
class="c-dial__filled-value"
|
||||||
>
|
:style="`transform: rotate(${degValueFilledDial}deg)`"
|
||||||
<path
|
>
|
||||||
v-if="typeFilledDial && degValue > 0"
|
<rect
|
||||||
d="M256,31A224.3,224.3,0,0,0,98.3,95.5l48.4,49.2a156,156,0,1,1-1,221.6L96.9,415.1A224.4,224.4,0,0,0,256,481c124.3,0,225-100.7,225-225S380.3,31,256,31Z"
|
v-if="degValue >= getLimitDegree('low', 'q1')"
|
||||||
:style="`transform: rotate(${degValue}deg)`"
|
class="c-dial__filled-value__low"
|
||||||
/>
|
x="5"
|
||||||
<path
|
y="5"
|
||||||
v-if="typeNeedleDial && valueInBounds"
|
width="5"
|
||||||
d="M256,86c-93.9,0-170,76.1-170,170c0,43.9,16.6,83.9,43.9,114.1l-38.7,38.7c-3.3,3.3-3.3,8.7,0,12s8.7,3.3,12,0 l38.7-38.7C172.1,409.4,212.1,426,256,426c93.9,0,170-76.1,170-170S349.9,86,256,86z M256,411.7c-86,0-155.7-69.7-155.7-155.7 S170,100.3,256,100.3S411.7,170,411.7,256S342,411.7,256,411.7z"
|
height="5"
|
||||||
:style="`transform: rotate(${degValue}deg)`"
|
/>
|
||||||
/>
|
<rect
|
||||||
</svg>
|
v-if="degValue >= getLimitDegree('low', 'q2')"
|
||||||
|
class="c-dial__filled-value__mid"
|
||||||
|
x="5"
|
||||||
|
y="0"
|
||||||
|
width="5"
|
||||||
|
height="5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
v-if="degValue >= getLimitDegree('low', 'q3')"
|
||||||
|
class="c-dial__filled-value__high"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="5"
|
||||||
|
height="5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
v-if="valueInBounds && typeNeedleDial"
|
||||||
|
class="c-dial__needle-value-wrapper"
|
||||||
|
viewBox="0 0 10 10"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
class="c-dial__needle-value"
|
||||||
|
:style="`transform: rotate(${degValue}deg)`"
|
||||||
|
>
|
||||||
|
<path d="M4.90234 9.39453L5.09766 9.39453L5.30146 8.20874C6.93993 8.05674 8.22265 6.67817 8.22266 5C8.22266 3.22018 6.77982 1.77734 5 1.77734C3.22018 1.77734 1.77734 3.22018 1.77734 5C1.77734 6.67817 3.06007 8.05674 4.69854 8.20874L4.90234 9.39453Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="typeMeter">
|
||||||
|
<div class="c-meter">
|
||||||
|
<div
|
||||||
|
v-if="displayMinMax"
|
||||||
|
class="c-gauge__range c-meter__range js-gauge-meter-range"
|
||||||
|
>
|
||||||
|
<div class="c-meter__range__high">{{ rangeHigh }}</div>
|
||||||
|
<div class="c-meter__range__low">{{ rangeLow }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="c-meter__bg">
|
||||||
|
<template v-if="typeMeterVertical">
|
||||||
|
<div
|
||||||
|
class="c-meter__value"
|
||||||
|
:style="`transform: translateY(${meterValueToPerc}%)`"
|
||||||
|
></div>
|
||||||
|
|
||||||
<template v-if="typeMeter">
|
<div
|
||||||
<div class="c-meter">
|
v-if="limitHigh !== null && meterHighLimitPerc > 0"
|
||||||
<div
|
class="c-meter__limit-high"
|
||||||
v-if="displayMinMax"
|
:style="`height: ${meterHighLimitPerc}%`"
|
||||||
class="c-gauge__range c-meter__range"
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="limitLow !== null && meterLowLimitPerc > 0"
|
||||||
|
class="c-meter__limit-low"
|
||||||
|
:style="`height: ${meterLowLimitPerc}%`"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="typeMeterHorizontal">
|
||||||
|
<div
|
||||||
|
class="c-meter__value"
|
||||||
|
:style="`transform: translateX(${meterValueToPerc * -1}%)`"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="limitHigh !== null && meterHighLimitPerc > 0"
|
||||||
|
class="c-meter__limit-high"
|
||||||
|
:style="`width: ${meterHighLimitPerc}%`"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="limitLow !== null && meterLowLimitPerc > 0"
|
||||||
|
class="c-meter__limit-low"
|
||||||
|
:style="`width: ${meterLowLimitPerc}%`"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class="c-meter__current-value-text-wrapper"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
>
|
>
|
||||||
<div class="c-meter__range__high">{{ rangeHigh }}</div>
|
|
||||||
<div class="c-meter__range__low">{{ rangeLow }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="c-meter__bg">
|
|
||||||
<template v-if="typeMeterVertical">
|
|
||||||
<div
|
|
||||||
class="c-meter__value"
|
|
||||||
:style="`transform: translateY(${meterValueToPerc}%)`"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="limitHigh && meterHighLimitPerc > 0"
|
|
||||||
class="c-meter__limit-high"
|
|
||||||
:style="`height: ${meterHighLimitPerc}%`"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="limitLow && meterLowLimitPerc > 0"
|
|
||||||
class="c-meter__limit-low"
|
|
||||||
:style="`height: ${meterLowLimitPerc}%`"
|
|
||||||
></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="typeMeterHorizontal">
|
|
||||||
<div
|
|
||||||
class="c-meter__value"
|
|
||||||
:style="`transform: translateX(${meterValueToPerc * -1}%)`"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="limitHigh && meterHighLimitPerc > 0"
|
|
||||||
class="c-meter__limit-high"
|
|
||||||
:style="`width: ${meterHighLimitPerc}%`"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="limitLow && meterLowLimitPerc > 0"
|
|
||||||
class="c-meter__limit-low"
|
|
||||||
:style="`width: ${meterLowLimitPerc}%`"
|
|
||||||
></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
v-if="displayCurVal"
|
v-if="displayCurVal"
|
||||||
class="c-gauge__curval"
|
class="c-meter__current-value-text-sizer"
|
||||||
:viewBox="curValViewBox"
|
:viewBox="curValViewBox"
|
||||||
preserveAspectRatio="xMidYMid meet"
|
preserveAspectRatio="xMidYMid meet"
|
||||||
>
|
>
|
||||||
<text
|
<text
|
||||||
class="c-gauge__curval-text"
|
class="c-dial__current-value-text js-meter-current-value"
|
||||||
text-anchor="middle"
|
|
||||||
lengthAdjust="spacing"
|
lengthAdjust="spacing"
|
||||||
|
text-anchor="middle"
|
||||||
|
style="transform: translate(50%, 70%)"
|
||||||
>{{ curVal }}</text>
|
>{{ curVal }}</text>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { DIAL_VALUE_DEG_OFFSET, getLimitDegree } from '../gauge-limit-util';
|
||||||
|
|
||||||
const LIMIT_PADDING_IN_PERCENT = 10;
|
const LIMIT_PADDING_IN_PERCENT = 10;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -209,6 +300,13 @@ export default {
|
|||||||
degValue() {
|
degValue() {
|
||||||
return this.percentToDegrees(this.valToPercent(this.curVal));
|
return this.percentToDegrees(this.valToPercent(this.curVal));
|
||||||
},
|
},
|
||||||
|
degValueFilledDial() {
|
||||||
|
if (this.curVal > this.rangeHigh) {
|
||||||
|
return this.percentToDegrees(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.percentToDegrees(this.valToPercent(this.curVal));
|
||||||
|
},
|
||||||
dialHighLimitDeg() {
|
dialHighLimitDeg() {
|
||||||
return this.percentToDegrees(this.valToPercent(this.limitHigh));
|
return this.percentToDegrees(this.valToPercent(this.limitHigh));
|
||||||
},
|
},
|
||||||
@ -299,6 +397,7 @@ export default {
|
|||||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getLimitDegree: getLimitDegree,
|
||||||
addTelemetryObjectAndSubscribe(domainObject) {
|
addTelemetryObjectAndSubscribe(domainObject) {
|
||||||
this.telemetryObject = domainObject;
|
this.telemetryObject = domainObject;
|
||||||
this.request();
|
this.request();
|
||||||
@ -340,7 +439,7 @@ export default {
|
|||||||
return this.gaugeType.indexOf(str) !== -1;
|
return this.gaugeType.indexOf(str) !== -1;
|
||||||
},
|
},
|
||||||
percentToDegrees(vPercent) {
|
percentToDegrees(vPercent) {
|
||||||
return this.round((vPercent / 100) * 270, 2);
|
return this.round(((vPercent / 100) * 270) + DIAL_VALUE_DEG_OFFSET, 2);
|
||||||
},
|
},
|
||||||
removeFromComposition(telemetryObject = this.telemetryObject) {
|
removeFromComposition(telemetryObject = this.telemetryObject) {
|
||||||
let composition = this.domainObject.composition.filter(id =>
|
let composition = this.domainObject.composition.filter(id =>
|
||||||
@ -453,7 +552,7 @@ export default {
|
|||||||
valToPercent(vValue) {
|
valToPercent(vValue) {
|
||||||
// Used by dial
|
// Used by dial
|
||||||
if (vValue >= this.rangeHigh && this.typeFilledDial) {
|
if (vValue >= this.rangeHigh && this.typeFilledDial) {
|
||||||
// Don't peg at 100% if the gaugeType isn't a filled shape
|
// For filled dial, clip values over the high range to prevent over-rotation
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
:class="model.cssClass"
|
:class="model.cssClass"
|
||||||
>
|
>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
|
:id="'gaugeToggle'"
|
||||||
:checked="isUseTelemetryLimits"
|
:checked="isUseTelemetryLimits"
|
||||||
label="Use telemetry limits for minimum and maximum ranges"
|
label="Use telemetry limits for minimum and maximum ranges"
|
||||||
@change="toggleUseTelemetryLimits"
|
@change="toggleUseTelemetryLimits"
|
||||||
|
39
src/plugins/gauge/gauge-limit-util.js
Normal file
39
src/plugins/gauge/gauge-limit-util.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
const GAUGE_LIMITS = {
|
||||||
|
q1: 0,
|
||||||
|
q2: 90,
|
||||||
|
q3: 180,
|
||||||
|
q4: 270
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DIAL_VALUE_DEG_OFFSET = 45;
|
||||||
|
|
||||||
|
// type: low, high
|
||||||
|
// quadrant: low, mid, high, max
|
||||||
|
export function getLimitDegree(type, quadrant) {
|
||||||
|
if (quadrant === 'max') {
|
||||||
|
return GAUGE_LIMITS.q4 + DIAL_VALUE_DEG_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type === 'low'
|
||||||
|
? getLowLimitDegree(quadrant)
|
||||||
|
: getHighLimitDegree(quadrant)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLowLimitDegree(quadrant) {
|
||||||
|
return GAUGE_LIMITS[quadrant] + DIAL_VALUE_DEG_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHighLimitDegree(quadrant) {
|
||||||
|
if (quadrant === 'q1') {
|
||||||
|
return GAUGE_LIMITS.q4 + DIAL_VALUE_DEG_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quadrant === 'q2') {
|
||||||
|
return GAUGE_LIMITS.q3 + DIAL_VALUE_DEG_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quadrant === 'q3') {
|
||||||
|
return GAUGE_LIMITS.q2 + DIAL_VALUE_DEG_OFFSET;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,3 @@
|
|||||||
$dialClip: polygon(0 0, 100% 0, 100% 100%, 50% 50%, 0 100%);
|
|
||||||
$dialClip90: polygon(0 0, 50% 50%, 0 100%);
|
|
||||||
$dialClip180: polygon(0 0, 100% 0, 0 100%);
|
|
||||||
$limitHighClip90: polygon(0 0, 100% 0, 100% 100%);
|
|
||||||
$limitHighClip180: polygon(100% 0, 100% 100%, 0 100%);
|
|
||||||
|
|
||||||
.is-object-type-gauge {
|
.is-object-type-gauge {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@ -13,10 +7,8 @@ $limitHighClip180: polygon(100% 0, 100% 100%, 0 100%);
|
|||||||
|
|
||||||
&.invalid,
|
&.invalid,
|
||||||
&.invalid.req { @include validationState($glyph-icon-x, $colorFormInvalid); }
|
&.invalid.req { @include validationState($glyph-icon-x, $colorFormInvalid); }
|
||||||
|
|
||||||
&.valid,
|
&.valid,
|
||||||
&.valid.req { @include validationState($glyph-icon-check, $colorFormValid); }
|
&.valid.req { @include validationState($glyph-icon-check, $colorFormValid); }
|
||||||
|
|
||||||
&.req { @include validationState($glyph-icon-asterisk, $colorFormRequired); }
|
&.req { @include validationState($glyph-icon-asterisk, $colorFormRequired); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,92 +29,47 @@ $limitHighClip180: polygon(100% 0, 100% 100%, 0 100%);
|
|||||||
@include abs();
|
@include abs();
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
|
||||||
path {
|
|
||||||
transform-origin: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.c-gauge__curval {
|
|
||||||
@include abs();
|
|
||||||
fill: $colorGaugeTextValue;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 2;
|
|
||||||
|
|
||||||
.c-gauge__curval-text {
|
|
||||||
font-family: $heroFont;
|
|
||||||
transform: translate(50%, 75%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='dial'] {
|
|
||||||
// Square aspect ratio
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='meter'] {
|
|
||||||
@include abs();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************** DIAL GAUGE */
|
/********************************************** DIAL GAUGE */
|
||||||
.c-dial {
|
svg[class*='c-dial'] {
|
||||||
// Dial elements
|
max-height: 100%;
|
||||||
@include abs();
|
max-width: 100%;
|
||||||
clip-path: $dialClip;
|
position: absolute;
|
||||||
|
|
||||||
svg,
|
g {
|
||||||
&__ticks,
|
transform-origin: center;
|
||||||
&__bg,
|
|
||||||
&[class*='__limit'],
|
|
||||||
&__value {
|
|
||||||
@include abs();
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-high-limit-clip--90 {
|
|
||||||
clip-path: $limitHighClip90;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-high-limit-clip--180 {
|
|
||||||
clip-path: $limitHighClip180;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__limit-high path { fill: $colorGaugeLimitHigh; }
|
|
||||||
&__limit-low path { fill: $colorGaugeLimitLow; }
|
|
||||||
|
|
||||||
&__value,
|
|
||||||
&__limit-low {
|
|
||||||
&.c-dial-clip--90 {
|
|
||||||
clip-path: $dialClip90;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.c-dial-clip--180 {
|
|
||||||
clip-path: $dialClip180;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__value {
|
|
||||||
path,
|
|
||||||
polygon {
|
|
||||||
fill: $colorGaugeValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__bg {
|
|
||||||
path {
|
|
||||||
fill: $colorGaugeBg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-gauge--dial-needle .c-dial__value {
|
.c-dial {
|
||||||
path {
|
&__bg {
|
||||||
|
background: $colorGaugeBg;
|
||||||
|
clip-path: url(#gaugeBgMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__limit-high rect { fill: $colorGaugeLimitHigh; }
|
||||||
|
&__limit-low rect { fill: $colorGaugeLimitLow; }
|
||||||
|
|
||||||
|
&__filled-value-wrapper {
|
||||||
|
clip-path: url(#gaugeValueMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__needle-value-wrapper {
|
||||||
|
clip-path: url(#gaugeValueMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__filled-value { fill: $colorGaugeValue; }
|
||||||
|
|
||||||
|
&__needle-value {
|
||||||
|
fill: $colorGaugeValue;
|
||||||
transition: transform $transitionTimeGauge;
|
transition: transform $transitionTimeGauge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__current-value-text {
|
||||||
|
fill: $colorGaugeTextValue;
|
||||||
|
font-family: $heroFont;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************** METER GAUGE */
|
/********************************************** METER GAUGE */
|
||||||
@ -131,6 +78,13 @@ $limitHighClip180: polygon(100% 0, 100% 100%, 0 100%);
|
|||||||
@include abs();
|
@include abs();
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
// current-value-text
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&__range {
|
&__range {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
>
|
>
|
||||||
<div class="c-image-controls__input icon-brightness">
|
<div class="c-image-controls__input icon-brightness">
|
||||||
<input
|
<input
|
||||||
v-model="filters.contrast"
|
v-model="filters.brightness"
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
max="500"
|
max="500"
|
||||||
@ -69,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="c-image-controls__input icon-contrast">
|
<div class="c-image-controls__input icon-contrast">
|
||||||
<input
|
<input
|
||||||
v-model="filters.brightness"
|
v-model="filters.contrast"
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
max="500"
|
max="500"
|
||||||
@ -174,7 +174,7 @@ export default {
|
|||||||
this.$emit('filtersUpdated', this.filters);
|
this.$emit('filtersUpdated', this.filters);
|
||||||
},
|
},
|
||||||
handleResetFilters() {
|
handleResetFilters() {
|
||||||
this.filters = DEFAULT_FILTER_VALUES;
|
this.filters = {...DEFAULT_FILTER_VALUES};
|
||||||
this.notifyFiltersChanged();
|
this.notifyFiltersChanged();
|
||||||
},
|
},
|
||||||
limitZoomRange(factor) {
|
limitZoomRange(factor) {
|
||||||
|
@ -97,8 +97,7 @@
|
|||||||
'transform': `scale(${zoomFactor}) translate(${imageTranslateX}px, ${imageTranslateY}px)`,
|
'transform': `scale(${zoomFactor}) translate(${imageTranslateX}px, ${imageTranslateY}px)`,
|
||||||
'transition': `${!pan && animateZoom ? 'transform 250ms ease-in' : 'initial'}`,
|
'transition': `${!pan && animateZoom ? 'transform 250ms ease-in' : 'initial'}`,
|
||||||
'width': `${sizedImageWidth}px`,
|
'width': `${sizedImageWidth}px`,
|
||||||
'height': `${sizedImageHeight}px`,
|
'height': `${sizedImageHeight}px`
|
||||||
|
|
||||||
}"
|
}"
|
||||||
></div>
|
></div>
|
||||||
<Compass
|
<Compass
|
||||||
@ -143,13 +142,13 @@
|
|||||||
<!-- spacecraft position fresh -->
|
<!-- spacecraft position fresh -->
|
||||||
<div
|
<div
|
||||||
v-if="relatedTelemetry.hasRelatedTelemetry && isSpacecraftPositionFresh"
|
v-if="relatedTelemetry.hasRelatedTelemetry && isSpacecraftPositionFresh"
|
||||||
class="c-imagery__age icon-check c-imagery--new"
|
class="c-imagery__age icon-check c-imagery--new no-animation"
|
||||||
>POS</div>
|
>POS</div>
|
||||||
|
|
||||||
<!-- camera position fresh -->
|
<!-- camera position fresh -->
|
||||||
<div
|
<div
|
||||||
v-if="relatedTelemetry.hasRelatedTelemetry && isCameraPositionFresh"
|
v-if="relatedTelemetry.hasRelatedTelemetry && isCameraPositionFresh"
|
||||||
class="c-imagery__age icon-check c-imagery--new"
|
class="c-imagery__age icon-check c-imagery--new no-animation"
|
||||||
>CAM</div>
|
>CAM</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-local-controls">
|
<div class="h-local-controls">
|
||||||
@ -331,6 +330,16 @@ export default {
|
|||||||
},
|
},
|
||||||
isImageNew() {
|
isImageNew() {
|
||||||
let cutoff = FIVE_MINUTES;
|
let cutoff = FIVE_MINUTES;
|
||||||
|
if (this.imageFreshnessOptions) {
|
||||||
|
const { fadeOutDelayTime, fadeOutDurationTime} = this.imageFreshnessOptions;
|
||||||
|
// convert css duration to IS8601 format for parsing
|
||||||
|
const isoFormattedDuration = 'PT' + fadeOutDurationTime.toUpperCase();
|
||||||
|
const isoFormattedDelay = 'PT' + fadeOutDelayTime.toUpperCase();
|
||||||
|
const parsedDuration = moment.duration(isoFormattedDuration).asMilliseconds();
|
||||||
|
const parsedDelay = moment.duration(isoFormattedDelay).asMilliseconds();
|
||||||
|
cutoff = parsedDuration + parsedDelay;
|
||||||
|
}
|
||||||
|
|
||||||
let age = this.numericDuration;
|
let age = this.numericDuration;
|
||||||
|
|
||||||
return age < cutoff && !this.refreshCSS;
|
return age < cutoff && !this.refreshCSS;
|
||||||
@ -372,6 +381,9 @@ export default {
|
|||||||
formattedDuration() {
|
formattedDuration() {
|
||||||
let result = 'N/A';
|
let result = 'N/A';
|
||||||
let negativeAge = -1;
|
let negativeAge = -1;
|
||||||
|
if (!Number.isInteger(this.numericDuration)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.numericDuration > TWENTYFOUR_HOURS) {
|
if (this.numericDuration > TWENTYFOUR_HOURS) {
|
||||||
negativeAge *= (this.numericDuration / TWENTYFOUR_HOURS);
|
negativeAge *= (this.numericDuration / TWENTYFOUR_HOURS);
|
||||||
@ -514,6 +526,8 @@ export default {
|
|||||||
if (!this.isPaused) {
|
if (!this.isPaused) {
|
||||||
this.setFocusedImage(imageIndex);
|
this.setFocusedImage(imageIndex);
|
||||||
this.scrollToRight();
|
this.scrollToRight();
|
||||||
|
} else {
|
||||||
|
this.scrollToFocused();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
@ -822,8 +836,10 @@ export default {
|
|||||||
let currentTime = this.timeContext.clock() && this.timeContext.clock().currentValue();
|
let currentTime = this.timeContext.clock() && this.timeContext.clock().currentValue();
|
||||||
if (currentTime === undefined) {
|
if (currentTime === undefined) {
|
||||||
this.numericDuration = currentTime;
|
this.numericDuration = currentTime;
|
||||||
} else {
|
} else if (Number.isInteger(this.parsedSelectedTime)) {
|
||||||
this.numericDuration = currentTime - this.parsedSelectedTime;
|
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||||
|
} else {
|
||||||
|
this.numericDuration = undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetAgeCSS() {
|
resetAgeCSS() {
|
||||||
@ -977,6 +993,7 @@ export default {
|
|||||||
|
|
||||||
this.setSizedImageDimensions();
|
this.setSizedImageDimensions();
|
||||||
this.calculateViewHeight();
|
this.calculateViewHeight();
|
||||||
|
this.scrollToFocused();
|
||||||
},
|
},
|
||||||
setSizedImageDimensions() {
|
setSizedImageDimensions() {
|
||||||
this.focusedImageNaturalAspectRatio = this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
|
this.focusedImageNaturalAspectRatio = this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@use 'sass:math';
|
||||||
|
|
||||||
@keyframes fade-out {
|
@keyframes fade-out {
|
||||||
from {
|
from {
|
||||||
background-color: rgba($colorOk, 0.5);
|
background-color: rgba($colorOk, 0.5);
|
||||||
@ -61,6 +63,7 @@
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
|
height: 100%; //fallback value
|
||||||
}
|
}
|
||||||
&__image {
|
&__image {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -138,6 +141,9 @@
|
|||||||
animation-timing-function: ease-in;
|
animation-timing-function: ease-in;
|
||||||
animation-iteration-count: 1;
|
animation-iteration-count: 1;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
|
&.no-animation {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -211,7 +217,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c-thumb {
|
.c-thumb {
|
||||||
$w: $imageThumbsD / 2;
|
$w: math.div($imageThumbsD, 2);
|
||||||
min-width: $w;
|
min-width: $w;
|
||||||
width: $w;
|
width: $w;
|
||||||
|
|
||||||
|
@ -46,7 +46,6 @@ export default {
|
|||||||
|
|
||||||
// kickoff
|
// kickoff
|
||||||
this.subscribe();
|
this.subscribe();
|
||||||
this.requestHistory();
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.unsubscribe) {
|
if (this.unsubscribe) {
|
||||||
@ -71,22 +70,18 @@ export default {
|
|||||||
this.timeContext.off('timeSystem', this.timeSystemChange);
|
this.timeContext.off('timeSystem', this.timeSystemChange);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
datumIsNotValid(datum) {
|
isDatumValid(datum) {
|
||||||
if (this.imageHistory.length === 0) {
|
//TODO: Add a check to see if there are duplicate images (identical image timestamp and url subsequently)
|
||||||
|
if (!datum) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const datumURL = this.formatImageUrl(datum);
|
|
||||||
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
|
|
||||||
|
|
||||||
// datum is not valid if it matches the last datum in history,
|
|
||||||
// or it is before the last datum in the history
|
|
||||||
const datumTimeCheck = this.parseTime(datum);
|
const datumTimeCheck = this.parseTime(datum);
|
||||||
const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
|
const bounds = this.timeContext.bounds();
|
||||||
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
|
|
||||||
const isStale = datumTimeCheck < historyTimeCheck;
|
|
||||||
|
|
||||||
return matchesLast || isStale;
|
const isOutOfBounds = datumTimeCheck < bounds.start || datumTimeCheck > bounds.end;
|
||||||
|
|
||||||
|
return !isOutOfBounds;
|
||||||
},
|
},
|
||||||
formatImageUrl(datum) {
|
formatImageUrl(datum) {
|
||||||
if (!datum) {
|
if (!datum) {
|
||||||
@ -133,25 +128,19 @@ export default {
|
|||||||
return this.requestHistory();
|
return this.requestHistory();
|
||||||
},
|
},
|
||||||
async requestHistory() {
|
async requestHistory() {
|
||||||
let bounds = this.timeContext.bounds();
|
|
||||||
this.requestCount++;
|
this.requestCount++;
|
||||||
const requestId = this.requestCount;
|
const requestId = this.requestCount;
|
||||||
this.imageHistory = [];
|
const bounds = this.timeContext.bounds();
|
||||||
|
|
||||||
let data = await this.openmct.telemetry
|
const data = await this.openmct.telemetry
|
||||||
.request(this.domainObject, bounds) || [];
|
.request(this.domainObject, bounds) || [];
|
||||||
|
// wait until new request resolves to do comparison
|
||||||
if (this.requestCount === requestId) {
|
if (this.requestCount !== requestId) {
|
||||||
let imagery = [];
|
return this.imageHistory = [];
|
||||||
data.forEach((datum) => {
|
|
||||||
let image = this.normalizeDatum(datum);
|
|
||||||
if (image) {
|
|
||||||
imagery.push(image);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//this is to optimize anything that reacts to imageHistory length
|
|
||||||
this.imageHistory = imagery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const imagery = data.filter(this.isDatumValid).map(this.normalizeDatum);
|
||||||
|
this.imageHistory = imagery;
|
||||||
},
|
},
|
||||||
clearData(domainObjectToClear) {
|
clearData(domainObjectToClear) {
|
||||||
// global clearData button is accepted therefore no truthy check on inputted param
|
// global clearData button is accepted therefore no truthy check on inputted param
|
||||||
@ -183,27 +172,29 @@ export default {
|
|||||||
.subscribe(this.domainObject, (datum) => {
|
.subscribe(this.domainObject, (datum) => {
|
||||||
let parsedTimestamp = this.parseTime(datum);
|
let parsedTimestamp = this.parseTime(datum);
|
||||||
let bounds = this.timeContext.bounds();
|
let bounds = this.timeContext.bounds();
|
||||||
|
if (!(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
|
if (this.isDatumValid(datum)) {
|
||||||
let image = this.normalizeDatum(datum);
|
this.imageHistory.push(this.normalizeDatum(datum));
|
||||||
if (image) {
|
|
||||||
this.imageHistory.push(image);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
normalizeDatum(datum) {
|
normalizeDatum(datum) {
|
||||||
if (this.datumIsNotValid(datum)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = { ...datum };
|
const formattedTime = this.formatTime(datum);
|
||||||
image.formattedTime = this.formatTime(datum);
|
const url = this.formatImageUrl(datum);
|
||||||
image.url = this.formatImageUrl(datum);
|
const time = this.parseTime(formattedTime);
|
||||||
image.time = this.parseTime(image.formattedTime);
|
const imageDownloadName = this.getImageDownloadName(datum);
|
||||||
image.imageDownloadName = this.getImageDownloadName(datum);
|
|
||||||
|
|
||||||
return image;
|
return {
|
||||||
|
...datum,
|
||||||
|
formattedTime,
|
||||||
|
url,
|
||||||
|
time,
|
||||||
|
imageDownloadName
|
||||||
|
};
|
||||||
},
|
},
|
||||||
getFormatter(key) {
|
getFormatter(key) {
|
||||||
let metadataValue = this.metadata.value(key) || { format: key };
|
let metadataValue = this.metadata.value(key) || { format: key };
|
||||||
|
@ -84,7 +84,6 @@ describe("The Imagery View Layouts", () => {
|
|||||||
let telemetryPromise;
|
let telemetryPromise;
|
||||||
let telemetryPromiseResolve;
|
let telemetryPromiseResolve;
|
||||||
let cleanupFirst;
|
let cleanupFirst;
|
||||||
let isClearDataTriggered;
|
|
||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
let parent;
|
let parent;
|
||||||
@ -193,20 +192,12 @@ describe("The Imagery View Layouts", () => {
|
|||||||
cleanupFirst = [];
|
cleanupFirst = [];
|
||||||
|
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
openmct.time.timeSystem('utc', {
|
|
||||||
start: START - (5 * ONE_MINUTE),
|
|
||||||
end: START + (5 * ONE_MINUTE)
|
|
||||||
});
|
|
||||||
|
|
||||||
telemetryPromise = new Promise((resolve) => {
|
telemetryPromise = new Promise((resolve) => {
|
||||||
telemetryPromiseResolve = resolve;
|
telemetryPromiseResolve = resolve;
|
||||||
});
|
});
|
||||||
|
|
||||||
spyOn(openmct.telemetry, 'request').and.callFake(() => {
|
spyOn(openmct.telemetry, 'request').and.callFake(() => {
|
||||||
if (isClearDataTriggered) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
telemetryPromiseResolve(imageTelemetry);
|
telemetryPromiseResolve(imageTelemetry);
|
||||||
|
|
||||||
return telemetryPromise;
|
return telemetryPromise;
|
||||||
@ -325,44 +316,93 @@ describe("The Imagery View Layouts", () => {
|
|||||||
expect(imageryView).toBeDefined();
|
expect(imageryView).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("imagery view", () => {
|
describe("Clear data action for imagery", () => {
|
||||||
let applicableViews;
|
let applicableViews;
|
||||||
let imageryViewProvider;
|
let imageryViewProvider;
|
||||||
let imageryView;
|
let imageryView;
|
||||||
|
let componentView;
|
||||||
let clearDataPlugin;
|
let clearDataPlugin;
|
||||||
let clearDataAction;
|
let clearDataAction;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
openmct.time.timeSystem('utc', {
|
||||||
|
start: START - (5 * ONE_MINUTE),
|
||||||
|
end: START + (5 * ONE_MINUTE)
|
||||||
|
});
|
||||||
|
|
||||||
applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]);
|
applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]);
|
||||||
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
||||||
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
|
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
|
||||||
imageryView.show(child);
|
imageryView.show(child);
|
||||||
|
componentView = imageryView._getInstance().$children[0];
|
||||||
|
|
||||||
clearDataPlugin = new ClearDataPlugin(
|
clearDataPlugin = new ClearDataPlugin(
|
||||||
['example.imagery'],
|
['example.imagery'],
|
||||||
{indicator: true}
|
{indicator: true}
|
||||||
);
|
);
|
||||||
openmct.install(clearDataPlugin);
|
openmct.install(clearDataPlugin);
|
||||||
clearDataAction = openmct.actions.getAction('clear-data-action');
|
clearDataAction = openmct.actions.getAction('clear-data-action');
|
||||||
|
|
||||||
|
return Vue.nextTick();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clear data action is installed', () => {
|
||||||
|
expect(clearDataAction).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('on clearData action should clear data for object is selected', (done) => {
|
||||||
// force show the thumbnails
|
// force show the thumbnails
|
||||||
|
componentView.forceShowThumbnails = true;
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
let clearDataResolve;
|
||||||
|
let telemetryRequestPromise = new Promise((resolve) => {
|
||||||
|
clearDataResolve = resolve;
|
||||||
|
});
|
||||||
|
expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0);
|
||||||
|
|
||||||
|
openmct.objectViews.on('clearData', (_domainObject) => {
|
||||||
|
return Vue.nextTick(() => {
|
||||||
|
expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0);
|
||||||
|
|
||||||
|
clearDataResolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
clearDataAction.invoke(imageryObject);
|
||||||
|
|
||||||
|
telemetryRequestPromise.then(() => {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("imagery view", () => {
|
||||||
|
let applicableViews;
|
||||||
|
let imageryViewProvider;
|
||||||
|
let imageryView;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
openmct.time.timeSystem('utc', {
|
||||||
|
start: START - (5 * ONE_MINUTE),
|
||||||
|
end: START + (5 * ONE_MINUTE)
|
||||||
|
});
|
||||||
|
|
||||||
|
applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]);
|
||||||
|
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
||||||
|
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
|
||||||
|
imageryView.show(child);
|
||||||
|
|
||||||
imageryView._getInstance().$children[0].forceShowThumbnails = true;
|
imageryView._getInstance().$children[0].forceShowThumbnails = true;
|
||||||
|
|
||||||
return Vue.nextTick();
|
return Vue.nextTick();
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
|
||||||
isClearDataTriggered = false;
|
|
||||||
// openmct.time.stopClock();
|
|
||||||
// openmct.router.removeListener('change:hash', resolveFunction);
|
|
||||||
// imageryView.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("on mount should show the the most recent image", (done) => {
|
it("on mount should show the the most recent image", () => {
|
||||||
//Looks like we need Vue.nextTick here so that computed properties settle down
|
//Looks like we need Vue.nextTick here so that computed properties settle down
|
||||||
Vue.nextTick(() => {
|
return Vue.nextTick(() => {
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -398,7 +438,7 @@ describe("The Imagery View Layouts", () => {
|
|||||||
|
|
||||||
it("should show that an image is not new", (done) => {
|
it("should show that an image is not new", (done) => {
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
const target = imageTelemetry[2].url;
|
const target = imageTelemetry[4].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
@ -520,25 +560,6 @@ describe("The Imagery View Layouts", () => {
|
|||||||
expect(imageSizeAfter.width).toBeLessThan(imageSizeBefore.width);
|
expect(imageSizeAfter.width).toBeLessThan(imageSizeBefore.width);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clear data action is installed', () => {
|
|
||||||
expect(clearDataAction).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('on clearData action should clear data for object is selected', async (done) => {
|
|
||||||
// force show the thumbnails
|
|
||||||
imageryView._getInstance().$children[0].forceShowThumbnails = true;
|
|
||||||
await Vue.nextTick();
|
|
||||||
expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0);
|
|
||||||
openmct.objectViews.on('clearData', async (_domainObject) => {
|
|
||||||
await Vue.nextTick();
|
|
||||||
expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
// stubbed telemetry data will return empty array when true
|
|
||||||
isClearDataTriggered = true;
|
|
||||||
clearDataAction.invoke(imageryObject);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("imagery time strip view", () => {
|
describe("imagery time strip view", () => {
|
||||||
|
@ -192,7 +192,6 @@ export default {
|
|||||||
|
|
||||||
if (this.axisType === 'yAxis' && this.axis.get('logMode')) {
|
if (this.axisType === 'yAxis' && this.axis.get('logMode')) {
|
||||||
return getLogTicks(range.min, range.max, number, 4);
|
return getLogTicks(range.min, range.max, number, 4);
|
||||||
// return getLogTicks2(range.min, range.max, number);
|
|
||||||
} else {
|
} else {
|
||||||
return ticks(range.min, range.max, number);
|
return ticks(range.min, range.max, number);
|
||||||
}
|
}
|
||||||
@ -204,6 +203,7 @@ export default {
|
|||||||
|
|
||||||
updateTicks(forceRegeneration = false) {
|
updateTicks(forceRegeneration = false) {
|
||||||
const range = this.axis.get('displayRange');
|
const range = this.axis.get('displayRange');
|
||||||
|
|
||||||
if (!range) {
|
if (!range) {
|
||||||
delete this.min;
|
delete this.min;
|
||||||
delete this.max;
|
delete this.max;
|
||||||
|
@ -215,6 +215,10 @@ export default class YAxisModel extends Model {
|
|||||||
|
|
||||||
const _range = this.get('displayRange');
|
const _range = this.get('displayRange');
|
||||||
|
|
||||||
|
if (!_range) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.get('logMode')) {
|
if (this.get('logMode')) {
|
||||||
_range.min = antisymlog(_range.min, 10);
|
_range.min = antisymlog(_range.min, 10);
|
||||||
_range.max = antisymlog(_range.max, 10);
|
_range.max = antisymlog(_range.max, 10);
|
||||||
|
@ -132,12 +132,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.object && this.object.type === 'conditionWidget' && keys.includes('output')) {
|
|
||||||
this.openmct.objects.mutate(this.object, 'conditionalLabel', styleObj.output);
|
|
||||||
} else {
|
|
||||||
this.openmct.objects.mutate(this.object, 'conditionalLabel', '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -78,11 +78,6 @@ export function getLogTicks(start, stop, mainTickCount = 8, secondaryTickCount =
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLogTicks2(start, stop, count = 8) {
|
|
||||||
return ticks(antisymlog(start, 10), antisymlog(stop, 10), count)
|
|
||||||
.map(n => symlog(n, 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Linear tick generation from d3-array.
|
* Linear tick generation from d3-array.
|
||||||
*/
|
*/
|
||||||
|
@ -213,12 +213,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.domainObject && this.domainObject.type === 'conditionWidget' && keys.includes('output')) {
|
|
||||||
this.openmct.objects.mutate(this.domainObject, 'conditionalLabel', styleObj.output);
|
|
||||||
} else {
|
|
||||||
this.openmct.objects.mutate(this.domainObject, 'conditionalLabel', '');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
updateView(immediatelySelect) {
|
updateView(immediatelySelect) {
|
||||||
this.clear();
|
this.clear();
|
||||||
@ -450,4 +444,3 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -26,9 +26,10 @@ const config = {
|
|||||||
maelstromTheme: './src/plugins/themes/maelstrom-theme.scss'
|
maelstromTheme: './src/plugins/themes/maelstrom-theme.scss'
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
globalObject: "this",
|
globalObject: 'this',
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
library: '[name]',
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
library: 'openmct',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
publicPath: '',
|
publicPath: '',
|
||||||
hashFunction: 'xxhash64',
|
hashFunction: 'xxhash64',
|
||||||
|
@ -16,5 +16,5 @@ module.exports = merge(common, {
|
|||||||
__OPENMCT_ROOT_RELATIVE__: '""'
|
__OPENMCT_ROOT_RELATIVE__: '""'
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
devtool: 'source-map'
|
devtool: 'eval-source-map'
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user