Compare commits

...

43 Commits

Author SHA1 Message Date
Alize Nguyen
81799e2146 Merge branch '4777-plots-progress-bar' of https://github.com/nasa/openmct into 4777-plots-progress-bar 2022-06-23 12:12:44 -05:00
Alize Nguyen
373f2e5580 Merge branch 'master' into 4777-plots-progress-bar 2022-06-23 12:05:35 -05:00
Alize Nguyen
d92d4c1768 Put obj directly to model, removed computed prop 2022-06-23 12:02:04 -05:00
Alize Nguyen
9c34de37cc Check if delay is 0 and send, put post in a func 2022-06-23 12:01:08 -05:00
Shefali Joshi
34442c53c6 Update version for 2.1.0 (#5364)
* Bump d3-selection from 1.3.2 to 3.0.0

Bumps [d3-selection](https://github.com/d3/d3-selection) from 1.3.2 to 3.0.0.
- [Release notes](https://github.com/d3/d3-selection/releases)
- [Commits](https://github.com/d3/d3-selection/compare/v1.3.2...v3.0.0)

---
updated-dependencies:
- dependency-name: d3-selection
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Prepare for sprint 2.1.0

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-21 10:10:52 -07:00
dependabot[bot]
451ca075fe Bump mini-css-extract-plugin from 2.6.0 to 2.6.1 (#5352)
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.6.0...v2.6.1)

---
updated-dependencies:
- dependency-name: mini-css-extract-plugin
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 20:07:52 +00:00
Joshi
c3272f985d Merge branch 'master' of https://github.com/nasa/openmct into 4777-plots-progress-bar 2022-06-14 08:15:23 -07:00
Jesse Mazzella
84f1a61a8d Allow dragging of imagery filter sliders when embedded in flexible layout (#5342)
* Fix imagery filter sliders within flexible layout

* Add image filter value checks to e2e test

- Tests if filter values are updated correctly when dragging contrast/brightness sliders on imagery embedded in a flexible layout

* More e2e tests, add aria-role and update selectors

- Add 'toolbar' role for Image controls
2022-06-13 15:24:44 -07:00
dependabot[bot]
ea041aaaf9 Bump webpack-cli from 4.9.2 to 4.10.0 (#5344)
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 4.9.2 to 4.10.0.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/webpack-cli@4.9.2...webpack-cli@4.10.0)

---
updated-dependencies:
- dependency-name: webpack-cli
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-06-13 21:57:56 +00:00
dependabot[bot]
ac9420bfa1 Bump eslint-plugin-vue from 9.1.0 to 9.1.1 (#5343)
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 9.1.0 to 9.1.1.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v9.1.0...v9.1.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-vue
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-13 14:53:30 -07:00
dependabot[bot]
0ebab10578 Bump jasmine-core from 4.1.1 to 4.2.0 (#5332)
Bumps [jasmine-core](https://github.com/jasmine/jasmine) from 4.1.1 to 4.2.0.
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v4.1.1...v4.2.0)

---
updated-dependencies:
- dependency-name: jasmine-core
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-13 16:50:46 +00:00
Jesse Mazzella
cbdb9fc437 Fix test:watch -- begone, infinite watch loop! (#5337) 2022-06-13 08:40:37 -07:00
Adam Fahey
63d2246345 Created Example Imagery in Flexible layout test (#5239)
* Created Example Imagery in Flexible layout test

* Updated test to have describe and rebased

* added image to flexview

* Removed old comments

* Cant figure out how to zoom once in flexable layout

* Zoom in and pan left, right, up and down

* Finished

* Lint indentation fix

* Removed dangle comma

* DRY up tests made functions

* Fix lint errors

* Functions failing

* Zoom and pan functions

* Replaced zoom in with function

* Fixed lint errors

* white space

* Removed xpath using css for flexable layout click
2022-06-11 06:45:09 -07:00
Michael Pingleton
78002f0a24 Fixed bug which prevented historical images from loading. (#5271)
* Fixed bug which prevented historical images from loading.

imageLoadDelay variable would be initialized as a string when it's supposed to be initialized as an integer.

* Updated image load delay in E2E test.

* Implemented suggested tweak for parsing image delay.

Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-06-09 14:25:33 -07:00
Jesse Mazzella
f08fd58486 Add test for imagery filter reset (#5318) 2022-06-09 14:15:21 -07:00
Charles Hacskaylo
730272e165 Fixes #5303 (#5314)
- Fixed scrambled SVG definitions;
- Removed some unused artwork;

Co-authored-by: Scott Bell <scott@traclabs.com>
2022-06-09 11:54:15 +02:00
Alize Nguyen
ab71df77df Move loadingUpdate func into plot item for update 2022-06-08 23:54:04 -05:00
Alize Nguyen
586d0393e5 Merge branch '4777-plots-progress-bar' of https://github.com/nasa/openmct into 4777-plots-progress-bar 2022-06-08 22:08:35 -05:00
Alize Nguyen
52902a7c89 Add loading fix for stacked plots 2022-06-08 22:07:26 -05:00
Shefali Joshi
0f0c6a7b17 2.0.4 merge into master (#5297)
* Release 2.0.3

* 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.

* 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

* [LAD Tables] Use Telemetry Collections (#5127)

* Use telemetry collections to handle bounds checks

* added telemetry collection to alphanumeric telemetry view (#5131)

* 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

* 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>

* Gauge edit enabled 2.0.3 (#5133)

* Gauge plugin #4896, add edit mode

* 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>

* [Telemetry Collections] Include data with start and end bounds (#5145)

* Reverts forced precision for log plots axis labels (#5147)

* Condition Widgets trigger hundreds of persistence calls (#5146)

Co-authored-by: unlikelyzero <jchill2@gmail.com>

* Update version for 2.0.4 (#5255)

* Eliminate NaN conditions and clear stale duration (#5248)

* Temp source map fix 2.0.4 (#5267)

* use dev mode for production

* mode -> production

* added extra devtool options

* wip

* Imagery Fixes for release/2.0.4 (#5282)

* Fallback for height

* Remove duplicated requestHistory call since setDataTimeContext already invokes it on mount

* Inverted datumIsNotValid and refactored requestHistory

* Remove old datumIsNotValid func

* Return false if datum is falsy

* Corrected brightness/contrast input

* Clone default values to avoid mutation

* Changed index of imageTelemetry to an item within bounds

* Implement clearData test for imagery differently

* x-out clearData tests

Co-authored-by: Joshi <simplyrender@gmail.com>

* Imagery test fixes (#5293)

* Fallback for height

* Remove duplicated requestHistory call since setDataTimeContext already invokes it on mount

* Inverted datumIsNotValid and refactored requestHistory

* Remove old datumIsNotValid func

* Return false if datum is falsy

* Corrected brightness/contrast input

* Clone default values to avoid mutation

* Changed index of imageTelemetry to an item within bounds

* Implement clearData test for imagery differently

* x-out clearData tests

* Set bounds on each test rather than the wrapper

Co-authored-by: Michael Rogers <contact@mhrogers.com>

* Imagery validation fix (#5295)

* Remove check for duplicate images
* Remove commented out code and add TODO

* lint fix

* Add missing tests

* Use the master version and ignore release/2.0.4 changes

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Michael Rogers <contact@mhrogers.com>
Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-06-09 01:06:31 +00:00
Jamie V
370e6a0c37 fixing non functioning render test, boost cov also (#5311) 2022-06-08 17:39:43 -07:00
Andrew Henry
4373a34e3b Merge branch 'master' into 4777-plots-progress-bar 2022-06-08 17:27:40 -07:00
John Hill
815506cf17 Demote notebook tests (#5313)
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2022-06-09 00:17:41 +00:00
John Hill
72a7a334a3 Merge branch 'master' into 4777-plots-progress-bar 2022-06-08 17:10:02 -07:00
Shefali Joshi
bdb1867c73 Selection of stacked plot items and customizing them (#5198)
* Adds stacked plot inspector view provider for non subObjects

* Initialize config for telemetry objects that cannot be persisted with the config in the stacked plot
Use events to save telemetry object config changes to the stacked plot
Remove changes that weren't relevant anymore

* Ensure the telemetry objects that cannot be persisted are initialized correctly

* Fixes for selection indication in Stacked Plots
- Better theme constant colors.
- Fixed broken selectors.
- Changes also improve selection editing UI for Display and Flex Layouts.

* Ensure unique colors for stacked plot if they are auto assigned

* Fix bug hiding legend when viewing plots nested within a stacked plot

* Move stacked plots tests to it's own pluginSpec to simplify tests

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Rukmini Bose <rukmini.bose15@gmail.com>
2022-06-08 22:17:40 +00:00
Charles Hacskaylo
e288fdffea Fixes #3756 (#5192)
- Tweaks to image CSS to allow context click access.
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-06-08 21:47:51 +00:00
Jamie V
194060f30a [Flexible Layout] Unit test for rendering the view (#5308)
* flex layout render test to boost coverage
2022-06-08 13:58:49 -07:00
Alize Nguyen
47bb4c65e5 Merge branch 'master' into 4777-plots-progress-bar 2022-06-08 15:49:53 -05:00
Jesse Mazzella
45bc317a59 [e2e] Add clarity to console.error failures (#5304)
- Create a separate assert for each message

- Format the `ConsoleMessage` to provide location, line, and col numbers
2022-06-08 13:05:08 -07:00
Alize Nguyen
b3c137bab1 Moved math.max to load delay variable 2022-06-07 12:05:44 -05:00
Alize Nguyen
6785af6916 Add Math.max to timeout to handle negative inputs 2022-06-06 21:46:39 -05:00
John Hill
2e5ee80b02 Merge branch 'master' into 4777-plots-progress-bar 2022-06-06 15:41:59 -07:00
unlikelyzero
ef9512209f Update accessibility 2022-06-06 14:21:59 -07:00
unlikelyzero
383da6fc34 match load order 2022-06-06 12:06:34 -07:00
unlikelyzero
6bfd262484 fix linting errors 2022-06-06 11:16:23 -07:00
unlikelyzero
7088ec5b06 move localstorage file to test-data dir 2022-06-06 11:10:34 -07:00
John Hill
c2b6c63eee Merge branch 'master' into 4777-plots-progress-bar 2022-06-06 10:55:48 -07:00
John Hill
4e0e47a489 Merge branch 'master' into 4777-plots-progress-bar 2022-06-03 08:13:17 -07:00
Alize Nguyen
445df9ecb7 Add loading delay prop to swg 2022-06-02 20:54:05 -05:00
unlikelyzero
113c2fd5ad New pattern for controlled clock 2022-05-25 11:38:41 -07:00
Alize Nguyen
618c7648e1 Merge branch 'master' into 4777-plots-progress-bar 2022-05-24 18:40:26 -05:00
Alize Nguyen
4a0fecd008 Replace load spinner with progress bar for plots 2022-05-24 18:34:48 -05:00
Alize Nguyen
1525638df4 Add .bind(this) to stopLoading() in loadMoreData() 2022-05-24 18:17:52 -05:00
54 changed files with 2107 additions and 619 deletions

View File

@@ -4,15 +4,29 @@
const base = require('@playwright/test');
const { expect } = require('@playwright/test');
/**
* Takes a `ConsoleMessage` and returns a formatted string
* @param {import('@playwright/test').ConsoleMessage} msg
* @returns {String} formatted string with message type, text, url, and line and column numbers
*/
function consoleMessageToString(msg) {
const { url, lineNumber, columnNumber } = msg.location();
return `[${msg.type()}] ${msg.text()}
at (${url} ${lineNumber}:${columnNumber})`;
}
exports.test = base.test.extend({
page: async ({ baseURL, page }, use) => {
const messages = [];
page.on('console', msg => messages.push(`[${msg.type()}] ${msg.text()}`));
page.on('console', (msg) => messages.push(msg));
await use(page);
await expect.soft(messages.toString()).not.toContain('[error]');
messages.forEach(
msg => expect.soft(msg.type(), `Console error detected: ${consoleMessageToString(msg)}`).not.toEqual('error')
);
},
browser: async ({ playwright, browser }, use, workerInfo) => {
// Use browserless if configured
// Use browserless if configured
if (workerInfo.project.name.match(/browserless/)) {
const vBrowser = await playwright.chromium.connectOverCDP({
endpointURL: 'ws://localhost:3003'

View File

@@ -0,0 +1,22 @@
{
"cookies": [],
"origins": [
{
"origin": "http://localhost:8080",
"localStorage": [
{
"name": "tcHistory",
"value": "{\"utc\":[{\"start\":1654548551471,\"end\":1654550351471}]}"
},
{
"name": "mct",
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"527856c0-cced-4b64-bb19-f943432326d0\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1654550352296,\"modified\":1654550352296},\"527856c0-cced-4b64-bb19-f943432326d0\":{\"identifier\":{\"key\":\"527856c0-cced-4b64-bb19-f943432326d0\",\"namespace\":\"\"},\"name\":\"Unnamed Overlay Plot\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\",\"namespace\":\"\"}}],\"yAxis\":{},\"xAxis\":{}},\"modified\":1654550353356,\"location\":\"mine\",\"persisted\":1654550353357},\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\":{\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"identifier\":{\"key\":\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\"},\"modified\":1654550353350,\"location\":\"527856c0-cced-4b64-bb19-f943432326d0\",\"persisted\":1654550353350}}"
},
{
"name": "mct-tree-expanded",
"value": "[\"/browse/mine\"]"
}
]
}
]
}

View File

@@ -0,0 +1,22 @@
{
"cookies": [],
"origins": [
{
"origin": "http://localhost:8080",
"localStorage": [
{
"name": "tcHistory",
"value": "{\"utc\":[{\"start\":1654537164464,\"end\":1654538964464},{\"start\":1652301954635,\"end\":1652303754635}]}"
},
{
"name": "mct",
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1654538965703,\"modified\":1654538965703},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002},\"2d02a680-eb7e-4645-bba2-dd298f76efb8\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4291d80c-303c-4d8d-85e1-10f012b864fb\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1654538965702,\"location\":\"mine\",\"persisted\":1654538965702}}"
},
{
"name": "mct-tree-expanded",
"value": "[]"
}
]
}
]
}

View File

@@ -52,7 +52,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
]);
//Save localStorage for future test execution
await context.storageState({ path: './e2e/tests/recycled_storage.json' });
await context.storageState({ path: './e2e/test-data/recycled_local_storage.json' });
//Set object identifier from url
conditionSetUrl = await page.url();
@@ -65,7 +65,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
await browser.close();
});
//Load localStorage for subsequent tests
test.use({ storageState: './e2e/tests/recycled_storage.json' });
test.use({ storageState: './e2e/test-data/recycled_local_storage.json' });
//Begin suite of tests again localStorage
test('Condition set object properties persist in main view and inspector', async ({ page }) => {
//Navigate to baseURL with injected localStorage

View File

@@ -79,7 +79,15 @@ test.describe('Example Imagery', () => {
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
expect(imageMouseZoomedOut.height).toBeLessThan(imageMouseZoomedIn.height);
expect(imageMouseZoomedOut.width).toBeLessThan(imageMouseZoomedIn.width);
});
test('Can adjust image brightness/contrast by dragging the sliders', async ({ page }) => {
// Open the image filter menu
await page.locator('[role=toolbar] button[title="Brightness and contrast"]').click();
// Drag the brightness and contrast sliders around and assert filter values
await dragBrightnessSliderAndAssertFilterValues(page);
await dragContrastSliderAndAssertFilterValues(page);
});
test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
@@ -249,10 +257,8 @@ test('Example Imagery in Display layout', async ({ page }) => {
await page.click('text=Example Imagery');
// Clear and set Image load delay to minimum value
// FIXME: Update the value to 5000 ms when this bug is fixed.
// See: https://github.com/nasa/openmct/issues/5265
await page.locator('input[type="number"]').fill('');
await page.locator('input[type="number"]').fill('0');
await page.locator('input[type="number"]').fill('5000');
// Click text=OK
await Promise.all([
@@ -310,14 +316,7 @@ test('Example Imagery in Display layout', async ({ page }) => {
await page.locator('[data-testid=conductor-modeOption-realtime]').click();
// Zoom in on next image
await bgImageLocator.hover({trial: true});
await page.mouse.wheel(0, deltaYStep * 2);
// Wait for zoom animation to finish
await bgImageLocator.hover({trial: true});
const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
await mouseZoomIn(page);
// Click previous image button
await previousImageButton.click();
@@ -342,27 +341,14 @@ test('Example Imagery in Display layout', async ({ page }) => {
await page.locator('.pause-play').click();
//Get background-image url from background-image css prop
const backgroundImage = 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);
await assertBackgroundImageUrlFromBackgroundCss(page);
let backgroundImageUrl2;
await expect.poll(async () => {
// Verify next image has updated
let backgroundImageUrlNext = await backgroundImage.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
});
backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre
// Open the image filter menu
await page.locator('[role=toolbar] button[title="Brightness and contrast"]').click();
return backgroundImageUrl2;
}, {
message: "verify next image has updated",
timeout: 6 * 1000
}).not.toBe(backgroundImageUrl1);
console.log('backgroundImageUrl2 ' + backgroundImageUrl2);
// Drag the brightness and contrast sliders around and assert filter values
await dragBrightnessSliderAndAssertFilterValues(page);
await dragContrastSliderAndAssertFilterValues(page);
});
test.describe('Example imagery thumbnails resize in display layouts', () => {
@@ -454,15 +440,333 @@ test.describe('Example imagery thumbnails resize in display layouts', () => {
});
});
// 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('Clicking on the left arrow should pause the imagery and go to previous image');
// test.fixme('If the imagery view is in pause mode, images still come in');
// test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
test.describe('Example Imagery in Flexible layout', () => {
test.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('Example Imagery in Flexible layout', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5326'
});
// 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 the Create button
await page.click('button:has-text("Create")');
// Click text=Flexible Layout
await page.click('text=Flexible Layout');
// Assert Flexable layout
await expect(page.locator('.js-form-title')).toHaveText('Create a New Flexible Layout');
// Click text=OK
page.click('text=OK');
// Wait until Save Banner is gone
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
// Click My Items
await page.locator('form[name="mctForm"] >> text=My Items').click();
await Promise.all([
page.waitForNavigation({waitUntil: 'networkidle'})
]);
// Click My Items
await page.locator('.c-disclosure-triangle').click();
// Right click example imagery
await page.click(('text=Unnamed Example Imagery'), { button: 'right' });
// Click move
await page.locator('.icon-move').click();
// Click triangle to open sub menu
await page.locator('.c-form__section .c-disclosure-triangle').click();
// Click Flexable Layout
await page.click('.c-overlay__outer >> text=Unnamed Flexible Layout');
// Click text=OK
await page.locator('text=OK').click();
// Save template
await saveTemplate(page);
// Zoom in
await mouseZoomIn(page);
// Center the mouse pointer
const zoomedBoundingBox = await bgImageLocator.boundingBox();
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
await page.mouse.move(imageCenterX, imageCenterY);
// Pan zoom
await panZoomAndAssertImageProperties(page);
// Click previous image button
const previousImageButton = page.locator('.c-nav--prev');
await previousImageButton.click();
// Verify previous image
const selectedImage = page.locator('.selected');
await expect(selectedImage).toBeVisible();
// Click time conductor mode button
await page.locator('.c-mode-button').click();
// Select local clock mode
await page.locator('[data-testid=conductor-modeOption-realtime]').click();
// Zoom in on next image
await mouseZoomIn(page);
// Click previous image button
await previousImageButton.click();
// Verify previous image
await expect(selectedImage).toBeVisible();
const imageCount = await page.locator('.c-imagery__thumb').count();
await expect.poll(async () => {
const newImageCount = await page.locator('.c-imagery__thumb').count();
return newImageCount;
}, {
message: "verify that new images still stream in",
timeout: 6 * 1000
}).toBeGreaterThan(imageCount);
// Verify selected image is still displayed
await expect(selectedImage).toBeVisible();
// Unpause imagery
await page.locator('.pause-play').click();
//Get background-image url from background-image css prop
await assertBackgroundImageUrlFromBackgroundCss(page);
// Open the image filter menu
await page.locator('[role=toolbar] button[title="Brightness and contrast"]').click();
// Drag the brightness and contrast sliders around and assert filter values
await dragBrightnessSliderAndAssertFilterValues(page);
await dragContrastSliderAndAssertFilterValues(page);
});
});
/**
* @param {import('@playwright/test').Page} page
*/
async function saveTemplate(page) {
await page.locator('.c-button--menu.c-button--major.icon-save').click();
await page.locator('text=Save and Finish Editing').click();
}
/**
* Drag the brightness slider to max, min, and midpoint and assert the filter values
* @param {import('@playwright/test').Page} page
*/
async function dragBrightnessSliderAndAssertFilterValues(page) {
const brightnessSlider = 'div.c-image-controls__slider-wrapper.icon-brightness > input';
const brightnessBoundingBox = await page.locator(brightnessSlider).boundingBox();
const brightnessMidX = brightnessBoundingBox.x + brightnessBoundingBox.width / 2;
const brightnessMidY = brightnessBoundingBox.y + brightnessBoundingBox.height / 2;
await page.locator(brightnessSlider).hover();
await page.mouse.down();
await page.mouse.move(brightnessBoundingBox.x + brightnessBoundingBox.width, brightnessMidY);
await assertBackgroundImageBrightness(page, '500');
await page.mouse.move(brightnessBoundingBox.x, brightnessMidY);
await assertBackgroundImageBrightness(page, '0');
await page.mouse.move(brightnessMidX, brightnessMidY);
await assertBackgroundImageBrightness(page, '250');
await page.mouse.up();
}
/**
* Drag the contrast slider to max, min, and midpoint and assert the filter values
* @param {import('@playwright/test').Page} page
*/
async function dragContrastSliderAndAssertFilterValues(page) {
const contrastSlider = 'div.c-image-controls__slider-wrapper.icon-contrast > input';
const contrastBoundingBox = await page.locator(contrastSlider).boundingBox();
const contrastMidX = contrastBoundingBox.x + contrastBoundingBox.width / 2;
const contrastMidY = contrastBoundingBox.y + contrastBoundingBox.height / 2;
await page.locator(contrastSlider).hover();
await page.mouse.down();
await page.mouse.move(contrastBoundingBox.x + contrastBoundingBox.width, contrastMidY);
await assertBackgroundImageContrast(page, '500');
await page.mouse.move(contrastBoundingBox.x, contrastMidY);
await assertBackgroundImageContrast(page, '0');
await page.mouse.move(contrastMidX, contrastMidY);
await assertBackgroundImageContrast(page, '250');
await page.mouse.up();
}
/**
* Gets the filter:brightness value of the current background-image and
* asserts against an expected value
* @param {import('@playwright/test').Page} page
* @param {String} expected The expected brightness value
*/
async function assertBackgroundImageBrightness(page, expected) {
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
// Get the brightness filter value (i.e: filter: brightness(500%) => "500")
const actual = await backgroundImage.evaluate((el) => {
return el.style.filter.match(/brightness\((\d{1,3})%\)/)[1];
});
expect(actual).toBe(expected);
}
/**
* Gets the filter:contrast value of the current background-image and
* asserts against an expected value
* @param {import('@playwright/test').Page} page
* @param {String} expected The expected contrast value
*/
async function assertBackgroundImageContrast(page, expected) {
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
// Get the contrast filter value (i.e: filter: contrast(500%) => "500")
const actual = await backgroundImage.evaluate((el) => {
return el.style.filter.match(/contrast\((\d{1,3})%\)/)[1];
});
expect(actual).toBe(expected);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function assertBackgroundImageUrlFromBackgroundCss(page) {
const backgroundImage = 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);
let backgroundImageUrl2;
await expect.poll(async () => {
// Verify next image has updated
let backgroundImageUrlNext = await backgroundImage.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
});
backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre
return backgroundImageUrl2;
}, {
message: "verify next image has updated",
timeout: 6 * 1000
}).not.toBe(backgroundImageUrl1);
console.log('backgroundImageUrl2 ' + backgroundImageUrl2);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function panZoomAndAssertImageProperties(page) {
const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
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);
const bgImageLocator = page.locator(backgroundImageSelector);
const zoomedBoundingBox = await bgImageLocator.boundingBox();
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
// Pan right
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
await page.mouse.down();
await page.mouse.move(imageCenterX - 200, imageCenterY, 10);
await page.mouse.up();
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
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.mouse.down();
await page.mouse.move(imageCenterX, imageCenterY, 10);
await page.mouse.up();
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
const afterLeftPanBoundingBox = await bgImageLocator.boundingBox();
expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x);
// Pan up
await page.mouse.move(imageCenterX, imageCenterY);
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
await page.mouse.down();
await page.mouse.move(imageCenterX, imageCenterY + 200, 10);
await page.mouse.up();
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
const afterUpPanBoundingBox = await bgImageLocator.boundingBox();
expect(afterUpPanBoundingBox.y).toBeGreaterThanOrEqual(afterLeftPanBoundingBox.y);
// Pan down
await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
await page.mouse.down();
await page.mouse.move(imageCenterX, imageCenterY - 200, 10);
await page.mouse.up();
await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
const afterDownPanBoundingBox = await bgImageLocator.boundingBox();
expect(afterDownPanBoundingBox.y).toBeLessThanOrEqual(afterUpPanBoundingBox.y);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function mouseZoomIn(page) {
const bgImageLocator = await page.locator(backgroundImageSelector);
// 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;
// center the mouse pointer
await page.mouse.move(imageCenterX, imageCenterY);
// 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);
}
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');

View File

@@ -19,4 +19,4 @@
]
}
]
}
}

View File

@@ -0,0 +1,70 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*
Collection of Visual Tests set to run in a default context. The tests within this suite
are only meant to run against openmct's app.js started by `npm run start` within the
`./e2e/playwright-visual.config.js` file.
These should only use functional expect statements to verify assumptions about the state
in a test and not for functional verification of correctness. Visual tests are not supposed
to "fail" on assertions. Instead, they should be used to detect changes between builds or branches.
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
*/
const { test, expect } = require('@playwright/test');
const percySnapshot = require('@percy/playwright');
const path = require('path');
const sinon = require('sinon');
// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758
// Will replace with cy.clock() equivalent
test.beforeEach(async ({ context }) => {
await context.addInitScript({
// eslint-disable-next-line no-undef
path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js')
});
await context.addInitScript(() => {
window.__clock = sinon.useFakeTimers({
now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false, //Don't advance the clock
toFake: ["setTimeout", "nextTick"]
});
});
});
test.use({ storageState: './e2e/test-data/VisualTestData_storage.json' });
test('Visual - Overlay Plot Loading Indicator @localstorage', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
await page.locator('a:has-text("Unnamed Overlay Plot Overlay Plot")').click();
//Ensure that we're on the Unnamed Overlay Plot object
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot');
//Wait for canvas to be rendered and stop animating
await page.locator('canvas >> nth=1').hover({trial: true});
//Take snapshot of Sine Wave Generator within Overlay Plot
await percySnapshot(page, 'SineWaveInOverlayPlot');
});

View File

@@ -0,0 +1,95 @@
/*****************************************************************************
* 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 generating LocalStorage via Session Storage to be used
in some visual test suites like controlledClock.visual.spec.js. This suite should run to completion
and generate an artifact named ./e2e/test-data/VisualTestData_storage.json . This will run
on every Commit to ensure that this object still loads into tests correctly and will retain the
.e2e.spec.js suffix.
TODO: Provide additional validation of object properties as it grows.
*/
const { test } = require('../../fixtures.js');
const { expect } = require('@playwright/test');
test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
await page.locator('button:has-text("Create")').click();
// add overlay plot with defaults
await page.locator('li:has-text("Overlay Plot")').click();
// 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")');
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
//Wait for Save Banner to appear1
page.waitForSelector('.c-message-banner__message')
]);
//Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
// save (exit edit mode)
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
await page.locator('text=Save and Finish Editing').click();
// click create button
await page.locator('button:has-text("Create")').click();
// add sine wave generator with defaults
await page.locator('li:has-text("Sine Wave Generator")').click();
//Add a 5000 ms Delay
await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000');
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
await page.click('form[name="mctForm"] a:has-text("Overlay Plot")');
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
//Wait for Save Banner to appear1
page.waitForSelector('.c-message-banner__message')
]);
//Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
// focus the overlay plot
await page.locator('text=Open MCT My Items >> span').nth(3).click();
await Promise.all([
page.waitForNavigation(),
page.locator('text=Unnamed Overlay Plot').first().click()
]);
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot');
//Save localStorage for future test execution
await context.storageState({ path: './e2e/test-data/VisualTestData_storage.json' });
});

View File

@@ -32,7 +32,8 @@ define([
offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0
phase: 0,
loadDelay: 0
};
function GeneratorProvider(openmct) {
@@ -53,8 +54,9 @@ define([
'period',
'offset',
'dataRateInHz',
'randomness',
'phase',
'randomness'
'loadDelay'
];
request = request || {};

View File

@@ -116,6 +116,7 @@
var dataRateInHz = request.dataRateInHz;
var phase = request.phase;
var randomness = request.randomness;
var loadDelay = Math.max(request.loadDelay, 0);
var step = 1000 / dataRateInHz;
var nextStep = start - (start % step) + step;
@@ -133,6 +134,14 @@
});
}
if (loadDelay === 0) {
postOnRequest(message, request, data);
} else {
setTimeout(() => postOnRequest(message, request, data), loadDelay);
}
}
function postOnRequest(message, request, data) {
self.postMessage({
id: message.id,
data: request.spectra ? {

View File

@@ -132,6 +132,17 @@ define([
"telemetry",
"randomness"
]
},
{
name: "Loading Delay (ms)",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "loadDelay",
required: true,
property: [
"telemetry",
"loadDelay"
]
}
],
initialize: function (object) {
@@ -141,7 +152,8 @@ define([
offset: 0,
dataRateInHz: 1,
phase: 0,
randomness: 0
randomness: 0,
loadDelay: 0
};
}
});

View File

@@ -168,7 +168,7 @@ function getImageUrlListFromConfig(configuration) {
}
function getImageLoadDelay(domainObject) {
const imageLoadDelay = domainObject.configuration.imageLoadDelayInMilliSeconds;
const imageLoadDelay = Math.trunc(Number(domainObject.configuration.imageLoadDelayInMilliSeconds));
if (!imageLoadDelay) {
openmctInstance.objects.mutate(domainObject, 'configuration.imageLoadDelayInMilliSeconds', DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS);

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "2.0.5-SNAPSHOT",
"version": "2.1.0-SNAPSHOT",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.18.2",
@@ -25,7 +25,7 @@
"eslint": "8.13.0",
"eslint-plugin-compat": "4.0.2",
"eslint-plugin-playwright": "0.9.0",
"eslint-plugin-vue": "9.1.0",
"eslint-plugin-vue": "9.1.1",
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
"express": "4.13.1",
@@ -33,7 +33,7 @@
"git-rev-sync": "3.0.2",
"html2canvas": "1.4.1",
"imports-loader": "0.8.0",
"jasmine-core": "4.1.1",
"jasmine-core": "4.2.0",
"jsdoc": "3.5.5",
"karma": "6.3.20",
"karma-chrome-launcher": "3.1.1",
@@ -49,7 +49,7 @@
"lighthouse": "9.6.1",
"location-bar": "3.0.1",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.6.0",
"mini-css-extract-plugin": "2.6.1",
"moment": "2.29.3",
"moment-duration-format": "2.3.2",
"moment-timezone": "0.5.34",
@@ -70,7 +70,7 @@
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.68.0",
"webpack-cli": "4.9.2",
"webpack-cli": "4.10.0",
"webpack-dev-middleware": "5.3.3",
"webpack-hot-middleware": "2.25.1",
"webpack-merge": "5.8.0"
@@ -89,7 +89,7 @@
"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 branding default condition timeConductor clock exampleImagery notebook persistence performance",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance",
"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:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js",

View File

@@ -60,6 +60,7 @@
tabindex="0"
:disabled="isInvalid"
class="c-button c-button--major"
aria-label="Save"
@click="onSave"
>
{{ submitLabel }}
@@ -67,6 +68,7 @@
<button
tabindex="0"
class="c-button js-cancel-button"
aria-label="Cancel"
@click="onDismiss"
>
{{ cancelLabel }}

View File

@@ -28,6 +28,7 @@
>
<input
v-model="field"
:aria-label="model.name"
type="number"
:min="model.min"
:max="model.max"

View File

@@ -28,7 +28,6 @@
&[s-selected] {
// All frames selected while editing
border: $editFrameSelectedBorder;
box-shadow: $editFrameSelectedShdw;
.c-frame__move-bar {

View File

@@ -41,7 +41,7 @@ describe('the plugin', function () {
element.appendChild(child);
openmct.on('start', done);
openmct.startHeadless();
openmct.start(child);
});
afterEach(() => {

View File

@@ -141,6 +141,10 @@
}
}
}
[s-selected].c-fl-frame__drag-wrapper {
border: $editFrameSelectedBorder;
}
}
/****** THEIR FRAMES */

View File

@@ -22,6 +22,7 @@
import { createOpenMct, resetApplicationState } from 'utils/testing';
import FlexibleLayout from './plugin';
import Vue from 'vue';
describe('the plugin', function () {
let element;
@@ -61,7 +62,7 @@ describe('the plugin', function () {
element.appendChild(child);
openmct.on('start', done);
openmct.startHeadless();
openmct.start(child);
});
afterEach(() => {
@@ -83,6 +84,16 @@ describe('the plugin', function () {
it('provides a view', () => {
expect(flexibleLayoutViewProvider).toBeDefined();
});
it('renders a view', async () => {
const flexibleView = flexibleLayoutViewProvider.view(testViewObject, []);
flexibleView.show(child, false);
await Vue.nextTick();
const flexTitle = child.querySelector('.l-browse-bar .c-object-label__name');
expect(flexTitle).not.toBeNull();
});
});
describe('the toolbar', () => {

View File

@@ -14,6 +14,8 @@
type="range"
min="0"
max="500"
draggable="true"
@dragstart.stop.prevent
@change="notifyFiltersChanged"
@input="notifyFiltersChanged"
>
@@ -24,6 +26,8 @@
type="range"
min="0"
max="500"
draggable="true"
@dragstart.stop.prevent
@change="notifyFiltersChanged"
@input="notifyFiltersChanged"
>

View File

@@ -21,7 +21,11 @@
*****************************************************************************/
<template>
<div class="h-local-controls h-local-controls--overlay-content h-local-controls--menus-aligned c-local-controls--show-on-hover">
<div
class="h-local-controls h-local-controls--overlay-content h-local-controls--menus-aligned c-local-controls--show-on-hover"
role="toolbar"
aria-label="Image controls"
>
<imagery-view-menu-switcher
:icon-class="'icon-brightness'"
:title="'Brightness and contrast'"
@@ -173,7 +177,7 @@ export default {
this.$emit('filtersUpdated', this.filters);
},
handleResetFilters() {
this.filters = DEFAULT_FILTER_VALUES;
this.filters = {...DEFAULT_FILTER_VALUES};
this.notifyFiltersChanged();
},
limitZoomRange(factor) {

View File

@@ -403,6 +403,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);
@@ -905,8 +908,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() {

View File

@@ -68,15 +68,24 @@
overflow: hidden;
}
&__background-image {
// Actually does the image display
background-position: center;
background-repeat: no-repeat;
background-size: contain;
height: 100%; //fallback value
}
&__image {
// Present to allow Save As... image
position: absolute;
height: 100%;
width: 100%;
visibility: hidden;
display: contents;
opacity: 0;
}
&__image-save-proxy {
height: 100%;
width: 100%;
z-index: 10;
}
}

View File

@@ -70,22 +70,18 @@ export default {
this.timeContext.off('timeSystem', this.timeSystemChange);
}
},
datumIsNotValid(datum) {
if (this.imageHistory.length === 0) {
isDatumValid(datum) {
//TODO: Add a check to see if there are duplicate images (identical image timestamp and url subsequently)
if (!datum) {
return false;
}
const datumURL = this.formatImageUrl(datum);
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
// datum is not valid if it matches the last datum in history,
// or it is before the last datum in the history
const datumTimeCheck = this.parseTime(datum);
const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
const isStale = datumTimeCheck < historyTimeCheck;
const bounds = this.timeContext.bounds();
return matchesLast || isStale;
const isOutOfBounds = datumTimeCheck < bounds.start || datumTimeCheck > bounds.end;
return !isOutOfBounds;
},
formatImageUrl(datum) {
if (!datum) {
@@ -132,25 +128,19 @@ export default {
return this.requestHistory();
},
async requestHistory() {
let bounds = this.timeContext.bounds();
this.requestCount++;
const requestId = this.requestCount;
this.imageHistory = [];
const bounds = this.timeContext.bounds();
let data = await this.openmct.telemetry
const data = await this.openmct.telemetry
.request(this.domainObject, bounds) || [];
if (this.requestCount === requestId) {
let imagery = [];
data.forEach((datum) => {
let image = this.normalizeDatum(datum);
if (image) {
imagery.push(image);
}
});
//this is to optimize anything that reacts to imageHistory length
this.imageHistory = imagery;
// wait until new request resolves to do comparison
if (this.requestCount !== requestId) {
return this.imageHistory = [];
}
const imagery = data.filter(this.isDatumValid).map(this.normalizeDatum);
this.imageHistory = imagery;
},
clearData(domainObjectToClear) {
// global clearData button is accepted therefore no truthy check on inputted param
@@ -180,27 +170,29 @@ export default {
.subscribe(this.domainObject, (datum) => {
let parsedTimestamp = this.parseTime(datum);
let bounds = this.timeContext.bounds();
if (!(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end)) {
return;
}
if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
let image = this.normalizeDatum(datum);
if (image) {
this.imageHistory.push(image);
}
if (this.isDatumValid(datum)) {
this.imageHistory.push(this.normalizeDatum(datum));
}
});
},
normalizeDatum(datum) {
if (this.datumIsNotValid(datum)) {
return;
}
let image = { ...datum };
image.formattedTime = this.formatTime(datum);
image.url = this.formatImageUrl(datum);
image.time = this.parseTime(image.formattedTime);
image.imageDownloadName = this.getImageDownloadName(datum);
const formattedTime = this.formatTime(datum);
const url = this.formatImageUrl(datum);
const time = this.parseTime(formattedTime);
const imageDownloadName = this.getImageDownloadName(datum);
return image;
return {
...datum,
formattedTime,
url,
time,
imageDownloadName
};
},
getFormatter(key) {
let metadataValue = this.metadata.value(key) || { format: key };

View File

@@ -84,7 +84,6 @@ describe("The Imagery View Layouts", () => {
let telemetryPromise;
let telemetryPromiseResolve;
let cleanupFirst;
let isClearDataTriggered;
let openmct;
let parent;
@@ -205,20 +204,12 @@ describe("The Imagery View Layouts", () => {
cleanupFirst = [];
openmct = createOpenMct();
openmct.time.timeSystem('utc', {
start: START - (5 * ONE_MINUTE),
end: START + (5 * ONE_MINUTE)
});
telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve;
});
spyOn(openmct.telemetry, 'request').and.callFake(() => {
if (isClearDataTriggered) {
return [];
}
telemetryPromiseResolve(imageTelemetry);
return telemetryPromise;
@@ -337,44 +328,93 @@ describe("The Imagery View Layouts", () => {
expect(imageryView).toBeDefined();
});
describe("imagery view", () => {
describe("Clear data action for imagery", () => {
let applicableViews;
let imageryViewProvider;
let imageryView;
let componentView;
let clearDataPlugin;
let clearDataAction;
beforeEach(() => {
openmct.time.timeSystem('utc', {
start: START - (5 * ONE_MINUTE),
end: START + (5 * ONE_MINUTE)
});
applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]);
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
imageryView.show(child);
componentView = imageryView._getInstance().$children[0];
clearDataPlugin = new ClearDataPlugin(
['example.imagery'],
{indicator: true}
);
openmct.install(clearDataPlugin);
clearDataAction = openmct.actions.getAction('clear-data-action');
return Vue.nextTick();
});
it('clear data action is installed', () => {
expect(clearDataAction).toBeDefined();
});
it('on clearData action should clear data for object is selected', (done) => {
// force show the thumbnails
componentView.forceShowThumbnails = true;
Vue.nextTick(() => {
let clearDataResolve;
let telemetryRequestPromise = new Promise((resolve) => {
clearDataResolve = resolve;
});
expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0);
openmct.objectViews.on('clearData', (_domainObject) => {
return Vue.nextTick(() => {
expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0);
clearDataResolve();
});
});
clearDataAction.invoke(imageryObject);
telemetryRequestPromise.then(() => {
done();
});
});
});
});
describe("imagery view", () => {
let applicableViews;
let imageryViewProvider;
let imageryView;
beforeEach(() => {
openmct.time.timeSystem('utc', {
start: START - (5 * ONE_MINUTE),
end: START + (5 * ONE_MINUTE)
});
applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]);
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
imageryView.show(child);
imageryView._getInstance().$children[0].forceShowThumbnails = true;
return Vue.nextTick();
});
afterEach(() => {
isClearDataTriggered = false;
// openmct.time.stopClock();
// openmct.router.removeListener('change:hash', resolveFunction);
// imageryView.destroy();
});
it("on mount should show the the most recent image", (done) => {
it("on mount should show the the most recent image", () => {
//Looks like we need Vue.nextTick here so that computed properties settle down
Vue.nextTick(() => {
return Vue.nextTick(() => {
const imageInfo = getImageInfo(parent);
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
done();
});
});
@@ -422,7 +462,7 @@ describe("The Imagery View Layouts", () => {
it("should show that an image is not new", (done) => {
Vue.nextTick(() => {
const target = imageTelemetry[2].url;
const target = imageTelemetry[4].url;
parent.querySelectorAll(`img[src='${target}']`)[0].click();
Vue.nextTick(() => {
@@ -545,23 +585,32 @@ describe("The Imagery View Layouts", () => {
done();
});
it('clear data action is installed', () => {
expect(clearDataAction).toBeDefined();
});
it('on clearData action should clear data for object is selected', async (done) => {
// force show the thumbnails
imageryView._getInstance().$children[0].forceShowThumbnails = true;
it('should reset the brightness and contrast when clicking the reset button', async () => {
const viewInstance = imageryView._getInstance();
await Vue.nextTick();
expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0);
openmct.objectViews.on('clearData', async (_domainObject) => {
await Vue.nextTick();
expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0);
done();
// Save the original brightness and contrast values
const origBrightness = viewInstance.$refs.ImageryContainer.filters.brightness;
const origContrast = viewInstance.$refs.ImageryContainer.filters.contrast;
// Change them to something else (default: 100)
viewInstance.$refs.ImageryContainer.setFilters({
brightness: 200,
contrast: 200
});
// stubbed telemetry data will return empty array when true
isClearDataTriggered = true;
clearDataAction.invoke(imageryObject);
await Vue.nextTick();
// Verify that the values actually changed
expect(viewInstance.$refs.ImageryContainer.filters.brightness).toBe(200);
expect(viewInstance.$refs.ImageryContainer.filters.contrast).toBe(200);
// Click the reset button
parent.querySelector('.t-btn-reset').click();
await Vue.nextTick();
// Verify that the values were reset
expect(viewInstance.$refs.ImageryContainer.filters.brightness).toBe(origBrightness);
expect(viewInstance.$refs.ImageryContainer.filters.contrast).toBe(origContrast);
});
});

View File

@@ -26,6 +26,7 @@
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
>
<plot-legend
v-if="!isNestedWithinAStackedPlot"
:cursor-locked="!!lockHighlightPoint"
:series="seriesModels"
:highlights="highlights"
@@ -246,6 +247,18 @@ export default {
default() {
return 0;
}
},
limitLineLabels: {
type: Object,
default() {
return {};
}
},
colorPalette: {
type: Object,
default() {
return undefined;
}
}
},
data() {
@@ -266,7 +279,7 @@ export default {
isRealTime: this.openmct.time.clock() !== undefined,
loaded: false,
isTimeOutOfSync: false,
showLimitLineLabels: undefined,
showLimitLineLabels: this.limitLineLabels,
isFrozenOnMouseDown: false,
hasSameRangeValue: true,
cursorGuide: this.initCursorGuide,
@@ -274,13 +287,22 @@ export default {
};
},
computed: {
isNestedWithinAStackedPlot() {
const isNavigatedObject = this.openmct.router.isNavigatedObject([this.domainObject].concat(this.path));
return !isNavigatedObject && this.path.find((pathObject, pathObjIndex) => pathObject.type === 'telemetry.plot.stacked');
},
isFrozen() {
return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
},
plotLegendPositionClass() {
return `plot-legend-${this.config.legend.get('position')}`;
return !this.isNestedWithinAStackedPlot ? `plot-legend-${this.config.legend.get('position')}` : '';
},
plotLegendExpandedStateClass() {
if (this.isNestedWithinAStackedPlot) {
return '';
}
if (this.config.legend.get('expanded')) {
return 'plot-legend-expanded';
} else {
@@ -292,6 +314,12 @@ export default {
}
},
watch: {
limitLineLabels: {
handler(limitLineLabels) {
this.legendHoverChanged(limitLineLabels);
},
deep: true
},
initGridLines(newGridLines) {
this.gridLines = newGridLines;
},
@@ -310,6 +338,11 @@ export default {
this.config = this.getConfig();
this.legend = this.config.legend;
if (this.isNestedWithinAStackedPlot) {
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.$emit('configLoaded', configId);
}
this.listenTo(this.config.series, 'add', this.addSeries, this);
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
@@ -375,6 +408,7 @@ export default {
id: configId,
domainObject: this.domainObject,
openmct: this.openmct,
palette: this.colorPalette,
callback: (data) => {
this.data = data;
}
@@ -450,7 +484,7 @@ export default {
end: range.max,
domain: this.config.xAxis.get('key')
})
.then(this.stopLoading());
.then(this.stopLoading.bind(this));
if (purge) {
plotSeries.purgeRecordsOutsideRange(range);
}
@@ -758,6 +792,8 @@ export default {
};
});
}
this.$emit('highlights', this.highlights);
},
untrackMousePosition() {
@@ -792,6 +828,7 @@ export default {
if (this.isMouseClick()) {
this.lockHighlightPoint = !this.lockHighlightPoint;
this.$emit('lockHighlightPoint', this.lockHighlightPoint);
}
if (this.pan) {

View File

@@ -24,16 +24,16 @@
ref="plotWrapper"
class="c-plot holder holder-plot has-control-bar"
>
<div
ref="plotContainer"
class="l-view-section u-style-receiver js-style-receiver"
:class="{'s-status-timeconductor-unsynced': status && status === 'timeconductor-unsynced'}"
>
<div
<progress-bar
v-show="!!loading"
class="c-loading--overlay loading"
></div>
class="c-telemetry-table__progress-bar"
:model="{progressPerc: undefined}"
/>
<mct-plot
:init-grid-lines="gridLines"
:init-cursor-guide="cursorGuide"
@@ -49,10 +49,12 @@
import eventHelpers from './lib/eventHelpers';
import ImageExporter from '../../exporters/ImageExporter';
import MctPlot from './MctPlot.vue';
import ProgressBar from "../../ui/components/ProgressBar.vue";
export default {
components: {
MctPlot
MctPlot,
ProgressBar
},
inject: ['openmct', 'domainObject', 'path'],
props: {
@@ -89,17 +91,14 @@ export default {
destroy() {
this.stopListening();
},
exportJPG() {
const plotElement = this.$refs.plotContainer;
this.imageExporter.exportJPG(plotElement, 'plot.jpg', 'export-plot');
},
exportPNG() {
const plotElement = this.$refs.plotContainer;
this.imageExporter.exportPNG(plotElement, 'plot.png', 'export-plot');
},
setStatus(status) {
this.status = status;
},

View File

@@ -68,7 +68,8 @@ export default class PlotConfigurationModel extends Model {
this.series = new SeriesCollection({
models: options.model.series,
plot: this,
openmct: options.openmct
openmct: options.openmct,
palette: options.palette
});
if (this.get('domainObject').type === 'telemetry.plot.overlay') {

View File

@@ -39,7 +39,7 @@ export default class SeriesCollection extends Collection {
this.modelClass = PlotSeries;
this.plot = options.plot;
this.openmct = options.openmct;
this.palette = new ColorPalette();
this.palette = options.palette || new ColorPalette();
this.listenTo(this, 'add', this.onSeriesAdd, this);
this.listenTo(this, 'remove', this.onSeriesRemove, this);
this.listenTo(this.plot, 'change:domainObject', this.trackPersistedConfig, this);

View File

@@ -260,7 +260,7 @@ export default class YAxisModel extends Model {
const plotModel = this.plot.get('domainObject');
const label = plotModel.configuration?.yAxis?.label;
const sampleSeries = seriesCollection.first();
if (!sampleSeries) {
if (!sampleSeries || !sampleSeries.metadata) {
if (!label) {
this.unset('label');
}

View File

@@ -24,7 +24,10 @@
v-if="loaded"
class="js-plot-options-browse"
>
<ul class="c-tree">
<ul
v-if="!isStackedPlotObject"
class="c-tree"
>
<h2 title="Plot series display properties in this object">Plot Series</h2>
<plot-options-item
v-for="series in plotSeries"
@@ -36,7 +39,10 @@
v-if="plotSeries.length"
class="grid-properties"
>
<ul class="l-inspector-part">
<ul
v-if="!isStackedPlotObject"
class="l-inspector-part js-yaxis-properties"
>
<h2 title="Y axis settings for this object">Y Axis</h2>
<li class="grid-row">
<div
@@ -84,7 +90,10 @@
<div class="grid-cell value">{{ rangeMax }}</div>
</li>
</ul>
<ul class="l-inspector-part">
<ul
v-if="isStackedPlotObject || !isNestedWithinAStackedPlot"
class="l-inspector-part js-legend-properties"
>
<h2 title="Legend settings for this object">Legend</h2>
<li class="grid-row">
<div
@@ -144,7 +153,7 @@ export default {
components: {
PlotOptionsItem
},
inject: ['openmct', 'domainObject'],
inject: ['openmct', 'domainObject', 'path'],
data() {
return {
config: {},
@@ -167,12 +176,21 @@ export default {
plotSeries: []
};
},
computed: {
isNestedWithinAStackedPlot() {
return this.path.find((pathObject, pathObjIndex) => pathObjIndex > 0 && pathObject.type === 'telemetry.plot.stacked');
},
isStackedPlotObject() {
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
}
},
mounted() {
eventHelpers.extend(this);
this.config = this.getConfig();
this.registerListeners();
this.initConfiguration();
this.loaded = true;
},
beforeDestroy() {
this.stopListening();

View File

@@ -24,21 +24,31 @@
v-if="loaded"
class="js-plot-options-edit"
>
<ul class="c-tree">
<ul
v-if="!isStackedPlotObject"
class="c-tree"
>
<h2 title="Display properties for this object">Plot Series</h2>
<li
v-for="series in plotSeries"
:key="series.key"
>
<series-form :series="series" />
<series-form
:series="series"
@seriesUpdated="updateSeriesConfigForObject"
/>
</li>
</ul>
<y-axis-form
v-if="plotSeries.length"
v-if="plotSeries.length && !isStackedPlotObject"
class="grid-properties"
:y-axis="config.yAxis"
@seriesUpdated="updateSeriesConfigForObject"
/>
<ul class="l-inspector-part">
<ul
v-if="isStackedPlotObject || !isStackedPlotNestedObject"
class="l-inspector-part"
>
<h2 title="Legend options">Legend</h2>
<legend-form
v-if="plotSeries.length"
@@ -61,7 +71,7 @@ export default {
SeriesForm,
YAxisForm
},
inject: ['openmct', 'domainObject'],
inject: ['openmct', 'domainObject', 'path'],
data() {
return {
config: {},
@@ -69,6 +79,14 @@ export default {
loaded: false
};
},
computed: {
isStackedPlotNestedObject() {
return this.path.find((pathObject, pathObjIndex) => pathObjIndex > 0 && pathObject.type === 'telemetry.plot.stacked');
},
isStackedPlotObject() {
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
}
},
mounted() {
eventHelpers.extend(this);
this.config = this.getConfig();
@@ -98,6 +116,24 @@ export default {
resetAllSeries() {
this.plotSeries = [];
this.config.series.forEach(this.addSeries, this);
},
updateSeriesConfigForObject(config) {
const stackedPlotObject = this.path.find((pathObject) => pathObject.type === 'telemetry.plot.stacked');
let index = stackedPlotObject.configuration.series.findIndex((seriesConfig) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, config.identifier);
});
if (index < 0) {
index = stackedPlotObject.configuration.series.length;
}
const configPath = `configuration.series[${index}].${config.path}`;
this.openmct.objects.mutate(
stackedPlotObject,
configPath,
config.value
);
}
}
};

View File

@@ -13,8 +13,10 @@ export default function PlotsInspectorViewProvider(openmct) {
let object = selection[0][0].context.item;
return object
&& object.type === 'telemetry.plot.overlay';
const isOverlayPlotObject = object && object.type === 'telemetry.plot.overlay';
const isStackedPlotObject = object && object.type === 'telemetry.plot.stacked';
return isStackedPlotObject || isOverlayPlotObject;
},
view: function (selection) {
let component;

View File

@@ -0,0 +1,59 @@
import PlotOptions from "./PlotOptions.vue";
import Vue from 'vue';
export default function StackedPlotsInspectorViewProvider(openmct) {
return {
key: 'stacked-plots-inspector',
name: 'Stacked Plots Inspector View',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 0) {
return false;
}
const object = selection[0][0].context.item;
const parent = selection[0].length > 1 && selection[0][1].context.item;
const isOverlayPlotObject = object && object.type === 'telemetry.plot.overlay';
const isParentStackedPlotObject = parent && parent.type === 'telemetry.plot.stacked';
return !isOverlayPlotObject && isParentStackedPlotObject;
},
view: function (selection) {
let component;
let objectPath;
if (selection.length) {
objectPath = selection[0].map((selectionItem) => {
return selectionItem.context.item;
});
}
return {
show: function (element) {
component = new Vue({
el: element,
components: {
PlotOptions: PlotOptions
},
provide: {
openmct,
domainObject: selection[0][0].context.item,
path: objectPath
},
template: '<plot-options></plot-options>'
});
},
destroy: function () {
if (component) {
component.$destroy();
component = undefined;
}
}
};
},
priority: function () {
return 1;
}
};
}

View File

@@ -298,28 +298,45 @@ export default {
this.series.set('color', color);
const getPath = this.dynamicPathForKey('color');
const seriesColorPath = getPath(this.domainObject, this.series);
if (!this.domainObject.configuration || !this.domainObject.configuration.series) {
this.$emit('seriesUpdated', {
identifier: this.domainObject.identifier,
path: `series.color`,
value: color.asHexString()
});
} else {
const getPath = this.dynamicPathForKey('color');
const seriesColorPath = getPath(this.domainObject, this.series);
this.openmct.objects.mutate(
this.domainObject,
seriesColorPath,
color.asHexString()
);
this.openmct.objects.mutate(
this.domainObject,
seriesColorPath,
color.asHexString()
);
}
if (otherSeriesWithColor) {
otherSeriesWithColor.set('color', oldColor);
const otherSeriesColorPath = getPath(
this.domainObject,
otherSeriesWithColor
);
if (!this.domainObject.configuration || !this.domainObject.configuration.series) {
this.$emit('seriesUpdated', {
identifier: this.domainObject.identifier,
path: `series.color`,
value: oldColor.asHexString()
});
} else {
const getPath = this.dynamicPathForKey('color');
const otherSeriesColorPath = getPath(
this.domainObject,
otherSeriesWithColor
);
this.openmct.objects.mutate(
this.domainObject,
otherSeriesColorPath,
oldColor.asHexString()
);
this.openmct.objects.mutate(
this.domainObject,
otherSeriesColorPath,
oldColor.asHexString()
);
}
}
},
toggleExpanded() {
@@ -343,11 +360,19 @@ export default {
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
this.series.set(formKey, coerce(newVal, formField.coerce));
if (path) {
this.openmct.objects.mutate(
this.domainObject,
path(this.domainObject, this.series),
coerce(newVal, formField.coerce)
);
if (!this.domainObject.configuration || !this.domainObject.configuration.series) {
this.$emit('seriesUpdated', {
identifier: this.domainObject.identifier,
path: `series.${formKey}`,
value: coerce(newVal, formField.coerce)
});
} else {
this.openmct.objects.mutate(
this.domainObject,
path(this.domainObject, this.series),
coerce(newVal, formField.coerce)
);
}
}
}
},

View File

@@ -230,11 +230,19 @@ export default {
// TODO: Why do we mutate yAxis twice, once directly, once via objects.mutate? Or are they different objects?
this.yAxis.set(formKey, newVal);
if (path) {
this.openmct.objects.mutate(
this.domainObject,
path(this.domainObject, this.yAxis),
newVal
);
if (!this.domainObject.configuration || !this.domainObject.configuration.series) {
this.$emit('seriesUpdated', {
identifier: this.domainObject.identifier,
path: `yAxis.${formKey}`,
value: newVal
});
} else {
this.openmct.objects.mutate(
this.domainObject,
path(this.domainObject, this.yAxis),
newVal
);
}
}
}
}

View File

@@ -49,8 +49,8 @@
title="Cursor is point locked. Click anywhere in the plot to unlock."
></div>
<plot-legend-item-collapsed
v-for="seriesObject in series"
:key="seriesObject.keyString"
v-for="(seriesObject, seriesIndex) in series"
:key="`seriesObject.keyString-${seriesIndex}`"
:highlights="highlights"
:value-to-show-when-collapsed="legend.get('valueToShowWhenCollapsed')"
:series-object="seriesObject"
@@ -95,8 +95,8 @@
</thead>
<tbody>
<plot-legend-item-expanded
v-for="seriesObject in series"
:key="seriesObject.keyString"
v-for="(seriesObject, seriesIndex) in series"
:key="`seriesObject.keyString-${seriesIndex}`"
:series-object="seriesObject"
:highlights="highlights"
:legend="legend"

View File

@@ -41,7 +41,7 @@
<span class="plot-series-name">{{ nameWithUnit }}</span>
</div>
<div
v-show="!!highlights.length && (valueToShowWhenCollapsed !== 'none')"
v-show="!!highlights.length && (valueToShowWhenCollapsed !== 'none' && valueToShowWhenCollapsed !== 'units')"
class="plot-series-value hover-value-enabled"
:class="[{ 'cursor-hover': notNearest }, valueToDisplayWhenCollapsedClass, mctLimitStateClass]"
>

View File

@@ -26,6 +26,7 @@ import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
import PlotViewActions from "./actions/ViewActions";
import StackedPlotsInspectorViewProvider from "./inspector/StackedPlotsInspectorViewProvider";
export default function () {
return function install(openmct) {
@@ -39,9 +40,8 @@ export default function () {
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
series: [],
yAxis: {},
xAxis: {}
//series is an array of objects of type: {identifier, series: {color...}, yAxis:{}}
series: []
};
},
priority: 891
@@ -55,7 +55,11 @@ export default function () {
creatable: true,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {};
domainObject.configuration = {
series: [],
yAxis: {},
xAxis: {}
};
},
priority: 890
});
@@ -65,6 +69,7 @@ export default function () {
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
openmct.inspectorViews.addProvider(new StackedPlotsInspectorViewProvider(openmct));
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);

View File

@@ -23,7 +23,6 @@
import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} from "utils/testing";
import PlotVuePlugin from "./plugin";
import Vue from "vue";
import StackedPlot from "./stackedPlot/StackedPlot.vue";
import configStore from "./configuration/ConfigStore";
import EventEmitter from "EventEmitter";
import PlotOptions from "./inspector/PlotOptions.vue";
@@ -348,14 +347,20 @@ describe("the plugin", function () {
}
};
openmct.router.path = [testTelemetryObject];
applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === "plot-single");
plotView = plotViewProvider.view(testTelemetryObject, [testTelemetryObject]);
plotView = plotViewProvider.view(testTelemetryObject, []);
plotView.show(child, true);
return Vue.nextTick();
});
afterEach(() => {
openmct.router.path = null;
});
it("Makes only one request for telemetry on load", () => {
expect(openmct.telemetry.request).toHaveBeenCalledTimes(1);
});
@@ -523,360 +528,6 @@ describe("the plugin", function () {
});
});
describe("The stacked plot view", () => {
let testTelemetryObject;
let testTelemetryObject2;
let config;
let stackedPlotObject;
let component;
let mockComposition;
let plotViewComponentObject;
beforeEach(() => {
stackedPlotObject = {
identifier: {
namespace: "",
key: "test-plot"
},
type: "telemetry.plot.stacked",
name: "Test Stacked Plot"
};
testTelemetryObject = {
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
}
}]
},
configuration: {
objectStyles: {
staticStyle: {
style: {
backgroundColor: 'rgb(0, 200, 0)',
color: '',
border: ''
}
},
conditionSetIdentifier: {
namespace: '',
key: 'testConditionSetId'
},
selectedConditionId: 'conditionId1',
defaultConditionId: 'conditionId1',
styles: [
{
conditionId: 'conditionId1',
style: {
backgroundColor: 'rgb(0, 155, 0)',
color: '',
output: '',
border: ''
}
}
]
}
}
};
testTelemetryObject2 = {
identifier: {
namespace: "",
key: "test-object2"
},
type: "test-object",
name: "Test Object2",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key2",
name: "Some attribute2",
hints: {
range: 1
}
}, {
key: "some-other-key2",
name: "Another attribute2",
hints: {
range: 2
}
}]
}
};
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement("div");
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
StackedPlot
},
provide: {
openmct: openmct,
domainObject: stackedPlotObject,
composition: openmct.composition.get(stackedPlotObject),
path: [stackedPlotObject]
},
template: "<stacked-plot></stacked-plot>"
});
return telemetryPromise
.then(Vue.nextTick())
.then(() => {
plotViewComponentObject = component.$root.$children[0];
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
config = configStore.get(configId);
});
});
it("Renders a collapsed legend for every telemetry", () => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(1);
expect(legend[0].innerHTML).toEqual("Test Object");
});
it("Renders an expanded legend for every telemetry", () => {
let legendControl = element.querySelector(".c-plot-legend__view-control.gl-plot-legend__view-control.c-disclosure-triangle");
const clickEvent = createMouseEvent("click");
legendControl.dispatchEvent(clickEvent);
let legend = element.querySelectorAll(".plot-wrapper-expanded-legend .plot-legend-item td");
expect(legend.length).toBe(6);
});
it("Renders X-axis ticks for the telemetry object", (done) => {
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
expect(xAxisElement.length).toBe(1);
config.xAxis.set('displayRange', {
min: 0,
max: 4
});
Vue.nextTick(() => {
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(9);
done();
});
});
it("Renders Y-axis ticks for the telemetry object", (done) => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper");
expect(yAxisElement.length).toBe(1);
let ticks = yAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(6);
done();
});
});
it("Renders Y-axis options for the telemetry object", () => {
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-y-label__select");
expect(yAxisElement.length).toBe(1);
let options = yAxisElement[0].querySelectorAll("option");
expect(options.length).toBe(2);
expect(options[0].value).toBe("Some attribute");
expect(options[1].value).toBe("Another attribute");
});
it("turns on cursor Guides all telemetry objects", (done) => {
expect(plotViewComponentObject.$children[0].cursorGuide).toBeFalse();
plotViewComponentObject.$children[0].cursorGuide = true;
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[0].cursorGuide).toBeTrue();
done();
});
});
it("shows grid lines for all telemetry objects", () => {
expect(plotViewComponentObject.$children[0].gridLines).toBeTrue();
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
let visible = 0;
gridLinesContainer.forEach(el => {
if (el.style.display !== "none") {
visible++;
}
});
expect(visible).toBe(2);
});
it("hides grid lines for all telemetry objects", (done) => {
expect(plotViewComponentObject.$children[0].gridLines).toBeTrue();
plotViewComponentObject.$children[0].gridLines = false;
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[0].gridLines).toBeFalse();
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
let visible = 0;
gridLinesContainer.forEach(el => {
if (el.style.display !== "none") {
visible++;
}
});
expect(visible).toBe(0);
done();
});
});
it('plots a new series when a new telemetry object is added', (done) => {
mockComposition.emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2");
done();
});
});
it('removes plots from series when a telemetry object is removed', (done) => {
mockComposition.emit('remove', testTelemetryObject.identifier);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(0);
done();
});
});
it("Changes the label of the y axis when the option changes", (done) => {
let selectEl = element.querySelector('.gl-plot-y-label__select');
selectEl.value = 'Another attribute';
selectEl.dispatchEvent(new Event("change"));
Vue.nextTick(() => {
expect(config.yAxis.get('label')).toEqual('Another attribute');
done();
});
});
it("Renders a new series when added to one of the plots", (done) => {
mockComposition.emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2");
done();
});
});
it("Adds a new point to the plot", (done) => {
let originalLength = config.series.models[0].getSeriesData().length;
config.series.models[0].add({
utc: 2,
'some-key': 1,
'some-other-key': 2
});
Vue.nextTick(() => {
const seriesData = config.series.models[0].getSeriesData();
expect(seriesData.length).toEqual(originalLength + 1);
done();
});
});
it("updates the xscale", (done) => {
config.xAxis.set('displayRange', {
min: 0,
max: 10
});
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[0].component.$children[0].xScale.domain()).toEqual({
min: 0,
max: 10
});
done();
});
});
it("updates the yscale", (done) => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[0].component.$children[0].yScale.domain()).toEqual({
min: 10,
max: 20
});
done();
});
});
it("shows styles for telemetry objects if available", (done) => {
Vue.nextTick(() => {
let conditionalStylesContainer = element.querySelectorAll(".c-plot--stacked-container .js-style-receiver");
let hasStyles = 0;
conditionalStylesContainer.forEach(el => {
if (el.style.backgroundColor !== '') {
hasStyles++;
}
});
expect(hasStyles).toBe(1);
done();
});
});
describe('limits', () => {
it('lines are not displayed by default', () => {
let limitEl = element.querySelectorAll(".js-limit-area hr");
expect(limitEl.length).toBe(0);
});
it('lines are displayed when configuration is set to true', (done) => {
config.series.models[0].set('limitLines', true);
Vue.nextTick(() => {
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
expect(limitEl.length).toBe(4);
done();
});
});
});
});
describe('the inspector view', () => {
let component;
let viewComponentObject;
@@ -955,6 +606,7 @@ describe("the plugin", function () {
]
];
openmct.router.path = [testTelemetryObject];
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
@@ -993,6 +645,10 @@ describe("the plugin", function () {
});
});
afterEach(() => {
openmct.router.path = null;
});
describe('in view only mode', () => {
let browseOptionsEl;
let editOptionsEl;
@@ -1096,5 +752,24 @@ describe("the plugin", function () {
expect(colorSwatch).toBeDefined();
});
});
describe('limits', () => {
it('lines are not displayed by default', () => {
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
expect(limitEl.length).toBe(0);
});
xit('lines are displayed when configuration is set to true', (done) => {
config.series.models[0].set('limitLines', true);
Vue.nextTick(() => {
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
expect(limitEl.length).toBe(4);
done();
});
});
});
});
});

View File

@@ -21,21 +21,37 @@
-->
<template>
<div class="c-plot c-plot--stacked holder holder-plot has-control-bar">
<div
v-if="loaded"
class="c-plot c-plot--stacked holder holder-plot has-control-bar"
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
>
<plot-legend
:cursor-locked="!!lockHighlightPoint"
:series="seriesModels"
:highlights="highlights"
:legend="legend"
@legendHoverChanged="legendHoverChanged"
/>
<div class="l-view-section">
<stacked-plot-item
v-for="object in compositionObjects"
:key="object.id"
class="c-plot--stacked-container"
:object="object"
:child-object="object"
:options="options"
:grid-lines="gridLines"
:color-palette="colorPalette"
:cursor-guide="cursorGuide"
:show-limit-line-labels="showLimitLineLabels"
:plot-tick-width="maxTickWidth"
@plotTickWidth="onTickWidthChange"
@loadingUpdated="loadingUpdated"
@cursorGuide="onCursorGuideChange"
@gridLines="onGridLinesChange"
@lockHighlightPoint="lockHighlightPointUpdated"
@highlights="highlightsUpdated"
@configLoaded="registerSeriesListeners"
/>
</div>
</div>
@@ -43,12 +59,19 @@
<script>
import PlotConfigurationModel from '../configuration/PlotConfigurationModel';
import configStore from '../configuration/ConfigStore';
import ColorPalette from "@/ui/color/ColorPalette";
import PlotLegend from "../legend/PlotLegend.vue";
import StackedPlotItem from './StackedPlotItem.vue';
import ImageExporter from '../../../exporters/ImageExporter';
import eventHelpers from "@/plugins/plot/lib/eventHelpers";
export default {
components: {
StackedPlotItem
StackedPlotItem,
PlotLegend
},
inject: ['openmct', 'domainObject', 'composition', 'path'],
props: {
@@ -60,16 +83,35 @@ export default {
}
},
data() {
this.seriesConfig = {};
return {
hideExportButtons: false,
cursorGuide: false,
gridLines: true,
loading: false,
compositionObjects: [],
tickWidthMap: {}
tickWidthMap: {},
legend: {},
loaded: false,
lockHighlightPoint: false,
highlights: [],
seriesModels: [],
showLimitLineLabels: undefined,
colorPalette: new ColorPalette()
};
},
computed: {
plotLegendPositionClass() {
return `plot-legend-${this.config.legend.get('position')}`;
},
plotLegendExpandedStateClass() {
if (this.config.legend.get('expanded')) {
return 'plot-legend-expanded';
} else {
return 'plot-legend-collapsed';
}
},
maxTickWidth() {
return Math.max(...Object.values(this.tickWidthMap));
}
@@ -78,6 +120,13 @@ export default {
this.destroy();
},
mounted() {
eventHelpers.extend(this);
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.config = this.getConfig(configId);
this.legend = this.config.legend;
this.loaded = true;
this.imageExporter = new ImageExporter(this.openmct);
this.composition.on('add', this.addChild);
@@ -86,10 +135,29 @@ export default {
this.composition.load();
},
methods: {
getConfig(configId) {
let config = configStore.get(configId);
if (!config) {
config = new PlotConfigurationModel({
id: configId,
domainObject: this.domainObject,
openmct: this.openmct,
callback: (data) => {
this.data = data;
}
});
configStore.add(configId, config);
}
return config;
},
loadingUpdated(loaded) {
this.loading = loaded;
},
destroy() {
this.stopListening();
configStore.deleteStore(this.config.id);
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
this.composition.off('reorder', this.compositionReorder);
@@ -99,6 +167,19 @@ export default {
const id = this.openmct.objects.makeKeyString(child.identifier);
this.$set(this.tickWidthMap, id, 0);
const persistedConfig = this.domainObject.configuration.series && this.domainObject.configuration.series.find((seriesConfig) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, child.identifier);
});
if (persistedConfig === undefined) {
this.openmct.objects.mutate(
this.domainObject,
'configuration.series[' + this.compositionObjects.length + ']',
{
identifier: child.identifier
}
);
}
this.compositionObjects.push(child);
},
@@ -107,6 +188,13 @@ export default {
this.$delete(this.tickWidthMap, id);
const configIndex = this.domainObject.configuration.series.findIndex((seriesConfig) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, childIdentifier);
});
if (configIndex > -1) {
this.domainObject.configuration.series.splice(configIndex, 1);
}
const childObj = this.compositionObjects.filter((c) => {
const identifier = this.openmct.objects.makeKeyString(c.identifier);
@@ -158,6 +246,34 @@ export default {
this.$set(this.tickWidthMap, plotId, width);
},
legendHoverChanged(data) {
this.showLimitLineLabels = data;
},
lockHighlightPointUpdated(data) {
this.lockHighlightPoint = data;
},
highlightsUpdated(data) {
this.highlights = data;
},
registerSeriesListeners(configId) {
this.seriesConfig[configId] = this.getConfig(configId);
this.listenTo(this.seriesConfig[configId].series, 'add', this.addSeries, this);
this.listenTo(this.seriesConfig[configId].series, 'remove', this.removeSeries, this);
this.seriesConfig[configId].series.models.forEach(this.addSeries, this);
},
addSeries(series) {
const index = this.seriesModels.length;
this.$set(this.seriesModels, index, series);
},
removeSeries(plotSeries) {
const index = this.seriesModels.findIndex(seriesModel => this.openmct.objects.areIdsEqual(seriesModel.identifier, plotSeries.identifier));
if (index > -1) {
this.$delete(this.seriesModels, index);
}
this.stopListening(plotSeries);
},
onCursorGuideChange(cursorGuide) {
this.cursorGuide = cursorGuide === true;
},

View File

@@ -25,14 +25,17 @@
<script>
import MctPlot from '../MctPlot.vue';
import ProgressBar from "../../../ui/components/ProgressBar.vue";
import Vue from "vue";
import conditionalStylesMixin from "./mixins/objectStyles-mixin";
import configStore from "@/plugins/plot/configuration/ConfigStore";
import PlotConfigurationModel from "@/plugins/plot/configuration/PlotConfigurationModel";
export default {
mixins: [conditionalStylesMixin],
inject: ['openmct', 'domainObject', 'path'],
props: {
object: {
childObject: {
type: Object,
default() {
return {};
@@ -56,6 +59,18 @@ export default {
return true;
}
},
showLimitLineLabels: {
type: Object,
default() {
return {};
}
},
colorPalette: {
type: Object,
default() {
return undefined;
}
},
plotTickWidth: {
type: Number,
default() {
@@ -72,12 +87,22 @@ export default {
},
plotTickWidth(width) {
this.updateComponentProp('plotTickWidth', width);
},
showLimitLineLabels: {
handler(data) {
this.updateComponentProp('limitLineLabels', data);
},
deep: true
}
},
mounted() {
this.updateView();
},
beforeDestroy() {
if (this.removeSelectable) {
this.removeSelectable();
}
if (this.component) {
this.component.$destroy();
}
@@ -96,15 +121,18 @@ export default {
}
const onTickWidthChange = this.onTickWidthChange;
const onLockHighlightPointUpdated = this.onLockHighlightPointUpdated;
const onHighlightsUpdated = this.onHighlightsUpdated;
const onConfigLoaded = this.onConfigLoaded;
const onCursorGuideChange = this.onCursorGuideChange;
const onGridLinesChange = this.onGridLinesChange;
const loadingUpdated = this.loadingUpdated;
const setStatus = this.setStatus;
const openmct = this.openmct;
const object = this.object;
const path = this.path;
//If this object is not persistable, then package it with it's parent
const object = this.getPlotObject();
const getProps = this.getProps;
let viewContainer = document.createElement('div');
this.$el.append(viewContainer);
@@ -112,7 +140,8 @@ export default {
this.component = new Vue({
el: viewContainer,
components: {
MctPlot
MctPlot,
ProgressBar
},
provide: {
openmct,
@@ -123,14 +152,33 @@ export default {
return {
...getProps(),
onTickWidthChange,
onLockHighlightPointUpdated,
onHighlightsUpdated,
onConfigLoaded,
onCursorGuideChange,
onGridLinesChange,
loadingUpdated,
setStatus
setStatus,
loading: true
};
},
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><div v-show="!!loading" class="c-loading--overlay loading"></div><mct-plot :init-grid-lines="gridLines" :init-cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :options="options" @plotTickWidth="onTickWidthChange" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
methods: {
loadingUpdated(loaded) {
this.loading = loaded;
},
},
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><progress-bar v-show="loading !== false" class="c-telemetry-table__progress-bar" :model="{progressPerc: undefined}" /><mct-plot :init-grid-lines="gridLines" :init-cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :options="options" @plotTickWidth="onTickWidthChange" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
});
this.setSelection();
},
onLockHighlightPointUpdated() {
this.$emit('lockHighlightPoint', ...arguments);
},
onHighlightsUpdated() {
this.$emit('highlights', ...arguments);
},
onConfigLoaded() {
this.$emit('configLoaded', ...arguments);
},
onTickWidthChange() {
this.$emit('plotTickWidth', ...arguments);
@@ -145,19 +193,68 @@ export default {
this.status = status;
this.updateComponentProp('status', status);
},
loadingUpdated(loaded) {
this.loading = loaded;
this.updateComponentProp('loading', loaded);
setSelection() {
let childContext = {};
childContext.item = this.childObject;
this.context = childContext;
if (this.removeSelectable) {
this.removeSelectable();
}
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context);
},
getProps() {
return {
limitLineLabels: this.showLimitLineLabels,
gridLines: this.gridLines,
cursorGuide: this.cursorGuide,
plotTickWidth: this.plotTickWidth,
loading: this.loading,
options: this.options,
status: this.status
status: this.status,
colorPalette: this.colorPalette
};
},
getPlotObject() {
if (this.childObject.configuration && this.childObject.configuration.series) {
//If the object has a configuration, allow initialization of the config from it's persisted config
return this.childObject;
} else {
// If the object does not have configuration, initialize the series config with the persisted config from the stacked plot
const configId = this.openmct.objects.makeKeyString(this.childObject.identifier);
let config = configStore.get(configId);
if (!config) {
const persistedConfig = this.domainObject.configuration.series.find((seriesConfig) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, this.childObject.identifier);
});
if (persistedConfig) {
config = new PlotConfigurationModel({
id: configId,
domainObject: {
...this.childObject,
configuration: {
series: [
{
identifier: this.childObject.identifier,
...persistedConfig.series
}
],
yAxis: persistedConfig.yAxis
}
},
openmct: this.openmct,
palette: this.colorPalette,
callback: (data) => {
this.data = data;
}
});
configStore.add(configId, config);
}
}
return this.childObject;
}
}
}
};

View File

@@ -31,7 +31,7 @@ export default {
};
},
mounted() {
this.objectStyles = this.getObjectStyleForItem(this.object.configuration);
this.objectStyles = this.getObjectStyleForItem(this.childObject.configuration);
this.initObjectStyles();
},
beforeDestroy() {
@@ -62,18 +62,18 @@ export default {
this.stopListeningStyles();
}
this.stopListeningStyles = this.openmct.objects.observe(this.object, 'configuration.objectStyles', (newObjectStyle) => {
this.stopListeningStyles = this.openmct.objects.observe(this.childObject, 'configuration.objectStyles', (newObjectStyle) => {
//Updating styles in the inspector view will trigger this so that the changes are reflected immediately
this.styleRuleManager.updateObjectStyleConfig(newObjectStyle);
});
if (this.object && this.object.configuration && this.object.configuration.fontStyle) {
const { fontSize, font } = this.object.configuration.fontStyle;
if (this.childObject && this.childObject.configuration && this.childObject.configuration.fontStyle) {
const { fontSize, font } = this.childObject.configuration.fontStyle;
this.setFontSize(fontSize);
this.setFont(font);
}
this.stopListeningFontStyles = this.openmct.objects.observe(this.object, 'configuration.fontStyle', (newFontStyle) => {
this.stopListeningFontStyles = this.openmct.objects.observe(this.childObject, 'configuration.fontStyle', (newFontStyle) => {
this.setFontSize(newFontStyle.fontSize);
this.setFont(newFontStyle.font);
});

View File

@@ -0,0 +1,771 @@
/*****************************************************************************
* 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, spyOnBuiltins} from "utils/testing";
import PlotVuePlugin from "../plugin";
import Vue from "vue";
import StackedPlot from "./StackedPlot.vue";
import configStore from "../configuration/ConfigStore";
import EventEmitter from "EventEmitter";
import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
import PlotOptions from "../inspector/PlotOptions.vue";
describe("the plugin", function () {
let element;
let child;
let openmct;
let telemetryPromise;
let telemetryPromiseResolve;
let mockObjectPath;
let stackedPlotObject = {
identifier: {
namespace: "",
key: "test-plot"
},
type: "telemetry.plot.stacked",
name: "Test Stacked Plot",
configuration: {
series: []
}
};
beforeEach((done) => {
mockObjectPath = [
{
name: 'mock folder',
type: 'fake-folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
},
{
name: 'mock parent folder',
type: 'time-strip',
identifier: {
key: 'mock-parent-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'
}
];
const timeSystem = {
timeSystemKey: 'utc',
bounds: {
start: 0,
end: 4
}
};
openmct = createOpenMct(timeSystem);
telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve;
});
spyOn(openmct.telemetry, 'request').and.callFake(() => {
telemetryPromiseResolve(testTelemetry);
return telemetryPromise;
});
openmct.install(new PlotVuePlugin());
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.types.addType("test-object", {
creatable: true
});
spyOnBuiltins(["requestAnimationFrame"]);
window.requestAnimationFrame.and.callFake((callBack) => {
callBack();
});
openmct.router.path = [stackedPlotObject];
openmct.on("start", done);
openmct.startHeadless();
});
afterEach((done) => {
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
configStore.deleteAll();
resetApplicationState(openmct).then(done).catch(done);
});
afterAll(() => {
openmct.router.path = null;
});
describe("the plot views", () => {
it("provides a stacked plot view for objects with telemetry", () => {
const testTelemetryObject = {
id: "test-object",
type: "telemetry.plot.stacked",
telemetry: {
values: [{
key: "some-key"
}]
}
};
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-stacked");
expect(plotView).toBeDefined();
});
});
describe("The stacked plot view", () => {
let testTelemetryObject;
let testTelemetryObject2;
let config;
let component;
let mockComposition;
let plotViewComponentObject;
afterAll(() => {
openmct.router.path = null;
});
beforeEach(() => {
testTelemetryObject = {
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
}
}]
},
configuration: {
objectStyles: {
staticStyle: {
style: {
backgroundColor: 'rgb(0, 200, 0)',
color: '',
border: ''
}
},
conditionSetIdentifier: {
namespace: '',
key: 'testConditionSetId'
},
selectedConditionId: 'conditionId1',
defaultConditionId: 'conditionId1',
styles: [
{
conditionId: 'conditionId1',
style: {
backgroundColor: 'rgb(0, 155, 0)',
color: '',
output: '',
border: ''
}
}
]
}
}
};
testTelemetryObject2 = {
identifier: {
namespace: "",
key: "test-object2"
},
type: "test-object",
name: "Test Object2",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key2",
name: "Some attribute2",
hints: {
range: 1
}
}, {
key: "some-other-key2",
name: "Another attribute2",
hints: {
range: 2
}
}]
}
};
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement("div");
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
StackedPlot
},
provide: {
openmct: openmct,
domainObject: stackedPlotObject,
composition: openmct.composition.get(stackedPlotObject),
path: [stackedPlotObject]
},
template: "<stacked-plot></stacked-plot>"
});
return telemetryPromise
.then(Vue.nextTick())
.then(() => {
plotViewComponentObject = component.$root.$children[0];
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
config = configStore.get(configId);
});
});
it("Renders a collapsed legend for every telemetry", () => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(1);
expect(legend[0].innerHTML).toEqual("Test Object");
});
it("Renders an expanded legend for every telemetry", () => {
let legendControl = element.querySelector(".c-plot-legend__view-control.gl-plot-legend__view-control.c-disclosure-triangle");
const clickEvent = createMouseEvent("click");
legendControl.dispatchEvent(clickEvent);
let legend = element.querySelectorAll(".plot-wrapper-expanded-legend .plot-legend-item td");
expect(legend.length).toBe(6);
});
it("Renders X-axis ticks for the telemetry object", (done) => {
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
expect(xAxisElement.length).toBe(1);
config.xAxis.set('displayRange', {
min: 0,
max: 4
});
Vue.nextTick(() => {
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(9);
done();
});
});
it("Renders Y-axis ticks for the telemetry object", (done) => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper");
expect(yAxisElement.length).toBe(1);
let ticks = yAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(6);
done();
});
});
it("Renders Y-axis options for the telemetry object", () => {
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-y-label__select");
expect(yAxisElement.length).toBe(1);
let options = yAxisElement[0].querySelectorAll("option");
expect(options.length).toBe(2);
expect(options[0].value).toBe("Some attribute");
expect(options[1].value).toBe("Another attribute");
});
it("turns on cursor Guides all telemetry objects", (done) => {
expect(plotViewComponentObject.cursorGuide).toBeFalse();
plotViewComponentObject.cursorGuide = true;
Vue.nextTick(() => {
let childCursorGuides = element.querySelectorAll(".c-cursor-guide--v");
expect(childCursorGuides.length).toBe(1);
done();
});
});
it("shows grid lines for all telemetry objects", () => {
expect(plotViewComponentObject.gridLines).toBeTrue();
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
let visible = 0;
gridLinesContainer.forEach(el => {
if (el.style.display !== "none") {
visible++;
}
});
expect(visible).toBe(2);
});
it("hides grid lines for all telemetry objects", (done) => {
expect(plotViewComponentObject.gridLines).toBeTrue();
plotViewComponentObject.gridLines = false;
Vue.nextTick(() => {
expect(plotViewComponentObject.gridLines).toBeFalse();
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
let visible = 0;
gridLinesContainer.forEach(el => {
if (el.style.display !== "none") {
visible++;
}
});
expect(visible).toBe(0);
done();
});
});
it('plots a new series when a new telemetry object is added', (done) => {
mockComposition.emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2");
done();
});
});
it('removes plots from series when a telemetry object is removed', (done) => {
mockComposition.emit('remove', testTelemetryObject.identifier);
Vue.nextTick(() => {
expect(plotViewComponentObject.compositionObjects.length).toBe(0);
done();
});
});
it("Changes the label of the y axis when the option changes", (done) => {
let selectEl = element.querySelector('.gl-plot-y-label__select');
selectEl.value = 'Another attribute';
selectEl.dispatchEvent(new Event("change"));
Vue.nextTick(() => {
expect(config.yAxis.get('label')).toEqual('Another attribute');
done();
});
});
it("Renders a new series when added to one of the plots", (done) => {
mockComposition.emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2");
done();
});
});
it("Adds a new point to the plot", (done) => {
let originalLength = config.series.models[0].getSeriesData().length;
config.series.models[0].add({
utc: 2,
'some-key': 1,
'some-other-key': 2
});
Vue.nextTick(() => {
const seriesData = config.series.models[0].getSeriesData();
expect(seriesData.length).toEqual(originalLength + 1);
done();
});
});
it("updates the xscale", (done) => {
config.xAxis.set('displayRange', {
min: 0,
max: 10
});
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[1].component.$children[0].xScale.domain()).toEqual({
min: 0,
max: 10
});
done();
});
});
it("updates the yscale", (done) => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
expect(plotViewComponentObject.$children[1].component.$children[0].yScale.domain()).toEqual({
min: 10,
max: 20
});
done();
});
});
it("shows styles for telemetry objects if available", (done) => {
Vue.nextTick(() => {
let conditionalStylesContainer = element.querySelectorAll(".c-plot--stacked-container .js-style-receiver");
let hasStyles = 0;
conditionalStylesContainer.forEach(el => {
if (el.style.backgroundColor !== '') {
hasStyles++;
}
});
expect(hasStyles).toBe(1);
done();
});
});
});
describe('the stacked plot inspector view', () => {
let component;
let viewComponentObject;
let mockComposition;
let testTelemetryObject;
let selection;
let config;
beforeEach((done) => {
testTelemetryObject = {
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: {
type: 'telemetry.plot.stacked',
identifier: {
key: 'some-stacked-plot',
namespace: ''
},
configuration: {
series: []
}
}
}
}
]
];
openmct.router.path = [testTelemetryObject];
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
const configId = openmct.objects.makeKeyString(selection[0][0].context.item.identifier);
config = new PlotConfigurationModel({
id: configId,
domainObject: selection[0][0].context.item,
openmct: openmct
});
configStore.add(configId, config);
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
PlotOptions
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item]
},
template: '<plot-options/>'
});
Vue.nextTick(() => {
viewComponentObject = component.$root.$children[0];
done();
});
});
afterEach(() => {
openmct.router.path = null;
});
describe('in view only mode', () => {
let browseOptionsEl;
beforeEach(() => {
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
});
it('shows legend properties', () => {
const legendPropertiesEl = browseOptionsEl.querySelector('.js-legend-properties');
expect(legendPropertiesEl).not.toBeNull();
});
it('does not show series properties', () => {
const seriesPropertiesEl = browseOptionsEl.querySelector('.c-tree');
expect(seriesPropertiesEl).toBeNull();
});
it('does not show yaxis properties', () => {
const yAxisPropertiesEl = browseOptionsEl.querySelector('.js-yaxis-properties');
expect(yAxisPropertiesEl).toBeNull();
});
});
});
describe('inspector view of stacked plot child', () => {
let component;
let viewComponentObject;
let mockComposition;
let testTelemetryObject;
let selection;
let config;
beforeEach((done) => {
testTelemetryObject = {
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.overlay",
configuration: {
series: [
{
identifier: {
key: "test-object",
namespace: ''
}
}
]
},
composition: []
}
}
},
{
context: {
item: {
type: 'telemetry.plot.stacked',
identifier: {
key: 'some-stacked-plot',
namespace: ''
},
configuration: {
series: []
}
}
}
}
]
];
openmct.router.path = [testTelemetryObject];
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
const configId = openmct.objects.makeKeyString(selection[0][0].context.item.identifier);
config = new PlotConfigurationModel({
id: configId,
domainObject: selection[0][0].context.item,
openmct: openmct
});
configStore.add(configId, config);
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
PlotOptions
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item, selection[0][1].context.item]
},
template: '<plot-options/>'
});
Vue.nextTick(() => {
viewComponentObject = component.$root.$children[0];
done();
});
});
afterEach(() => {
openmct.router.path = null;
});
describe('in view only mode', () => {
let browseOptionsEl;
beforeEach(() => {
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
});
it('hides legend properties', () => {
const legendPropertiesEl = browseOptionsEl.querySelector('.js-legend-properties');
expect(legendPropertiesEl).toBeNull();
});
it('shows series properties', () => {
const seriesPropertiesEl = browseOptionsEl.querySelector('.c-tree');
expect(seriesPropertiesEl).not.toBeNull();
});
it('shows yaxis properties', () => {
const yAxisPropertiesEl = browseOptionsEl.querySelector('.js-yaxis-properties');
expect(yAxisPropertiesEl).not.toBeNull();
});
});
});
});

View File

@@ -144,7 +144,7 @@
<progress-bar
v-if="loading"
class="c-telemetry-table__progress-bar"
:model="progressLoad"
:model="{progressPerc: undefined}"
/>
<!-- Headers table -->
@@ -385,11 +385,6 @@ export default {
};
},
computed: {
progressLoad() {
return {
progressPerc: undefined
};
},
dropTargetStyle() {
return {
top: this.$refs.headersTable.offsetTop + 'px',

View File

@@ -164,7 +164,7 @@ $borderMissing: 1px dashed $colorAlert !important;
$editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor;
$editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIColorHov: pullForward(saturate($uiColor, 10%), 10%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #344b8d; // Base color, toolbar bg
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
$editUIBaseColorFg: #ffffff; // Toolbar button icon colors, etc.
@@ -178,11 +178,10 @@ $editFrameColor: $browseFrameColor; // Solid or dotted border applied to non-sel
$editFrameBorder: 1px dotted $editFrameColor;
$editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects
$editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ccc; // Border of selected frames
$editFrameColorSelected: #ffefc2; // Border of selected frames while editing
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
$editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@@ -191,6 +190,7 @@ $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); //
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
$editFrameSelectedBorder: $editMarqueeBorder; // Selected frame element
// Icons
$colorIconAlias: #4af6f3;

View File

@@ -168,7 +168,7 @@ $borderMissing: 1px dashed $colorAlert !important;
$editUIColor: $uiColor; // Base color
$editUIColorBg: $editUIColor;
$editUIColorFg: #fff;
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
$editUIColorHov: pullForward(saturate($uiColor, 10%), 10%); // Hover color when $editUIColor is applied as a base color
$editUIBaseColor: #344b8d; // Base color, toolbar bg
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
$editUIBaseColorFg: #ffffff; // Toolbar button icon colors, etc.
@@ -182,11 +182,10 @@ $editFrameColor: $browseFrameColor; // Solid or dotted border applied to non-sel
$editFrameBorder: 1px dotted $editFrameColor;
$editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects
$editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #ccc; // Border of selected frames
$editFrameColorSelected: #ffefc2; // Border of selected frames while editing
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
$editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@@ -195,6 +194,7 @@ $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); //
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
$editFrameSelectedBorder: $editMarqueeBorder; // Selected frame element
// Icons
$colorIconAlias: #4af6f3;

View File

@@ -178,11 +178,10 @@ $editFrameColor: $browseFrameColor; // Solid or dotted border applied to non-sel
$editFrameBorder: 1px dotted $editFrameColor;
$editFrameColorHov: $editUIColor; // Solid border hover on frames; hover should not be applied to selected objects
$editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
$editFrameColorSelected: #333; // Border of selected frames
$editFrameColorSelected: #ff7c00; // Border of selected frames
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 5px 2px;
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
@@ -191,6 +190,7 @@ $editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); //
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
$editFrameMovebarH: 10px; // Height of move bar in layout frame
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
$editFrameSelectedBorder: 1px dashed $editMarqueeBorder; // Selected frame element
// Icons
$colorIconAlias: #4af6f3;

View File

@@ -283,34 +283,32 @@ $bg-icon-info: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3
$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-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-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='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-96zM256 384L64 256l192-128 192 128z'/%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' xml:space='preserve'%3e%3cpath 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");
@@ -325,5 +323,5 @@ $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");
$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");

View File

@@ -241,12 +241,10 @@
.bg-icon-tabular { @include glyphBg($bg-icon-tabular); }
.bg-icon-tabular-lad { @include glyphBg($bg-icon-tabular-lad); }
.bg-icon-tabular-lad-set { @include glyphBg($bg-icon-tabular-lad-set); }
.bg-icon-tabular-realtime { @include glyphBg($bg-icon-tabular-realtime); }
.bg-icon-tabular-scrolling { @include glyphBg($bg-icon-tabular-scrolling); }
.bg-icon-telemetry { @include glyphBg($bg-icon-telemetry); }
.bg-icon-timeline { @include glyphBg($bg-icon-timeline); }
.bg-icon-timer { @include glyphBg($bg-icon-timer); }
.bg-icon-topic { @include glyphBg($bg-icon-topic); }
.bg-icon-box-with-dashed-lines { @include glyphBg($bg-icon-box-with-dashed-lines); }
.bg-icon-summary-widget { @include glyphBg($bg-icon-summary-widget); }
.bg-icon-notebook { @include glyphBg($bg-icon-notebook); }
@@ -264,5 +262,5 @@
.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); }
.bg-icon-notebook-shift-log { @include glyphBg($bg-icon-notebook-shift-log); }

View File

@@ -65,7 +65,6 @@ mct-plot {
.c-plot {
@include abs($mainViewPad);
display: flex;
flex-direction: column;
overflow: hidden;
min-height: $plotMinH;
@@ -83,11 +82,18 @@ mct-plot {
}
.c-plot--stacked-container {
border: 1px solid transparent;
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-height: $plotMinH;
overflow: hidden;
&[s-selected] {
.is-editing & {
border: $editMarqueeBorder;
}
}
}
;

View File

@@ -26,9 +26,10 @@ const config = {
maelstromTheme: './src/plugins/themes/maelstrom-theme.scss'
},
output: {
globalObject: "this",
globalObject: 'this',
filename: '[name].js',
library: '[name]',
path: path.resolve(__dirname, 'dist'),
library: 'openmct',
libraryTarget: 'umd',
publicPath: '',
hashFunction: 'xxhash64',

View File

@@ -6,6 +6,17 @@ const webpack = require('webpack');
module.exports = merge(common, {
mode: 'development',
watchOptions: {
// Since we use require.context, webpack is watching the entire directory.
// We need to exclude any files we don't want webpack to watch.
// See: https://webpack.js.org/configuration/watch/#watchoptions-exclude
ignored: [
'**/{node_modules,dist,docs,e2e}', // All files in node_modules, dist, docs, e2e,
'**/{*.yml,Procfile,webpack*.js,babel*.js,package*.json,tsconfig.json,jsdoc.json}', // Config files
'**/*.{sh,md,png,ttf,woff,svg}', // Non source files
'**/.*' // dotfiles and dotfolders
]
},
resolve: {
alias: {
"vue": path.join(__dirname, "node_modules/vue/dist/vue.js")

View File

@@ -16,5 +16,5 @@ module.exports = merge(common, {
__OPENMCT_ROOT_RELATIVE__: '""'
})
],
devtool: 'source-map'
devtool: 'eval-source-map'
});