Compare commits
20 Commits
omm-large-
...
fix-autosc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19bb256412 | ||
|
|
66cbc32dd8 | ||
|
|
4bec2c459c | ||
|
|
97781c216e | ||
|
|
9656783fbd | ||
|
|
4f37daafb5 | ||
|
|
80e16ae254 | ||
|
|
cbba210ee7 | ||
|
|
060ee35dbe | ||
|
|
dda6800858 | ||
|
|
7b2ad060ac | ||
|
|
7917f0977d | ||
|
|
2e5f8e7a47 | ||
|
|
1c79d2b5cf | ||
|
|
2b7129fe0b | ||
|
|
decec8deef | ||
|
|
50592fdc0e | ||
|
|
8bc698cfed | ||
|
|
430428f689 | ||
|
|
c6987cd866 |
@@ -144,7 +144,9 @@ async function createNotification(page, createNotificationOptions) {
|
||||
* @param {string} name
|
||||
*/
|
||||
async function expandTreePaneItemByName(page, name) {
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const treeItem = treePane.locator(`role=treeitem[expanded=false][name=/${name}/]`);
|
||||
const expandTriangle = treeItem.locator('.c-disclosure-triangle');
|
||||
await expandTriangle.click();
|
||||
@@ -218,6 +220,30 @@ async function openObjectTreeContextMenu(page, url) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the entire object tree (every expandable tree item).
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {"Main Tree" | "Create Modal Tree"} [treeName="Main Tree"]
|
||||
*/
|
||||
async function expandEntireTree(page, treeName = "Main Tree") {
|
||||
const treeLocator = page.getByRole('tree', {
|
||||
name: treeName
|
||||
});
|
||||
const collapsedTreeItems = treeLocator.getByRole('treeitem', {
|
||||
expanded: false
|
||||
}).locator('span.c-disclosure-triangle.is-enabled');
|
||||
|
||||
while (await collapsedTreeItems.count() > 0) {
|
||||
await collapsedTreeItems.nth(0).click();
|
||||
|
||||
// FIXME: Replace hard wait with something event-driven.
|
||||
// Without the wait, this fails periodically due to a race condition
|
||||
// with Vue rendering (loop exits prematurely).
|
||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID of the currently focused object by parsing the current URL
|
||||
* and returning the last UUID in the path.
|
||||
@@ -362,6 +388,7 @@ module.exports = {
|
||||
createDomainObjectWithDefaults,
|
||||
createNotification,
|
||||
expandTreePaneItemByName,
|
||||
expandEntireTree,
|
||||
createPlanFromJSON,
|
||||
openObjectTreeContextMenu,
|
||||
getHashUrlToDomainObject,
|
||||
|
||||
32
e2e/helper/addInitNotebookWithUrls.js
Normal file
32
e2e/helper/addInitNotebookWithUrls.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/*****************************************************************************
|
||||
* 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 should be used to install the re-instal default Notebook plugin with a simple url whitelist.
|
||||
// e.g.
|
||||
// await page.addInitScript({ path: path.join(__dirname, 'addInitNotebookWithUrls.js') });
|
||||
const NOTEBOOK_NAME = 'Notebook';
|
||||
const URL_WHITELIST = ['google.com'];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const openmct = window.openmct;
|
||||
openmct.install(openmct.plugins.Notebook(NOTEBOOK_NAME, URL_WHITELIST));
|
||||
});
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
const { test, expect } = require('../../pluginFixtures.js');
|
||||
const { createDomainObjectWithDefaults, createNotification } = require('../../appActions.js');
|
||||
const { createDomainObjectWithDefaults, createNotification, expandEntireTree } = require('../../appActions.js');
|
||||
|
||||
test.describe('AppActions', () => {
|
||||
test('createDomainObjectsWithDefaults', async ({ page }) => {
|
||||
@@ -109,4 +109,57 @@ test.describe('AppActions', () => {
|
||||
await expect(page.locator('.c-message-banner')).toHaveClass(/error/);
|
||||
await page.locator('[aria-label="Dismiss"]').click();
|
||||
});
|
||||
test('expandEntireTree', async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
const rootFolder = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Folder'
|
||||
});
|
||||
const folder1 = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Folder',
|
||||
parent: rootFolder.uuid
|
||||
});
|
||||
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Clock',
|
||||
parent: folder1.uuid
|
||||
});
|
||||
const folder2 = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Folder',
|
||||
parent: folder1.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Folder',
|
||||
parent: folder1.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Display Layout',
|
||||
parent: folder2.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Folder',
|
||||
parent: folder2.uuid
|
||||
});
|
||||
|
||||
await page.goto('./#/browse/mine');
|
||||
await expandEntireTree(page);
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: "Main Tree"
|
||||
});
|
||||
const treePaneCollapsedItems = treePane.getByRole('treeitem', { expanded: false });
|
||||
expect(await treePaneCollapsedItems.count()).toBe(0);
|
||||
|
||||
await page.goto('./#/browse/mine');
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
// Click the object specified by 'type'
|
||||
await page.click(`li[role='menuitem']:text("Clock")`);
|
||||
await expandEntireTree(page, "Create Modal Tree");
|
||||
const locatorTree = page.getByRole("tree", {
|
||||
name: "Create Modal Tree"
|
||||
});
|
||||
const locatorTreeCollapsedItems = locatorTree.locator('role=treeitem[expanded=false]');
|
||||
expect(await locatorTreeCollapsedItems.count()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,9 @@ test.describe('Move & link item tests', () => {
|
||||
// Attempt to move parent to its own grandparent
|
||||
await page.locator('button[title="Show selected item in tree"]').click();
|
||||
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
await treePane.getByRole('treeitem', {
|
||||
name: 'Parent Folder'
|
||||
}).click({
|
||||
@@ -63,28 +65,30 @@ test.describe('Move & link item tests', () => {
|
||||
name: /Move/
|
||||
}).click();
|
||||
|
||||
const locatorTree = page.locator('#locator-tree');
|
||||
const myItemsLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||
const createModalTree = page.getByRole('tree', {
|
||||
name: "Create Modal Tree"
|
||||
});
|
||||
const myItemsLocatorTreeItem = createModalTree.getByRole('treeitem', {
|
||||
name: myItemsFolderName
|
||||
});
|
||||
await myItemsLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||
await myItemsLocatorTreeItem.click();
|
||||
|
||||
const parentFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||
const parentFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
|
||||
name: parentFolder.name
|
||||
});
|
||||
await parentFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||
await parentFolderLocatorTreeItem.click();
|
||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||
|
||||
const childFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||
const childFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
|
||||
name: new RegExp(childFolder.name)
|
||||
});
|
||||
await childFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||
await childFolderLocatorTreeItem.click();
|
||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||
|
||||
const grandchildFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||
const grandchildFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
|
||||
name: grandchildFolder.name
|
||||
});
|
||||
await grandchildFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||
@@ -195,7 +199,9 @@ test.describe('Move & link item tests', () => {
|
||||
// Attempt to move parent to its own grandparent
|
||||
await page.locator('button[title="Show selected item in tree"]').click();
|
||||
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
await treePane.getByRole('treeitem', {
|
||||
name: 'Parent Folder'
|
||||
}).click({
|
||||
@@ -206,28 +212,30 @@ test.describe('Move & link item tests', () => {
|
||||
name: /Move/
|
||||
}).click();
|
||||
|
||||
const locatorTree = page.locator('#locator-tree');
|
||||
const myItemsLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||
const createModalTree = page.getByRole('tree', {
|
||||
name: "Create Modal Tree"
|
||||
});
|
||||
const myItemsLocatorTreeItem = createModalTree.getByRole('treeitem', {
|
||||
name: myItemsFolderName
|
||||
});
|
||||
await myItemsLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||
await myItemsLocatorTreeItem.click();
|
||||
|
||||
const parentFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||
const parentFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
|
||||
name: parentFolder.name
|
||||
});
|
||||
await parentFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||
await parentFolderLocatorTreeItem.click();
|
||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||
|
||||
const childFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||
const childFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
|
||||
name: new RegExp(childFolder.name)
|
||||
});
|
||||
await childFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||
await childFolderLocatorTreeItem.click();
|
||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||
|
||||
const grandchildFolderLocatorTreeItem = locatorTree.getByRole('treeitem', {
|
||||
const grandchildFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
|
||||
name: grandchildFolder.name
|
||||
});
|
||||
await grandchildFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
|
||||
|
||||
@@ -32,8 +32,7 @@ test.describe('Display Layout', () => {
|
||||
|
||||
// Create Sine Wave Generator
|
||||
sineWaveObject = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
name: "Test Sine Wave Generator"
|
||||
type: 'Sine Wave Generator'
|
||||
});
|
||||
});
|
||||
test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in real time', async ({ page }) => {
|
||||
@@ -48,7 +47,9 @@ test.describe('Display Layout', () => {
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// Add the Sine Wave Generator to the Display Layout and save changes
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(sineWaveObject.name)
|
||||
});
|
||||
@@ -80,7 +81,9 @@ test.describe('Display Layout', () => {
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// Add the Sine Wave Generator to the Display Layout and save changes
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(sineWaveObject.name)
|
||||
});
|
||||
@@ -116,7 +119,9 @@ test.describe('Display Layout', () => {
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// Add the Sine Wave Generator to the Display Layout and save changes
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(sineWaveObject.name)
|
||||
});
|
||||
@@ -131,7 +136,7 @@ test.describe('Display Layout', () => {
|
||||
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
|
||||
|
||||
// Bring up context menu and remove
|
||||
await page.locator('.c-tree__item.is-alias .c-tree__item__name:text("Test Sine Wave Generator")').first().click({ button: 'right' });
|
||||
await sineWaveGeneratorTreeItem.nth(1).click({ button: 'right' });
|
||||
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
|
||||
await page.locator('button:has-text("OK")').click();
|
||||
|
||||
@@ -146,8 +151,7 @@ test.describe('Display Layout', () => {
|
||||
});
|
||||
// Create a Display Layout
|
||||
const displayLayout = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Display Layout',
|
||||
name: "Test Display Layout"
|
||||
type: 'Display Layout'
|
||||
});
|
||||
// Edit Display Layout
|
||||
await page.locator('[title="Edit"]').click();
|
||||
@@ -155,7 +159,9 @@ test.describe('Display Layout', () => {
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// Add the Sine Wave Generator to the Display Layout and save changes
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(sineWaveObject.name)
|
||||
});
|
||||
@@ -173,7 +179,7 @@ test.describe('Display Layout', () => {
|
||||
await page.goto(sineWaveObject.url);
|
||||
|
||||
// Bring up context menu and remove
|
||||
await page.locator('.c-tree__item.is-alias .c-tree__item__name:text("Test Sine Wave Generator")').click({ button: 'right' });
|
||||
await sineWaveGeneratorTreeItem.first().click({ button: 'right' });
|
||||
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
|
||||
await page.locator('button:has-text("OK")').click();
|
||||
|
||||
|
||||
@@ -25,26 +25,33 @@ const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
||||
|
||||
test.describe('Flexible Layout', () => {
|
||||
let sineWaveObject;
|
||||
let clockObject;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Sine Wave Generator
|
||||
sineWaveObject = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
name: "Test Sine Wave Generator"
|
||||
type: 'Sine Wave Generator'
|
||||
});
|
||||
|
||||
// Create Clock Object
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Clock',
|
||||
name: "Test Clock"
|
||||
clockObject = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Clock'
|
||||
});
|
||||
});
|
||||
test('panes have the appropriate draggable attribute while in Edit and Browse modes', async ({ page }) => {
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(sineWaveObject.name)
|
||||
});
|
||||
const clockTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(clockObject.name)
|
||||
});
|
||||
// Create a Flexible Layout
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Flexible Layout',
|
||||
name: "Test Flexible Layout"
|
||||
type: 'Flexible Layout'
|
||||
});
|
||||
// Edit Flexible Layout
|
||||
await page.locator('[title="Edit"]').click();
|
||||
@@ -52,8 +59,8 @@ test.describe('Flexible Layout', () => {
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
|
||||
// Add the Sine Wave Generator and Clock to the Flexible Layout
|
||||
await page.dragAndDrop('text=Test Sine Wave Generator', '.c-fl__container.is-empty');
|
||||
await page.dragAndDrop('text=Test Clock', '.c-fl__container.is-empty');
|
||||
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
||||
await clockTreeItem.dragTo(page.locator('.c-fl__container.is-empty'));
|
||||
// Check that panes can be dragged while Flexible Layout is in Edit mode
|
||||
let dragWrapper = page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
|
||||
await expect(dragWrapper).toHaveAttribute('draggable', 'true');
|
||||
@@ -65,10 +72,15 @@ test.describe('Flexible Layout', () => {
|
||||
await expect(dragWrapper).toHaveAttribute('draggable', 'false');
|
||||
});
|
||||
test('items in a flexible layout can be removed with object tree context menu when viewing the flexible layout', async ({ page }) => {
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(sineWaveObject.name)
|
||||
});
|
||||
// Create a Display Layout
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Flexible Layout',
|
||||
name: "Test Flexible Layout"
|
||||
type: 'Flexible Layout'
|
||||
});
|
||||
// Edit Flexible Layout
|
||||
await page.locator('[title="Edit"]').click();
|
||||
@@ -76,7 +88,7 @@ test.describe('Flexible Layout', () => {
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
|
||||
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
||||
await page.dragAndDrop('text=Test Sine Wave Generator', '.c-fl__container.is-empty');
|
||||
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
||||
await page.locator('button[title="Save"]').click();
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
|
||||
@@ -86,7 +98,7 @@ test.describe('Flexible Layout', () => {
|
||||
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
|
||||
|
||||
// Bring up context menu and remove
|
||||
await page.locator('.c-tree__item.is-alias .c-tree__item__name:text("Test Sine Wave Generator")').first().click({ button: 'right' });
|
||||
await sineWaveGeneratorTreeItem.first().click({ button: 'right' });
|
||||
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
|
||||
await page.locator('button:has-text("OK")').click();
|
||||
|
||||
@@ -98,10 +110,16 @@ test.describe('Flexible Layout', () => {
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/3117'
|
||||
});
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(sineWaveObject.name)
|
||||
});
|
||||
|
||||
// Create a Flexible Layout
|
||||
const flexibleLayout = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Flexible Layout',
|
||||
name: "Test Flexible Layout"
|
||||
type: 'Flexible Layout'
|
||||
});
|
||||
// Edit Flexible Layout
|
||||
await page.locator('[title="Edit"]').click();
|
||||
@@ -109,7 +127,7 @@ test.describe('Flexible Layout', () => {
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
||||
await page.dragAndDrop('text=Test Sine Wave Generator', '.c-fl__container.is-empty');
|
||||
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
||||
await page.locator('button[title="Save"]').click();
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
|
||||
@@ -122,7 +140,7 @@ test.describe('Flexible Layout', () => {
|
||||
await page.goto(sineWaveObject.url);
|
||||
|
||||
// Bring up context menu and remove
|
||||
await page.locator('.c-tree__item.is-alias .c-tree__item__name:text("Test Sine Wave Generator")').click({ button: 'right' });
|
||||
await sineWaveGeneratorTreeItem.first().click({ button: 'right' });
|
||||
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
|
||||
await page.locator('button:has-text("OK")').click();
|
||||
|
||||
|
||||
@@ -25,8 +25,11 @@ This test suite is dedicated to tests which verify the basic operations surround
|
||||
*/
|
||||
|
||||
const { test, expect } = require('../../../../pluginFixtures');
|
||||
const { expandTreePaneItemByName, createDomainObjectWithDefaults } = require('../../../../appActions');
|
||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
||||
const nbUtils = require('../../../../helper/notebookUtils');
|
||||
const path = require('path');
|
||||
|
||||
const NOTEBOOK_NAME = 'Notebook';
|
||||
|
||||
test.describe('Notebook CRUD Operations', () => {
|
||||
test.fixme('Can create a Notebook Object', async ({ page }) => {
|
||||
@@ -73,8 +76,7 @@ test.describe('Notebook section tests', () => {
|
||||
|
||||
// Create Notebook
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Notebook',
|
||||
name: "Test Notebook"
|
||||
type: NOTEBOOK_NAME
|
||||
});
|
||||
});
|
||||
test('Default and new sections are automatically named Unnamed Section with Unnamed Page', async ({ page }) => {
|
||||
@@ -135,8 +137,7 @@ test.describe('Notebook page tests', () => {
|
||||
|
||||
// Create Notebook
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Notebook',
|
||||
name: "Test Notebook"
|
||||
type: NOTEBOOK_NAME
|
||||
});
|
||||
});
|
||||
//Test will need to be implemented after a refactor in #5713
|
||||
@@ -207,24 +208,30 @@ test.describe('Notebook search tests', () => {
|
||||
});
|
||||
|
||||
test.describe('Notebook entry tests', () => {
|
||||
// Create Notebook with URL Whitelist
|
||||
let notebookObject;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
await page.addInitScript({ path: path.join(__dirname, '../../../../helper/', 'addInitNotebookWithUrls.js') });
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
notebookObject = await createDomainObjectWithDefaults(page, {
|
||||
type: NOTEBOOK_NAME
|
||||
});
|
||||
});
|
||||
test.fixme('When a new entry is created, it should be focused', async ({ page }) => {});
|
||||
test('When an object is dropped into a notebook, a new entry is created and it should be focused @unstable', async ({ page }) => {
|
||||
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
const notebook = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Notebook',
|
||||
name: "Embed Test Notebook"
|
||||
});
|
||||
// Create Overlay Plot
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot',
|
||||
name: "Dropped Overlay Plot"
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
await expandTreePaneItemByName(page, 'My Items');
|
||||
// Navigate to the notebook object
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await page.goto(notebook.url);
|
||||
await page.dragAndDrop('role=treeitem[name=/Dropped Overlay Plot/]', '.c-notebook__drag-area');
|
||||
|
||||
const embed = page.locator('.c-ne__embed__link');
|
||||
@@ -234,22 +241,16 @@ test.describe('Notebook entry tests', () => {
|
||||
expect(embedName).toBe('Dropped Overlay Plot');
|
||||
});
|
||||
test('When an object is dropped into a notebooks existing entry, it should be focused @unstable', async ({ page }) => {
|
||||
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
const notebook = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Notebook',
|
||||
name: "Embed Test Notebook"
|
||||
});
|
||||
// Create Overlay Plot
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot',
|
||||
name: "Dropped Overlay Plot"
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
await expandTreePaneItemByName(page, 'My Items');
|
||||
// Navigate to the notebook object
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
await page.goto(notebook.url);
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, 'Entry to drop into');
|
||||
await page.dragAndDrop('role=treeitem[name=/Dropped Overlay Plot/]', 'text=Entry to drop into');
|
||||
@@ -263,19 +264,14 @@ test.describe('Notebook entry tests', () => {
|
||||
});
|
||||
test.fixme('new entries persist through navigation events without save', async ({ page }) => {});
|
||||
test.fixme('previous and new entries can be deleted', async ({ page }) => {});
|
||||
test.fixme('when a valid link is entered into a notebook entry, it becomes clickable when viewing', async ({ page }) => {
|
||||
test('when a valid link is entered into a notebook entry, it becomes clickable when viewing', async ({ page }) => {
|
||||
const TEST_LINK = 'http://www.google.com';
|
||||
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
const notebook = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Notebook',
|
||||
name: "Entry Link Test"
|
||||
});
|
||||
// Navigate to the notebook object
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
await expandTreePaneItemByName(page, 'My Items');
|
||||
|
||||
await page.goto(notebook.url);
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`);
|
||||
|
||||
@@ -293,19 +289,14 @@ test.describe('Notebook entry tests', () => {
|
||||
|
||||
expect(await validLink.count()).toBe(1);
|
||||
});
|
||||
test.fixme('when an invalid link is entered into a notebook entry, it does not become clickable when viewing', async ({ page }) => {
|
||||
test('when an invalid link is entered into a notebook entry, it does not become clickable when viewing', async ({ page }) => {
|
||||
const TEST_LINK = 'www.google.com';
|
||||
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
const notebook = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Notebook',
|
||||
name: "Entry Link Test"
|
||||
});
|
||||
// Navigate to the notebook object
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
await expandTreePaneItemByName(page, 'My Items');
|
||||
|
||||
await page.goto(notebook.url);
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`);
|
||||
|
||||
@@ -313,20 +304,70 @@ test.describe('Notebook entry tests', () => {
|
||||
|
||||
expect(await invalidLink.count()).toBe(0);
|
||||
});
|
||||
test.fixme('when a nefarious link is entered into a notebook entry, it is sanitized when viewing', async ({ page }) => {
|
||||
test('when a link is entered, but it is not in the whitelisted urls, it does not become clickable when viewing', async ({ page }) => {
|
||||
const TEST_LINK = 'http://www.bing.com';
|
||||
|
||||
// Navigate to the notebook object
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`);
|
||||
|
||||
const invalidLink = page.locator(`a[href="${TEST_LINK}"]`);
|
||||
|
||||
expect(await invalidLink.count()).toBe(0);
|
||||
});
|
||||
test('when a valid link with a subdomain and a valid domain in the whitelisted urls is entered into a notebook entry, it becomes clickable when viewing', async ({ page }) => {
|
||||
const INVALID_TEST_LINK = 'http://bing.google.com';
|
||||
|
||||
// Navigate to the notebook object
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should be a link: ${INVALID_TEST_LINK} is it?`);
|
||||
|
||||
const validLink = page.locator(`a[href="${INVALID_TEST_LINK}"]`);
|
||||
|
||||
expect(await validLink.count()).toBe(1);
|
||||
});
|
||||
test('when a valid secure link is entered into a notebook entry, it becomes clickable when viewing', async ({ page }) => {
|
||||
const TEST_LINK = 'https://www.google.com';
|
||||
|
||||
// Navigate to the notebook object
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`);
|
||||
|
||||
const validLink = page.locator(`a[href="${TEST_LINK}"]`);
|
||||
|
||||
// Start waiting for popup before clicking. Note no await.
|
||||
const popupPromise = page.waitForEvent('popup');
|
||||
|
||||
await validLink.click();
|
||||
const popup = await popupPromise;
|
||||
|
||||
// Wait for the popup to load.
|
||||
await popup.waitForLoadState();
|
||||
expect.soft(popup.url()).toContain('www.google.com');
|
||||
|
||||
expect(await validLink.count()).toBe(1);
|
||||
});
|
||||
test('when a nefarious link is entered into a notebook entry, it is sanitized when viewing', async ({ page }) => {
|
||||
const TEST_LINK = 'http://www.google.com?bad=';
|
||||
const TEST_LINK_BAD = `http://www.google.com?bad=<script>alert('gimme your cookies')</script>`;
|
||||
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Notebook
|
||||
const notebook = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Notebook',
|
||||
name: "Entry Link Test"
|
||||
});
|
||||
// Navigate to the notebook object
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
await expandTreePaneItemByName(page, 'My Items');
|
||||
|
||||
await page.goto(notebook.url);
|
||||
// Reveal the notebook in the tree
|
||||
await page.getByTitle('Show selected item in tree').click();
|
||||
|
||||
await nbUtils.enterTextEntry(page, `This should be a link, BUT not a bad link: ${TEST_LINK_BAD} is it?`);
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
});
|
||||
|
||||
test('Inspect Notebook Entry Network Requests', async ({ page }) => {
|
||||
await page.getByText('Annotations').click();
|
||||
// Expand sidebar
|
||||
await page.locator('.c-notebook__toggle-nav-button').click();
|
||||
|
||||
@@ -162,20 +163,20 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
await page.locator('[aria-label="Notebook Entry Input"] >> nth=2').press('Enter');
|
||||
|
||||
// Add three tags
|
||||
await page.hover(`button:has-text("Add Tag") >> nth=2`);
|
||||
await page.locator(`button:has-text("Add Tag") >> nth=2`).click();
|
||||
await page.hover(`button:has-text("Add Tag")`);
|
||||
await page.locator(`button:has-text("Add Tag")`).click();
|
||||
await page.locator('[placeholder="Type to select tag"]').click();
|
||||
await page.locator('[aria-label="Autocomplete Options"] >> text=Science').click();
|
||||
await page.waitForSelector('[aria-label="Tag"]:has-text("Science")');
|
||||
|
||||
await page.hover(`button:has-text("Add Tag") >> nth=2`);
|
||||
await page.locator(`button:has-text("Add Tag") >> nth=2`).click();
|
||||
await page.hover(`button:has-text("Add Tag")`);
|
||||
await page.locator(`button:has-text("Add Tag")`).click();
|
||||
await page.locator('[placeholder="Type to select tag"]').click();
|
||||
await page.locator('[aria-label="Autocomplete Options"] >> text=Drilling').click();
|
||||
await page.waitForSelector('[aria-label="Tag"]:has-text("Drilling")');
|
||||
|
||||
await page.hover(`button:has-text("Add Tag") >> nth=2`);
|
||||
await page.locator(`button:has-text("Add Tag") >> nth=2`).click();
|
||||
await page.hover(`button:has-text("Add Tag")`);
|
||||
await page.locator(`button:has-text("Add Tag")`).click();
|
||||
await page.locator('[placeholder="Type to select tag"]').click();
|
||||
await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click();
|
||||
await page.waitForSelector('[aria-label="Tag"]:has-text("Driving")');
|
||||
@@ -231,6 +232,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
type: 'issue',
|
||||
description: 'https://github.com/akhenry/openmct-yamcs/issues/69'
|
||||
});
|
||||
await page.getByText('Annotations').click();
|
||||
await page.locator('text=To start a new entry, click here or drag and drop any object').click();
|
||||
await page.locator('[aria-label="Notebook Entry Input"]').click();
|
||||
await page.locator('[aria-label="Notebook Entry Input"]').fill(`First Entry`);
|
||||
|
||||
@@ -198,7 +198,9 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
||||
page.click('.c-disclosure-triangle')
|
||||
]);
|
||||
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
// Click Clock
|
||||
await treePane.getByRole('treeitem', {
|
||||
name: clock.name
|
||||
|
||||
@@ -160,35 +160,16 @@ async function testRegularTicks(page) {
|
||||
*/
|
||||
async function testLogTicks(page) {
|
||||
const yTicks = await page.locator('.gl-plot-y-tick-label');
|
||||
expect(await yTicks.count()).toBe(28);
|
||||
expect(await yTicks.count()).toBe(9);
|
||||
await expect(yTicks.nth(0)).toHaveText('-2.98');
|
||||
await expect(yTicks.nth(1)).toHaveText('-2.50');
|
||||
await expect(yTicks.nth(2)).toHaveText('-2.00');
|
||||
await expect(yTicks.nth(3)).toHaveText('-1.51');
|
||||
await expect(yTicks.nth(4)).toHaveText('-1.20');
|
||||
await expect(yTicks.nth(5)).toHaveText('-1.00');
|
||||
await expect(yTicks.nth(6)).toHaveText('-0.80');
|
||||
await expect(yTicks.nth(7)).toHaveText('-0.58');
|
||||
await expect(yTicks.nth(8)).toHaveText('-0.40');
|
||||
await expect(yTicks.nth(9)).toHaveText('-0.20');
|
||||
await expect(yTicks.nth(10)).toHaveText('-0.00');
|
||||
await expect(yTicks.nth(11)).toHaveText('0.20');
|
||||
await expect(yTicks.nth(12)).toHaveText('0.40');
|
||||
await expect(yTicks.nth(13)).toHaveText('0.58');
|
||||
await expect(yTicks.nth(14)).toHaveText('0.80');
|
||||
await expect(yTicks.nth(15)).toHaveText('1.00');
|
||||
await expect(yTicks.nth(16)).toHaveText('1.20');
|
||||
await expect(yTicks.nth(17)).toHaveText('1.51');
|
||||
await expect(yTicks.nth(18)).toHaveText('2.00');
|
||||
await expect(yTicks.nth(19)).toHaveText('2.50');
|
||||
await expect(yTicks.nth(20)).toHaveText('2.98');
|
||||
await expect(yTicks.nth(21)).toHaveText('3.50');
|
||||
await expect(yTicks.nth(22)).toHaveText('4.00');
|
||||
await expect(yTicks.nth(23)).toHaveText('4.50');
|
||||
await expect(yTicks.nth(24)).toHaveText('5.31');
|
||||
await expect(yTicks.nth(25)).toHaveText('7.00');
|
||||
await expect(yTicks.nth(26)).toHaveText('8.00');
|
||||
await expect(yTicks.nth(27)).toHaveText('9.00');
|
||||
await expect(yTicks.nth(1)).toHaveText('-1.51');
|
||||
await expect(yTicks.nth(2)).toHaveText('-0.58');
|
||||
await expect(yTicks.nth(3)).toHaveText('-0.00');
|
||||
await expect(yTicks.nth(4)).toHaveText('0.58');
|
||||
await expect(yTicks.nth(5)).toHaveText('1.51');
|
||||
await expect(yTicks.nth(6)).toHaveText('2.98');
|
||||
await expect(yTicks.nth(7)).toHaveText('5.31');
|
||||
await expect(yTicks.nth(8)).toHaveText('9.00');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
79
e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js
Normal file
79
e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
Tests to verify log plot functionality. Note this test suite if very much under active development and should not
|
||||
necessarily be used for reference when writing new tests in this area.
|
||||
*/
|
||||
|
||||
const { test, expect } = require('../../../../pluginFixtures');
|
||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
||||
|
||||
test.describe('Stacked Plot', () => {
|
||||
|
||||
test('Using the remove action removes the correct plot', async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: "Overlay Plot"
|
||||
});
|
||||
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
name: 'swg a',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
name: 'swg b',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
name: 'swg c',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
await page.goto(overlayPlot.url);
|
||||
await page.click('button[title="Edit"]');
|
||||
|
||||
// Expand the elements pool vertically
|
||||
await page.locator('.l-pane__handle').nth(2).hover({ trial: true });
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(0, 100);
|
||||
await page.mouse.up();
|
||||
|
||||
await page.locator('.js-elements-pool__tree >> text=swg b').click({ button: 'right' });
|
||||
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
|
||||
await page.locator('.js-overlay .js-overlay__button >> text=OK').click();
|
||||
|
||||
// Wait until the number of elements in the elements pool has changed, and then confirm that the correct children were retained
|
||||
// await page.waitForFunction(() => {
|
||||
// return Array.from(document.querySelectorAll('.js-elements-pool__tree .js-elements-pool__item')).length === 2;
|
||||
// });
|
||||
// Wait until there are only two items in the elements pool (ie the remove action has completed)
|
||||
await expect(page.locator('.js-elements-pool__tree .js-elements-pool__item')).toHaveCount(2);
|
||||
|
||||
// Confirm that the elements pool contains the items we expect
|
||||
await expect(page.locator('.js-elements-pool__tree >> text=swg a')).toHaveCount(1);
|
||||
await expect(page.locator('.js-elements-pool__tree >> text=swg b')).toHaveCount(0);
|
||||
await expect(page.locator('.js-elements-pool__tree >> text=swg c')).toHaveCount(1);
|
||||
});
|
||||
});
|
||||
@@ -24,15 +24,22 @@ const { test, expect } = require('../../pluginFixtures.js');
|
||||
const { createDomainObjectWithDefaults } = require('../../appActions.js');
|
||||
|
||||
test.describe('Recent Objects', () => {
|
||||
test('Recent Objects CRUD operations', async ({ page }) => {
|
||||
let recentObjectsList;
|
||||
let clock;
|
||||
let folderA;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Set Recent Objects List locator for subsequent tests
|
||||
recentObjectsList = page.getByRole('list', {
|
||||
name: 'Recent Objects'
|
||||
});
|
||||
|
||||
// Create a folder and nest a Clock within it
|
||||
const recentObjectsList = page.locator('[aria-label="Recent Objects"]');
|
||||
const folderA = await createDomainObjectWithDefaults(page, {
|
||||
folderA = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Folder'
|
||||
});
|
||||
const clock = await createDomainObjectWithDefaults(page, {
|
||||
clock = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Clock',
|
||||
parent: folderA.uuid
|
||||
});
|
||||
@@ -42,7 +49,8 @@ test.describe('Recent Objects', () => {
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(0, 100);
|
||||
await page.mouse.up();
|
||||
|
||||
});
|
||||
test('Recent Objects CRUD operations', async ({ page }) => {
|
||||
// Verify that both created objects appear in the list and are in the correct order
|
||||
expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeTruthy();
|
||||
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
|
||||
@@ -52,7 +60,7 @@ test.describe('Recent Objects', () => {
|
||||
expect(recentObjectsList.getByRole('listitem').nth(1).getByText(folderA.name)).toBeTruthy();
|
||||
|
||||
// Navigate to the folder by clicking on the main object name in the recent objects list item
|
||||
await recentObjectsList.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
|
||||
await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
|
||||
await page.waitForURL(`**/${folderA.uuid}?*`);
|
||||
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
|
||||
|
||||
@@ -63,7 +71,11 @@ test.describe('Recent Objects', () => {
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
// Verify rename has been applied in recent objects list item and objects paths
|
||||
expect(page.getByRole('listitem', { name: clock.name }).locator('a').getByText(folderA.name)).toBeTruthy();
|
||||
expect(await page.getByRole('navigation', {
|
||||
name: `${clock.name} Breadcrumb`
|
||||
}).locator('a').filter({
|
||||
hasText: folderA.name
|
||||
}).count()).toBeGreaterThan(0);
|
||||
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
|
||||
|
||||
// Delete
|
||||
@@ -79,7 +91,42 @@ test.describe('Recent Objects', () => {
|
||||
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
|
||||
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
|
||||
});
|
||||
test.fixme("Clicking on the 'target button' scrolls the object into view in the tree and highlights it");
|
||||
test.fixme("Clicking on an object in the path of a recent object navigates to the object");
|
||||
test.fixme("Tests for context menu actions from recent objects");
|
||||
test("Clicking on an object in the path of a recent object navigates to the object", async ({ page, openmctConfig }) => {
|
||||
const { myItemsFolderName } = openmctConfig;
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/6151'
|
||||
});
|
||||
await page.goto('./#/browse/mine');
|
||||
|
||||
// Navigate to the folder by clicking on its entry in the Clock's breadcrumb
|
||||
const waitForFolderNavigation = page.waitForURL(`**/${folderA.uuid}?*`);
|
||||
await page.getByRole('navigation', {
|
||||
name: `${clock.name} Breadcrumb`
|
||||
}).locator('a').filter({
|
||||
hasText: folderA.name
|
||||
}).click();
|
||||
|
||||
// Verify that the hash URL updates correctly
|
||||
await waitForFolderNavigation;
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
expect(page.url()).toMatch(new RegExp(`.*${folderA.uuid}\?.*`));
|
||||
|
||||
// Navigate to My Items by clicking on its entry in the Clock's breadcrumb
|
||||
const waitForMyItemsNavigation = page.waitForURL(`**/mine?*`);
|
||||
await page.getByRole('navigation', {
|
||||
name: `${clock.name} Breadcrumb`
|
||||
}).locator('a').filter({
|
||||
hasText: myItemsFolderName
|
||||
}).click();
|
||||
|
||||
// Verify that the hash URL updates correctly
|
||||
await waitForMyItemsNavigation;
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
expect(page.url()).toMatch(new RegExp(`.*mine\?.*`));
|
||||
});
|
||||
test.fixme("Clicking on the 'target button' scrolls the object into view in the tree and highlights it", async ({ page }) => {
|
||||
});
|
||||
test.fixme("Tests for context menu actions from recent objects", async ({ page }) => {
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,7 +116,9 @@ async function getAndAssertTreeItems(page, expected) {
|
||||
* @param {string} name
|
||||
*/
|
||||
async function expandTreePaneItemByName(page, name) {
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const treeItem = treePane.locator(`role=treeitem[expanded=false][name=/${name}/]`);
|
||||
const expandTriangle = treeItem.locator('.c-disclosure-triangle');
|
||||
await expandTriangle.click();
|
||||
|
||||
@@ -57,7 +57,7 @@ test.describe('Visual - Tree Pane', () => {
|
||||
name: 'Z Clock'
|
||||
});
|
||||
|
||||
const treePane = "#tree-pane";
|
||||
const treePane = "[role=tree][aria-label='Main Tree']";
|
||||
|
||||
await percySnapshot(page, `Tree Pane w/ collapsed tree (theme: ${theme})`, {
|
||||
scope: treePane
|
||||
@@ -94,7 +94,7 @@ test.describe('Visual - Tree Pane', () => {
|
||||
* @param {string} name
|
||||
*/
|
||||
async function expandTreePaneItemByName(page, name) {
|
||||
const treePane = page.locator('#tree-pane');
|
||||
const treePane = page.getByTestId('tree-pane');
|
||||
const treeItem = treePane.locator(`role=treeitem[expanded=false][name=/${name}/]`);
|
||||
const expandTriangle = treeItem.locator('.c-disclosure-triangle');
|
||||
await expandTriangle.click();
|
||||
|
||||
@@ -23,14 +23,18 @@
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export default class SinewaveLimitProvider extends EventEmitter {
|
||||
#openmct;
|
||||
#observingStaleness;
|
||||
#watchingTheClock;
|
||||
#isRealTime;
|
||||
|
||||
constructor(openmct) {
|
||||
super();
|
||||
|
||||
this.openmct = openmct;
|
||||
this.observingStaleness = {};
|
||||
this.watchingTheClock = false;
|
||||
this.isRealTime = undefined;
|
||||
this.#openmct = openmct;
|
||||
this.#observingStaleness = {};
|
||||
this.#watchingTheClock = false;
|
||||
this.#isRealTime = undefined;
|
||||
}
|
||||
|
||||
supportsStaleness(domainObject) {
|
||||
@@ -38,114 +42,116 @@ export default class SinewaveLimitProvider extends EventEmitter {
|
||||
}
|
||||
|
||||
isStale(domainObject, options) {
|
||||
if (!this.providingStaleness(domainObject)) {
|
||||
if (!this.#providingStaleness(domainObject)) {
|
||||
return Promise.resolve({
|
||||
isStale: false,
|
||||
utc: 0
|
||||
});
|
||||
}
|
||||
|
||||
const id = this.getObjectKeyString(domainObject);
|
||||
const id = this.#getObjectKeyString(domainObject);
|
||||
|
||||
if (!this.observerExists(id)) {
|
||||
this.createObserver(id);
|
||||
if (!this.#observerExists(id)) {
|
||||
this.#createObserver(id);
|
||||
}
|
||||
|
||||
return Promise.resolve(this.observingStaleness[id].isStale);
|
||||
return Promise.resolve(this.#observingStaleness[id].isStale);
|
||||
}
|
||||
|
||||
subscribeToStaleness(domainObject, callback) {
|
||||
const id = this.getObjectKeyString(domainObject);
|
||||
const id = this.#getObjectKeyString(domainObject);
|
||||
|
||||
if (this.isRealTime === undefined) {
|
||||
this.updateRealTime(this.openmct.time.clock());
|
||||
if (this.#isRealTime === undefined) {
|
||||
this.#updateRealTime(this.#openmct.time.clock());
|
||||
}
|
||||
|
||||
this.handleClockUpdate();
|
||||
this.#handleClockUpdate();
|
||||
|
||||
if (this.observerExists(id)) {
|
||||
this.addCallbackToObserver(id, callback);
|
||||
if (this.#observerExists(id)) {
|
||||
this.#addCallbackToObserver(id, callback);
|
||||
} else {
|
||||
this.createObserver(id, callback);
|
||||
this.#createObserver(id, callback);
|
||||
}
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
if (this.providingStaleness(domainObject)) {
|
||||
this.updateStaleness(id, !this.observingStaleness[id].isStale);
|
||||
if (this.#providingStaleness(domainObject)) {
|
||||
this.#updateStaleness(id, !this.#observingStaleness[id].isStale);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
this.updateStaleness(id, false);
|
||||
this.handleClockUpdate();
|
||||
this.destroyObserver(id);
|
||||
this.#updateStaleness(id, false);
|
||||
this.#handleClockUpdate();
|
||||
this.#destroyObserver(id);
|
||||
};
|
||||
}
|
||||
|
||||
handleClockUpdate() {
|
||||
let observers = Object.values(this.observingStaleness).length > 0;
|
||||
#handleClockUpdate() {
|
||||
let observers = Object.values(this.#observingStaleness).length > 0;
|
||||
|
||||
if (observers && !this.watchingTheClock) {
|
||||
this.watchingTheClock = true;
|
||||
this.openmct.time.on('clock', this.updateRealTime, this);
|
||||
} else if (!observers && this.watchingTheClock) {
|
||||
this.watchingTheClock = false;
|
||||
this.openmct.time.off('clock', this.updateRealTime, this);
|
||||
if (observers && !this.#watchingTheClock) {
|
||||
this.#watchingTheClock = true;
|
||||
this.#openmct.time.on('clock', this.#updateRealTime, this);
|
||||
} else if (!observers && this.#watchingTheClock) {
|
||||
this.#watchingTheClock = false;
|
||||
this.#openmct.time.off('clock', this.#updateRealTime, this);
|
||||
}
|
||||
}
|
||||
|
||||
updateRealTime(clock) {
|
||||
this.isRealTime = clock !== undefined;
|
||||
#updateRealTime(clock) {
|
||||
this.#isRealTime = clock !== undefined;
|
||||
|
||||
if (!this.isRealTime) {
|
||||
Object.keys(this.observingStaleness).forEach((id) => {
|
||||
this.updateStaleness(id, false);
|
||||
if (!this.#isRealTime) {
|
||||
Object.keys(this.#observingStaleness).forEach((id) => {
|
||||
this.#updateStaleness(id, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateStaleness(id, isStale) {
|
||||
this.observingStaleness[id].isStale = isStale;
|
||||
this.observingStaleness[id].utc = Date.now();
|
||||
this.observingStaleness[id].callback({
|
||||
isStale: this.observingStaleness[id].isStale,
|
||||
utc: this.observingStaleness[id].utc
|
||||
#updateStaleness(id, isStale) {
|
||||
this.#observingStaleness[id].isStale = isStale;
|
||||
this.#observingStaleness[id].utc = Date.now();
|
||||
this.#observingStaleness[id].callback({
|
||||
isStale: this.#observingStaleness[id].isStale,
|
||||
utc: this.#observingStaleness[id].utc
|
||||
});
|
||||
this.emit('stalenessEvent', {
|
||||
id,
|
||||
isStale: this.observingStaleness[id].isStale
|
||||
isStale: this.#observingStaleness[id].isStale
|
||||
});
|
||||
}
|
||||
|
||||
createObserver(id, callback) {
|
||||
this.observingStaleness[id] = {
|
||||
#createObserver(id, callback) {
|
||||
this.#observingStaleness[id] = {
|
||||
isStale: false,
|
||||
utc: Date.now()
|
||||
};
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
this.addCallbackToObserver(id, callback);
|
||||
this.#addCallbackToObserver(id, callback);
|
||||
}
|
||||
}
|
||||
|
||||
destroyObserver(id) {
|
||||
delete this.observingStaleness[id];
|
||||
#destroyObserver(id) {
|
||||
if (this.#observingStaleness[id]) {
|
||||
delete this.#observingStaleness[id];
|
||||
}
|
||||
}
|
||||
|
||||
providingStaleness(domainObject) {
|
||||
return domainObject.telemetry?.staleness === true && this.isRealTime;
|
||||
#providingStaleness(domainObject) {
|
||||
return domainObject.telemetry?.staleness === true && this.#isRealTime;
|
||||
}
|
||||
|
||||
getObjectKeyString(object) {
|
||||
return this.openmct.objects.makeKeyString(object.identifier);
|
||||
#getObjectKeyString(object) {
|
||||
return this.#openmct.objects.makeKeyString(object.identifier);
|
||||
}
|
||||
|
||||
addCallbackToObserver(id, callback) {
|
||||
this.observingStaleness[id].callback = callback;
|
||||
#addCallbackToObserver(id, callback) {
|
||||
this.#observingStaleness[id].callback = callback;
|
||||
}
|
||||
|
||||
observerExists(id) {
|
||||
return this.observingStaleness?.[id];
|
||||
#observerExists(id) {
|
||||
return this.#observingStaleness?.[id];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,35 @@
|
||||
import CompositionAPI from './CompositionAPI';
|
||||
import { createOpenMct, resetApplicationState } from '../../utils/testing';
|
||||
import CompositionCollection from './CompositionCollection';
|
||||
|
||||
describe('The Composition API', function () {
|
||||
let publicAPI;
|
||||
let compositionAPI;
|
||||
let topicService;
|
||||
let mutationTopic;
|
||||
|
||||
beforeEach(function () {
|
||||
beforeEach(function (done) {
|
||||
publicAPI = createOpenMct();
|
||||
compositionAPI = publicAPI.composition;
|
||||
|
||||
mutationTopic = jasmine.createSpyObj('mutationTopic', [
|
||||
'listen'
|
||||
]);
|
||||
topicService = jasmine.createSpy('topicService');
|
||||
topicService.and.returnValue(mutationTopic);
|
||||
publicAPI = {};
|
||||
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
|
||||
'get',
|
||||
'mutate',
|
||||
'observe',
|
||||
'areIdsEqual'
|
||||
const mockObjectProvider = jasmine.createSpyObj("mock provider", [
|
||||
"create",
|
||||
"update",
|
||||
"get"
|
||||
]);
|
||||
|
||||
publicAPI.objects.areIdsEqual.and.callFake(function (id1, id2) {
|
||||
return id1.namespace === id2.namespace && id1.key === id2.key;
|
||||
mockObjectProvider.create.and.returnValue(Promise.resolve(true));
|
||||
mockObjectProvider.update.and.returnValue(Promise.resolve(true));
|
||||
mockObjectProvider.get.and.callFake((identifier) => {
|
||||
return Promise.resolve({identifier});
|
||||
});
|
||||
|
||||
publicAPI.composition = jasmine.createSpyObj('CompositionAPI', [
|
||||
'checkPolicy'
|
||||
]);
|
||||
publicAPI.composition.checkPolicy.and.returnValue(true);
|
||||
publicAPI.objects.addProvider('test', mockObjectProvider);
|
||||
publicAPI.objects.addProvider('custom', mockObjectProvider);
|
||||
|
||||
publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [
|
||||
'on'
|
||||
]);
|
||||
publicAPI.objects.get.and.callFake(function (identifier) {
|
||||
return Promise.resolve({identifier: identifier});
|
||||
});
|
||||
publicAPI.$injector = jasmine.createSpyObj('$injector', [
|
||||
'get'
|
||||
]);
|
||||
publicAPI.$injector.get.and.returnValue(topicService);
|
||||
compositionAPI = new CompositionAPI(publicAPI);
|
||||
publicAPI.on('start', done);
|
||||
publicAPI.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
return resetApplicationState(publicAPI);
|
||||
});
|
||||
|
||||
it('returns falsy if an object does not support composition', function () {
|
||||
@@ -106,6 +94,9 @@ describe('The Composition API', function () {
|
||||
let listener;
|
||||
beforeEach(function () {
|
||||
listener = jasmine.createSpy('reorderListener');
|
||||
spyOn(publicAPI.objects, 'mutate');
|
||||
publicAPI.objects.mutate.and.callThrough();
|
||||
|
||||
composition.on('reorder', listener);
|
||||
|
||||
return composition.load();
|
||||
@@ -136,18 +127,20 @@ describe('The Composition API', function () {
|
||||
});
|
||||
});
|
||||
it('supports adding an object to composition', function () {
|
||||
let addListener = jasmine.createSpy('addListener');
|
||||
let mockChildObject = {
|
||||
identifier: {
|
||||
key: 'mock-key',
|
||||
namespace: ''
|
||||
}
|
||||
};
|
||||
composition.on('add', addListener);
|
||||
composition.add(mockChildObject);
|
||||
|
||||
expect(domainObject.composition.length).toBe(4);
|
||||
expect(domainObject.composition[3]).toEqual(mockChildObject.identifier);
|
||||
return new Promise((resolve) => {
|
||||
composition.on('add', resolve);
|
||||
composition.add(mockChildObject);
|
||||
}).then(() => {
|
||||
expect(domainObject.composition.length).toBe(4);
|
||||
expect(domainObject.composition[3]).toEqual(mockChildObject.identifier);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ export default class CompositionProvider {
|
||||
* @private
|
||||
* @param {DomainObject} oldDomainObject
|
||||
*/
|
||||
#onMutation(oldDomainObject) {
|
||||
#onMutation(newDomainObject, oldDomainObject) {
|
||||
const id = objectUtils.makeKeyString(oldDomainObject.identifier);
|
||||
const listeners = this.#listeningTo[id];
|
||||
|
||||
@@ -232,8 +232,8 @@ export default class CompositionProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldComposition = listeners.composition.map(objectUtils.makeKeyString);
|
||||
const newComposition = oldDomainObject.composition.map(objectUtils.makeKeyString);
|
||||
const oldComposition = oldDomainObject.composition.map(objectUtils.makeKeyString);
|
||||
const newComposition = newDomainObject.composition.map(objectUtils.makeKeyString);
|
||||
|
||||
const added = _.difference(newComposition, oldComposition).map(objectUtils.parseKeyString);
|
||||
const removed = _.difference(oldComposition, newComposition).map(objectUtils.parseKeyString);
|
||||
@@ -248,8 +248,6 @@ export default class CompositionProvider {
|
||||
};
|
||||
}
|
||||
|
||||
listeners.composition = newComposition.map(objectUtils.parseKeyString);
|
||||
|
||||
added.forEach(function (addedChild) {
|
||||
listeners.add.forEach(notify(addedChild));
|
||||
});
|
||||
|
||||
@@ -99,8 +99,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
|
||||
objectListeners = this.listeningTo[keyString] = {
|
||||
add: [],
|
||||
remove: [],
|
||||
reorder: [],
|
||||
composition: [].slice.apply(domainObject.composition)
|
||||
reorder: []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -172,8 +171,9 @@ export default class DefaultCompositionProvider extends CompositionProvider {
|
||||
*/
|
||||
add(parent, childId) {
|
||||
if (!this.includes(parent, childId)) {
|
||||
parent.composition.push(childId);
|
||||
this.publicAPI.objects.mutate(parent, 'composition', parent.composition);
|
||||
const composition = structuredClone(parent.composition);
|
||||
composition.push(childId);
|
||||
this.publicAPI.objects.mutate(parent, 'composition', composition);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
<template>
|
||||
<mct-tree
|
||||
id="locator-tree"
|
||||
:is-selector-tree="true"
|
||||
:initial-selection="model.parent"
|
||||
@tree-item-selection="handleItemSelection"
|
||||
|
||||
@@ -75,21 +75,23 @@ class MutableDomainObject {
|
||||
return eventOff;
|
||||
}
|
||||
$set(path, value) {
|
||||
const oldModel = JSON.parse(JSON.stringify(this));
|
||||
const oldValue = _.get(oldModel, path);
|
||||
MutableDomainObject.mutateObject(this, path, value);
|
||||
|
||||
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
|
||||
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
|
||||
|
||||
//Emit a general "any object" event
|
||||
this._globalEventEmitter.emit(ANY_OBJECT_EVENT, this);
|
||||
this._globalEventEmitter.emit(ANY_OBJECT_EVENT, this, oldModel);
|
||||
//Emit wildcard event, with path so that callback knows what changed
|
||||
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this, path, value);
|
||||
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this, path, value, oldModel, oldValue);
|
||||
|
||||
//Emit events specific to properties affected
|
||||
let parentPropertiesList = path.split('.');
|
||||
for (let index = parentPropertiesList.length; index > 0; index--) {
|
||||
let parentPropertyPath = parentPropertiesList.slice(0, index).join('.');
|
||||
this._globalEventEmitter.emit(qualifiedEventName(this, parentPropertyPath), _.get(this, parentPropertyPath));
|
||||
this._globalEventEmitter.emit(qualifiedEventName(this, parentPropertyPath), _.get(this, parentPropertyPath), _.get(oldModel, parentPropertyPath));
|
||||
}
|
||||
|
||||
//TODO: Emit events for listeners of child properties when parent changes.
|
||||
|
||||
@@ -648,7 +648,7 @@ export default class ObjectAPI {
|
||||
* @param {module:openmct.DomainObject} object the object to observe
|
||||
* @param {string} path the property to observe
|
||||
* @param {Function} callback a callback to invoke when new values for
|
||||
* this property are observed
|
||||
* this property are observed.
|
||||
* @method observe
|
||||
* @memberof module:openmct.ObjectAPI#
|
||||
*/
|
||||
|
||||
@@ -399,7 +399,7 @@ describe("The Object API", () => {
|
||||
unlisten = objectAPI.observe(mutableSecondInstance, 'otherAttribute', mutationCallback);
|
||||
objectAPI.mutate(mutable, 'otherAttribute', 'some-new-value');
|
||||
}).then(function () {
|
||||
expect(mutationCallback).toHaveBeenCalledWith('some-new-value');
|
||||
expect(mutationCallback).toHaveBeenCalledWith('some-new-value', 'other-attribute-value');
|
||||
unlisten();
|
||||
});
|
||||
});
|
||||
@@ -419,14 +419,20 @@ describe("The Object API", () => {
|
||||
|
||||
objectAPI.mutate(mutable, 'objectAttribute.embeddedObject.embeddedKey', 'updated-embedded-value');
|
||||
}).then(function () {
|
||||
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value');
|
||||
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value', 'embedded-value');
|
||||
expect(embeddedObjectCallback).toHaveBeenCalledWith({
|
||||
embeddedKey: 'updated-embedded-value'
|
||||
}, {
|
||||
embeddedKey: 'embedded-value'
|
||||
});
|
||||
expect(objectAttributeCallback).toHaveBeenCalledWith({
|
||||
embeddedObject: {
|
||||
embeddedKey: 'updated-embedded-value'
|
||||
}
|
||||
}, {
|
||||
embeddedObject: {
|
||||
embeddedKey: 'embedded-value'
|
||||
}
|
||||
});
|
||||
|
||||
listeners.forEach(listener => listener());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="c-overlay">
|
||||
<div class="c-overlay js-overlay">
|
||||
<div
|
||||
class="c-overlay__blocker"
|
||||
@click="destroy"
|
||||
@@ -26,7 +26,7 @@
|
||||
v-for="(button, index) in buttons"
|
||||
ref="buttons"
|
||||
:key="index"
|
||||
class="c-button"
|
||||
class="c-button js-overlay__button"
|
||||
tabindex="0"
|
||||
:class="{'c-button--major': focusIndex===index}"
|
||||
@focus="focusIndex=index"
|
||||
|
||||
@@ -218,6 +218,7 @@ export default {
|
||||
this.stalenessSubscription[keystring].unsubscribe();
|
||||
this.stalenessSubscription[keystring].stalenessUtils.destroy();
|
||||
this.handleStaleness(keystring, { isStale: false }, SKIP_CHECK);
|
||||
delete this.stalenessSubscription[keystring];
|
||||
};
|
||||
},
|
||||
handleStaleness(id, stalenessResponse, skipCheck = false) {
|
||||
|
||||
@@ -232,7 +232,9 @@ export default {
|
||||
this.stalenessSubscription[keyString].stalenessUtils = new StalenessUtils(this.openmct, domainObject);
|
||||
|
||||
this.openmct.telemetry.isStale(domainObject).then((stalenessResponse) => {
|
||||
this.hanldeStaleness(keyString, stalenessResponse);
|
||||
if (stalenessResponse !== undefined) {
|
||||
this.hanldeStaleness(keyString, stalenessResponse);
|
||||
}
|
||||
});
|
||||
const stalenessSubscription = this.openmct.telemetry.subscribeToStaleness(domainObject, (stalenessResponse) => {
|
||||
this.hanldeStaleness(keyString, stalenessResponse);
|
||||
@@ -259,6 +261,7 @@ export default {
|
||||
keyString,
|
||||
isStale: false
|
||||
});
|
||||
delete this.stalenessSubscription[keyString];
|
||||
}
|
||||
},
|
||||
hanldeStaleness(keyString, stalenessResponse) {
|
||||
|
||||
@@ -83,6 +83,11 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
if (!this.stalenessSubscription[id]) {
|
||||
this.stalenessSubscription[id] = {};
|
||||
this.stalenessSubscription[id].stalenessUtils = new StalenessUtils(this.openmct, telemetryObject);
|
||||
this.openmct.telemetry.isStale(telemetryObject).then((stalenessResponse) => {
|
||||
if (stalenessResponse !== undefined) {
|
||||
this.handleStaleTelemetry(id, stalenessResponse);
|
||||
}
|
||||
});
|
||||
this.stalenessSubscription[id].unsubscribe = this.openmct.telemetry.subscribeToStaleness(
|
||||
telemetryObject,
|
||||
(stalenessResponse) => {
|
||||
|
||||
@@ -59,7 +59,7 @@ export default class CreateAction extends PropertiesAction {
|
||||
_.set(this.domainObject, key, value);
|
||||
});
|
||||
|
||||
const parentDomainObject = parentDomainObjectPath[0];
|
||||
const parentDomainObject = this.openmct.objects.toMutable(parentDomainObjectPath[0]);
|
||||
|
||||
this.domainObject.modified = Date.now();
|
||||
this.domainObject.location = this.openmct.objects.makeKeyString(parentDomainObject.identifier);
|
||||
@@ -85,6 +85,7 @@ export default class CreateAction extends PropertiesAction {
|
||||
console.error(err);
|
||||
this.openmct.notifications.error(`Error saving objects: ${err}`);
|
||||
} finally {
|
||||
this.openmct.objects.destroyMutable(parentDomainObject);
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
@@ -142,18 +143,21 @@ export default class CreateAction extends PropertiesAction {
|
||||
}
|
||||
};
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.domainObject = this.openmct.objects.toMutable(domainObject);
|
||||
|
||||
if (definition.initialize) {
|
||||
definition.initialize(domainObject);
|
||||
definition.initialize(this.domainObject);
|
||||
}
|
||||
|
||||
const createWizard = new CreateWizard(this.openmct, domainObject, this.parentDomainObject);
|
||||
const createWizard = new CreateWizard(this.openmct, this.domainObject, this.parentDomainObject);
|
||||
const formStructure = createWizard.getFormStructure(true);
|
||||
formStructure.title = 'Create a New ' + definition.name;
|
||||
|
||||
this.openmct.forms.showForm(formStructure)
|
||||
.then(this._onSave.bind(this))
|
||||
.catch(this._onCancel.bind(this));
|
||||
.catch(this._onCancel.bind(this))
|
||||
.finally(() => {
|
||||
this.openmct.objects.destroyMutable(this.domainObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,8 @@ export default class MoveAction {
|
||||
this.addToNewParent(this.object, parent);
|
||||
this.removeFromOldParent(this.object);
|
||||
|
||||
await this.saveTransaction();
|
||||
|
||||
if (!inNavigationPath) {
|
||||
return;
|
||||
}
|
||||
@@ -102,8 +104,6 @@ export default class MoveAction {
|
||||
}
|
||||
}
|
||||
|
||||
await this.saveTransaction();
|
||||
|
||||
this.navigateTo(newObjectPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -381,7 +381,7 @@ export default {
|
||||
});
|
||||
},
|
||||
updateSelection(selection) {
|
||||
if (selection?.[0]?.[1]?.context?.targetDetails?.entryId === undefined) {
|
||||
if (selection?.[0]?.[0]?.context?.targetDetails?.entryId === undefined) {
|
||||
this.selectedEntryId = '';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<div class="c-ne__tags c-tag-holder">
|
||||
<div
|
||||
v-for="(tag, index) in entryTags"
|
||||
:key="index"
|
||||
@@ -472,16 +472,11 @@ export default {
|
||||
targetDomainObjects[keyString] = this.domainObject;
|
||||
this.openmct.selection.select(
|
||||
[
|
||||
{
|
||||
element: this.openmct.layout.$refs.browseObject.$el,
|
||||
context: {
|
||||
item: this.domainObject
|
||||
}
|
||||
},
|
||||
{
|
||||
element: event.currentTarget,
|
||||
context: {
|
||||
type: 'notebook-entry-selection',
|
||||
item: this.domainObject,
|
||||
targetDetails,
|
||||
targetDomainObjects,
|
||||
annotations: this.notebookAnnotations,
|
||||
|
||||
@@ -105,10 +105,6 @@ function installBaseNotebookFunctionality(openmct) {
|
||||
|
||||
function NotebookPlugin(name = 'Notebook', entryUrlWhitelist = []) {
|
||||
return function install(openmct) {
|
||||
if (openmct[NOTEBOOK_INSTALLED_KEY]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const icon = 'icon-notebook';
|
||||
const description = 'Create and save timestamped notes with embedded object snapshots.';
|
||||
const snapshotContainer = getSnapshotContainer(openmct);
|
||||
@@ -122,8 +118,6 @@ function NotebookPlugin(name = 'Notebook', entryUrlWhitelist = []) {
|
||||
openmct.objectViews.addProvider(notebookView, entryUrlWhitelist);
|
||||
|
||||
installBaseNotebookFunctionality(openmct);
|
||||
|
||||
openmct[NOTEBOOK_INSTALLED_KEY] = true;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,16 +23,8 @@
|
||||
<div
|
||||
v-if="loaded"
|
||||
class="gl-plot"
|
||||
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
|
||||
>
|
||||
<plot-legend
|
||||
v-if="!isNestedWithinAStackedPlot"
|
||||
:cursor-locked="!!lockHighlightPoint"
|
||||
:series="seriesModels"
|
||||
:highlights="highlights"
|
||||
:legend="legend"
|
||||
@legendHoverChanged="legendHoverChanged"
|
||||
/>
|
||||
<slot></slot>
|
||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
||||
<div
|
||||
v-if="seriesModels.length"
|
||||
@@ -42,13 +34,14 @@
|
||||
v-for="(yAxis, index) in yAxesIds"
|
||||
:id="yAxis.id"
|
||||
:key="`yAxis-${yAxis.id}-${index}`"
|
||||
:multiple-left-axes="multipleLeftAxes"
|
||||
:has-multiple-left-axes="hasMultipleLeftAxes"
|
||||
:position="yAxis.id > 2 ? 'right' : 'left'"
|
||||
:class="{'plot-yaxis-right': yAxis.id > 2}"
|
||||
:tick-width="yAxis.tickWidth"
|
||||
:used-tick-width="plotFirstLeftTickWidth"
|
||||
:plot-left-tick-width="yAxis.id > 2 ? yAxis.tickWidth: plotLeftTickWidth"
|
||||
@yKeyChanged="setYAxisKey"
|
||||
@tickWidthChanged="onTickWidthChange"
|
||||
@plotYTickWidth="onYTickWidthChange"
|
||||
@toggleAxisVisibility="toggleSeriesForYAxis"
|
||||
/>
|
||||
</div>
|
||||
@@ -69,7 +62,6 @@
|
||||
v-show="gridLines && !options.compact"
|
||||
:axis-type="'xAxis'"
|
||||
:position="'right'"
|
||||
@plotTickWidth="onTickWidthChange"
|
||||
/>
|
||||
|
||||
<mct-ticks
|
||||
@@ -79,7 +71,7 @@
|
||||
:axis-type="'yAxis'"
|
||||
:position="'bottom'"
|
||||
:axis-id="yAxis.id"
|
||||
@plotTickWidth="onTickWidthChange"
|
||||
@plotTickWidth="onYTickWidthChange"
|
||||
/>
|
||||
|
||||
<div
|
||||
@@ -94,7 +86,6 @@
|
||||
:highlights="highlights"
|
||||
:annotated-points="annotatedPoints"
|
||||
:annotation-selections="annotationSelections"
|
||||
:show-limit-line-labels="showLimitLineLabels"
|
||||
:hidden-y-axis-ids="hiddenYAxisIds"
|
||||
:annotation-viewing-and-editing-allowed="annotationViewingAndEditingAllowed"
|
||||
@plotReinitializeCanvas="initCanvas"
|
||||
@@ -217,7 +208,6 @@ import LinearScale from "./LinearScale";
|
||||
import PlotConfigurationModel from './configuration/PlotConfigurationModel';
|
||||
import configStore from './configuration/ConfigStore';
|
||||
|
||||
import PlotLegend from "./legend/PlotLegend.vue";
|
||||
import MctTicks from "./MctTicks.vue";
|
||||
import MctChart from "./chart/MctChart.vue";
|
||||
import XAxis from "./axis/XAxis.vue";
|
||||
@@ -232,7 +222,6 @@ export default {
|
||||
components: {
|
||||
XAxis,
|
||||
YAxis,
|
||||
PlotLegend,
|
||||
MctTicks,
|
||||
MctChart
|
||||
},
|
||||
@@ -258,10 +247,14 @@ export default {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
plotTickWidth: {
|
||||
type: Number,
|
||||
parentYTickWidth: {
|
||||
type: Object,
|
||||
default() {
|
||||
return 0;
|
||||
return {
|
||||
leftTickWidth: 0,
|
||||
rightTickWidth: 0,
|
||||
hasMultipleLeftAxes: false
|
||||
};
|
||||
}
|
||||
},
|
||||
limitLineLabels: {
|
||||
@@ -296,7 +289,6 @@ export default {
|
||||
isRealTime: this.openmct.time.clock() !== undefined,
|
||||
loaded: false,
|
||||
isTimeOutOfSync: false,
|
||||
showLimitLineLabels: this.limitLineLabels,
|
||||
isFrozenOnMouseDown: false,
|
||||
cursorGuide: this.initCursorGuide,
|
||||
gridLines: this.initGridLines,
|
||||
@@ -308,13 +300,14 @@ export default {
|
||||
computed: {
|
||||
xAxisStyle() {
|
||||
const rightAxis = this.yAxesIds.find(yAxis => yAxis.id > 2);
|
||||
const leftOffset = this.multipleLeftAxes ? 2 * AXES_PADDING : AXES_PADDING;
|
||||
const leftOffset = this.hasMultipleLeftAxes ? 2 * AXES_PADDING : AXES_PADDING;
|
||||
let style = {
|
||||
left: `${this.plotLeftTickWidth + leftOffset}px`
|
||||
};
|
||||
const parentRightAxisWidth = this.parentYTickWidth.rightTickWidth;
|
||||
|
||||
if (rightAxis) {
|
||||
style.right = `${rightAxis.tickWidth + AXES_PADDING}px`;
|
||||
if (parentRightAxisWidth || rightAxis) {
|
||||
style.right = `${(parentRightAxisWidth || rightAxis.tickWidth) + AXES_PADDING}px`;
|
||||
}
|
||||
|
||||
return style;
|
||||
@@ -322,8 +315,8 @@ export default {
|
||||
yAxesIds() {
|
||||
return this.yAxes.filter(yAxis => yAxis.seriesCount > 0);
|
||||
},
|
||||
multipleLeftAxes() {
|
||||
return this.yAxes.filter(yAxis => yAxis.seriesCount > 0 && yAxis.id <= 2).length > 1;
|
||||
hasMultipleLeftAxes() {
|
||||
return this.parentYTickWidth.hasMultipleLeftAxes || this.yAxes.filter(yAxis => yAxis.seriesCount > 0 && yAxis.id <= 2).length > 1;
|
||||
},
|
||||
isNestedWithinAStackedPlot() {
|
||||
const isNavigatedObject = this.openmct.router.isNavigatedObject([this.domainObject].concat(this.path));
|
||||
@@ -334,22 +327,13 @@ export default {
|
||||
return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
|
||||
},
|
||||
annotationViewingAndEditingAllowed() {
|
||||
// only allow annotations viewing/editing if plot is paused or in fixed time mode
|
||||
// only allow annotations viewing/editing if plot is paused or in fixed time mode
|
||||
return this.isFrozen || !this.isRealTime;
|
||||
},
|
||||
plotLegendPositionClass() {
|
||||
return !this.isNestedWithinAStackedPlot ? `plot-legend-${this.config.legend.get('position')}` : '';
|
||||
},
|
||||
plotLegendExpandedStateClass() {
|
||||
if (this.isNestedWithinAStackedPlot) {
|
||||
return '';
|
||||
}
|
||||
plotFirstLeftTickWidth() {
|
||||
const firstYAxis = this.yAxes.find(yAxis => yAxis.id === 1);
|
||||
|
||||
if (this.config.legend.get('expanded')) {
|
||||
return 'plot-legend-expanded';
|
||||
} else {
|
||||
return 'plot-legend-collapsed';
|
||||
}
|
||||
return firstYAxis ? firstYAxis.tickWidth : 0;
|
||||
},
|
||||
plotLeftTickWidth() {
|
||||
let leftTickWidth = 0;
|
||||
@@ -360,17 +344,12 @@ export default {
|
||||
|
||||
leftTickWidth = leftTickWidth + yAxis.tickWidth;
|
||||
});
|
||||
const parentLeftTickWidth = this.parentYTickWidth.leftTickWidth;
|
||||
|
||||
return this.plotTickWidth || leftTickWidth;
|
||||
return parentLeftTickWidth || leftTickWidth;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
limitLineLabels: {
|
||||
handler(limitLineLabels) {
|
||||
this.legendHoverChanged(limitLineLabels);
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
initGridLines(newGridLines) {
|
||||
this.gridLines = newGridLines;
|
||||
},
|
||||
@@ -406,8 +385,7 @@ export default {
|
||||
}));
|
||||
}
|
||||
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.$emit('configLoaded', configId);
|
||||
this.$emit('configLoaded', true);
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
||||
@@ -439,15 +417,20 @@ export default {
|
||||
methods: {
|
||||
updateSelection(selection) {
|
||||
const selectionContext = selection?.[0]?.[0]?.context?.item;
|
||||
if (!selectionContext
|
||||
|| this.openmct.objects.areIdsEqual(selectionContext.identifier, this.domainObject.identifier)) {
|
||||
// Selection changed, but it's us, so ignoring it
|
||||
// on clicking on a search result we highlight the annotation and zoom - we know it's an annotation result when isAnnotationSearchResult === true
|
||||
// We shouldn't zoom when we're selecting existing annotations to view them or creating new annotations.
|
||||
const selectionType = selection?.[0]?.[0]?.context?.type;
|
||||
const validSelectionTypes = ['clicked-on-plot-selection', 'plot-annotation-search-result'];
|
||||
const isAnnotationSearchResult = selectionType === 'plot-annotation-search-result';
|
||||
|
||||
if (!validSelectionTypes.includes(selectionType)) {
|
||||
// wrong type of selection
|
||||
return;
|
||||
}
|
||||
|
||||
const selectionType = selection?.[0]?.[1]?.context?.type;
|
||||
if (selectionType !== 'plot-points-selection') {
|
||||
// wrong type of selection
|
||||
if (selectionContext
|
||||
&& (!isAnnotationSearchResult)
|
||||
&& this.openmct.objects.areIdsEqual(selectionContext.identifier, this.domainObject.identifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -460,7 +443,18 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedAnnotations = selection?.[0]?.[1]?.context?.annotations;
|
||||
const selectedAnnotations = selection?.[0]?.[0]?.context?.annotations;
|
||||
//This section is only for the annotations search results entry to displaying annotations
|
||||
if (isAnnotationSearchResult) {
|
||||
this.showAnnotationsFromSearchResults(selectedAnnotations);
|
||||
}
|
||||
|
||||
//This section is common to all entry points for annotation display
|
||||
this.prepareExistingAnnotationSelection(selectedAnnotations);
|
||||
},
|
||||
showAnnotationsFromSearchResults(selectedAnnotations) {
|
||||
//Start section
|
||||
|
||||
if (selectedAnnotations?.length) {
|
||||
// just use first annotation
|
||||
const boundingBoxes = Object.values(selectedAnnotations[0].targets);
|
||||
@@ -494,10 +488,9 @@ export default {
|
||||
min: minY,
|
||||
max: maxY
|
||||
});
|
||||
//Zoom out just a touch so that the highlighted section for annotations doesn't take over the whole view - which is not a nice look.
|
||||
this.zoom('out', 0.2);
|
||||
}
|
||||
|
||||
this.prepareExistingAnnotationSelection(selectedAnnotations);
|
||||
},
|
||||
handleKeyDown(event) {
|
||||
if (event.key === 'Alt') {
|
||||
@@ -575,6 +568,14 @@ export default {
|
||||
updateTicksAndSeriesForYAxis(newAxisId, oldAxisId) {
|
||||
this.updateAxisUsageCount(oldAxisId, -1);
|
||||
this.updateAxisUsageCount(newAxisId, 1);
|
||||
|
||||
const foundYAxis = this.yAxes.find(yAxis => yAxis.id === oldAxisId);
|
||||
if (foundYAxis.seriesCount === 0) {
|
||||
this.onYTickWidthChange({
|
||||
width: foundYAxis.tickWidth,
|
||||
yAxisId: foundYAxis.id
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updateAxisUsageCount(yAxisId, updateCountBy) {
|
||||
@@ -688,9 +689,15 @@ export default {
|
||||
series.reset();
|
||||
});
|
||||
},
|
||||
shareCommonParent(domainObjectToFind) {
|
||||
return false;
|
||||
},
|
||||
compositionPathContainsId(domainObjectToFind) {
|
||||
if (!domainObjectToFind.composition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
compositionPathContainsId(domainObjectToClear) {
|
||||
return domainObjectToClear.composition.some((compositionIdentifier) => {
|
||||
return domainObjectToFind.composition.some((compositionIdentifier) => {
|
||||
return this.openmct.objects.areIdsEqual(compositionIdentifier, this.domainObject.identifier);
|
||||
});
|
||||
},
|
||||
@@ -820,21 +827,29 @@ export default {
|
||||
|
||||
marqueeAnnotations(annotationsToSelect) {
|
||||
annotationsToSelect.forEach(annotationToSelect => {
|
||||
const firstTargetKeyString = Object.keys(annotationToSelect.targets)[0];
|
||||
const firstTarget = annotationToSelect.targets[firstTargetKeyString];
|
||||
const rectangle = {
|
||||
start: {
|
||||
x: firstTarget.minX,
|
||||
y: firstTarget.minY
|
||||
},
|
||||
end: {
|
||||
x: firstTarget.maxX,
|
||||
y: firstTarget.maxY
|
||||
},
|
||||
color: [1, 1, 1, 0.10]
|
||||
};
|
||||
this.rectangles.push(rectangle);
|
||||
Object.keys(annotationToSelect.targets).forEach(targetKeyString => {
|
||||
const target = annotationToSelect.targets[targetKeyString];
|
||||
const series = this.seriesModels.find(seriesModel => seriesModel.keyString === targetKeyString);
|
||||
if (!series) {
|
||||
return;
|
||||
}
|
||||
|
||||
const yAxisId = series.get('yAxisId');
|
||||
const rectangle = {
|
||||
start: {
|
||||
x: target.minX,
|
||||
y: [target.minY],
|
||||
yAxisIds: [yAxisId]
|
||||
},
|
||||
end: {
|
||||
x: target.maxX,
|
||||
y: [target.maxY],
|
||||
yAxisIds: [yAxisId]
|
||||
},
|
||||
color: [1, 1, 1, 0.10]
|
||||
};
|
||||
this.rectangles.push(rectangle);
|
||||
});
|
||||
});
|
||||
},
|
||||
gatherNearbyAnnotations() {
|
||||
@@ -938,8 +953,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
onTickWidthChange(data, fromDifferentObject) {
|
||||
const {width, yAxisId} = data;
|
||||
/**
|
||||
* Aggregate widths of all left and right y axes and send them up to any parent plots
|
||||
* @param {Object} tickWidthWithYAxisId - the width and yAxisId of the tick bar
|
||||
* @param fromDifferentObject
|
||||
*/
|
||||
onYTickWidthChange(tickWidthWithYAxisId, fromDifferentObject) {
|
||||
const {width, yAxisId} = tickWidthWithYAxisId;
|
||||
if (yAxisId) {
|
||||
const index = this.yAxes.findIndex(yAxis => yAxis.id === yAxisId);
|
||||
if (fromDifferentObject) {
|
||||
@@ -948,13 +968,23 @@ export default {
|
||||
} else {
|
||||
// Otherwise, only accept tick with if it's larger.
|
||||
const newWidth = Math.max(width, this.yAxes[index].tickWidth);
|
||||
if (newWidth !== this.yAxes[index].tickWidth) {
|
||||
if (width !== this.yAxes[index].tickWidth) {
|
||||
this.yAxes[index].tickWidth = newWidth;
|
||||
}
|
||||
}
|
||||
|
||||
const id = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.$emit('plotTickWidth', this.yAxes[index].tickWidth, id);
|
||||
const leftTickWidth = this.yAxes.filter(yAxis => yAxis.id < 3).reduce((previous, current) => {
|
||||
return previous + current.tickWidth;
|
||||
}, 0);
|
||||
const rightTickWidth = this.yAxes.filter(yAxis => yAxis.id > 2).reduce((previous, current) => {
|
||||
return previous + current.tickWidth;
|
||||
}, 0);
|
||||
this.$emit('plotYTickWidth', {
|
||||
hasMultipleLeftAxes: this.hasMultipleLeftAxes,
|
||||
leftTickWidth,
|
||||
rightTickWidth
|
||||
}, id);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1036,8 +1066,6 @@ export default {
|
||||
|
||||
highlightValues(point) {
|
||||
this.highlightPoint = point;
|
||||
// TODO: used in StackedPlotController
|
||||
this.$emit('plotHighlightUpdate', point);
|
||||
if (this.lockHighlightPoint) {
|
||||
return;
|
||||
}
|
||||
@@ -1149,7 +1177,7 @@ export default {
|
||||
endPixels: this.positionOverElement,
|
||||
start: this.positionOverPlot,
|
||||
end: this.positionOverPlot,
|
||||
color: [1, 1, 1, 0.5]
|
||||
color: [1, 1, 1, 0.25]
|
||||
};
|
||||
if (annotationEvent) {
|
||||
this.marquee.annotationEvent = true;
|
||||
@@ -1160,13 +1188,21 @@ export default {
|
||||
}
|
||||
},
|
||||
selectNearbyAnnotations(event) {
|
||||
// need to stop propagation right away to prevent selecting the plot itself
|
||||
event.stopPropagation();
|
||||
|
||||
if (!this.annotationViewingAndEditingAllowed || this.annotationSelections.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nearbyAnnotations = this.gatherNearbyAnnotations();
|
||||
if (!nearbyAnnotations.length) {
|
||||
const emptySelection = this.createPathSelection();
|
||||
this.openmct.selection.select(emptySelection, true);
|
||||
// should show plot itself if we didn't find any annotations
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { targetDomainObjects, targetDetails } = this.prepareExistingAnnotationSelection(nearbyAnnotations);
|
||||
this.selectPlotAnnotations({
|
||||
targetDetails,
|
||||
@@ -1174,43 +1210,56 @@ export default {
|
||||
annotations: nearbyAnnotations
|
||||
});
|
||||
},
|
||||
createPathSelection() {
|
||||
let selection = [];
|
||||
this.path.forEach((pathObject, index) => {
|
||||
selection.push({
|
||||
element: this.openmct.layout.$refs.browseObject.$el,
|
||||
context: {
|
||||
item: pathObject
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return selection;
|
||||
},
|
||||
selectPlotAnnotations({targetDetails, targetDomainObjects, annotations}) {
|
||||
const selection =
|
||||
[
|
||||
{
|
||||
element: this.openmct.layout.$refs.browseObject.$el,
|
||||
context: {
|
||||
item: this.domainObject
|
||||
}
|
||||
},
|
||||
{
|
||||
element: this.$el,
|
||||
context: {
|
||||
type: 'plot-points-selection',
|
||||
targetDetails,
|
||||
targetDomainObjects,
|
||||
annotations,
|
||||
annotationType: this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL,
|
||||
onAnnotationChange: this.onAnnotationChange
|
||||
}
|
||||
}
|
||||
];
|
||||
const annotationContext = {
|
||||
type: 'clicked-on-plot-selection',
|
||||
targetDetails,
|
||||
targetDomainObjects,
|
||||
annotations,
|
||||
annotationType: this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL,
|
||||
onAnnotationChange: this.onAnnotationChange
|
||||
};
|
||||
const selection = this.createPathSelection();
|
||||
if (selection.length && this.openmct.objects.areIdsEqual(selection[0].context.item.identifier, this.domainObject.identifier)) {
|
||||
selection[0].context = {
|
||||
...selection[0].context,
|
||||
...annotationContext
|
||||
};
|
||||
} else {
|
||||
selection.unshift({
|
||||
element: this.$el,
|
||||
context: {
|
||||
item: this.domainObject,
|
||||
...annotationContext
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.openmct.selection.select(selection, true);
|
||||
},
|
||||
selectNewPlotAnnotations(minX, minY, maxX, maxY, pointsInBox, event) {
|
||||
const boundingBox = {
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY
|
||||
};
|
||||
selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event) {
|
||||
let targetDomainObjects = {};
|
||||
let targetDetails = {};
|
||||
let annotations = {};
|
||||
let annotations = [];
|
||||
pointsInBox.forEach(pointInBox => {
|
||||
if (pointInBox.length) {
|
||||
const seriesID = pointInBox[0].series.keyString;
|
||||
targetDetails[seriesID] = boundingBox;
|
||||
const boundingBoxWithId = boundingBoxPerYAxis.find(box => box.id === pointInBox[0].series.get('yAxisId'));
|
||||
targetDetails[seriesID] = boundingBoxWithId?.boundingBox;
|
||||
|
||||
targetDomainObjects[seriesID] = pointInBox[0].series.domainObject;
|
||||
}
|
||||
});
|
||||
@@ -1225,10 +1274,23 @@ export default {
|
||||
rawAnnotations.forEach(rawAnnotation => {
|
||||
if (rawAnnotation.targets) {
|
||||
const targetValues = Object.values(rawAnnotation.targets);
|
||||
const targetKeys = Object.keys(rawAnnotation.targets);
|
||||
if (targetValues && targetValues.length) {
|
||||
// just get the first one
|
||||
const boundingBox = Object.values(targetValues)?.[0];
|
||||
const pointsInBox = this.getPointsInBox(boundingBox, rawAnnotation);
|
||||
let boundingBoxPerYAxis = [];
|
||||
targetValues.forEach((boundingBox, index) => {
|
||||
const seriesId = targetKeys[index];
|
||||
const series = this.seriesModels.find(seriesModel => seriesModel.keyString === seriesId);
|
||||
if (!series) {
|
||||
return;
|
||||
}
|
||||
|
||||
boundingBoxPerYAxis.push({
|
||||
id: series.get('yAxisId'),
|
||||
boundingBox
|
||||
});
|
||||
});
|
||||
|
||||
const pointsInBox = this.getPointsInBox(boundingBoxPerYAxis, rawAnnotation);
|
||||
if (pointsInBox && pointsInBox.length) {
|
||||
annotationsByPoints.push(pointsInBox.flat());
|
||||
}
|
||||
@@ -1238,10 +1300,17 @@ export default {
|
||||
|
||||
return annotationsByPoints.flat();
|
||||
},
|
||||
getPointsInBox(boundingBox, rawAnnotation) {
|
||||
getPointsInBox(boundingBoxPerYAxis, rawAnnotation) {
|
||||
// load series models in KD-Trees
|
||||
const seriesKDTrees = [];
|
||||
this.seriesModels.forEach(seriesModel => {
|
||||
const boundingBoxWithId = boundingBoxPerYAxis.find(box => box.id === seriesModel.get('yAxisId'));
|
||||
const boundingBox = boundingBoxWithId?.boundingBox;
|
||||
//Series was probably added after the last annotations were saved
|
||||
if (!boundingBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
const seriesData = seriesModel.getSeriesData();
|
||||
if (seriesData && seriesData.length) {
|
||||
const kdTree = new KDBush(seriesData,
|
||||
@@ -1283,25 +1352,31 @@ export default {
|
||||
return seriesKDTrees;
|
||||
},
|
||||
endAnnotationMarquee(event) {
|
||||
const minX = Math.min(this.marquee.start.x, this.marquee.end.x);
|
||||
const startMinY = this.marquee.start.y.reduce((previousY, currentY) => {
|
||||
return Math.min(previousY, currentY);
|
||||
}, this.marquee.start.y[0]);
|
||||
const endMinY = this.marquee.end.y.reduce((previousY, currentY) => {
|
||||
return Math.min(previousY, currentY);
|
||||
}, this.marquee.end.y[0]);
|
||||
const minY = Math.min(startMinY, endMinY);
|
||||
const maxX = Math.max(this.marquee.start.x, this.marquee.end.x);
|
||||
const maxY = Math.max(startMinY, endMinY);
|
||||
const boundingBox = {
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY
|
||||
};
|
||||
const pointsInBox = this.getPointsInBox(boundingBox);
|
||||
const boundingBoxPerYAxis = [];
|
||||
this.yAxisListWithRange.forEach((yAxis, yIndex) => {
|
||||
const minX = Math.min(this.marquee.start.x, this.marquee.end.x);
|
||||
const minY = Math.min(this.marquee.start.y[yIndex], this.marquee.end.y[yIndex]);
|
||||
const maxX = Math.max(this.marquee.start.x, this.marquee.end.x);
|
||||
const maxY = Math.max(this.marquee.start.y[yIndex], this.marquee.end.y[yIndex]);
|
||||
const boundingBox = {
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY
|
||||
};
|
||||
boundingBoxPerYAxis.push({
|
||||
id: yAxis.get('id'),
|
||||
boundingBox
|
||||
});
|
||||
});
|
||||
|
||||
const pointsInBox = this.getPointsInBox(boundingBoxPerYAxis);
|
||||
if (!pointsInBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.annotationSelections = pointsInBox.flat();
|
||||
this.selectNewPlotAnnotations(minX, minY, maxX, maxY, pointsInBox, event);
|
||||
this.selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event);
|
||||
},
|
||||
endZoomMarquee() {
|
||||
const startPixels = this.marquee.startPixels;
|
||||
@@ -1681,7 +1756,9 @@ export default {
|
||||
},
|
||||
|
||||
destroy() {
|
||||
configStore.deleteStore(this.config.id);
|
||||
if (this.config) {
|
||||
configStore.deleteStore(this.config.id);
|
||||
}
|
||||
|
||||
this.stopListening();
|
||||
|
||||
@@ -1722,9 +1799,6 @@ export default {
|
||||
this.config.series.models.forEach(this.loadSeriesData, this);
|
||||
}
|
||||
},
|
||||
legendHoverChanged(data) {
|
||||
this.showLimitLineLabels = data;
|
||||
},
|
||||
toggleCursorGuide() {
|
||||
this.cursorGuide = !this.cursorGuide;
|
||||
this.$emit('cursorGuide', this.cursorGuide);
|
||||
|
||||
@@ -86,6 +86,8 @@ import eventHelpers from "./lib/eventHelpers";
|
||||
import { ticks, getLogTicks, getFormattedTicks } from "./tickUtils";
|
||||
import configStore from "./configuration/ConfigStore";
|
||||
|
||||
const SECONDARY_TICK_NUMBER = 2;
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
@@ -205,7 +207,7 @@ export default {
|
||||
}
|
||||
|
||||
if (this.axisType === 'yAxis' && this.axis.get('logMode')) {
|
||||
return getLogTicks(range.min, range.max, number, 4);
|
||||
return getLogTicks(range.min, range.max, number, SECONDARY_TICK_NUMBER);
|
||||
} else {
|
||||
return ticks(range.min, range.max, number);
|
||||
}
|
||||
|
||||
@@ -36,12 +36,26 @@
|
||||
:model="{progressPerc: undefined}"
|
||||
/>
|
||||
<mct-plot
|
||||
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
|
||||
:init-grid-lines="gridLines"
|
||||
:init-cursor-guide="cursorGuide"
|
||||
:options="options"
|
||||
:limit-line-labels="limitLineLabels"
|
||||
@loadingUpdated="loadingUpdated"
|
||||
@statusUpdated="setStatus"
|
||||
/>
|
||||
@configLoaded="updateReady"
|
||||
@lockHighlightPoint="lockHighlightPointUpdated"
|
||||
@highlights="highlightsUpdated"
|
||||
>
|
||||
<plot-legend
|
||||
v-if="configReady"
|
||||
:cursor-locked="lockHighlightPoint"
|
||||
:highlights="highlights"
|
||||
@legendHoverChanged="legendHoverChanged"
|
||||
@expanded="updateExpanded"
|
||||
@position="updatePosition"
|
||||
/>
|
||||
</mct-plot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -50,13 +64,15 @@
|
||||
import eventHelpers from './lib/eventHelpers';
|
||||
import ImageExporter from '../../exporters/ImageExporter';
|
||||
import MctPlot from './MctPlot.vue';
|
||||
import PlotLegend from "./legend/PlotLegend.vue";
|
||||
import ProgressBar from "../../ui/components/ProgressBar.vue";
|
||||
import StalenessUtils from '@/utils/staleness';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MctPlot,
|
||||
ProgressBar
|
||||
ProgressBar,
|
||||
PlotLegend
|
||||
},
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
props: {
|
||||
@@ -77,7 +93,13 @@ export default {
|
||||
gridLines: !this.options.compact,
|
||||
loading: false,
|
||||
status: '',
|
||||
staleObjects: []
|
||||
staleObjects: [],
|
||||
limitLineLabels: undefined,
|
||||
lockHighlightPoint: false,
|
||||
highlights: [],
|
||||
expanded: false,
|
||||
position: undefined,
|
||||
configReady: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -87,6 +109,16 @@ export default {
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
plotLegendPositionClass() {
|
||||
return this.position ? `plot-legend-${this.position}` : '';
|
||||
},
|
||||
plotLegendExpandedStateClass() {
|
||||
if (this.expanded) {
|
||||
return 'plot-legend-expanded';
|
||||
} else {
|
||||
return 'plot-legend-collapsed';
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -134,6 +166,7 @@ export default {
|
||||
this.stalenessSubscription[keystring].unsubscribe();
|
||||
this.stalenessSubscription[keystring].stalenessUtils.destroy();
|
||||
this.handleStaleness(keystring, { isStale: false }, SKIP_CHECK);
|
||||
delete this.stalenessSubscription[keystring];
|
||||
},
|
||||
handleStaleness(id, stalenessResponse, skipCheck = false) {
|
||||
if (skipCheck || this.stalenessSubscription[id].stalenessUtils.shouldUpdateStaleness(stalenessResponse, id)) {
|
||||
@@ -183,6 +216,24 @@ export default {
|
||||
exportPNG: this.exportPNG,
|
||||
exportJPG: this.exportJPG
|
||||
};
|
||||
},
|
||||
lockHighlightPointUpdated(data) {
|
||||
this.lockHighlightPoint = data;
|
||||
},
|
||||
highlightsUpdated(data) {
|
||||
this.highlights = data;
|
||||
},
|
||||
legendHoverChanged(data) {
|
||||
this.limitLineLabels = data;
|
||||
},
|
||||
updateExpanded(expanded) {
|
||||
this.expanded = expanded;
|
||||
},
|
||||
updatePosition(position) {
|
||||
this.position = position;
|
||||
},
|
||||
updateReady(ready) {
|
||||
this.configReady = ready;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -152,7 +152,7 @@ export default {
|
||||
this.selectedXKeyOptionKey = this.xKeyOptions.length > 0 ? this.getXKeyOption(xAxisKey).key : xAxisKey;
|
||||
},
|
||||
onTickWidthChange(width) {
|
||||
this.$emit('tickWidthChanged', width);
|
||||
this.$emit('plotXTickWidth', width);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -101,7 +101,13 @@ export default {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
multipleLeftAxes: {
|
||||
usedTickWidth: {
|
||||
type: Number,
|
||||
default() {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
hasMultipleLeftAxes: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
@@ -138,14 +144,14 @@ export default {
|
||||
let style = {
|
||||
width: `${this.tickWidth + AXIS_PADDING}px`
|
||||
};
|
||||
const multipleAxesPadding = this.multipleLeftAxes ? AXIS_PADDING : 0;
|
||||
const multipleAxesPadding = this.hasMultipleLeftAxes ? AXIS_PADDING : 0;
|
||||
|
||||
if (this.position === 'right') {
|
||||
style.left = `-${this.tickWidth + AXIS_PADDING}px`;
|
||||
} else {
|
||||
const thisIsTheSecondLeftAxis = (this.id - 1) > 0;
|
||||
if (this.multipleLeftAxes && thisIsTheSecondLeftAxis) {
|
||||
style.left = 0;
|
||||
if (this.hasMultipleLeftAxes && thisIsTheSecondLeftAxis) {
|
||||
style.left = `${this.plotLeftTickWidth - this.usedTickWidth - this.tickWidth}px`;
|
||||
style['border-right'] = `1px solid`;
|
||||
} else {
|
||||
style.left = `${ this.plotLeftTickWidth - this.tickWidth + multipleAxesPadding}px`;
|
||||
@@ -202,6 +208,7 @@ export default {
|
||||
}
|
||||
|
||||
this.listenTo(series, 'change:yAxisId', this.addOrRemoveSeries.bind(this, series), this);
|
||||
this.listenTo(series, 'change:color', this.updateSeriesColors.bind(this, series), this);
|
||||
},
|
||||
removeSeries(plotSeries) {
|
||||
const seriesIndex = this.seriesModels.findIndex(model => this.openmct.objects.areIdsEqual(model.get('identifier'), plotSeries.get('identifier')));
|
||||
@@ -216,6 +223,9 @@ export default {
|
||||
return model.get('yKey') === this.seriesModels[0].get('yKey');
|
||||
});
|
||||
this.singleSeries = this.seriesModels.length === 1;
|
||||
this.updateSeriesColors();
|
||||
},
|
||||
updateSeriesColors() {
|
||||
this.seriesColors = this.seriesModels.map(model => {
|
||||
return model.get('color').asHexString();
|
||||
});
|
||||
@@ -252,7 +262,7 @@ export default {
|
||||
}
|
||||
},
|
||||
onTickWidthChange(data) {
|
||||
this.$emit('tickWidthChanged', {
|
||||
this.$emit('plotYTickWidth', {
|
||||
width: data.width,
|
||||
yAxisId: this.id
|
||||
});
|
||||
|
||||
@@ -105,6 +105,9 @@ export default class MCTChartAlarmLineSet {
|
||||
|
||||
reset() {
|
||||
this.limits = [];
|
||||
if (this.series.limits) {
|
||||
this.getLimitPoints(this.series);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
||||
@@ -52,6 +52,16 @@ const MARKER_SIZE = 6.0;
|
||||
const HIGHLIGHT_SIZE = MARKER_SIZE * 2.0;
|
||||
const ANNOTATION_SIZE = MARKER_SIZE * 3.0;
|
||||
const CLEARANCE = 15;
|
||||
const HANDLED_ATTRIBUTES = {
|
||||
key: 'key',
|
||||
displayRange: 'displayRange',
|
||||
xKey: 'xKey',
|
||||
interpolate: 'interpolate',
|
||||
markers: 'markers',
|
||||
alarmMarkers: 'alarmMarkers',
|
||||
limitLines: 'limitLines',
|
||||
yAxisId: 'yAxisId'
|
||||
};
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
@@ -121,6 +131,7 @@ export default {
|
||||
hiddenYAxisIds() {
|
||||
this.hiddenYAxisIds.forEach(id => {
|
||||
this.resetYOffsetAndSeriesDataForYAxis(id);
|
||||
this.drawLimitLines();
|
||||
});
|
||||
this.scheduleDraw();
|
||||
}
|
||||
@@ -137,14 +148,16 @@ export default {
|
||||
this.offset = {
|
||||
[yAxisId]: {}
|
||||
};
|
||||
this.listenTo(this.config.yAxis, 'change:key', this.resetYOffsetAndSeriesDataForYAxis.bind(this, yAxisId), this);
|
||||
this.listenTo(this.config.yAxis, 'change', this.updateLimitsAndDraw);
|
||||
this.listenTo(this.config.yAxis, `change:${HANDLED_ATTRIBUTES.displayRange}`, this.scheduleDraw);
|
||||
this.listenTo(this.config.yAxis, `change:${HANDLED_ATTRIBUTES.key}`, this.resetYOffsetAndSeriesDataForYAxis.bind(this, yAxisId), this);
|
||||
this.listenTo(this.config.yAxis, 'change', this.redrawIfNotAlreadyHandled);
|
||||
if (this.config.additionalYAxes.length) {
|
||||
this.config.additionalYAxes.forEach(yAxis => {
|
||||
const id = yAxis.get('id');
|
||||
this.offset[id] = {};
|
||||
this.listenTo(yAxis, 'change', this.updateLimitsAndDraw);
|
||||
this.listenTo(yAxis, 'change:key', this.resetYOffsetAndSeriesDataForYAxis.bind(this, id), this);
|
||||
this.listenTo(yAxis, `change:${HANDLED_ATTRIBUTES.displayRange}`, this.scheduleDraw);
|
||||
this.listenTo(yAxis, `change:${HANDLED_ATTRIBUTES.key}`, this.resetYOffsetAndSeriesDataForYAxis.bind(this, id), this);
|
||||
this.listenTo(yAxis, 'change', this.redrawIfNotAlreadyHandled);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -161,7 +174,7 @@ export default {
|
||||
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
||||
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
||||
|
||||
this.listenTo(this.config.xAxis, 'change', this.updateLimitsAndDraw);
|
||||
this.listenTo(this.config.xAxis, 'change', this.redrawIfNotAlreadyHandled);
|
||||
this.config.series.forEach(this.onSeriesAdd, this);
|
||||
this.$emit('chartLoaded');
|
||||
},
|
||||
@@ -190,21 +203,31 @@ export default {
|
||||
this.changeLimitLines(mode, o, series);
|
||||
},
|
||||
onSeriesAdd(series) {
|
||||
this.listenTo(series, 'change:xKey', this.reDraw, this);
|
||||
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
|
||||
this.listenTo(series, 'change:markers', this.changeMarkers, this);
|
||||
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
|
||||
this.listenTo(series, 'change:limitLines', this.changeLimitLines, this);
|
||||
this.listenTo(series, 'change:yAxisId', this.resetAxisAndRedraw, this);
|
||||
this.listenTo(series, 'change', this.scheduleDraw);
|
||||
this.listenTo(series, `change:${HANDLED_ATTRIBUTES.xKey}`, this.reDraw, this);
|
||||
this.listenTo(series, `change:${HANDLED_ATTRIBUTES.interpolate}`, this.changeInterpolate, this);
|
||||
this.listenTo(series, `change:${HANDLED_ATTRIBUTES.markers}`, this.changeMarkers, this);
|
||||
this.listenTo(series, `change:${HANDLED_ATTRIBUTES.alarmMarkers}`, this.changeAlarmMarkers, this);
|
||||
this.listenTo(series, `change:${HANDLED_ATTRIBUTES.limitLines}`, this.changeLimitLines, this);
|
||||
this.listenTo(series, `change:${HANDLED_ATTRIBUTES.yAxisId}`, this.resetAxisAndRedraw, this);
|
||||
this.listenTo(series, 'change', this.redrawIfNotAlreadyHandled);
|
||||
this.listenTo(series, 'add', this.onAddPoint);
|
||||
this.makeChartElement(series);
|
||||
this.makeLimitLines(series);
|
||||
},
|
||||
onAddPoint(point, insertIndex, series) {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
const seriesYAxisId = series.get('yAxisId');
|
||||
const xRange = this.config.xAxis.get('displayRange');
|
||||
//TODO: get the yAxis of this series
|
||||
const yRange = this.config.yAxis.get('displayRange');
|
||||
|
||||
let yRange;
|
||||
if (seriesYAxisId === mainYAxisId) {
|
||||
yRange = this.config.yAxis.get('displayRange');
|
||||
} else {
|
||||
yRange = this.config.additionalYAxes.find(
|
||||
yAxis => yAxis.get('id') === seriesYAxisId
|
||||
).get('displayRange');
|
||||
}
|
||||
|
||||
const xValue = series.getXVal(point);
|
||||
const yValue = series.getYVal(point);
|
||||
|
||||
@@ -519,6 +542,14 @@ export default {
|
||||
|
||||
return true;
|
||||
},
|
||||
redrawIfNotAlreadyHandled(attribute, value, oldValue) {
|
||||
if (Object.keys(HANDLED_ATTRIBUTES).includes(attribute) && oldValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn('Unhandled change:', attribute);
|
||||
this.updateLimitsAndDraw();
|
||||
},
|
||||
updateLimitsAndDraw() {
|
||||
this.drawLimitLines();
|
||||
this.scheduleDraw();
|
||||
@@ -615,9 +646,13 @@ export default {
|
||||
alarmSets.forEach(this.drawAlarmPoints, this);
|
||||
},
|
||||
drawLimitLines() {
|
||||
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
||||
this.config.series.models.forEach(series => {
|
||||
const yAxisId = series.get('yAxisId');
|
||||
this.drawLimitLinesForSeries(yAxisId, series);
|
||||
|
||||
if (this.hiddenYAxisIds.indexOf(yAxisId) < 0) {
|
||||
this.drawLimitLinesForSeries(yAxisId, series);
|
||||
}
|
||||
});
|
||||
},
|
||||
drawLimitLinesForSeries(yAxisId, series) {
|
||||
@@ -631,12 +666,11 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
||||
let limitPointOverlap = [];
|
||||
this.limitLines.forEach((limitLine) => {
|
||||
let limitContainerEl = this.$refs.limitArea;
|
||||
limitLine.limits.forEach((limit) => {
|
||||
if (!series.includes(limit.seriesKey)) {
|
||||
if (series.keyString !== limit.seriesKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -744,6 +778,10 @@ export default {
|
||||
}
|
||||
},
|
||||
annotatedPointWithinRange(annotatedPoint, xRange, yRange) {
|
||||
if (!yRange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const xValue = annotatedPoint.series.getXVal(annotatedPoint.point);
|
||||
const yValue = annotatedPoint.series.getYVal(annotatedPoint.point);
|
||||
|
||||
|
||||
@@ -68,27 +68,26 @@ export default class PlotConfigurationModel extends Model {
|
||||
//Add any axes in addition to the main yAxis above - we must always have at least 1 y-axis
|
||||
//Addition axes ids will be the MAIN_Y_AXES_ID + x where x is between 1 and MAX_ADDITIONAL_AXES
|
||||
this.additionalYAxes = [];
|
||||
if (Array.isArray(options.model.additionalYAxes)) {
|
||||
const maxLength = Math.min(MAX_ADDITIONAL_AXES, options.model.additionalYAxes.length);
|
||||
for (let yAxisCount = 0; yAxisCount < maxLength; yAxisCount++) {
|
||||
const yAxis = options.model.additionalYAxes[yAxisCount];
|
||||
const hasAdditionalAxesConfiguration = Array.isArray(options.model.additionalYAxes);
|
||||
|
||||
for (let yAxisCount = 0; yAxisCount < MAX_ADDITIONAL_AXES; yAxisCount++) {
|
||||
const yAxisId = MAIN_Y_AXES_ID + yAxisCount + 1;
|
||||
const yAxis = hasAdditionalAxesConfiguration && options.model.additionalYAxes.find(additionalYAxis => additionalYAxis?.id === yAxisId);
|
||||
if (yAxis) {
|
||||
this.additionalYAxes.push(new YAxisModel({
|
||||
model: yAxis,
|
||||
plot: this,
|
||||
openmct: options.openmct,
|
||||
id: yAxis.id || (MAIN_Y_AXES_ID + yAxisCount + 1)
|
||||
id: yAxis.id
|
||||
}));
|
||||
} else {
|
||||
this.additionalYAxes.push(new YAxisModel({
|
||||
plot: this,
|
||||
openmct: options.openmct,
|
||||
id: yAxisId
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// If the saved options config doesn't include information about all the additional axes, we initialize the remaining here
|
||||
for (let axesCount = this.additionalYAxes.length; axesCount < MAX_ADDITIONAL_AXES; axesCount++) {
|
||||
this.additionalYAxes.push(new YAxisModel({
|
||||
plot: this,
|
||||
openmct: options.openmct,
|
||||
id: MAIN_Y_AXES_ID + axesCount + 1
|
||||
}));
|
||||
}
|
||||
// end add additional axes
|
||||
|
||||
this.legend = new LegendModel({
|
||||
|
||||
@@ -73,7 +73,7 @@ export default class PlotSeries extends Model {
|
||||
|
||||
super(options);
|
||||
|
||||
this.logMode = options.collection.plot.model.yAxis.logMode;
|
||||
this.logMode = this.getLogMode(options);
|
||||
|
||||
this.listenTo(this, 'change:xKey', this.onXKeyChange, this);
|
||||
this.listenTo(this, 'change:yKey', this.onYKeyChange, this);
|
||||
@@ -87,6 +87,17 @@ export default class PlotSeries extends Model {
|
||||
this.unPlottableValues = [undefined, Infinity, -Infinity];
|
||||
}
|
||||
|
||||
getLogMode(options) {
|
||||
const yAxisId = this.get('yAxisId');
|
||||
if (yAxisId === 1) {
|
||||
return options.collection.plot.model.yAxis.logMode;
|
||||
} else {
|
||||
const foundYAxis = options.collection.plot.model.additionalYAxes.find(yAxis => yAxis.id === yAxisId);
|
||||
|
||||
return foundYAxis ? foundYAxis.logMode : false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set defaults for telemetry series.
|
||||
* @param {import('./Model').ModelOptions<PlotSeriesModelType, PlotSeriesModelOptions>} options
|
||||
|
||||
@@ -56,6 +56,10 @@ export default class SeriesCollection extends Collection {
|
||||
const series = this.byIdentifier(seriesConfig.identifier);
|
||||
if (series) {
|
||||
series.persistedConfig = seriesConfig;
|
||||
if (!series.persistedConfig.yAxisId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (series.get('yAxisId') !== series.persistedConfig.yAxisId) {
|
||||
series.set('yAxisId', series.persistedConfig.yAxisId);
|
||||
}
|
||||
@@ -63,6 +67,10 @@ export default class SeriesCollection extends Collection {
|
||||
}, this);
|
||||
}
|
||||
watchTelemetryContainer(domainObject) {
|
||||
if (domainObject.type === 'telemetry.plot.stacked') {
|
||||
return;
|
||||
}
|
||||
|
||||
const composition = this.openmct.composition.get(domainObject);
|
||||
this.listenTo(composition, 'add', this.addTelemetryObject, this);
|
||||
this.listenTo(composition, 'remove', this.removeTelemetryObject, this);
|
||||
|
||||
@@ -287,7 +287,8 @@ export default class YAxisModel extends Model {
|
||||
this.resetSeries();
|
||||
}
|
||||
resetSeries() {
|
||||
this.plot.series.forEach((plotSeries) => {
|
||||
const series = this.getSeriesForYAxis(this.seriesCollection);
|
||||
series.forEach((plotSeries) => {
|
||||
plotSeries.logMode = this.get('logMode');
|
||||
plotSeries.reset(plotSeries.getSeriesData());
|
||||
});
|
||||
@@ -376,11 +377,8 @@ export default class YAxisModel extends Model {
|
||||
autoscale: true,
|
||||
logMode: options.model?.logMode ?? false,
|
||||
autoscalePadding: 0.1,
|
||||
id: options.id
|
||||
|
||||
// 'range' is not specified here, it is undefined at first. When the
|
||||
// user turns off autoscale, the current 'displayRange' is used for
|
||||
// the initial value of 'range'.
|
||||
id: options.id,
|
||||
range: options.model?.range
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
v-if="plotSeries.length && (isStackedPlotObject || !isNestedWithinAStackedPlot)"
|
||||
v-if="isStackedPlotObject || !isNestedWithinAStackedPlot"
|
||||
class="grid-properties"
|
||||
>
|
||||
<ul
|
||||
@@ -190,10 +190,13 @@ export default {
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.initYAxesConfiguration();
|
||||
if (!this.isStackedPlotObject) {
|
||||
this.initYAxesConfiguration();
|
||||
this.registerListeners();
|
||||
} else {
|
||||
this.initLegendConfiguration();
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
this.initLegendConfiguration();
|
||||
this.loaded = true;
|
||||
|
||||
},
|
||||
@@ -245,9 +248,9 @@ export default {
|
||||
}
|
||||
},
|
||||
getConfig() {
|
||||
this.configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return configStore.get(this.configId);
|
||||
return configStore.get(configId);
|
||||
},
|
||||
registerListeners() {
|
||||
if (this.config) {
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
>
|
||||
<h2 title="Legend options">Legend</h2>
|
||||
<legend-form
|
||||
v-if="plotSeries.length"
|
||||
class="grid-properties"
|
||||
:legend="config.legend"
|
||||
/>
|
||||
@@ -97,20 +96,23 @@ export default {
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.yAxes = [{
|
||||
id: this.config.yAxis.id,
|
||||
seriesCount: 0
|
||||
}];
|
||||
if (this.config.additionalYAxes) {
|
||||
this.yAxes = this.yAxes.concat(this.config.additionalYAxes.map(yAxis => {
|
||||
return {
|
||||
id: yAxis.id,
|
||||
seriesCount: 0
|
||||
};
|
||||
}));
|
||||
if (!this.isStackedPlotObject) {
|
||||
this.yAxes = [{
|
||||
id: this.config.yAxis.id,
|
||||
seriesCount: 0
|
||||
}];
|
||||
if (this.config.additionalYAxes) {
|
||||
this.yAxes = this.yAxes.concat(this.config.additionalYAxes.map(yAxis => {
|
||||
return {
|
||||
id: yAxis.id,
|
||||
seriesCount: 0
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
this.loaded = true;
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
||||
@@ -12,11 +12,12 @@ export default function PlotsInspectorViewProvider(openmct) {
|
||||
}
|
||||
|
||||
let object = selection[0][0].context.item;
|
||||
let parent = selection[0].length > 1 && selection[0][1].context.item;
|
||||
|
||||
const isOverlayPlotObject = object && object.type === 'telemetry.plot.overlay';
|
||||
const isStackedPlotObject = object && object.type === 'telemetry.plot.stacked';
|
||||
const isParentStackedPlotObject = parent && parent.type === 'telemetry.plot.stacked';
|
||||
|
||||
return isStackedPlotObject || isOverlayPlotObject;
|
||||
return isOverlayPlotObject || isParentStackedPlotObject;
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
|
||||
@@ -12,12 +12,10 @@ export default function StackedPlotsInspectorViewProvider(openmct) {
|
||||
}
|
||||
|
||||
const object = selection[0][0].context.item;
|
||||
const parent = selection[0].length > 1 && selection[0][1].context.item;
|
||||
|
||||
const isOverlayPlotObject = object && object.type === 'telemetry.plot.overlay';
|
||||
const isParentStackedPlotObject = parent && parent.type === 'telemetry.plot.stacked';
|
||||
const isStackedPlotObject = object && object.type === 'telemetry.plot.stacked';
|
||||
|
||||
return !isOverlayPlotObject && isParentStackedPlotObject;
|
||||
return isStackedPlotObject;
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
>Minimum Value</div>
|
||||
<div class="grid-cell value">
|
||||
<input
|
||||
v-model="rangeMin"
|
||||
v-model.lazy="rangeMin"
|
||||
class="c-input--flex"
|
||||
type="number"
|
||||
@change="updateForm('range')"
|
||||
@@ -91,7 +91,7 @@
|
||||
title="Maximum Y axis value."
|
||||
>Maximum Value</div>
|
||||
<div class="grid-cell value"><input
|
||||
v-model="rangeMax"
|
||||
v-model.lazy="rangeMax"
|
||||
class="c-input--flex"
|
||||
type="number"
|
||||
@change="updateForm('range')"
|
||||
@@ -169,7 +169,7 @@ export default {
|
||||
objectPath: `${prefix}.logMode`
|
||||
},
|
||||
range: {
|
||||
objectPath: `${prefix}.range'`,
|
||||
objectPath: `${prefix}.range`,
|
||||
coerce: function coerceRange(range) {
|
||||
const newRange = {
|
||||
min: -1,
|
||||
@@ -219,16 +219,18 @@ export default {
|
||||
this.autoscale = this.yAxis.get('autoscale');
|
||||
this.logMode = this.yAxis.get('logMode');
|
||||
this.autoscalePadding = this.yAxis.get('autoscalePadding');
|
||||
const range = this.yAxis.get('range') ?? this.yAxis.get('displayRange');
|
||||
this.rangeMin = range?.min;
|
||||
this.rangeMax = range?.max;
|
||||
const range = this.yAxis.get('range');
|
||||
if (range) {
|
||||
this.rangeMin = range?.min;
|
||||
this.rangeMax = range?.max;
|
||||
}
|
||||
},
|
||||
getPrefix() {
|
||||
let prefix = 'yAxis';
|
||||
if (this.isAdditionalYAxis) {
|
||||
let index = -1;
|
||||
if (this.additionalYAxes) {
|
||||
index = this.additionalYAxes.findIndex((yAxis) => {
|
||||
if (this.domainObject?.configuration?.additionalYAxes) {
|
||||
index = this.domainObject?.configuration?.additionalYAxes.findIndex((yAxis) => {
|
||||
return yAxis.id === this.id;
|
||||
});
|
||||
}
|
||||
@@ -308,6 +310,15 @@ export default {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//If autoscale is turned off, we must know what the min and max ranges are
|
||||
if (formKey === 'autoscale') {
|
||||
const rangeFormField = this.fields.range;
|
||||
this.validationErrors.range = rangeFormField.validate?.({
|
||||
min: this.rangeMin,
|
||||
max: this.rangeMax
|
||||
}, this.yAxis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +49,10 @@
|
||||
title="Cursor is point locked. Click anywhere in the plot to unlock."
|
||||
></div>
|
||||
<plot-legend-item-collapsed
|
||||
v-for="(seriesObject, seriesIndex) in series"
|
||||
:key="`${seriesObject.keyString}-${seriesIndex}`"
|
||||
v-for="(seriesObject, seriesIndex) in seriesModels"
|
||||
:key="`${seriesObject.keyString}-${seriesIndex}-collapsed`"
|
||||
:highlights="highlights"
|
||||
:value-to-show-when-collapsed="legend.get('valueToShowWhenCollapsed')"
|
||||
:value-to-show-when-collapsed="valueToShowWhenCollapsed"
|
||||
:series-object="seriesObject"
|
||||
@legendHoverChanged="legendHoverChanged"
|
||||
/>
|
||||
@@ -95,11 +95,10 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<plot-legend-item-expanded
|
||||
v-for="(seriesObject, seriesIndex) in series"
|
||||
v-for="(seriesObject, seriesIndex) in seriesModels"
|
||||
:key="`${seriesObject.keyString}-${seriesIndex}-expanded`"
|
||||
:series-object="seriesObject"
|
||||
:highlights="highlights"
|
||||
:legend="legend"
|
||||
@legendHoverChanged="legendHoverChanged"
|
||||
/>
|
||||
</tbody>
|
||||
@@ -111,6 +110,9 @@
|
||||
<script>
|
||||
import PlotLegendItemCollapsed from "./PlotLegendItemCollapsed.vue";
|
||||
import PlotLegendItemExpanded from "./PlotLegendItemExpanded.vue";
|
||||
import configStore from "../configuration/ConfigStore";
|
||||
import eventHelpers from "../lib/eventHelpers";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PlotLegendItemExpanded,
|
||||
@@ -124,57 +126,120 @@ export default {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
series: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
highlights: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLegendExpanded: this.legend.get('expanded') === true
|
||||
isLegendExpanded: false,
|
||||
seriesModels: [],
|
||||
loaded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showUnitsWhenExpanded() {
|
||||
return this.legend.get('showUnitsWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showUnitsWhenExpanded') === true;
|
||||
},
|
||||
showMinimumWhenExpanded() {
|
||||
return this.legend.get('showMinimumWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showMinimumWhenExpanded') === true;
|
||||
},
|
||||
showMaximumWhenExpanded() {
|
||||
return this.legend.get('showMaximumWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showMaximumWhenExpanded') === true;
|
||||
},
|
||||
showValueWhenExpanded() {
|
||||
return this.legend.get('showValueWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showValueWhenExpanded') === true;
|
||||
},
|
||||
showTimestampWhenExpanded() {
|
||||
return this.legend.get('showTimestampWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showTimestampWhenExpanded') === true;
|
||||
},
|
||||
isLegendHidden() {
|
||||
return this.legend.get('hideLegendWhenSmall') === true;
|
||||
return this.loaded && this.legend.get('hideLegendWhenSmall') === true;
|
||||
},
|
||||
valueToShowWhenCollapsed() {
|
||||
return this.loaded && this.legend.get('valueToShowWhenCollapsed');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.seriesModels = [];
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.legend = this.config.legend;
|
||||
this.loaded = true;
|
||||
this.isLegendExpanded = this.legend.get('expanded') === true;
|
||||
this.listenTo(this.config.legend, 'change:position', this.updatePosition, this);
|
||||
this.updatePosition();
|
||||
|
||||
this.initialize();
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.objectComposition) {
|
||||
this.objectComposition.off('add', this.addTelemetryObject);
|
||||
this.objectComposition.off('remove', this.removeTelemetryObject);
|
||||
}
|
||||
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
initialize() {
|
||||
if (this.domainObject.type === 'telemetry.plot.stacked') {
|
||||
this.objectComposition = this.openmct.composition.get(this.domainObject);
|
||||
this.objectComposition.on('add', this.addTelemetryObject);
|
||||
this.objectComposition.on('remove', this.removeTelemetryObject);
|
||||
this.objectComposition.load();
|
||||
} else {
|
||||
this.registerListeners(this.config);
|
||||
}
|
||||
},
|
||||
getConfig() {
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return configStore.get(configId);
|
||||
},
|
||||
addTelemetryObject(object) {
|
||||
//get the config for each child
|
||||
const configId = this.openmct.objects.makeKeyString(object.identifier);
|
||||
const config = configStore.get(configId);
|
||||
if (config) {
|
||||
this.registerListeners(config);
|
||||
}
|
||||
},
|
||||
removeTelemetryObject(identifier) {
|
||||
const configId = this.openmct.objects.makeKeyString(identifier);
|
||||
const config = configStore.get(configId);
|
||||
if (config) {
|
||||
config.series.forEach(this.removeSeries, this);
|
||||
}
|
||||
},
|
||||
registerListeners(config) {
|
||||
//listen to any changes to the telemetry endpoints that are associated with the child
|
||||
this.listenTo(config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(config.series, 'remove', this.removeSeries, this);
|
||||
config.series.forEach(this.addSeries, this);
|
||||
},
|
||||
addSeries(series) {
|
||||
this.$set(this.seriesModels, this.seriesModels.length, series);
|
||||
},
|
||||
|
||||
removeSeries(plotSeries) {
|
||||
this.stopListening(plotSeries);
|
||||
|
||||
const seriesIndex = this.seriesModels.findIndex(series => series.keyString === plotSeries.keyString);
|
||||
this.seriesModels.splice(seriesIndex, 1);
|
||||
},
|
||||
expandLegend() {
|
||||
this.isLegendExpanded = !this.isLegendExpanded;
|
||||
this.legend.set('expanded', this.isLegendExpanded);
|
||||
this.$emit('expanded', this.isLegendExpanded);
|
||||
},
|
||||
legendHoverChanged(data) {
|
||||
this.$emit('legendHoverChanged', data);
|
||||
},
|
||||
updatePosition() {
|
||||
this.$emit('position', this.legend.get('position'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -57,15 +57,12 @@
|
||||
import {getLimitClass} from "@/plugins/plot/chart/limitUtil";
|
||||
import eventHelpers from "../lib/eventHelpers";
|
||||
import stalenessMixin from '@/ui/mixins/staleness-mixin';
|
||||
import configStore from "../configuration/ConfigStore";
|
||||
|
||||
export default {
|
||||
mixins: [stalenessMixin],
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
valueToShowWhenCollapsed: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
seriesObject: {
|
||||
type: Object,
|
||||
required: true,
|
||||
@@ -88,10 +85,14 @@ export default {
|
||||
formattedYValue: '',
|
||||
formattedXValue: '',
|
||||
mctLimitStateClass: '',
|
||||
formattedYValueFromStats: ''
|
||||
formattedYValueFromStats: '',
|
||||
loaded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
valueToShowWhenCollapsed() {
|
||||
return this.loaded ? this.legend.get('valueToShowWhenCollapsed') : [];
|
||||
},
|
||||
valueToDisplayWhenCollapsedClass() {
|
||||
return `value-to-display-${ this.valueToShowWhenCollapsed }`;
|
||||
},
|
||||
@@ -109,6 +110,9 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.legend = this.config.legend;
|
||||
this.loaded = true;
|
||||
this.listenTo(this.seriesObject, 'change:color', (newColor) => {
|
||||
this.updateColor(newColor);
|
||||
}, this);
|
||||
@@ -122,8 +126,13 @@ export default {
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return configStore.get(configId);
|
||||
},
|
||||
initialize(highlightedObject) {
|
||||
const seriesObject = highlightedObject ? highlightedObject.series : this.seriesObject;
|
||||
const seriesObject = highlightedObject?.series || this.seriesObject;
|
||||
|
||||
this.isMissing = seriesObject.domainObject.status === 'missing';
|
||||
this.colorAsHexString = seriesObject.get('color').asHexString();
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
import {getLimitClass} from "@/plugins/plot/chart/limitUtil";
|
||||
import eventHelpers from "@/plugins/plot/lib/eventHelpers";
|
||||
import stalenessMixin from '@/ui/mixins/staleness-mixin';
|
||||
import configStore from "../configuration/ConfigStore";
|
||||
|
||||
export default {
|
||||
mixins: [stalenessMixin],
|
||||
@@ -100,10 +101,6 @@ export default {
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -116,24 +113,25 @@ export default {
|
||||
formattedXValue: '',
|
||||
formattedMinY: '',
|
||||
formattedMaxY: '',
|
||||
mctLimitStateClass: ''
|
||||
mctLimitStateClass: '',
|
||||
loaded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showUnitsWhenExpanded() {
|
||||
return this.legend.get('showUnitsWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showUnitsWhenExpanded') === true;
|
||||
},
|
||||
showMinimumWhenExpanded() {
|
||||
return this.legend.get('showMinimumWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showMinimumWhenExpanded') === true;
|
||||
},
|
||||
showMaximumWhenExpanded() {
|
||||
return this.legend.get('showMaximumWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showMaximumWhenExpanded') === true;
|
||||
},
|
||||
showValueWhenExpanded() {
|
||||
return this.legend.get('showValueWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showValueWhenExpanded') === true;
|
||||
},
|
||||
showTimestampWhenExpanded() {
|
||||
return this.legend.get('showTimestampWhenExpanded') === true;
|
||||
return this.loaded && this.legend.get('showTimestampWhenExpanded') === true;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -146,6 +144,9 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.legend = this.config.legend;
|
||||
this.loaded = true;
|
||||
this.listenTo(this.seriesObject, 'change:color', (newColor) => {
|
||||
this.updateColor(newColor);
|
||||
}, this);
|
||||
@@ -159,8 +160,13 @@ export default {
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return configStore.get(configId);
|
||||
},
|
||||
initialize(highlightedObject) {
|
||||
const seriesObject = highlightedObject ? highlightedObject.series : this.seriesObject;
|
||||
const seriesObject = highlightedObject?.series || this.seriesObject;
|
||||
|
||||
this.isMissing = seriesObject.domainObject.status === 'missing';
|
||||
this.colorAsHexString = seriesObject.get('color').asHexString();
|
||||
|
||||
@@ -28,7 +28,7 @@ import EventEmitter from "EventEmitter";
|
||||
import PlotOptions from "./inspector/PlotOptions.vue";
|
||||
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
|
||||
|
||||
const TEST_KEY_ID = 'test-key';
|
||||
const TEST_KEY_ID = 'some-other-key';
|
||||
|
||||
describe("the plugin", function () {
|
||||
let element;
|
||||
@@ -533,6 +533,30 @@ describe("the plugin", function () {
|
||||
expect(openmct.telemetry.request).toHaveBeenCalledTimes(2);
|
||||
|
||||
});
|
||||
|
||||
describe('limits', () => {
|
||||
|
||||
it('lines are not displayed by default', () => {
|
||||
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
|
||||
expect(limitEl.length).toBe(0);
|
||||
});
|
||||
|
||||
it('lines are displayed when configuration is set to true', (done) => {
|
||||
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
|
||||
const config = configStore.get(configId);
|
||||
config.yAxis.set('displayRange', {
|
||||
min: 0,
|
||||
max: 4
|
||||
});
|
||||
config.series.models[0].set('limitLines', true);
|
||||
|
||||
Vue.nextTick(() => {
|
||||
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
|
||||
expect(limitEl.length).toBe(4);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('controls in time strip view', () => {
|
||||
@@ -867,24 +891,5 @@ describe("the plugin", function () {
|
||||
expect(colorSwatch).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('limits', () => {
|
||||
|
||||
it('lines are not displayed by default', () => {
|
||||
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
|
||||
expect(limitEl.length).toBe(0);
|
||||
});
|
||||
|
||||
xit('lines are displayed when configuration is set to true', (done) => {
|
||||
config.series.models[0].set('limitLines', true);
|
||||
|
||||
Vue.nextTick(() => {
|
||||
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
|
||||
expect(limitEl.length).toBe(4);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,31 +27,34 @@
|
||||
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
|
||||
>
|
||||
<plot-legend
|
||||
v-if="compositionObjectsConfigLoaded"
|
||||
:cursor-locked="!!lockHighlightPoint"
|
||||
:series="seriesModels"
|
||||
:highlights="highlights"
|
||||
:legend="legend"
|
||||
@legendHoverChanged="legendHoverChanged"
|
||||
@expanded="updateExpanded"
|
||||
@position="updatePosition"
|
||||
/>
|
||||
<div class="l-view-section">
|
||||
<div
|
||||
class="l-view-section"
|
||||
>
|
||||
<stacked-plot-item
|
||||
v-for="object in compositionObjects"
|
||||
:key="object.id"
|
||||
v-for="objectWrapper in compositionObjects"
|
||||
:key="objectWrapper.keyString"
|
||||
class="c-plot--stacked-container"
|
||||
:child-object="object"
|
||||
:child-object="objectWrapper.object"
|
||||
:options="options"
|
||||
:grid-lines="gridLines"
|
||||
:color-palette="colorPalette"
|
||||
:cursor-guide="cursorGuide"
|
||||
:show-limit-line-labels="showLimitLineLabels"
|
||||
:plot-tick-width="maxTickWidth"
|
||||
@plotTickWidth="onTickWidthChange"
|
||||
:parent-y-tick-width="maxTickWidth"
|
||||
@plotYTickWidth="onYTickWidthChange"
|
||||
@loadingUpdated="loadingUpdated"
|
||||
@cursorGuide="onCursorGuideChange"
|
||||
@gridLines="onGridLinesChange"
|
||||
@lockHighlightPoint="lockHighlightPointUpdated"
|
||||
@highlights="highlightsUpdated"
|
||||
@configLoaded="registerSeriesListeners"
|
||||
@configLoaded="configLoadedForObject(objectWrapper.keyString)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,14 +69,13 @@ import ColorPalette from "@/ui/color/ColorPalette";
|
||||
import PlotLegend from "../legend/PlotLegend.vue";
|
||||
import StackedPlotItem from './StackedPlotItem.vue';
|
||||
import ImageExporter from '../../../exporters/ImageExporter';
|
||||
import eventHelpers from "@/plugins/plot/lib/eventHelpers";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StackedPlotItem,
|
||||
PlotLegend
|
||||
},
|
||||
inject: ['openmct', 'domainObject', 'composition', 'path'],
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
@@ -87,48 +89,59 @@ export default {
|
||||
hideExportButtons: false,
|
||||
cursorGuide: false,
|
||||
gridLines: true,
|
||||
loading: false,
|
||||
configLoaded: {},
|
||||
compositionObjects: [],
|
||||
tickWidthMap: {},
|
||||
legend: {},
|
||||
loaded: false,
|
||||
lockHighlightPoint: false,
|
||||
highlights: [],
|
||||
seriesModels: [],
|
||||
showLimitLineLabels: undefined,
|
||||
colorPalette: new ColorPalette()
|
||||
colorPalette: new ColorPalette(),
|
||||
compositionObjectsConfigLoaded: false,
|
||||
position: 'top',
|
||||
expanded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
plotLegendPositionClass() {
|
||||
return `plot-legend-${this.config.legend.get('position')}`;
|
||||
return `plot-legend-${this.position}`;
|
||||
},
|
||||
plotLegendExpandedStateClass() {
|
||||
if (this.config.legend.get('expanded')) {
|
||||
if (this.expanded) {
|
||||
return 'plot-legend-expanded';
|
||||
} else {
|
||||
return 'plot-legend-collapsed';
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns the maximum width of the left and right y axes ticks of this stacked plots children
|
||||
* @returns {{rightTickWidth: number, leftTickWidth: number, hasMultipleLeftAxes: boolean}}
|
||||
*/
|
||||
maxTickWidth() {
|
||||
return Math.max(...Object.values(this.tickWidthMap));
|
||||
const tickWidthValues = Object.values(this.tickWidthMap);
|
||||
const maxLeftTickWidth = Math.max(...tickWidthValues.map(tickWidthItem => tickWidthItem.leftTickWidth));
|
||||
const maxRightTickWidth = Math.max(...tickWidthValues.map(tickWidthItem => tickWidthItem.rightTickWidth));
|
||||
const hasMultipleLeftAxes = tickWidthValues.some(tickWidthItem => tickWidthItem.hasMultipleLeftAxes === true);
|
||||
|
||||
return {
|
||||
leftTickWidth: maxLeftTickWidth,
|
||||
rightTickWidth: maxRightTickWidth,
|
||||
hasMultipleLeftAxes
|
||||
};
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroy();
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.seriesConfig = {};
|
||||
|
||||
//We only need to initialize the stacked plot config for legend properties
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.config = this.getConfig(configId);
|
||||
|
||||
this.legend = this.config.legend;
|
||||
|
||||
this.loaded = true;
|
||||
this.imageExporter = new ImageExporter(this.openmct);
|
||||
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.composition.on('add', this.addChild);
|
||||
this.composition.on('remove', this.removeChild);
|
||||
this.composition.on('reorder', this.compositionReorder);
|
||||
@@ -142,7 +155,6 @@ export default {
|
||||
id: configId,
|
||||
domainObject: this.domainObject,
|
||||
openmct: this.openmct,
|
||||
palette: this.colorPalette,
|
||||
callback: (data) => {
|
||||
this.data = data;
|
||||
}
|
||||
@@ -155,10 +167,19 @@ export default {
|
||||
loadingUpdated(loaded) {
|
||||
this.loading = loaded;
|
||||
},
|
||||
destroy() {
|
||||
this.stopListening();
|
||||
configStore.deleteStore(this.config.id);
|
||||
configLoadedForObject(childObjIdentifier) {
|
||||
const childObjId = this.openmct.objects.makeKeyString(childObjIdentifier);
|
||||
this.configLoaded[childObjId] = true;
|
||||
this.setConfigLoadedForComposition();
|
||||
},
|
||||
setConfigLoadedForComposition() {
|
||||
this.compositionObjectsConfigLoaded = this.compositionObjects.length && this.compositionObjects.every(childObject => {
|
||||
const id = childObject.keyString;
|
||||
|
||||
return this.configLoaded[id] === true;
|
||||
});
|
||||
},
|
||||
destroy() {
|
||||
this.composition.off('add', this.addChild);
|
||||
this.composition.off('remove', this.removeChild);
|
||||
this.composition.off('reorder', this.compositionReorder);
|
||||
@@ -167,9 +188,16 @@ export default {
|
||||
addChild(child) {
|
||||
const id = this.openmct.objects.makeKeyString(child.identifier);
|
||||
|
||||
this.$set(this.tickWidthMap, id, 0);
|
||||
this.$set(this.tickWidthMap, id, {
|
||||
leftTickWidth: 0,
|
||||
rightTickWidth: 0
|
||||
});
|
||||
|
||||
this.compositionObjects.push(child);
|
||||
this.compositionObjects.push({
|
||||
object: child,
|
||||
keyString: id
|
||||
});
|
||||
this.setConfigLoadedForComposition();
|
||||
},
|
||||
|
||||
removeChild(childIdentifier) {
|
||||
@@ -177,26 +205,36 @@ export default {
|
||||
|
||||
this.$delete(this.tickWidthMap, id);
|
||||
|
||||
const childObj = this.compositionObjects.filter((c) => {
|
||||
const identifier = c.keyString;
|
||||
|
||||
return identifier === id;
|
||||
})[0];
|
||||
|
||||
if (childObj) {
|
||||
if (childObj.object.type !== 'telemetry.plot.overlay') {
|
||||
const config = this.getConfig(childObj.keyString);
|
||||
if (config) {
|
||||
config.series.remove(config.series.at(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.compositionObjects = this.compositionObjects.filter((c) => {
|
||||
const identifier = c.keyString;
|
||||
|
||||
return identifier !== id;
|
||||
});
|
||||
|
||||
const configIndex = this.domainObject.configuration.series.findIndex((seriesConfig) => {
|
||||
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, childIdentifier);
|
||||
});
|
||||
if (configIndex > -1) {
|
||||
this.domainObject.configuration.series.splice(configIndex, 1);
|
||||
const cSeries = this.domainObject.configuration.series.slice();
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.series', cSeries);
|
||||
}
|
||||
|
||||
this.removeSeries({
|
||||
keyString: id
|
||||
});
|
||||
|
||||
const childObj = this.compositionObjects.filter((c) => {
|
||||
const identifier = this.openmct.objects.makeKeyString(c.identifier);
|
||||
|
||||
return identifier === id;
|
||||
})[0];
|
||||
if (childObj) {
|
||||
const index = this.compositionObjects.indexOf(childObj);
|
||||
this.compositionObjects.splice(index, 1);
|
||||
}
|
||||
this.setConfigLoadedForComposition();
|
||||
},
|
||||
|
||||
compositionReorder(reorderPlan) {
|
||||
@@ -209,7 +247,10 @@ export default {
|
||||
|
||||
resetTelemetryAndTicks(domainObject) {
|
||||
this.compositionObjects = [];
|
||||
this.tickWidthMap = {};
|
||||
this.tickWidthMap = {
|
||||
leftTickWidth: 0,
|
||||
rightTickWidth: 0
|
||||
};
|
||||
},
|
||||
|
||||
exportJPG() {
|
||||
@@ -232,12 +273,18 @@ export default {
|
||||
this.hideExportButtons = false;
|
||||
}.bind(this));
|
||||
},
|
||||
onTickWidthChange(width, plotId) {
|
||||
/**
|
||||
* @typedef {Object} PlotYTickData
|
||||
* @property {Number} leftTickWidth the width of the ticks for all the y axes on the left of the plot.
|
||||
* @property {Number} rightTickWidth the width of the ticks for all the y axes on the right of the plot.
|
||||
* @property {Boolean} hasMultipleLeftAxes whether or not there is more than one left y axis.
|
||||
*/
|
||||
onYTickWidthChange(data, plotId) {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.tickWidthMap, plotId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$set(this.tickWidthMap, plotId, width);
|
||||
this.$set(this.tickWidthMap, plotId, data);
|
||||
},
|
||||
legendHoverChanged(data) {
|
||||
this.showLimitLineLabels = data;
|
||||
@@ -245,39 +292,18 @@ export default {
|
||||
lockHighlightPointUpdated(data) {
|
||||
this.lockHighlightPoint = data;
|
||||
},
|
||||
updateExpanded(expanded) {
|
||||
this.expanded = expanded;
|
||||
},
|
||||
updatePosition(position) {
|
||||
this.position = position;
|
||||
},
|
||||
updateReady(ready) {
|
||||
this.configReady = ready;
|
||||
},
|
||||
highlightsUpdated(data) {
|
||||
this.highlights = data;
|
||||
},
|
||||
registerSeriesListeners(configId) {
|
||||
const config = this.getConfig(configId);
|
||||
this.seriesConfig[configId] = config;
|
||||
const childObject = config.get('domainObject');
|
||||
|
||||
//TODO differentiate between objects with composition and those without
|
||||
if (childObject.type === 'telemetry.plot.overlay') {
|
||||
this.listenTo(config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(config.series, 'remove', this.removeSeries, this);
|
||||
}
|
||||
|
||||
config.series.models.forEach(this.addSeries, this);
|
||||
},
|
||||
addSeries(series) {
|
||||
const childObject = series.domainObject;
|
||||
//don't add the series if it can have child series this will happen in registerSeriesListeners
|
||||
if (childObject.type !== 'telemetry.plot.overlay') {
|
||||
const index = this.seriesModels.length;
|
||||
this.$set(this.seriesModels, index, series);
|
||||
}
|
||||
|
||||
},
|
||||
removeSeries(plotSeries) {
|
||||
const index = this.seriesModels.findIndex(seriesModel => seriesModel.keyString === plotSeries.keyString);
|
||||
if (index > -1) {
|
||||
this.$delete(this.seriesModels, index);
|
||||
}
|
||||
|
||||
this.stopListening(plotSeries);
|
||||
},
|
||||
onCursorGuideChange(cursorGuide) {
|
||||
this.cursorGuide = cursorGuide === true;
|
||||
},
|
||||
|
||||
@@ -72,10 +72,14 @@ export default {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
plotTickWidth: {
|
||||
type: Number,
|
||||
parentYTickWidth: {
|
||||
type: Object,
|
||||
default() {
|
||||
return 0;
|
||||
return {
|
||||
leftTickWidth: 0,
|
||||
rightTickWidth: 0,
|
||||
hasMultipleLeftAxes: false
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -86,8 +90,8 @@ export default {
|
||||
cursorGuide(newCursorGuide) {
|
||||
this.updateComponentProp('cursorGuide', newCursorGuide);
|
||||
},
|
||||
plotTickWidth(width) {
|
||||
this.updateComponentProp('plotTickWidth', width);
|
||||
parentYTickWidth(width) {
|
||||
this.updateComponentProp('parentYTickWidth', width);
|
||||
},
|
||||
showLimitLineLabels: {
|
||||
handler(data) {
|
||||
@@ -100,10 +104,6 @@ export default {
|
||||
this.updateView();
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
|
||||
if (this.component) {
|
||||
this.component.$destroy();
|
||||
}
|
||||
@@ -125,7 +125,7 @@ export default {
|
||||
this.$el.innerHTML = '';
|
||||
}
|
||||
|
||||
const onTickWidthChange = this.onTickWidthChange;
|
||||
const onYTickWidthChange = this.onYTickWidthChange;
|
||||
const onLockHighlightPointUpdated = this.onLockHighlightPointUpdated;
|
||||
const onHighlightsUpdated = this.onHighlightsUpdated;
|
||||
const onConfigLoaded = this.onConfigLoaded;
|
||||
@@ -162,7 +162,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
...getProps(),
|
||||
onTickWidthChange,
|
||||
onYTickWidthChange,
|
||||
onLockHighlightPointUpdated,
|
||||
onHighlightsUpdated,
|
||||
onConfigLoaded,
|
||||
@@ -178,10 +178,31 @@ export default {
|
||||
this.loading = loaded;
|
||||
}
|
||||
},
|
||||
template: '<div v-if="!isMissing" ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\', \'is-stale\': isStale}"><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>'
|
||||
template: `
|
||||
<div v-if="!isMissing" ref="plotWrapper"
|
||||
class="l-view-section u-style-receiver js-style-receiver"
|
||||
:class="{'s-status-timeconductor-unsynced': status && status === 'timeconductor-unsynced', 'is-stale': isStale}">
|
||||
<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"
|
||||
:parent-y-tick-width="parentYTickWidth"
|
||||
:limit-line-labels="limitLineLabels"
|
||||
:color-palette="colorPalette"
|
||||
:options="options"
|
||||
@plotYTickWidth="onYTickWidthChange"
|
||||
@lockHighlightPoint="onLockHighlightPointUpdated"
|
||||
@highlights="onHighlightsUpdated"
|
||||
@configLoaded="onConfigLoaded"
|
||||
@cursorGuide="onCursorGuideChange"
|
||||
@gridLines="onGridLinesChange"
|
||||
@statusUpdated="setStatus"
|
||||
@loadingUpdated="loadingUpdated"/>
|
||||
</div>`
|
||||
});
|
||||
|
||||
this.setSelection();
|
||||
},
|
||||
onLockHighlightPointUpdated() {
|
||||
this.$emit('lockHighlightPoint', ...arguments);
|
||||
@@ -192,8 +213,8 @@ export default {
|
||||
onConfigLoaded() {
|
||||
this.$emit('configLoaded', ...arguments);
|
||||
},
|
||||
onTickWidthChange() {
|
||||
this.$emit('plotTickWidth', ...arguments);
|
||||
onYTickWidthChange() {
|
||||
this.$emit('plotYTickWidth', ...arguments);
|
||||
},
|
||||
onCursorGuideChange() {
|
||||
this.$emit('cursorGuide', ...arguments);
|
||||
@@ -205,23 +226,12 @@ export default {
|
||||
this.status = status;
|
||||
this.updateComponentProp('status', status);
|
||||
},
|
||||
setSelection() {
|
||||
let childContext = {};
|
||||
childContext.item = this.childObject;
|
||||
this.context = childContext;
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context);
|
||||
},
|
||||
getProps() {
|
||||
return {
|
||||
limitLineLabels: this.showLimitLineLabels,
|
||||
gridLines: this.gridLines,
|
||||
cursorGuide: this.cursorGuide,
|
||||
plotTickWidth: this.plotTickWidth,
|
||||
parentYTickWidth: this.parentYTickWidth,
|
||||
options: this.options,
|
||||
status: this.status,
|
||||
colorPalette: this.colorPalette,
|
||||
@@ -230,7 +240,7 @@ export default {
|
||||
},
|
||||
getPlotObject() {
|
||||
if (this.childObject.configuration && this.childObject.configuration.series) {
|
||||
//If the object has a configuration, allow initialization of the config from it's persisted config
|
||||
//If the object has a configuration (like an overlay plot), allow initialization of the config from it's persisted config
|
||||
return this.childObject;
|
||||
} else {
|
||||
//If object is missing, warn and return object
|
||||
|
||||
@@ -57,7 +57,6 @@ export default function StackedPlotViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
composition: openmct.composition.get(domainObject),
|
||||
path: objectPath
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -173,7 +173,7 @@ describe("the plugin", function () {
|
||||
let testTelemetryObject2;
|
||||
let config;
|
||||
let component;
|
||||
let mockComposition;
|
||||
let mockCompositionList = [];
|
||||
let plotViewComponentObject;
|
||||
|
||||
afterAll(() => {
|
||||
@@ -271,14 +271,34 @@ describe("the plugin", function () {
|
||||
}
|
||||
};
|
||||
|
||||
mockComposition = new EventEmitter();
|
||||
mockComposition.load = () => {
|
||||
mockComposition.emit('add', testTelemetryObject);
|
||||
stackedPlotObject.composition = [{
|
||||
identifier: testTelemetryObject.identifier
|
||||
}];
|
||||
|
||||
return [testTelemetryObject];
|
||||
};
|
||||
mockCompositionList = [];
|
||||
spyOn(openmct.composition, 'get').and.callFake((domainObject) => {
|
||||
//We need unique compositions here - one for the StackedPlot view and one for the PlotLegend view
|
||||
const numObjects = domainObject.composition.length;
|
||||
const mockComposition = new EventEmitter();
|
||||
mockComposition.load = () => {
|
||||
if (numObjects === 1) {
|
||||
mockComposition.emit('add', testTelemetryObject);
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
return [testTelemetryObject];
|
||||
} else if (numObjects === 2) {
|
||||
mockComposition.emit('add', testTelemetryObject);
|
||||
mockComposition.emit('add', testTelemetryObject2);
|
||||
|
||||
return [testTelemetryObject, testTelemetryObject2];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
mockCompositionList.push(mockComposition);
|
||||
|
||||
return mockComposition;
|
||||
});
|
||||
|
||||
let viewContainer = document.createElement("div");
|
||||
child.append(viewContainer);
|
||||
@@ -290,7 +310,6 @@ describe("the plugin", function () {
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: stackedPlotObject,
|
||||
composition: openmct.composition.get(stackedPlotObject),
|
||||
path: [stackedPlotObject]
|
||||
},
|
||||
template: "<stacked-plot></stacked-plot>"
|
||||
@@ -321,7 +340,7 @@ describe("the plugin", function () {
|
||||
expect(legend.length).toBe(6);
|
||||
});
|
||||
|
||||
it("Renders X-axis ticks for the telemetry object", (done) => {
|
||||
it("Renders X-axis ticks for the telemetry object", () => {
|
||||
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
|
||||
expect(xAxisElement.length).toBe(1);
|
||||
|
||||
@@ -329,13 +348,8 @@ describe("the plugin", function () {
|
||||
min: 0,
|
||||
max: 4
|
||||
});
|
||||
|
||||
Vue.nextTick(() => {
|
||||
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
|
||||
expect(ticks.length).toBe(9);
|
||||
|
||||
done();
|
||||
});
|
||||
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
|
||||
expect(ticks.length).toBe(9);
|
||||
});
|
||||
|
||||
it("Renders Y-axis ticks for the telemetry object", (done) => {
|
||||
@@ -401,17 +415,22 @@ describe("the plugin", function () {
|
||||
});
|
||||
|
||||
it('plots a new series when a new telemetry object is added', (done) => {
|
||||
mockComposition.emit('add', testTelemetryObject2);
|
||||
//setting composition here so that any new triggers to composition.load with correctly load the mockComposition in the beforeEach
|
||||
stackedPlotObject.composition = [testTelemetryObject, testTelemetryObject2];
|
||||
mockCompositionList[0].emit('add', testTelemetryObject2);
|
||||
|
||||
Vue.nextTick(() => {
|
||||
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
|
||||
expect(legend.length).toBe(2);
|
||||
expect(legend[1].innerHTML).toEqual("Test Object2");
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('removes plots from series when a telemetry object is removed', (done) => {
|
||||
mockComposition.emit('remove', testTelemetryObject.identifier);
|
||||
stackedPlotObject.composition = [];
|
||||
mockCompositionList[0].emit('remove', testTelemetryObject.identifier);
|
||||
Vue.nextTick(() => {
|
||||
expect(plotViewComponentObject.compositionObjects.length).toBe(0);
|
||||
done();
|
||||
@@ -429,16 +448,6 @@ describe("the plugin", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it("Renders a new series when added to one of the plots", (done) => {
|
||||
mockComposition.emit('add', testTelemetryObject2);
|
||||
Vue.nextTick(() => {
|
||||
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
|
||||
expect(legend.length).toBe(2);
|
||||
expect(legend[1].innerHTML).toEqual("Test Object2");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("Adds a new point to the plot", (done) => {
|
||||
let originalLength = config.series.models[0].getSeriesData().length;
|
||||
config.series.models[0].add({
|
||||
@@ -459,7 +468,7 @@ describe("the plugin", function () {
|
||||
max: 10
|
||||
});
|
||||
Vue.nextTick(() => {
|
||||
expect(plotViewComponentObject.$children[1].component.$children[1].xScale.domain()).toEqual({
|
||||
expect(plotViewComponentObject.$children[0].component.$children[1].xScale.domain()).toEqual({
|
||||
min: 0,
|
||||
max: 10
|
||||
});
|
||||
@@ -476,7 +485,7 @@ describe("the plugin", function () {
|
||||
});
|
||||
});
|
||||
Vue.nextTick(() => {
|
||||
const yAxesScales = plotViewComponentObject.$children[1].component.$children[1].yScale;
|
||||
const yAxesScales = plotViewComponentObject.$children[0].component.$children[1].yScale;
|
||||
yAxesScales.forEach((yAxisScale) => {
|
||||
expect(yAxisScale.scale.domain()).toEqual({
|
||||
min: 10,
|
||||
|
||||
@@ -293,6 +293,7 @@ define([
|
||||
this.stalenessSubscription[keyString].unsubscribe();
|
||||
this.stalenessSubscription[keyString].stalenessUtils.destroy();
|
||||
this.handleStaleness(keyString, { isStale: false }, SKIP_CHECK);
|
||||
delete this.stalenessSubscription[keyString];
|
||||
}
|
||||
|
||||
clearData() {
|
||||
|
||||
@@ -390,7 +390,7 @@ $colorItemTreeHoverBg: rgba(#fff, 0.1);
|
||||
$colorItemTreeHoverFg: #fff;
|
||||
$colorItemTreeIcon: $colorKey; // Used
|
||||
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
||||
$colorItemTreeFg: $colorBodyFg;
|
||||
$colorItemTreeFg: #ccc;
|
||||
$colorItemTreeSelectedBg: $colorSelectedBg;
|
||||
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
|
||||
$filterItemTreeSelected: $filterHov;
|
||||
|
||||
@@ -394,7 +394,7 @@ $colorItemTreeHoverBg: rgba(#fff, 0.03);
|
||||
$colorItemTreeHoverFg: #fff;
|
||||
$colorItemTreeIcon: $colorKey; // Used
|
||||
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
||||
$colorItemTreeFg: $colorBodyFg;
|
||||
$colorItemTreeFg: $colorA;
|
||||
$colorItemTreeSelectedBg: $colorSelectedBg;
|
||||
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
|
||||
$filterItemTreeSelected: $filterHov;
|
||||
|
||||
@@ -94,7 +94,7 @@ $messageListIconD: 32px;
|
||||
$tableResizeColHitareaD: 6px;
|
||||
/*************** Misc */
|
||||
$drawingObjBorderW: 3px;
|
||||
|
||||
$tagBorderRadius: 3px;
|
||||
/************************** MOBILE */
|
||||
$mobileMenuIconD: 24px; // Used
|
||||
$mobileTreeItemH: 35px; // Used
|
||||
|
||||
@@ -270,9 +270,11 @@ button {
|
||||
flex: 0 0 auto;
|
||||
width: $d;
|
||||
position: relative;
|
||||
visibility: hidden;
|
||||
|
||||
&.is-enabled {
|
||||
cursor: pointer;
|
||||
visibility: visible;
|
||||
|
||||
&:hover {
|
||||
color: $colorDisclosureCtrlHov;
|
||||
@@ -403,16 +405,18 @@ textarea {
|
||||
|
||||
&--autocomplete {
|
||||
&__wrapper {
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__input {
|
||||
min-width: 100px;
|
||||
width: 100%;
|
||||
|
||||
// Fend off from afford-arrow
|
||||
min-height: 2em;
|
||||
padding-right: 2.5em !important;
|
||||
}
|
||||
|
||||
@@ -435,7 +439,10 @@ textarea {
|
||||
}
|
||||
|
||||
&__afford-arrow {
|
||||
$p: 2px;
|
||||
font-size: 0.8em;
|
||||
padding-bottom: $p;
|
||||
padding-top: $p;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
z-index: 2;
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
@import "../ui/inspector/elements.scss";
|
||||
@import "../ui/inspector/inspector.scss";
|
||||
@import "../ui/inspector/location.scss";
|
||||
@import "../ui/inspector/annotations/annotation-inspector.scss";
|
||||
@import "../ui/layout/app-logo.scss";
|
||||
@import "../ui/layout/create-button.scss";
|
||||
@import "../ui/layout/layout.scss";
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
<ul
|
||||
v-if="orderedPath.length"
|
||||
class="c-location"
|
||||
:aria-label="`${domainObject.name} Breadcrumb`"
|
||||
role="navigation"
|
||||
>
|
||||
<li
|
||||
v-for="pathObject in orderedPath"
|
||||
@@ -34,6 +36,7 @@
|
||||
:domain-object="pathObject.domainObject"
|
||||
:object-path="pathObject.objectPath"
|
||||
:read-only="readOnly"
|
||||
:navigate-to-path="navigateToPath(pathObject.objectPath)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -110,6 +113,18 @@ export default {
|
||||
this.orderedPath = pathWithDomainObject.slice(1, pathWithDomainObject.length - 1).reverse();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Generate the hash url for the given object path, removing the '/ROOT' prefix if present.
|
||||
* @param {import('../../api/objects/ObjectAPI').DomainObject[]} objectPath
|
||||
*/
|
||||
navigateToPath(objectPath) {
|
||||
/** @type {String} */
|
||||
const path = `/browse/${this.openmct.objects.getRelativePath(objectPath)}`;
|
||||
|
||||
return path.replace('ROOT/', '');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -21,10 +21,11 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-tag-applier">
|
||||
<div class="c-tag-applier has-tag-applier">
|
||||
<TagSelection
|
||||
v-for="(addedTag, index) in addedTags"
|
||||
:key="index"
|
||||
:class="{ 'w-tag-wrapper--tag-selector' : addedTag.newTag }"
|
||||
:selected-tag="addedTag.newTag ? null : addedTag"
|
||||
:new-tag="addedTag.newTag"
|
||||
:added-tags="addedTags"
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-tag__parent">
|
||||
<div class="c-tag_selection">
|
||||
<div class="w-tag-wrapper">
|
||||
<template v-if="newTag">
|
||||
<AutoCompleteField
|
||||
v-if="newTag"
|
||||
ref="tagSelection"
|
||||
@@ -32,8 +32,9 @@
|
||||
:item-css-class="'icon-circle'"
|
||||
@onChange="tagSelected"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-else
|
||||
class="c-tag"
|
||||
:class="{'c-tag-edit': !readOnly}"
|
||||
:style="{ background: selectedBackgroundColor, color: selectedForegroundColor }"
|
||||
@@ -48,7 +49,7 @@
|
||||
@click="removeTag"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
@mixin tagHolder() {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
$m: $interiorMarginSm;
|
||||
|
||||
margin: 0 $m $m 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************* TAGS */
|
||||
.c-tag {
|
||||
border-radius: 10px; //TODO: convert to theme constant
|
||||
border-radius: $tagBorderRadius;
|
||||
display: inline-flex;
|
||||
padding: 1px 10px; //TODO: convert to theme constant
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
overflow: hidden;
|
||||
padding: 1px 6px; //TODO: convert to theme constant
|
||||
transition: $transIn;
|
||||
|
||||
&__remove-btn {
|
||||
color: inherit !important;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
padding: 1px !important;
|
||||
padding: 0; // Overrides default <button> padding
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
transition: $transIn;
|
||||
width: 0;
|
||||
|
||||
@@ -28,28 +39,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
/******************************* TAG EDITOR */
|
||||
.c-tag-applier {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
.c-tag-holder {
|
||||
@include tagHolder;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
.w-tag-wrapper {
|
||||
$m: $interiorMarginSm;
|
||||
|
||||
margin: 0 $m $m 0;
|
||||
}
|
||||
|
||||
/******************************* TAGS IN INSPECTOR / TAG SELECTION & APPLICATION */
|
||||
.c-tag-applier {
|
||||
$tagApplierPadding: 3px 6px;
|
||||
@include tagHolder;
|
||||
grid-column: 1 / 3;
|
||||
|
||||
&__tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__add-btn {
|
||||
border-radius: $tagBorderRadius;
|
||||
padding: 3px 10px 3px 4px;
|
||||
|
||||
&:before { font-size: 0.9em; }
|
||||
}
|
||||
|
||||
.c-tag {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-right: 3px !important;
|
||||
padding: $tagApplierPadding;
|
||||
|
||||
&__remove-btn {
|
||||
display: block;
|
||||
> * + * { margin-left: $interiorMarginSm; }
|
||||
}
|
||||
|
||||
.c-tag-selection {
|
||||
.c-input--autocomplete__input {
|
||||
min-height: auto !important;
|
||||
padding: $tagApplierPadding;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,10 +92,18 @@
|
||||
.has-tag-applier {
|
||||
// Apply this class to all components that should trigger tag removal btn on hover
|
||||
&:hover {
|
||||
.c-tag__remove-btn {
|
||||
width: 1.1em;
|
||||
opacity: 0.7;
|
||||
.c-tag {
|
||||
padding-right: 17px !important;
|
||||
transition: $transOut;
|
||||
}
|
||||
|
||||
.c-tag__remove-btn {
|
||||
//display: block;
|
||||
//margin-left: $interiorMarginSm;
|
||||
width: 1em;
|
||||
opacity: 0.8;
|
||||
transition: $transOut;
|
||||
//transition-delay: 250ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
@drop="emitDropEvent"
|
||||
>
|
||||
<div
|
||||
class="c-tree__item c-elements-pool__item"
|
||||
class="c-tree__item c-elements-pool__item js-elements-pool__item"
|
||||
:class="{
|
||||
'is-context-clicked': contextClickActive,
|
||||
'hover': hover,
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<ul
|
||||
v-if="hasElements"
|
||||
id="inspector-elements-tree"
|
||||
class="c-tree c-elements-pool__tree"
|
||||
class="c-tree c-elements-pool__tree js-elements-pool__tree"
|
||||
>
|
||||
<div class="c-elements-pool__instructions"> Select and drag an element to move it into a different axis. </div>
|
||||
<element-item-group
|
||||
@@ -145,7 +145,7 @@ export default {
|
||||
|
||||
this.unlistenComposition();
|
||||
|
||||
if (this.parentObject) {
|
||||
if (this.parentObject && this.parentObject.type === 'telemetry.plot.overlay') {
|
||||
this.setYAxisIds();
|
||||
this.composition = this.openmct.composition.get(this.parentObject);
|
||||
|
||||
@@ -175,6 +175,7 @@ export default {
|
||||
setYAxisIds() {
|
||||
const configId = this.openmct.objects.makeKeyString(this.parentObject.identifier);
|
||||
this.config = configStore.get(configId);
|
||||
this.yAxes = [];
|
||||
this.yAxes.push({
|
||||
id: this.config.yAxis.id,
|
||||
elements: this.parentObject.configuration.series.filter(
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-annotation__row">
|
||||
<textarea
|
||||
v-model="contentModel"
|
||||
class="c-annotation__text_area"
|
||||
type="text"
|
||||
></textarea>
|
||||
<div>
|
||||
<span>{{ modifiedOnDate }}</span>
|
||||
<span>{{ modifiedOnTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Moment from 'moment';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
annotation: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
contentModel: {
|
||||
get() {
|
||||
return this.annotation.contentText;
|
||||
},
|
||||
set(contentText) {
|
||||
console.debug(`Set tag called with ${contentText}`);
|
||||
}
|
||||
},
|
||||
modifiedOnDate() {
|
||||
return this.formatTime(this.annotation.modified, 'YYYY-MM-DD');
|
||||
},
|
||||
modifiedOnTime() {
|
||||
return this.formatTime(this.annotation.modified, 'HH:mm:ss');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
getAvailableTagByID(tagID) {
|
||||
return this.openmct.annotation.getAvailableTags().find(tag => {
|
||||
return tag.id === tagID;
|
||||
});
|
||||
},
|
||||
formatTime(unixTime, timeFormat) {
|
||||
return Moment.utc(unixTime).format(timeFormat);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="c-inspector__properties c-inspect-properties has-tag-applier"
|
||||
class="c-inspector__properties c-inspect-properties"
|
||||
aria-label="Tags Inspector"
|
||||
>
|
||||
<div
|
||||
@@ -111,25 +111,31 @@ export default {
|
||||
return this?.selection?.[0]?.[0]?.context?.item;
|
||||
},
|
||||
targetDetails() {
|
||||
return this?.selection?.[0]?.[1]?.context?.targetDetails ?? {};
|
||||
return this?.selection?.[0]?.[0]?.context?.targetDetails ?? {};
|
||||
},
|
||||
shouldShowTagsEditor() {
|
||||
return Object.keys(this.targetDetails).length > 0;
|
||||
const showingTagsEditor = Object.keys(this.targetDetails).length > 0;
|
||||
|
||||
if (showingTagsEditor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
targetDomainObjects() {
|
||||
return this?.selection?.[0]?.[1]?.context?.targetDomainObjects ?? {};
|
||||
return this?.selection?.[0]?.[0]?.context?.targetDomainObjects ?? {};
|
||||
},
|
||||
selectedAnnotations() {
|
||||
return this?.selection?.[0]?.[1]?.context?.annotations;
|
||||
return this?.selection?.[0]?.[0]?.context?.annotations;
|
||||
},
|
||||
annotationType() {
|
||||
return this?.selection?.[0]?.[1]?.context?.annotationType;
|
||||
return this?.selection?.[0]?.[0]?.context?.annotationType;
|
||||
},
|
||||
annotationFilter() {
|
||||
return this?.selection?.[0]?.[1]?.context?.annotationFilter;
|
||||
return this?.selection?.[0]?.[0]?.context?.annotationFilter;
|
||||
},
|
||||
onAnnotationChange() {
|
||||
return this?.selection?.[0]?.[1]?.context?.onAnnotationChange;
|
||||
return this?.selection?.[0]?.[0]?.context?.onAnnotationChange;
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -195,6 +201,7 @@ export default {
|
||||
}
|
||||
},
|
||||
async loadAnnotationForTargetObject(target) {
|
||||
console.debug(`📝 Loading annotations for target`, target);
|
||||
const targetID = this.openmct.objects.makeKeyString(target.identifier);
|
||||
const allAnnotationsForTarget = await this.openmct.annotation.getAnnotations(target.identifier);
|
||||
const filteredAnnotationsForSelection = allAnnotationsForTarget.filter(annotation => {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
.c-inspect-annotations {
|
||||
> * + * {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
|
||||
&__content{
|
||||
> * + * {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
|
||||
&__group {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
margin-top: $interiorMarginLg;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
<script>
|
||||
import CreateAction from '@/plugins/formActions/CreateAction';
|
||||
import objectUtils from 'objectUtils';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
@@ -74,23 +73,9 @@ export default {
|
||||
this.openmct.menus.showSuperMenu(x, y, this.sortedItems, menuOptions);
|
||||
},
|
||||
create(key) {
|
||||
// Hack for support. TODO: rewrite create action.
|
||||
// 1. Get contextual object from navigation
|
||||
// 2. Get legacy type from legacy api
|
||||
// 3. Instantiate create action with type, parent, context
|
||||
// 4. perform action.
|
||||
return this.openmct.objects.get(this.openmct.router.path[0].identifier)
|
||||
.then((currentObject) => {
|
||||
const createAction = new CreateAction(this.openmct, key, currentObject);
|
||||
const createAction = new CreateAction(this.openmct, key, this.openmct.router.path[0]);
|
||||
|
||||
createAction.invoke();
|
||||
});
|
||||
},
|
||||
convertToLegacy(domainObject) {
|
||||
let keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
let oldModel = objectUtils.toOldFormat(domainObject);
|
||||
|
||||
return this.openmct.$injector.get('instantiate')(oldModel, keyString);
|
||||
createAction.invoke();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,9 +79,7 @@
|
||||
<multipane
|
||||
type="vertical"
|
||||
>
|
||||
<pane
|
||||
id="tree-pane"
|
||||
>
|
||||
<pane>
|
||||
<mct-tree
|
||||
ref="mctTree"
|
||||
:sync-tree-navigation="triggerSync"
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
ref="mainTree"
|
||||
class="c-tree-and-search__tree c-tree"
|
||||
role="tree"
|
||||
:aria-label="getAriaLabel"
|
||||
aria-expanded="true"
|
||||
>
|
||||
|
||||
@@ -192,6 +193,9 @@ export default {
|
||||
focusedItems() {
|
||||
return this.activeSearch ? this.searchResultItems : this.treeItems;
|
||||
},
|
||||
getAriaLabel() {
|
||||
return this.isSelectorTree ? "Create Modal Tree" : "Main Tree";
|
||||
},
|
||||
pageThreshold() {
|
||||
return Math.ceil(this.mainTreeHeight / this.itemHeight) + ITEM_BUFFER;
|
||||
},
|
||||
@@ -311,7 +315,7 @@ export default {
|
||||
}
|
||||
},
|
||||
targetedPathAnimationEnd() {
|
||||
this.targetedPath = undefined;
|
||||
this.targetedPath = null;
|
||||
},
|
||||
treeItemSelection(item) {
|
||||
this.selectedItem = item;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
align-items: flex-start;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
+ .c-recentobjects-listitem {
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
&__type-icon {
|
||||
color: $colorItemTreeIcon;
|
||||
font-size: 2.2em;
|
||||
font-size: 1.25em;
|
||||
|
||||
// TEMP: uses object-label component, hide label part
|
||||
.c-object-label__name {
|
||||
@@ -72,6 +72,7 @@
|
||||
|
||||
&__body {
|
||||
flex: 1 1 auto;
|
||||
padding-top: 2px; // Align with type icon
|
||||
|
||||
> * + * {
|
||||
margin-top: $interiorMarginSm;
|
||||
@@ -89,7 +90,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__tags {
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
&__title {
|
||||
border-radius: $basicCr;
|
||||
color: pullForward($colorBodyFg, 30%);
|
||||
color: $colorItemTreeFg;
|
||||
cursor: pointer;
|
||||
padding: $interiorMarginSm;
|
||||
|
||||
|
||||
@@ -150,16 +150,11 @@ export default {
|
||||
});
|
||||
const selection =
|
||||
[
|
||||
{
|
||||
element: this.openmct.layout.$refs.browseObject.$el,
|
||||
context: {
|
||||
item: this.result
|
||||
}
|
||||
},
|
||||
{
|
||||
element: this.$el,
|
||||
context: {
|
||||
type: 'plot-points-selection',
|
||||
item: this.result.targetModels[0],
|
||||
type: 'plot-annotation-search-result',
|
||||
targetDetails,
|
||||
targetDomainObjects,
|
||||
annotations: [this.result],
|
||||
|
||||
@@ -62,7 +62,7 @@ export default {
|
||||
},
|
||||
targetedPath: {
|
||||
type: String,
|
||||
required: true
|
||||
default: null
|
||||
},
|
||||
selectedItem: {
|
||||
type: Object,
|
||||
|
||||
Reference in New Issue
Block a user