Compare commits

..

16 Commits

Author SHA1 Message Date
Joshi
fd17ed07cd Add null checks to prevent hard failure 2022-05-31 13:58:34 -07:00
Nikhil
2a165a4549 Temp source map fix 2.0.4 (#5267)
* use dev mode for production

* mode -> production

* added extra devtool options

* wip
2022-05-26 15:39:35 -07:00
Michael Rogers
6abd395605 Eliminate NaN conditions and clear stale duration (#5248) 2022-05-25 10:52:51 -07:00
Shefali Joshi
610f78b6fb Update version for 2.0.4 (#5255) 2022-05-24 21:51:58 +00:00
Nikhil
663f42ad2e Condition Widgets trigger hundreds of persistence calls (#5146)
Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-05-02 15:05:21 -07:00
Andrew Henry
f80a3c13c1 Reverts forced precision for log plots axis labels (#5147) 2022-05-02 17:07:26 +00:00
Jamie V
a94ec344ea [Telemetry Collections] Include data with start and end bounds (#5145) 2022-04-29 23:01:42 +00:00
Charles Hacskaylo
e75befafbd Dynamic dial-type Gauge sizing by height and width (#5129)
* Improve sizing strategy for gauges.
* Do not install gauge by default for now

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
Co-authored-by: Jamie Vigliotta <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-04-28 14:03:35 -07:00
Nikhil
d7d06b59ea Gauge edit enabled 2.0.3 (#5133)
* Gauge plugin #4896, add edit mode
2022-04-28 13:32:12 -07:00
David Tsay
8b4a55a7ec Fix transactions overwriting latest objects with stale objects on save (#5132)
* use object (map) instead of set to track dirty objects
* fix tests due to internals change

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2022-04-28 12:43:30 -07:00
Michael Rogers
2519e601d7 Added animation styling for POS and CAM; adjusted cutoff for isNewImage (#5116)
* Added animation styling for POS and CAM; adjusted cutoff for isNewImage

* Remove animation from POS and CAM
2022-04-28 12:03:42 -07:00
Jamie V
b7b205621b added telemetry collection to alphanumeric telemetry view (#5131) 2022-04-28 11:35:35 -07:00
Jamie V
3d2d932323 [LAD Tables] Use Telemetry Collections (#5127)
* Use telemetry collections to handle bounds checks
2022-04-28 11:21:58 -07:00
Shefali Joshi
ce28dd2b9f Handle scrolling to focused image on resize/new data (#5121)
* Scroll to focused image when view resizes - this will force scrolling to focused image when going to/from view large mode

* Scroll to the right if there is no paused focused image
2022-04-28 10:44:53 -07:00
Shefali Joshi
286a533dad Fix tick values for plots ticks in log mode and null check (#5119)
* [2297] When there is no display range or range, skip setting the range value when auto scale is turned off.

* If the formatted value is a number and a float, set precision to 2 decimal points.

* Fix value assignment

* Use whole numbers in log mode

* Revert whole numbers fix - need floats for values between 0 and 1.
2022-04-26 13:40:19 -07:00
Joshi
378a4ca282 Release 2.0.3 2022-04-25 11:58:50 -07:00
113 changed files with 562 additions and 5629 deletions

View File

@@ -2,7 +2,7 @@ version: 2.1
executors:
pw-focal-development:
docker:
- image: mcr.microsoft.com/playwright:v1.21.1-focal
- image: mcr.microsoft.com/playwright:v1.19.2-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.3.0
browser-tools: circleci/browser-tools@1.2.3
jobs:
npm-audit:
parameters:

View File

@@ -40,7 +40,6 @@ assignees: ''
- [ ] Is there a workaround available?
- [ ] Does this impact a critical component?
- [ ] Is this just a visual bug with no functional impact?
- [ ] Does this block the execution of e2e tests?
#### Additional Information
<!--- Include any screenshots, gifs, or logs which will expedite triage -->

View File

@@ -16,7 +16,7 @@ Closes <!--- Insert Issue Number(s) this PR addresses. Start by typing # will op
* [ ] Unit tests included and/or updated with changes?
* [ ] Command line build passes?
* [ ] Has this been smoke tested?
* [ ] Testing instructions included in associated issue OR is this a dependency/testcase change?
* [ ] Testing instructions included in associated issue?
### Reviewer Checklist

View File

@@ -32,12 +32,12 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v1
with:
languages: javascript
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v1

View File

@@ -30,7 +30,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: '16'
- run: npx playwright@1.21.1 install
- run: npx playwright@1.19.2 install
- run: npm install
- run: npm run test:e2e:full
- name: Archive test results

View File

@@ -17,7 +17,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: '16'
- run: npx playwright@1.21.1 install
- run: npx playwright@1.19.2 install
- run: npm install
- name: Run the e2e visual tests
run: npm run test:e2e:visual

View File

@@ -1,27 +0,0 @@
/* eslint-disable no-undef */
// This file extends the base functionality of the playwright test framework
const base = require('@playwright/test');
const { expect } = require('@playwright/test');
exports.test = base.test.extend({
page: async ({ baseURL, page }, use) => {
const messages = [];
page.on('console', msg => messages.push(`[${msg.type()}] ${msg.text()}`));
await use(page);
await expect.soft(messages.toString()).not.toContain('[error]');
},
browser: async ({ playwright, browser }, use, workerInfo) => {
// Use browserless if configured
if (workerInfo.project.name.match(/browserless/)) {
const vBrowser = await playwright.chromium.connectOverCDP({
endpointURL: 'ws://localhost:3003'
});
await use(vBrowser);
} else {
// Use Local Browser for testing.
await use(browser);
}
}
});

View File

@@ -2,14 +2,13 @@
// playwright.config.js
// @ts-check
// eslint-disable-next-line no-unused-vars
const { devices } = require('@playwright/test');
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 1,
retries: 2,
testDir: 'tests',
timeout: 60 * 1000,
timeout: 90 * 1000,
webServer: {
command: 'npm run start',
port: 8080,
@@ -29,12 +28,12 @@ const config = {
{
name: 'chrome',
use: {
browserName: 'chromium'
browserName: 'chromium',
...devices['Desktop Chrome']
}
},
{
name: 'MMOC',
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
viewport: {

View File

@@ -2,7 +2,6 @@
// playwright.config.js
// @ts-check
// eslint-disable-next-line no-unused-vars
const { devices } = require('@playwright/test');
/** @type {import('@playwright/test').PlaywrightTestConfig} */
@@ -30,12 +29,12 @@ const config = {
{
name: 'chrome',
use: {
browserName: 'chromium'
browserName: 'chromium',
...devices['Desktop Chrome']
}
},
{
name: 'MMOC',
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
viewport: {

View File

@@ -1,79 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify form functionality.
*/
const { test, expect } = require('@playwright/test');
const TEST_FOLDER = 'test folder';
test.describe('forms set', () => {
test('New folder form has title as required field', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click button:has-text("Create")
await page.click('button:has-text("Create")');
// Click :nth-match(:text("Folder"), 2)
await page.click(':nth-match(:text("Folder"), 2)');
// Click text=Properties Title Notes >> input[type="text"]
await page.click('text=Properties Title Notes >> input[type="text"]');
// Fill text=Properties Title Notes >> input[type="text"]
await page.fill('text=Properties Title Notes >> input[type="text"]', '');
// Press Tab
await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab');
// Click text=OK Cancel
await page.click('text=OK', { force: true });
const okButton = page.locator('text=OK');
await expect(okButton).toBeDisabled();
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/);
// Click text=Properties Title Notes >> input[type="text"]
await page.click('text=Properties Title Notes >> input[type="text"]');
// Fill text=Properties Title Notes >> input[type="text"]
await page.fill('text=Properties Title Notes >> input[type="text"]', TEST_FOLDER);
// Press Tab
await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab');
await expect(page.locator('.c-form-row__state-indicator').first()).not.toHaveClass(/invalid/);
// Click text=OK
await Promise.all([
page.waitForNavigation(),
page.click('text=OK')
]);
await expect(page.locator('.l-browse-bar__object-name')).toContainText(TEST_FOLDER);
});
test.fixme('Create all object types and verify correctness', async ({ page }) => {
//Create the following Domain Objects with their unique Object Types
// Sine Wave Generator (number object)
// Timer Object
// Plan View Object
// Clock Object
// Hyperlink
});
});

View File

@@ -24,8 +24,7 @@
This test suite is dedicated to tests which verify branding related components.
*/
const { test } = require('../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test.describe('Branding tests', () => {
test('About Modal launches with basic branding properties', async ({ page }) => {
@@ -58,6 +57,6 @@ test.describe('Branding tests', () => {
page.waitForEvent('popup'),
page.locator('text=click here for third party licensing information').click()
]);
expect(page2.waitForURL('**/licenses**')).toBeTruthy();
expect(page2.waitForURL('**\/licenses**')).toBeTruthy();
});
});

View File

@@ -24,8 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding the example event generator.
*/
const { test } = require('../../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test.describe('Example Event Generator Operations', () => {
test('Can create example event generator with a name', async ({ page }) => {

View File

@@ -24,8 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
*/
const { test } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test.describe('Sine Wave Generator', () => {
test('Create new Sine Wave Generator Object and validate create Form Logic', async ({ page }) => {

View File

@@ -24,119 +24,19 @@
This test suite is dedicated to tests which verify the basic operations surrounding moving objects.
*/
const { test } = require('../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test.describe('Move item tests', () => {
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 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 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();
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.
});
});

View File

@@ -24,8 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
*/
const { test } = require('../../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
const path = require('path');
// https://github.com/nasa/openmct/issues/4323#issuecomment-1067282651

View File

@@ -24,10 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding exportAsJSON.
*/
const { test } = require('../../../fixtures.js');
// FIXME: Remove this eslint exception once tests are implemented
// eslint-disable-next-line no-unused-vars
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test.describe('ExportAsJSON', () => {
test.fixme('Create a basic object and verify that it can be exported as JSON from Tree', async ({ page }) => {

View File

@@ -24,10 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding importAsJSON.
*/
const { test } = require('../../../fixtures.js');
// FIXME: Remove this eslint exception once tests are implemented
// eslint-disable-next-line no-unused-vars
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test.describe('ExportAsJSON', () => {
test.fixme('Verify that domain object can be importAsJSON from Tree', async ({ page }) => {

View File

@@ -24,8 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding Clock.
*/
const { test } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test.describe('Clock Generator', () => {

View File

@@ -26,8 +26,7 @@ suite is sharing state between tests which is considered an anti-pattern. Implim
demonstrate some playwright for test developers. This pattern should not be re-used in other CRUD suites.
*/
const { test } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
let conditionSetUrl;
let getConditionSetIdentifierFromUrl;

View File

@@ -24,15 +24,13 @@
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 } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');
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,18 +40,12 @@ test.describe('Example Imagery', () => {
// Click text=Example Imagery
await page.click('text=Example Imagery');
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
await page.click('form[name="mctForm"] a:has-text("My Items")');
// Click text=OK
await Promise.all([
page.waitForNavigation({waitUntil: 'networkidle'}),
page.click('text=OK'),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
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')
]);
//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');
});
@@ -85,11 +77,9 @@ 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();
@@ -101,49 +91,40 @@ 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 Promise.all(panHotkey.map(x => page.keyboard.down(x)));
await page.keyboard.down('Alt');
await page.mouse.down();
await page.mouse.move(imageCenterX - 200, imageCenterY, 10);
await page.mouse.up();
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
await page.keyboard.up('Alt');
const afterRightPanBoundingBox = await bgImageLocator.boundingBox();
expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x);
// pan left
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
await page.keyboard.down('Alt');
await page.mouse.down();
await page.mouse.move(imageCenterX, imageCenterY, 10);
await page.mouse.up();
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
await page.keyboard.up('Alt');
const afterLeftPanBoundingBox = await bgImageLocator.boundingBox();
expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x);
// pan up
await page.mouse.move(imageCenterX, imageCenterY);
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
await page.keyboard.down('Alt');
await page.mouse.down();
await page.mouse.move(imageCenterX, imageCenterY + 200, 10);
await page.mouse.up();
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
await page.keyboard.up('Alt');
const afterUpPanBoundingBox = await bgImageLocator.boundingBox();
expect(afterUpPanBoundingBox.y).toBeGreaterThan(afterLeftPanBoundingBox.y);
// pan down
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
await page.keyboard.down('Alt');
await page.mouse.down();
await page.mouse.move(imageCenterX, imageCenterY - 200, 10);
await page.mouse.up();
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
await page.keyboard.up('Alt');
const afterDownPanBoundingBox = await bgImageLocator.boundingBox();
expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y);
@@ -175,26 +156,20 @@ 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();
@@ -205,160 +180,38 @@ test.describe('Example Imagery', () => {
expect(resetBoundingBox.width).toEqual(initialBoundingBox.width);
});
test('Using the zoom features does not pause telemetry', async ({ page }) => {
const bgImageLocator = page.locator(backgroundImageSelector);
const pausePlayButton = page.locator('.c-button.pause-play');
// wait for zoom animation to finish
await bgImageLocator.hover();
// open the time conductor drop down
await page.locator('.c-conductor__controls button.c-mode-button').click();
// Click local clock
await page.locator('.icon-clock >> text=Local Clock').click();
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
const zoomInBtn = page.locator('.t-btn-zoom-in');
await zoomInBtn.click();
// wait for zoom animation to finish
await bgImageLocator.hover();
return expect(pausePlayButton).not.toHaveClass(/is-paused/);
});
//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');
});
// The following test case will cover these scenarios
// ('Can use Mouse Wheel to zoom in and out of previous image');
// ('Can use alt+drag to move around image once zoomed in');
// ('Clicking on the left arrow should pause the imagery and go to previous image');
// ('If the imagery view is in pause mode, it should not be updated when new images come in');
// ('If the imagery view is not in pause mode, it should be updated when new images come in');
const backgroundImageSelector = '.c-imagery__main-image__background-image';
test('Example Imagery in Display layout', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click the Create button
await page.click('button:has-text("Create")');
// Click text=Example Imagery
await page.click('text=Example Imagery');
// Clear and set Image load delay (milliseconds)
await page.click('input[type="number"]', {clickCount: 3});
await page.type('input[type="number"]', "20");
// Click text=OK
await Promise.all([
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');
const bgImageLocator = await page.locator(backgroundImageSelector);
await bgImageLocator.hover();
// Click previous image button
const previousImageButton = await page.locator('.c-nav--prev');
await previousImageButton.click();
// Verify previous image
const selectedImage = page.locator('.selected');
await expect(selectedImage).toBeVisible();
// Zoom in
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
await bgImageLocator.hover();
const deltaYStep = 100; // equivalent to 1x zoom
await page.mouse.wheel(0, deltaYStep * 2);
const zoomedBoundingBox = await bgImageLocator.boundingBox();
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
// Wait for zoom animation to finish
await bgImageLocator.hover();
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
// Center the mouse pointer
await page.mouse.move(imageCenterX, imageCenterY);
// Pan Imagery Hints
console.log('process.platform is ' + process.platform);
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);
// Click next image button
const nextImageButton = await page.locator('.c-nav--next');
await nextImageButton.click();
// 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();
// Zoom in on next image
await bgImageLocator.hover();
await page.mouse.wheel(0, deltaYStep * 2);
// Wait for zoom animation to finish
await bgImageLocator.hover();
const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
// Click previous image button
await previousImageButton.click();
// Verify previous image
await expect(selectedImage).toBeVisible();
// Wait 20ms to verify no new image has come in
await page.waitForTimeout(21);
//Get background-image url from background-image css prop
const backgroundImage = await page.locator('.c-imagery__main-image__background-image');
let backgroundImageUrl = await backgroundImage.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
});
let backgroundImageUrl1 = backgroundImageUrl.slice(1, -1); //forgive me, padre
console.log('backgroundImageUrl1 ' + backgroundImageUrl1);
// sleep 21ms
await page.waitForTimeout(21);
// Verify next image has updated
let backgroundImageUrlNext = await backgroundImage.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
});
let backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre
console.log('backgroundImageUrl2 ' + backgroundImageUrl2);
// Expect backgroundImageUrl2 to be greater then backgroundImageUrl1
expect(backgroundImageUrl2 >= backgroundImageUrl1);
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.describe('Example Imagery in Flexible layout', () => {
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.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.describe('Example Imagery in Tabs view', () => {
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');
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');
});

View File

@@ -21,11 +21,10 @@
*****************************************************************************/
/*
Testsuite for plot autoscale.
Test for plot autoscale.
*/
const { test: _test } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');
const { test: _test, expect } = require('@playwright/test');
// create a new `test` API that will not append platform details to snapshot
// file names, only for the tests in this file, so that the same snapshots will
@@ -48,10 +47,7 @@ test.use({
});
test.describe('ExportAsJSON', () => {
test('User can set autoscale with a valid range @snapshot', async ({ page }) => {
//This is necessary due to the size of the test suite.
await test.setTimeout(120 * 1000);
test('autoscale off causes no error from undefined user range', async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle' });
await setTimeRange(page);
@@ -72,6 +68,14 @@ 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, {
@@ -87,6 +91,12 @@ 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']),
@@ -124,14 +134,9 @@ async function createSinewaveOverlayPlot(page) {
// add overlay plot with defaults
await page.locator('li:has-text("Overlay Plot")').click();
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
//Wait for Save Banner to appear1
page.waitForSelector('.c-message-banner__message')
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()
]);
//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();
@@ -143,19 +148,14 @@ 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(),
page.locator('text=OK').click(),
//Wait for Save Banner to appear1
page.waitForSelector('.c-message-banner__message')
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()
]);
//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(),
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=Unnamed Overlay Plot').first().click()
]);
}
@@ -168,18 +168,11 @@ async function turnOffAutoscale(page) {
await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click();
// uncheck autoscale
await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"] >> nth=1').uncheck();
await page.locator('text=Y Axis Scaling Auto scale Padding >> input[type="checkbox"]').uncheck();
// save
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).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'});
await page.locator('text=Save and Finish Editing').click();
}
/**
@@ -187,7 +180,6 @@ 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: 15 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -21,18 +21,13 @@
*****************************************************************************/
/*
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.
Tests to verify log plot functionality.
*/
const { test } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test.describe('Log plot tests', () => {
test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({ page }) => {
//This is necessary due to the size of the test suite.
await test.setTimeout(120 * 1000);
test.only('Can create a log plot.', async ({ page }) => {
await makeOverlayPlot(page);
await testRegularTicks(page);
await enableEditMode(page);
@@ -44,20 +39,17 @@ test.describe('Log plot tests', () => {
await testLogTicks(page);
await saveOverlayPlot(page);
await testLogTicks(page);
//await testLogPlotPixels(page);
await testLogPlotPixels(page);
// 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');
// refresh page
await page.reload();
// test log ticks hold up after refresh
await testLogTicks(page);
//await testLogPlotPixels(page);
await testLogPlotPixels(page);
});
test.skip('Verify that log mode option is reflected in import/export JSON', async ({ page }) => {
test.only('Verify that log mode option is reflected in import/export JSON', async ({ page }) => {
await makeOverlayPlot(page);
await enableEditMode(page);
await enableLogMode(page);
@@ -65,7 +57,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...
@@ -96,18 +88,14 @@ 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({ waitUntil: 'networkidle'}),
page.locator('text=OK').click(),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
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()
]);
//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 saveOverlayPlot(page);
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();
// create a sinewave generator
@@ -128,20 +116,15 @@ async function makeOverlayPlot(page) {
// Click OK to make generator
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle'}),
page.locator('text=OK').click(),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
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()
]);
//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(),
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=Unnamed Overlay Plot').first().click()
]);
}
@@ -150,7 +133,7 @@ async function makeOverlayPlot(page) {
* @param {import('@playwright/test').Page} page
*/
async function testRegularTicks(page) {
const yTicks = await page.locator('.gl-plot-y-tick-label');
const yTicks = 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');
@@ -165,7 +148,7 @@ async function testRegularTicks(page) {
* @param {import('@playwright/test').Page} page
*/
async function testLogTicks(page) {
const yTicks = await page.locator('.gl-plot-y-tick-label');
const yTicks = 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');
@@ -203,7 +186,6 @@ 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();
}
/**
@@ -228,27 +210,17 @@ 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 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' });
await page.locator('text=Save and Finish Editing').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
// FIXME: Remove this eslint exception once implemented
// eslint-disable-next-line no-unused-vars
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, 5 * 1000));
await new Promise((r) => setTimeout(r, 50));
// These are some pixels that should be blue points in the log plot.
// If the plot changes shape to an unexpected shape, this will

View File

@@ -20,8 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { test } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test.describe('Time counductor operations', () => {
test('validate start time does not exceeds end time', async ({ page }) => {
@@ -68,42 +67,3 @@ 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' });
// 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');
});
});

View File

@@ -33,8 +33,7 @@ comfortable running this test during a live mission?" Avoid creating or deleting
Make no assumptions about the order that elements appear in the DOM.
*/
const { test } = require('../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
test('Verify that the create button appears and that the Folder Domain Object is available for selection', async ({ page }) => {

View File

@@ -24,53 +24,16 @@ import EventEmitter from 'EventEmitter';
import uuid from 'uuid';
import createExampleUser from './exampleUserCreator';
const STATUSES = [{
key: "NO_STATUS",
label: "Not set",
iconClass: "icon-question-mark",
iconClassPoll: "icon-status-poll-question-mark"
}, {
key: "GO",
label: "GO",
iconClass: "icon-check",
iconClassPoll: "icon-status-poll-question-mark",
statusClass: "s-status-ok",
statusBgColor: "#33cc33",
statusFgColor: "#000"
}, {
key: "MAYBE",
label: "MAYBE",
iconClass: "icon-alert-triangle",
iconClassPoll: "icon-status-poll-question-mark",
statusClass: "s-status-warning",
statusBgColor: "#ffb66c",
statusFgColor: "#000"
}, {
key: "NO_GO",
label: "NO GO",
iconClass: "icon-circle-slash",
iconClassPoll: "icon-status-poll-question-mark",
statusClass: "s-status-error",
statusBgColor: "#9900cc",
statusFgColor: "#fff"
}];
/**
* @implements {StatusUserProvider}
*/
export default class ExampleUserProvider extends EventEmitter {
constructor(openmct, {defaultStatusRole} = {defaultStatusRole: undefined}) {
constructor(openmct) {
super();
this.openmct = openmct;
this.user = undefined;
this.loggedIn = false;
this.autoLoginUser = undefined;
this.status = STATUSES[1];
this.pollQuestion = undefined;
this.defaultStatusRole = defaultStatusRole;
this.ExampleUser = createExampleUser(this.openmct.user.User);
this.loginPromise = undefined;
}
isLoggedIn() {
@@ -82,19 +45,11 @@ export default class ExampleUserProvider extends EventEmitter {
}
getCurrentUser() {
if (!this.loginPromise) {
this.loginPromise = this._login().then(() => this.user);
if (this.loggedIn) {
return Promise.resolve(this.user);
}
return this.loginPromise;
}
canProvideStatusForRole() {
return Promise.resolve(true);
}
canSetPollQuestion() {
return Promise.resolve(true);
return this._login().then(() => this.user);
}
hasRole(roleId) {
@@ -105,55 +60,6 @@ export default class ExampleUserProvider extends EventEmitter {
return Promise.resolve(this.user.getRoles().includes(roleId));
}
getStatusRoleForCurrentUser() {
return Promise.resolve(this.defaultStatusRole);
}
getAllStatusRoles() {
return Promise.resolve([this.defaultStatusRole]);
}
getStatusForRole(role) {
return Promise.resolve(this.status);
}
async getDefaultStatusForRole(role) {
const allRoles = await this.getPossibleStatuses();
return allRoles?.[0];
}
setStatusForRole(role, status) {
this.status = status;
this.emit('statusChange', {
role,
status
});
return true;
}
getPollQuestion() {
return Promise.resolve({
question: 'Set "GO" if your position is ready for a boarding action on the Klingon cruiser',
timestamp: Date.now()
});
}
setPollQuestion(pollQuestion) {
this.pollQuestion = {
question: pollQuestion,
timestamp: Date.now()
};
this.emit("pollQuestionChange", this.pollQuestion);
return true;
}
getPossibleStatuses() {
return Promise.resolve(STATUSES);
}
_login() {
const id = uuid();
@@ -202,6 +108,3 @@ export default class ExampleUserProvider extends EventEmitter {
);
}
}
/**
* @typedef {import('@/api/user/StatusUserProvider').default} StatusUserProvider
*/

View File

@@ -22,19 +22,8 @@
import ExampleUserProvider from './ExampleUserProvider';
export default function ExampleUserPlugin({autoLoginUser, defaultStatusRole} = {
autoLoginUser: 'guest',
defaultStatusRole: 'test-role'
}) {
export default function ExampleUserPlugin() {
return function install(openmct) {
const userProvider = new ExampleUserProvider(openmct, {
defaultStatusRole
});
if (autoLoginUser !== undefined) {
userProvider.autoLogin(autoLoginUser);
}
openmct.user.setProvider(userProvider);
openmct.user.setProvider(new ExampleUserProvider(openmct));
};
}

View File

@@ -26,7 +26,7 @@ import {
} from '../../src/utils/testing';
import ExampleUserProvider from './ExampleUserProvider';
describe("The Example User Plugin", () => {
xdescribe("The Example User Plugin", () => {
let openmct;
beforeEach(() => {
@@ -47,4 +47,9 @@ describe("The Example User Plugin", () => {
});
openmct.install(openmct.plugins.example.ExampleUser());
});
// The rest of the functionality of the ExampleUser Plugin is
// tested in both the UserAPISpec.js and in the UserIndicatorPlugin spec.
// If that changes, those tests can be moved here.
});

View File

@@ -1,13 +1,13 @@
{
"name": "openmct",
"version": "2.0.4-SNAPSHOT",
"version": "2.0.4",
"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.3",
"@playwright/test": "1.21.1",
"@percy/playwright": "1.0.2",
"@playwright/test": "1.19.2",
"@types/eventemitter3": "^1.0.0",
"@types/jasmine": "^4.0.1",
"@types/karma": "^6.3.2",
@@ -17,15 +17,15 @@
"babel-loader": "8.2.3",
"babel-plugin-istanbul": "6.1.1",
"comma-separated-values": "3.6.4",
"copy-webpack-plugin": "11.0.0",
"copy-webpack-plugin": "10.2.0",
"cross-env": "7.0.3",
"css-loader": "4.0.0",
"d3-axis": "3.0.0",
"d3-scale": "3.3.0",
"d3-selection": "3.0.0",
"d3-axis": "1.0.x",
"d3-scale": "1.0.x",
"d3-selection": "1.3.x",
"eslint": "8.13.0",
"eslint-plugin-compat": "4.0.2",
"eslint-plugin-playwright": "0.9.0",
"eslint-plugin-playwright": "0.8.0",
"eslint-plugin-vue": "8.5.0",
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
@@ -35,12 +35,12 @@
"git-rev-sync": "3.0.2",
"html2canvas": "1.4.1",
"imports-loader": "0.8.0",
"jasmine-core": "4.1.1",
"jasmine-core": "4.0.1",
"jsdoc": "3.5.5",
"karma": "6.3.18",
"karma-chrome-launcher": "3.1.1",
"karma-cli": "2.0.0",
"karma-coverage": "2.2.0",
"karma-coverage": "2.1.1",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-firefox-launcher": "2.1.2",
"karma-jasmine": "4.0.1",
@@ -52,13 +52,13 @@
"location-bar": "3.0.1",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.6.0",
"moment": "2.29.3",
"moment": "2.29.1",
"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.12.0",
"plotly.js-gl2d-dist": "2.12.0",
"plotly.js-basic-dist": "2.5.0",
"plotly.js-gl2d-dist": "2.5.0",
"printj": "1.3.1",
"request": "2.88.2",
"resolve-url-loader": "5.0.0",
@@ -82,8 +82,8 @@
"clean": "rm -rf ./dist ./node_modules ./package-lock.json",
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
"start": "node app.js",
"lint": "eslint example src e2e --ext .js,.vue openmct.js",
"lint:fix": "eslint example src e2e --ext .js,.vue openmct.js --fix",
"lint": "eslint example src --ext .js,.vue openmct.js",
"lint:fix": "eslint example src --ext .js,.vue openmct.js --fix",
"build:prod": "cross-env webpack --config webpack.prod.js",
"build:dev": "webpack --config webpack.dev.js",
"build:coverage": "webpack --config webpack.coverage.js",
@@ -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 branding clock exampleImagery",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition timeConductor",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome --grep @snapshot --update-snapshots",
"test:e2e:debug": "npm run test:e2e:local -- --debug",
"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",

View File

@@ -242,8 +242,7 @@ define([
// Plugins that are installed by default
this.install(this.plugins.Plot());
this.install(this.plugins.ScatterPlot());
this.install(this.plugins.BarChart());
this.install(this.plugins.Chart());
this.install(this.plugins.TelemetryTable.default());
this.install(PreviewPlugin.default());
this.install(LicensesPlugin.default());

View File

@@ -56,7 +56,7 @@ define([
CompositionAPI: CompositionAPI,
EditorAPI: EditorAPI,
FormsAPI: FormsAPI,
IndicatorAPI: IndicatorAPI.default,
IndicatorAPI: IndicatorAPI,
MenuAPI: MenuAPI.default,
NotificationAPI: NotificationAPI.default,
ObjectAPI: ObjectAPI,

View File

@@ -23,13 +23,10 @@
import FormController from './FormController';
import FormProperties from './components/FormProperties.vue';
import EventEmitter from 'EventEmitter';
import Vue from 'vue';
export default class FormsAPI extends EventEmitter {
export default class FormsAPI {
constructor(openmct) {
super();
this.openmct = openmct;
this.formController = new FormController(openmct);
}
@@ -110,8 +107,6 @@ export default class FormsAPI extends EventEmitter {
let onDismiss;
let onSave;
const self = this;
const promise = new Promise((resolve, reject) => {
onSave = onFormSave(resolve);
onDismiss = onFormDismiss(reject);
@@ -120,7 +115,7 @@ export default class FormsAPI extends EventEmitter {
const vm = new Vue({
components: { FormProperties },
provide: {
openmct: self.openmct
openmct: this.openmct
},
data() {
return {
@@ -137,7 +132,7 @@ export default class FormsAPI extends EventEmitter {
if (element) {
element.append(formElement);
} else {
overlay = self.openmct.overlays.overlay({
overlay = this.openmct.overlays.overlay({
element: vm.$el,
size: 'small',
onDestroy: () => vm.$destroy()
@@ -145,7 +140,6 @@ export default class FormsAPI extends EventEmitter {
}
function onFormPropertyChange(data) {
self.emit('onFormPropertyChange', data);
if (onChange) {
onChange(data);
}

View File

@@ -1,157 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, resetApplicationState } from '../../utils/testing';
describe('The Forms API', () => {
let openmct;
let element;
beforeEach((done) => {
element = document.createElement('div');
element.style.display = 'block';
element.style.width = '1920px';
element.style.height = '1080px';
openmct = createOpenMct();
openmct.on('start', done);
openmct.startHeadless(element);
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('openmct supports form API', () => {
expect(openmct.forms).not.toBe(null);
});
describe('check default form controls exists', () => {
it('autocomplete', () => {
const control = openmct.forms.getFormControl('autocomplete');
expect(control).not.toBe(null);
});
it('clock', () => {
const control = openmct.forms.getFormControl('composite');
expect(control).not.toBe(null);
});
it('datetime', () => {
const control = openmct.forms.getFormControl('datetime');
expect(control).not.toBe(null);
});
it('file-input', () => {
const control = openmct.forms.getFormControl('file-input');
expect(control).not.toBe(null);
});
it('locator', () => {
const control = openmct.forms.getFormControl('locator');
expect(control).not.toBe(null);
});
it('numberfield', () => {
const control = openmct.forms.getFormControl('numberfield');
expect(control).not.toBe(null);
});
it('select', () => {
const control = openmct.forms.getFormControl('select');
expect(control).not.toBe(null);
});
it('textarea', () => {
const control = openmct.forms.getFormControl('textarea');
expect(control).not.toBe(null);
});
it('textfield', () => {
const control = openmct.forms.getFormControl('textfield');
expect(control).not.toBe(null);
});
});
it('supports user defined form controls', () => {
const newFormControl = {
show: () => {
console.log('show new control');
},
destroy: () => {
console.log('destroy');
}
};
openmct.forms.addNewFormControl('newFormControl', newFormControl);
const control = openmct.forms.getFormControl('newFormControl');
expect(control).not.toBe(null);
expect(control.show).not.toBe(null);
expect(control.destroy).not.toBe(null);
});
describe('show form on UI', () => {
let formStructure;
beforeEach(() => {
formStructure = {
title: 'Test Show Form',
sections: [
{
rows: [
{
key: 'name',
control: 'textfield',
name: 'Title',
pattern: '\\S+',
required: false,
cssClass: 'l-input-lg',
value: 'Test Name'
}
]
}
]
};
});
it('when container element is provided', (done) => {
openmct.forms.showForm(formStructure, { element }).catch(() => {
done();
});
const titleElement = element.querySelector('.c-overlay__dialog-title');
expect(titleElement.textContent).toBe(formStructure.title);
element.querySelector('.js-cancel-button').click();
});
it('when container element is not provided', (done) => {
openmct.forms.showForm(formStructure).catch(() => {
done();
});
const titleElement = document.querySelector('.c-overlay__dialog-title');
const title = titleElement.textContent;
expect(title).toBe(formStructure.title);
document.querySelector('.js-cancel-button').click();
});
});
});

View File

@@ -21,9 +21,9 @@
*****************************************************************************/
<template>
<div class="c-form js-form">
<div class="c-form">
<div class="c-overlay__top-bar c-form__top-bar">
<div class="c-overlay__dialog-title js-form-title">{{ model.title }}</div>
<div class="c-overlay__dialog-title">{{ model.title }}</div>
<div class="c-overlay__dialog-hint hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
</div>
<form
@@ -70,7 +70,7 @@
</button>
<button
tabindex="0"
class="c-button js-cancel-button"
class="c-button"
@click="onDismiss"
>
{{ cancelLabel }}

View File

@@ -40,12 +40,6 @@
>
{{ name }}
</button>
<button
v-if="removable"
class="c-button icon-trash"
title="Remove file"
@click="removeFile"
></button>
</span>
</span>
</template>
@@ -69,9 +63,6 @@ export default {
const fileInfo = this.fileInfo || this.model.value;
return fileInfo && fileInfo.name || this.model.text;
},
removable() {
return (this.fileInfo || this.model.value) && this.model.removable;
}
},
mounted() {
@@ -106,15 +97,6 @@ export default {
},
selectFile() {
this.$refs.fileInput.click();
},
removeFile() {
this.model.value = undefined;
this.fileInfo = undefined;
const data = {
model: this.model,
value: undefined
};
this.$emit('onChange', data);
}
}
};

View File

@@ -19,27 +19,27 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from "EventEmitter";
import SimpleIndicator from "./SimpleIndicator";
class IndicatorAPI extends EventEmitter {
constructor(openmct) {
super();
define([
'./SimpleIndicator',
'lodash'
], function (
SimpleIndicator,
_
) {
function IndicatorAPI(openmct) {
this.openmct = openmct;
this.indicatorObjects = [];
}
getIndicatorObjectsByPriority() {
IndicatorAPI.prototype.getIndicatorObjectsByPriority = function () {
const sortedIndicators = this.indicatorObjects.sort((a, b) => b.priority - a.priority);
return sortedIndicators;
}
};
simpleIndicator() {
IndicatorAPI.prototype.simpleIndicator = function () {
return new SimpleIndicator(this.openmct);
}
};
/**
* Accepts an indicator object, which is a simple object
@@ -62,16 +62,14 @@ class IndicatorAPI extends EventEmitter {
* myIndicator.iconClass("icon-info");
*
*/
add(indicator) {
IndicatorAPI.prototype.add = function (indicator) {
if (!indicator.priority) {
indicator.priority = this.openmct.priority.DEFAULT;
}
this.indicatorObjects.push(indicator);
};
this.emit('addIndicator', indicator);
}
return IndicatorAPI;
}
export default IndicatorAPI;
});

View File

@@ -20,107 +20,82 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import indicatorTemplate from './res/indicator-template.html';
define(['zepto', './res/indicator-template.html'],
function ($, indicatorTemplate) {
const DEFAULT_ICON_CLASS = 'icon-info';
const DEFAULT_ICON_CLASS = 'icon-info';
function SimpleIndicator(openmct) {
this.openmct = openmct;
this.element = $(indicatorTemplate)[0];
this.priority = openmct.priority.DEFAULT;
class SimpleIndicator extends EventEmitter {
constructor(openmct) {
super();
this.textElement = this.element.querySelector('.js-indicator-text');
this.openmct = openmct;
this.element = compileTemplate(indicatorTemplate)[0];
this.priority = openmct.priority.DEFAULT;
this.textElement = this.element.querySelector('.js-indicator-text');
//Set defaults
this.text('New Indicator');
this.description('');
this.iconClass(DEFAULT_ICON_CLASS);
this.click = this.click.bind(this);
this.element.addEventListener('click', this.click);
openmct.once('destroy', () => {
this.removeAllListeners();
this.element.removeEventListener('click', this.click);
});
}
text(text) {
if (text !== undefined && text !== this.textValue) {
this.textValue = text;
this.textElement.innerText = text;
if (!text) {
this.element.classList.add('hidden');
} else {
this.element.classList.remove('hidden');
}
//Set defaults
this.text('New Indicator');
this.description('');
this.iconClass(DEFAULT_ICON_CLASS);
this.statusClass('');
}
return this.textValue;
}
SimpleIndicator.prototype.text = function (text) {
if (text !== undefined && text !== this.textValue) {
this.textValue = text;
this.textElement.innerText = text;
description(description) {
if (description !== undefined && description !== this.descriptionValue) {
this.descriptionValue = description;
this.element.title = description;
}
return this.descriptionValue;
}
iconClass(iconClass) {
if (iconClass !== undefined && iconClass !== this.iconClassValue) {
// element.classList is precious and throws errors if you try and add
// or remove empty strings
if (this.iconClassValue) {
this.element.classList.remove(this.iconClassValue);
if (!text) {
this.element.classList.add('hidden');
} else {
this.element.classList.remove('hidden');
}
}
if (iconClass) {
this.element.classList.add(iconClass);
return this.textValue;
};
SimpleIndicator.prototype.description = function (description) {
if (description !== undefined && description !== this.descriptionValue) {
this.descriptionValue = description;
this.element.title = description;
}
this.iconClassValue = iconClass;
}
return this.descriptionValue;
};
return this.iconClassValue;
}
SimpleIndicator.prototype.iconClass = function (iconClass) {
if (iconClass !== undefined && iconClass !== this.iconClassValue) {
// element.classList is precious and throws errors if you try and add
// or remove empty strings
if (this.iconClassValue) {
this.element.classList.remove(this.iconClassValue);
}
statusClass(statusClass) {
if (arguments.length === 1 && statusClass !== this.statusClassValue) {
if (this.statusClassValue) {
this.element.classList.remove(this.statusClassValue);
if (iconClass) {
this.element.classList.add(iconClass);
}
this.iconClassValue = iconClass;
}
if (statusClass !== undefined) {
this.element.classList.add(statusClass);
return this.iconClassValue;
};
SimpleIndicator.prototype.statusClass = function (statusClass) {
if (statusClass !== undefined && statusClass !== this.statusClassValue) {
if (this.statusClassValue) {
this.element.classList.remove(this.statusClassValue);
}
if (statusClass) {
this.element.classList.add(statusClass);
}
this.statusClassValue = statusClass;
}
this.statusClassValue = statusClass;
}
return this.statusClassValue;
};
return this.statusClassValue;
return SimpleIndicator;
}
click(event) {
this.emit('click', event);
}
getElement() {
return this.element;
}
}
function compileTemplate(htmlTemplate) {
const templateNode = document.createElement('template');
templateNode.innerHTML = htmlTemplate;
return templateNode.content.cloneNode(true).children;
}
export default SimpleIndicator;
);

View File

@@ -26,31 +26,29 @@ import { createOpenMct, createMouseEvent, resetApplicationState } from '../../ut
describe ('The Menu API', () => {
let openmct;
let appHolder;
let element;
let menuAPI;
let actionsArray;
let x;
let y;
let result;
let menuElement;
const x = 8;
const y = 16;
const menuOptions = {
onDestroy: () => {
console.log('default onDestroy');
}
};
let onDestroy;
beforeEach((done) => {
appHolder = document.createElement('div');
const appHolder = document.createElement('div');
appHolder.style.display = 'block';
appHolder.style.width = '1920px';
appHolder.style.height = '1080px';
openmct = createOpenMct();
element = document.createElement('div');
element.style.display = 'block';
element.style.width = '1920px';
element.style.height = '1080px';
openmct.on('start', done);
openmct.startHeadless();
openmct.startHeadless(appHolder);
menuAPI = new MenuAPI(openmct);
actionsArray = [
@@ -58,7 +56,7 @@ describe ('The Menu API', () => {
key: 'test-css-class-1',
name: 'Test Action 1',
cssClass: 'icon-clock',
description: 'This is a test action 1',
description: 'This is a test action',
onItemClicked: () => {
result = 'Test Action 1 Invoked';
}
@@ -67,165 +65,149 @@ describe ('The Menu API', () => {
key: 'test-css-class-2',
name: 'Test Action 2',
cssClass: 'icon-clock',
description: 'This is a test action 2',
description: 'This is a test action',
onItemClicked: () => {
result = 'Test Action 2 Invoked';
}
}
];
x = 8;
y = 16;
});
afterEach(() => {
return resetApplicationState(openmct);
});
describe('showMenu method', () => {
beforeAll(() => {
spyOn(menuOptions, 'onDestroy').and.callThrough();
});
it('creates an instance of Menu when invoked', (done) => {
menuOptions.onDestroy = done;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
describe("showMenu method", () => {
it("creates an instance of Menu when invoked", () => {
menuAPI.showMenu(x, y, actionsArray);
expect(menuAPI.menuComponent).toBeInstanceOf(Menu);
document.body.click();
});
describe('creates a menu component', () => {
it('with all the actions passed in', (done) => {
menuOptions.onDestroy = done;
describe("creates a menu component", () => {
let menuComponent;
let vueComponent;
beforeEach(() => {
onDestroy = jasmine.createSpy('onDestroy');
const menuOptions = {
onDestroy
};
menuAPI.showMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-menu');
expect(menuElement).toBeDefined();
vueComponent = menuAPI.menuComponent.component;
menuComponent = document.querySelector(".c-menu");
const listItems = menuElement.children[0].children;
spyOn(vueComponent, '$destroy');
});
it("renders a menu component in the expected x and y coordinates", () => {
let boundingClientRect = menuComponent.getBoundingClientRect();
let left = boundingClientRect.left;
let top = boundingClientRect.top;
expect(left).toEqual(x);
expect(top).toEqual(y);
});
it("with all the actions passed in", () => {
expect(menuComponent).toBeDefined();
let listItems = menuComponent.children[0].children;
expect(listItems.length).toEqual(actionsArray.length);
document.body.click();
});
it('with click-able menu items, that will invoke the correct callBack', (done) => {
menuOptions.onDestroy = done;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-menu');
const listItem1 = menuElement.children[0].children[0];
it("with click-able menu items, that will invoke the correct callBacks", () => {
let listItem1 = menuComponent.children[0].children[0];
listItem1.click();
expect(result).toEqual('Test Action 1 Invoked');
expect(result).toEqual("Test Action 1 Invoked");
});
it('dismisses the menu when action is clicked on', (done) => {
menuOptions.onDestroy = done;
it("dismisses the menu when action is clicked on", () => {
let listItem1 = menuComponent.children[0].children[0];
menuAPI.showMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-menu');
const listItem1 = menuElement.children[0].children[0];
listItem1.click();
menuElement = document.querySelector('.c-menu');
let menu = document.querySelector('.c-menu');
expect(menuElement).toBeNull();
expect(menu).toBeNull();
});
it('invokes the destroy method when menu is dismissed', (done) => {
menuOptions.onDestroy = done;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
const vueComponent = menuAPI.menuComponent.component;
spyOn(vueComponent, '$destroy');
it("invokes the destroy method when menu is dismissed", () => {
document.body.click();
expect(vueComponent.$destroy).toHaveBeenCalled();
});
it('invokes the onDestroy callback if passed in', (done) => {
let count = 0;
menuOptions.onDestroy = () => {
count++;
expect(count).toEqual(1);
done();
};
menuAPI.showMenu(x, y, actionsArray, menuOptions);
it("invokes the onDestroy callback if passed in", () => {
document.body.click();
expect(onDestroy).toHaveBeenCalled();
});
});
});
describe('superMenu method', () => {
it('creates a superMenu', (done) => {
menuOptions.onDestroy = done;
describe("superMenu method", () => {
it("creates a superMenu", () => {
menuAPI.showSuperMenu(x, y, actionsArray);
menuAPI.showSuperMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-super-menu__menu');
const superMenu = document.querySelector('.c-super-menu__menu');
expect(menuElement).not.toBeNull();
document.body.click();
expect(superMenu).not.toBeNull();
});
it('Mouse over a superMenu shows correct description', (done) => {
menuOptions.onDestroy = done;
it("Mouse over a superMenu shows correct description", (done) => {
menuAPI.showSuperMenu(x, y, actionsArray);
menuAPI.showSuperMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-super-menu__menu');
const superMenuItem = menuElement.querySelector('li');
const superMenu = document.querySelector('.c-super-menu__menu');
const superMenuItem = superMenu.querySelector('li');
const mouseOverEvent = createMouseEvent('mouseover');
superMenuItem.dispatchEvent(mouseOverEvent);
const itemDescription = document.querySelector('.l-item-description__description');
menuAPI.menuComponent.component.$nextTick(() => {
expect(menuElement).not.toBeNull();
setTimeout(() => {
expect(itemDescription.innerText).toEqual(actionsArray[0].description);
document.body.click();
});
expect(superMenu).not.toBeNull();
done();
}, 300);
});
});
describe('Menu Placements', () => {
it('default menu position BOTTOM_RIGHT', (done) => {
menuOptions.onDestroy = done;
describe("Menu Placements", () => {
it("default menu position BOTTOM_RIGHT", () => {
menuAPI.showMenu(x, y, actionsArray);
menuAPI.showMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-menu');
const menu = document.querySelector('.c-menu');
const boundingClientRect = menuElement.getBoundingClientRect();
const boundingClientRect = menu.getBoundingClientRect();
const left = boundingClientRect.left;
const top = boundingClientRect.top;
expect(left).toEqual(x);
expect(top).toEqual(y);
document.body.click();
});
it('menu position BOTTOM_RIGHT', (done) => {
menuOptions.onDestroy = done;
menuOptions.placement = openmct.menus.menuPlacement.BOTTOM_RIGHT;
it("menu position BOTTOM_RIGHT", () => {
const menuOptions = {
placement: openmct.menus.menuPlacement.BOTTOM_RIGHT
};
menuAPI.showMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-menu');
const boundingClientRect = menuElement.getBoundingClientRect();
const menu = document.querySelector('.c-menu');
const boundingClientRect = menu.getBoundingClientRect();
const left = boundingClientRect.left;
const top = boundingClientRect.top;
expect(left).toEqual(x);
expect(top).toEqual(y);
document.body.click();
});
});
});

View File

@@ -512,7 +512,7 @@ define([
TelemetryAPI.prototype.handleMissingRequestProvider = function (domainObject) {
this.noRequestProviderForAllObjects = this.requestProviders.every(requestProvider => {
const supportsRequest = requestProvider.supportsRequest.apply(requestProvider, arguments);
const hasRequestProvider = Object.prototype.hasOwnProperty.call(requestProvider, 'request') && typeof requestProvider.request === 'function';
const hasRequestProvider = Object.hasOwn(requestProvider, 'request');
return supportsRequest && hasRequestProvider;
});

View File

@@ -22,7 +22,11 @@
import _ from 'lodash';
import EventEmitter from 'EventEmitter';
import { LOADED_ERROR, TIMESYSTEM_KEY_NOTIFICATION, TIMESYSTEM_KEY_WARNING } from './constants';
const ERRORS = {
TIMESYSTEM_KEY: 'All telemetry metadata must have a telemetry value with a key that matches the key of the active time system.',
LOADED: 'Telemetry Collection has already been loaded.'
};
/** Class representing a Telemetry Collection. */
@@ -57,7 +61,7 @@ export class TelemetryCollection extends EventEmitter {
*/
load() {
if (this.loaded) {
this._error(LOADED_ERROR);
this._error(ERRORS.LOADED);
}
this._setTimeSystem(this.openmct.time.timeSystem());
@@ -263,10 +267,6 @@ export class TelemetryCollection extends EventEmitter {
this.lastBounds = bounds;
if (isTick) {
if (this.timeKey === undefined) {
return;
}
// need to check futureBuffer and need to check
// if anything has fallen out of bounds
let startIndex = 0;
@@ -306,6 +306,7 @@ export class TelemetryCollection extends EventEmitter {
if (added.length > 0) {
this.emit('add', added);
}
} else {
// user bounds change, reset
this._reset();
@@ -325,16 +326,12 @@ export class TelemetryCollection extends EventEmitter {
let domains = this.metadata.valuesForHints(['domain']);
let domain = domains.find((d) => d.key === timeSystem.key);
if (domain !== undefined) {
// timeKey is used to create a dummy datum used for sorting
this.timeKey = domain.source;
} else {
this.timeKey = undefined;
this._warn(TIMESYSTEM_KEY_WARNING);
this.openmct.notifications.alert(TIMESYSTEM_KEY_NOTIFICATION);
if (domain === undefined) {
this._error(ERRORS.TIMESYSTEM_KEY);
}
// timeKey is used to create a dummy datum used for sorting
this.timeKey = domain.source; // this defaults to key if no source is set
let metadataValue = this.metadata.value(timeSystem.key) || { format: timeSystem.key };
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
@@ -405,8 +402,4 @@ export class TelemetryCollection extends EventEmitter {
_error(message) {
throw new Error(message);
}
_warn(message) {
console.warn(message);
}
}

View File

@@ -1,101 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
import { TIMESYSTEM_KEY_WARNING } from './constants';
describe('Telemetry Collection', () => {
let openmct;
let mockMetadataProvider;
let mockMetadata = {};
let domainObject;
beforeEach(done => {
openmct = createOpenMct();
openmct.on('start', done);
domainObject = {
identifier: {
key: 'a',
namespace: 'b'
},
type: 'sample-type'
};
mockMetadataProvider = {
key: 'mockMetadataProvider',
supportsMetadata() {
return true;
},
getMetadata() {
return mockMetadata;
}
};
openmct.telemetry.addProvider(mockMetadataProvider);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState();
});
it('Warns if telemetry metadata does not match the active timesystem', () => {
mockMetadata.values = [
{
key: 'foo',
name: 'Bar',
hints: {
domain: 1
}
}
];
const telemetryCollection = openmct.telemetry.requestCollection(domainObject);
spyOn(telemetryCollection, '_warn');
telemetryCollection.load();
expect(telemetryCollection._warn).toHaveBeenCalledOnceWith(TIMESYSTEM_KEY_WARNING);
});
it('Does not warn if telemetry metadata matches the active timesystem', () => {
mockMetadata.values = [
{
key: 'utc',
name: 'Timestamp',
format: 'utc',
hints: {
domain: 1
}
}
];
const telemetryCollection = openmct.telemetry.requestCollection(domainObject);
spyOn(telemetryCollection, '_warn');
telemetryCollection.load();
expect(telemetryCollection._warn).not.toHaveBeenCalled();
});
});

View File

@@ -138,7 +138,7 @@ define([
valueMetadata = this.values()[0];
}
return valueMetadata;
return valueMetadata.key;
};
return TelemetryMetadataManager;

View File

@@ -1,25 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export const TIMESYSTEM_KEY_WARNING = 'All telemetry metadata must have a telemetry value with a key that matches the key of the active time system.';
export const TIMESYSTEM_KEY_NOTIFICATION = 'Telemetry metadata does not match the active time system.';
export const LOADED_ERROR = 'Telemetry Collection has already been loaded.';

View File

@@ -1,298 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from "EventEmitter";
export default class StatusAPI extends EventEmitter {
#userAPI;
#openmct;
constructor(userAPI, openmct) {
super();
this.#userAPI = userAPI;
this.#openmct = openmct;
this.onProviderStatusChange = this.onProviderStatusChange.bind(this);
this.onProviderPollQuestionChange = this.onProviderPollQuestionChange.bind(this);
this.listenToStatusEvents = this.listenToStatusEvents.bind(this);
this.#openmct.once('destroy', () => {
const provider = this.#userAPI.getProvider();
if (typeof provider?.off === 'function') {
provider.off('statusChange', this.onProviderStatusChange);
provider.off('pollQuestionChange', this.onProviderPollQuestionChange);
}
});
this.#userAPI.on('providerAdded', this.listenToStatusEvents);
}
/**
* Fetch the currently defined operator status poll question. When presented with a status poll question, all operators will reply with their current status.
* @returns {Promise<PollQuestion>}
*/
getPollQuestion() {
const provider = this.#userAPI.getProvider();
if (provider.getPollQuestion) {
return provider.getPollQuestion();
} else {
this.#userAPI.error("User provider does not support polling questions");
}
}
/**
* Set a poll question for operators to respond to. When presented with a status poll question, all operators will reply with their current status.
* @param {String} questionText - The text of the question
* @returns {Promise<Boolean>} true if operation was successful, otherwise false.
*/
async setPollQuestion(questionText) {
if (this.canSetPollQuestion()) {
const provider = this.#userAPI.getProvider();
const result = await provider.setPollQuestion(questionText);
// TODO re-implement clearing all statuses
try {
await this.resetAllStatuses();
} catch (error) {
console.warn("Poll question set but unable to clear operator statuses.");
console.error(error);
}
return result;
} else {
this.#userAPI.error("User provider does not support setting polling question");
}
}
/**
* Can the currently logged in user set the operator status poll question.
* @returns {Promise<Boolean>}
*/
canSetPollQuestion() {
const provider = this.#userAPI.getProvider();
if (provider.canSetPollQuestion) {
return provider.canSetPollQuestion();
} else {
return Promise.resolve(false);
}
}
/**
* @returns {Promise<Array<Status>>} the complete list of possible states that an operator can reply to a poll question with.
*/
async getPossibleStatuses() {
const provider = this.#userAPI.getProvider();
if (provider.getPossibleStatuses) {
const possibleStatuses = await provider.getPossibleStatuses() || [];
return possibleStatuses.map(status => status);
} else {
this.#userAPI.error("User provider cannot provide statuses");
}
}
/**
* @param {import("./UserAPI").Role} role The role to fetch the current status for.
* @returns {Promise<Status>} the current status of the provided role
*/
async getStatusForRole(role) {
const provider = this.#userAPI.getProvider();
if (provider.getStatusForRole) {
const status = await provider.getStatusForRole(role);
if (status !== undefined) {
return status;
} else {
return undefined;
}
} else {
this.#userAPI.error("User provider does not support role status");
}
}
/**
* @param {import("./UserAPI").Role} role
* @returns {Promise<Boolean>} true if the configured UserProvider can provide status for the given role
* @see StatusUserProvider
*/
canProvideStatusForRole(role) {
const provider = this.#userAPI.getProvider();
if (provider.canProvideStatusForRole) {
return provider.canProvideStatusForRole(role);
} else {
return false;
}
}
/**
* @param {import("./UserAPI").Role} role The role to set the status for.
* @param {Status} status The status to set for the provided role
* @returns {Promise<Boolean>} true if operation was successful, otherwise false.
*/
setStatusForRole(role, status) {
const provider = this.#userAPI.getProvider();
if (provider.setStatusForRole) {
return provider.setStatusForRole(role, status);
} else {
this.#userAPI.error("User provider does not support setting role status");
}
}
/**
* Resets the status of the provided role back to its default status.
* @param {import("./UserAPI").Role} role The role to set the status for.
* @returns {Promise<Boolean>} true if operation was successful, otherwise false.
*/
async resetStatusForRole(role) {
const provider = this.#userAPI.getProvider();
const defaultStatus = await this.getDefaultStatus();
if (provider.setStatusForRole) {
return provider.setStatusForRole(role, defaultStatus);
} else {
this.#userAPI.error("User provider does not support resetting role status");
}
}
/**
* Resets the status of all operators to their default status
* @returns {Promise<Boolean>} true if operation was successful, otherwise false.
*/
async resetAllStatuses() {
const allStatusRoles = await this.getAllStatusRoles();
return Promise.all(allStatusRoles.map(role => this.resetStatusForRole(role)));
}
/**
* The default status. This is the status that will be used before the user has selected any status.
* @param {import("./UserAPI").Role} role
* @returns {Promise<Status>} the default operator status if no other has been set.
*/
async getDefaultStatusForRole(role) {
const provider = this.#userAPI.getProvider();
const defaultStatus = await provider.getDefaultStatusForRole(role);
return defaultStatus;
}
/**
* All possible status roles. A status role is a user role that can provide status. In some systems
* this may be all user roles, but there may be cases where some users are not are not polled
* for status if they do not have a real-time operational role.
*
* @returns {Promise<Array<import("./UserAPI").Role>>} the default operator status if no other has been set.
*/
getAllStatusRoles() {
const provider = this.#userAPI.getProvider();
if (provider.getAllStatusRoles) {
return provider.getAllStatusRoles();
} else {
this.#userAPI.error("User provider cannot provide all status roles");
}
}
/**
* The status role of the current user. A user may have multiple roles, but will only have one role
* that provides status at any time.
* @returns {Promise<import("./UserAPI").Role>} the role for which the current user can provide status.
*/
getStatusRoleForCurrentUser() {
const provider = this.#userAPI.getProvider();
if (provider.getStatusRoleForCurrentUser) {
return provider.getStatusRoleForCurrentUser();
} else {
this.#userAPI.error("User provider cannot provide role status for this user");
}
}
/**
* @returns {Promise<Boolean>} true if the configured UserProvider can provide status for the currently logged in user, false otherwise.
* @see StatusUserProvider
*/
async canProvideStatusForCurrentUser() {
const provider = this.#userAPI.getProvider();
if (provider.getStatusRoleForCurrentUser) {
const activeStatusRole = await this.#userAPI.getProvider().getStatusRoleForCurrentUser();
const canProvideStatus = await this.canProvideStatusForRole(activeStatusRole);
return canProvideStatus;
} else {
return false;
}
}
/**
* Private internal function that cannot be made #private because it needs to be registered as a callback to the user provider
* @private
*/
listenToStatusEvents(provider) {
if (typeof provider.on === 'function') {
provider.on('statusChange', this.onProviderStatusChange);
provider.on('pollQuestionChange', this.onProviderPollQuestionChange);
}
}
/**
* @private
*/
onProviderStatusChange(newStatus) {
this.emit('statusChange', newStatus);
}
/**
* @private
*/
onProviderPollQuestionChange(pollQuestion) {
this.emit('pollQuestionChange', pollQuestion);
}
}
/**
* @typedef {import('./UserAPI').UserProvider} UserProvider
*/
/**
* @typedef {import('./UserAPI').StatusUserProvider} StatusUserProvider
*/
/**
* The PollQuestion type
* @typedef {Object} PollQuestion
* @property {String} question - The question to be presented to users
* @property {Number} timestamp - The time that the poll question was set.
*/
/**
* The Status type
* @typedef {Object} Status
* @property {String} key - A unique identifier for this status
* @property {Number} label - A human readable label for this status
*/

View File

@@ -1,81 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import UserProvider from "./UserProvider";
export default class StatusUserProvider extends UserProvider {
/**
* @param {('statusChange'|'pollQuestionChange')} event the name of the event to listen to
* @param {Function} callback a function to invoke when this event occurs
*/
on(event, callback) {}
/**
* @param {('statusChange'|'pollQuestionChange')} event the name of the event to stop listen to
* @param {Function} callback the callback function used to register the listener
*/
off(event, callback) {}
/**
* @returns {import("./StatusAPI").PollQuestion} the current status poll question
*/
async getPollQuestion() {}
/**
* @param {import("./StatusAPI").PollQuestion} pollQuestion a new poll question to set
* @returns {Promise<Boolean>} true if operation was successful, otherwise false
*/
async setPollQuestion(pollQuestion) {}
/**
* @returns {Promise<Boolean>} true if the current user can set the poll question, otherwise false
*/
async canSetPollQuestion() {}
/**
* @returns {Promise<Array<import("./StatusAPI").Status>>} a list of the possible statuses that an operator can be in
*/
async getPossibleStatuses() {}
/**
* @param {import("./UserAPI").Role} role
* @returns {Promise<import("./StatusAPI").Status}
*/
async getStatusForRole(role) {}
/**
* @param {import("./UserAPI").Role} role
* @returns {Promise<import("./StatusAPI").Status}
*/
async getDefaultStatusForRole(role) {}
/**
* @param {import("./UserAPI").Role} role
* @param {*} status
* @returns {Promise<Boolean>} true if operation was successful, otherwise false.
*/
async setStatusForRole(role, status) {}
/**
* @param {import("./UserAPI").Role} role
* @returns {Promise<Boolean} true if the user provider can provide status for the given role
*/
async canProvideStatusForRole(role) {}
/**
* @returns {Promise<Array<import("./UserAPI").Role>>} a list of all available status roles, if user permissions allow it.
*/
async getAllStatusRoles() {}
/**
* @returns {Promise<import("./UserAPI").Role>} the active status role for the currently logged in user
*/
async getStatusRoleForCurrentUser() {}
}

View File

@@ -25,22 +25,16 @@ import {
MULTIPLE_PROVIDER_ERROR,
NO_PROVIDER_ERROR
} from './constants';
import StatusAPI from './StatusAPI';
import User from './User';
class UserAPI extends EventEmitter {
/**
* @param {OpenMCT} openmct
* @param {UserAPIConfiguration} config
*/
constructor(openmct, config) {
constructor(openmct) {
super();
this._openmct = openmct;
this._provider = undefined;
this.User = User;
this.status = new StatusAPI(this, openmct, config);
}
/**
@@ -53,15 +47,12 @@ class UserAPI extends EventEmitter {
*/
setProvider(provider) {
if (this.hasProvider()) {
this.error(MULTIPLE_PROVIDER_ERROR);
this._error(MULTIPLE_PROVIDER_ERROR);
}
this._provider = provider;
this.emit('providerAdded', this._provider);
}
getProvider() {
return this._provider;
this.emit('providerAdded', this._provider);
}
/**
@@ -83,7 +74,7 @@ class UserAPI extends EventEmitter {
* @throws Will throw an error if no user provider is set
*/
getCurrentUser() {
this.noProviderCheck();
this._noProviderCheck();
return this._provider.getCurrentUser();
}
@@ -114,7 +105,7 @@ class UserAPI extends EventEmitter {
* @throws Will throw an error if no user provider is set
*/
hasRole(roleId) {
this.noProviderCheck();
this._noProviderCheck();
return this._provider.hasRole(roleId);
}
@@ -125,9 +116,9 @@ class UserAPI extends EventEmitter {
* @private
* @throws Will throw an error if no user provider is set
*/
noProviderCheck() {
_noProviderCheck() {
if (!this.hasProvider()) {
this.error(NO_PROVIDER_ERROR);
this._error(NO_PROVIDER_ERROR);
}
}
@@ -138,26 +129,9 @@ class UserAPI extends EventEmitter {
* @param {string} error description of error
* @throws Will throw error passed in
*/
error(error) {
_error(error) {
throw new Error(error);
}
}
export default UserAPI;
/**
* @typedef {String} Role
*/
/**
* @typedef {Object} OpenMCT
*/
/**
* @typedef {{statusStyles: Object.<string, StatusStyleDefinition>}} UserAPIConfiguration
*/
/**
* @typedef {Object} StatusStyleDefinition
* @property {String} iconClass The icon class to apply to the status indicator when this status is active "icon-circle-slash",
* @property {String} iconClassPoll The icon class to apply to the poll question indicator when this style is active eg. "icon-status-poll-question-mark"
* @property {String} statusClass The class to apply to the indicator when this status is active eg. "s-status-error"
* @property {String} statusBgColor The background color to apply in the status summary section of the poll question popup for this status eg."#9900cc"
* @property {String} statusFgColor The foreground color to apply in the status summary section of the poll question popup for this status eg. "#fff"
*/

View File

@@ -40,13 +40,11 @@ describe("The User API", () => {
});
afterEach(() => {
const activeOverlays = openmct.overlays.activeOverlays;
activeOverlays.forEach(overlay => overlay.dismiss());
return resetApplicationState(openmct);
});
describe('with regard to user providers', () => {
it('allows you to specify a user provider', () => {
openmct.user.on('providerAdded', (provider) => {
expect(provider).toBeInstanceOf(ExampleUserProvider);

View File

@@ -1,36 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default class UserProvider {
/**
* @returns {Promise<User>} A promise that resolves with the currently logged in user
*/
getCurrentUser() {}
/**
* @returns {Boolean} true if a user is currently logged in, otherwise false
*/
isLoggedIn() {}
/**
* @param {String} role
* @returns {Promise<Boolean>} true if the current user has the given role
*/
hasRole(role) {}
}

View File

@@ -40,14 +40,6 @@ export default {
BarGraph
},
inject: ['openmct', 'domainObject', 'path'],
props: {
options: {
type: Object,
default() {
return {};
}
}
},
data() {
this.telemetryObjects = {};
this.telemetryObjectFormats = {};
@@ -255,7 +247,7 @@ export default {
}
});
let trace = {
const trace = {
key,
name: telemetryObject.name,
x: xValues,
@@ -263,18 +255,13 @@ export default {
text: yValues.map(String),
xAxisMetadata: axisMetadata.xAxisMetadata,
yAxisMetadata: axisMetadata.yAxisMetadata,
type: this.options.type ? this.options.type : 'bar',
type: 'bar',
marker: {
color: this.domainObject.configuration.barStyles.series[key].color
},
hoverinfo: 'skip'
};
if (this.options.type) {
trace.mode = 'markers';
trace.hoverinfo = 'x+y';
}
this.addTrace(trace, key);
},
isDataInTimeRange(datum, key) {

View File

@@ -1,57 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { SCATTER_PLOT_KEY } from './scatterPlotConstants';
export default function ScatterPlotCompositionPolicy(openmct) {
function hasRange(metadata) {
const rangeValues = metadata.valuesForHints(['range']).map((value) => {
return value.source;
});
const uniqueRangeValues = new Set(rangeValues);
return uniqueRangeValues && uniqueRangeValues.size > 1;
}
function hasScatterPlotTelemetry(domainObject) {
if (!openmct.telemetry.isTelemetryObject(domainObject)) {
return false;
}
let metadata = openmct.telemetry.getMetadata(domainObject);
return metadata.values().length > 0 && hasRange(metadata);
}
return {
allow: function (parent, child) {
if (parent.type === SCATTER_PLOT_KEY) {
if ((child.type === 'conditionSet') || (!hasScatterPlotTelemetry(child))) {
return false;
}
}
return true;
}
};
}

View File

@@ -1,146 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<span class="form-control">
<span
class="field control"
:class="model.cssClass"
>
<div
class="c-form--sub-grid"
>
<div class="c-form__row">
<span
class="req-indicator"
:class="{'req': isRequired}"
>
</span>
<label>Minimum X axis value</label>
<input
ref="domainMin"
v-model.number="domainMin"
data-field-name="domainMin"
type="number"
@input="onChange('domainMin')"
>
</div>
<div class="c-form__row">
<span
class="req-indicator"
:class="{'req': isRequired}"
>
</span>
<label>Maximum X axis value</label>
<input
ref="domainMax"
v-model.number="domainMax"
data-field-name="domainMax"
type="number"
@input="onChange('domainMax')"
>
</div>
<div class="c-form__row">
<span
class="req-indicator"
:class="{'req': isRequired}"
>
</span>
<label>Minimum Y axis value</label>
<input
ref="rangeMin"
v-model.number="rangeMin"
data-field-name="rangeMin"
type="number"
@input="onChange('rangeMin')"
>
</div>
<div class="c-form__row">
<span
class="req-indicator"
:class="{'req': isRequired}"
>
</span>
<label>Maximum Y axis value</label>
<input
ref="rangeMax"
v-model.number="rangeMax"
data-field-name="rangeMax"
type="number"
@input="onChange('rangeMax')"
>
</div>
</div>
</span>
</span>
</template>
<script>
export default {
props: {
model: {
type: Object,
required: true
}
},
data() {
return {
rangeMax: this.model.value.rangeMax,
rangeMin: this.model.value.rangeMin,
domainMax: this.model.value.domainMax,
domainMin: this.model.value.domainMin
};
},
computed: {
isRequired() {
return [this.rangeMax, this.rangeMin, this.domainMin, this.domainMax].some(value => value !== undefined && value !== '');
}
},
methods: {
onChange(property) {
if (this[property] === '') {
this[property] = undefined;
}
const data = {
model: this.model,
value: {
rangeMax: this.rangeMax,
rangeMin: this.rangeMin,
domainMax: this.domainMax,
domainMin: this.domainMin
}
};
if (property) {
this.model.validate(data);
}
this.$emit('onChange', data);
}
}
};
</script>

View File

@@ -1,346 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2021, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<ScatterPlotWithUnderlay
class="c-plot c-scatter-chart-view"
:data="trace"
:plot-axis-title="plotAxisTitle"
@subscribe="subscribeToAll"
@unsubscribe="removeAllSubscriptions"
/>
</template>
<script>
import ScatterPlotWithUnderlay from './ScatterPlotWithUnderlay.vue';
import _ from 'lodash';
export default {
components: {
ScatterPlotWithUnderlay
},
inject: ['openmct', 'domainObject', 'path'],
data() {
this.telemetryObjects = {};
this.telemetryObjectFormats = {};
this.valuesByTimestamp = {};
this.subscriptions = [];
return {
trace: []
};
},
computed: {
plotAxisTitle() {
const { xAxisMetadata = {}, yAxisMetadata = {} } = this.trace[0] || {};
const xAxisUnit = xAxisMetadata.units ? `(${xAxisMetadata.units})` : '';
const yAxisUnit = yAxisMetadata.units ? `(${yAxisMetadata.units})` : '';
return {
xAxisTitle: `${xAxisMetadata.name || ''} ${xAxisUnit}`,
yAxisTitle: `${yAxisMetadata.name || ''} ${yAxisUnit}`
};
}
},
mounted() {
this.setTimeContext();
this.loadComposition();
this.reloadTelemetry = this.reloadTelemetry.bind(this);
this.reloadTelemetry = _.debounce(this.reloadTelemetry, 500);
this.unobserve = this.openmct.objects.observe(this.domainObject, 'configuration.axes', this.reloadTelemetry);
this.unobserveUnderlayRanges = this.openmct.objects.observe(this.domainObject, 'configuration.ranges', this.reloadTelemetry);
},
beforeDestroy() {
this.stopFollowingTimeContext();
if (!this.composition) {
return;
}
this.removeAllSubscriptions();
this.composition.off('add', this.addToComposition);
this.composition.off('remove', this.removeTelemetryObject);
if (this.unobserve) {
this.unobserve();
}
if (this.unobserveUnderlayRanges) {
this.unobserveUnderlayRanges();
}
},
methods: {
setTimeContext() {
this.stopFollowingTimeContext();
this.timeContext = this.openmct.time.getContextForView(this.path);
this.followTimeContext();
},
followTimeContext() {
this.timeContext.on('bounds', this.reloadTelemetry);
},
stopFollowingTimeContext() {
if (this.timeContext) {
this.timeContext.off('bounds', this.reloadTelemetry);
}
},
addToComposition(telemetryObject) {
if (Object.values(this.telemetryObjects).length > 0) {
this.confirmRemoval(telemetryObject);
} else {
this.addTelemetryObject(telemetryObject);
}
},
removeFromComposition(telemetryObject) {
let composition = this.domainObject.composition.filter(id =>
!this.openmct.objects.areIdsEqual(id, telemetryObject.identifier)
);
this.openmct.objects.mutate(this.domainObject, 'composition', composition);
},
addTelemetryObject(telemetryObject) {
// grab information we need from the added telmetry object
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
this.telemetryObjects[key] = telemetryObject;
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
this.telemetryObjectFormats[key] = this.openmct.telemetry.getFormatMap(metadata);
this.getDataForTelemetry(key);
},
confirmRemoval(telemetryObject) {
const dialog = this.openmct.overlays.dialog({
iconClass: 'alert',
message: 'This action will replace the current telemetry source. Do you want to continue?',
buttons: [
{
label: 'Ok',
emphasis: true,
callback: () => {
const oldTelemetryObject = Object.values(this.telemetryObjects)[0];
this.removeFromComposition(oldTelemetryObject);
this.removeTelemetryObject(oldTelemetryObject.identifier);
this.valuesByTimestamp = {};
this.addTelemetryObject(telemetryObject);
dialog.dismiss();
}
},
{
label: 'Cancel',
callback: () => {
this.removeFromComposition(telemetryObject);
dialog.dismiss();
}
}
]
});
},
getTelemetryProcessor(keyString) {
return (telemetry) => {
//Check that telemetry object has not been removed since telemetry was requested.
const telemetryObject = this.telemetryObjects[keyString];
if (!telemetryObject) {
return;
}
telemetry.forEach(datum => {
this.addDataToGraph(telemetryObject, datum);
});
this.updateTrace(telemetryObject);
};
},
getAxisMetadata(telemetryObject) {
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
if (!metadata) {
return {};
}
return metadata.valuesForHints(['range']);
},
loadComposition() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addToComposition);
this.composition.on('remove', this.removeTelemetryObject);
this.composition.load();
},
reloadTelemetry() {
this.valuesByTimestamp = {};
Object.keys(this.telemetryObjects).forEach(key => {
this.getDataForTelemetry(key);
});
},
getDataForTelemetry(key) {
const telemetryObject = this.telemetryObjects[key];
if (!telemetryObject) {
return;
}
const telemetryProcessor = this.getTelemetryProcessor(key);
const options = this.getOptions();
this.openmct.telemetry.request(telemetryObject, options).then(telemetryProcessor);
this.subscribeToObject(telemetryObject);
},
removeTelemetryObject(identifier) {
const key = this.openmct.objects.makeKeyString(identifier);
if (this.telemetryObjects[key]) {
delete this.telemetryObjects[key];
}
if (this.telemetryObjectFormats && this.telemetryObjectFormats[key]) {
delete this.telemetryObjectFormats[key];
}
this.removeSubscription(key);
},
addDataToGraph(telemetryObject, data) {
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (data.message) {
this.openmct.notifications.alert(data.message);
}
if (!this.domainObject.configuration.axes.xKey || !this.domainObject.configuration.axes.yKey) {
return;
}
const timestamp = this.getTimestampForDatum(data, key, telemetryObject);
let valueForTimestamp = this.valuesByTimestamp[timestamp] || {};
//populate x values
let metadataKey = this.domainObject.configuration.axes.xKey;
if (data[metadataKey] !== undefined) {
valueForTimestamp.x = this.format(key, metadataKey, data);
}
metadataKey = this.domainObject.configuration.axes.yKey;
if (data[metadataKey] !== undefined) {
valueForTimestamp.y = this.format(key, metadataKey, data);
}
this.valuesByTimestamp[timestamp] = valueForTimestamp;
},
updateTrace(telemetryObject) {
const xAndyValues = Object.values(this.valuesByTimestamp);
const xValues = xAndyValues.map(value => value.x);
const yValues = xAndyValues.map(value => value.y);
const axisMetadata = this.getAxisMetadata(telemetryObject);
const xAxisMetadata = axisMetadata.find(metadata => metadata.source === this.domainObject.configuration.axes.xKey);
let yAxisMetadata = {};
if (this.domainObject.configuration.axes.yKey) {
yAxisMetadata = axisMetadata.find(metadata => metadata.source === this.domainObject.configuration.axes.yKey);
}
let trace = {
key: this.openmct.objects.makeKeyString(this.domainObject.identifier),
name: this.domainObject.name,
x: xValues,
y: yValues,
text: yValues.map(String),
xAxisMetadata: xAxisMetadata,
yAxisMetadata: yAxisMetadata,
type: 'scatter',
mode: 'markers',
marker: {
color: this.domainObject.configuration.styles.color
},
hoverinfo: 'x+y'
};
if (this.domainObject.configuration.ranges !== undefined && this.domainObject.configuration.ranges.domainMin !== undefined && this.domainObject.configuration.ranges.domainMax !== undefined) {
trace.xaxis = {
min: this.domainObject.configuration.ranges.domainMin,
max: this.domainObject.configuration.ranges.domainMax
};
}
if (this.domainObject.configuration.ranges !== undefined && this.domainObject.configuration.ranges.rangeMin !== undefined && this.domainObject.configuration.ranges.rangeMax !== undefined) {
trace.yaxis = {
min: this.domainObject.configuration.ranges.rangeMin,
max: this.domainObject.configuration.ranges.rangeMax
};
}
this.trace = [trace];
},
getTimestampForDatum(datum, key, telemetryObject) {
const timeSystemKey = this.timeContext.timeSystem().key;
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
let metadataValue = metadata.value(timeSystemKey) || { format: timeSystemKey };
return this.parse(key, metadataValue.source, datum);
},
format(telemetryObjectKey, metadataKey, data) {
const formats = this.telemetryObjectFormats[telemetryObjectKey];
return formats[metadataKey].format(data);
},
parse(telemetryObjectKey, metadataKey, datum) {
if (!datum) {
return;
}
const formats = this.telemetryObjectFormats[telemetryObjectKey];
return formats[metadataKey].parse(datum);
},
getOptions() {
const { start, end } = this.timeContext.bounds();
return {
end,
start
};
},
subscribeToObject(telemetryObject) {
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
this.removeSubscription(key);
const options = this.getOptions();
const unsubscribe = this.openmct.telemetry.subscribe(telemetryObject,
data => this.addDataToGraph(telemetryObject, data)
, options);
this.subscriptions.push({
key,
unsubscribe
});
},
subscribeToAll() {
const telemetryObjects = Object.values(this.telemetryObjects);
telemetryObjects.forEach(this.subscribeToObject);
},
removeAllSubscriptions() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
this.subscriptions = [];
},
removeSubscription(key) {
const found = this.subscriptions.findIndex(subscription => subscription.key === key);
if (found > -1) {
this.subscriptions[found].unsubscribe();
this.subscriptions.splice(found, 1);
}
}
}
};
</script>

View File

@@ -1,79 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ScatterPlotView from './ScatterPlotView.vue';
import { SCATTER_PLOT_KEY, SCATTER_PLOT_VIEW, TIME_STRIP_KEY } from './scatterPlotConstants.js';
import Vue from 'vue';
export default function ScatterPlotViewProvider(openmct) {
function isCompactView(objectPath) {
let isChildOfTimeStrip = objectPath.find(object => object.type === TIME_STRIP_KEY);
return isChildOfTimeStrip && !openmct.router.isNavigatedObject(objectPath);
}
return {
key: SCATTER_PLOT_VIEW,
name: 'Scatter Plot',
cssClass: 'icon-telemetry',
canView(domainObject, objectPath) {
return domainObject && domainObject.type === SCATTER_PLOT_KEY;
},
canEdit(domainObject, objectPath) {
return domainObject && domainObject.type === SCATTER_PLOT_KEY;
},
view: function (domainObject, objectPath) {
let component;
return {
show: function (element) {
let isCompact = isCompactView(objectPath);
component = new Vue({
el: element,
components: {
ScatterPlotView
},
provide: {
openmct,
domainObject,
path: objectPath
},
data() {
return {
options: {
compact: isCompact
}
};
},
template: '<scatter-plot-view :options="options"></scatter-plot-view>'
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
};
}
};
}

View File

@@ -1,393 +0,0 @@
<template>
<div
ref="plotWrapper"
class="has-local-controls"
:class="{ 's-unsynced' : isZoomed }"
>
<div
v-if="isZoomed"
class="l-state-indicators"
>
<span
class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
></span>
</div>
<div
ref="plot"
class="c-scatter-chart"
></div>
<div
ref="localControl"
class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
>
<button
v-if="data.length"
class="c-button icon-reset"
:disabled="!isZoomed"
title="Reset pan/zoom"
@click="reset()"
>
</button>
</div>
</div>
</template>
<script>
import Plotly from 'plotly-basic';
const MULTI_AXES_X_PADDING_PERCENT = {
LEFT: 8,
RIGHT: 94
};
import { getValidatedData } from "@/plugins/plan/util";
const PATH_COLORS = ['blue', 'red', 'green'];
const MARKER_COLOR = 'white';
export default {
inject: ['openmct', 'domainObject'],
props: {
data: {
type: Array,
default() {
return [];
}
},
plotAxisTitle: {
type: Object,
default() {
return {};
}
}
},
data() {
return {
isZoomed: false,
yAxisRange: {
min: '',
max: ''
},
xAxisRange: {
min: '',
max: ''
}
};
},
watch: {
data: {
immediate: false,
handler: 'updateData'
}
},
mounted() {
this.getUnderlayPlotData();
Plotly.newPlot(this.$refs.plot, Array.from(this.data.concat(this.getShapes(this.shapesData))), this.getLayout(), {
responsive: true,
displayModeBar: false
});
this.registerListeners();
this.$refs.plot.on('plotly_relayout', this.zoom);
},
beforeDestroy() {
if (this.$refs.plot && this.$refs.plot.off) {
this.$refs.plot.off('plotly_relayout', this.zoom);
}
if (this.plotResizeObserver) {
this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
clearTimeout(this.resizeTimer);
}
if (this.unlistenUnderlay) {
this.unlistenUnderlay();
}
if (this.unlistenUnderlayRanges) {
this.unlistenUnderlayRanges();
}
if (this.unobserveColorChanges) {
this.unobserveColorChanges();
}
},
methods: {
getUnderlayPlotData() {
if (this.domainObject.selectFile) {
this.shapesData = getValidatedData(this.domainObject);
} else {
this.shapesData = [];
}
},
observeForUnderlayPlotChanges() {
this.getUnderlayPlotData();
this.updateData();
},
getAxisMinMax() {
if (!this.data.length) {
return;
}
// For now, use x and y axes min, max values only if an underlay is available
if (this.shapesData.length && this.data[0].xaxis) {
this.xAxisRange = this.data[0].xaxis;
}
if (this.shapesData.length && this.data[0].yaxis) {
this.yAxisRange = this.data[0].yaxis;
}
},
getLayout() {
this.getAxisMinMax();
const yAxesMeta = this.getYAxisMeta();
const primaryYaxis = this.getYaxisLayout(yAxesMeta['1']);
const xAxisDomain = this.getXAxisDomain(yAxesMeta);
const shapes = this.shapesData.map((shapeData, index) => {
if (!shapeData.x || !shapeData.y
|| !shapeData.x.length || !shapeData.y.length
|| shapeData.x.length !== shapeData.y.length) {
return "";
}
let path = `M ${shapeData.x[0]},${shapeData.y[0]}`;
shapeData.x.forEach((point, shapeIndex) => {
if (shapeIndex > 0) {
path = `${path} L${point},${shapeData.y[shapeIndex]}`;
}
});
return {
path,
type: 'path',
line: {
color: PATH_COLORS[index]
},
opacity: 0.5
};
});
return {
autosize: true,
showlegend: false,
textposition: 'auto',
font: {
family: 'Helvetica Neue, Helvetica, Arial, sans-serif',
size: '12px',
color: '#666'
},
xaxis: {
domain: xAxisDomain,
range: [this.xAxisRange.min, this.xAxisRange.max],
title: this.plotAxisTitle.xAxisTitle,
automargin: true
},
yaxis: primaryYaxis,
margin: {
l: 5,
r: 5,
t: 5,
b: 0
},
paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent',
shapes,
layer: 'below'
};
},
getYAxisMeta() {
const yAxisMeta = {};
this.data.forEach(datum => {
const yAxisMetadata = datum.yAxisMetadata;
const range = '1';
const side = 'left';
const name = yAxisMetadata.name;
const unit = yAxisMetadata.units;
yAxisMeta[range] = {
range,
side,
name,
unit
};
});
return yAxisMeta;
},
getXAxisDomain(yAxisMeta) {
let leftPaddingPerc = 0;
let rightPaddingPerc = 100;
let rightSide = yAxisMeta && Object.values(yAxisMeta).filter((axisMeta => axisMeta.side === 'right'));
let leftSide = yAxisMeta && Object.values(yAxisMeta).filter((axisMeta => axisMeta.side === 'left'));
if (yAxisMeta && rightSide.length > 1) {
rightPaddingPerc = MULTI_AXES_X_PADDING_PERCENT.RIGHT;
}
if (yAxisMeta && leftSide.length > 1) {
leftPaddingPerc = MULTI_AXES_X_PADDING_PERCENT.LEFT;
}
return [leftPaddingPerc / 100, rightPaddingPerc / 100];
},
getYaxisLayout(yAxisMeta) {
if (!yAxisMeta) {
return {};
}
const { name, range, side = 'left', unit } = yAxisMeta;
const title = `${name} ${unit ? '(' + unit + ')' : ''}`;
const yaxis = {
automargin: true,
title
};
let yRange = this.yAxisRange;
if (range === '1') {
yaxis.range = [yRange.min, yRange.max];
return yaxis;
}
yaxis.range = [yRange.min, yRange.max];
yaxis.anchor = side.toLowerCase() === 'left'
? 'free'
: 'x';
yaxis.showline = side.toLowerCase() === 'left';
yaxis.side = side.toLowerCase();
yaxis.overlaying = 'y';
yaxis.position = 0.01;
return yaxis;
},
registerListeners() {
this.unobserveColorChanges = this.openmct.objects.observe(this.domainObject, 'configuration.styles.color', this.updateColors);
this.unlistenUnderlay = this.openmct.objects.observe(this.domainObject, 'selectFile', this.observeForUnderlayPlotChanges);
this.unlistenUnderlayRanges = this.openmct.objects.observe(this.domainObject, 'configuration.ranges', this.updateData);
this.resizeTimer = false;
if (window.ResizeObserver) {
this.plotResizeObserver = new ResizeObserver(() => {
// debounce and trigger window resize so that plotly can resize the plot
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
window.dispatchEvent(new Event('resize'));
}, 250);
});
this.plotResizeObserver.observe(this.$refs.plotWrapper);
}
},
updateColors() {
const colors = [];
const indices = [];
this.data.forEach((item, index) => {
const colorExists = this.domainObject.configuration.styles.color;
indices.push(index);
if (colorExists) {
colors.push(this.domainObject.configuration.styles.color);
} else {
colors.push(item.marker.color);
}
});
const plotUpdate = {
'marker.color': colors
};
Plotly.restyle(this.$refs.plot, plotUpdate, indices);
},
reset() {
this.isZoomed = false;
this.updatePlot();
this.$emit('subscribe');
},
updateData() {
this.updatePlot();
},
updateLocalControlPosition() {
const localControl = this.$refs.localControl;
localControl.style.display = 'none';
const plot = this.$refs.plot;
const bgLayer = this.$el.querySelector('.bglayer');
const plotBoundingRect = plot.getBoundingClientRect();
const bgLayerBoundingRect = bgLayer.getBoundingClientRect();
const top = bgLayerBoundingRect.top - plotBoundingRect.top + 5;
const left = bgLayerBoundingRect.left - plotBoundingRect.left + 5;
localControl.style.top = `${top}px`;
localControl.style.left = `${left}px`;
localControl.style.display = 'block';
},
updatePlot() {
if (!this.$refs || !this.$refs.plot || this.isZoomed) {
return;
}
Plotly.react(this.$refs.plot, Array.from(this.data.concat(this.getShapes(this.shapesData))), this.getLayout());
},
zoom(eventData) {
const autorange = eventData['xaxis.autorange'];
const { autosize } = eventData;
if (autosize || autorange) {
return;
}
this.isZoomed = true;
this.$emit('unsubscribe');
},
getShapes() {
let markerData = {
x: [],
y: []
};
const shapes = this.shapesData.map((shapeData, index) => {
if (!shapeData.x || !shapeData.y
|| !shapeData.x.length || !shapeData.y.length
|| shapeData.x.length !== shapeData.y.length) {
return "";
}
let text = [];
shapeData.x.forEach((point) => {
text.push(`${parseFloat(point).toPrecision(2)}`);
});
markerData.x = markerData.x.concat(shapeData.x);
markerData.y = markerData.y.concat(shapeData.y);
return {
x: shapeData.x,
y: shapeData.y,
mode: 'text',
text,
textfont: {
family: 'Helvetica Neue, Helvetica, Arial, sans-serif',
size: '12px',
color: PATH_COLORS[index]
},
opacity: 0.5
};
});
shapes.push({
x: markerData.x,
y: markerData.y,
mode: "markers",
marker: {
size: 6,
color: MARKER_COLOR
}
});
return shapes;
}
}
};
</script>

View File

@@ -1,64 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2022, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div>
<div v-if="canEdit">
<plot-options-edit />
</div>
<div v-else>
<plot-options-browse />
</div>
</div>
</template>
<script>
import PlotOptionsBrowse from "./PlotOptionsBrowse.vue";
import PlotOptionsEdit from "./PlotOptionsEdit.vue";
export default {
components: {
PlotOptionsBrowse,
PlotOptionsEdit
},
inject: ['openmct', 'domainObject'],
data() {
return {
isEditing: this.openmct.editor.isEditing()
};
},
computed: {
canEdit() {
return this.isEditing && !this.domainObject.locked;
}
},
mounted() {
this.openmct.editor.on('isEditing', this.setEditState);
},
beforeDestroy() {
this.openmct.editor.off('isEditing', this.setEditState);
},
methods: {
setEditState(isEditing) {
this.isEditing = isEditing;
}
}
};
</script>

View File

@@ -1,153 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2022, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div class="js-plot-options-browse grid-properties">
<ul class="l-inspector-part">
<h2 title="Object view settings">Settings</h2>
<li class="grid-row">
<div
class="grid-cell label"
title="X axis selection"
>X Axis</div>
<div class="grid-cell value">{{ xKeyLabel }}</div>
</li>
<li class="grid-row">
<div
class="grid-cell label"
title="Y axis selection"
>Y Axis</div>
<div class="grid-cell value">{{ yKeyLabel }}</div>
</li>
<ColorSwatch
:current-color="currentColor"
edit-title="Manually set the color for this plot"
view-title="The marker color for this plot"
short-label="Color"
/>
</ul>
</div>
</template>
<script>
import ColorSwatch from "../../../../ui/color/ColorSwatch.vue";
import Color from "../../../../ui/color/Color";
import ColorPalette from "../../../../ui/color/ColorPalette";
export default {
components: { ColorSwatch },
inject: ['openmct', 'domainObject'],
data() {
return {
xKeyLabel: '',
yKeyLabel: '',
currentColor: undefined
};
},
mounted() {
this.plotSeries = [];
this.colorPalette = new ColorPalette();
this.initColor();
this.composition = this.openmct.composition.get(this.domainObject);
this.registerListeners();
this.composition.load();
},
beforeDestroy() {
this.stopListening();
},
methods: {
initColor() {
// this is called before the plot is initialized
if (!this.domainObject.configuration.styles || !this.domainObject.configuration.styles.color) {
const color = this.colorPalette.getNextColor().asHexString();
this.domainObject.configuration.styles = {
color
};
}
this.currentColor = this.domainObject.configuration.styles.color;
const colorObject = Color.fromHexString(this.currentColor);
this.colorPalette.remove(colorObject);
},
registerListeners() {
this.composition.on('add', this.addSeries);
this.composition.on('remove', this.removeSeries);
this.unobserve = this.openmct.objects.observe(this.domainObject, 'configuration.axes', this.setAxesLabels);
},
stopListening() {
this.composition.off('add', this.addSeries);
this.composition.off('remove', this.removeSeries);
if (this.unobserve) {
this.unobserve();
}
},
addSeries(series, index) {
this.$set(this.plotSeries, this.plotSeries.length, series);
this.setAxesLabels();
},
removeSeries(series) {
const index = this.plotSeries.findIndex(plotSeries => this.openmct.objects.areIdsEqual(series.identifier, plotSeries.identifier));
if (index !== undefined) {
this.$delete(this.plotSeries, index);
this.setAxesLabels();
}
},
setAxesLabels() {
let xKeyOptions = [];
let yKeyOptions = [];
if (this.plotSeries.length <= 0) {
return;
}
const series = this.plotSeries[0];
const metadataValues = this.openmct.telemetry.getMetadata(series).valuesForHints(['range']);
metadataValues.forEach((metadataValue) => {
xKeyOptions.push({
name: metadataValue.name || metadataValue.key,
value: metadataValue.source || metadataValue.key
});
yKeyOptions.push({
name: metadataValue.name || metadataValue.key,
value: metadataValue.source || metadataValue.key
});
});
let xKeyOptionIndex;
let yKeyOptionIndex;
if (this.domainObject.configuration.axes.xKey) {
xKeyOptionIndex = xKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.xKey);
if (xKeyOptionIndex > -1) {
this.xKeyLabel = xKeyOptions[xKeyOptionIndex].name;
}
}
if (metadataValues.length > 1 && this.domainObject.configuration.axes.yKey) {
yKeyOptionIndex = yKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.yKey);
if (yKeyOptionIndex > -1) {
this.yKeyLabel = yKeyOptions[yKeyOptionIndex].name;
}
}
}
}
};
</script>

View File

@@ -1,262 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2022, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div class="js-plot-options-edit grid-properties">
<ul class="l-inspector-part">
<h2 title="Object view settings">Settings</h2>
<li class="grid-row">
<div
class="grid-cell label"
title="X axis selection."
>X Axis</div>
<div class="grid-cell value">
<select
v-model="xKey"
@change="updateForm('xKey')"
>
<option
v-for="option in xKeyOptions"
:key="`xKey-${option.value}`"
:value="option.value"
:selected="option.value == xKey"
>
{{ option.name }}
</option>
</select>
</div>
</li>
<li class="grid-row">
<div
class="grid-cell label"
title="Y axis selection."
>Y Axis</div>
<div class="grid-cell value">
<select
v-model="yKey"
@change="updateForm('yKey')"
>
<option
v-for="option in yKeyOptions"
:key="`yKey-${option.value}`"
:value="option.value"
:selected="option.value == yKey"
>
{{ option.name }}
</option>
</select>
</div>
</li>
<ColorSwatch
:current-color="currentColor"
title="Manually set the line and marker color for this plot."
edit-title="Manually set the line and marker color for this plot."
view-title="The line and marker color for this plot."
short-label="Color"
@colorSet="setColor"
/>
</ul>
</div>
</template>
<script>
import Color from "../../../../ui/color/Color";
import ColorPalette from "../../../../ui/color/ColorPalette";
import ColorSwatch from "../../../../ui/color/ColorSwatch.vue";
export default {
components: { ColorSwatch },
inject: ['openmct', 'domainObject'],
data() {
return {
xKey: undefined,
yKey: undefined,
xKeyOptions: [],
yKeyOptions: [],
currentColor: undefined
};
},
mounted() {
this.plotSeries = [];
this.colorPalette = new ColorPalette();
this.initColor();
this.composition = this.openmct.composition.get(this.domainObject);
this.registerListeners();
this.composition.load();
},
beforeDestroy() {
this.stopListening();
},
methods: {
initColor() {
// this is called before the plot is initialized
if (!this.domainObject.configuration.styles || !this.domainObject.configuration.styles.color) {
const color = this.colorPalette.getNextColor().asHexString();
this.domainObject.configuration.styles = {
color
};
}
this.currentColor = this.domainObject.configuration.styles.color;
const colorObject = Color.fromHexString(this.currentColor);
this.colorPalette.remove(colorObject);
},
setColor(chosenColor) {
this.currentColor = chosenColor.asHexString();
this.openmct.objects.mutate(
this.domainObject,
`configuration.styles.color`,
this.currentColor
);
},
registerListeners() {
this.composition.on('add', this.addSeries);
this.composition.on('remove', this.removeSeries);
this.unobserve = this.openmct.objects.observe(this.domainObject, 'configuration.axes', this.setupOptions);
},
stopListening() {
this.composition.off('add', this.addSeries);
this.composition.off('remove', this.removeSeries);
if (this.unobserve) {
this.unobserve();
}
},
addSeries(series, index) {
this.$set(this.plotSeries, this.plotSeries.length, series);
this.setupOptions();
},
removeSeries(seriesIdentifier) {
const index = this.plotSeries.findIndex(plotSeries => this.openmct.objects.areIdsEqual(seriesIdentifier, plotSeries.identifier));
if (index >= 0) {
this.$delete(this.plotSeries, index);
this.setupOptions();
}
},
setupOptions() {
this.xKeyOptions = [];
this.yKeyOptions = [];
if (this.plotSeries.length <= 0) {
return;
}
let update = false;
const series = this.plotSeries[0];
const metadataValues = this.openmct.telemetry.getMetadata(series).valuesForHints(['range']);
metadataValues.forEach((metadataValue) => {
this.xKeyOptions.push({
name: metadataValue.name || metadataValue.key,
value: metadataValue.source || metadataValue.key
});
this.yKeyOptions.push({
name: metadataValue.name || metadataValue.key,
value: metadataValue.source || metadataValue.key
});
});
let xKeyOptionIndex;
let yKeyOptionIndex;
if (this.domainObject.configuration.axes.xKey) {
xKeyOptionIndex = this.xKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.xKey);
if (xKeyOptionIndex > -1) {
this.xKey = this.xKeyOptions[xKeyOptionIndex].value;
} else {
this.xKey = undefined;
}
}
if (this.xKey === undefined) {
update = true;
xKeyOptionIndex = 0;
this.xKey = this.xKeyOptions[xKeyOptionIndex].value;
}
if (metadataValues.length > 1) {
if (this.domainObject.configuration.axes.yKey) {
yKeyOptionIndex = this.yKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.yKey);
if (yKeyOptionIndex > -1 && yKeyOptionIndex !== xKeyOptionIndex) {
this.yKey = this.yKeyOptions[yKeyOptionIndex].value;
} else {
this.yKey = undefined;
}
}
if (this.yKey === undefined) {
update = true;
yKeyOptionIndex = this.yKeyOptions.findIndex((option, index) => index !== xKeyOptionIndex);
this.yKey = this.yKeyOptions[yKeyOptionIndex].value;
}
this.yKeyOptions = this.yKeyOptions.map((option, index) => {
if (index === xKeyOptionIndex) {
option.name = `${option.name} (swap)`;
option.swap = yKeyOptionIndex;
} else {
option.name = option.name.replace(' (swap)', '');
option.swap = undefined;
}
return option;
});
}
this.xKeyOptions = this.xKeyOptions.map((option, index) => {
if (index === yKeyOptionIndex) {
option.name = `${option.name} (swap)`;
option.swap = xKeyOptionIndex;
} else {
option.name = option.name.replace(' (swap)', '');
option.swap = undefined;
}
return option;
});
if (update === true) {
this.saveConfiguration();
}
},
updateForm(property) {
if (property === 'xKey') {
const xKeyOption = this.xKeyOptions.find(option => option.value === this.xKey);
if (xKeyOption.swap !== undefined) {
//swap
this.yKey = this.xKeyOptions[xKeyOption.swap].value;
}
} else if (property === 'yKey') {
const yKeyOption = this.yKeyOptions.find(option => option.value === this.yKey);
if (yKeyOption.swap !== undefined) {
//swap
this.xKey = this.yKeyOptions[yKeyOption.swap].value;
}
}
this.saveConfiguration();
},
saveConfiguration() {
this.openmct.objects.mutate(this.domainObject, `configuration.axes`, {
xKey: this.xKey,
yKey: this.yKey
});
}
}
};
</script>

View File

@@ -1,48 +0,0 @@
import { SCATTER_PLOT_INSPECTOR_KEY, SCATTER_PLOT_KEY } from '../scatterPlotConstants';
import Vue from 'vue';
import PlotOptions from "./PlotOptions.vue";
export default function ScatterPlotInspectorViewProvider(openmct) {
return {
key: SCATTER_PLOT_INSPECTOR_KEY,
name: 'Bar Graph Inspector View',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 0) {
return false;
}
let object = selection[0][0].context.item;
return object
&& object.type === SCATTER_PLOT_KEY;
},
view: function (selection) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
PlotOptions
},
provide: {
openmct,
domainObject: selection[0][0].context.item
},
template: '<plot-options></plot-options>'
});
},
destroy: function () {
if (component) {
component.$destroy();
component = undefined;
}
}
};
},
priority: function () {
return 1;
}
};
}

View File

@@ -1,127 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { SCATTER_PLOT_KEY } from './scatterPlotConstants.js';
import ScatterPlotViewProvider from './ScatterPlotViewProvider';
import ScatterPlotInspectorViewProvider from './inspector/ScatterPlotInspectorViewProvider';
import ScatterPlotCompositionPolicy from './ScatterPlotCompositionPolicy';
import Vue from "vue";
import ScatterPlotForm from "./ScatterPlotForm.vue";
export default function () {
return function install(openmct) {
openmct.forms.addNewFormControl('scatter-plot-form-control', getScatterPlotFormControl(openmct));
openmct.types.addType(SCATTER_PLOT_KEY, {
key: SCATTER_PLOT_KEY,
name: "Scatter Plot",
cssClass: "icon-plot-scatter",
description: "View data as a scatter plot.",
creatable: true,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
styles: {},
axes: {},
ranges: {}
};
},
form: [
{
name: 'Underlay data (JSON file)',
key: 'selectFile',
control: 'file-input',
text: 'Select File...',
type: 'application/json',
removable: true,
hideFromInspector: true,
property: [
"selectFile"
]
},
{
name: "Underlay ranges",
control: "scatter-plot-form-control",
cssClass: "l-input",
key: "scatterPlotForm",
required: false,
hideFromInspector: false,
property: [
"configuration",
"ranges"
],
validate: ({ value }, callback) => {
const { rangeMin, rangeMax, domainMin, domainMax } = value;
const valid = {
rangeMin,
rangeMax,
domainMin,
domainMax
};
if (callback) {
callback(valid);
}
const values = Object.values(valid);
const hasAllValues = values.every(rangeValue => rangeValue !== undefined);
const hasNoValues = values.every(rangeValue => rangeValue === undefined);
return hasAllValues || hasNoValues;
}
}
],
priority: 891
});
openmct.objectViews.addProvider(new ScatterPlotViewProvider(openmct));
openmct.inspectorViews.addProvider(new ScatterPlotInspectorViewProvider(openmct));
openmct.composition.addPolicy(new ScatterPlotCompositionPolicy(openmct).allow);
};
function getScatterPlotFormControl(openmct) {
return {
show(element, model, onChange) {
const rowComponent = new Vue({
el: element,
components: {
ScatterPlotForm
},
provide: {
openmct
},
data() {
return {
model,
onChange
};
},
template: `<scatter-plot-form :model="model" @onChange="onChange"></scatter-plot-form>`
});
return rowComponent;
}
};
}
}

View File

@@ -1,421 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {createOpenMct, resetApplicationState} from "utils/testing";
import Vue from "vue";
import ScatterPlotPlugin from "./plugin";
import ScatterPlot from './ScatterPlotView.vue';
import EventEmitter from "EventEmitter";
import { SCATTER_PLOT_VIEW, SCATTER_PLOT_KEY } from './scatterPlotConstants';
describe("the plugin", function () {
let element;
let child;
let openmct;
let telemetryPromise;
let telemetryPromiseResolve;
let mockObjectPath;
beforeEach((done) => {
mockObjectPath = [
{
name: 'mock folder',
type: 'fake-folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
}
];
const testTelemetry = [
{
'utc': 1,
'some-key': 'some-value 1',
'some-other-key': 'some-other-value 1'
},
{
'utc': 2,
'some-key': 'some-value 2',
'some-other-key': 'some-other-value 2'
},
{
'utc': 3,
'some-key': 'some-value 3',
'some-other-key': 'some-other-value 3'
}
];
openmct = createOpenMct();
telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve;
});
spyOn(openmct.telemetry, 'request').and.callFake(() => {
telemetryPromiseResolve(testTelemetry);
return telemetryPromise;
});
openmct.install(new ScatterPlotPlugin());
element = document.createElement("div");
element.style.width = "640px";
element.style.height = "480px";
child = document.createElement("div");
child.style.width = "640px";
child.style.height = "480px";
element.appendChild(child);
document.body.appendChild(element);
spyOn(window, 'ResizeObserver').and.returnValue({
observe() {},
unobserve() {},
disconnect() {}
});
openmct.time.timeSystem("utc", {
start: 0,
end: 4
});
openmct.types.addType("test-object", {
creatable: true
});
openmct.on("start", done);
openmct.startHeadless();
});
afterEach((done) => {
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
resetApplicationState(openmct).then(done).catch(done);
});
describe("The scatter plot view", () => {
let testDomainObject;
let scatterPlotObject;
// eslint-disable-next-line no-unused-vars
let component;
let mockComposition;
beforeEach(async () => {
scatterPlotObject = {
identifier: {
namespace: "",
key: "test-plot"
},
type: "telemetry.plot.scatter-plot",
name: "Test Scatter Plot",
configuration: {
axes: {},
styles: {}
}
};
testDomainObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key",
name: "Some attribute",
hints: {
range: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 2
}
}]
}
};
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testDomainObject);
return [testDomainObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement("div");
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
ScatterPlot
},
provide: {
openmct: openmct,
domainObject: scatterPlotObject,
composition: openmct.composition.get(scatterPlotObject)
},
template: "<ScatterPlot></ScatterPlot>"
});
await Vue.nextTick();
});
it("provides a scatter plot view", () => {
const applicableViews = openmct.objectViews.get(scatterPlotObject, mockObjectPath);
const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === SCATTER_PLOT_VIEW);
expect(plotViewProvider).toBeDefined();
});
it("Renders plotly scatter plot", () => {
let scatterPlotElement = element.querySelectorAll(".plotly");
expect(scatterPlotElement.length).toBe(1);
});
});
describe("the scatter plot objects", () => {
const mockObject = {
name: 'A very nice scatter plot',
key: SCATTER_PLOT_KEY,
creatable: true
};
it('defines a scatter plot object type with the correct key', () => {
const objectDef = openmct.types.get(SCATTER_PLOT_KEY).definition;
expect(objectDef.key).toEqual(mockObject.key);
});
it('is creatable', () => {
const objectDef = openmct.types.get(SCATTER_PLOT_KEY).definition;
expect(objectDef.creatable).toEqual(mockObject.creatable);
});
});
describe("The scatter plot composition policy", () => {
it("allows composition for telemetry that contain at least 2 ranges", () => {
const parent = {
"composition": [],
"configuration": {
axes: {},
styles: {}
},
"name": "Some Scatter Plot",
"type": "telemetry.plot.scatter-plot",
"location": "mine",
"modified": 1631005183584,
"persisted": 1631005183502,
"identifier": {
"namespace": "",
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
}
};
const testTelemetryObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "some-key",
name: "Some attribute",
hints: {
domain: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 1
}
}, {
key: "some-other-key2",
name: "Another attribute2",
hints: {
range: 2
}
}]
}
};
const composition = openmct.composition.get(parent);
expect(() => {
composition.add(testTelemetryObject);
}).not.toThrow();
expect(parent.composition.length).toBe(1);
});
it("disallows composition for telemetry that don't contain at least 2 range hints", () => {
const parent = {
"composition": [],
"configuration": {
axes: {},
styles: {}
},
"name": "Some Scatter Plot",
"type": "telemetry.plot.scatter-plot",
"location": "mine",
"modified": 1631005183584,
"persisted": 1631005183502,
"identifier": {
"namespace": "",
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
}
};
const testTelemetryObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "some-key",
name: "Some attribute",
hints: {
domain: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 1
}
}]
}
};
const composition = openmct.composition.get(parent);
expect(() => {
composition.add(testTelemetryObject);
}).toThrow();
expect(parent.composition.length).toBe(0);
});
});
describe('the inspector view', () => {
let mockComposition;
let testDomainObject;
let selection;
let plotInspectorView;
let viewContainer;
let optionsElement;
beforeEach(async () => {
testDomainObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key",
name: "Some attribute",
hints: {
range: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 2
}
}]
}
};
selection = [
[
{
context: {
item: {
id: "test-object",
identifier: {
key: "test-object",
namespace: ''
},
type: "telemetry.plot.scatter-plot",
configuration: {
axes: {},
styles: {
}
},
composition: [
{
key: '~Some~foo.scatter'
}
]
}
}
}
]
];
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testDomainObject);
return [testDomainObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
viewContainer = document.createElement('div');
child.append(viewContainer);
const applicableViews = openmct.inspectorViews.get(selection);
plotInspectorView = applicableViews[0];
plotInspectorView.show(viewContainer);
await Vue.nextTick();
optionsElement = element.querySelector('.c-scatter-plot-options');
});
afterEach(() => {
plotInspectorView.destroy();
});
it('it renders the options', () => {
expect(optionsElement).toBeDefined();
});
});
});

View File

@@ -1,4 +0,0 @@
export const SCATTER_PLOT_VIEW = 'scatter-plot.view';
export const SCATTER_PLOT_KEY = 'telemetry.plot.scatter-plot';
export const SCATTER_PLOT_INSPECTOR_KEY = 'telemetry.plot.scatter-plot.inspector';
export const TIME_STRIP_KEY = 'time-strip';

View File

@@ -91,7 +91,7 @@ export default {
width: DEFAULT_TELEMETRY_DIMENSIONS[0],
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
displayMode: 'all',
value: metadata.getDefaultDisplayValue()?.key,
value: metadata.getDefaultDisplayValue(),
stroke: "",
fill: "",
color: "",

View File

@@ -1,128 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import CreateAction from './CreateAction';
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
import { debounce } from 'lodash';
let parentObject;
let parentObjectPath;
let unObserve;
describe("The create action plugin", () => {
let openmct;
const TYPES = [
'clock',
'conditionWidget',
'conditionWidget',
'example.imagery',
'example.state-generator',
'flexible-layout',
'folder',
'generator',
'hyperlink',
'LadTable',
'LadTableSet',
'layout',
'mmgis',
'notebook',
'plan',
'table',
'tabs',
'telemetry-mean',
'telemetry.plot.bar-graph',
'telemetry.plot.overlay',
'telemetry.plot.stacked',
'time-strip',
'timer',
'webpage'
];
beforeEach((done) => {
openmct = createOpenMct();
openmct.on('start', done);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
describe('creates new objects for a', () => {
beforeEach(() => {
parentObject = {
name: 'mock folder',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
composition: []
};
parentObjectPath = [parentObject];
spyOn(openmct.objects, 'save');
openmct.objects.save.and.callThrough();
spyOn(openmct.forms, 'showForm');
openmct.forms.showForm.and.callFake(formStructure => {
return Promise.resolve({
name: 'test',
notes: 'test notes',
location: parentObjectPath
});
});
});
afterEach(() => {
parentObject = null;
unObserve();
});
TYPES.forEach(type => {
it(`type ${type}`, (done) => {
function callback(newObject) {
const composition = newObject.composition;
openmct.objects.get(composition[0])
.then(object => {
expect(object.type).toEqual(type);
expect(object.location).toEqual(openmct.objects.makeKeyString(parentObject.identifier));
done();
});
}
const deBouncedCallback = debounce(callback, 300);
unObserve = openmct.objects.observe(parentObject, '*', deBouncedCallback);
const createAction = new CreateAction(openmct, type, parentObject);
createAction.invoke();
});
});
});
});

View File

@@ -45,7 +45,7 @@ export default class EditPropertiesAction extends PropertiesAction {
}
invoke(objectPath) {
return this._showEditForm(objectPath);
this._showEditForm(objectPath);
}
/**
@@ -86,7 +86,7 @@ export default class EditPropertiesAction extends PropertiesAction {
const formStructure = createWizard.getFormStructure(false);
formStructure.title = 'Edit ' + this.domainObject.name;
return this.openmct.forms.showForm(formStructure)
this.openmct.forms.showForm(formStructure)
.then(this._onSave.bind(this));
}
}

View File

@@ -1,222 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createMouseEvent,
createOpenMct,
resetApplicationState
} from 'utils/testing';
import { debounce } from 'lodash';
describe('EditPropertiesAction plugin', () => {
let editPropertiesAction;
let openmct;
let element;
beforeEach((done) => {
element = document.createElement('div');
element.style.display = 'block';
element.style.width = '1920px';
element.style.height = '1080px';
openmct = createOpenMct();
openmct.on('start', done);
openmct.startHeadless(element);
editPropertiesAction = openmct.actions.getAction('properties');
});
afterEach(() => {
editPropertiesAction = null;
return resetApplicationState(openmct);
});
it('editPropertiesAction exists', () => {
expect(editPropertiesAction.key).toEqual('properties');
});
it('edit properties action applies to only persistable objects', () => {
spyOn(openmct.objects, 'isPersistable').and.returnValue(true);
const domainObject = {
name: 'mock folder',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
composition: []
};
const isApplicableTo = editPropertiesAction.appliesTo([domainObject]);
expect(isApplicableTo).toBe(true);
});
it('edit properties action does not apply to non persistable objects', () => {
spyOn(openmct.objects, 'isPersistable').and.returnValue(false);
const domainObject = {
name: 'mock folder',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
composition: []
};
const isApplicableTo = editPropertiesAction.appliesTo([domainObject]);
expect(isApplicableTo).toBe(false);
});
it('edit properties action when invoked shows form', (done) => {
const domainObject = {
name: 'mock folder',
notes: 'mock notes',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
modified: 1643065068597,
persisted: 1643065068600,
composition: []
};
const deBouncedFormChange = debounce(handleFormPropertyChange, 500);
openmct.forms.on('onFormPropertyChange', deBouncedFormChange);
function handleFormPropertyChange(data) {
const form = document.querySelector('.js-form');
const title = form.querySelector('input');
expect(title.value).toEqual(domainObject.name);
const notes = form.querySelector('textArea');
expect(notes.value).toEqual(domainObject.notes);
const buttons = form.querySelectorAll('button');
expect(buttons[0].textContent.trim()).toEqual('OK');
expect(buttons[1].textContent.trim()).toEqual('Cancel');
const clickEvent = createMouseEvent('click');
buttons[1].dispatchEvent(clickEvent);
openmct.forms.off('onFormPropertyChange', deBouncedFormChange);
}
editPropertiesAction.invoke([domainObject])
.catch(() => {
done();
});
});
it('edit properties action saves changes', (done) => {
const oldName = 'mock folder';
const newName = 'renamed mock folder';
const domainObject = {
name: oldName,
notes: 'mock notes',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
modified: 1643065068597,
persisted: 1643065068600,
composition: []
};
let unObserve;
function callback(newObject) {
expect(newObject.name).not.toEqual(oldName);
expect(newObject.name).toEqual(newName);
unObserve();
done();
}
const deBouncedCallback = debounce(callback, 300);
unObserve = openmct.objects.observe(domainObject, '*', deBouncedCallback);
let changed = false;
const deBouncedFormChange = debounce(handleFormPropertyChange, 500);
openmct.forms.on('onFormPropertyChange', deBouncedFormChange);
function handleFormPropertyChange(data) {
const form = document.querySelector('.js-form');
const title = form.querySelector('input');
const notes = form.querySelector('textArea');
const buttons = form.querySelectorAll('button');
expect(buttons[0].textContent.trim()).toEqual('OK');
expect(buttons[1].textContent.trim()).toEqual('Cancel');
if (!changed) {
expect(title.value).toEqual(domainObject.name);
expect(notes.value).toEqual(domainObject.notes);
// change input field value and dispatch event for it
title.focus();
title.value = newName;
title.dispatchEvent(new Event('input'));
title.blur();
changed = true;
} else {
// click ok to save form changes
const clickEvent = createMouseEvent('click');
buttons[0].dispatchEvent(clickEvent);
openmct.forms.off('onFormPropertyChange', deBouncedFormChange);
}
}
editPropertiesAction.invoke([domainObject]);
});
it('edit properties action discards changes', (done) => {
const name = 'mock folder';
const domainObject = {
name,
notes: 'mock notes',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
modified: 1643065068597,
persisted: 1643065068600,
composition: []
};
editPropertiesAction.invoke([domainObject])
.catch(() => {
expect(domainObject.name).toEqual(name);
done();
});
const form = document.querySelector('.js-form');
const buttons = form.querySelectorAll('button');
const clickEvent = createMouseEvent('click');
buttons[1].dispatchEvent(clickEvent);
});
});

View File

@@ -55,7 +55,7 @@
<div
v-if="zoomFactor > 1"
class="c-imagery__hints"
>{{ formatImageAltText }}</div>
>Alt-drag to pan</div>
<div
ref="focusedImageWrapper"
class="image-wrapper"
@@ -382,6 +382,9 @@ export default {
formattedDuration() {
let result = 'N/A';
let negativeAge = -1;
if (!Number.isInteger(this.numericDuration)) {
return result;
}
if (this.numericDuration > TWENTYFOUR_HOURS) {
negativeAge *= (this.numericDuration / TWENTYFOUR_HOURS);
@@ -488,16 +491,6 @@ 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: {
@@ -691,7 +684,7 @@ export default {
mostRecent = await this.relatedTelemetry[key].requestLatestFor(targetDatum);
return mostRecent[valueKey];
return mostRecent ? mostRecent[valueKey] : undefined;
},
// will subscribe to data for this key if not already done
subscribeToDataForKey(key) {
@@ -728,7 +721,7 @@ export default {
let valuesOnTelemetry = this.relatedTelemetry[key].hasTelemetryOnDatum;
let value = await this.getMostRecentRelatedTelemetry(key, this.focusedImage);
if (!valuesOnTelemetry) {
if (!valuesOnTelemetry && this.imageHistory[this.focusedImageIndex]) {
this.$set(this.imageHistory[this.focusedImageIndex], key, value); // manually add to telemetry
}
@@ -844,8 +837,10 @@ export default {
let currentTime = this.timeContext.clock() && this.timeContext.clock().currentValue();
if (currentTime === undefined) {
this.numericDuration = currentTime;
} else {
} else if (Number.isInteger(this.parsedSelectedTime)) {
this.numericDuration = currentTime - this.parsedSelectedTime;
} else {
this.numericDuration = undefined;
}
},
resetAgeCSS() {
@@ -887,6 +882,10 @@ export default {
this.imageTranslateY = 0;
},
handlePanZoomUpdate({ newScaleFactor, screenClientX, screenClientY }) {
if (!this.isPaused) {
this.paused(true);
}
if (!(screenClientX || screenClientY)) {
return this.updatePanZoom(newScaleFactor, 0, 0);
}
@@ -1036,6 +1035,9 @@ export default {
},
wheelZoom(e) {
e.preventDefault();
if (!this.isPaused) {
this.paused(true);
}
this.$refs.imageControls.wheelZoom(e);
},

View File

@@ -46,6 +46,7 @@ export default {
// kickoff
this.subscribe();
this.requestHistory();
},
beforeDestroy() {
if (this.unsubscribe) {
@@ -168,6 +169,8 @@ export default {
// splice array to encourage garbage collection
this.imageHistory.splice(0, this.imageHistory.length);
// requesting history effectively clears imageHistory array
return this.requestHistory();
},
timeSystemChange() {
this.timeSystem = this.timeContext.timeSystem();

View File

@@ -91,7 +91,6 @@ describe("The Move Action plugin", () => {
});
describe("when moving an object to a new parent and removing from the old parent", () => {
let unObserve;
beforeEach((done) => {
openmct.router.path = [];
@@ -105,7 +104,7 @@ describe("The Move Action plugin", () => {
});
});
unObserve = openmct.objects.observe(parentObject, '*', (newObject) => {
openmct.objects.observe(parentObject, '*', (newObject) => {
done();
});
@@ -114,10 +113,6 @@ describe("The Move Action plugin", () => {
moveAction.invoke([childObject, parentObject]);
});
afterEach(() => {
unObserve();
});
it("the child object's identifier should be in the new parent's composition", () => {
let newParentChild = anotherParentObject.composition[0];
expect(newParentChild).toEqual(childObject.identifier);

View File

@@ -28,16 +28,12 @@
@drop.prevent="dropOnEntry"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time-and-creator">
<span class="c-ne__created-date">{{ createdOnDate }}</span>
<span class="c-ne__created-time">{{ createdOnTime }}</span>
<span
v-if="entry.createdBy"
class="c-ne__creator"
>
<span class="icon-person"></span> {{ entry.createdBy }}
</span>
<div class="c-ne__time">
<template v-if="entry.createdBy">
<span class="c-icon icon-person">{{ entry.createdBy }}</span>
</template>
<span>{{ createdOnDate }}</span>
<span>{{ createdOnTime }}</span>
</div>
<div class="c-ne__content">
<template v-if="readOnly && result">

View File

@@ -145,7 +145,7 @@ define([
item.size = element.size || DEFAULT_SIZE;
item.identifier = telemetryObjects[element.id].identifier;
item.displayMode = element.titled ? 'all' : 'value';
item.value = openmct.telemetry.getMetadata(telemetryObjects[element.id]).getDefaultDisplayValue()?.key;
item.value = openmct.telemetry.getMetadata(telemetryObjects[element.id]).getDefaultDisplayValue();
} else if (element.type === 'fixed.box') {
item.type = "box-view";
item.stroke = element.stroke || DEFAULT_STROKE;

View File

@@ -1,102 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import raf from '@/utils/raf';
export default class AbstractStatusIndicator {
#popupComponent;
#indicator;
#configuration;
constructor(openmct, configuration) {
this.openmct = openmct;
this.#configuration = configuration;
this.showPopup = this.showPopup.bind(this);
this.clearPopup = this.clearPopup.bind(this);
this.positionBox = this.positionBox.bind(this);
this.positionBox = raf(this.positionBox);
this.#indicator = this.createIndicator();
this.#popupComponent = this.createPopupComponent();
}
install() {
this.openmct.indicators.add(this.#indicator);
}
showPopup() {
const popupElement = this.getPopupElement();
document.body.appendChild(popupElement.$el);
//Use capture so we don't trigger immediately on the same iteration of the event loop
document.addEventListener('click', this.clearPopup, {
capture: true
});
this.positionBox();
window.addEventListener('resize', this.positionBox);
}
positionBox() {
const popupElement = this.getPopupElement();
const indicator = this.getIndicator();
let indicatorBox = indicator.element.getBoundingClientRect();
popupElement.positionX = indicatorBox.left;
popupElement.positionY = indicatorBox.bottom;
const popupRight = popupElement.positionX + popupElement.$el.clientWidth;
const offsetLeft = Math.min(window.innerWidth - popupRight, 0);
popupElement.positionX = popupElement.positionX + offsetLeft;
}
clearPopup(clickAwayEvent) {
const popupElement = this.getPopupElement();
if (!popupElement.$el.contains(clickAwayEvent.target)) {
popupElement.$el.remove();
document.removeEventListener('click', this.clearPopup);
window.removeEventListener('resize', this.positionBox);
}
}
createPopupComponent() {
throw new Error('Must override createPopupElement method');
}
getPopupElement() {
return this.#popupComponent;
}
createIndicator() {
throw new Error('Must override createIndicator method');
}
getIndicator() {
return this.#indicator;
}
getConfiguration() {
return this.#configuration;
}
}

View File

@@ -1,142 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
$statusCountWidth: 30px;
.c-status-poll-panel {
@include menuOuter();
display: flex;
flex-direction: column;
padding: $interiorMarginLg;
min-width: 350px;
max-width: 35%;
> * + * {
margin-top: $interiorMarginLg;
}
*:before {
font-size: 0.8em;
margin-right: $interiorMarginSm;
}
&__section {
display: flex;
align-items: center;
flex-direction: row;
> * + * {
margin-left: $interiorMarginLg;
}
}
&__top {
text-transform: uppercase;
}
&__user-role,
&__updated {
opacity: 50%;
}
&__updated {
flex: 1 1 auto;
text-align: right;
}
&__poll-question {
background: $colorBodyFg;
color: $colorBodyBg;
border-radius: $controlCr;
font-weight: bold;
padding: $interiorMarginSm $interiorMargin;
.c-status-poll-panel--admin & {
background: rgba($colorBodyFg, 0.1);
color: $colorBodyFg;
}
}
/****** Admin interface */
&__content {
$m: $interiorMargin;
display: grid;
grid-template-columns: min-content 1fr;
grid-column-gap: $m;
grid-row-gap: $m;
[class*='__label'] {
padding: 3px 0;
}
[class*='new-question'] {
align-items: center;
display: flex;
flex-direction: row;
> * + * { margin-left: $interiorMargin; }
input {
flex: 1 1 auto;
height: $btnStdH;
}
button { flex: 0 0 auto; }
}
}
}
.c-status-poll-report {
display: flex;
flex-direction: row;
> * + * { margin-left: $interiorMargin; }
&__count {
background: rgba($colorBodyFg, 0.2);
border-radius: $controlCr;
display: flex;
flex-direction: row;
font-size: 1.25em;
align-items: center;
padding: $interiorMarginSm $interiorMarginLg;
&-type {
line-height: 1em;
opacity: 0.6;
}
}
}
.c-indicator {
&:before {
// Indicator icon
color: $colorKey;
}
&--operator-status {
cursor: pointer;
max-width: 150px;
@include hover() {
background: $colorIndicatorBgHov;
}
}
}

View File

@@ -1,187 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2022, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div
:style="position"
class="c-status-poll-panel c-status-poll-panel--operator"
@click.stop="noop"
>
<div class="c-status-poll-panel__section c-status-poll-panel__top">
<div
class="c-status-poll-panel__title"
>Status Poll</div>
<div class="c-status-poll-panel__user-role icon-person">{{ role }}</div>
<div class="c-status-poll-panel__updated">{{ pollQuestionUpdated }}</div>
</div>
<div class="c-status-poll-panel__section c-status-poll-panel__poll-question">
{{ currentPollQuestion }}
</div>
<div class="c-status-poll-panel__section c-status-poll-panel__bottom">
<div class="c-status-poll-panel__set-status-label">My status:</div>
<select
v-model="selectedStatus"
name="setStatus"
@change="changeStatus"
>
<option
v-for="status in allStatuses"
:key="status.key"
:value="status.key"
>
{{ status.label }}
</option>
</select>
</div>
</div>
</template>
<script>
export default {
inject: ['openmct', 'indicator', 'configuration'],
props: {
positionX: {
type: Number,
required: true
},
positionY: {
type: Number,
required: true
}
},
data() {
return {
allRoles: [],
role: '--',
pollQuestionUpdated: '--',
currentPollQuestion: '--',
selectedStatus: undefined,
allStatuses: []
};
},
computed: {
position() {
return {
position: 'absolute',
left: `${this.positionX}px`,
top: `${this.positionY}px`
};
}
},
beforeDestroy() {
this.openmct.user.status.off('statusChange', this.setStatus);
this.openmct.user.status.off('pollQuestionChange', this.setPollQuestion);
},
async mounted() {
this.unsubscribe = [];
await this.fetchUser();
await this.findFirstApplicableRole();
this.fetchPossibleStatusesForUser();
this.fetchCurrentPoll();
this.fetchMyStatus();
this.subscribeToMyStatus();
this.subscribeToPollQuestion();
},
methods: {
async findFirstApplicableRole() {
this.role = await this.openmct.user.status.getStatusRoleForCurrentUser();
},
async fetchUser() {
this.user = await this.openmct.user.getCurrentUser();
},
async fetchCurrentPoll() {
const pollQuestion = await this.openmct.user.status.getPollQuestion();
if (pollQuestion !== undefined) {
this.setPollQuestion(pollQuestion);
}
},
async fetchPossibleStatusesForUser() {
this.allStatuses = await this.openmct.user.status.getPossibleStatuses();
},
setPollQuestion(pollQuestion) {
this.currentPollQuestion = pollQuestion.question;
this.pollQuestionUpdated = new Date(pollQuestion.timestamp).toISOString();
this.indicator.text(pollQuestion?.question || '');
},
async fetchMyStatus() {
const activeStatusRole = await this.openmct.user.status.getStatusRoleForCurrentUser();
const status = await this.openmct.user.status.getStatusForRole(activeStatusRole);
if (status !== undefined) {
this.setStatus({status});
}
},
subscribeToMyStatus() {
this.openmct.user.status.on('statusChange', this.setStatus);
},
subscribeToPollQuestion() {
this.openmct.user.status.on('pollQuestionChange', this.setPollQuestion);
},
setStatus({role, status}) {
status = this.applyStyling(status);
this.selectedStatus = status.key;
this.indicator.iconClass(status.iconClassPoll);
this.indicator.statusClass(status.statusClass);
if (this.isDefaultStatus(status)) {
this.indicator.text(this.currentPollQuestion);
} else {
this.indicator.text(status.label);
}
},
isDefaultStatus(status) {
return status.key === this.allStatuses[0].key;
},
findStatusByKey(statusKey) {
return this.allStatuses.find(possibleMatch => possibleMatch.key === statusKey);
},
async changeStatus() {
if (this.selectedStatus !== undefined) {
const statusObject = this.findStatusByKey(this.selectedStatus);
const result = await this.openmct.user.status.setStatusForRole(this.role, statusObject);
if (result === true) {
this.openmct.notifications.info("Successfully set operator status");
} else {
this.openmct.notifications.error("Unable to set operator status");
}
}
},
applyStyling(status) {
const stylesForStatus = this.configuration?.statusStyles?.[status.label];
if (stylesForStatus !== undefined) {
return {
...status,
...stylesForStatus
};
} else {
return status;
}
},
noop() {}
}
};
</script>

View File

@@ -1,63 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Vue from 'vue';
import AbstractStatusIndicator from '../AbstractStatusIndicator';
import OperatorStatusComponent from './OperatorStatus.vue';
export default class OperatorStatusIndicator extends AbstractStatusIndicator {
createPopupComponent() {
const indicator = this.getIndicator();
const popupElement = new Vue({
components: {
OperatorStatus: OperatorStatusComponent
},
provide: {
openmct: this.openmct,
indicator: indicator,
configuration: this.getConfiguration()
},
data() {
return {
positionX: 0,
positionY: 0
};
},
template: '<operator-status :positionX="positionX" :positionY="positionY" />'
}).$mount();
return popupElement;
}
createIndicator() {
const operatorIndicator = this.openmct.indicators.simpleIndicator();
operatorIndicator.text("My Operator Status");
operatorIndicator.description("Set my operator status");
operatorIndicator.iconClass('icon-status-poll-question-mark');
operatorIndicator.element.classList.add("c-indicator--operator-status");
operatorIndicator.element.classList.add("no-minify");
operatorIndicator.on('click', this.showPopup);
return operatorIndicator;
}
}

View File

@@ -1,46 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import OperatorStatusIndicator from './operatorStatus/OperatorStatusIndicator';
import PollQuestionIndicator from './pollQuestion/PollQuestionIndicator';
export default function operatorStatusPlugin(configuration) {
return function install(openmct) {
if (openmct.user.hasProvider()) {
openmct.user.status.canProvideStatusForCurrentUser().then(canProvideStatus => {
if (canProvideStatus) {
const operatorStatusIndicator = new OperatorStatusIndicator(openmct, configuration);
operatorStatusIndicator.install();
}
});
openmct.user.status.canSetPollQuestion().then(canSetPollQuestion => {
if (canSetPollQuestion) {
const pollQuestionIndicator = new PollQuestionIndicator(openmct, configuration);
pollQuestionIndicator.install();
}
});
}
};
}

View File

@@ -1,184 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2022, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div
:style="position"
class="c-status-poll-panel c-status-poll-panel--admin"
@click.stop="noop"
>
<div class="c-status-poll-panel__section c-status-poll-panel__top">
<div
class="c-status-poll-panel__title"
>Manage Status Poll</div>
<div class="c-status-poll-panel__updated">Last updated: {{ pollQuestionUpdated }}</div>
</div>
<div class="c-status-poll__section c-status-poll-panel__content c-spq">
<!-- Grid layout -->
<div class="c-spq__label">Current:</div>
<div class="c-spq__value c-status-poll-panel__poll-question">{{ currentPollQuestion }}</div>
<template v-if="statusCountViewModel.length > 0">
<div class="c-spq__label">Reporting:</div>
<div class="c-spq__value c-status-poll-panel__poll-reporting c-status-poll-report">
<div
v-for="entry in statusCountViewModel"
:key="entry.status.key"
class="c-status-poll-report__count"
:style="[{
background: entry.status.statusBgColor,
color: entry.status.statusFgColor
}]"
>
<div
class="c-status-poll-report__count-type"
:class="entry.status.iconClass"
></div>
<div class="c-status-poll-report__count-value">
{{ entry.roleCount }}
</div>
</div>
</div>
</template>
<div class="c-spq__label">New poll:</div>
<div class="c-spq__value c-status-poll-panel__poll-new-question">
<input
v-model="newPollQuestion"
type="text"
name="newPollQuestion"
>
<button
class="c-button"
@click="updatePollQuestion"
>Update</button>
</div>
</div>
</div>
</template>
<script>
export default {
inject: ['openmct', 'indicator', 'configuration'],
props: {
positionX: {
type: Number,
required: true
},
positionY: {
type: Number,
required: true
}
},
data() {
return {
pollQuestionUpdated: '--',
currentPollQuestion: '--',
newPollQuestion: undefined,
statusCountViewModel: []
};
},
computed: {
position() {
return {
position: 'absolute',
left: `${this.positionX}px`,
top: `${this.positionY}px`
};
}
},
mounted() {
this.fetchCurrentPoll();
this.subscribeToPollQuestion();
this.fetchStatusSummary();
this.openmct.user.status.on('statusChange', this.fetchStatusSummary);
},
beforeDestroy() {
this.openmct.user.status.off('statusChange', this.fetchStatusSummary);
this.openmct.user.status.off('pollQuestionChange', this.setPollQuestion);
},
methods: {
async fetchCurrentPoll() {
const pollQuestion = await this.openmct.user.status.getPollQuestion();
if (pollQuestion !== undefined) {
this.setPollQuestion(pollQuestion);
}
},
subscribeToPollQuestion() {
this.openmct.user.status.on('pollQuestionChange', this.setPollQuestion);
},
setPollQuestion(pollQuestion) {
this.currentPollQuestion = pollQuestion.question;
this.pollQuestionUpdated = new Date(pollQuestion.timestamp).toISOString();
this.indicator.text(pollQuestion.question);
},
async updatePollQuestion() {
const result = await this.openmct.user.status.setPollQuestion(this.newPollQuestion);
if (result === true) {
this.openmct.notifications.info("Successfully set new poll question");
} else {
this.openmct.notifications.error("Unable to set new poll question.");
}
this.newPollQuestion = undefined;
},
async fetchStatusSummary() {
const allStatuses = await this.openmct.user.status.getPossibleStatuses();
const statusCountMap = allStatuses.reduce((statusToCountMap, status) => {
statusToCountMap[status.key] = 0;
return statusToCountMap;
}, {});
const allStatusRoles = await this.openmct.user.status.getAllStatusRoles();
const statusesForRoles = await Promise.all(allStatusRoles.map(role => this.openmct.user.status.getStatusForRole(role)));
statusesForRoles.forEach((status, i) => {
const currentCount = statusCountMap[status.key];
statusCountMap[status.key] = currentCount + 1;
});
this.statusCountViewModel = allStatuses.map((status) => {
return {
status: this.applyStyling(status),
roleCount: statusCountMap[status.key]
};
});
},
applyStyling(status) {
const stylesForStatus = this.configuration?.statusStyles?.[status.label];
if (stylesForStatus !== undefined) {
return {
...status,
...stylesForStatus
};
} else {
return status;
}
},
noop() {}
}
};
</script>

View File

@@ -1,63 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Vue from 'vue';
import AbstractStatusIndicator from '../AbstractStatusIndicator';
import PollQuestionComponent from './PollQuestion.vue';
export default class PollQuestionIndicator extends AbstractStatusIndicator {
createPopupComponent() {
const indicator = this.getIndicator();
const pollQuestionElement = new Vue({
components: {
PollQuestion: PollQuestionComponent
},
provide: {
openmct: this.openmct,
indicator: indicator,
configuration: this.getConfiguration()
},
data() {
return {
positionX: 0,
positionY: 0
};
},
template: '<poll-question :positionX="positionX" :positionY="positionY" />'
}).$mount();
return pollQuestionElement;
}
createIndicator() {
const pollQuestionIndicator = this.openmct.indicators.simpleIndicator();
pollQuestionIndicator.text("Poll Question");
pollQuestionIndicator.description("Set the current poll question");
pollQuestionIndicator.iconClass('icon-status-poll-edit');
pollQuestionIndicator.element.classList.add("c-indicator--operator-status");
pollQuestionIndicator.element.classList.add("no-minify");
pollQuestionIndicator.on('click', this.showPopup);
return pollQuestionIndicator;
}
}

View File

@@ -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 `~/homebrew/etc/local.ini` and add: `
6. Enable cors in CouchDB by editing `/usr/local/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! 🏆

View File

@@ -49,7 +49,7 @@
import * as d3Scale from 'd3-scale';
import TimelineAxis from "../../ui/components/TimeSystemAxis.vue";
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
import { getValidatedData } from "./util";
import { getValidatedPlan } from "./util";
import Vue from "vue";
const PADDING = 1;
@@ -161,7 +161,7 @@ export default {
return clientWidth - 200;
},
getPlanData(domainObject) {
this.planData = getValidatedData(domainObject);
this.planData = getValidatedPlan(domainObject);
},
updateViewBounds(bounds) {
if (bounds) {

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
export function getValidatedData(domainObject) {
export function getValidatedPlan(domainObject) {
let sourceMap = domainObject.sourceMap;
let body = domainObject.selectFile.body;
let json = {};

View File

@@ -37,8 +37,7 @@ define([
'./URLIndicatorPlugin/URLIndicatorPlugin',
'./telemetryMean/plugin',
'./plot/plugin',
'./charts/bar/plugin',
'./charts/scatter/plugin',
'./charts/plugin',
'./telemetryTable/plugin',
'./staticRootPlugin/plugin',
'./notebook/plugin',
@@ -78,7 +77,6 @@ define([
'./userIndicator/plugin',
'../../example/exampleUser/plugin',
'./localStorage/plugin',
'./operatorStatus/plugin',
'./gauge/GaugePlugin',
'./timelist/plugin'
], function (
@@ -98,8 +96,7 @@ define([
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
BarChartPlugin,
ScatterPlotPlugin,
ChartPlugin,
TelemetryTablePlugin,
StaticRootPlugin,
Notebook,
@@ -139,7 +136,6 @@ define([
UserIndicator,
ExampleUser,
LocalStorage,
OperatorStatus,
GaugePlugin,
TimeList
) {
@@ -176,8 +172,7 @@ define([
plugins.ImageryPlugin = ImageryPlugin;
plugins.Plot = PlotPlugin.default;
plugins.BarChart = BarChartPlugin.default;
plugins.ScatterPlot = ScatterPlotPlugin.default;
plugins.Chart = ChartPlugin.default;
plugins.TelemetryTable = TelemetryTablePlugin;
plugins.SummaryWidget = SummaryWidget;
@@ -219,7 +214,6 @@ define([
plugins.DeviceClassifier = DeviceClassifier.default;
plugins.UserIndicator = UserIndicator.default;
plugins.LocalStorage = LocalStorage.default;
plugins.OperatorStatus = OperatorStatus.default;
plugins.Gauge = GaugePlugin.default;
plugins.Timelist = TimeList.default;

View File

@@ -61,7 +61,7 @@
import TimelineObjectView from './TimelineObjectView.vue';
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
import { getValidatedData } from "../plan/util";
import { getValidatedPlan } from "../plan/util";
const unknownObjectType = {
definition: {
@@ -110,7 +110,7 @@ export default {
let objectPath = [domainObject].concat(this.objectPath.slice());
let rowCount = 0;
if (domainObject.type === 'plan') {
rowCount = Object.keys(getValidatedData(domainObject)).length;
rowCount = Object.keys(getValidatedPlan(domainObject)).length;
}
let height = domainObject.type === 'telemetry.plot.stacked' ? `${domainObject.composition.length * 100}px` : '100px';

View File

@@ -35,7 +35,7 @@
</template>
<script>
import {getValidatedData} from "../plan/util";
import {getValidatedPlan} from "../plan/util";
import ListView from '../../ui/components/List/ListView.vue';
import {getPreciseDuration} from "../../utils/duration";
import ticker from 'utils/clock/Ticker';
@@ -164,7 +164,7 @@ export default {
}
},
getPlanData(domainObject) {
this.planData = getValidatedData(domainObject);
this.planData = getValidatedPlan(domainObject);
},
setViewBounds() {
const pastEventsIndex = this.domainObject.configuration.pastEventsIndex;

View File

@@ -156,13 +156,6 @@ $glyph-icon-notebook-page: '\e92c';
$glyph-icon-unlocked: '\e92d';
$glyph-icon-circle: '\e92e';
$glyph-icon-draft: '\e92f';
$glyph-icon-circle-slash: '\e930';
$glyph-icon-question-mark: '\e931';
$glyph-icon-status-poll-check: '\e932';
$glyph-icon-status-poll-caution: '\e933';
$glyph-icon-status-poll-circle-slash: '\e934';
$glyph-icon-status-poll-question-mark: '\e935';
$glyph-icon-status-poll-edit: '\e936';
$glyph-icon-arrows-right-left: '\ea00';
$glyph-icon-arrows-up-down: '\ea01';
$glyph-icon-bullet: '\ea02';
@@ -271,8 +264,6 @@ $glyph-icon-bar-chart: '\eb2c';
$glyph-icon-map: '\eb2d';
$glyph-icon-plan: '\eb2e';
$glyph-icon-timelist: '\eb2f';
$glyph-icon-notebook-shift-log: '\eb31';
$glyph-icon-plot-scatter: '\eb30';
/************************** GLYPHS AS DATA URI */
// Only objects have been converted, for use in Create menu and folder views
@@ -280,41 +271,44 @@ $bg-icon-alert-rect: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://
$bg-icon-alert-triangle: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M499.1 424.4L287.8 54.6c-17.5-30.6-46-30.6-63.5 0L12.9 424.4C-4.6 455 9.9 480 45.1 480h421.7c35.3 0 49.8-25 32.3-55.6zM288 448h-64v-64h64v64zm10.9-192L280 352h-48l-18.9-96V128H299v128z' fill='%23000000'/%3e%3c/svg%3e");
$bg-icon-bell: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg fill='%23000000'%3e%3cpath d='M256 512c53 0 96-43 96-96H160c0 53 43 96 96 96zM448 224v-32C448 86 362 0 256 0S64 86 64 192v32c0 35.3-28.7 64-64 64v64h512v-64c-35.3 0-64-28.7-64-64z'/%3e%3c/g%3e%3c/svg%3e");
$bg-icon-info: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm0 64c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zm96 352H160v-64h32V224h128v128h32v64z' fill='%23000000'/%3e%3c/svg%3e");
$bg-icon-activity: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M288 32H160l160 160H174.872C152.74 153.742 111.377 128 64 128H0v256h64c47.377 0 88.74-25.742 110.872-64H320L160 480h128l224-224L288 32z'/%3e%3c/svg%3e");
$bg-icon-activity-mode: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 0C148.6 0 56.6 66.2 18.6 160H64c28.4 0 54 12.4 71.5 32H256l-96-96h128l160 160-160 160H160l96-96H135.5C118 339.6 92.4 352 64 352H18.6c38 93.8 129.9 160 237.4 160 141.4 0 256-114.6 256-256S397.4 0 256 0z'/%3e%3c/svg%3e");
$bg-icon-autoflow-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h32V0H96zM192 0h128v512H192zM416 0h-32v352h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e");
$bg-icon-plus: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M480,192H320V32A32.1,32.1,0,0,0,288,0H224a32.1,32.1,0,0,0-32,32V192H32A32.1,32.1,0,0,0,0,224v64a32.1,32.1,0,0,0,32,32H192V480a32.1,32.1,0,0,0,32,32h64a32.1,32.1,0,0,0,32-32V320H480a32.1,32.1,0,0,0,32-32V224A32.1,32.1,0,0,0,480,192Z' transform='translate(0)'/%3e%3c/svg%3e");
$bg-icon-grippy-ew: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M416 0v512h-64V0zM288 0v512h-64V0zM160 0v512H96V0z'/%3e%3c/svg%3e");
$bg-icon-chain-links: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M479.2 32.8C457.3 10.9 428.7 0 400 0c-28.7 0-57.3 10.9-79.2 32.8l-64 64c-37 37-42.7 93.5-17 136.5l-6.4 6.4C215.7 229.3 195.9 224 176 224c-28.7 0-57.3 10.9-79.2 32.8l-64 64c-43.7 43.7-43.7 114.7 0 158.4C54.7 501.1 83.3 512 112 512c28.7 0 57.3-10.9 79.2-32.8l64-64c37-37 42.7-93.5 17-136.5l6.4-6.4c17.6 10.5 37.5 15.8 57.3 15.8 28.7 0 57.3-10.9 79.2-32.8l64-64c43.8-43.8 43.8-114.8.1-158.5zM209.9 369.9l-64 64c-9 9.1-21.1 14.1-33.9 14.1-12.8 0-24.9-5-33.9-14.1-18.7-18.7-18.7-49.2 0-67.9l64-64c9.1-9.1 21.1-14.1 33.9-14.1 2.8 0 5.6.3 8.4.7l-27.8 27.8c-5.2 5.2-8.1 12.1-8.1 19.4s2.9 14.3 8.1 19.4c5.2 5.2 12.1 8.1 19.4 8.1s14.3-2.9 19.4-8.1l27.8-27.8c2.7 15.2-1.8 31.1-13.3 42.5zm224-224l-64 64c-9 9.1-21.1 14.1-33.9 14.1-2.8 0-5.6-.3-8.4-.7l27.8-27.8c5.2-5.2 8.1-12.1 8.1-19.4s-2.9-14.3-8.1-19.4c-5.2-5.2-12.1-8.1-19.4-8.1s-14.3 2.9-19.4 8.1l-27.8 27.8c-2.6-14.9 1.8-30.8 13.3-42.3l64-64C375.1 69 387.2 64 400 64s24.9 5 33.9 14.1c18.8 18.7 18.8 49.1 0 67.8z'/%3e%3c/svg%3e");
$bg-icon-clock: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M288 32H160l160 160H174.872C152.74 153.742 111.377 128 64 128H0v256h64c47.377 0 88.74-25.742 110.872-64H320L160 480h128l224-224L288 32z'/%3e%3c/svg%3e");
$bg-icon-database: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 0C148.6 0 56.6 66.2 18.6 160H64c28.4 0 54 12.4 71.5 32H256l-96-96h128l160 160-160 160H160l96-96H135.5C118 339.6 92.4 352 64 352H18.6c38 93.8 129.9 160 237.4 160 141.4 0 256-114.6 256-256S397.4 0 256 0z'/%3e%3c/svg%3e");
$bg-icon-database-query: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h32V0H96zM192 0h128v512H192zM416 0h-32v352h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e");
$bg-icon-dataset: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm135 345c-6.4 11.1-18.3 18-31.2 18-6.3 0-12.5-1.7-18-4.8l-110.9-64-.1-.1c-.4-.2-.8-.5-1.2-.7l-.4-.3-.9-.6-.6-.5-.6-.5-.9-.7-.3-.3c-.4-.3-.7-.6-1.1-.9-2.5-2.3-4.7-5-6.5-7.9-.1-.2-.3-.5-.4-.7s-.3-.5-.4-.7c-1.6-3-2.9-6.2-3.6-9.6v-.1c-.1-.5-.2-.9-.3-1.4 0-.1 0-.3-.1-.4-.1-.3-.1-.7-.1-1.1s-.1-.5-.1-.8 0-.5-.1-.8-.1-.8-.1-1.1v-.5-1.4V81c0-19.9 16.1-36 36-36s36 16.1 36 36v161.2l92.9 53.6c17.1 10 22.9 32 13 49.2z'/%3e%3c/svg%3e");
$bg-icon-datatable: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 256C114.615 256 0 213.019 0 160v256c0 53.019 114.615 96 256 96s256-42.981 256-96V160c0 53.019-114.615 96-256 96z'/%3e%3cellipse fill='%23000000' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
$bg-icon-dictionary: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M341.76 409.643C316.369 423.871 287.118 432 256 432c-97.047 0-176-78.953-176-176S158.953 80 256 80s176 78.953 176 176c0 31.118-8.129 60.369-22.357 85.76l95.846 95.846C509.747 430.661 512 423.429 512 416V96c0-53.019-114.615-96-256-96S0 42.981 0 96v320c0 53.019 114.615 96 256 96 63.055 0 120.774-8.554 165.388-22.73l-79.628-79.627z'/%3e%3cpath fill='%23000000' d='M176 256c0 44.112 35.888 80 80 80s80-35.888 80-80-35.888-80-80-80-80 35.888-80 80z'/%3e%3c/svg%3e");
$bg-icon-folder: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64zM160 448H96V288h64v160zm128 0h-64V288h64v160zm128 0h-64V288h64v160z'/%3e%3c/svg%3e");
$bg-icon-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 256C114.6 256 0 213 0 160v256c0 53 114.6 96 256 96s256-43 256-96V160c0 53-114.6 96-256 96zm192 31.5v128c-18.3 7.8-39.9 14.4-64 19.7v-128c24.1-5.3 45.7-11.9 64-19.7zm-320 19.7v128c-24.1-5.2-45.7-11.9-64-19.7v-128c18.3 7.8 39.9 14.4 64 19.7zM192 445V317c20.5 2 41.9 3 64 3s43.5-1.1 64-3v128c-20.5 2-41.9 3-64 3s-43.5-1.1-64-3z'/%3e%3cellipse fill='%23000000' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
$bg-icon-layout: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M416 320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96v160l-64-32-64 32V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96v-96c0 52.8-43.2 96-96 96H96v-96h320z'/%3e%3c/svg%3e");
$bg-icon-object: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z'/%3e%3c/svg%3e");
$bg-icon-object-unknown: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zm0 448H64V64h384v384z'/%3e%3cpath fill='%23000000' d='M160 128l-64 64v224h320V256l-64-64-64 64z'/%3e%3c/svg%3e");
$bg-icon-packet: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M224 0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h128V0zM416 0H288v288.832h224V96c0-52.8-43.2-96-96-96zM288 512h128c52.8 0 96-43.2 96-96v-64.832H288V512z'/%3e%3c/svg%3e");
$bg-icon-page: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23000000' d='M256 512l256-160V160L255.99 0 0 160v192l256 160zm0-416l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
$bg-icon-plot-overlay: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M255-1L-1 159v192l256 160 256-160V159L255-1zm37.7 430.6c-10.6 10.4-23 15.4-38 15.4-15.6 0-28.1-4.9-38.1-14.8-10-10-14.8-22.4-14.8-38.1 0-15.2 5.1-27.6 15.5-38.1s22.6-15.6 37.4-15.6c14.8 0 27.1 5.2 37.8 16 10.7 10.8 15.9 23.2 15.9 38-.1 14.5-5.4 27-15.7 37.2zm26.4-156.3c-11.8 5.9-18.7 11-21.7 16.2-1.8 3.1-3 7.4-3.7 13.4v20.5H213v-22.1c0-20.1 2.2-34.9 6.5-44 4-8.6 11.3-15.1 22.4-20l17.4-7.7c16-7.1 24.1-17.6 24.1-31.4 0-8-3-15.2-8.6-20.9-5.6-5.6-12.8-8.6-20.8-8.6-12 0-27.2 5-31.4 28.7l-1.1 6.1H148l.7-8.1c2-22.3 8.5-41.2 19.4-56.1 9.8-13.5 22.8-24.3 38.5-32.3 15.7-8 32.3-12 49.1-12 30.3 0 55.1 9.7 75.7 29.8 20.6 20 30.6 44 30.6 73.6 0 35.4-14.4 60.7-42.9 74.9z'/%3e%3c/svg%3e");
$bg-icon-plot-stacked: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23000000' d='M256 0L0 160v256c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V160L256 0zm0 96l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
$bg-icon-session: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M352 256c-52.8 0-96-43.2-96-96V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V256H352z'/%3e%3cpath fill='%23000000' d='M384 192h128L320 0v128c0 35.2 28.8 64 64 64z'/%3e%3c/svg%3e");
$bg-icon-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M415 0H97C43.65 0 0 43.65 0 97v203.41c7.09 9.32 12.83 14.17 16 15.42 7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73C403.71 188.64 440.64 124 496 124a69.55 69.55 0 0 1 16 1.87V97c0-53.35-43.65-97-97-97z'/%3e%3cpath fill='%23000000' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73C108.29 323.36 71.36 388 16 388a69.56 69.56 0 0 1-16-1.87V415c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97V211.59c-7.09-9.32-12.83-14.17-16-15.42z'/%3e%3c/svg%3e");
$bg-icon-tabular-lad: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M44.8 156c12.49 0 24.48-13.26 42.76-35.09 22.71-27.14 51-60.91 98-60.91 22.32 0 43.31 7.73 62.4 23 14.34 11.45 25.58 25.21 36.46 38.53C303.63 145 314 156 326.4 156H512V97c0-53.35-43.65-97-97-97H97C43.65 0 0 43.65 0 97v59h44.8z'/%3e%3cpath fill='%23000000' d='M264.75 205.2c-14.12-11.32-25.26-25-36-38.14C211 145.32 199.37 132 185.6 132c-12.53 0-24.54 13.27-42.83 35.12-22.7 27.12-51 60.88-98 60.88H0v56h185.6c22 0 42.77 7.67 61.65 22.8 14.12 11.32 25.26 25 36 38.14C301 366.68 312.63 380 326.4 380c12.53 0 24.54-13.27 42.83-35.12 22.7-27.12 51-60.88 98-60.88H512v-56H326.4c-22.03 0-42.77-7.67-61.65-22.8z'/%3e%3cpath fill='%23000000' d='M467.2 356c-12.49 0-24.48 13.26-42.76 35.09-22.71 27.14-51 60.91-98 60.91-22.32 0-43.31-7.73-62.4-23-14.34-11.45-25.58-25.21-36.46-38.53C208.37 367 198 356 185.6 356H0v59c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97v-59h-44.8z'/%3e%3c/svg%3e");
$bg-icon-tabular-lad-set: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M317.8 262.2c3.3 2.1 6.6 4.3 9.6 6.8l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l67.6-54c.1-2.4.1-4.7.1-7.1 0-26.1-3.9-51.2-11.1-74.9L423.5 243c-29.1 23.3-70.1 29.6-105.7 19.2zM124.3 317.1l60.2-48.2c29-23.2 70-29.6 105.6-19.2-3.3-2.1-6.6-4.3-9.6-6.8l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L103.5 243c-20 16-45.7 24-71.5 24-10.8 0-21.5-1.4-31.9-4.2v.8c2.5 1.7 5 3.4 7.3 5.3l60.2 48.2c14.9 11.9 41.9 11.9 56.7 0z'/%3e%3cpath fill='%23000000' d='M60.3 189.1l60.2-48.2c40.1-32.1 102.8-32.1 142.9 0l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l90.5-72.4C425.2 46.5 346 0 256 0 136.7 0 36.4 81.6 8 192.1c15.4 8.8 38.9 7.8 52.3-3zM344.5 371l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L167.5 371c-20 16-45.7 24-71.5 24-23.9 0-47.7-6.9-67.1-20.7C71.7 456.1 157.3 512 256 512s184.3-55.9 227.1-137.7c-40.2 28.7-99.9 27.6-138.6-3.3z'/%3e%3c/svg%3e");
$bg-icon-tabular-realtime: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zM320 224H192v-96h128v96zm-128 32h128v96H192v-96zm-32 96H32v-96h128v96zm0-224v96H32v-96h128zM64 480c-8.5 0-16.5-3.3-22.6-9.4S32 456.5 32 448v-64h128v96H64zm128 0v-96h128v96H192zm288-32c0 8.5-3.3 16.5-9.4 22.6S456.5 480 448 480h-96v-96h128v64zm0-96H352v-96h128v96zm0-128H352v-96h128v96z'/%3e%3c/svg%3e");
$bg-icon-tabular-scrolling: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.7.1.1 28.7 0 64v384c.1 35.3 28.7 63.9 64 64h384c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM32 128h128v96H32v-96zm0 128h128v96H32v-96zm32 224c-17.6-.1-31.9-14.4-32-32v-64h128v96H64zm128 0v-96h128v96H192zm288-32c-.1 17.6-14.4 31.9-32 32h-96v-96h128v64zm0-192v96H192v-96h32v-32h-32v-96h288v96h-32v32h32z'/%3e%3cpath fill='%23000000' d='M391.2 273.7L336 246.1V160c0-8.8-7.2-16-16-16s-16 7.2-16 16v105.9l72.8 36.4c7.9 4 17.5.8 21.5-7.2 4-7.8.8-17.5-7.1-21.4z'/%3e%3c/svg%3e");
$bg-icon-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M64 384V96c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64H128c-35.3-.1-63.9-28.7-64-64z'/%3e%3cpath fill='%23000000' d='M448 0H160c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM128 96h96v64h-96V96zm0 96h96v96h-96v-96zm32 192c-17.6-.1-31.9-14.4-32-32v-32h96v64h-64zm96 0v-64h96v64h-96zm224-32c-.1 17.6-14.4 31.9-32 32h-64v-64h96v32zm0-64H256V96h224v192z'/%3e%3cpath fill='%23000000' d='M416 240c8.8 0 16-7.2 16-16 0-6.9-4.4-13-10.9-15.2L384 196.5V144c0-8.8-7.2-16-16-16s-16 7.2-16 16v75.5l58.9 19.6c1.7.6 3.4.9 5.1.9z'/%3e%3c/svg%3e");
$bg-icon-clock: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm135 345c-6.4 11.1-18.3 18-31.2 18-6.3 0-12.5-1.7-18-4.8l-110.9-64-.1-.1c-.4-.2-.8-.5-1.2-.7l-.4-.3-.9-.6-.6-.5-.6-.5-.9-.7-.3-.3c-.4-.3-.7-.6-1.1-.9-2.5-2.3-4.7-5-6.5-7.9-.1-.2-.3-.5-.4-.7s-.3-.5-.4-.7c-1.6-3-2.9-6.2-3.6-9.6v-.1c-.1-.5-.2-.9-.3-1.4 0-.1 0-.3-.1-.4-.1-.3-.1-.7-.1-1.1s-.1-.5-.1-.8 0-.5-.1-.8-.1-.8-.1-1.1v-.5-1.4V81c0-19.9 16.1-36 36-36s36 16.1 36 36v161.2l92.9 53.6c17.1 10 22.9 32 13 49.2z'/%3e%3c/svg%3e");
$bg-icon-database: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 256C114.615 256 0 213.019 0 160v256c0 53.019 114.615 96 256 96s256-42.981 256-96V160c0 53.019-114.615 96-256 96z'/%3e%3cellipse fill='%23000000' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
$bg-icon-database-query: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M341.76 409.643C316.369 423.871 287.118 432 256 432c-97.047 0-176-78.953-176-176S158.953 80 256 80s176 78.953 176 176c0 31.118-8.129 60.369-22.357 85.76l95.846 95.846C509.747 430.661 512 423.429 512 416V96c0-53.019-114.615-96-256-96S0 42.981 0 96v320c0 53.019 114.615 96 256 96 63.055 0 120.774-8.554 165.388-22.73l-79.628-79.627z'/%3e%3cpath fill='%23000000' d='M176 256c0 44.112 35.888 80 80 80s80-35.888 80-80-35.888-80-80-80-80 35.888-80 80z'/%3e%3c/svg%3e");
$bg-icon-dataset: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64zM160 448H96V288h64v160zm128 0h-64V288h64v160zm128 0h-64V288h64v160z'/%3e%3c/svg%3e");
$bg-icon-datatable: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 256C114.6 256 0 213 0 160v256c0 53 114.6 96 256 96s256-43 256-96V160c0 53-114.6 96-256 96zm192 31.5v128c-18.3 7.8-39.9 14.4-64 19.7v-128c24.1-5.3 45.7-11.9 64-19.7zm-320 19.7v128c-24.1-5.2-45.7-11.9-64-19.7v-128c18.3 7.8 39.9 14.4 64 19.7zM192 445V317c20.5 2 41.9 3 64 3s43.5-1.1 64-3v128c-20.5 2-41.9 3-64 3s-43.5-1.1-64-3z'/%3e%3cellipse fill='%23000000' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
$bg-icon-dictionary: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M416 320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96v160l-64-32-64 32V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96v-96c0 52.8-43.2 96-96 96H96v-96h320z'/%3e%3c/svg%3e");
$bg-icon-folder: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z'/%3e%3c/svg%3e");
$bg-icon-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zm0 448H64V64h384v384z'/%3e%3cpath fill='%23000000' d='M160 128l-64 64v224h320V256l-64-64-64 64z'/%3e%3c/svg%3e");
$bg-icon-layout: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M224 0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h128V0zM416 0H288v288.832h224V96c0-52.8-43.2-96-96-96zM288 512h128c52.8 0 96-43.2 96-96v-64.832H288V512z'/%3e%3c/svg%3e");
$bg-icon-object: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23000000' d='M256 512l256-160V160L255.99 0 0 160v192l256 160zm0-416l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
$bg-icon-object-unknown: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M255-1L-1 159v192l256 160 256-160V159L255-1zm37.7 430.6c-10.6 10.4-23 15.4-38 15.4-15.6 0-28.1-4.9-38.1-14.8-10-10-14.8-22.4-14.8-38.1 0-15.2 5.1-27.6 15.5-38.1s22.6-15.6 37.4-15.6c14.8 0 27.1 5.2 37.8 16 10.7 10.8 15.9 23.2 15.9 38-.1 14.5-5.4 27-15.7 37.2zm26.4-156.3c-11.8 5.9-18.7 11-21.7 16.2-1.8 3.1-3 7.4-3.7 13.4v20.5H213v-22.1c0-20.1 2.2-34.9 6.5-44 4-8.6 11.3-15.1 22.4-20l17.4-7.7c16-7.1 24.1-17.6 24.1-31.4 0-8-3-15.2-8.6-20.9-5.6-5.6-12.8-8.6-20.8-8.6-12 0-27.2 5-31.4 28.7l-1.1 6.1H148l.7-8.1c2-22.3 8.5-41.2 19.4-56.1 9.8-13.5 22.8-24.3 38.5-32.3 15.7-8 32.3-12 49.1-12 30.3 0 55.1 9.7 75.7 29.8 20.6 20 30.6 44 30.6 73.6 0 35.4-14.4 60.7-42.9 74.9z'/%3e%3c/svg%3e");
$bg-icon-packet: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23000000' d='M256 0L0 160v256c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V160L256 0zm0 96l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
$bg-icon-page: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M352 256c-52.8 0-96-43.2-96-96V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V256H352z'/%3e%3cpath fill='%23000000' d='M384 192h128L320 0v128c0 35.2 28.8 64 64 64z'/%3e%3c/svg%3e");
$bg-icon-plot-overlay: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M415 0H97C43.65 0 0 43.65 0 97v203.41c7.09 9.32 12.83 14.17 16 15.42 7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73C403.71 188.64 440.64 124 496 124a69.55 69.55 0 0 1 16 1.87V97c0-53.35-43.65-97-97-97z'/%3e%3cpath fill='%23000000' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73C108.29 323.36 71.36 388 16 388a69.56 69.56 0 0 1-16-1.87V415c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97V211.59c-7.09-9.32-12.83-14.17-16-15.42z'/%3e%3c/svg%3e");
$bg-icon-plot-stacked: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M44.8 156c12.49 0 24.48-13.26 42.76-35.09 22.71-27.14 51-60.91 98-60.91 22.32 0 43.31 7.73 62.4 23 14.34 11.45 25.58 25.21 36.46 38.53C303.63 145 314 156 326.4 156H512V97c0-53.35-43.65-97-97-97H97C43.65 0 0 43.65 0 97v59h44.8z'/%3e%3cpath fill='%23000000' d='M264.75 205.2c-14.12-11.32-25.26-25-36-38.14C211 145.32 199.37 132 185.6 132c-12.53 0-24.54 13.27-42.83 35.12-22.7 27.12-51 60.88-98 60.88H0v56h185.6c22 0 42.77 7.67 61.65 22.8 14.12 11.32 25.26 25 36 38.14C301 366.68 312.63 380 326.4 380c12.53 0 24.54-13.27 42.83-35.12 22.7-27.12 51-60.88 98-60.88H512v-56H326.4c-22.03 0-42.77-7.67-61.65-22.8z'/%3e%3cpath fill='%23000000' d='M467.2 356c-12.49 0-24.48 13.26-42.76 35.09-22.71 27.14-51 60.91-98 60.91-22.32 0-43.31-7.73-62.4-23-14.34-11.45-25.58-25.21-36.46-38.53C208.37 367 198 356 185.6 356H0v59c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97v-59h-44.8z'/%3e%3c/svg%3e");
$bg-icon-session: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M317.8 262.2c3.3 2.1 6.6 4.3 9.6 6.8l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l67.6-54c.1-2.4.1-4.7.1-7.1 0-26.1-3.9-51.2-11.1-74.9L423.5 243c-29.1 23.3-70.1 29.6-105.7 19.2zM124.3 317.1l60.2-48.2c29-23.2 70-29.6 105.6-19.2-3.3-2.1-6.6-4.3-9.6-6.8l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L103.5 243c-20 16-45.7 24-71.5 24-10.8 0-21.5-1.4-31.9-4.2v.8c2.5 1.7 5 3.4 7.3 5.3l60.2 48.2c14.9 11.9 41.9 11.9 56.7 0z'/%3e%3cpath fill='%23000000' d='M60.3 189.1l60.2-48.2c40.1-32.1 102.8-32.1 142.9 0l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l90.5-72.4C425.2 46.5 346 0 256 0 136.7 0 36.4 81.6 8 192.1c15.4 8.8 38.9 7.8 52.3-3zM344.5 371l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L167.5 371c-20 16-45.7 24-71.5 24-23.9 0-47.7-6.9-67.1-20.7C71.7 456.1 157.3 512 256 512s184.3-55.9 227.1-137.7c-40.2 28.7-99.9 27.6-138.6-3.3z'/%3e%3c/svg%3e");
$bg-icon-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zM320 224H192v-96h128v96zm-128 32h128v96H192v-96zm-32 96H32v-96h128v96zm0-224v96H32v-96h128zM64 480c-8.5 0-16.5-3.3-22.6-9.4S32 456.5 32 448v-64h128v96H64zm128 0v-96h128v96H192zm288-32c0 8.5-3.3 16.5-9.4 22.6S456.5 480 448 480h-96v-96h128v64zm0-96H352v-96h128v96zm0-128H352v-96h128v96z'/%3e%3c/svg%3e");
$bg-icon-tabular-lad: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.7.1.1 28.7 0 64v384c.1 35.3 28.7 63.9 64 64h384c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM32 128h128v96H32v-96zm0 128h128v96H32v-96zm32 224c-17.6-.1-31.9-14.4-32-32v-64h128v96H64zm128 0v-96h128v96H192zm288-32c-.1 17.6-14.4 31.9-32 32h-96v-96h128v64zm0-192v96H192v-96h32v-32h-32v-96h288v96h-32v32h32z'/%3e%3cpath fill='%23000000' d='M391.2 273.7L336 246.1V160c0-8.8-7.2-16-16-16s-16 7.2-16 16v105.9l72.8 36.4c7.9 4 17.5.8 21.5-7.2 4-7.8.8-17.5-7.1-21.4z'/%3e%3c/svg%3e");
$bg-icon-tabular-lad-set: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M64 384V96c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64H128c-35.3-.1-63.9-28.7-64-64z'/%3e%3cpath fill='%23000000' d='M448 0H160c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM128 96h96v64h-96V96zm0 96h96v96h-96v-96zm32 192c-17.6-.1-31.9-14.4-32-32v-32h96v64h-64zm96 0v-64h96v64h-96zm224-32c-.1 17.6-14.4 31.9-32 32h-64v-64h96v32zm0-64H256V96h224v192z'/%3e%3cpath fill='%23000000' d='M416 240c8.8 0 16-7.2 16-16 0-6.9-4.4-13-10.9-15.2L384 196.5V144c0-8.8-7.2-16-16-16s-16 7.2-16 16v75.5l58.9 19.6c1.7.6 3.4.9 5.1.9z'/%3e%3c/svg%3e");
$bg-icon-tabular-realtime: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M0 64v384c0 35.2 28.8 64 64 64h288c35.2 0 64-28.8 64-64V340c-19.8 7.8-41.4 12-64 12-35.4 0-68.4-10.5-96-28.6V352h-96v-96h35.3c-5.2-10.1-9.4-20.8-12.6-32H160v-96h22.7C203.6 54.2 271.6 0 352 0H64C28.8 0 0 28.8 0 64zm288 320h96v64c0 8.5-3.3 16.5-9.4 22.6S360.5 480 352 480h-64v-96zm-160 96H64c-8.5 0-16.5-3.3-22.6-9.4S32 456.5 32 448v-64h96v96zm0-128H32v-96h96v96zm32 32h96v96h-96v-96zm-32-160H32v-96h96v96z'/%3e%3cpath fill='%23000000' d='M192 160c0 88.4 71.6 160 160 160s160-71.6 160-160S440.4 0 352 0 192 71.6 192 160zm49.7 39.8L227 187.5c-1.4-6.4-2.3-12.9-2.7-19.6 15.1-.1 30.1-5 41.9-14.8l39.6-33c7.5-6.2 21.1-6.2 28.6 0l39.6 33c2.8 2.3 5.7 4.3 8.8 6.1-23-11.7-52.7-9.2-72.8 7.5l-39.6 33c-7.6 6.3-21.2 6.3-28.7.1zM352 288c-36.7 0-69.7-15.4-93-40.1 14.2-.6 28.1-5.5 39.2-14.7l39.6-33c7.5-6.2 21.1-6.2 28.6 0l39.6 33c11 9.2 25 14.1 39.2 14.7-23.5 24.7-56.5 40.1-93.2 40.1zm125.9-151.3c1.4 7.5 2.1 15.3 2.1 23.3 0 9.4-1 18.6-3 27.5l-14.7 12.3c-7.5 6.2-21.1 6.2-28.6 0l-39.6-33c-2.8-2.3-5.7-4.3-8.8-6.1 23 11.7 52.7 9.2 72.8-7.5l19.8-16.5zM352 32c46.4 0 87.1 24.7 109.5 61.7l-31.2 26c-7.5 6.2-21.1 6.2-28.6 0l-39.6-33c-23.6-19.7-60.6-19.7-84.3 0l-39.6 33c-2.5 2.1-5.7 3.5-9.1 4.2C244.7 70.8 293.8 32 352 32z'/%3e%3c/svg%3e");
$bg-icon-tabular-scrolling: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M32 0C14.4 0 0 14.4 0 32v96h224V0H32zM512 128V32c0-17.6-14.4-32-32-32H288v128h224zM0 192v96c0 17.6 14.4 32 32 32h192V192H0zM480 320c17.6 0 32-14.4 32-32v-96H288v128h192zM256 512L128 384h256z'/%3e%3c/svg%3e");
$bg-icon-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M16 315.83c7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73 18.7-47.75 49.57-103.57 94.47-116.23A255.87 255.87 0 0 0 256 0C114.62 0 0 114.62 0 256a257.18 257.18 0 0 0 5 50.52c4.77 5.39 8.61 8.37 11 9.31z'/%3e%3cpath fill='%23000000' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73-18.7 47.75-49.57 103.57-94.47 116.23A255.87 255.87 0 0 0 256 512c141.38 0 256-114.62 256-256a257.18 257.18 0 0 0-5-50.52c-4.77-5.39-8.61-8.37-11-9.31z'/%3e%3c/svg%3e");
$bg-icon-timeline: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M416 0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96ZM64 160V96h128v64Zm64 64h192v64H128Zm320 192H224v-64h224Zm0-128h-64v-64h64Zm0-128H256V96h192Z'/%3e%3c/svg%3e");
$bg-icon-timer: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M32 0C14.4 0 0 14.4 0 32v96h224V0H32zM512 128V32c0-17.6-14.4-32-32-32H288v128h224zM0 192v96c0 17.6 14.4 32 32 32h192V192H0zM480 320c17.6 0 32-14.4 32-32v-96H288v128h192zM256 512L128 384h256z'/%3e%3c/svg%3e");
$bg-icon-topic: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M16 315.83c7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73 18.7-47.75 49.57-103.57 94.47-116.23A255.87 255.87 0 0 0 256 0C114.62 0 0 114.62 0 256a257.18 257.18 0 0 0 5 50.52c4.77 5.39 8.61 8.37 11 9.31z'/%3e%3cpath fill='%23000000' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73-18.7 47.75-49.57 103.57-94.47 116.23A255.87 255.87 0 0 0 256 512c141.38 0 256-114.62 256-256a257.18 257.18 0 0 0-5-50.52c-4.77-5.39-8.61-8.37-11-9.31z'/%3e%3c/svg%3e");
$bg-icon-timer: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M288 73.3V32.01a32 32 0 0 0-32-32h-64a32 32 0 0 0-32 32V73.3C67.48 100.84 0 186.54 0 288.01c0 123.71 100.29 224 224 224s224-100.29 224-224c0-101.48-67.5-187.2-160-214.71zm-54 224.71l-131.88 105.5A167.4 167.4 0 0 1 56 288.01c0-92.64 75.36-168 168-168 3.36 0 6.69.11 10 .31v177.69z'/%3e%3c/svg%3e");
$bg-icon-topic: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M227.18 238.32l43.15-43.15a25.18 25.18 0 0 1 35.36 0l43.15 43.15a94.42 94.42 0 0 0 35.18 22.25V174.5l-28.82-28.82a95.11 95.11 0 0 0-134.35 0l-43.15 43.15a25.18 25.18 0 0 1-35.36 0L128 174.5v86.07a95.11 95.11 0 0 0 99.18-22.25z'/%3e%3cpath fill='%23000000' d='M252.82 273.68l-43.15 43.15a25.18 25.18 0 0 1-35.36 0l-43.15-43.15c-1-1-2.1-2-3.18-3v98.68a95.11 95.11 0 0 0 131.18-3l43.15-43.15a25.18 25.18 0 0 1 35.36 0l43.15 43.15c1 1 2.1 2 3.18 3v-98.68a95.11 95.11 0 0 0-131.18 3z'/%3e%3cpath fill='%23000000' d='M416 0h-64v96h63.83l.17.17v319.66l-.17.17H352v96h64c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96zM160 416H96.17l-.17-.17V96.17l.17-.17H160V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h64v-96z'/%3e%3c/svg%3e");
$bg-icon-box-with-dashed-lines: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M0 192h64v128H0zM64 64.11l.11-.11H160V0H64A64.19 64.19 0 0 0 0 64v96h64V64.11zM64 447.89V352H0v96a64.19 64.19 0 0 0 64 64h96v-64H64.11zM192 0h128v64H192zM448 447.89l-.11.11H352v64h96a64.19 64.19 0 0 0 64-64v-96h-64v95.89zM448 0h-96v64h95.89l.11.11V160h64V64a64.19 64.19 0 0 0-64-64zM448 192h64v128h-64zM192 448h128v64H192zM128 128h256v256H128z'/%3e%3c/svg%3e");
$bg-icon-summary-widget: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M128 128h192v64H128zM192 224h192v64H192zM160 320h192v64H160z'/%3e%3cpath fill='%23000000' d='M416 0h-64v96h63.8c.1 0 .1.1.2.2v319.7c0 .1-.1.1-.2.2H352v96h64c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96zM96 415.8V96.2c0-.1.1-.1.2-.2H160V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h64v-96H96.2c-.1 0-.2-.1-.2-.2z'/%3e%3c/svg%3e");
$bg-icon-notebook: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M288 73.3V32.01a32 32 0 0 0-32-32h-64a32 32 0 0 0-32 32V73.3C67.48 100.84 0 186.54 0 288.01c0 123.71 100.29 224 224 224s224-100.29 224-224c0-101.48-67.5-187.2-160-214.71zm-54 224.71l-131.88 105.5A167.4 167.4 0 0 1 56 288.01c0-92.64 75.36-168 168-168 3.36 0 6.69.11 10 .31v177.69z'/%3e%3c/svg%3e");
$bg-icon-summary-widget: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zm-24.1 305.2l-41.3 71.6-94.8-65.8 9.6 115h-82.7l9.6-115-94.8 65.8-41.3-71.6L192.5 256 88.1 206.8l41.3-71.6 94.8 65.8-9.6-115h82.7l-9.6 115 94.8-65.8 41.3 71.6L319.5 256l104.4 49.2z'/%3e%3c/svg%3e");
$bg-icon-notebook: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 55.4c0-39.9-27.7-63.7-61.5-52.7L0 128h448V55.4zM448 160H0v288c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64zm-32 256H224V256h192v160z'/%3e%3c/svg%3e");
$bg-icon-tabs-view: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M0 448a64.2 64.2 0 0 0 64 64h384a64.2 64.2 0 0 0 64-64V144H256L230.9 31.2C227.1 14.1 209.6 0 192 0H64A64.2 64.2 0 0 0 0 64zm416-64H96V256h320z'/%3e%3cpath d='M240 0c17.6 0 35.1 14.1 38.9 31.2l18 80.8H512V64a64.2 64.2 0 0 0-64-64z'/%3e%3c/svg%3e");
$bg-icon-flexible-layout: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M0 416c0 52.8 43.2 96 96 96h32V224H0zM0 96v64h128V0H96C43.2 0 0 43.2 0 96zM384 512h32c52.8 0 96-43.2 96-96v-64H384zM192 0h128v512H192zM416 0h-32v288h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e");
$bg-icon-generator-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M76 236.9c5.4-2.1 20.4-17.8 34.9-54.7C126.8 141.5 154.5 93 196 93c19.7 0 38 10.9 54.6 32.5 14 18.2 24.4 40.8 30.5 56.7 14.5 36.9 29.5 52.6 34.9 54.7 5.4-2.1 20.4-17.8 34.9-54.7S388 104.5 421.7 95A192 192 0 0 0 256 0C150 0 64 86 64 192a197.2 197.2 0 0 0 3.7 37.9c3.6 4 6.5 6.3 8.3 7zM442.3 238.5A192.9 192.9 0 0 0 448 192a197.2 197.2 0 0 0-3.7-37.9c-3.6-4-6.5-6.3-8.3-7-5.4 2.1-20.4 17.8-34.9 54.7-10.9 27.9-27.3 59.5-50 76.6z'/%3e%3cpath d='M256 320l67.5-29.5a60.3 60.3 0 0 1-7.5.5c-19.7 0-38-10.9-54.6-32.5-14-18.2-24.4-40.8-30.5-56.7-14.5-36.9-29.5-52.6-34.9-54.7-5.4 2.1-20.4 17.8-34.9 54.7-8.2 21.1-19.6 44.2-34.4 61.6z'/%3e%3cpath d='M512 240L256 352 0 240v160l256 112 256-112V240z'/%3e%3c/svg%3e");
$bg-icon-generator-events: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M160 96h192v32H160zM160 224h192v32H160zM160 160h160v32H160z'/%3e%3cpath d='M128 64.1h256V264l64-28V64a64.2 64.2 0 0 0-64-64H128a64.2 64.2 0 0 0-64 64v172l64 28zM329.1 288H182.9l73.1 32 73.1-32z'/%3e%3cpath d='M256 352L0 240v160l256 112 256-112V240L256 352z'/%3e%3c/svg%3e");
$bg-icon-generator-events: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M160 96h192v32H160zM160 224h192v32H160zM160 160h160v32H160z'/%3e%3cpath d='M128 64.1h256V264l64-28V64a64.2 64.2 0 0 0-64-64H128a64.2 64.2 0 0 0-64 64v172l64 28zM329.1 288H182.9l73.1 32 73.1-32z'/%3e%3cpath d='M256 352L0 240v160l256 112 256-112V240L256 352z'/%3e%3c/svg%3e");
$bg-icon-gauge: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M256 0C114.6 0 0 114.6 0 256c0 113.2 73.5 209.2 175.3 243L304 256v251.5C422.4 485 512 381 512 256 512 114.6 397.4 0 256 0zm121.4 263.9a159.8 159.8 0 0 0-242.8 0l-73-62.5c4.3-5 8.7-9.8 13.4-14.4a255.9 255.9 0 0 1 362 0c4.7 4.6 9.1 9.4 13.4 14.4z'/%3e%3c/svg%3e");
$bg-icon-spectra: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M384 352H128l51.2-89.6L0 288v127c0 53.3 43.7 97 97 97h318c53.4 0 97-43.7 97-97v-31l-162.9-93.1zM415 0H97C43.7 0 0 43.6 0 97v159l200-30.1 56-97.9 54.9 96H512V97a97.2 97.2 0 00-97-97zM512 320v-32l-192-32 192 64z'/%3e%3c/svg%3e");
$bg-icon-spectra-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 128l54.9 96H510C494.3 97.7 386.5 0 256 0 114.6 0 0 114.6 0 256l200-30.1zM384 352H128l51.2-89.6L2 287.7C17.6 414.1 125.4 512 256 512c100.8 0 188-58.3 229.8-143l-136.7-78.1zM320 256l192 64v-32l-192-32z'/%3e%3c/svg%3e");
@@ -325,5 +319,3 @@ $bg-icon-bar-chart: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://w
$bg-icon-map: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 32.7 384 64v448l64-31.3c35.2-17.21 64-60.1 64-95.3v-320c0-35.2-28.8-49.91-64-32.7ZM160 456l193.6 48.4v-448L160 8v448zM129.6.4 128 0 64 31.3C28.8 48.51 0 91.4 0 126.6v320c0 35.2 28.8 49.91 64 32.7l64-31.3 1.6.4Z'/%3e%3c/svg%3e");
$bg-icon-plan: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cg data-name='Layer 1'%3e%3cpath fill='%23000000' d='M128 96V64a64.19 64.19 0 0 1 64-64h128a64.19 64.19 0 0 1 64 64v32Z'/%3e%3cpath fill='%23000000' d='M416 64v64H96V64c-52.8 0-96 43.2-96 96v256c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V160c0-52.8-43.2-96-96-96ZM64 288v-64h128v64Zm256 128H128v-64h192Zm128 0h-64v-64h64Zm0-128H256v-64h192Z'/%3e%3c/g%3e%3c/g%3e%3c/svg%3e");
$bg-icon-timelist: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cpath d='M448 0H64A64.19 64.19 0 0 0 0 64v384a64.19 64.19 0 0 0 64 64h384a64.19 64.19 0 0 0 64-64V64a64.19 64.19 0 0 0-64-64ZM213.47 266.73a24 24 0 0 1-32.2 10.74L104 238.83V128a24 24 0 0 1 48 0v81.17l50.73 25.36a24 24 0 0 1 10.74 32.2ZM448 448H288v-64h160Zm0-96H288v-64h160Zm0-96H288v-64h160Zm0-96H288V96h160Z' data-name='Layer 1'/%3e%3c/g%3e%3c/svg%3e");
$bg-icon-notebook-shift-log: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M448 55.36c0-39.95-27.69-63.66-61.54-52.68L0 128h448V55.36ZM448 160H0v288c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64ZM128 416H64v-64h64v64Zm0-96H64v-64h64v64Zm320 96H192v-64h256v64Zm0-96H192v-64h256v64Z'/%3e%3c/svg%3e");
$bg-icon-plot-scatter: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cpath d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96ZM64 176a48 48 0 1 1 48 48 48 48 0 0 1-48-48Zm80 240a48 48 0 1 1 48-48 48 48 0 0 1-48 48Zm128-96a48 48 0 1 1 48-48 48 48 0 0 1-48 48Zm0-160a48 48 0 1 1 48-48 48 48 0 0 1-48 48Zm128 256a48 48 0 1 1 48-48 48 48 0 0 1-48 48Z' data-name='Layer 1'/%3e%3c/g%3e%3c/svg%3e");

View File

@@ -87,13 +87,6 @@
.icon-unlocked { @include glyphBefore($glyph-icon-unlocked); }
.icon-circle { @include glyphBefore($glyph-icon-circle); }
.icon-draft { @include glyphBefore($glyph-icon-draft); }
.icon-question-mark { @include glyphBefore($glyph-icon-question-mark); }
.icon-circle-slash { @include glyphBefore($glyph-icon-circle-slash); }
.icon-status-poll-check { @include glyphBefore($glyph-icon-status-poll-check); }
.icon-status-poll-caution { @include glyphBefore($glyph-icon-status-poll-caution); }
.icon-status-poll-circle-slash { @include glyphBefore($glyph-icon-status-poll-circle-slash); }
.icon-status-poll-question-mark { @include glyphBefore($glyph-icon-status-poll-question-mark); }
.icon-status-poll-edit { @include glyphBefore($glyph-icon-status-poll-edit); }
.icon-arrows-right-left { @include glyphBefore($glyph-icon-arrows-right-left); }
.icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); }
.icon-bullet { @include glyphBefore($glyph-icon-bullet); }
@@ -202,8 +195,6 @@
.icon-map { @include glyphBefore($glyph-icon-map); }
.icon-plan { @include glyphBefore($glyph-icon-plan); }
.icon-timelist { @include glyphBefore($glyph-icon-timelist); }
.icon-notebook-shift-log { @include glyphBefore($glyph-icon-notebook-shift-log); }
.icon-plot-scatter { @include glyphBefore($glyph-icon-plot-scatter); }
/************************** 12 PX CLASSES */
// TODO: sync with 16px redo as of 10/25/18
@@ -219,6 +210,9 @@
.bg-icon-alert-triangle { @include glyphBg($bg-icon-alert-triangle); }
.bg-icon-bell { @include glyphBg($bg-icon-bell); }
.bg-icon-info { @include glyphBg($bg-icon-info); }
.bg-icon-activity { @include glyphBg($bg-icon-activity); }
.bg-icon-activity-mode { @include glyphBg($bg-icon-activity-mode); }
.bg-icon-autoflow-tabular { @include glyphBg($bg-icon-autoflow-tabular); }
.bg-icon-plus { @include glyphBg($bg-icon-plus); }
.bg-icon-grippy-ew { @include glyphBg($bg-icon-grippy-ew); }
.bg-icon-chain-links { @include glyphBg($bg-icon-chain-links); }
@@ -264,5 +258,3 @@
.bg-icon-map { @include glyphBg($bg-icon-map); }
.bg-icon-plan { @include glyphBg($bg-icon-plan); }
.bg-icon-timelist { @include glyphBg($bg-icon-timelist); }
.bg-icon-notebook-shift-log { @include glyphBg($bg-icon-notebook-shift-log); }
.bg-icon-plot-scatter { @include glyphBg($bg-icon-plot-scatter); }

View File

@@ -749,12 +749,6 @@ mct-plot {
overflow: hidden;
}
/***************** SCATTER PLOTS */
.c-scatter-chart {
flex: 1 1 auto;
overflow: hidden;
}
/***************** CURSOR GUIDES */
[class*='c-cursor-guide'] {
box-shadow: $shdwCursorGuide;

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="Open-MCT-Symbols-16px" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="921.6" descent="-102.4" />
<font-face units-per-em="1024" ascent="819.2" descent="-204.8" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="0" d="" />
<glyph unicode="&#xe900;" glyph-name="icon-alert-rect-v2" d="M896 832h-768c-70.6-0.2-127.8-57.4-128-128v-768c0.2-70.6 57.4-127.8 128-128h768c70.6 0.2 127.8 57.4 128 128v768c-0.2 70.6-57.4 127.8-128 128zM576-64h-128v128h128v-128zM597.8 320l-37.8-192h-96l-37.8 192v384h171.8v-384z" />
@@ -55,13 +55,6 @@
<glyph unicode="&#xe92d;" glyph-name="icon-unlocked" d="M768 832c-141.339-0.114-255.886-114.661-256-255.989v-128.011h-448c-35.301-0.113-63.887-28.699-64-63.989v-512.011c0.113-35.301 28.699-63.887 63.989-64h638.011c35.301 0.113 63.887 28.699 64 63.989v512.011c-0.113 35.301-28.699 63.887-63.989 64h-62.011v128c0 70.692 57.308 128 128 128s128-57.308 128-128v0-128h128v128c-0.114 141.339-114.661 255.886-255.989 256h-0.011z" />
<glyph unicode="&#xe92e;" glyph-name="icon-circle" d="M1024 320c0-282.77-229.23-512-512-512s-512 229.23-512 512c0 282.77 229.23 512 512 512s512-229.23 512-512z" />
<glyph unicode="&#xe92f;" glyph-name="icon-draft" d="M876.34 196.42l-49.9-49.88-19.26-19.5-26-8.7-423.040-144.2 144.2 423.28 8.84 25.78 150 149.88-85.6 149.78c-34.92 61.12-92 61.12-127 0l-422.78-739.72c-34.94-61.14-5.92-111.14 64.48-111.14h843.44c70.4 0 99.42 50 64.48 111.14zM973.18 589.16c-19.32 19.3-40.66 34.62-60.16 43.16-34.42 15.12-52.38 4.54-60.1-3.16l-258.12-258.12-82.8-243.040 243 82.8 3.36 3.4 254.76 254.76c4.94 4.94 10.88 13.88 10.88 28.3 0 25.34-19.5 60.56-50.82 91.9zM631 212.18l-34.88 34.86 34.64 101.6 9.24 3.36h32v-64h64v-32l-3.42-9.26z" />
<glyph unicode="&#xe930;" glyph-name="icon-circle-slash" d="M512 832c-282.78 0-512-229.22-512-512s229.22-512 512-512 512 229.22 512 512-229.22 512-512 512zM263.1 568.9c66.48 66.48 154.88 103.1 248.9 103.1 66.74 0 130.64-18.48 185.9-52.96l-484.94-484.94c-34.5 55.24-52.96 119.16-52.96 185.9 0 94.020 36.62 182.42 103.1 248.9zM760.9 71.1c-66.48-66.48-154.88-103.1-248.9-103.1-66.74 0-130.64 18.48-185.9 52.96l484.94 484.94c34.5-55.24 52.96-119.16 52.96-185.9 0-94.020-36.62-182.42-103.1-248.9z" />
<glyph unicode="&#xe931;" glyph-name="icon-question-mark" horiz-adv-x="697" d="M136.86 779.74c54.080 34.82 120.58 52.26 199.44 52.26 103.6 0 189.7-24.76 258.24-74.28s102.82-122.88 102.82-220.060c0-59.6-14.86-109.8-44.58-150.6-17.38-24.76-50.76-56.4-100.14-94.9l-48.68-37.82c-26.54-20.64-44.14-44.7-52.82-72.2-5.5-17.44-8.46-44.48-8.92-81.14h-186.4c2.74 77.48 10.060 131 21.94 160.58s42.5 63.62 91.88 102.12l50.060 39.2c16.46 12.38 29.72 25.9 39.78 40.58 18.28 25.2 27.42 52.96 27.42 83.22 0 34.84-10.18 66.6-30.52 95.24-20.36 28.64-57.52 42.98-111.48 42.98s-90.68-17.66-112.88-52.96c-22.18-35.32-33.26-71.98-33.26-110.040h-198.76c5.5 130.64 51.12 223.24 136.86 277.82zM251.020 6.76h205.62v-198.74h-205.62v198.74z" />
<glyph unicode="&#xe932;" glyph-name="icon-status-poll-check" d="M512 832c-282.76 0-512-214.9-512-480 0-92.26 27.8-178.44 75.92-251.6l-75.92-292.4 313.5 101.42c61.040-24.1 128.12-37.42 198.5-37.42 282.76 0 512 214.9 512 480s-229.24 480-512 480zM768 384l-320-320-192 192v192l192-192 320 320v-192z" />
<glyph unicode="&#xe933;" glyph-name="icon-status-poll-caution" d="M512 832c-282.76 0-512-214.9-512-480 0-92.26 27.8-178.44 75.92-251.6l-75.92-292.4 313.5 101.42c61.040-24.1 128.12-37.42 198.5-37.42 282.76 0 512 214.9 512 480s-229.24 480-512 480zM781.36 128h-538.72c-44.96 0-63.5 31.94-41.2 70.98l270 472.48c22.3 39.040 58.82 39.040 81.12 0l269.98-472.48c22.3-39.040 3.78-70.98-41.2-70.98zM457.14 414.14l24.2-122.64h61.32l24.2 122.64v163.5h-109.72v-163.5zM471.12 250.64h81.76v-81.76h-81.76v81.76z" />
<glyph unicode="&#xe934;" glyph-name="icon-status-poll-circle-slash" d="M391.18 163.3c35.72-22.98 77.32-35.3 120.82-35.3 59.84 0 116.080 23.3 158.4 65.6 42.3 42.3 65.6 98.56 65.6 158.4 0 43.5-12.32 85.080-35.3 120.82l-309.52-309.52zM512 576c-59.84 0-116.080-23.3-158.4-65.6-42.3-42.3-65.6-98.56-65.6-158.4 0-43.5 12.32-85.080 35.3-120.82l309.52 309.52c-35.72 22.98-77.32 35.3-120.82 35.3zM512 832c-282.76 0-512-214.9-512-480 0-92.26 27.8-178.44 75.92-251.6l-75.92-292.4 313.5 101.42c61.040-24.1 128.12-37.42 198.5-37.42 282.76 0 512 214.9 512 480s-229.24 480-512 480zM512 32c-176.74 0-320 143.26-320 320s143.26 320 320 320 320-143.26 320-320-143.26-320-320-320z" />
<glyph unicode="&#xe935;" glyph-name="icon-status-poll-question-mark" d="M512 832c-282.76 0-512-214.9-512-480 0-92.26 27.8-178.44 75.92-251.6l-75.92-292.4 313.5 101.42c61.040-24.1 128.12-37.42 198.5-37.42 282.76 0 512 214.9 512 480s-229.24 480-512 480zM579.020 0h-141.36v136.64h141.36v-136.64zM713.84 398.1c-11.94-17.020-34.9-38.78-68.84-65.24l-33.48-26c-18.24-14.18-30.34-30.74-36.32-49.64-3.78-11.98-5.82-30.58-6.14-55.8h-128.12c1.88 53.26 6.92 90.060 15.080 110.4 8.18 20.34 29.22 43.74 63.16 70.22l34.42 26.94c11.3 8.52 20.42 17.8 27.34 27.9 12.56 17.34 18.86 36.4 18.86 57.2 0 23.94-7 45.78-20.98 65.48-14 19.7-39.54 29.54-76.64 29.54s-62.34-12.14-77.6-36.4c-15.24-24.28-22.88-49.48-22.88-75.64h-136.64c3.78 89.84 35.14 153.5 94.080 191.020 37.18 23.94 82.9 35.94 137.12 35.94 71.22 0 130.42-17.020 177.54-51.060s70.68-84.48 70.68-151.3c0-40.98-10.22-75.5-30.66-103.54z" />
<glyph unicode="&#xe936;" glyph-name="icon-status-poll-edit" d="M1000.080 497.36l-336.6-336.76-20.52-6.88-450.96-153.72 160.68 471.52 332.34 332.34c-54.040 18.2-112.28 28.14-173.020 28.14-282.76 0-512-214.9-512-480 0-92.26 27.8-178.44 75.92-251.6l-75.92-292.4 313.5 101.42c61.040-24.1 128.12-37.42 198.5-37.42 282.76 0 512 214.9 512 480 0 50.68-8.4 99.5-23.92 145.36zM408.42 436.76l-2.16-6.3-111.7-327.9 334.12 113.86 4.62 4.68 350.28 350.28c6.8 6.78 14.96 19.1 14.96 38.9 0 34.86-26.82 83.28-69.88 126.38-26.54 26.54-55.9 47.6-82.7 59.34-47.34 20.8-72.020 6.24-82.64-4.36l-354.9-354.88zM470.56 410.58h44v-88h88v-44l-4.7-12.72-139.68-47.54-47.94 47.94 47.6 139.72 12.72 4.6z" />
<glyph unicode="&#xea00;" glyph-name="icon-arrows-right-left" d="M1024 320l-448-512v1024zM448 832l-448-512 448-512z" />
<glyph unicode="&#xea01;" glyph-name="icon-arrows-up-down" d="M512 832l512-448h-1024zM0 256l512-448 512 448z" />
<glyph unicode="&#xea02;" glyph-name="icon-bullet" d="M832 80c0-44-36-80-80-80h-480c-44 0-80 36-80 80v480c0 44 36 80 80 80h480c44 0 80-36 80-80v-480z" />
@@ -135,7 +128,7 @@
<glyph unicode="&#xeb0a;" glyph-name="icon-image" d="M896 832h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM896-64h-768v768h768v-768zM320 576l-128-128v-448h640v320l-128 128-128-128z" />
<glyph unicode="&#xeb0b;" glyph-name="icon-layout" d="M448 832h-256c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h256v1024zM832 832h-256v-577.664h448v385.664c0 105.6-86.4 192-192 192zM576-192h256c105.6 0 192 86.4 192 192v129.664h-448v-321.664z" />
<glyph unicode="&#xeb0c;" glyph-name="icon-object" d="M512-192l512 320v384l-512.020 320-511.98-320v-384l512-320zM512 640l358.4-224-358.4-224-358.4 224 358.4 224z" />
<glyph unicode="&#xeb0d;" glyph-name="icon-object-unknown" d="M511.98 832l-511.98-320v-384l512-320 512 320v384l-512.020 320zM586.22-64h-141.36v136.64h141.36v-136.64zM721.040 334.1c-11.94-17.020-34.9-38.78-68.84-65.24l-33.48-26c-18.24-14.18-30.34-30.74-36.32-49.64-3.78-11.98-5.82-30.58-6.14-55.8h-128.12c1.88 53.26 6.92 90.060 15.080 110.4 8.18 20.34 29.22 43.74 63.16 70.22l34.42 26.94c11.3 8.52 20.42 17.8 27.34 27.9 12.56 17.34 18.86 36.4 18.86 57.2 0 23.94-7 45.78-20.98 65.48-14 19.7-39.54 29.54-76.64 29.54s-62.34-12.14-77.6-36.4c-15.24-24.28-22.88-49.48-22.88-75.64h-136.64c3.78 89.84 35.14 153.5 94.080 191.020 37.18 23.94 82.9 35.94 137.12 35.94 71.22 0 130.42-17.020 177.54-51.060s70.68-84.48 70.68-151.3c0-40.98-10.22-75.5-30.66-103.54z" />
<glyph unicode="&#xeb0d;" glyph-name="icon-object-unknown" d="M510 834l-512-320v-384l512-320 512 320v384l-512 320zM585.4-27.2c-21.2-20.8-46-30.8-76-30.8-31.2 0-56.2 9.8-76.2 29.6-20 20-29.6 44.8-29.6 76.2 0 30.4 10.2 55.2 31 76.2s45.2 31.2 74.8 31.2c29.6 0 54.2-10.4 75.6-32s31.8-46.4 31.8-76c-0.2-29-10.8-54-31.4-74.4zM638.2 285.4c-23.6-11.8-37.4-22-43.4-32.4-3.6-6.2-6-14.8-7.4-26.8v-41h-161.4v44.2c0 40.2 4.4 69.8 13 88 8 17.2 22.6 30.2 44.8 40l34.8 15.4c32 14.2 48.2 35.2 48.2 62.8 0 16-6 30.4-17.2 41.8-11.2 11.2-25.6 17.2-41.6 17.2-24 0-54.4-10-62.8-57.4l-2.2-12.2h-147l1.4 16.2c4 44.6 17 82.4 38.8 112.2 19.6 27 45.6 48.6 77 64.6s64.6 24 98.2 24c60.6 0 110.2-19.4 151.4-59.6 41.2-40 61.2-88 61.2-147.2 0-70.8-28.8-121.4-85.8-149.8z" />
<glyph unicode="&#xeb0e;" glyph-name="icon-packet" d="M512 832l-512-320v-512c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v512l-512 320zM512 640l358.4-224-358.4-224-358.4 224 358.4 224z" />
<glyph unicode="&#xeb0f;" glyph-name="icon-page" d="M704 320c-105.6 0-192 86.4-192 192v320h-320c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v320h-320zM768 448h256l-384 384v-256c0-70.4 57.6-128 128-128z" />
<glyph unicode="&#xeb10;" glyph-name="icon-plot-overlay" d="M830 832h-636c-106.7 0-194-87.3-194-194v-406.82c14.18-18.64 25.66-28.34 32-30.84 14.28 5.62 54.44 47.54 92.96 146 42.46 108.38 116.32 237.66 227.040 237.66 52.4 0 101.42-29.16 145.7-86.68 37.34-48.5 64.84-108.92 81.34-151.080 38.52-98.38 78.68-140.3 92.96-146 14.28 5.62 54.44 47.54 92.96 146 42.46 108.48 116.32 237.76 227.040 237.76 11.355-0.003 22.389-1.366 32.952-3.936l-0.952 0.196v57.74c0 106.7-87.3 194-194 194zM992 439.66c-14.28-5.62-54.44-47.52-92.96-146-42.46-108.38-116.32-237.66-227.040-237.66-52.4 0-101.42 29.16-145.7 86.68-37.34 48.5-64.84 108.92-81.34 151.080-38.52 98.38-78.68 140.3-92.96 146-14.28-5.62-54.44-47.52-92.96-146-42.46-108.48-116.32-237.76-227.040-237.76-11.355 0.003-22.389 1.367-32.952 3.936l0.952-0.196v-57.74c0-106.7 87.3-194 194-194h636c106.7 0 194 87.3 194 194v406.82c-14.18 18.64-25.66 28.34-32 30.84z" />
@@ -170,5 +163,4 @@
<glyph unicode="&#xeb2d;" glyph-name="icon-map" d="M896 766.6l-128-62.6v-896l128 62.6c70.4 34.42 128 120.2 128 190.6v640c0 70.4-57.6 99.82-128 65.4zM320-80l387.2-96.8v896l-387.2 96.8v-896zM259.2 831.2l-3.2 0.8-128-62.6c-70.4-34.42-128-120.2-128-190.6v-640c0-70.4 57.6-99.82 128-65.4l128 62.6 3.2-0.8z" />
<glyph unicode="&#xeb2e;" glyph-name="icon-plan" d="M256 640v64c0.215 70.606 57.394 127.785 127.979 128h256.021c70.606-0.215 127.785-57.394 128-127.979v-64.021zM832 704v-128h-640v128c-105.6 0-192-86.4-192-192v-512c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v512c0 105.6-86.4 192-192 192zM128 256v128h256v-128zM640 0h-384v128h384zM896 0h-128v128h128zM896 256h-384v128h384z" />
<glyph unicode="&#xeb2f;" glyph-name="icon-timelist" d="M896 832h-768c-70.606-0.215-127.785-57.394-128-127.979v-768.021c0.215-70.606 57.394-127.785 127.979-128h768.021c70.606 0.215 127.785 57.394 128 127.979v768.021c-0.215 70.606-57.394 127.785-127.979 128h-0.021zM426.94 298.54c-8.054-15.864-24.249-26.545-42.938-26.545-7.823 0-15.209 1.871-21.734 5.191l0.273-0.126-154.54 77.28v221.66c0 26.51 21.49 48 48 48s48-21.49 48-48v0-162.34l101.46-50.72c15.864-8.054 26.545-24.249 26.545-42.938 0-7.823-1.871-15.209-5.191-21.734l0.126 0.273zM896-64h-320v128h320zM896 128h-320v128h320zM896 320h-320v128h320zM896 512h-320v128h320z" />
<glyph unicode="&#xeb31;" glyph-name="icon-notebook-restricted" d="M896 721.28c0 79.9-55.38 127.32-123.080 105.36l-772.92-250.64h896v145.28zM896 512h-896v-576c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v448c0 70.4-57.6 128-128 128zM256 0h-128v128h128v-128zM256 192h-128v128h128v-128zM896 0h-512v128h512v-128zM896 192h-512v128h512v-128z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Some files were not shown because too many files have changed in this diff Show More