Compare commits
18 Commits
v2.0.4
...
sprint-2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aedf7c6aee | ||
|
|
0a0826f87e | ||
|
|
efb5d270d9 | ||
|
|
de1b877954 | ||
|
|
e063442d8c | ||
|
|
6a5823ab5c | ||
|
|
0493e5ae3c | ||
|
|
24f13b6249 | ||
|
|
4db2f547d9 | ||
|
|
221fb4d6bf | ||
|
|
257742b45b | ||
|
|
44edec4f04 | ||
|
|
ab4d0dd37f | ||
|
|
c089a4760d | ||
|
|
b77a4066f2 | ||
|
|
20d7e80502 | ||
|
|
d63fec51a7 | ||
|
|
d30c4fcb53 |
@@ -2,7 +2,7 @@ version: 2.1
|
||||
executors:
|
||||
pw-focal-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:v1.19.2-focal
|
||||
- image: mcr.microsoft.com/playwright:v1.21.1-focal
|
||||
environment:
|
||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||
parameters:
|
||||
@@ -64,7 +64,7 @@ commands:
|
||||
- run: curl -Os https://uploader.codecov.io/latest/linux/codecov;chmod +x codecov;./codecov
|
||||
orbs:
|
||||
node: circleci/node@4.9.0
|
||||
browser-tools: circleci/browser-tools@1.2.3
|
||||
browser-tools: circleci/browser-tools@1.3.0
|
||||
jobs:
|
||||
npm-audit:
|
||||
parameters:
|
||||
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -32,12 +32,12 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: javascript
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
2
.github/workflows/e2e-pr.yml
vendored
2
.github/workflows/e2e-pr.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: npx playwright@1.19.2 install
|
||||
- run: npx playwright@1.21.1 install
|
||||
- run: npm install
|
||||
- run: npm run test:e2e:full
|
||||
- name: Archive test results
|
||||
|
||||
2
.github/workflows/e2e-visual.yml
vendored
2
.github/workflows/e2e-visual.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: npx playwright@1.19.2 install
|
||||
- run: npx playwright@1.21.1 install
|
||||
- run: npm install
|
||||
- name: Run the e2e visual tests
|
||||
run: npm run test:e2e:visual
|
||||
|
||||
@@ -6,9 +6,9 @@ const { devices } = require('@playwright/test');
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 2,
|
||||
retries: 1,
|
||||
testDir: 'tests',
|
||||
timeout: 90 * 1000,
|
||||
timeout: 60 * 1000,
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
port: 8080,
|
||||
@@ -28,12 +28,12 @@ const config = {
|
||||
{
|
||||
name: 'chrome',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
...devices['Desktop Chrome']
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MMOC',
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
viewport: {
|
||||
|
||||
@@ -29,12 +29,12 @@ const config = {
|
||||
{
|
||||
name: 'chrome',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
...devices['Desktop Chrome']
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MMOC',
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
viewport: {
|
||||
|
||||
@@ -27,16 +27,115 @@ This test suite is dedicated to tests which verify the basic operations surround
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
test.describe('Move item tests', () => {
|
||||
test.fixme('Create a basic object and verify that it can be moved to another Folder', async ({ page }) => {
|
||||
//Create and save Folder
|
||||
//Create and save Domain Object
|
||||
//Verify that the newly created domain object can be moved to Folder from Step 1.
|
||||
//Verify that newly moved object appears in the correct point in Tree
|
||||
//Verify that newly moved object appears correctly in Inspector panel
|
||||
test('Create a basic object and verify that it can be moved to another folder', async ({ page }) => {
|
||||
// Go to Open MCT
|
||||
await page.goto('/');
|
||||
|
||||
// Create a new folder in the root my items folder
|
||||
let folder1 = "Folder1";
|
||||
await page.locator('button:has-text("Create")').click();
|
||||
await page.locator('li.icon-folder').click();
|
||||
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder1);
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=OK').click(),
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||
|
||||
// Create another folder with a new name at default location, which is currently inside Folder 1
|
||||
let folder2 = "Folder2";
|
||||
await page.locator('button:has-text("Create")').click();
|
||||
await page.locator('li.icon-folder').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder2);
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=OK').click(),
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||
|
||||
// Move Folder 2 from Folder 1 to My Items
|
||||
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||
await page.locator('.c-tree__scrollable div div:nth-child(2) .c-tree__item .c-tree__item__view-control').click();
|
||||
|
||||
await page.locator(`a:has-text("${folder2}")`).click({
|
||||
button: 'right'
|
||||
});
|
||||
await page.locator('li.icon-move').click();
|
||||
await page.locator('form[name="mctForm"] >> text=My Items').click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=OK').click()
|
||||
]);
|
||||
|
||||
// Expect that Folder 2 is in My Items, the root folder
|
||||
expect(page.locator(`text=My Items >> nth=0:has(text=${folder2})`)).toBeTruthy();
|
||||
});
|
||||
test.fixme('Create a basic object and verify that it cannot be moved to object without Composition Provider', async ({ page }) => {
|
||||
//Create and save Telemetry Object
|
||||
//Create and save Domain Object
|
||||
//Verify that the newly created domain object cannot be moved to Telemetry Object from step 1.
|
||||
test('Create a basic object and verify that it cannot be moved to telemetry object without Composition Provider', async ({ page }) => {
|
||||
// Go to Open MCT
|
||||
await page.goto('/');
|
||||
|
||||
// Create Telemetry Table
|
||||
let telemetryTable = 'Test Telemetry Table';
|
||||
await page.locator('button:has-text("Create")').click();
|
||||
await page.locator('li:has-text("Telemetry Table")').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable);
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=OK').click()
|
||||
]);
|
||||
|
||||
// Finish editing and save Telemetry Table
|
||||
await page.locator('.c-button--menu.c-button--major.icon-save').click();
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
|
||||
// Create New Folder Basic Domain Object
|
||||
let folder = 'Test Folder';
|
||||
await page.locator('button:has-text("Create")').click();
|
||||
await page.locator('li:has-text("Folder")').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder);
|
||||
|
||||
// See if it's possible to put the folder in the Telemetry object during creation (Soft Assert)
|
||||
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
|
||||
let okButton = await page.locator('button.c-button.c-button--major:has-text("OK")');
|
||||
let okButtonStateDisabled = await okButton.isDisabled();
|
||||
expect.soft(okButtonStateDisabled).toBeTruthy();
|
||||
|
||||
// Continue test regardless of assertion and create it in My Items
|
||||
await page.locator('form[name="mctForm"] >> text=My Items').click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=OK').click()
|
||||
]);
|
||||
|
||||
// Open My Items
|
||||
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||
|
||||
// Select Folder Object and select Move from context menu
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator(`a:has-text("${folder}")`).click()
|
||||
]);
|
||||
await page.locator('.c-tree__item.is-navigated-object .c-tree__item__label .c-tree__item__type-icon').click({
|
||||
button: 'right'
|
||||
});
|
||||
await page.locator('li.icon-move').click();
|
||||
|
||||
// See if it's possible to put the folder in the Telemetry object after creation
|
||||
await page.locator('text=Location Open MCT My Items >> span').nth(3).click();
|
||||
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
|
||||
let okButton2 = await page.locator('button.c-button.c-button--major:has-text("OK")');
|
||||
let okButtonStateDisabled2 = await okButton2.isDisabled();
|
||||
expect(okButtonStateDisabled2).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
test.describe('Condition Set Operations', () => {
|
||||
test('Create new button `condition set` creates new condition object', async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
let conditionSetUrl;
|
||||
let getConditionSetIdentifierFromUrl;
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
test('Create new Condition Set object and store @localStorage', async ({ page, context }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
// Click text=Condition Set
|
||||
await page.click('text=Condition Set');
|
||||
//Click the Create button
|
||||
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([
|
||||
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.click('text=OK')
|
||||
page.reload(),
|
||||
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 }) => {
|
||||
//Go to object created in step one
|
||||
//Verify the Condition Set properties persist on Save
|
||||
//Verify the Condition Set properties persist on page.reload()
|
||||
});
|
||||
test.fixme('condition set object can be modified', async ({ page }) => {
|
||||
//Go to object created in step one
|
||||
test('condition set object can be modified on @localStorage', async ({ page }) => {
|
||||
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');
|
||||
|
||||
//Update the Condition Set properties
|
||||
//Verify the Condition Set properties persist on Save
|
||||
//Verify the Condition Set properties persist on page.reload()
|
||||
// Click Edit Button
|
||||
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 }) => {
|
||||
//Go to object created in step one
|
||||
//Verify that Condition Set object can be deleted
|
||||
//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
|
||||
test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
//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');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,13 +24,14 @@
|
||||
This test suite is dedicated to tests which verify the basic operations surrounding imagery,
|
||||
but only assume that example imagery is present.
|
||||
*/
|
||||
/* globals process */
|
||||
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
test.describe('Example Imagery', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
page.on('console', msg => console.log(msg.text()))
|
||||
page.on('console', msg => console.log(msg.text()));
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
@@ -42,10 +43,13 @@ test.describe('Example Imagery', () => {
|
||||
|
||||
// Click text=OK
|
||||
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.click('text=OK')
|
||||
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||
page.click('text=OK'),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
|
||||
//Wait until Save Banner is gone
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
|
||||
});
|
||||
|
||||
@@ -77,9 +81,11 @@ test.describe('Example Imagery', () => {
|
||||
|
||||
test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
|
||||
const deltaYStep = 100; //equivalent to 1x zoom
|
||||
const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
|
||||
|
||||
const bgImageLocator = await page.locator(backgroundImageSelector);
|
||||
await bgImageLocator.hover();
|
||||
|
||||
// zoom in
|
||||
await page.mouse.wheel(0, deltaYStep * 2);
|
||||
await bgImageLocator.hover();
|
||||
@@ -91,40 +97,49 @@ test.describe('Example Imagery', () => {
|
||||
// center the mouse pointer
|
||||
await page.mouse.move(imageCenterX, imageCenterY);
|
||||
|
||||
//Get Diagnostic info about process environment
|
||||
console.log('process.platform is ' + process.platform);
|
||||
const getUA = await page.evaluate(() => navigator.userAgent);
|
||||
console.log('navigator.userAgent ' + getUA);
|
||||
// Pan Imagery Hints
|
||||
const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan';
|
||||
const imageryHintsText = await page.locator('.c-imagery__hints').innerText();
|
||||
expect(expectedAltText).toEqual(imageryHintsText);
|
||||
|
||||
// pan right
|
||||
await page.keyboard.down('Alt');
|
||||
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(imageCenterX - 200, imageCenterY, 10);
|
||||
await page.mouse.up();
|
||||
await page.keyboard.up('Alt');
|
||||
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
|
||||
const afterRightPanBoundingBox = await bgImageLocator.boundingBox();
|
||||
expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x);
|
||||
|
||||
// pan left
|
||||
await page.keyboard.down('Alt');
|
||||
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(imageCenterX, imageCenterY, 10);
|
||||
await page.mouse.up();
|
||||
await page.keyboard.up('Alt');
|
||||
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
|
||||
const afterLeftPanBoundingBox = await bgImageLocator.boundingBox();
|
||||
expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x);
|
||||
|
||||
// pan up
|
||||
await page.mouse.move(imageCenterX, imageCenterY);
|
||||
await page.keyboard.down('Alt');
|
||||
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(imageCenterX, imageCenterY + 200, 10);
|
||||
await page.mouse.up();
|
||||
await page.keyboard.up('Alt');
|
||||
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
|
||||
const afterUpPanBoundingBox = await bgImageLocator.boundingBox();
|
||||
expect(afterUpPanBoundingBox.y).toBeGreaterThan(afterLeftPanBoundingBox.y);
|
||||
|
||||
// pan down
|
||||
await page.keyboard.down('Alt');
|
||||
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(imageCenterX, imageCenterY - 200, 10);
|
||||
await page.mouse.up();
|
||||
await page.keyboard.up('Alt');
|
||||
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
|
||||
const afterDownPanBoundingBox = await bgImageLocator.boundingBox();
|
||||
expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y);
|
||||
|
||||
@@ -156,20 +171,26 @@ test.describe('Example Imagery', () => {
|
||||
|
||||
test('Can use the reset button to reset the image', async ({ page }) => {
|
||||
const bgImageLocator = await page.locator(backgroundImageSelector);
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
|
||||
const zoomInBtn = await page.locator('.t-btn-zoom-in');
|
||||
const zoomResetBtn = await page.locator('.t-btn-zoom-reset');
|
||||
const initialBoundingBox = await bgImageLocator.boundingBox();
|
||||
|
||||
await zoomInBtn.click();
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
await zoomInBtn.click();
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
|
||||
const zoomedInBoundingBox = await bgImageLocator.boundingBox();
|
||||
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
|
||||
expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
|
||||
|
||||
await zoomResetBtn.click();
|
||||
// wait for zoom animation to finish
|
||||
await bgImageLocator.hover();
|
||||
|
||||
const resetBoundingBox = await bgImageLocator.boundingBox();
|
||||
@@ -180,38 +201,38 @@ test.describe('Example Imagery', () => {
|
||||
expect(resetBoundingBox.width).toEqual(initialBoundingBox.width);
|
||||
});
|
||||
|
||||
//test('Can use Mouse Wheel to zoom in and out of previous image');
|
||||
//test('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
|
||||
//test.skip('Can zoom into a previous image from thumbstrip in real-time or fixed-time');
|
||||
//test.skip('Clicking on the left arrow should pause the imagery and go to previous image');
|
||||
//test.skip('If the imagery view is in pause mode, it should not be updated when new images come in');
|
||||
//test.skip('If the imagery view is not in pause mode, it should be updated when new images come in');
|
||||
//test.fixme('Can use Mouse Wheel to zoom in and out of previous image');
|
||||
//test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
|
||||
//test.fixme('Can zoom into a previous image from thumbstrip in real-time or fixed-time');
|
||||
//test.fixme('Clicking on the left arrow should pause the imagery and go to previous image');
|
||||
//test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in');
|
||||
//test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
|
||||
});
|
||||
|
||||
test.describe('Example Imagery in Display layout', () => {
|
||||
test.skip('Can use Mouse Wheel to zoom in and out of previous image');
|
||||
test.skip('Can use alt+drag to move around image once zoomed in');
|
||||
test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
|
||||
test.skip('Clicking on the left arrow should pause the imagery and go to previous image');
|
||||
test.skip('If the imagery view is in pause mode, it should not be updated when new images come in');
|
||||
test.skip('If the imagery view is not in pause mode, it should be updated when new images come in');
|
||||
test.fixme('Can use Mouse Wheel to zoom in and out of previous image');
|
||||
test.fixme('Can use alt+drag to move around image once zoomed in');
|
||||
test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
|
||||
test.fixme('Clicking on the left arrow should pause the imagery and go to previous image');
|
||||
test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in');
|
||||
test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
|
||||
});
|
||||
|
||||
test.describe('Example Imagery in Flexible layout', () => {
|
||||
test.skip('Can use Mouse Wheel to zoom in and out of previous image');
|
||||
test.skip('Can use alt+drag to move around image once zoomed in');
|
||||
test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
|
||||
test.skip('Clicking on the left arrow should pause the imagery and go to previous image');
|
||||
test.skip('If the imagery view is in pause mode, it should not be updated when new images come in');
|
||||
test.skip('If the imagery view is not in pause mode, it should be updated when new images come in');
|
||||
test.fixme('Can use Mouse Wheel to zoom in and out of previous image');
|
||||
test.fixme('Can use alt+drag to move around image once zoomed in');
|
||||
test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
|
||||
test.fixme('Clicking on the left arrow should pause the imagery and go to previous image');
|
||||
test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in');
|
||||
test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
|
||||
});
|
||||
|
||||
test.describe('Example Imagery in Tabs view', () => {
|
||||
test.skip('Can use Mouse Wheel to zoom in and out of previous image');
|
||||
test.skip('Can use alt+drag to move around image once zoomed in');
|
||||
test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
|
||||
test.skip('Can zoom into a previous image from thumbstrip in real-time or fixed-time');
|
||||
test.skip('Clicking on the left arrow should pause the imagery and go to previous image');
|
||||
test.skip('If the imagery view is in pause mode, it should not be updated when new images come in');
|
||||
test.skip('If the imagery view is not in pause mode, it should be updated when new images come in');
|
||||
test.fixme('Can use Mouse Wheel to zoom in and out of previous image');
|
||||
test.fixme('Can use alt+drag to move around image once zoomed in');
|
||||
test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
|
||||
test.fixme('Can zoom into a previous image from thumbstrip in real-time or fixed-time');
|
||||
test.fixme('Clicking on the left arrow should pause the imagery and go to previous image');
|
||||
test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in');
|
||||
test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
Test for plot autoscale.
|
||||
Testsuite for plot autoscale.
|
||||
*/
|
||||
|
||||
const { test: _test, expect } = require('@playwright/test');
|
||||
@@ -47,7 +47,7 @@ test.use({
|
||||
});
|
||||
|
||||
test.describe('ExportAsJSON', () => {
|
||||
test('autoscale off causes no error from undefined user range', async ({ page }) => {
|
||||
test.slow('User can set autoscale with a valid range @snapshot', async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
await setTimeRange(page);
|
||||
@@ -68,14 +68,6 @@ test.describe('ExportAsJSON', () => {
|
||||
.then(shot => expect(shot).toMatchSnapshot('autoscale-canvas-prepan.png', { maxDiffPixels: 40 }))
|
||||
]);
|
||||
|
||||
let errorCount = 0;
|
||||
|
||||
function onError() {
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
page.on('pageerror', onError);
|
||||
|
||||
await page.keyboard.down('Alt');
|
||||
|
||||
await canvas.dragTo(canvas, {
|
||||
@@ -91,12 +83,6 @@ test.describe('ExportAsJSON', () => {
|
||||
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
page.off('pageerror', onError);
|
||||
|
||||
// There would have been an error at this point. So if there isn't, then
|
||||
// we fixed it.
|
||||
expect(errorCount).toBe(0);
|
||||
|
||||
// Ensure the drag worked.
|
||||
await Promise.all([
|
||||
testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00']),
|
||||
@@ -134,9 +120,14 @@ async function createSinewaveOverlayPlot(page) {
|
||||
// add overlay plot with defaults
|
||||
await page.locator('li:has-text("Overlay Plot")').click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-overlay' }*/),
|
||||
page.locator('text=OK').click()
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=OK').click(),
|
||||
//Wait for Save Banner to appear1
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||
|
||||
// save (exit edit mode)
|
||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||
@@ -148,14 +139,19 @@ async function createSinewaveOverlayPlot(page) {
|
||||
// add sine wave generator with defaults
|
||||
await page.locator('li:has-text("Sine Wave Generator")').click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396/5cfa5c69-17bc-4a99-9545-4da8125380c5?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-single' }*/),
|
||||
page.locator('text=OK').click()
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=OK').click(),
|
||||
//Wait for Save Banner to appear1
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||
|
||||
// focus the overlay plot
|
||||
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-overlay' }*/),
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Overlay Plot').first().click()
|
||||
]);
|
||||
}
|
||||
@@ -168,11 +164,18 @@ async function turnOffAutoscale(page) {
|
||||
await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click();
|
||||
|
||||
// uncheck autoscale
|
||||
await page.locator('text=Y Axis Scaling Auto scale Padding >> input[type="checkbox"]').uncheck();
|
||||
await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"] >> nth=1').uncheck();
|
||||
|
||||
// save
|
||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
await Promise.all([
|
||||
page.locator('text=Save and Finish Editing').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,6 +183,7 @@ async function turnOffAutoscale(page) {
|
||||
*/
|
||||
async function testYTicks(page, values) {
|
||||
const yTicks = page.locator('.gl-plot-y-tick-label');
|
||||
await page.locator('canvas >> nth=1').hover();
|
||||
let promises = [yTicks.count().then(c => expect(c).toBe(values.length))];
|
||||
|
||||
for (let i = 0, l = values.length; i < l; i += 1) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 18 KiB |
@@ -21,13 +21,14 @@
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
Tests to verify log plot functionality.
|
||||
Tests to verify log plot functionality. Note this test suite if very much under active development and should not
|
||||
necessarily be used for reference when writing new tests in this area.
|
||||
*/
|
||||
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
test.describe('Log plot tests', () => {
|
||||
test.only('Can create a log plot.', async ({ page }) => {
|
||||
test.slow('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({ page }) => {
|
||||
await makeOverlayPlot(page);
|
||||
await testRegularTicks(page);
|
||||
await enableEditMode(page);
|
||||
@@ -39,17 +40,20 @@ test.describe('Log plot tests', () => {
|
||||
await testLogTicks(page);
|
||||
await saveOverlayPlot(page);
|
||||
await testLogTicks(page);
|
||||
await testLogPlotPixels(page);
|
||||
//await testLogPlotPixels(page);
|
||||
|
||||
// refresh page
|
||||
await page.reload();
|
||||
// refresh page and wait for charts and ticks to load
|
||||
await page.waitForTimeout(1 * 1000);
|
||||
await page.reload({ waitUntil: 'networkidle'});
|
||||
await page.waitForSelector('.gl-plot-chart-area');
|
||||
await page.waitForSelector('.gl-plot-y-tick-label');
|
||||
|
||||
// test log ticks hold up after refresh
|
||||
await testLogTicks(page);
|
||||
await testLogPlotPixels(page);
|
||||
//await testLogPlotPixels(page);
|
||||
});
|
||||
|
||||
test.only('Verify that log mode option is reflected in import/export JSON', async ({ page }) => {
|
||||
test.skip('Verify that log mode option is reflected in import/export JSON', async ({ page }) => {
|
||||
await makeOverlayPlot(page);
|
||||
await enableEditMode(page);
|
||||
await enableLogMode(page);
|
||||
@@ -57,7 +61,7 @@ test.describe('Log plot tests', () => {
|
||||
|
||||
// TODO ...export, delete the overlay, then import it...
|
||||
|
||||
await testLogTicks(page);
|
||||
//await testLogTicks(page);
|
||||
|
||||
// TODO, the plot is slightly at different position that in the other test, so this fails.
|
||||
// ...We can fix it by copying all steps from the first test...
|
||||
@@ -88,14 +92,18 @@ async function makeOverlayPlot(page) {
|
||||
await page.locator('button.c-create-button').click();
|
||||
await page.locator('li:has-text("Overlay Plot")').click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/8caf7072-535b-4af6-8394-edd86e3ea35f?tc.mode=fixed&tc.startBound=1648590633191&tc.endBound=1648592433191&tc.timeSystem=utc&view=plot-overlay' }*/),
|
||||
page.locator('text=OK').click()
|
||||
page.waitForNavigation({ waitUntil: 'networkidle'}),
|
||||
page.locator('text=OK').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||
|
||||
// save the overlay plot
|
||||
|
||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
await saveOverlayPlot(page);
|
||||
|
||||
// create a sinewave generator
|
||||
|
||||
@@ -116,15 +124,20 @@ async function makeOverlayPlot(page) {
|
||||
// Click OK to make generator
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/8caf7072-535b-4af6-8394-edd86e3ea35f/6e58b26a-8a73-4df6-b3a6-918decc0bbfa?tc.mode=fixed&tc.startBound=1648590633191&tc.endBound=1648592433191&tc.timeSystem=utc&view=plot-single' }*/),
|
||||
page.locator('text=OK').click()
|
||||
page.waitForNavigation({ waitUntil: 'networkidle'}),
|
||||
page.locator('text=OK').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||
|
||||
// click on overlay plot
|
||||
|
||||
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||
await Promise.all([
|
||||
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/8caf7072-535b-4af6-8394-edd86e3ea35f?tc.mode=fixed&tc.startBound=1648590633191&tc.endBound=1648592433191&tc.timeSystem=utc&view=plot-overlay' }*/),
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Overlay Plot').first().click()
|
||||
]);
|
||||
}
|
||||
@@ -133,7 +146,7 @@ async function makeOverlayPlot(page) {
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function testRegularTicks(page) {
|
||||
const yTicks = page.locator('.gl-plot-y-tick-label');
|
||||
const yTicks = await page.locator('.gl-plot-y-tick-label');
|
||||
expect(await yTicks.count()).toBe(7);
|
||||
await expect(yTicks.nth(0)).toHaveText('-2');
|
||||
await expect(yTicks.nth(1)).toHaveText('0');
|
||||
@@ -148,7 +161,7 @@ async function testRegularTicks(page) {
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function testLogTicks(page) {
|
||||
const yTicks = page.locator('.gl-plot-y-tick-label');
|
||||
const yTicks = await page.locator('.gl-plot-y-tick-label');
|
||||
expect(await yTicks.count()).toBe(28);
|
||||
await expect(yTicks.nth(0)).toHaveText('-2.98');
|
||||
await expect(yTicks.nth(1)).toHaveText('-2.50');
|
||||
@@ -186,6 +199,7 @@ async function testLogTicks(page) {
|
||||
async function enableEditMode(page) {
|
||||
// turn on edit mode
|
||||
await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click();
|
||||
await expect(await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1)).toBeVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,7 +224,15 @@ async function disableLogMode(page) {
|
||||
async function saveOverlayPlot(page) {
|
||||
// save overlay plot
|
||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
|
||||
await Promise.all([
|
||||
page.locator('text=Save and Finish Editing').click(),
|
||||
//Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
//Wait until Save Banner is gone
|
||||
await page.locator('.c-message-banner__close-button').click();
|
||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +242,7 @@ async function testLogPlotPixels(page) {
|
||||
const pixelsMatch = await page.evaluate(async () => {
|
||||
// TODO get canvas pixels at a few locations to make sure they're the correct color, to test that the plot comes out as expected.
|
||||
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
await new Promise((r) => setTimeout(r, 5 * 1000));
|
||||
|
||||
// These are some pixels that should be blue points in the log plot.
|
||||
// If the plot changes shape to an unexpected shape, this will
|
||||
@@ -67,3 +67,46 @@ test.describe('Time counductor operations', () => {
|
||||
expect(endDateValidityStatus).not.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Testing instructions:
|
||||
// Try to change the realtime offsets when in realtime (local clock) mode.
|
||||
test.describe('Time conductor input fields real-time mode', () => {
|
||||
test('validate input fields in real-time mode', async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
// Set realtime "local clock" mode offsets
|
||||
const timeInputs = page.locator('input.c-input--datetime');
|
||||
|
||||
// Click fixed timespan button
|
||||
await page.locator('.c-button__label >> text=Fixed Timespan').click();
|
||||
|
||||
// Click local clock
|
||||
await page.locator('.icon-clock >> text=Local Clock').click();
|
||||
|
||||
// Click time offset button
|
||||
await page.locator('.c-conductor__delta-button >> text=00:30:00').click();
|
||||
|
||||
// Input start time offset
|
||||
await page.fill('.pr-time-controls__secs', '23');
|
||||
|
||||
// Click the check button
|
||||
await page.locator('.icon-check').click();
|
||||
|
||||
// Verify time was updated on time offset button
|
||||
await expect(page.locator('.c-conductor__delta-button').first()).toContainText('00:30:23');
|
||||
|
||||
// Click time offset set preceding now button
|
||||
await page.locator('.c-conductor__delta-button >> text=00:00:30').click();
|
||||
|
||||
// Input preceding time offset
|
||||
await page.fill('.pr-time-controls__secs', '31')
|
||||
|
||||
// Click the check buttons
|
||||
await page.locator('.icon-check').click();
|
||||
|
||||
// Verify time was updated on preceding time offset button
|
||||
await expect(page.locator('.c-conductor__delta-button').nth(1)).toContainText('00:00:31');
|
||||
});
|
||||
});
|
||||
|
||||
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": "[]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
22
package.json
22
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "2.0.3-SNAPSHOT",
|
||||
"version": "2.0.4-SNAPSHOT",
|
||||
"description": "The Open MCT core platform",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.16.3",
|
||||
"@braintree/sanitize-url": "6.0.0",
|
||||
"@percy/cli": "1.0.4",
|
||||
"@percy/playwright": "1.0.2",
|
||||
"@playwright/test": "1.19.2",
|
||||
"@playwright/test": "1.21.1",
|
||||
"@types/eventemitter3": "^1.0.0",
|
||||
"@types/jasmine": "^4.0.1",
|
||||
"@types/karma": "^6.3.2",
|
||||
@@ -20,12 +20,12 @@
|
||||
"copy-webpack-plugin": "10.2.0",
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "4.0.0",
|
||||
"d3-axis": "1.0.x",
|
||||
"d3-scale": "1.0.x",
|
||||
"d3-selection": "1.3.x",
|
||||
"d3-axis": "3.0.0",
|
||||
"d3-scale": "3.3.0",
|
||||
"d3-selection": "3.0.0",
|
||||
"eslint": "8.13.0",
|
||||
"eslint-plugin-compat": "4.0.2",
|
||||
"eslint-plugin-playwright": "0.8.0",
|
||||
"eslint-plugin-playwright": "0.9.0",
|
||||
"eslint-plugin-vue": "8.5.0",
|
||||
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
|
||||
"eventemitter3": "1.2.0",
|
||||
@@ -52,13 +52,13 @@
|
||||
"location-bar": "3.0.1",
|
||||
"lodash": "4.17.21",
|
||||
"mini-css-extract-plugin": "2.6.0",
|
||||
"moment": "2.29.1",
|
||||
"moment": "2.29.3",
|
||||
"moment-duration-format": "2.3.2",
|
||||
"moment-timezone": "0.5.34",
|
||||
"node-bourbon": "4.2.3",
|
||||
"painterro": "1.2.56",
|
||||
"plotly.js-basic-dist": "2.5.0",
|
||||
"plotly.js-gl2d-dist": "2.5.0",
|
||||
"plotly.js-basic-dist": "2.12.0",
|
||||
"plotly.js-gl2d-dist": "2.12.0",
|
||||
"printj": "1.3.1",
|
||||
"request": "2.88.2",
|
||||
"resolve-url-loader": "5.0.0",
|
||||
@@ -92,9 +92,9 @@
|
||||
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
|
||||
"test:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
|
||||
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
|
||||
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition timeConductor",
|
||||
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition timeConductor branding clock",
|
||||
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
|
||||
"test:e2e:debug": "npm run test:e2e:local -- --debug",
|
||||
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome --grep @snapshot --update-snapshots",
|
||||
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js default",
|
||||
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js",
|
||||
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
||||
|
||||
@@ -241,8 +241,6 @@ define([
|
||||
this.branding = BrandingAPI.default;
|
||||
|
||||
// Plugins that are installed by default
|
||||
|
||||
this.install(this.plugins.Gauge());
|
||||
this.install(this.plugins.Plot());
|
||||
this.install(this.plugins.Chart());
|
||||
this.install(this.plugins.TelemetryTable.default());
|
||||
|
||||
@@ -22,12 +22,14 @@
|
||||
|
||||
export default class Transaction {
|
||||
constructor(objectAPI) {
|
||||
this.dirtyObjects = new Set();
|
||||
this.dirtyObjects = {};
|
||||
this.objectAPI = objectAPI;
|
||||
}
|
||||
|
||||
add(object) {
|
||||
this.dirtyObjects.add(object);
|
||||
const key = this.objectAPI.makeKeyString(object.identifier);
|
||||
|
||||
this.dirtyObjects[key] = object;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
@@ -37,7 +39,8 @@ export default class Transaction {
|
||||
commit() {
|
||||
const promiseArray = [];
|
||||
const save = this.objectAPI.save.bind(this.objectAPI);
|
||||
this.dirtyObjects.forEach(object => {
|
||||
|
||||
Object.values(this.dirtyObjects).forEach(object => {
|
||||
promiseArray.push(this.createDirtyObjectPromise(object, save));
|
||||
});
|
||||
|
||||
@@ -48,7 +51,9 @@ export default class Transaction {
|
||||
return new Promise((resolve, reject) => {
|
||||
action(object)
|
||||
.then((success) => {
|
||||
this.dirtyObjects.delete(object);
|
||||
const key = this.objectAPI.makeKeyString(object.identifier);
|
||||
|
||||
delete this.dirtyObjects[key];
|
||||
resolve(success);
|
||||
})
|
||||
.catch(reject);
|
||||
@@ -57,7 +62,8 @@ export default class Transaction {
|
||||
|
||||
getDirtyObject(identifier) {
|
||||
let dirtyObject;
|
||||
this.dirtyObjects.forEach(object => {
|
||||
|
||||
Object.values(this.dirtyObjects).forEach(object => {
|
||||
const areIdsEqual = this.objectAPI.areIdsEqual(object.identifier, identifier);
|
||||
if (areIdsEqual) {
|
||||
dirtyObject = object;
|
||||
@@ -67,14 +73,11 @@ export default class Transaction {
|
||||
return dirtyObject;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.dirtyObjects = new Set();
|
||||
}
|
||||
|
||||
_clear() {
|
||||
const promiseArray = [];
|
||||
const refresh = this.objectAPI.refresh.bind(this.objectAPI);
|
||||
this.dirtyObjects.forEach(object => {
|
||||
|
||||
Object.values(this.dirtyObjects).forEach(object => {
|
||||
promiseArray.push(this.createDirtyObjectPromise(object, refresh));
|
||||
});
|
||||
|
||||
|
||||
@@ -34,24 +34,24 @@ describe("Transaction Class", () => {
|
||||
});
|
||||
|
||||
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', () => {
|
||||
const mockDomainObjects = createMockDomainObjects();
|
||||
transaction.add(mockDomainObjects[0]);
|
||||
expect(transaction.dirtyObjects.size).toEqual(1);
|
||||
expect(Object.keys(transaction.dirtyObjects).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('cancel(), clears all dirtyObjects', (done) => {
|
||||
const mockDomainObjects = createMockDomainObjects(3);
|
||||
mockDomainObjects.forEach(transaction.add.bind(transaction));
|
||||
|
||||
expect(transaction.dirtyObjects.size).toEqual(3);
|
||||
expect(Object.keys(transaction.dirtyObjects).length).toEqual(3);
|
||||
|
||||
transaction.cancel()
|
||||
.then(success => {
|
||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
||||
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||
}).finally(done);
|
||||
});
|
||||
|
||||
@@ -59,12 +59,12 @@ describe("Transaction Class", () => {
|
||||
const mockDomainObjects = createMockDomainObjects(3);
|
||||
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();
|
||||
|
||||
transaction.commit()
|
||||
.then(success => {
|
||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
||||
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||
expect(objectAPI.save.calls.count()).toEqual(3);
|
||||
}).finally(done);
|
||||
});
|
||||
@@ -73,7 +73,7 @@ describe("Transaction Class", () => {
|
||||
const mockDomainObjects = createMockDomainObjects();
|
||||
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);
|
||||
|
||||
expect(dirtyObject).toEqual(mockDomainObjects[0]);
|
||||
@@ -82,7 +82,7 @@ describe("Transaction Class", () => {
|
||||
it('getDirtyObject(), returns empty dirtyObject for no active transaction', () => {
|
||||
const mockDomainObjects = createMockDomainObjects();
|
||||
|
||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
||||
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||
const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier);
|
||||
|
||||
expect(dirtyObject).toEqual(undefined);
|
||||
|
||||
@@ -185,8 +185,8 @@ export class TelemetryCollection extends EventEmitter {
|
||||
|
||||
for (let datum of data) {
|
||||
parsedValue = this.parseTime(datum);
|
||||
beforeStartOfBounds = parsedValue <= this.lastBounds.start;
|
||||
afterEndOfBounds = parsedValue >= this.lastBounds.end;
|
||||
beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
||||
afterEndOfBounds = parsedValue > this.lastBounds.end;
|
||||
|
||||
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
||||
let isDuplicate = false;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* 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.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.bounds = this.openmct.time.bounds();
|
||||
|
||||
this.limitEvaluator = this.openmct
|
||||
.telemetry
|
||||
.limitEvaluator(this.domainObject);
|
||||
|
||||
this.openmct.time.on('timeSystem', this.updateTimeSystem);
|
||||
this.openmct.time.on('bounds', this.updateBounds);
|
||||
|
||||
this.timestampKey = this.openmct.time.timeSystem().key;
|
||||
|
||||
@@ -135,72 +132,41 @@ export default {
|
||||
|
||||
this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined;
|
||||
|
||||
this.unsubscribe = this.openmct
|
||||
.telemetry
|
||||
.subscribe(this.domainObject, this.setLatestValues);
|
||||
|
||||
this.requestHistory();
|
||||
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {
|
||||
size: 1,
|
||||
strategy: 'latest'
|
||||
});
|
||||
this.telemetryCollection.on('add', this.setLatestValues);
|
||||
this.telemetryCollection.on('clear', this.resetValues);
|
||||
this.telemetryCollection.load();
|
||||
|
||||
if (this.hasUnits) {
|
||||
this.setUnit();
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
this.unsubscribe();
|
||||
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: {
|
||||
updateView() {
|
||||
if (!this.updatingView) {
|
||||
this.updatingView = true;
|
||||
requestAnimationFrame(() => {
|
||||
let newTimestamp = this.getParsedTimestamp(this.latestDatum);
|
||||
|
||||
if (this.shouldUpdate(newTimestamp)) {
|
||||
this.timestamp = newTimestamp;
|
||||
this.datum = this.latestDatum;
|
||||
}
|
||||
|
||||
this.timestamp = this.getParsedTimestamp(this.latestDatum);
|
||||
this.datum = this.latestDatum;
|
||||
this.updatingView = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
setLatestValues(datum) {
|
||||
this.latestDatum = datum;
|
||||
|
||||
setLatestValues(data) {
|
||||
this.latestDatum = data[data.length - 1];
|
||||
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) {
|
||||
this.resetValues();
|
||||
this.timestampKey = timeSystem.key;
|
||||
},
|
||||
updateViewContext() {
|
||||
@@ -241,4 +207,3 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ describe("The LAD Table", () => {
|
||||
|
||||
let openmct;
|
||||
let ladPlugin;
|
||||
let historicalProvider;
|
||||
let parent;
|
||||
let child;
|
||||
let telemetryCount = 3;
|
||||
@@ -81,6 +82,13 @@ describe("The LAD Table", () => {
|
||||
|
||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||
|
||||
historicalProvider = {
|
||||
request: () => {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
};
|
||||
spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider);
|
||||
|
||||
openmct.time.bounds({
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
@@ -147,7 +155,7 @@ describe("The LAD Table", () => {
|
||||
// add another telemetry object as composition in lad table to test multi rows
|
||||
mockObj.ladTable.composition.push(anotherTelemetryObj.identifier);
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeEach(async (done) => {
|
||||
let telemetryRequestResolve;
|
||||
let telemetryObjectResolve;
|
||||
let anotherTelemetryObjectResolve;
|
||||
@@ -166,11 +174,12 @@ describe("The LAD Table", () => {
|
||||
callBack();
|
||||
});
|
||||
|
||||
openmct.telemetry.request.and.callFake(() => {
|
||||
historicalProvider.request = () => {
|
||||
telemetryRequestResolve(mockTelemetry);
|
||||
|
||||
return telemetryRequestPromise;
|
||||
});
|
||||
};
|
||||
|
||||
openmct.objects.get.and.callFake((obj) => {
|
||||
if (obj.key === 'telemetry-object') {
|
||||
telemetryObjectResolve(mockObj.telemetry);
|
||||
@@ -195,6 +204,8 @@ describe("The LAD Table", () => {
|
||||
|
||||
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
|
||||
await Vue.nextTick();
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it("should show one row per object in the composition", () => {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
:href="url"
|
||||
>
|
||||
<div class="c-condition-widget__label">
|
||||
{{ internalDomainObject.conditionalLabel || internalDomainObject.label }}
|
||||
{{ label }}
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
@@ -39,28 +39,112 @@ export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data: function () {
|
||||
return {
|
||||
internalDomainObject: this.domainObject
|
||||
conditionalLabel: '',
|
||||
conditionSetIdentifier: null,
|
||||
domainObjectLabel: '',
|
||||
url: null,
|
||||
urlDefined: false,
|
||||
useConditionSetOutputAsLabel: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
urlDefined() {
|
||||
return this.internalDomainObject.url && this.internalDomainObject.url.length > 0;
|
||||
},
|
||||
url() {
|
||||
return this.urlDefined ? sanitizeUrl(this.internalDomainObject.url) : null;
|
||||
label() {
|
||||
return this.useConditionSetOutputAsLabel
|
||||
? this.conditionalLabel
|
||||
: this.domainObjectLabel
|
||||
;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
conditionSetIdentifier: {
|
||||
handler(newValue, oldValue) {
|
||||
if (!oldValue || !newValue || !this.openmct.objects.areIdsEqual(newValue, oldValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listenToConditionSetChanges();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
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() {
|
||||
this.conditionSetIdentifier = null;
|
||||
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
|
||||
this.stopListeningToConditionSetChanges();
|
||||
},
|
||||
methods: {
|
||||
updateInternalDomainObject(domainObject) {
|
||||
this.internalDomainObject = domainObject;
|
||||
async listenToConditionSetChanges() {
|
||||
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);
|
||||
}
|
||||
|
||||
this.openmct.time.on("bounds", this.refreshData);
|
||||
|
||||
this.status = this.openmct.status.get(this.item.identifier);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.removeSubscription();
|
||||
this.removeStatusListener();
|
||||
|
||||
if (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) {
|
||||
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}`;
|
||||
},
|
||||
requestHistoricalData() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let options = {
|
||||
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));
|
||||
setLatestValues(data) {
|
||||
this.latestDatum = data[data.length - 1];
|
||||
this.updateView();
|
||||
},
|
||||
updateView() {
|
||||
if (!this.updatingView) {
|
||||
@@ -291,17 +266,10 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
removeSubscription() {
|
||||
if (this.subscription) {
|
||||
this.subscription();
|
||||
this.subscription = undefined;
|
||||
}
|
||||
},
|
||||
refreshData(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
this.latestDatum = undefined;
|
||||
this.updateView();
|
||||
this.requestHistoricalData(this.domainObject);
|
||||
}
|
||||
},
|
||||
setObject(domainObject) {
|
||||
@@ -315,8 +283,13 @@ export default {
|
||||
const valueMetadata = this.metadata.value(this.item.value);
|
||||
this.customStringformatter = this.openmct.telemetry.customStringFormatter(valueMetadata, this.item.format);
|
||||
|
||||
this.requestHistoricalData();
|
||||
this.subscribeToObject();
|
||||
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {
|
||||
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.unshift(this.domainObject);
|
||||
|
||||
@@ -53,6 +53,8 @@ describe('Gauge plugin', () => {
|
||||
openmct = createOpenMct();
|
||||
openmct.on('start', done);
|
||||
|
||||
openmct.install(openmct.plugins.Gauge());
|
||||
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
@@ -190,28 +192,27 @@ describe('Gauge plugin', () => {
|
||||
});
|
||||
|
||||
it('renders gauge element', () => {
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||
expect(gaugeElement.length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders major elements', () => {
|
||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||
const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
|
||||
const dialElement = gaugeHolder.querySelector('.c-dial');
|
||||
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.js-gauge-dial-range');
|
||||
const valueElement = gaugeHolder.querySelector('.js-dial-current-value');
|
||||
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||
|
||||
expect(hasMajorElements).toBe(true);
|
||||
});
|
||||
|
||||
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) => {
|
||||
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));
|
||||
done();
|
||||
}
|
||||
@@ -326,28 +327,27 @@ describe('Gauge plugin', () => {
|
||||
});
|
||||
|
||||
it('renders gauge element', () => {
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||
expect(gaugeElement.length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders major elements', () => {
|
||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||
const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
|
||||
const dialElement = gaugeHolder.querySelector('.c-dial');
|
||||
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.js-gauge-dial-range');
|
||||
const valueElement = gaugeHolder.querySelector('.js-dial-current-value');
|
||||
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||
|
||||
expect(hasMajorElements).toBe(true);
|
||||
});
|
||||
|
||||
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) => {
|
||||
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));
|
||||
done();
|
||||
}
|
||||
@@ -462,28 +462,27 @@ describe('Gauge plugin', () => {
|
||||
});
|
||||
|
||||
it('renders gauge element', () => {
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||
expect(gaugeElement.length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders major elements', () => {
|
||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||
const curveElement = gaugeHolder.querySelector('.c-meter');
|
||||
const dialElement = gaugeHolder.querySelector('.c-meter__bg');
|
||||
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range');
|
||||
const valueElement = gaugeHolder.querySelector('.js-meter-current-value');
|
||||
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||
|
||||
expect(hasMajorElements).toBe(true);
|
||||
});
|
||||
|
||||
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) => {
|
||||
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));
|
||||
done();
|
||||
}
|
||||
@@ -560,17 +559,16 @@ describe('Gauge plugin', () => {
|
||||
});
|
||||
|
||||
it('renders gauge element', () => {
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||
expect(gaugeElement.length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders major elements', () => {
|
||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||
const curveElement = gaugeHolder.querySelector('.c-meter');
|
||||
const dialElement = gaugeHolder.querySelector('.c-meter__bg');
|
||||
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range');
|
||||
const valueElement = gaugeHolder.querySelector('.js-meter-current-value');
|
||||
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||
|
||||
expect(hasMajorElements).toBe(true);
|
||||
});
|
||||
@@ -643,17 +641,16 @@ describe('Gauge plugin', () => {
|
||||
});
|
||||
|
||||
it('renders gauge element', () => {
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||
expect(gaugeElement.length).toBe(1);
|
||||
});
|
||||
|
||||
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 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);
|
||||
});
|
||||
@@ -772,28 +769,27 @@ describe('Gauge plugin', () => {
|
||||
});
|
||||
|
||||
it('renders gauge element', () => {
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||
const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper');
|
||||
expect(gaugeElement.length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders major elements', () => {
|
||||
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||
const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
|
||||
const dialElement = gaugeHolder.querySelector('.c-dial');
|
||||
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.js-gauge-dial-range');
|
||||
const valueElement = gaugeHolder.querySelector('.js-dial-current-value');
|
||||
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||
|
||||
expect(hasMajorElements).toBe(true);
|
||||
});
|
||||
|
||||
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) => {
|
||||
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));
|
||||
done();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ export default function GaugeViewProvider(openmct) {
|
||||
return domainObject.type === 'gauge';
|
||||
},
|
||||
canEdit: function (domainObject) {
|
||||
return false;
|
||||
if (domainObject.type === 'gauge') {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
@@ -21,168 +21,259 @@
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div
|
||||
class="c-gauge"
|
||||
class="c-gauge__wrapper js-gauge-wrapper"
|
||||
:class="`c-gauge--${gaugeType}`"
|
||||
>
|
||||
<div class="c-gauge__wrapper">
|
||||
<template v-if="typeDial">
|
||||
<svg
|
||||
class="c-gauge__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>
|
||||
<template v-if="typeDial">
|
||||
<svg
|
||||
width="0"
|
||||
height="0"
|
||||
class="c-dial__clip-paths"
|
||||
>
|
||||
<defs>
|
||||
<clipPath
|
||||
id="gaugeBgMask"
|
||||
clipPathUnits="objectBoundingBox"
|
||||
>
|
||||
<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" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="gaugeValueMask"
|
||||
clipPathUnits="objectBoundingBox"
|
||||
>
|
||||
<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
|
||||
v-if="displayCurVal"
|
||||
class="c-gauge__curval"
|
||||
class="c-dial__current-value-text-sizer"
|
||||
:viewBox="curValViewBox"
|
||||
>
|
||||
<text
|
||||
class="c-gauge__curval-text"
|
||||
class="c-dial__current-value-text js-dial-current-value"
|
||||
lengthAdjust="spacing"
|
||||
text-anchor="middle"
|
||||
style="transform: translate(50%, 70%)"
|
||||
>{{ curVal }}</text>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
<div class="c-dial">
|
||||
<svg
|
||||
class="c-dial__bg"
|
||||
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
|
||||
class="c-dial__bg"
|
||||
viewBox="0 0 10 10"
|
||||
>
|
||||
|
||||
<svg
|
||||
v-if="limitHigh && dialHighLimitDeg < 270"
|
||||
class="c-dial__limit-high"
|
||||
viewBox="0 0 512 512"
|
||||
:class="{
|
||||
'c-high-limit-clip--90': dialHighLimitDeg > 90,
|
||||
'c-high-limit-clip--180': dialHighLimitDeg >= 180
|
||||
}"
|
||||
>
|
||||
<path
|
||||
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"
|
||||
:style="`transform: rotate(${dialHighLimitDeg}deg)`"
|
||||
/>
|
||||
</svg>
|
||||
<g
|
||||
v-if="limitLow !== null && dialLowLimitDeg < getLimitDegree('low', 'max')"
|
||||
class="c-dial__limit-low"
|
||||
:style="`transform: rotate(${dialLowLimitDeg}deg)`"
|
||||
>
|
||||
<rect
|
||||
v-if="dialLowLimitDeg >= getLimitDegree('low', 'q1')"
|
||||
class="c-dial__low-limit__low"
|
||||
x="5"
|
||||
y="5"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<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
|
||||
v-if="limitLow && dialLowLimitDeg < 270"
|
||||
class="c-dial__limit-low"
|
||||
viewBox="0 0 512 512"
|
||||
:class="{
|
||||
'c-dial-clip--90': dialLowLimitDeg < 90,
|
||||
'c-dial-clip--180': dialLowLimitDeg >= 90 && dialLowLimitDeg < 180
|
||||
}"
|
||||
>
|
||||
<path
|
||||
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"
|
||||
:style="`transform: rotate(${dialLowLimitDeg}deg)`"
|
||||
/>
|
||||
</svg>
|
||||
<g
|
||||
v-if="limitHigh !== null && dialHighLimitDeg < getLimitDegree('high', 'max')"
|
||||
class="c-dial__limit-high"
|
||||
:style="`transform: rotate(${dialHighLimitDeg}deg)`"
|
||||
>
|
||||
<rect
|
||||
v-if="dialHighLimitDeg <= getLimitDegree('high', 'max')"
|
||||
class="c-dial__high-limit__low"
|
||||
x="0"
|
||||
y="5"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<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
|
||||
class="c-dial__value"
|
||||
viewBox="0 0 512 512"
|
||||
:class="{
|
||||
'c-dial-clip--90': degValue < 90 && typeFilledDial,
|
||||
'c-dial-clip--180': degValue >= 90 && degValue < 180 && typeFilledDial
|
||||
}"
|
||||
>
|
||||
<path
|
||||
v-if="typeFilledDial && degValue > 0"
|
||||
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"
|
||||
:style="`transform: rotate(${degValue}deg)`"
|
||||
/>
|
||||
<path
|
||||
v-if="typeNeedleDial && valueInBounds"
|
||||
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"
|
||||
:style="`transform: rotate(${degValue}deg)`"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
v-if="typeFilledDial"
|
||||
class="c-dial__filled-value-wrapper"
|
||||
viewBox="0 0 10 10"
|
||||
>
|
||||
<g
|
||||
class="c-dial__filled-value"
|
||||
:style="`transform: rotate(${degValueFilledDial}deg)`"
|
||||
>
|
||||
<rect
|
||||
v-if="degValue >= getLimitDegree('low', 'q1')"
|
||||
class="c-dial__filled-value__low"
|
||||
x="5"
|
||||
y="5"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
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>
|
||||
</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 class="c-meter">
|
||||
<div
|
||||
v-if="displayMinMax"
|
||||
class="c-gauge__range c-meter__range"
|
||||
<div
|
||||
v-if="limitHigh !== null && meterHighLimitPerc > 0"
|
||||
class="c-meter__limit-high"
|
||||
:style="`height: ${meterHighLimitPerc}%`"
|
||||
></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
|
||||
v-if="displayCurVal"
|
||||
class="c-gauge__curval"
|
||||
class="c-meter__current-value-text-sizer"
|
||||
:viewBox="curValViewBox"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<text
|
||||
class="c-gauge__curval-text"
|
||||
text-anchor="middle"
|
||||
class="c-dial__current-value-text js-meter-current-value"
|
||||
lengthAdjust="spacing"
|
||||
text-anchor="middle"
|
||||
style="transform: translate(50%, 70%)"
|
||||
>{{ curVal }}</text>
|
||||
</svg>
|
||||
</div>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DIAL_VALUE_DEG_OFFSET, getLimitDegree } from '../gauge-limit-util';
|
||||
|
||||
const LIMIT_PADDING_IN_PERCENT = 10;
|
||||
|
||||
export default {
|
||||
@@ -209,6 +300,13 @@ export default {
|
||||
degValue() {
|
||||
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() {
|
||||
return this.percentToDegrees(this.valToPercent(this.limitHigh));
|
||||
},
|
||||
@@ -299,6 +397,7 @@ export default {
|
||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
||||
},
|
||||
methods: {
|
||||
getLimitDegree: getLimitDegree,
|
||||
addTelemetryObjectAndSubscribe(domainObject) {
|
||||
this.telemetryObject = domainObject;
|
||||
this.request();
|
||||
@@ -340,7 +439,7 @@ export default {
|
||||
return this.gaugeType.indexOf(str) !== -1;
|
||||
},
|
||||
percentToDegrees(vPercent) {
|
||||
return this.round((vPercent / 100) * 270, 2);
|
||||
return this.round(((vPercent / 100) * 270) + DIAL_VALUE_DEG_OFFSET, 2);
|
||||
},
|
||||
removeFromComposition(telemetryObject = this.telemetryObject) {
|
||||
let composition = this.domainObject.composition.filter(id =>
|
||||
@@ -453,7 +552,7 @@ export default {
|
||||
valToPercent(vValue) {
|
||||
// Used by dial
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
:class="model.cssClass"
|
||||
>
|
||||
<ToggleSwitch
|
||||
:id="'gaugeToggle'"
|
||||
:checked="isUseTelemetryLimits"
|
||||
label="Use telemetry limits for minimum and maximum ranges"
|
||||
@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 {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -13,10 +7,8 @@ $limitHighClip180: polygon(100% 0, 100% 100%, 0 100%);
|
||||
|
||||
&.invalid,
|
||||
&.invalid.req { @include validationState($glyph-icon-x, $colorFormInvalid); }
|
||||
|
||||
&.valid,
|
||||
&.valid.req { @include validationState($glyph-icon-check, $colorFormValid); }
|
||||
|
||||
&.req { @include validationState($glyph-icon-asterisk, $colorFormRequired); }
|
||||
}
|
||||
|
||||
@@ -37,92 +29,47 @@ $limitHighClip180: polygon(100% 0, 100% 100%, 0 100%);
|
||||
@include abs();
|
||||
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 */
|
||||
.c-dial {
|
||||
// Dial elements
|
||||
@include abs();
|
||||
clip-path: $dialClip;
|
||||
svg[class*='c-dial'] {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
position: absolute;
|
||||
|
||||
svg,
|
||||
&__ticks,
|
||||
&__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;
|
||||
}
|
||||
g {
|
||||
transform-origin: center;
|
||||
}
|
||||
}
|
||||
|
||||
.c-gauge--dial-needle .c-dial__value {
|
||||
path {
|
||||
.c-dial {
|
||||
&__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;
|
||||
}
|
||||
|
||||
&__current-value-text {
|
||||
fill: $colorGaugeTextValue;
|
||||
font-family: $heroFont;
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************** METER GAUGE */
|
||||
@@ -131,6 +78,13 @@ $limitHighClip180: polygon(100% 0, 100% 100%, 0 100%);
|
||||
@include abs();
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
// current-value-text
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__range {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<div
|
||||
v-if="zoomFactor > 1"
|
||||
class="c-imagery__hints"
|
||||
>Alt-drag to pan</div>
|
||||
>{{formatImageAltText}}</div>
|
||||
<div
|
||||
ref="focusedImageWrapper"
|
||||
class="image-wrapper"
|
||||
@@ -143,13 +143,13 @@
|
||||
<!-- spacecraft position fresh -->
|
||||
<div
|
||||
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>
|
||||
|
||||
<!-- camera position fresh -->
|
||||
<div
|
||||
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>
|
||||
</div>
|
||||
<div class="h-local-controls">
|
||||
@@ -331,6 +331,16 @@ export default {
|
||||
},
|
||||
isImageNew() {
|
||||
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;
|
||||
|
||||
return age < cutoff && !this.refreshCSS;
|
||||
@@ -478,6 +488,16 @@ export default {
|
||||
width: this.sizedImageWidth,
|
||||
height: this.sizedImageHeight
|
||||
};
|
||||
},
|
||||
formatImageAltText() {
|
||||
const regexLinux = /Linux/;
|
||||
const navigator = window.navigator.userAgent;
|
||||
|
||||
if (regexLinux.test(navigator)) {
|
||||
return 'Ctrl+Alt drag to pan';
|
||||
}
|
||||
|
||||
return 'Alt drag to pan';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -514,6 +534,8 @@ export default {
|
||||
if (!this.isPaused) {
|
||||
this.setFocusedImage(imageIndex);
|
||||
this.scrollToRight();
|
||||
} else {
|
||||
this.scrollToFocused();
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
@@ -977,6 +999,7 @@ export default {
|
||||
|
||||
this.setSizedImageDimensions();
|
||||
this.calculateViewHeight();
|
||||
this.scrollToFocused();
|
||||
},
|
||||
setSizedImageDimensions() {
|
||||
this.focusedImageNaturalAspectRatio = this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@use 'sass:math';
|
||||
|
||||
@keyframes fade-out {
|
||||
from {
|
||||
background-color: rgba($colorOk, 0.5);
|
||||
@@ -138,6 +140,9 @@
|
||||
animation-timing-function: ease-in;
|
||||
animation-iteration-count: 1;
|
||||
animation-fill-mode: forwards;
|
||||
&.no-animation {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +216,7 @@
|
||||
}
|
||||
|
||||
.c-thumb {
|
||||
$w: $imageThumbsD / 2;
|
||||
$w: math.div($imageThumbsD, 2);
|
||||
min-width: $w;
|
||||
width: $w;
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Add a line to install the CouchDB plugin for OpenMCT:
|
||||
```
|
||||
openmct.install(openmct.plugins.CouchDB("http://localhost:5984/openmct"));
|
||||
```
|
||||
6. Enable cors in CouchDB by editing `/usr/local/etc/local.ini` and add: `
|
||||
6. Enable cors in CouchDB by editing `~/homebrew/etc/local.ini` and add: `
|
||||
```
|
||||
[chttpd]
|
||||
enable_cors = true
|
||||
@@ -45,4 +45,4 @@ origins = http://localhost:8080
|
||||
9. Navigate to http://localhost:8080/ and create a random object in OpenMCT (e.g., a `Clock`) and save. You may get an error saying that the objects failed to persist. This is a known error that you can ignore, and will only happen the first time you save.
|
||||
10. Navigate to: http://127.0.0.1:5984/_utils/#database/openmct/_all_docs
|
||||
11. Look at the `JSON` tab and ensure you can see the `Clock` object you created above.
|
||||
12. All done! 🏆
|
||||
12. All done! 🏆
|
||||
|
||||
@@ -192,7 +192,6 @@ export default {
|
||||
|
||||
if (this.axisType === 'yAxis' && this.axis.get('logMode')) {
|
||||
return getLogTicks(range.min, range.max, number, 4);
|
||||
// return getLogTicks2(range.min, range.max, number);
|
||||
} else {
|
||||
return ticks(range.min, range.max, number);
|
||||
}
|
||||
@@ -204,6 +203,7 @@ export default {
|
||||
|
||||
updateTicks(forceRegeneration = false) {
|
||||
const range = this.axis.get('displayRange');
|
||||
|
||||
if (!range) {
|
||||
delete this.min;
|
||||
delete this.max;
|
||||
|
||||
@@ -215,6 +215,10 @@ export default class YAxisModel extends Model {
|
||||
|
||||
const _range = this.get('displayRange');
|
||||
|
||||
if (!_range) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.get('logMode')) {
|
||||
_range.min = antisymlog(_range.min, 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;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
this.clear();
|
||||
@@ -450,4 +444,3 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export default {
|
||||
}
|
||||
},
|
||||
drawAxis(bounds, timeSystem) {
|
||||
let viewBounds = Object.assign({}, bounds);
|
||||
let viewBounds = Object.create(bounds);
|
||||
|
||||
this.setScale(viewBounds, timeSystem);
|
||||
this.setAxis(viewBounds);
|
||||
|
||||
@@ -44,7 +44,7 @@ const config = {
|
||||
"bourbon": "bourbon.scss",
|
||||
"plotly-basic": "plotly.js-basic-dist",
|
||||
"plotly-gl2d": "plotly.js-gl2d-dist",
|
||||
"d3-scale": path.join(__dirname, "node_modules/d3-scale/build/d3-scale.min.js"),
|
||||
"d3-scale": path.join(__dirname, "node_modules/d3-scale/dist/d3-scale.min.js"),
|
||||
"printj": path.join(__dirname, "node_modules/printj/dist/printj.min.js"),
|
||||
"styles": path.join(__dirname, "src/styles"),
|
||||
"MCT": path.join(__dirname, "src/MCT"),
|
||||
|
||||
Reference in New Issue
Block a user