mirror of
https://github.com/nasa/openmct.git
synced 2025-07-03 21:38:13 +00:00
Compare commits
26 Commits
eval-sourc
...
mct5221
Author | SHA1 | Date | |
---|---|---|---|
a31e5e591a | |||
dfa0b54c7c | |||
a040bb30c2 | |||
0a2e0a4e65 | |||
e8df2bd437 | |||
ccd2a8b64c | |||
2bd35bb2a5 | |||
28dbd724d6 | |||
5a1c329c66 | |||
00a5cbd2fd | |||
a2d698d5c1 | |||
5685a5b393 | |||
164f39695e | |||
c384cf67da | |||
417b225505 | |||
e5e93f311c | |||
39e6d9c90c | |||
60d021ef82 | |||
59880955a2 | |||
b51ed7e844 | |||
7bbaec4006 | |||
c0f24b3925 | |||
4e79725897 | |||
0674c9fc33 | |||
de1b877954 | |||
4db2f547d9 |
22
e2e/test-data/VisualTestData_storage.json
Normal file
22
e2e/test-data/VisualTestData_storage.json
Normal 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\"]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
22
e2e/test-data/recycled_local_storage.json
Normal file
22
e2e/test-data/recycled_local_storage.json
Normal 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": "[]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -40,9 +40,6 @@ test.describe('Move item tests', () => {
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder1);
|
||||
|
||||
// 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(),
|
||||
@ -59,9 +56,6 @@ test.describe('Move item tests', () => {
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder2);
|
||||
|
||||
// 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(),
|
||||
@ -97,9 +91,6 @@ test.describe('Move item tests', () => {
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable);
|
||||
|
||||
// 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 page.locator('text=OK').click();
|
||||
|
||||
// Finish editing and save Telemetry Table
|
||||
|
@ -28,9 +28,7 @@ const { test } = require('../../fixtures.js');
|
||||
const { expect } = require('@playwright/test');
|
||||
const path = require('path');
|
||||
|
||||
// https://github.com/nasa/openmct/issues/4323#issuecomment-1067282651
|
||||
|
||||
test.describe('Persistence operations', () => {
|
||||
test.describe('Persistence operations @addInit', () => {
|
||||
// add non persistable root item
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
@ -38,6 +36,10 @@ test.describe('Persistence operations', () => {
|
||||
});
|
||||
|
||||
test('Persistability should be respected in the create form location field', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/4323'
|
||||
});
|
||||
// Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
|
@ -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
|
||||
|
30
e2e/tests/plugins/gauge/addInitGauge.js
Normal file
30
e2e/tests/plugins/gauge/addInitGauge.js
Normal file
@ -0,0 +1,30 @@
|
||||
/*****************************************************************************
|
||||
* 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 will be called from the test suite with
|
||||
// await page.addInitScript({ path: path.join(__dirname, 'addInitGauge.js') });
|
||||
// it will install the Gauge since it is not installed by default
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const openmct = window.openmct;
|
||||
openmct.install(openmct.plugins.Gauge());
|
||||
});
|
@ -41,9 +41,6 @@ test.describe('Example Imagery', () => {
|
||||
// Click text=Example Imagery
|
||||
await page.click('text=Example Imagery');
|
||||
|
||||
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
|
||||
await page.click('form[name="mctForm"] a:has-text("My Items")');
|
||||
|
||||
// Click text=OK
|
||||
await Promise.all([
|
||||
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||
@ -331,38 +328,12 @@ test('Example Imagery in Display layout', async ({ page }) => {
|
||||
|
||||
return newImageCount;
|
||||
}, {
|
||||
message: "verify that new images still stream in",
|
||||
message: "verify that old images are discarded",
|
||||
timeout: 6 * 1000
|
||||
}).toBeGreaterThan(imageCount);
|
||||
}).toBe(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
|
||||
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);
|
||||
});
|
||||
|
||||
test.describe('Example imagery thumbnails resize in display layouts', () => {
|
||||
|
@ -125,17 +125,17 @@ async function openContextMenuRestrictedNotebook(page) {
|
||||
return;
|
||||
}
|
||||
|
||||
test.describe('Restricted Notebook', () => {
|
||||
test.describe('Restricted Notebook @addInit', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await startAndAddNotebookObject(page);
|
||||
});
|
||||
|
||||
test('Can be renamed', async ({ page }) => {
|
||||
test('Can be renamed @addInit', async ({ page }) => {
|
||||
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText(`Unnamed ${CUSTOM_NAME}`);
|
||||
});
|
||||
|
||||
test('Can be deleted if there are no locked pages', async ({ page }) => {
|
||||
test('Can be deleted if there are no locked pages @addInit', async ({ page }) => {
|
||||
await openContextMenuRestrictedNotebook(page);
|
||||
|
||||
const menuOptions = page.locator('.c-menu ul');
|
||||
@ -158,7 +158,7 @@ test.describe('Restricted Notebook', () => {
|
||||
expect.soft(await restrictedNotebookTreeObject.count()).toEqual(0);
|
||||
});
|
||||
|
||||
test('Can be locked if at least one page has one entry', async ({ page }) => {
|
||||
test('Can be locked if at least one page has one entry @addInit', async ({ page }) => {
|
||||
|
||||
await enterTextEntry(page);
|
||||
|
||||
@ -168,7 +168,7 @@ test.describe('Restricted Notebook', () => {
|
||||
|
||||
});
|
||||
|
||||
test.describe('Restricted Notebook with at least one entry and with the page locked', () => {
|
||||
test.describe('Restricted Notebook with at least one entry and with the page locked @addInit', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await startAndAddNotebookObject(page);
|
||||
@ -179,7 +179,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
||||
await page.locator('button.c-notebook__toggle-nav-button').click();
|
||||
});
|
||||
|
||||
test('Locked page should now be in a locked state', async ({ page }) => {
|
||||
test('Locked page should now be in a locked state @addInit', async ({ page }) => {
|
||||
// main lock message on page
|
||||
const lockMessage = page.locator('text=This page has been committed and cannot be modified or removed');
|
||||
expect.soft(await lockMessage.count()).toEqual(1);
|
||||
@ -197,7 +197,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
||||
|
||||
});
|
||||
|
||||
test('Can still: add page, rename, add entry, delete unlocked pages', async ({ page }) => {
|
||||
test('Can still: add page, rename, add entry, delete unlocked pages @addInit', async ({ page }) => {
|
||||
// Click text=Page Add >> button
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
@ -237,14 +237,14 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Restricted Notebook with a page locked and with an embed', () => {
|
||||
test.describe('Restricted Notebook with a page locked and with an embed @addInit', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await startAndAddNotebookObject(page);
|
||||
await dragAndDropEmbed(page);
|
||||
});
|
||||
|
||||
test('Allows embeds to be deleted if page unlocked', async ({ page }) => {
|
||||
test('Allows embeds to be deleted if page unlocked @addInit', async ({ page }) => {
|
||||
// Click .c-ne__embed__name .c-popup-menu-button
|
||||
await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu
|
||||
|
||||
@ -252,7 +252,7 @@ test.describe('Restricted Notebook with a page locked and with an embed', () =>
|
||||
await expect.soft(embedMenu).toContainText('Remove This Embed');
|
||||
});
|
||||
|
||||
test('Disallows embeds to be deleted if page locked', async ({ page }) => {
|
||||
test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => {
|
||||
await lockPage(page);
|
||||
// Click .c-ne__embed__name .c-popup-menu-button
|
||||
await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu
|
||||
|
146
e2e/tests/plugins/notebook/tags.e2e.spec.js
Normal file
146
e2e/tests/plugins/notebook/tags.e2e.spec.js
Normal file
@ -0,0 +1,146 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
This test suite is dedicated to tests which verify form functionality.
|
||||
*/
|
||||
|
||||
const { expect } = require('@playwright/test');
|
||||
const { test } = require('../../../fixtures');
|
||||
|
||||
/**
|
||||
* Creates a notebook object and adds an entry.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function createNotebookAndEntry(page) {
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
// Click button:has-text("Create")
|
||||
await page.locator('button:has-text("Create")').click();
|
||||
// Click li:has-text("Notebook")
|
||||
await page.locator('li:has-text("Notebook")').click();
|
||||
// Click button:has-text("OK")
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('button:has-text("OK")').click()
|
||||
]);
|
||||
|
||||
// Click text=To start a new entry, click here or drag and drop any object
|
||||
await page.locator('text=To start a new entry, click here or drag and drop any object').click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a notebook object, adds an entry, and adds a tag.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function createNotebookEntryAndTags(page) {
|
||||
await createNotebookAndEntry(page);
|
||||
// Click text=To start a new entry, click here or drag and drop any object
|
||||
await page.locator('button:has-text("Add Tag")').click();
|
||||
|
||||
// Click [placeholder="Type to select tag"]
|
||||
await page.locator('[placeholder="Type to select tag"]').click();
|
||||
// Click text=Driving
|
||||
await page.locator('text=Driving').click();
|
||||
|
||||
// Click button:has-text("Add Tag")
|
||||
await page.locator('button:has-text("Add Tag")').click();
|
||||
// Click [placeholder="Type to select tag"]
|
||||
await page.locator('[placeholder="Type to select tag"]').click();
|
||||
// Click text=Science
|
||||
await page.locator('text=Science').click();
|
||||
}
|
||||
|
||||
test.describe('Tagging in Notebooks', () => {
|
||||
test('Can load tags', async ({ page }) => {
|
||||
await createNotebookAndEntry(page);
|
||||
// Click text=To start a new entry, click here or drag and drop any object
|
||||
await page.locator('button:has-text("Add Tag")').click();
|
||||
|
||||
// Click [placeholder="Type to select tag"]
|
||||
await page.locator('[placeholder="Type to select tag"]').click();
|
||||
|
||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling");
|
||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Driving");
|
||||
});
|
||||
test('Can add tags', async ({ page }) => {
|
||||
await createNotebookEntryAndTags(page);
|
||||
|
||||
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving");
|
||||
|
||||
// Click button:has-text("Add Tag")
|
||||
await page.locator('button:has-text("Add Tag")').click();
|
||||
// Click [placeholder="Type to select tag"]
|
||||
await page.locator('[placeholder="Type to select tag"]').click();
|
||||
|
||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Driving");
|
||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling");
|
||||
});
|
||||
test('Can search for tags', async ({ page }) => {
|
||||
await createNotebookEntryAndTags(page);
|
||||
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Driving");
|
||||
|
||||
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc');
|
||||
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Driving");
|
||||
|
||||
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq');
|
||||
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Can delete tags', async ({ page }) => {
|
||||
await createNotebookEntryAndTags(page);
|
||||
await page.locator('[aria-label="Notebook Entries"]').click();
|
||||
// Delete Driving
|
||||
await page.locator('text=Science Driving Add Tag >> button').nth(1).click();
|
||||
|
||||
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Notebook Entry"]')).not.toContainText("Driving");
|
||||
|
||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving");
|
||||
});
|
||||
test('Tags persist across reload', async ({ page }) => {
|
||||
await createNotebookEntryAndTags(page);
|
||||
//Go to baseURL
|
||||
await page.reload();
|
||||
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving");
|
||||
});
|
||||
});
|
@ -39,9 +39,6 @@ test.describe('Telemetry Table', () => {
|
||||
await page.locator(createButton).click();
|
||||
await page.locator('li:has-text("Telemetry Table")').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(),
|
||||
@ -59,9 +56,6 @@ test.describe('Telemetry Table', () => {
|
||||
// add Sine Wave Generator with defaults
|
||||
await page.locator('li:has-text("Sine Wave Generator")').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(),
|
||||
|
184
e2e/tests/plugins/timer/timer.e2e.spec.js
Normal file
184
e2e/tests/plugins/timer/timer.e2e.spec.js
Normal file
@ -0,0 +1,184 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
const { test } = require('../../../fixtures.js');
|
||||
const { expect } = require('@playwright/test');
|
||||
|
||||
test.describe('Timer', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
// Click 'Timer'
|
||||
await page.click('text=Timer');
|
||||
|
||||
// Click text=OK
|
||||
await Promise.all([
|
||||
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||
page.click('text=OK')
|
||||
]);
|
||||
|
||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer');
|
||||
});
|
||||
|
||||
test('Can perform actions on the Timer', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/4313'
|
||||
});
|
||||
|
||||
await test.step("From the tree context menu", async () => {
|
||||
await triggerTimerContextMenuAction(page, 'Start');
|
||||
await triggerTimerContextMenuAction(page, 'Pause');
|
||||
await triggerTimerContextMenuAction(page, 'Restart at 0');
|
||||
await triggerTimerContextMenuAction(page, 'Stop');
|
||||
});
|
||||
|
||||
await test.step("From the 3dot menu", async () => {
|
||||
await triggerTimer3dotMenuAction(page, 'Start');
|
||||
await triggerTimer3dotMenuAction(page, 'Pause');
|
||||
await triggerTimer3dotMenuAction(page, 'Restart at 0');
|
||||
await triggerTimer3dotMenuAction(page, 'Stop');
|
||||
});
|
||||
|
||||
await test.step("From the object view", async () => {
|
||||
await triggerTimerViewAction(page, 'Start');
|
||||
await triggerTimerViewAction(page, 'Pause');
|
||||
await triggerTimerViewAction(page, 'Restart at 0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Actions that can be performed on a timer from context menus.
|
||||
* @typedef {'Start' | 'Stop' | 'Pause' | 'Restart at 0'} TimerAction
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actions that can be performed on a timer from the object view.
|
||||
* @typedef {'Start' | 'Pause' | 'Restart at 0'} TimerViewAction
|
||||
*/
|
||||
|
||||
/**
|
||||
* Open the timer context menu from the object tree.
|
||||
* Expands the 'My Items' folder if it is not already expanded.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function openTimerContextMenu(page) {
|
||||
const myItemsFolder = page.locator('text=Open MCT My Items >> span').nth(3);
|
||||
const className = await myItemsFolder.getAttribute('class');
|
||||
if (!className.includes('c-disclosure-triangle--expanded')) {
|
||||
await myItemsFolder.click();
|
||||
}
|
||||
|
||||
await page.locator(`a:has-text("Unnamed Timer")`).click({
|
||||
button: 'right'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a timer action from the tree context menu
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {TimerAction} action
|
||||
*/
|
||||
async function triggerTimerContextMenuAction(page, action) {
|
||||
const menuAction = `.c-menu ul li >> text="${action}"`;
|
||||
await openTimerContextMenu(page);
|
||||
await page.locator(menuAction).click();
|
||||
assertTimerStateAfterAction(page, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a timer action from the 3dot menu
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {TimerAction} action
|
||||
*/
|
||||
async function triggerTimer3dotMenuAction(page, action) {
|
||||
const menuAction = `.c-menu ul li >> text="${action}"`;
|
||||
const threeDotMenuButton = 'button[title="More options"]';
|
||||
let isActionAvailable = false;
|
||||
let iterations = 0;
|
||||
// Dismiss/open the 3dot menu until the action is available
|
||||
// or a maxiumum number of iterations is reached
|
||||
while (!isActionAvailable && iterations <= 20) {
|
||||
await page.click('.c-object-view');
|
||||
await page.click(threeDotMenuButton);
|
||||
isActionAvailable = await page.locator(menuAction).isVisible();
|
||||
iterations++;
|
||||
}
|
||||
|
||||
await page.locator(menuAction).click();
|
||||
assertTimerStateAfterAction(page, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a timer action from the object view
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {TimerViewAction} action
|
||||
*/
|
||||
async function triggerTimerViewAction(page, action) {
|
||||
const buttonTitle = buttonTitleFromAction(action);
|
||||
await page.click(`button[title="${buttonTitle}"]`);
|
||||
assertTimerStateAfterAction(page, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in a TimerViewAction and returns the button title
|
||||
* @param {TimerViewAction} action
|
||||
*/
|
||||
function buttonTitleFromAction(action) {
|
||||
switch (action) {
|
||||
case 'Start':
|
||||
return 'Start';
|
||||
case 'Pause':
|
||||
return 'Pause';
|
||||
case 'Restart at 0':
|
||||
return 'Reset';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the timer state after a timer action has been performed.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {TimerAction} action
|
||||
*/
|
||||
async function assertTimerStateAfterAction(page, action) {
|
||||
let timerStateClass;
|
||||
switch (action) {
|
||||
case 'Start':
|
||||
case 'Restart at 0':
|
||||
timerStateClass = "is-started";
|
||||
break;
|
||||
case 'Stop':
|
||||
timerStateClass = 'is-stopped';
|
||||
break;
|
||||
case 'Pause':
|
||||
timerStateClass = 'is-paused';
|
||||
break;
|
||||
}
|
||||
|
||||
await expect.soft(page.locator('.c-timer')).toHaveClass(new RegExp(timerStateClass));
|
||||
}
|
111
e2e/tests/ui/layout/search/grandsearch.e2e.spec.js
Normal file
111
e2e/tests/ui/layout/search/grandsearch.e2e.spec.js
Normal file
@ -0,0 +1,111 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
This test suite is dedicated to tests which verify search functionality.
|
||||
*/
|
||||
|
||||
const { expect } = require('@playwright/test');
|
||||
const { test } = require('../../../../fixtures');
|
||||
|
||||
/**
|
||||
* Creates a notebook object and adds an entry.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function createClockAndDisplayLayout(page) {
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
// Click button:has-text("Create")
|
||||
await page.locator('button:has-text("Create")').click();
|
||||
// Click li:has-text("Notebook")
|
||||
await page.locator('li:has-text("Clock")').click();
|
||||
// Click button:has-text("OK")
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('button:has-text("OK")').click()
|
||||
]);
|
||||
|
||||
// Click a:has-text("My Items")
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('a:has-text("My Items") >> nth=0').click()
|
||||
]);
|
||||
// Click button:has-text("Create")
|
||||
await page.locator('button:has-text("Create")').click();
|
||||
// Click li:has-text("Notebook")
|
||||
await page.locator('li:has-text("Display Layout")').click();
|
||||
// Click button:has-text("OK")
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('button:has-text("OK")').click()
|
||||
]);
|
||||
}
|
||||
|
||||
test.describe('Grand Search', () => {
|
||||
test('Can search for objects, and subsequent search dropdown behaves properly', async ({ page }) => {
|
||||
await createClockAndDisplayLayout(page);
|
||||
|
||||
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cl');
|
||||
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Clock');
|
||||
// Click text=Elements >> nth=0
|
||||
await page.locator('text=Elements').first().click();
|
||||
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||
|
||||
// Click [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
|
||||
// Click [aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock
|
||||
await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click();
|
||||
await expect(page.locator('.js-preview-window')).toBeVisible();
|
||||
|
||||
// Click [aria-label="Close"]
|
||||
await page.locator('[aria-label="Close"]').click();
|
||||
await expect(page.locator('[aria-label="Search Result"]')).toBeVisible();
|
||||
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Cloc');
|
||||
|
||||
// Click [aria-label="OpenMCT Search"] a >> nth=0
|
||||
await page.locator('[aria-label="OpenMCT Search"] a').first().click();
|
||||
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||
|
||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('foo');
|
||||
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||
|
||||
// Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
|
||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||
// Click text=Save and Finish Editing
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
// Click [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
|
||||
// Fill [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
|
||||
// Click text=Unnamed Clock
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Clock').click()
|
||||
]);
|
||||
await expect(page.locator('.is-object-type-clock')).toBeVisible();
|
||||
});
|
||||
});
|
72
e2e/tests/visual/addInit.visual.spec.js
Normal file
72
e2e/tests/visual/addInit.visual.spec.js
Normal file
@ -0,0 +1,72 @@
|
||||
/* eslint-disable no-undef */
|
||||
/*****************************************************************************
|
||||
* 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 with modified init scripts to inject plugins not otherwise available in the default contexts.
|
||||
|
||||
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 } = require('@playwright/test');
|
||||
const percySnapshot = require('@percy/playwright');
|
||||
const path = require('path');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const VISUAL_GRACE_PERIOD = 5 * 1000; //Lets the application "simmer" before the snapshot is taken
|
||||
|
||||
// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758
|
||||
// Will replace with cy.clock() equivalent
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await context.addInitScript({
|
||||
path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js')
|
||||
});
|
||||
await context.addInitScript(() => {
|
||||
window.__clock = sinon.useFakeTimers({
|
||||
now: 0,
|
||||
shouldAdvanceTime: true
|
||||
}); //Set browser clock to UNIX Epoch
|
||||
});
|
||||
});
|
||||
|
||||
test('Visual - Default Gauge is correct @addInit', async ({ page }) => {
|
||||
|
||||
await page.addInitScript({ path: path.join(__dirname, '../plugins/gauge', './addInitGauge.js') });
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
await page.click('text=Gauge');
|
||||
|
||||
await page.click('text=OK');
|
||||
|
||||
// Take a snapshot of the newly created Gauge object
|
||||
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||
await percySnapshot(page, 'Default Gauge');
|
||||
|
||||
});
|
70
e2e/tests/visual/controlledClock.visual.spec.js
Normal file
70
e2e/tests/visual/controlledClock.visual.spec.js
Normal 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');
|
||||
});
|
95
e2e/tests/visual/generateVisualTestData.e2e.spec.js
Normal file
95
e2e/tests/visual/generateVisualTestData.e2e.spec.js
Normal 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' });
|
||||
});
|
104
e2e/tests/visual/search.visual.spec.js
Normal file
104
e2e/tests/visual/search.visual.spec.js
Normal file
@ -0,0 +1,104 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
This test suite is dedicated to tests which verify search functionality.
|
||||
*/
|
||||
|
||||
const { test, expect } = require('@playwright/test');
|
||||
const percySnapshot = require('@percy/playwright');
|
||||
|
||||
/**
|
||||
* Creates a notebook object and adds an entry.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function createClockAndDisplayLayout(page) {
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
// Click button:has-text("Create")
|
||||
await page.locator('button:has-text("Create")').click();
|
||||
// Click li:has-text("Notebook")
|
||||
await page.locator('li:has-text("Clock")').click();
|
||||
// Click button:has-text("OK")
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('button:has-text("OK")').click()
|
||||
]);
|
||||
|
||||
// Click a:has-text("My Items")
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('a:has-text("My Items") >> nth=0').click()
|
||||
]);
|
||||
// Click button:has-text("Create")
|
||||
await page.locator('button:has-text("Create")').click();
|
||||
// Click li:has-text("Notebook")
|
||||
await page.locator('li:has-text("Display Layout")').click();
|
||||
// Click button:has-text("OK")
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('button:has-text("OK")').click()
|
||||
]);
|
||||
}
|
||||
|
||||
test.describe('Grand Search', () => {
|
||||
test('Can search for objects, and subsequent search dropdown behaves properly', async ({ page }) => {
|
||||
await createClockAndDisplayLayout(page);
|
||||
|
||||
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cl');
|
||||
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Clock');
|
||||
await percySnapshot(page, 'Searching for Clocks');
|
||||
// Click text=Elements >> nth=0
|
||||
await page.locator('text=Elements').first().click();
|
||||
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||
|
||||
// Click [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
|
||||
// Click [aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock
|
||||
await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click();
|
||||
await percySnapshot(page, 'Preview for clock should display when editing enabled and search item clicked');
|
||||
|
||||
// Click [aria-label="Close"]
|
||||
await page.locator('[aria-label="Close"]').click();
|
||||
await percySnapshot(page, 'Search should still be showing after preview closed');
|
||||
|
||||
// Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
|
||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||
// Click text=Save and Finish Editing
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
// Click [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
|
||||
// Fill [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
|
||||
// Click text=Unnamed Clock
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('text=Unnamed Clock').click()
|
||||
]);
|
||||
await percySnapshot(page, 'Clicking on search results should navigate to them if not editing');
|
||||
|
||||
});
|
||||
});
|
@ -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 || {};
|
||||
|
@ -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 ? {
|
||||
|
@ -81,7 +81,7 @@ define([
|
||||
{
|
||||
name: "Amplitude",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
cssClass: "l-numeric",
|
||||
key: "amplitude",
|
||||
required: true,
|
||||
property: [
|
||||
@ -92,7 +92,7 @@ define([
|
||||
{
|
||||
name: "Offset",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
cssClass: "l-numeric",
|
||||
key: "offset",
|
||||
required: true,
|
||||
property: [
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
|
||||
@ -190,7 +190,9 @@ function getRealtimeProvider() {
|
||||
subscribe: (domainObject, callback) => {
|
||||
const delay = getImageLoadDelay(domainObject);
|
||||
const interval = setInterval(() => {
|
||||
callback(pointForTimestamp(Date.now(), domainObject.name, getImageSamples(domainObject.configuration), delay));
|
||||
const imageSamples = getImageSamples(domainObject.configuration);
|
||||
const datum = pointForTimestamp(Date.now(), domainObject.name, imageSamples, delay);
|
||||
callback(datum);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
@ -229,8 +231,9 @@ function getLadProvider() {
|
||||
},
|
||||
request: (domainObject, options) => {
|
||||
const delay = getImageLoadDelay(domainObject);
|
||||
const datum = pointForTimestamp(Date.now(), domainObject.name, getImageSamples(domainObject.configuration), delay);
|
||||
|
||||
return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name, delay)]);
|
||||
return Promise.resolve([datum]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "2.0.5-SNAPSHOT",
|
||||
"version": "2.0.5",
|
||||
"description": "The Open MCT core platform",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.18.2",
|
||||
@ -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 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 grandsearch notebook/tags",
|
||||
"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",
|
||||
|
@ -85,8 +85,6 @@ class ActionCollection extends EventEmitter {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.removeAllListeners();
|
||||
|
||||
if (!this.skipEnvironmentObservers) {
|
||||
this.objectUnsubscribes.forEach(unsubscribe => {
|
||||
unsubscribe();
|
||||
@ -96,6 +94,7 @@ class ActionCollection extends EventEmitter {
|
||||
}
|
||||
|
||||
this.emit('destroy', this.view);
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
getVisibleActions() {
|
||||
|
@ -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 }}
|
||||
|
@ -44,6 +44,7 @@
|
||||
<div
|
||||
v-if="!hideOptions"
|
||||
class="c-menu c-input--autocomplete__options"
|
||||
aria-label="Autocomplete Options"
|
||||
@blur="hideOptions = true"
|
||||
>
|
||||
<ul>
|
||||
|
@ -28,6 +28,7 @@
|
||||
>
|
||||
<input
|
||||
v-model="field"
|
||||
:aria-label="model.name"
|
||||
type="number"
|
||||
:min="model.min"
|
||||
:max="model.max"
|
||||
|
@ -224,7 +224,8 @@ class InMemorySearchProvider {
|
||||
|
||||
/**
|
||||
* Schedule an id to be indexed at a later date. If there are less
|
||||
* pending requests then allowed, will kick off an indexing request.
|
||||
* pending requests than the maximum allowed, this will kick off an indexing request.
|
||||
* This is done only when indexing first begins and we need to index a lot of objects.
|
||||
*
|
||||
* @private
|
||||
* @param {identifier} id to be indexed.
|
||||
@ -258,8 +259,12 @@ class InMemorySearchProvider {
|
||||
}
|
||||
|
||||
onAnnotationCreation(annotationObject) {
|
||||
const provider = this;
|
||||
provider.index(annotationObject);
|
||||
|
||||
const objectProvider = this.openmct.objects.getProvider(annotationObject.identifier);
|
||||
if (objectProvider === undefined || objectProvider.search === undefined) {
|
||||
const provider = this;
|
||||
provider.index(annotationObject);
|
||||
}
|
||||
}
|
||||
|
||||
onNameMutation(domainObject, name) {
|
||||
@ -270,7 +275,6 @@ class InMemorySearchProvider {
|
||||
}
|
||||
|
||||
onTagMutation(domainObject, newTags) {
|
||||
domainObject.oldTags = domainObject.tags;
|
||||
domainObject.tags = newTags;
|
||||
const provider = this;
|
||||
|
||||
@ -404,20 +408,16 @@ class InMemorySearchProvider {
|
||||
}
|
||||
|
||||
});
|
||||
// remove old tags
|
||||
if (model.oldTags) {
|
||||
model.oldTags.forEach(tagIDToRemove => {
|
||||
const existsInNewModel = model.tags.includes(tagIDToRemove);
|
||||
if (!existsInNewModel && this.localIndexedAnnotationsByTag[tagIDToRemove]) {
|
||||
this.localIndexedAnnotationsByTag[tagIDToRemove] = this.localIndexedAnnotationsByTag[tagIDToRemove].
|
||||
filter(annotationToRemove => {
|
||||
const shouldKeep = annotationToRemove.keyString !== keyString;
|
||||
const tagsToRemoveFromIndex = Object.keys(this.localIndexedAnnotationsByTag).filter(indexedTag => {
|
||||
return !(model.tags.includes(indexedTag));
|
||||
});
|
||||
tagsToRemoveFromIndex.forEach(tagToRemoveFromIndex => {
|
||||
this.localIndexedAnnotationsByTag[tagToRemoveFromIndex] = this.localIndexedAnnotationsByTag[tagToRemoveFromIndex].filter(indexedAnnotation => {
|
||||
const shouldKeep = indexedAnnotation.keyString !== keyString;
|
||||
|
||||
return shouldKeep;
|
||||
});
|
||||
}
|
||||
return shouldKeep;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
localIndexAnnotation(objectToIndex, model) {
|
||||
@ -449,7 +449,7 @@ class InMemorySearchProvider {
|
||||
keyString
|
||||
};
|
||||
if (model && (model.type === 'annotation')) {
|
||||
if (model.targets && model.targets) {
|
||||
if (model.targets) {
|
||||
this.localIndexAnnotation(objectToIndex, model);
|
||||
}
|
||||
|
||||
|
@ -94,19 +94,16 @@
|
||||
|
||||
});
|
||||
// remove old tags
|
||||
if (model.oldTags) {
|
||||
model.oldTags.forEach(tagIDToRemove => {
|
||||
const existsInNewModel = model.tags.includes(tagIDToRemove);
|
||||
if (!existsInNewModel && indexedAnnotationsByTag[tagIDToRemove]) {
|
||||
indexedAnnotationsByTag[tagIDToRemove] = indexedAnnotationsByTag[tagIDToRemove].
|
||||
filter(annotationToRemove => {
|
||||
const shouldKeep = annotationToRemove.keyString !== keyString;
|
||||
const tagsToRemoveFromIndex = Object.keys(indexedAnnotationsByTag).filter(indexedTag => {
|
||||
return !(model.tags.includes(indexedTag));
|
||||
});
|
||||
tagsToRemoveFromIndex.forEach(tagToRemoveFromIndex => {
|
||||
indexedAnnotationsByTag[tagToRemoveFromIndex] = indexedAnnotationsByTag[tagToRemoveFromIndex].filter(indexedAnnotation => {
|
||||
const shouldKeep = indexedAnnotation.keyString !== keyString;
|
||||
|
||||
return shouldKeep;
|
||||
});
|
||||
}
|
||||
return shouldKeep;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function indexItem(keyString, model) {
|
||||
@ -116,7 +113,7 @@
|
||||
keyString
|
||||
};
|
||||
if (model && (model.type === 'annotation')) {
|
||||
if (model.targets && model.targets) {
|
||||
if (model.targets) {
|
||||
indexAnnotation(objectToIndex, model);
|
||||
}
|
||||
|
||||
|
@ -315,7 +315,7 @@ define([
|
||||
* @returns {Promise.<object[]>} a promise for an array of
|
||||
* telemetry data
|
||||
*/
|
||||
TelemetryAPI.prototype.request = function (domainObject) {
|
||||
TelemetryAPI.prototype.request = async function (domainObject) {
|
||||
if (this.noRequestProviderForAllObjects) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
@ -337,6 +337,9 @@ define([
|
||||
return this.handleMissingRequestProvider(domainObject);
|
||||
}
|
||||
|
||||
const timeContext = this.openmct.time.getContextForView();
|
||||
await timeContext.hasInitialTick();
|
||||
|
||||
return provider.request.apply(provider, arguments)
|
||||
.catch((rejected) => {
|
||||
if (rejected.name !== 'AbortError') {
|
||||
|
@ -344,6 +344,18 @@ class TimeContext extends EventEmitter {
|
||||
return this.activeClock;
|
||||
}
|
||||
|
||||
hasInitialTick() {
|
||||
const waitForInitialTick = (resolve) => {
|
||||
if (this.activeClock === undefined || this.activeClock.isInitialized()) {
|
||||
resolve(true);
|
||||
} else {
|
||||
setTimeout(() => waitForInitialTick(resolve), 100);
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(waitForInitialTick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bounds based on provided time and current offsets
|
||||
* @param {number} timestamp A time from which bounds will be calculated
|
||||
|
@ -168,7 +168,7 @@ export default class StatusAPI extends EventEmitter {
|
||||
*/
|
||||
async resetStatusForRole(role) {
|
||||
const provider = this.#userAPI.getProvider();
|
||||
const defaultStatus = await this.getDefaultStatus();
|
||||
const defaultStatus = await this.getDefaultStatusForRole(role);
|
||||
|
||||
if (provider.setStatusForRole) {
|
||||
return provider.setStatusForRole(role, defaultStatus);
|
||||
|
@ -136,8 +136,8 @@ export default {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
const conditionSetIdentifier = domainObject.configuration.objectStyles.conditionSetIdentifier;
|
||||
if (this.conditionSetIdentifier !== conditionSetIdentifier) {
|
||||
const conditionSetIdentifier = domainObject.configuration?.objectStyles?.conditionSetIdentifier;
|
||||
if (conditionSetIdentifier && this.conditionSetIdentifier !== conditionSetIdentifier) {
|
||||
this.conditionSetIdentifier = conditionSetIdentifier;
|
||||
}
|
||||
|
||||
|
@ -128,6 +128,30 @@ export default class ExportAsJSONAction {
|
||||
|
||||
return copyOfChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} child
|
||||
* @param {object} parent
|
||||
* @returns {object}
|
||||
*/
|
||||
_rewriteLinkForReference(child, parent) {
|
||||
const childId = this._getId(child);
|
||||
this.externalIdentifiers.push(childId);
|
||||
const copyOfChild = JSON.parse(JSON.stringify(child));
|
||||
|
||||
copyOfChild.identifier.key = uuid();
|
||||
const newIdString = this._getId(copyOfChild);
|
||||
const parentId = this._getId(parent);
|
||||
|
||||
this.idMap[childId] = newIdString;
|
||||
copyOfChild.location = null;
|
||||
parent.configuration.objectStyles.conditionSetIdentifier = copyOfChild.identifier;
|
||||
this.tree[newIdString] = copyOfChild;
|
||||
this.tree[parentId].configuration.objectStyles.conditionSetIdentifier = copyOfChild.identifier;
|
||||
|
||||
return copyOfChild;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -159,23 +183,27 @@ export default class ExportAsJSONAction {
|
||||
"rootId": this._getId(this.root)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} parent
|
||||
*/
|
||||
_write(parent) {
|
||||
this.calls++;
|
||||
//conditional object styles are not saved on the composition, so we need to check for them
|
||||
let childObjectReferenceId = parent.configuration?.objectStyles?.conditionSetIdentifier;
|
||||
|
||||
const composition = this.openmct.composition.get(parent);
|
||||
if (composition !== undefined) {
|
||||
composition.load()
|
||||
.then((children) => {
|
||||
children.forEach((child, index) => {
|
||||
// Only export if object is creatable
|
||||
// Only export if object is creatable
|
||||
if (this._isCreatableAndPersistable(child)) {
|
||||
// Prevents infinite export of self-contained objs
|
||||
// Prevents infinite export of self-contained objs
|
||||
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
|
||||
// If object is a link to something absent from
|
||||
// tree, generate new id and treat as new object
|
||||
// If object is a link to something absent from
|
||||
// tree, generate new id and treat as new object
|
||||
if (this._isLinkedObject(child, parent)) {
|
||||
child = this._rewriteLink(child, parent);
|
||||
} else {
|
||||
@ -186,18 +214,41 @@ export default class ExportAsJSONAction {
|
||||
}
|
||||
}
|
||||
});
|
||||
this.calls--;
|
||||
if (this.calls === 0) {
|
||||
this._rewriteReferences();
|
||||
this._saveAs(this._wrapTree());
|
||||
}
|
||||
this._decrementCallsAndSave();
|
||||
});
|
||||
} else {
|
||||
this.calls--;
|
||||
if (this.calls === 0) {
|
||||
this._rewriteReferences();
|
||||
this._saveAs(this._wrapTree());
|
||||
}
|
||||
} else if (!childObjectReferenceId) {
|
||||
this._decrementCallsAndSave();
|
||||
}
|
||||
|
||||
if (childObjectReferenceId) {
|
||||
this.openmct.objects.get(childObjectReferenceId)
|
||||
.then((child) => {
|
||||
// Only export if object is creatable
|
||||
if (this._isCreatableAndPersistable(child)) {
|
||||
// Prevents infinite export of self-contained objs
|
||||
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
|
||||
// If object is a link to something absent from
|
||||
// tree, generate new id and treat as new object
|
||||
if (this._isLinkedObject(child, parent)) {
|
||||
child = this._rewriteLinkForReference(child, parent);
|
||||
} else {
|
||||
this.tree[this._getId(child)] = child;
|
||||
}
|
||||
|
||||
this._write(child);
|
||||
}
|
||||
}
|
||||
|
||||
this._decrementCallsAndSave();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_decrementCallsAndSave() {
|
||||
this.calls--;
|
||||
if (this.calls === 0) {
|
||||
this._rewriteReferences();
|
||||
this._saveAs(this._wrapTree());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -322,4 +322,57 @@ describe('Export as JSON plugin', () => {
|
||||
|
||||
exportAsJSONAction.invoke([parent]);
|
||||
});
|
||||
|
||||
it('ExportAsJSONAction exports object references from tree', (done) => {
|
||||
const parent = {
|
||||
composition: [],
|
||||
configuration: {
|
||||
objectStyles: {
|
||||
conditionSetIdentifier: {
|
||||
key: 'child',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
identifier: {
|
||||
key: 'parent',
|
||||
namespace: ''
|
||||
},
|
||||
name: 'Parent',
|
||||
type: 'folder',
|
||||
modified: 1503598129176,
|
||||
location: 'mine',
|
||||
persisted: 1503598129176
|
||||
};
|
||||
|
||||
const child = {
|
||||
composition: [],
|
||||
identifier: {
|
||||
key: 'child',
|
||||
namespace: ''
|
||||
},
|
||||
name: 'Child',
|
||||
type: 'folder',
|
||||
modified: 1503598132428,
|
||||
location: null,
|
||||
persisted: 1503598132428
|
||||
};
|
||||
|
||||
spyOn(openmct.objects, 'get').and.callFake(object => {
|
||||
return Promise.resolve(child);
|
||||
});
|
||||
|
||||
spyOn(exportAsJSONAction, '_saveAs').and.callFake(completedTree => {
|
||||
expect(Object.keys(completedTree).length).toBe(2);
|
||||
const conditionSetId = Object.keys(completedTree.openmct)[1];
|
||||
expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy();
|
||||
expect(Object.prototype.hasOwnProperty.call(completedTree, 'rootId')).toBeTruthy();
|
||||
expect(Object.prototype.hasOwnProperty.call(completedTree.openmct, 'parent')).toBeTruthy();
|
||||
expect(completedTree.openmct[conditionSetId].name).toBe('Child');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
exportAsJSONAction.invoke([parent]);
|
||||
});
|
||||
});
|
||||
|
@ -472,7 +472,7 @@ describe('Gauge plugin', () => {
|
||||
it('renders major elements', () => {
|
||||
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range');
|
||||
const valueElement = gaugeHolder.querySelector('.js-meter-current-value');
|
||||
const valueElement = gaugeHolder.querySelector('.js-gauge-current-value');
|
||||
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||
|
||||
@ -485,7 +485,7 @@ describe('Gauge plugin', () => {
|
||||
|
||||
it('renders correct current value', (done) => {
|
||||
function WatchUpdateValue() {
|
||||
const textElement = gaugeHolder.querySelector('.js-meter-current-value');
|
||||
const textElement = gaugeHolder.querySelector('.js-gauge-current-value');
|
||||
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
||||
done();
|
||||
}
|
||||
@ -570,7 +570,7 @@ describe('Gauge plugin', () => {
|
||||
it('renders major elements', () => {
|
||||
const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper');
|
||||
const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range');
|
||||
const valueElement = gaugeHolder.querySelector('.js-meter-current-value');
|
||||
const valueElement = gaugeHolder.querySelector('.js-gauge-current-value');
|
||||
|
||||
const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement);
|
||||
|
||||
|
@ -23,189 +23,217 @@
|
||||
<div
|
||||
class="c-gauge__wrapper js-gauge-wrapper"
|
||||
:class="`c-gauge--${gaugeType}`"
|
||||
:title="gaugeTitle"
|
||||
>
|
||||
<template v-if="typeDial">
|
||||
<svg
|
||||
width="0"
|
||||
height="0"
|
||||
class="c-dial__clip-paths"
|
||||
class="c-gauge c-dial"
|
||||
viewBox="0 0 10 10"
|
||||
>
|
||||
<defs>
|
||||
<clipPath
|
||||
id="gaugeBgMask"
|
||||
clipPathUnits="objectBoundingBox"
|
||||
>
|
||||
<path d="M0.853553 0.853553C0.944036 0.763071 1 0.638071 1 0.5C1 0.223858 0.776142 0 0.5 0C0.223858 0 0 0.223858 0 0.5C0 0.638071 0.0559644 0.763071 0.146447 0.853553L0.285934 0.714066C0.23115 0.659281 0.197266 0.583598 0.197266 0.5C0.197266 0.332804 0.332804 0.197266 0.5 0.197266C0.667196 0.197266 0.802734 0.332804 0.802734 0.5C0.802734 0.583598 0.76885 0.659281 0.714066 0.714066L0.853553 0.853553Z" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="gaugeValueMask"
|
||||
clipPathUnits="objectBoundingBox"
|
||||
>
|
||||
<path d="M0.18926 0.81074C0.109735 0.731215 0.0605469 0.621351 0.0605469 0.5C0.0605469 0.257298 0.257298 0.0605469 0.5 0.0605469C0.742702 0.0605469 0.939453 0.257298 0.939453 0.5C0.939453 0.621351 0.890265 0.731215 0.81074 0.81074L0.714066 0.714066C0.76885 0.659281 0.802734 0.583599 0.802734 0.5C0.802734 0.332804 0.667196 0.197266 0.5 0.197266C0.332804 0.197266 0.197266 0.332804 0.197266 0.5C0.197266 0.583599 0.23115 0.659281 0.285934 0.714066L0.18926 0.81074Z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<g class="c-dial__masks">
|
||||
<mask id="gaugeValueMask">
|
||||
<path
|
||||
d="M1.8926 8.1074C1.09734 7.31215 0.605469 6.21352 0.605469 5C0.605469 2.57297 2.57297 0.605469 5 0.605469C7.42703 0.605469 9.39453 2.57297 9.39453 5C9.39453 6.21352 8.90266 7.31215 8.1074 8.1074L7.14066 7.14066C7.6885 6.59281 8.02734 5.83598 8.02734 5C8.02734 3.32804 6.67196 1.97266 5 1.97266C3.32804 1.97266 1.97266 3.32804 1.97266 5C1.97266 5.83598 2.3115 6.59281 2.85934 7.14066L1.8926 8.1074Z"
|
||||
fill="white"
|
||||
/>
|
||||
</mask>
|
||||
<mask id="gaugeBgMask">
|
||||
<path
|
||||
d="M8.53553 8.53553C9.44036 7.63071 10 6.38071 10 5C10 2.23858 7.76142 0 5 0C2.23858 0 0 2.23858 0 5C0 6.38071 0.559644 7.63071 1.46447 8.53553L2.85934 7.14066C2.3115 6.59281 1.97266 5.83598 1.97266 5C1.97266 3.32804 3.32804 1.97266 5 1.97266C6.67196 1.97266 8.02734 3.32804 8.02734 5C8.02734 5.83598 7.6885 6.59281 7.14066 7.14066L8.53553 8.53553Z"
|
||||
fill="white"
|
||||
/>
|
||||
</mask>
|
||||
</g>
|
||||
|
||||
<svg
|
||||
class="c-dial__range c-gauge__range js-gauge-dial-range"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<text
|
||||
v-if="displayMinMax"
|
||||
font-size="35"
|
||||
transform="translate(105 455) rotate(-45)"
|
||||
>{{ rangeLow }}</text>
|
||||
<text
|
||||
v-if="displayMinMax"
|
||||
font-size="35"
|
||||
transform="translate(407 455) rotate(45)"
|
||||
text-anchor="end"
|
||||
>{{ rangeHigh }}</text>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
v-if="displayCurVal"
|
||||
class="c-dial__current-value-text-wrapper"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<svg
|
||||
class="c-dial__current-value-text-sizer"
|
||||
:viewBox="curValViewBox"
|
||||
<g
|
||||
class="c-dial__graphics"
|
||||
mask="url(#gaugeBgMask)"
|
||||
>
|
||||
<rect
|
||||
class="c-dial__bg"
|
||||
x="0"
|
||||
y="0"
|
||||
width="10"
|
||||
height="10"
|
||||
/>
|
||||
<g
|
||||
v-if="isDialLowLimit"
|
||||
class="c-dial__limit-low"
|
||||
:style="`transform: rotate(${dialLowLimitDeg}deg)`"
|
||||
>
|
||||
<rect
|
||||
v-if="isDialLowLimitLow"
|
||||
class="c-dial__low-limit__low"
|
||||
x="5"
|
||||
y="5"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialLowLimitMid"
|
||||
class="c-dial__low-limit__mid"
|
||||
x="5"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialLowLimitHigh"
|
||||
class="c-dial__low-limit__high"
|
||||
x="0"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
v-if="isDialHighLimit"
|
||||
class="c-dial__limit-high"
|
||||
:style="`transform: rotate(${dialHighLimitDeg}deg)`"
|
||||
>
|
||||
<rect
|
||||
v-if="isDialHighLimitLow"
|
||||
class="c-dial__high-limit__low"
|
||||
x="0"
|
||||
y="5"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialHighLimitMid"
|
||||
class="c-dial__high-limit__mid"
|
||||
x="0"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialHighLimitHigh"
|
||||
class="c-dial__high-limit__high"
|
||||
x="5"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g
|
||||
class="c-dial__graphics"
|
||||
mask="url(#gaugeValueMask)"
|
||||
>
|
||||
<g
|
||||
v-if="typeFilledDial"
|
||||
class="c-dial__filled-value"
|
||||
:style="`transform: rotate(${degValueFilledDial}deg)`"
|
||||
>
|
||||
<rect
|
||||
v-if="isDialFilledValueLow"
|
||||
class="c-dial__filled-value__low"
|
||||
x="5"
|
||||
y="5"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialFilledValueMid"
|
||||
class="c-dial__filled-value__mid"
|
||||
x="5"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialFilledValueHigh"
|
||||
class="c-dial__filled-value__high"
|
||||
x="0"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
</g>
|
||||
<g
|
||||
v-if="valueInBounds && typeNeedleDial"
|
||||
class="c-dial__needle-value"
|
||||
:style="`transform: rotate(${degValue}deg)`"
|
||||
>
|
||||
<path d="M4.90234 9.39453L5.09766 9.39453L5.30146 8.20874C6.93993 8.05674 8.22265 6.67817 8.22266 5C8.22266 3.22018 6.77982 1.77734 5 1.77734C3.22018 1.77734 1.77734 3.22018 1.77734 5C1.77734 6.67817 3.06007 8.05674 4.69854 8.20874L4.90234 9.39453Z" />
|
||||
</g>
|
||||
<path
|
||||
id="dialTextPath"
|
||||
class="c-dial__range-msg-path"
|
||||
d="M8.3501 5.0001C8.3501 6.85025 6.85025 8.3501 5.0001 8.3501C3.14994 8.3501 1.6501 6.85025 1.6501 5.0001C1.6501 3.14994 3.14994 1.6501 5.0001 1.6501C6.85025 1.6501 8.3501 3.14994 8.3501 5.0001Z"
|
||||
fill="none"
|
||||
style="transform-origin: center; transform: rotate(182deg)"
|
||||
/>
|
||||
</g>
|
||||
<g class="c-dial__text">
|
||||
<text
|
||||
x="50%"
|
||||
y="70%"
|
||||
text-anchor="middle"
|
||||
class="c-gauge__units"
|
||||
font-size="8%"
|
||||
>{{ units }}</text>
|
||||
|
||||
<g
|
||||
v-if="displayMinMax"
|
||||
class="c-dial__range-text js-gauge-dial-range"
|
||||
:font-size="rangeFontSize"
|
||||
>
|
||||
<text
|
||||
transform="translate(1.5 8.7) rotate(-45)"
|
||||
dominant-baseline="hanging"
|
||||
>{{ rangeLow }}</text>
|
||||
<text
|
||||
transform="translate(8.4 8.7) rotate(45)"
|
||||
dominant-baseline="hanging"
|
||||
text-anchor="end"
|
||||
>{{ rangeHigh }}</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<svg
|
||||
v-if="!valueInBounds && valueExpected"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
xml:space="preserve"
|
||||
class="c-dial__value-oor-indicator"
|
||||
x="45%"
|
||||
y="80%"
|
||||
width="1"
|
||||
height="1"
|
||||
><path
|
||||
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-64zM288 448h-64v-64h64v64zm10.9-192L280 352h-48l-18.9-96V64H299v192h-.1z"
|
||||
/></svg>
|
||||
|
||||
<svg
|
||||
class="c-gauge__current-value-text-wrapper"
|
||||
:viewBox="curValViewBox"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<rect
|
||||
class="svg-viewbox-debug"
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
<text
|
||||
class="c-dial__current-value-text js-dial-current-value"
|
||||
font-size="3.5"
|
||||
lengthAdjust="spacing"
|
||||
text-anchor="middle"
|
||||
style="transform: translate(50%, 70%)"
|
||||
>{{ curVal }}</text>
|
||||
dominant-baseline="middle"
|
||||
x="50%"
|
||||
y="50%"
|
||||
>
|
||||
<template v-if="displayCurVal">
|
||||
<tspan>{{ curVal }}</tspan>
|
||||
</template>
|
||||
</text>
|
||||
</svg>
|
||||
<svg
|
||||
class="c-gauge__units c-dial__units"
|
||||
viewBox="0 0 50 100"
|
||||
>
|
||||
<text
|
||||
class="c-dial__units-text"
|
||||
lengthAdjust="spacing"
|
||||
text-anchor="middle"
|
||||
style="transform: translate(50%, 72%)"
|
||||
>{{ units }}</text>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
class="c-dial__bg"
|
||||
viewBox="0 0 10 10"
|
||||
>
|
||||
<g
|
||||
v-if="isDialLowLimit"
|
||||
class="c-dial__limit-low"
|
||||
:style="`transform: rotate(${dialLowLimitDeg}deg)`"
|
||||
>
|
||||
<rect
|
||||
v-if="isDialLowLimitLow"
|
||||
class="c-dial__low-limit__low"
|
||||
x="5"
|
||||
y="5"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialLowLimitMid"
|
||||
class="c-dial__low-limit__mid"
|
||||
x="5"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialLowLimitHigh"
|
||||
class="c-dial__low-limit__high"
|
||||
x="0"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g
|
||||
v-if="isDialHighLimit"
|
||||
class="c-dial__limit-high"
|
||||
:style="`transform: rotate(${dialHighLimitDeg}deg)`"
|
||||
>
|
||||
<rect
|
||||
v-if="isDialHighLimitLow"
|
||||
class="c-dial__high-limit__low"
|
||||
x="0"
|
||||
y="5"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialHighLimitMid"
|
||||
class="c-dial__high-limit__mid"
|
||||
x="0"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialHighLimitHigh"
|
||||
class="c-dial__high-limit__high"
|
||||
x="5"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
v-if="typeFilledDial"
|
||||
class="c-dial__filled-value-wrapper"
|
||||
viewBox="0 0 10 10"
|
||||
>
|
||||
<g
|
||||
class="c-dial__filled-value"
|
||||
:style="`transform: rotate(${degValueFilledDial}deg)`"
|
||||
>
|
||||
<rect
|
||||
v-if="isDialFilledValueLow"
|
||||
class="c-dial__filled-value__low"
|
||||
x="5"
|
||||
y="5"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialFilledValueMid"
|
||||
class="c-dial__filled-value__mid"
|
||||
x="5"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
v-if="isDialFilledValueHigh"
|
||||
class="c-dial__filled-value__high"
|
||||
x="0"
|
||||
y="0"
|
||||
width="5"
|
||||
height="5"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
v-if="valueInBounds && typeNeedleDial"
|
||||
class="c-dial__needle-value-wrapper"
|
||||
viewBox="0 0 10 10"
|
||||
>
|
||||
<g
|
||||
class="c-dial__needle-value"
|
||||
:style="`transform: rotate(${degValue}deg)`"
|
||||
>
|
||||
<path d="M4.90234 9.39453L5.09766 9.39453L5.30146 8.20874C6.93993 8.05674 8.22265 6.67817 8.22266 5C8.22266 3.22018 6.77982 1.77734 5 1.77734C3.22018 1.77734 1.77734 3.22018 1.77734 5C1.77734 6.67817 3.06007 8.05674 4.69854 8.20874L4.90234 9.39453Z" />
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -219,9 +247,22 @@
|
||||
<div class="c-meter__range__low">{{ rangeLow }}</div>
|
||||
</div>
|
||||
<div class="c-meter__bg">
|
||||
<div
|
||||
v-if="!valueInBounds && valueExpected"
|
||||
class="c-meter__value-oor-indicator"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
:preserveAspectRatio="meterOutOfRangeIndicatorAspectRatio"
|
||||
><path
|
||||
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-64zM288 448h-64v-64h64v64zm10.9-192L280 352h-48l-18.9-96V64H299v192h-.1z"
|
||||
/></svg></div>
|
||||
|
||||
<template v-if="typeMeterVertical">
|
||||
<div
|
||||
v-if="valueExpected"
|
||||
class="c-meter__value"
|
||||
:class="{'c-meter__value-needle' : typeNeedleMeter }"
|
||||
:style="`transform: translateY(${meterValueToPerc}%)`"
|
||||
></div>
|
||||
|
||||
@ -240,7 +281,9 @@
|
||||
|
||||
<template v-if="typeMeterHorizontal">
|
||||
<div
|
||||
v-if="valueExpected"
|
||||
class="c-meter__value"
|
||||
:class="{'c-meter__value-needle' : typeNeedleMeter }"
|
||||
:style="`transform: translateX(${meterValueToPerc * -1}%)`"
|
||||
></div>
|
||||
|
||||
@ -258,38 +301,42 @@
|
||||
</template>
|
||||
|
||||
<svg
|
||||
class="c-meter__current-value-text-wrapper"
|
||||
viewBox="0 0 512 512"
|
||||
class="c-gauge__current-value-text-wrapper"
|
||||
:viewBox="curValViewBox"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<svg
|
||||
v-if="displayCurVal"
|
||||
class="c-meter__current-value-text-sizer"
|
||||
:viewBox="curValViewBox"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
<rect
|
||||
class="svg-viewbox-debug"
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
<text
|
||||
class="c-meter__current-value-text js-gauge-current-value"
|
||||
font-size="4"
|
||||
lengthAdjust="spacing"
|
||||
text-anchor="middle"
|
||||
:dominant-baseline="meterTextBaseline"
|
||||
x="50%"
|
||||
y="50%"
|
||||
>
|
||||
<text
|
||||
class="c-dial__current-value-text js-meter-current-value"
|
||||
lengthAdjust="spacing"
|
||||
text-anchor="middle"
|
||||
style="transform: translate(50%, 70%)"
|
||||
>
|
||||
<template v-if="displayCurVal">
|
||||
<tspan>{{ curVal }}</tspan>
|
||||
<tspan
|
||||
v-if="typeMeterHorizontal && displayUnits"
|
||||
class="c-gauge__units"
|
||||
font-size="10"
|
||||
font-size="80%"
|
||||
>{{ units }}</tspan>
|
||||
</text>
|
||||
<text
|
||||
v-if="typeMeterVertical && displayUnits"
|
||||
dy="12"
|
||||
class="c-gauge__units"
|
||||
font-size="10"
|
||||
lengthAdjust="spacing"
|
||||
text-anchor="middle"
|
||||
style="transform: translate(50%, 70%)"
|
||||
>{{ units }}</text>
|
||||
</svg>
|
||||
<tspan
|
||||
v-if="typeMeterVertical && displayUnits"
|
||||
x="50%"
|
||||
dy="3.5"
|
||||
class="c-gauge__units"
|
||||
font-size="80%"
|
||||
>{{ units }}</tspan>
|
||||
</template>
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@ -343,14 +390,28 @@ export default {
|
||||
dialLowLimitDeg() {
|
||||
return this.percentToDegrees(this.valToPercent(this.limitLow));
|
||||
},
|
||||
meterOutOfRangeIndicatorAspectRatio() {
|
||||
return this.typeMeterVertical ? 'xMidYMax meet' : 'xMinYMid meet';
|
||||
},
|
||||
meterTextBaseline() {
|
||||
return this.typeMeterVertical ? 'auto' : 'middle';
|
||||
},
|
||||
curValViewBox() {
|
||||
const DIGITS_RATIO = 10;
|
||||
const VIEWBOX_STR = '0 0 X 15';
|
||||
const DIGITS_RATIO = 3;
|
||||
const VIEWBOX_STR = '0 0 X 10';
|
||||
|
||||
return VIEWBOX_STR.replace('X', this.digits * DIGITS_RATIO);
|
||||
},
|
||||
rangeFontSize() {
|
||||
const CHAR_THRESHOLD = 3;
|
||||
const START_PERC = 8.5;
|
||||
const REDUCE_PERC = 0.8;
|
||||
const RANGE_CHARS_MAX = Math.max(this.rangeLow.toString().length, this.rangeHigh.toString().length);
|
||||
|
||||
return this.fontSizeFromChars(RANGE_CHARS_MAX, CHAR_THRESHOLD, START_PERC, REDUCE_PERC);
|
||||
},
|
||||
isDialLowLimit() {
|
||||
return this.limitLow.length > 0 && this.dialLowLimitDeg < getLimitDegree('low', 'max');
|
||||
return this.limitLow.toString().length > 0 && this.dialLowLimitDeg < getLimitDegree('low', 'max');
|
||||
},
|
||||
isDialLowLimitLow() {
|
||||
return this.dialLowLimitDeg >= getLimitDegree('low', 'q1');
|
||||
@ -362,7 +423,7 @@ export default {
|
||||
return this.dialLowLimitDeg >= getLimitDegree('low', 'q3');
|
||||
},
|
||||
isDialHighLimit() {
|
||||
return this.limitHigh.length > 0 && this.dialHighLimitDeg < getLimitDegree('high', 'max');
|
||||
return this.limitHigh.toString().length > 0 && this.dialHighLimitDeg < getLimitDegree('high', 'max');
|
||||
},
|
||||
isDialHighLimitLow() {
|
||||
return this.dialHighLimitDeg <= getLimitDegree('high', 'max');
|
||||
@ -383,10 +444,13 @@ export default {
|
||||
return this.degValue >= getLimitDegree('low', 'q3');
|
||||
},
|
||||
isMeterLimitHigh() {
|
||||
return this.limitHigh.length > 0 && this.meterHighLimitPerc > 0;
|
||||
return this.limitHigh.toString().length > 0 && this.meterHighLimitPerc > 0;
|
||||
},
|
||||
isMeterLimitLow() {
|
||||
return this.limitLow.length > 0 && this.meterLowLimitPerc > 0;
|
||||
return this.limitLow.toString().length > 0 && this.meterLowLimitPerc > 0;
|
||||
},
|
||||
gaugeTitle() {
|
||||
return this.valueInBounds ? 'Gauge' : 'Value is currently out of range and cannot be graphically displayed';
|
||||
},
|
||||
typeDial() {
|
||||
return this.matchGaugeType('dial');
|
||||
@ -409,15 +473,25 @@ export default {
|
||||
typeMeterInverted() {
|
||||
return this.matchGaugeType('inverted');
|
||||
},
|
||||
typeFilledMeter() {
|
||||
return true; // Stubbing in for future capability
|
||||
},
|
||||
typeNeedleMeter() {
|
||||
return false; // Stubbing in for future capability
|
||||
},
|
||||
meterValueToPerc() {
|
||||
const meterDirection = (this.typeMeterInverted) ? -1 : 1;
|
||||
|
||||
if (this.curVal <= this.rangeLow) {
|
||||
return meterDirection * 100;
|
||||
}
|
||||
if (this.typeFilledMeter) {
|
||||
// Filled meter is a filled rectangle that is transformed along a vertical or horizontal axis
|
||||
// So never move it below the low range more than 100%, or above the high range more than 0%
|
||||
if (this.curVal <= this.rangeLow) {
|
||||
return meterDirection * 100;
|
||||
}
|
||||
|
||||
if (this.curVal >= this.rangeHigh) {
|
||||
return 0;
|
||||
if (this.curVal >= this.rangeHigh) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return this.valToPercentMeter(this.curVal) * meterDirection;
|
||||
@ -428,6 +502,13 @@ export default {
|
||||
meterLowLimitPerc() {
|
||||
return 100 - this.valToPercentMeter(this.limitLow);
|
||||
},
|
||||
valueExpected() {
|
||||
if (this.curVal === undefined || Object.is(this.curVal, 'null')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.curVal.toString().indexOf(DEFAULT_CURRENT_VALUE) === -1;
|
||||
},
|
||||
valueInBounds() {
|
||||
return (this.curVal >= this.rangeLow && this.curVal <= this.rangeHigh);
|
||||
},
|
||||
@ -504,6 +585,11 @@ export default {
|
||||
]
|
||||
});
|
||||
},
|
||||
fontSizeFromChars(charNum, charThreshold, startPerc, reducePerc) {
|
||||
const fs = (charNum <= charThreshold) ? startPerc : (startPerc - ((charNum - charThreshold) * reducePerc));
|
||||
|
||||
return fs.toString() + "%";
|
||||
},
|
||||
matchGaugeType(str) {
|
||||
return this.gaugeType.indexOf(str) !== -1;
|
||||
},
|
||||
|
@ -1,3 +1,8 @@
|
||||
$meterNeedlePerc: 1%;
|
||||
$meterNeedleMinPx: 4px;
|
||||
$meterNeedleMaxPx: 20px;
|
||||
$meterNeedleBorderRadius: 5px;
|
||||
|
||||
.is-object-type-gauge {
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -28,63 +33,64 @@
|
||||
@include abs();
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__current-value-text-wrapper {
|
||||
// SVG
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.c-dial__value-oor-indicator,
|
||||
.c-meter__value-oor-indicator {
|
||||
fill: $colorGaugeRange;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/********************************************** DIAL GAUGE */
|
||||
svg[class*='c-dial'] {
|
||||
.c-dial {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
position: absolute;
|
||||
|
||||
g {
|
||||
transform-origin: center;
|
||||
}
|
||||
}
|
||||
|
||||
.c-dial {
|
||||
&__bg {
|
||||
background: $colorGaugeBg;
|
||||
clip-path: url(#gaugeBgMask);
|
||||
fill: $colorGaugeBg;
|
||||
}
|
||||
|
||||
&__limit-high rect { fill: $colorGaugeLimitHigh; }
|
||||
&__limit-low rect { fill: $colorGaugeLimitLow; }
|
||||
|
||||
&__filled-value-wrapper {
|
||||
clip-path: url(#gaugeValueMask);
|
||||
&__limit-high rect {
|
||||
fill: $colorGaugeLimitHigh;
|
||||
}
|
||||
|
||||
&__needle-value-wrapper {
|
||||
clip-path: url(#gaugeValueMask);
|
||||
&__limit-low rect {
|
||||
fill: $colorGaugeLimitLow;
|
||||
}
|
||||
&__filled-value,
|
||||
&__range-msg-text {
|
||||
fill: $colorGaugeValue;
|
||||
}
|
||||
|
||||
&__filled-value { fill: $colorGaugeValue; }
|
||||
|
||||
&__needle-value {
|
||||
fill: $colorGaugeValue;
|
||||
transition: transform $transitionTimeGauge;
|
||||
}
|
||||
|
||||
&__current-value-text,
|
||||
&__units-text {
|
||||
&__current-value-text {
|
||||
fill: $colorGaugeTextValue;
|
||||
font-family: $heroFont;
|
||||
}
|
||||
|
||||
&__units-text,
|
||||
&__range-text {
|
||||
fill: $colorGaugeRange;
|
||||
}
|
||||
|
||||
&__graphics g {
|
||||
transform-origin: center;
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************** METER GAUGE */
|
||||
.c-meter {
|
||||
// Common styles for c-meter
|
||||
$meterOutOfRangeIndicatorMaxSize: 50%;
|
||||
@include abs();
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
// current-value-text
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__range {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
@ -102,10 +108,42 @@ svg[class*='c-dial'] {
|
||||
// Filled area
|
||||
position: absolute;
|
||||
background: $colorGaugeValue;
|
||||
transition: transform $transitionTimeGauge;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__value-needle {
|
||||
background: none !important;
|
||||
&:before {
|
||||
@include abs();
|
||||
content: '';
|
||||
display: block;
|
||||
background: $colorGaugeValue;
|
||||
}
|
||||
}
|
||||
|
||||
&__value-oor-indicator {
|
||||
$mxPx: 50px;
|
||||
$wh: 50%;
|
||||
position: absolute;
|
||||
height: $wh;
|
||||
width: $wh;
|
||||
max-height: $mxPx;
|
||||
max-width: $mxPx;
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__current-value-text {
|
||||
fill: $colorGaugeTextValue;
|
||||
font-family: $heroFont;
|
||||
}
|
||||
|
||||
.c-gauge__curval {
|
||||
fill: $colorGaugeMeterTextValue !important;
|
||||
}
|
||||
@ -142,10 +180,28 @@ svg[class*='c-dial'] {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&__value-needle {
|
||||
right: 0;
|
||||
|
||||
&:before {
|
||||
border-bottom-left-radius: $meterNeedleBorderRadius;
|
||||
border-top-left-radius: $meterNeedleBorderRadius;
|
||||
height: $meterNeedlePerc;
|
||||
min-height: $meterNeedleMinPx;
|
||||
max-height: $meterNeedleMaxPx;
|
||||
}
|
||||
}
|
||||
|
||||
[class*='limit'] {
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.c-meter__value-oor-indicator {
|
||||
bottom: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.c-gauge--meter-vertical & {
|
||||
@ -156,6 +212,13 @@ svg[class*='c-dial'] {
|
||||
&__limit-high {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&__value-needle {
|
||||
&:before {
|
||||
bottom: auto;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-gauge--meter-vertical-inverted & {
|
||||
@ -174,6 +237,13 @@ svg[class*='c-dial'] {
|
||||
&__range__high {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
&__value-needle {
|
||||
&:before {
|
||||
top: auto;
|
||||
transform: translateY(50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-gauge--meter-horizontal & {
|
||||
@ -207,6 +277,20 @@ svg[class*='c-dial'] {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&__value-needle {
|
||||
top: 0;
|
||||
|
||||
&:before {
|
||||
border-bottom-left-radius: $meterNeedleBorderRadius;
|
||||
border-bottom-right-radius: $meterNeedleBorderRadius;
|
||||
left: auto;
|
||||
width: $meterNeedlePerc;
|
||||
min-width: $meterNeedleMinPx;
|
||||
max-width: $meterNeedleMaxPx;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
}
|
||||
|
||||
[class*='limit'] {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
@ -219,5 +303,16 @@ svg[class*='c-dial'] {
|
||||
&__limit-high {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.c-meter__value-oor-indicator {
|
||||
// Horizontal meter
|
||||
left: 2%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.svg-viewbox-debug {
|
||||
fill: rgba(deeppink, 0.5);
|
||||
display: none;
|
||||
}
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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'"
|
||||
|
@ -30,7 +30,7 @@ export default {
|
||||
this.timeSystemChange = this.timeSystemChange.bind(this);
|
||||
this.setDataTimeContext = this.setDataTimeContext.bind(this);
|
||||
this.setDataTimeContext();
|
||||
this.openmct.objectViews.on('clearData', this.clearData);
|
||||
this.openmct.objectViews.on('clearData', this.dataCleared);
|
||||
|
||||
// set
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
@ -44,8 +44,11 @@ export default {
|
||||
this.timeKey = this.timeSystem.key;
|
||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||
|
||||
// kickoff
|
||||
this.subscribe();
|
||||
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {});
|
||||
this.telemetryCollection.on('add', this.dataAdded);
|
||||
this.telemetryCollection.on('remove', this.dataRemoved);
|
||||
this.telemetryCollection.on('clear', this.dataCleared);
|
||||
this.telemetryCollection.load();
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unsubscribe) {
|
||||
@ -54,9 +57,31 @@ export default {
|
||||
}
|
||||
|
||||
this.stopFollowingDataTimeContext();
|
||||
this.openmct.objectViews.off('clearData', this.clearData);
|
||||
this.openmct.objectViews.off('clearData', this.dataCleared);
|
||||
|
||||
this.telemetryCollection.off('add', this.dataAdded);
|
||||
this.telemetryCollection.off('remove', this.dataRemoved);
|
||||
this.telemetryCollection.off('clear', this.dataCleared);
|
||||
|
||||
this.telemetryCollection.destroy();
|
||||
},
|
||||
methods: {
|
||||
dataAdded(dataToAdd) {
|
||||
const normalizedDataToAdd = dataToAdd.map(datum => this.normalizeDatum(datum));
|
||||
this.imageHistory = this.imageHistory.concat(normalizedDataToAdd);
|
||||
},
|
||||
dataCleared() {
|
||||
this.imageHistory = [];
|
||||
},
|
||||
dataRemoved(dataToRemove) {
|
||||
this.imageHistory = this.imageHistory.filter(existingDatum => {
|
||||
const shouldKeep = dataToRemove.some(datumToRemove => {
|
||||
return (existingDatum.utc !== datumToRemove.utc);
|
||||
});
|
||||
|
||||
return shouldKeep;
|
||||
});
|
||||
},
|
||||
setDataTimeContext() {
|
||||
this.stopFollowingDataTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
@ -70,19 +95,6 @@ export default {
|
||||
this.timeContext.off('timeSystem', this.timeSystemChange);
|
||||
}
|
||||
},
|
||||
isDatumValid(datum) {
|
||||
//TODO: Add a check to see if there are duplicate images (identical image timestamp and url subsequently)
|
||||
if (!datum) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const datumTimeCheck = this.parseTime(datum);
|
||||
const bounds = this.timeContext.bounds();
|
||||
|
||||
const isOutOfBounds = datumTimeCheck < bounds.start || datumTimeCheck > bounds.end;
|
||||
|
||||
return !isOutOfBounds;
|
||||
},
|
||||
formatImageUrl(datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
@ -124,40 +136,6 @@ export default {
|
||||
// forcibly reset the imageContainer size to prevent an aspect ratio distortion
|
||||
delete this.imageContainerWidth;
|
||||
delete this.imageContainerHeight;
|
||||
|
||||
return this.requestHistory();
|
||||
},
|
||||
async requestHistory() {
|
||||
this.requestCount++;
|
||||
const requestId = this.requestCount;
|
||||
const bounds = this.timeContext.bounds();
|
||||
|
||||
const data = await this.openmct.telemetry
|
||||
.request(this.domainObject, bounds) || [];
|
||||
// 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
|
||||
const clearDataForObjectSelected = Boolean(domainObjectToClear);
|
||||
if (clearDataForObjectSelected) {
|
||||
const idsEqual = this.openmct.objects.areIdsEqual(
|
||||
domainObjectToClear.identifier,
|
||||
this.domainObject.identifier
|
||||
);
|
||||
if (!idsEqual) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// splice array to encourage garbage collection
|
||||
this.imageHistory.splice(0, this.imageHistory.length);
|
||||
|
||||
},
|
||||
timeSystemChange() {
|
||||
this.timeSystem = this.timeContext.timeSystem();
|
||||
@ -165,22 +143,7 @@ export default {
|
||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
},
|
||||
subscribe() {
|
||||
this.unsubscribe = this.openmct.telemetry
|
||||
.subscribe(this.domainObject, (datum) => {
|
||||
let parsedTimestamp = this.parseTime(datum);
|
||||
let bounds = this.timeContext.bounds();
|
||||
if (!(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isDatumValid(datum)) {
|
||||
this.imageHistory.push(this.normalizeDatum(datum));
|
||||
}
|
||||
});
|
||||
},
|
||||
normalizeDatum(datum) {
|
||||
|
||||
const formattedTime = this.formatTime(datum);
|
||||
const url = this.formatImageUrl(datum);
|
||||
const time = this.parseTime(formattedTime);
|
||||
|
@ -88,6 +88,7 @@ describe("The Imagery View Layouts", () => {
|
||||
let openmct;
|
||||
let parent;
|
||||
let child;
|
||||
let historicalProvider;
|
||||
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
||||
let imageryObject = {
|
||||
identifier: {
|
||||
@ -122,50 +123,6 @@ describe("The Imagery View Layouts", () => {
|
||||
"priority": 3
|
||||
},
|
||||
"source": "url"
|
||||
// "relatedTelemetry": {
|
||||
// "heading": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "heading",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "roll": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "roll",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "pitch": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "pitch",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "cameraPan": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "cameraPan",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "cameraTilt": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "cameraTilt",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "sunOrientation": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "sunOrientation",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
@ -209,6 +166,13 @@ describe("The Imagery View Layouts", () => {
|
||||
telemetryPromiseResolve = resolve;
|
||||
});
|
||||
|
||||
historicalProvider = {
|
||||
request: () => {
|
||||
return Promise.resolve(imageTelemetry);
|
||||
}
|
||||
};
|
||||
spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider);
|
||||
|
||||
spyOn(openmct.telemetry, 'request').and.callFake(() => {
|
||||
telemetryPromiseResolve(imageTelemetry);
|
||||
|
||||
@ -584,6 +548,34 @@ describe("The Imagery View Layouts", () => {
|
||||
expect(imageSizeAfter.width).toBeLessThan(imageSizeBefore.width);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should reset the brightness and contrast when clicking the reset button', async () => {
|
||||
const viewInstance = imageryView._getInstance();
|
||||
await Vue.nextTick();
|
||||
|
||||
// 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
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe("imagery time strip view", () => {
|
||||
@ -598,6 +590,20 @@ describe("The Imagery View Layouts", () => {
|
||||
end: START + (5 * ONE_MINUTE)
|
||||
});
|
||||
|
||||
const mockClock = jasmine.createSpyObj("clock", [
|
||||
"on",
|
||||
"off",
|
||||
"currentValue"
|
||||
]);
|
||||
mockClock.key = 'mockClock';
|
||||
mockClock.currentValue.and.returnValue(1);
|
||||
|
||||
openmct.time.addClock(mockClock);
|
||||
openmct.time.clock('mockClock', {
|
||||
start: START - (5 * ONE_MINUTE),
|
||||
end: START + (5 * ONE_MINUTE)
|
||||
});
|
||||
|
||||
openmct.router.path = [{
|
||||
identifier: {
|
||||
key: 'test-timestrip',
|
||||
@ -632,7 +638,7 @@ describe("The Imagery View Layouts", () => {
|
||||
it("on mount should show imagery within the given bounds", (done) => {
|
||||
Vue.nextTick(() => {
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(6);
|
||||
expect(imageElements.length).toEqual(5);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -652,5 +658,46 @@ describe("The Imagery View Layouts", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove images when clock advances", async () => {
|
||||
openmct.time.tick(ONE_MINUTE * 2);
|
||||
await Vue.nextTick();
|
||||
await Vue.nextTick();
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(4);
|
||||
});
|
||||
|
||||
it("should remove images when start bounds shorten", async () => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: START,
|
||||
end: START + (5 * ONE_MINUTE)
|
||||
});
|
||||
await Vue.nextTick();
|
||||
await Vue.nextTick();
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should remove images when end bounds shorten", async () => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: START - (5 * ONE_MINUTE),
|
||||
end: START - (2 * ONE_MINUTE)
|
||||
});
|
||||
await Vue.nextTick();
|
||||
await Vue.nextTick();
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(4);
|
||||
});
|
||||
|
||||
it("should remove images when both bounds shorten", async () => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: START - (2 * ONE_MINUTE),
|
||||
end: START + (2 * ONE_MINUTE)
|
||||
});
|
||||
await Vue.nextTick();
|
||||
await Vue.nextTick();
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -82,12 +82,14 @@ export default class ImportAsJSONAction {
|
||||
* @param {object} seen
|
||||
*/
|
||||
_deepInstantiate(parent, tree, seen) {
|
||||
if (this.openmct.composition.get(parent)) {
|
||||
let objectIdentifiers = this._getObjectReferenceIds(parent);
|
||||
|
||||
if (objectIdentifiers.length) {
|
||||
let newObj;
|
||||
|
||||
seen.push(parent.id);
|
||||
|
||||
parent.composition.forEach(async (childId) => {
|
||||
objectIdentifiers.forEach(async (childId) => {
|
||||
const keystring = this.openmct.objects.makeKeyString(childId);
|
||||
if (!tree[keystring] || seen.includes(keystring)) {
|
||||
return;
|
||||
@ -101,6 +103,27 @@ export default class ImportAsJSONAction {
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} parent
|
||||
* @returns [identifiers]
|
||||
*/
|
||||
_getObjectReferenceIds(parent) {
|
||||
let objectIdentifiers = [];
|
||||
|
||||
let parentComposition = this.openmct.composition.get(parent);
|
||||
if (parentComposition) {
|
||||
objectIdentifiers = Array.from(parentComposition.domainObject.composition);
|
||||
}
|
||||
|
||||
//conditional object styles are not saved on the composition, so we need to check for them
|
||||
let parentObjectReference = parent.configuration?.objectStyles?.conditionSetIdentifier;
|
||||
if (parentObjectReference) {
|
||||
objectIdentifiers.push(parentObjectReference);
|
||||
}
|
||||
|
||||
return objectIdentifiers;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {object} tree
|
||||
|
@ -144,6 +144,7 @@
|
||||
v-if="selectedSection && selectedPage"
|
||||
ref="notebookEntries"
|
||||
class="c-notebook__entries"
|
||||
aria-label="Notebook Entries"
|
||||
>
|
||||
<NotebookEntry
|
||||
v-for="entry in filteredAndSortedEntries"
|
||||
|
@ -23,6 +23,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="c-notebook__entry c-ne has-local-controls has-tag-applier"
|
||||
aria-label="Notebook Entry"
|
||||
:class="{ 'locked': isLocked }"
|
||||
@dragover="changeCursor"
|
||||
@drop.capture="cancelEditMode"
|
||||
|
@ -93,8 +93,7 @@
|
||||
message.state = 'close';
|
||||
break;
|
||||
default:
|
||||
// Assume connection is closed
|
||||
message.state = 'close';
|
||||
message.state = 'unknown';
|
||||
console.error('🚨 Received unexpected readyState value from CouchDB EventSource feed: 🚨', readyState);
|
||||
break;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
import CouchDocument from "./CouchDocument";
|
||||
import CouchObjectQueue from "./CouchObjectQueue";
|
||||
import { PENDING, CONNECTED, DISCONNECTED } from "./CouchStatusIndicator";
|
||||
import { PENDING, CONNECTED, DISCONNECTED, UNKNOWN } from "./CouchStatusIndicator";
|
||||
import { isNotebookType } from '../../notebook/notebook-constants.js';
|
||||
|
||||
const REV = "_rev";
|
||||
@ -112,7 +112,7 @@ class CouchObjectProvider {
|
||||
* Takes in a state message from the CouchDB SharedWorker and returns an IndicatorState.
|
||||
* @private
|
||||
* @param {'open'|'close'|'pending'} message
|
||||
* @returns import('./CouchStatusIndicator').IndicatorState
|
||||
* @returns {import('./CouchStatusIndicator').IndicatorState}
|
||||
*/
|
||||
#messageToIndicatorState(message) {
|
||||
let state;
|
||||
@ -126,14 +126,52 @@ class CouchObjectProvider {
|
||||
case 'pending':
|
||||
state = PENDING;
|
||||
break;
|
||||
default:
|
||||
state = PENDING;
|
||||
case 'unknown':
|
||||
state = UNKNOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an HTTP status code and returns an IndicatorState
|
||||
* @private
|
||||
* @param {number} statusCode
|
||||
* @returns {import("./CouchStatusIndicator").IndicatorState}
|
||||
*/
|
||||
#statusCodeToIndicatorState(statusCode) {
|
||||
let state;
|
||||
switch (statusCode) {
|
||||
case CouchObjectProvider.HTTP_OK:
|
||||
case CouchObjectProvider.HTTP_CREATED:
|
||||
case CouchObjectProvider.HTTP_ACCEPTED:
|
||||
case CouchObjectProvider.HTTP_NOT_MODIFIED:
|
||||
case CouchObjectProvider.HTTP_BAD_REQUEST:
|
||||
case CouchObjectProvider.HTTP_UNAUTHORIZED:
|
||||
case CouchObjectProvider.HTTP_FORBIDDEN:
|
||||
case CouchObjectProvider.HTTP_NOT_FOUND:
|
||||
case CouchObjectProvider.HTTP_METHOD_NOT_ALLOWED:
|
||||
case CouchObjectProvider.HTTP_NOT_ACCEPTABLE:
|
||||
case CouchObjectProvider.HTTP_CONFLICT:
|
||||
case CouchObjectProvider.HTTP_PRECONDITION_FAILED:
|
||||
case CouchObjectProvider.HTTP_REQUEST_ENTITY_TOO_LARGE:
|
||||
case CouchObjectProvider.HTTP_UNSUPPORTED_MEDIA_TYPE:
|
||||
case CouchObjectProvider.HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
|
||||
case CouchObjectProvider.HTTP_EXPECTATION_FAILED:
|
||||
case CouchObjectProvider.HTTP_SERVER_ERROR:
|
||||
state = CONNECTED;
|
||||
break;
|
||||
case CouchObjectProvider.HTTP_SERVICE_UNAVAILABLE:
|
||||
state = DISCONNECTED;
|
||||
break;
|
||||
default:
|
||||
state = UNKNOWN;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
//backwards compatibility, options used to be a url. Now it's an object
|
||||
#normalize(options) {
|
||||
if (typeof options === 'string') {
|
||||
@ -163,21 +201,11 @@ class CouchObjectProvider {
|
||||
let response = null;
|
||||
try {
|
||||
response = await fetch(this.url + '/' + subPath, fetchOptions);
|
||||
this.indicator.setIndicatorToState(CONNECTED);
|
||||
const { status } = response;
|
||||
const json = await response.json();
|
||||
this.#handleResponseCode(status, json, fetchOptions);
|
||||
|
||||
if (response.status === CouchObjectProvider.HTTP_CONFLICT) {
|
||||
throw new this.openmct.objects.errors.Conflict(`Conflict persisting ${fetchOptions.body.name}`);
|
||||
} else if (response.status === CouchObjectProvider.HTTP_BAD_REQUEST
|
||||
|| response.status === CouchObjectProvider.HTTP_UNAUTHORIZED
|
||||
|| response.status === CouchObjectProvider.HTTP_NOT_FOUND
|
||||
|| response.status === CouchObjectProvider.HTTP_PRECONDITION_FAILED) {
|
||||
const error = await response.json();
|
||||
throw new Error(`CouchDB Error: "${error.error}: ${error.reason}"`);
|
||||
} else if (response.status === CouchObjectProvider.HTTP_SERVER_ERROR) {
|
||||
throw new Error('CouchDB Error: "500 Internal Server Error"');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
return json;
|
||||
} catch (error) {
|
||||
// Network error, CouchDB unreachable.
|
||||
if (response === null) {
|
||||
@ -188,6 +216,24 @@ class CouchObjectProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the response code from a CouchDB request.
|
||||
* Sets the CouchDB indicator status and throws an error if needed.
|
||||
* @private
|
||||
*/
|
||||
#handleResponseCode(status, json, fetchOptions) {
|
||||
this.indicator.setIndicatorToState(this.#statusCodeToIndicatorState(status));
|
||||
if (status === CouchObjectProvider.HTTP_CONFLICT) {
|
||||
throw new this.openmct.objects.errors.Conflict(`Conflict persisting ${fetchOptions.body.name}`);
|
||||
} else if (status >= CouchObjectProvider.HTTP_BAD_REQUEST) {
|
||||
if (!json.error || !json.reason) {
|
||||
throw new Error(`CouchDB Error ${status}`);
|
||||
}
|
||||
|
||||
throw new Error(`CouchDB Error ${status}: "${json.error} - ${json.reason}"`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the response to a create/update/delete request;
|
||||
* track the rev if it's valid, otherwise return false to
|
||||
@ -627,11 +673,25 @@ class CouchObjectProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.couchdb.org/en/3.2.0/api/basics.html
|
||||
CouchObjectProvider.HTTP_OK = 200;
|
||||
CouchObjectProvider.HTTP_CREATED = 201;
|
||||
CouchObjectProvider.HTTP_ACCEPTED = 202;
|
||||
CouchObjectProvider.HTTP_NOT_MODIFIED = 304;
|
||||
CouchObjectProvider.HTTP_BAD_REQUEST = 400;
|
||||
CouchObjectProvider.HTTP_UNAUTHORIZED = 401;
|
||||
CouchObjectProvider.HTTP_FORBIDDEN = 403;
|
||||
CouchObjectProvider.HTTP_NOT_FOUND = 404;
|
||||
CouchObjectProvider.HTTP_METHOD_NOT_ALLOWED = 404;
|
||||
CouchObjectProvider.HTTP_NOT_ACCEPTABLE = 406;
|
||||
CouchObjectProvider.HTTP_CONFLICT = 409;
|
||||
CouchObjectProvider.HTTP_PRECONDITION_FAILED = 412;
|
||||
CouchObjectProvider.HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
|
||||
CouchObjectProvider.HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
|
||||
CouchObjectProvider.HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
|
||||
CouchObjectProvider.HTTP_EXPECTATION_FAILED = 417;
|
||||
CouchObjectProvider.HTTP_SERVER_ERROR = 500;
|
||||
// If CouchDB is containerized via Docker it will return 503 if service is unavailable.
|
||||
CouchObjectProvider.HTTP_SERVICE_UNAVAILABLE = 503;
|
||||
|
||||
export default CouchObjectProvider;
|
||||
|
@ -102,7 +102,7 @@ class CouchSearchProvider {
|
||||
},
|
||||
{
|
||||
"model.annotationType": {
|
||||
"$eq": "notebook"
|
||||
"$eq": "NOTEBOOK"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -56,10 +56,10 @@ export const DISCONNECTED = {
|
||||
description: "CouchDB is offline and unavailable for requests."
|
||||
};
|
||||
/** @type {IndicatorState} */
|
||||
export const MAINTENANCE = {
|
||||
statusClass: "s-status-warning-lo",
|
||||
text: "CouchDB is in maintenance mode",
|
||||
description: "CouchDB is online, but not currently accepting requests."
|
||||
export const UNKNOWN = {
|
||||
statusClass: "s-status-info",
|
||||
text: "CouchDB connectivity unknown",
|
||||
description: "CouchDB is in an unknown state of connectivity."
|
||||
};
|
||||
|
||||
export default class CouchStatusIndicator {
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
createOpenMct,
|
||||
resetApplicationState, spyOnBuiltins
|
||||
} from 'utils/testing';
|
||||
import { CONNECTED, DISCONNECTED, PENDING } from './CouchStatusIndicator';
|
||||
import { CONNECTED, DISCONNECTED, PENDING, UNKNOWN } from './CouchStatusIndicator';
|
||||
|
||||
describe('the plugin', () => {
|
||||
let openmct;
|
||||
@ -397,5 +397,38 @@ describe('the view', () => {
|
||||
|
||||
assertCouchIndicatorStatus(PENDING);
|
||||
});
|
||||
|
||||
it("to 'unknown'", async () => {
|
||||
const workerMessage = {
|
||||
data: {
|
||||
type: 'state',
|
||||
state: 'unknown'
|
||||
}
|
||||
};
|
||||
mockPromise = Promise.resolve({
|
||||
status: 200,
|
||||
json: () => {
|
||||
return {
|
||||
ok: true,
|
||||
_id: 'some-value',
|
||||
id: 'some-value',
|
||||
_rev: 1,
|
||||
model: {}
|
||||
};
|
||||
}
|
||||
});
|
||||
fetch.and.returnValue(mockPromise);
|
||||
|
||||
await openmct.objects.get({
|
||||
namespace: '',
|
||||
key: 'object-1'
|
||||
});
|
||||
|
||||
// Simulate 'pending' state from worker message
|
||||
provider.onSharedWorkerMessage(workerMessage);
|
||||
await Vue.nextTick();
|
||||
|
||||
assertCouchIndicatorStatus(UNKNOWN);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ export default function PlanViewProvider(openmct) {
|
||||
},
|
||||
|
||||
canEdit(domainObject) {
|
||||
return domainObject.type === 'plan';
|
||||
return false;
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
||||
|
@ -96,6 +96,18 @@ describe('the plugin', function () {
|
||||
let planView = applicableViews.find((viewProvider) => viewProvider.key === 'plan.view');
|
||||
expect(planView).toBeDefined();
|
||||
});
|
||||
|
||||
it('is not an editable view', () => {
|
||||
const testViewObject = {
|
||||
id: "test-object",
|
||||
type: "plan"
|
||||
};
|
||||
openmct.router.path = [testViewObject];
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testViewObject, [testViewObject]);
|
||||
let planView = applicableViews.find((viewProvider) => viewProvider.key === 'plan.view');
|
||||
expect(planView.canEdit()).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the plan view displays activities', () => {
|
||||
|
@ -484,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);
|
||||
}
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -64,6 +64,7 @@ import YAxisForm from "./forms/YAxisForm.vue";
|
||||
import LegendForm from "./forms/LegendForm.vue";
|
||||
import eventHelpers from "../lib/eventHelpers";
|
||||
import configStore from "../configuration/ConfigStore";
|
||||
import _ from "lodash";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -125,15 +126,25 @@ export default {
|
||||
});
|
||||
if (index < 0) {
|
||||
index = stackedPlotObject.configuration.series.length;
|
||||
const configPath = `configuration.series[${index}]`;
|
||||
let newConfig = {
|
||||
identifier: config.identifier
|
||||
};
|
||||
_.set(newConfig, `${config.path}`, config.value);
|
||||
this.openmct.objects.mutate(
|
||||
stackedPlotObject,
|
||||
configPath,
|
||||
newConfig
|
||||
);
|
||||
} else {
|
||||
const configPath = `configuration.series[${index}].${config.path}`;
|
||||
this.openmct.objects.mutate(
|
||||
stackedPlotObject,
|
||||
configPath,
|
||||
config.value
|
||||
);
|
||||
}
|
||||
|
||||
const configPath = `configuration.series[${index}].${config.path}`;
|
||||
this.openmct.objects.mutate(
|
||||
stackedPlotObject,
|
||||
configPath,
|
||||
config.value
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -227,8 +227,9 @@ export default {
|
||||
|
||||
const path = objectPath(formField.objectPath);
|
||||
if (!_.isEqual(newVal, oldVal)) {
|
||||
// TODO: Why do we mutate yAxis twice, once directly, once via objects.mutate? Or are they different objects?
|
||||
// We mutate the model for the plots first PlotConfigurationModel - this triggers changes that affects the plot behavior
|
||||
this.yAxis.set(formKey, newVal);
|
||||
// Then we mutate the domain object configuration to persist the settings
|
||||
if (path) {
|
||||
if (!this.domainObject.configuration || !this.domainObject.configuration.series) {
|
||||
this.$emit('seriesUpdated', {
|
||||
|
@ -167,18 +167,6 @@ 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);
|
||||
},
|
||||
|
@ -29,6 +29,7 @@ import Vue from "vue";
|
||||
import conditionalStylesMixin from "./mixins/objectStyles-mixin";
|
||||
import configStore from "@/plugins/plot/configuration/ConfigStore";
|
||||
import PlotConfigurationModel from "@/plugins/plot/configuration/PlotConfigurationModel";
|
||||
import ProgressBar from "../../../ui/components/ProgressBar.vue";
|
||||
|
||||
export default {
|
||||
mixins: [conditionalStylesMixin],
|
||||
@ -125,7 +126,6 @@ export default {
|
||||
const onConfigLoaded = this.onConfigLoaded;
|
||||
const onCursorGuideChange = this.onCursorGuideChange;
|
||||
const onGridLinesChange = this.onGridLinesChange;
|
||||
const loadingUpdated = this.loadingUpdated;
|
||||
const setStatus = this.setStatus;
|
||||
|
||||
const openmct = this.openmct;
|
||||
@ -140,7 +140,8 @@ export default {
|
||||
this.component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
MctPlot
|
||||
MctPlot,
|
||||
ProgressBar
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
@ -156,11 +157,16 @@ export default {
|
||||
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" :limit-line-labels="limitLineLabels" :color-palette="colorPalette" :options="options" @plotTickWidth="onTickWidthChange" @lockHighlightPoint="onLockHighlightPointUpdated" @highlights="onHighlightsUpdated" @configLoaded="onConfigLoaded" @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" :limit-line-labels="limitLineLabels" :color-palette="colorPalette" :options="options" @plotTickWidth="onTickWidthChange" @lockHighlightPoint="onLockHighlightPointUpdated" @highlights="onHighlightsUpdated" @configLoaded="onConfigLoaded" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
|
||||
});
|
||||
|
||||
this.setSelection();
|
||||
@ -198,17 +204,12 @@ export default {
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context);
|
||||
},
|
||||
loadingUpdated(loaded) {
|
||||
this.loading = loaded;
|
||||
this.updateComponentProp('loading', loaded);
|
||||
},
|
||||
getProps() {
|
||||
return {
|
||||
limitLineLabels: this.showLimitLineLabels,
|
||||
gridLines: this.gridLines,
|
||||
cursorGuide: this.cursorGuide,
|
||||
plotTickWidth: this.plotTickWidth,
|
||||
loading: this.loading,
|
||||
options: this.options,
|
||||
status: this.status,
|
||||
colorPalette: this.colorPalette
|
||||
@ -223,33 +224,39 @@ export default {
|
||||
const configId = this.openmct.objects.makeKeyString(this.childObject.identifier);
|
||||
let config = configStore.get(configId);
|
||||
if (!config) {
|
||||
const persistedConfig = this.domainObject.configuration.series.find((seriesConfig) => {
|
||||
let persistedSeriesConfig = 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);
|
||||
if (!persistedSeriesConfig) {
|
||||
persistedSeriesConfig = {
|
||||
series: {},
|
||||
yAxis: {}
|
||||
};
|
||||
}
|
||||
|
||||
config = new PlotConfigurationModel({
|
||||
id: configId,
|
||||
domainObject: {
|
||||
...this.childObject,
|
||||
configuration: {
|
||||
series: [
|
||||
{
|
||||
identifier: this.childObject.identifier,
|
||||
...persistedSeriesConfig.series
|
||||
}
|
||||
],
|
||||
yAxis: persistedSeriesConfig.yAxis
|
||||
|
||||
}
|
||||
},
|
||||
openmct: this.openmct,
|
||||
palette: this.colorPalette,
|
||||
callback: (data) => {
|
||||
this.data = data;
|
||||
}
|
||||
});
|
||||
configStore.add(configId, config);
|
||||
}
|
||||
|
||||
return this.childObject;
|
||||
|
@ -459,7 +459,7 @@ describe("the plugin", function () {
|
||||
max: 10
|
||||
});
|
||||
Vue.nextTick(() => {
|
||||
expect(plotViewComponentObject.$children[1].component.$children[0].xScale.domain()).toEqual({
|
||||
expect(plotViewComponentObject.$children[1].component.$children[1].xScale.domain()).toEqual({
|
||||
min: 0,
|
||||
max: 10
|
||||
});
|
||||
@ -473,7 +473,7 @@ describe("the plugin", function () {
|
||||
max: 20
|
||||
});
|
||||
Vue.nextTick(() => {
|
||||
expect(plotViewComponentObject.$children[1].component.$children[0].yScale.domain()).toEqual({
|
||||
expect(plotViewComponentObject.$children[1].component.$children[1].yScale.domain()).toEqual({
|
||||
min: 10,
|
||||
max: 20
|
||||
});
|
||||
|
@ -112,6 +112,7 @@ export default class RemoteClock extends DefaultClock {
|
||||
|
||||
if (time > this.lastTick) {
|
||||
this.tick(time);
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,10 +123,7 @@ export default class RemoveAction {
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
let currentItemInView = this.openmct.router.path[0];
|
||||
let domainObject = objectPath[0];
|
||||
|
||||
if (this.openmct.objects.areIdsEqual(currentItemInView.identifier, domainObject.identifier)) {
|
||||
if (this.openmct.router.isNavigatedObject(objectPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
|
70
src/plugins/timeline/TimelineCompositionPolicy.js
Normal file
70
src/plugins/timeline/TimelineCompositionPolicy.js
Normal file
@ -0,0 +1,70 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
const ALLOWED_TYPES = [
|
||||
'telemetry.plot.overlay',
|
||||
'telemetry.plot.stacked',
|
||||
'plan'
|
||||
];
|
||||
const DISALLOWED_TYPES = [
|
||||
'telemetry.plot.bar-graph',
|
||||
'telemetry.plot.scatter-plot'
|
||||
];
|
||||
export default function TimelineCompositionPolicy(openmct) {
|
||||
function hasNumericTelemetry(domainObject, metadata) {
|
||||
const hasTelemetry = openmct.telemetry.isTelemetryObject(domainObject);
|
||||
if (!hasTelemetry || !metadata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return metadata.values().length > 0 && hasDomainAndRange(metadata);
|
||||
}
|
||||
|
||||
function hasDomainAndRange(metadata) {
|
||||
return (metadata.valuesForHints(['range']).length > 0
|
||||
&& metadata.valuesForHints(['domain']).length > 0);
|
||||
}
|
||||
|
||||
function hasImageTelemetry(domainObject, metadata) {
|
||||
if (!metadata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return metadata.valuesForHints(['image']).length > 0;
|
||||
}
|
||||
|
||||
return {
|
||||
allow: function (parent, child) {
|
||||
if (parent.type === 'time-strip') {
|
||||
const metadata = openmct.telemetry.getMetadata(child);
|
||||
|
||||
if (!DISALLOWED_TYPES.includes(child.type)
|
||||
&& (hasNumericTelemetry(child, metadata) || hasImageTelemetry(child, metadata) || ALLOWED_TYPES.includes(child.type))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
|
||||
import TimelineViewProvider from './TimelineViewProvider';
|
||||
import timelineInterceptor from "./timelineInterceptor";
|
||||
import TimelineCompositionPolicy from "./TimelineCompositionPolicy";
|
||||
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
@ -39,6 +40,8 @@ export default function () {
|
||||
}
|
||||
});
|
||||
timelineInterceptor(openmct);
|
||||
openmct.composition.addPolicy(new TimelineCompositionPolicy(openmct).allow);
|
||||
|
||||
openmct.objectViews.addProvider(new TimelineViewProvider(openmct));
|
||||
};
|
||||
}
|
||||
|
@ -62,6 +62,34 @@ describe('the plugin', function () {
|
||||
})
|
||||
}
|
||||
};
|
||||
let timelineObject = {
|
||||
"composition": [],
|
||||
configuration: {
|
||||
useIndependentTime: false,
|
||||
timeOptions: {
|
||||
mode: {
|
||||
key: 'fixed'
|
||||
},
|
||||
fixedOffsets: {
|
||||
start: 10,
|
||||
end: 11
|
||||
},
|
||||
clockOffsets: {
|
||||
start: -(30 * 60 * 1000),
|
||||
end: (30 * 60 * 1000)
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Some timestrip",
|
||||
"type": "time-strip",
|
||||
"location": "mine",
|
||||
"modified": 1631005183584,
|
||||
"persisted": 1631005183502,
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach((done) => {
|
||||
mockObjectPath = [
|
||||
@ -134,28 +162,7 @@ describe('the plugin', function () {
|
||||
|
||||
beforeEach(() => {
|
||||
testViewObject = {
|
||||
id: "test-object",
|
||||
identifier: {
|
||||
key: "test-object",
|
||||
namespace: ''
|
||||
},
|
||||
type: "time-strip",
|
||||
configuration: {
|
||||
useIndependentTime: false,
|
||||
timeOptions: {
|
||||
mode: {
|
||||
key: 'fixed'
|
||||
},
|
||||
fixedOffsets: {
|
||||
start: 10,
|
||||
end: 11
|
||||
},
|
||||
clockOffsets: {
|
||||
start: -(30 * 60 * 1000),
|
||||
end: (30 * 60 * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
...timelineObject
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testViewObject, mockObjectPath);
|
||||
@ -187,15 +194,7 @@ describe('the plugin', function () {
|
||||
|
||||
beforeEach(() => {
|
||||
timelineDomainObject = {
|
||||
identifier: {
|
||||
key: 'test-object',
|
||||
namespace: ''
|
||||
},
|
||||
type: 'time-strip',
|
||||
id: "test-object",
|
||||
configuration: {
|
||||
useIndependentTime: false
|
||||
},
|
||||
...timelineObject,
|
||||
composition: [
|
||||
{
|
||||
identifier: {
|
||||
@ -236,27 +235,10 @@ describe('the plugin', function () {
|
||||
describe('the independent time conductor', () => {
|
||||
let timelineView;
|
||||
let testViewObject = {
|
||||
id: "test-object",
|
||||
identifier: {
|
||||
key: "test-object",
|
||||
namespace: ''
|
||||
},
|
||||
type: "time-strip",
|
||||
...timelineObject,
|
||||
configuration: {
|
||||
useIndependentTime: true,
|
||||
timeOptions: {
|
||||
mode: {
|
||||
key: 'local'
|
||||
},
|
||||
fixedOffsets: {
|
||||
start: 10,
|
||||
end: 11
|
||||
},
|
||||
clockOffsets: {
|
||||
start: -(30 * 60 * 1000),
|
||||
end: (30 * 60 * 1000)
|
||||
}
|
||||
}
|
||||
...timelineObject.configuration,
|
||||
useIndependentTime: true
|
||||
}
|
||||
};
|
||||
|
||||
@ -284,27 +266,15 @@ describe('the plugin', function () {
|
||||
describe('the independent time conductor - fixed', () => {
|
||||
let timelineView;
|
||||
let testViewObject2 = {
|
||||
...timelineObject,
|
||||
id: "test-object2",
|
||||
identifier: {
|
||||
key: "test-object2",
|
||||
namespace: ''
|
||||
},
|
||||
type: "time-strip",
|
||||
configuration: {
|
||||
useIndependentTime: true,
|
||||
timeOptions: {
|
||||
mode: {
|
||||
key: 'fixed'
|
||||
},
|
||||
fixedOffsets: {
|
||||
start: 10,
|
||||
end: 11
|
||||
},
|
||||
clockOffsets: {
|
||||
start: -(30 * 60 * 1000),
|
||||
end: (30 * 60 * 1000)
|
||||
}
|
||||
}
|
||||
...timelineObject.configuration,
|
||||
useIndependentTime: true
|
||||
}
|
||||
};
|
||||
|
||||
@ -328,4 +298,68 @@ describe('the plugin', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("The timestrip composition policy", () => {
|
||||
let testObject;
|
||||
beforeEach(() => {
|
||||
testObject = {
|
||||
...timelineObject,
|
||||
composition: []
|
||||
};
|
||||
});
|
||||
|
||||
it("allows composition for plots", () => {
|
||||
const testTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
const composition = openmct.composition.get(testObject);
|
||||
expect(() => {
|
||||
composition.add(testTelemetryObject);
|
||||
}).not.toThrow();
|
||||
expect(testObject.composition.length).toBe(1);
|
||||
});
|
||||
|
||||
it("allows composition for plans", () => {
|
||||
const composition = openmct.composition.get(testObject);
|
||||
expect(() => {
|
||||
composition.add(planObject);
|
||||
}).not.toThrow();
|
||||
expect(testObject.composition.length).toBe(1);
|
||||
});
|
||||
|
||||
it("disallows composition for non time-based plots", () => {
|
||||
const barGraphObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
type: "telemetry.plot.bar-graph",
|
||||
name: "Test Object"
|
||||
};
|
||||
const composition = openmct.composition.get(testObject);
|
||||
expect(() => {
|
||||
composition.add(barGraphObject);
|
||||
}).toThrow();
|
||||
expect(testObject.composition.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -43,14 +43,19 @@ export default class PauseTimerAction {
|
||||
|
||||
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
appliesTo(objectPath, view = {}) {
|
||||
const domainObject = objectPath[0];
|
||||
if (!domainObject || !domainObject.configuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use object configuration timerState for viewless context menus,
|
||||
// otherwise manually show/hide based on the view's timerState
|
||||
const viewKey = view.key;
|
||||
const { timerState } = domainObject.configuration;
|
||||
|
||||
return domainObject.type === 'timer' && timerState === 'started';
|
||||
return viewKey
|
||||
? domainObject.type === 'timer'
|
||||
: domainObject.type === 'timer' && timerState === 'started';
|
||||
}
|
||||
}
|
||||
|
@ -44,14 +44,19 @@ export default class RestartTimerAction {
|
||||
|
||||
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
appliesTo(objectPath, view = {}) {
|
||||
const domainObject = objectPath[0];
|
||||
if (!domainObject || !domainObject.configuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use object configuration timerState for viewless context menus,
|
||||
// otherwise manually show/hide based on the view's timerState
|
||||
const viewKey = view.key;
|
||||
const { timerState } = domainObject.configuration;
|
||||
|
||||
return domainObject.type === 'timer' && timerState !== 'stopped';
|
||||
return viewKey
|
||||
? domainObject.type === 'timer'
|
||||
: domainObject.type === 'timer' && timerState !== 'stopped';
|
||||
}
|
||||
}
|
||||
|
@ -63,14 +63,19 @@ export default class StartTimerAction {
|
||||
newConfiguration.pausedTime = undefined;
|
||||
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
appliesTo(objectPath, view = {}) {
|
||||
const domainObject = objectPath[0];
|
||||
if (!domainObject || !domainObject.configuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use object configuration timerState for viewless context menus,
|
||||
// otherwise manually show/hide based on the view's timerState
|
||||
const viewKey = view.key;
|
||||
const { timerState } = domainObject.configuration;
|
||||
|
||||
return domainObject.type === 'timer' && timerState !== 'started';
|
||||
return viewKey
|
||||
? domainObject.type === 'timer'
|
||||
: domainObject.type === 'timer' && timerState !== 'started';
|
||||
}
|
||||
}
|
||||
|
@ -44,14 +44,19 @@ export default class StopTimerAction {
|
||||
|
||||
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
appliesTo(objectPath, view = {}) {
|
||||
const domainObject = objectPath[0];
|
||||
if (!domainObject || !domainObject.configuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use object configuration timerState for viewless context menus,
|
||||
// otherwise manually show/hide based on the view's timerState
|
||||
const viewKey = view.key;
|
||||
const { timerState } = domainObject.configuration;
|
||||
|
||||
return domainObject.type === 'timer' && timerState !== 'stopped';
|
||||
return viewKey
|
||||
? domainObject.type === 'timer'
|
||||
: domainObject.type === 'timer' && timerState !== 'stopped';
|
||||
}
|
||||
}
|
||||
|
@ -179,6 +179,15 @@ export default {
|
||||
return timerSign;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
timerState() {
|
||||
if (!this.viewActionsCollection) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showOrHideAvailableActions();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.configuration && this.configuration.timerState === undefined) {
|
||||
@ -190,6 +199,9 @@ export default {
|
||||
this.unlisten = ticker.listen(() => {
|
||||
this.openmct.objects.refresh(this.domainObject);
|
||||
});
|
||||
|
||||
this.viewActionsCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
|
||||
this.showOrHideAvailableActions();
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
@ -228,6 +240,22 @@ export default {
|
||||
if (action) {
|
||||
action.invoke(this.objectPath, this.currentView);
|
||||
}
|
||||
},
|
||||
showOrHideAvailableActions() {
|
||||
switch (this.timerState) {
|
||||
case 'started':
|
||||
this.viewActionsCollection.hide(['timer.start']);
|
||||
this.viewActionsCollection.show(['timer.stop', 'timer.pause', 'timer.restart']);
|
||||
break;
|
||||
case 'paused':
|
||||
this.viewActionsCollection.hide(['timer.pause']);
|
||||
this.viewActionsCollection.show(['timer.stop', 'timer.start', 'timer.restart']);
|
||||
break;
|
||||
case 'stopped':
|
||||
this.viewActionsCollection.hide(['timer.stop', 'timer.pause', 'timer.restart']);
|
||||
this.viewActionsCollection.show(['timer.start']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -40,6 +40,7 @@ export default class LocalClock extends DefaultClock {
|
||||
this.period = period;
|
||||
this.timeoutHandle = undefined;
|
||||
this.lastTick = Date.now();
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
start() {
|
||||
|
@ -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");
|
||||
|
@ -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); }
|
||||
|
@ -5,6 +5,7 @@
|
||||
>
|
||||
<input
|
||||
class="c-search__input"
|
||||
aria-label="Search Input"
|
||||
tabindex="10000"
|
||||
type="search"
|
||||
v-bind="$attrs"
|
||||
|
@ -100,7 +100,7 @@ export default {
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.annontation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
|
||||
this.annotation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
|
||||
this.addAnnotationListener(this.annotation);
|
||||
if (this.annotation && this.annotation.tags) {
|
||||
this.tagsChanged(this.annotation.tags);
|
||||
@ -133,21 +133,18 @@ export default {
|
||||
this.addedTags.push(newTagValue);
|
||||
this.userAddingTag = true;
|
||||
},
|
||||
async tagRemoved(tagToRemove) {
|
||||
const existingAnnotation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
|
||||
|
||||
return this.openmct.annotation.removeAnnotationTag(existingAnnotation, tagToRemove);
|
||||
tagRemoved(tagToRemove) {
|
||||
return this.openmct.annotation.removeAnnotationTag(this.annotation, tagToRemove);
|
||||
},
|
||||
async tagAdded(newTag) {
|
||||
const existingAnnotation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
|
||||
|
||||
const newAnnotation = await this.openmct.annotation.addAnnotationTag(existingAnnotation,
|
||||
const annotationWasCreated = this.annotation === null || this.annotation === undefined;
|
||||
this.annotation = await this.openmct.annotation.addAnnotationTag(this.annotation,
|
||||
this.domainObject, this.targetSpecificDetails, this.annotationType, newTag);
|
||||
if (!this.annotation) {
|
||||
this.addAnnotationListener(newAnnotation);
|
||||
if (annotationWasCreated) {
|
||||
this.addAnnotationListener(this.annotation);
|
||||
}
|
||||
|
||||
this.tagsChanged(newAnnotation.tags);
|
||||
this.tagsChanged(this.annotation.tags);
|
||||
this.userAddingTag = false;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
<template>
|
||||
<div
|
||||
class="c-gsearch-result c-gsearch-result--annotation"
|
||||
aria-label="Search Result"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
class="c-gsearch-result__type-icon"
|
||||
|
@ -64,8 +64,6 @@ export default {
|
||||
objectSearchResults: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
destroyed() {
|
||||
document.body.removeEventListener('click', this.handleOutsideClick);
|
||||
},
|
||||
@ -135,8 +133,11 @@ export default {
|
||||
// dropdown is visible, this will collapse the dropdown.
|
||||
if (this.$refs.GrandSearch) {
|
||||
const clickedInsideDropdown = this.$refs.GrandSearch.contains(event.target);
|
||||
if (!clickedInsideDropdown && this.$refs.searchResultsDropDown._data.resultsShown) {
|
||||
this.$refs.searchResultsDropDown._data.resultsShown = false;
|
||||
const clickedPreviewClose = event.target.parentElement && event.target.parentElement.querySelector('.js-preview-window');
|
||||
const searchResultsDropDown = this.$refs.searchResultsDropDown._data;
|
||||
if (!clickedInsideDropdown && searchResultsDropDown.resultsShown && !searchResultsDropDown.previewVisible && !clickedPreviewClose) {
|
||||
searchResultsDropDown.resultsShown = false;
|
||||
document.body.removeEventListener('click', this.handleOutsideClick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
203
src/ui/layout/search/GrandSearchSpec.js
Normal file
203
src/ui/layout/search/GrandSearchSpec.js
Normal file
@ -0,0 +1,203 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import {
|
||||
createOpenMct,
|
||||
resetApplicationState
|
||||
} from 'utils/testing';
|
||||
import Vue from 'vue';
|
||||
import GrandSearch from './GrandSearch.vue';
|
||||
import ExampleTagsPlugin from '../../../../example/exampleTags/plugin';
|
||||
import DisplayLayoutPlugin from '../../../plugins/displayLayout/plugin';
|
||||
|
||||
describe("GrandSearch", () => {
|
||||
let openmct;
|
||||
let grandSearchComponent;
|
||||
let viewContainer;
|
||||
let parent;
|
||||
let sharedWorkerToRestore;
|
||||
let mockDomainObject;
|
||||
let mockAnnotationObject;
|
||||
let mockDisplayLayout;
|
||||
let mockFolderObject;
|
||||
let originalRouterPath;
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
originalRouterPath = openmct.router.path;
|
||||
openmct.router.path = [mockDisplayLayout];
|
||||
openmct.editor.edit();
|
||||
|
||||
openmct.install(new ExampleTagsPlugin());
|
||||
openmct.install(new DisplayLayoutPlugin());
|
||||
const availableTags = openmct.annotation.getAvailableTags();
|
||||
mockDomainObject = {
|
||||
type: 'notebook',
|
||||
name: 'fooRabbitNotebook',
|
||||
identifier: {
|
||||
key: 'some-object',
|
||||
namespace: 'fooNameSpace'
|
||||
},
|
||||
configuration: {
|
||||
entries: {
|
||||
someSection: {
|
||||
somePage: [
|
||||
{
|
||||
id: 'fooBarEntry',
|
||||
text: 'Foo Bar Text'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
mockFolderObject = {
|
||||
type: 'folder',
|
||||
name: 'Test Folder',
|
||||
identifier: {
|
||||
key: 'some-folder',
|
||||
namespace: 'fooNameSpace'
|
||||
}
|
||||
};
|
||||
mockDisplayLayout = {
|
||||
type: 'layout',
|
||||
name: 'Bar Layout',
|
||||
identifier: {
|
||||
key: 'some-layout',
|
||||
namespace: 'fooNameSpace'
|
||||
},
|
||||
configuration: {
|
||||
items: [],
|
||||
layoutGrid: [10, 10]
|
||||
}
|
||||
};
|
||||
mockAnnotationObject = {
|
||||
type: 'annotation',
|
||||
name: 'Some Notebook Annotation',
|
||||
annotationType: openmct.annotation.ANNOTATION_TYPES.NOTEBOOK,
|
||||
tags: [availableTags[0].id, availableTags[1].id],
|
||||
identifier: {
|
||||
key: 'anAnnotationKey',
|
||||
namespace: 'fooNameSpace'
|
||||
},
|
||||
targets: {
|
||||
'fooNameSpace:some-object': {
|
||||
entryId: 'fooBarEntry'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(false);
|
||||
const mockObjectProvider = jasmine.createSpyObj("mock object provider", [
|
||||
"create",
|
||||
"update",
|
||||
"get"
|
||||
]);
|
||||
// eslint-disable-next-line require-await
|
||||
mockObjectProvider.get = async (identifier) => {
|
||||
if (identifier.key === mockDomainObject.identifier.key) {
|
||||
return mockDomainObject;
|
||||
} else if (identifier.key === mockAnnotationObject.identifier.key) {
|
||||
return mockAnnotationObject;
|
||||
} else if (identifier.key === mockDisplayLayout.identifier.key) {
|
||||
return mockDisplayLayout;
|
||||
} else if (identifier.key === mockFolderObject.identifier.key) {
|
||||
return mockFolderObject;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
mockObjectProvider.create.and.returnValue(Promise.resolve(true));
|
||||
mockObjectProvider.update.and.returnValue(Promise.resolve(true));
|
||||
|
||||
openmct.objects.addProvider('fooNameSpace', mockObjectProvider);
|
||||
|
||||
const mockViewProvider = jasmine.createSpyObj("mock view provider", [
|
||||
"key",
|
||||
"view",
|
||||
"canView"
|
||||
]);
|
||||
|
||||
openmct.objectViews.addProvider(mockViewProvider);
|
||||
|
||||
openmct.on('start', async () => {
|
||||
// use local worker
|
||||
sharedWorkerToRestore = openmct.objects.inMemorySearchProvider.worker;
|
||||
openmct.objects.inMemorySearchProvider.worker = null;
|
||||
await openmct.objects.inMemorySearchProvider.index(mockDomainObject);
|
||||
await openmct.objects.inMemorySearchProvider.index(mockDisplayLayout);
|
||||
await openmct.objects.inMemorySearchProvider.index(mockFolderObject);
|
||||
await openmct.objects.inMemorySearchProvider.index(mockAnnotationObject);
|
||||
parent = document.createElement('div');
|
||||
document.body.appendChild(parent);
|
||||
viewContainer = document.createElement('div');
|
||||
parent.append(viewContainer);
|
||||
grandSearchComponent = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
GrandSearch
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
template: '<GrandSearch/>'
|
||||
}).$mount();
|
||||
await Vue.nextTick();
|
||||
done();
|
||||
});
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.objects.inMemorySearchProvider.worker = sharedWorkerToRestore;
|
||||
openmct.router.path = originalRouterPath;
|
||||
grandSearchComponent.$destroy();
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("should render an object search result", async () => {
|
||||
await grandSearchComponent.$children[0].searchEverything('foo');
|
||||
await Vue.nextTick();
|
||||
const searchResult = document.querySelector('[aria-label="fooRabbitNotebook notebook result"]');
|
||||
expect(searchResult).toBeDefined();
|
||||
});
|
||||
|
||||
it("should render an annotation search result", async () => {
|
||||
await grandSearchComponent.$children[0].searchEverything('S');
|
||||
await Vue.nextTick();
|
||||
const annotationResult = document.querySelector('[aria-label="Search Result"]');
|
||||
expect(annotationResult).toBeDefined();
|
||||
});
|
||||
|
||||
it("should preview object search results in edit mode if object clicked", async () => {
|
||||
await grandSearchComponent.$children[0].searchEverything('Folder');
|
||||
grandSearchComponent._provided.openmct.router.path = [mockDisplayLayout];
|
||||
await Vue.nextTick();
|
||||
const searchResult = document.querySelector('[name="Test Folder"]');
|
||||
expect(searchResult).toBeDefined();
|
||||
searchResult.click();
|
||||
const previewWindow = document.querySelector('.js-preview-window');
|
||||
expect(previewWindow).toBeDefined();
|
||||
});
|
||||
});
|
@ -23,6 +23,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="c-gsearch-result c-gsearch-result--object"
|
||||
aria-label="Search Result"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
@ -37,6 +38,8 @@
|
||||
<div
|
||||
class="c-gsearch-result__title"
|
||||
:name="resultName"
|
||||
draggable="true"
|
||||
@dragstart="dragStart"
|
||||
@click="clickedResult"
|
||||
>
|
||||
{{ resultName }}
|
||||
@ -56,6 +59,7 @@
|
||||
<script>
|
||||
import ObjectPath from '../../components/ObjectPath.vue';
|
||||
import objectPathToUrl from '../../../tools/url';
|
||||
import PreviewAction from '../../preview/PreviewAction';
|
||||
|
||||
export default {
|
||||
name: 'ObjectSearchResult',
|
||||
@ -90,12 +94,43 @@ export default {
|
||||
}
|
||||
};
|
||||
this.$refs.objectpath.updateSelection([[selectionObject]]);
|
||||
this.previewAction = new PreviewAction(this.openmct);
|
||||
this.previewAction.on('isVisible', this.togglePreviewState);
|
||||
},
|
||||
destroyed() {
|
||||
this.previewAction.off('isVisible', this.togglePreviewState);
|
||||
},
|
||||
methods: {
|
||||
clickedResult() {
|
||||
clickedResult(event) {
|
||||
if (this.openmct.editor.isEditing()) {
|
||||
event.preventDefault();
|
||||
this.preview();
|
||||
} else {
|
||||
const objectPath = this.result.originalPath;
|
||||
const resultUrl = objectPathToUrl(this.openmct, objectPath);
|
||||
this.openmct.router.navigate(resultUrl);
|
||||
}
|
||||
},
|
||||
togglePreviewState(previewState) {
|
||||
this.$emit('preview-changed', previewState);
|
||||
},
|
||||
preview() {
|
||||
const objectPath = this.result.originalPath;
|
||||
const resultUrl = objectPathToUrl(this.openmct, objectPath);
|
||||
this.openmct.router.navigate(resultUrl);
|
||||
if (this.previewAction.appliesTo(objectPath)) {
|
||||
this.previewAction.invoke(objectPath);
|
||||
}
|
||||
},
|
||||
dragStart(event) {
|
||||
const navigatedObject = this.openmct.router.path[0];
|
||||
const objectPath = this.result.originalPath;
|
||||
const serializedPath = JSON.stringify(objectPath);
|
||||
const keyString = this.openmct.objects.makeKeyString(this.result.identifier);
|
||||
if (this.openmct.composition.checkPolicy(navigatedObject, this.result)) {
|
||||
event.dataTransfer.setData("openmct/composable-domain-object", JSON.stringify(this.result));
|
||||
}
|
||||
|
||||
event.dataTransfer.setData("openmct/domain-object-path", serializedPath);
|
||||
event.dataTransfer.setData(`openmct/domain-object/${keyString}`, this.result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -42,6 +42,7 @@
|
||||
v-for="(objectResult, index) in objectResults"
|
||||
:key="index"
|
||||
:result="objectResult"
|
||||
@preview-changed="previewChanged"
|
||||
@click.native="selectedResult"
|
||||
/>
|
||||
</div>
|
||||
@ -76,12 +77,18 @@ export default {
|
||||
return {
|
||||
resultsShown: false,
|
||||
annotationResults: [],
|
||||
objectResults: []
|
||||
objectResults: [],
|
||||
previewVisible: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
selectedResult() {
|
||||
this.resultsShown = false;
|
||||
if (!this.previewVisible) {
|
||||
this.resultsShown = false;
|
||||
}
|
||||
},
|
||||
previewChanged(changedPreviewState) {
|
||||
this.previewVisible = changedPreviewState;
|
||||
},
|
||||
showResults(passedAnnotationResults, passedObjectResults) {
|
||||
if ((passedAnnotationResults && passedAnnotationResults.length)
|
||||
|
@ -39,6 +39,7 @@
|
||||
padding: $interiorMarginLg;
|
||||
min-width: 500px;
|
||||
max-height: 500px;
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
&__results,
|
||||
|
@ -21,9 +21,11 @@
|
||||
*****************************************************************************/
|
||||
import Preview from './Preview.vue';
|
||||
import Vue from 'vue';
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export default class PreviewAction {
|
||||
export default class PreviewAction extends EventEmitter {
|
||||
constructor(openmct) {
|
||||
super();
|
||||
/**
|
||||
* Metadata
|
||||
*/
|
||||
@ -75,10 +77,12 @@ export default class PreviewAction {
|
||||
onDestroy: () => {
|
||||
PreviewAction.isVisible = false;
|
||||
preview.$destroy();
|
||||
this.emit('isVisible', false);
|
||||
}
|
||||
});
|
||||
|
||||
PreviewAction.isVisible = true;
|
||||
this.emit('isVisible', true);
|
||||
}
|
||||
|
||||
appliesTo(objectPath, view = {}) {
|
||||
|
@ -38,6 +38,7 @@ export default class DefaultClock extends EventEmitter {
|
||||
this.cssClass = 'icon-clock';
|
||||
this.name = 'Clock';
|
||||
this.description = "A default clock for openmct.";
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
tick(tickValue) {
|
||||
@ -86,4 +87,7 @@ export default class DefaultClock extends EventEmitter {
|
||||
return this.lastTick;
|
||||
}
|
||||
|
||||
isInitialized() {
|
||||
return this.initialized;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user