[Inspector Tabs] Updates (#7987)
Some checks failed
CodeQL / Analyze (push) Has been cancelled

* added getTypes to types api, modifying which tabs are shown, working on annoatation tab
* moving hasNumericTelemetry to an api method
* updated types api to return all types, updated annotations api to return annotatable types, cleaned up use of hasNumericTelemetry elsewhere in the code

* Changes for tabs visibility and priority
- Alphanumeric formatting tab set to default priority while editing, low priority during browse.
- Good styling for Format tab contents in browse mode.
- Properties tab set to low priority during editing, default during browse.
- Make Elements pool visible in browse mode, omit edit capabilities.
- Edit and browse mode priorities for Properties and Elements.
- Adjusted edit and browse mode priorities for Properties and Elements.
- Priority set for Gantt view.
- Priorities set for Graph, Lad Table, Scatter Plot, Telem Tables and Time List views.
- Changed several Inspector tab names to 'Config'; tests and other code changed to target `key` instead of `name`:
  - LAD Table
  - Time List - will need regression testing for change noted re. `key` above.
- Created browse mode read-only Inspector views:
  - LAD Table, Lad Table set.
  - Telemetry Table.
  and `showTab` functions; `showTab` has just been set to true for now.
to prevent it from displaying when no filters can be set.
- Plot plugin.js now adds configuration.objectStyles {} for overlay and stacked plots on initialize.
- FiltersView now displays a message for telem sources that don't have filter criteria available.
- Annotations tab now set to never display when a view is being edited.
- Added `objectStyles: {}` to initialize functions for multiple objects:
  - Condition Widget
  - Gauge
  - LAD Table

---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Jamie V. 2025-05-15 16:55:15 -07:00 committed by GitHub
parent 10bc8eb55d
commit fa1a45b6cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
71 changed files with 825 additions and 256 deletions

View File

@ -484,7 +484,8 @@
"darkmatter",
"Undeletes",
"SSSZ",
"pageerror"
"pageerror",
"annotatable"
],
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"],
"ignorePaths": [

12
API.md
View File

@ -31,6 +31,10 @@
- [`latest` request strategy](#latest-request-strategy)
- [`minmax` request strategy](#minmax-request-strategy)
- [Telemetry Formats](#telemetry-formats)
- [Built-in Formats](#built-in-formats)
- [**Number Format (default):**](#number-format-default)
- [**String Format**](#string-format)
- [**Enum Format**](#enum-format)
- [Registering Formats](#registering-formats)
- [Telemetry Data](#telemetry-data)
- [Telemetry Datums](#telemetry-datums)
@ -59,6 +63,12 @@
- [Custom Indicators](#custom-indicators)
- [Priority API](#priority-api)
- [Priority Types](#priority-types)
- [User API](#user-api)
- [Example](#example)
- [Visibility-Based Rendering in View Providers](#visibility-based-rendering-in-view-providers)
- [Overview](#overview)
- [Implementing Visibility-Based Rendering](#implementing-visibility-based-rendering)
- [Example](#example-1)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -1301,9 +1311,11 @@ Open MCT provides some built-in priority values that can be used in the applicat
Currently, the Open MCT Priority API provides (type: numeric value):
- HIGHEST: Infinity
- HIGH: 1000
- Default: 0
- LOW: -1000
- LOWEST: -Infinity
View provider Example:

View File

@ -68,7 +68,11 @@ import { v4 as genUuid } from 'uuid';
* @param {string | import('../src/api/objects/ObjectAPI').Identifier} [options.parent='mine'] - The Identifier or uuid of the parent object. Defaults to 'mine' folder
* @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object.
*/
async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine' }) {
async function createDomainObjectWithDefaults(
page,
{ type, name, parent = 'mine' },
additionalOptions = {}
) {
if (!name) {
name = `${type}:${genUuid()}`;
}
@ -89,6 +93,13 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
await page.getByLabel('Title', { exact: true }).fill('');
await page.getByLabel('Title', { exact: true }).fill(name);
if (additionalOptions) {
for (const [key, value] of Object.entries(additionalOptions)) {
// eslint-disable-next-line playwright/no-raw-locators
await page.locator(`#form-${key}`).fill(value);
}
}
if (page.testNotes) {
// Fill the "Notes" section with information about the
// currently running test and its project.
@ -105,7 +116,7 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
if (await _isInEditMode(page, uuid)) {
// Save (exit edit mode)
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('button', { name: 'Save', exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
}

View File

@ -224,7 +224,7 @@ export async function createTimelistWithPlanAndSetActivityInProgress(page, planJ
await page.getByRole('button', { name: 'Edit Object' }).click();
// Find the display properties section in the inspector
await page.getByRole('tab', { name: 'View Properties' }).click();
await page.getByRole('tab', { name: 'Config' }).click();
// Switch to expanded view and save the setting
await page.getByLabel('Display Style').selectOption({ label: 'Expanded' });

View File

@ -76,6 +76,7 @@ export async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {
export async function testTelemetryItem(page, telemetryItem) {
// Check that telemetry item also received the tag
await page.goto(telemetryItem.url);
await page.getByRole('tab', { name: 'Annotations' }).click();
await expect(page.getByText('No tags to display for this item')).toBeVisible();
@ -93,6 +94,7 @@ export async function testTelemetryItem(page, telemetryItem) {
y: 100
}
});
await page.getByRole('tab', { name: 'Annotations' }).click();
await expect(page.getByText('Science')).toBeVisible();
await expect(page.getByText('Driving')).toBeHidden();
@ -107,6 +109,8 @@ export async function basicTagsTests(page) {
// Search for Driving
await page.getByRole('searchbox', { name: 'Search Input' }).click();
await page.getByRole('tab', { name: 'Annotations' }).click();
// Clicking elsewhere should cause annotation selection to be cleared
await expect(page.getByText('No tags to display for this item')).toBeVisible();
//
@ -119,6 +123,8 @@ export async function basicTagsTests(page) {
.first()
.click();
await page.getByRole('tab', { name: 'Annotations' }).click();
// Delete Driving Tag
await page.hover('[aria-label="Tag"]:has-text("Driving")');
await page.locator('[aria-label="Remove tag Driving"]').click();
@ -155,6 +161,8 @@ export async function basicTagsTests(page) {
}
});
await page.getByRole('tab', { name: 'Annotations' }).click();
//Expect Science to be visible but Driving to be hidden
await expect(page.getByText('Science')).toBeVisible();
await expect(page.getByText('Driving')).toBeHidden();
@ -170,7 +178,7 @@ export async function basicTagsTests(page) {
});
// Add Driving Tag again
await page.getByText('Annotations').click();
await page.getByRole('tab', { name: 'Annotations' }).click();
await page.getByRole('button', { name: /Add Tag/ }).click();
await page.getByPlaceholder('Type to select tag').click();
await page.getByText('Driving').click();

View File

@ -132,7 +132,7 @@ test("View a timelist in expanded view, verify all the activities are displayed
await page.getByRole('button', { name: 'Edit Object' }).click();
// Find the display properties section in the inspector
await page.getByRole('tab', { name: 'View Properties' }).click();
await page.getByRole('tab', { name: 'Config' }).click();
// Switch to expanded view and save the setting
await page.getByLabel('Display Style').selectOption({ label: 'Expanded' });

View File

@ -236,7 +236,7 @@ test.describe('Display Layout', () => {
name: new RegExp(sineWaveObject.name)
});
await sineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid'));
await page.locator('button[title="Save"]').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Subscribe to the Sine Wave Generator data
@ -278,7 +278,7 @@ test.describe('Display Layout', () => {
name: new RegExp(sineWaveObject.name)
});
await sineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid'));
await page.locator('button[title="Save"]').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Subscribe to the Sine Wave Generator data
@ -317,7 +317,7 @@ test.describe('Display Layout', () => {
name: new RegExp(sineWaveObject.name)
});
await sineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid'));
await page.locator('button[title="Save"]').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
expect.soft(await page.locator('.l-layout .l-layout__frame').count()).toEqual(1);
@ -358,7 +358,7 @@ test.describe('Display Layout', () => {
name: new RegExp(sineWaveObject.name)
});
await sineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid'));
await page.locator('button[title="Save"]').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
expect.soft(await page.locator('.l-layout .l-layout__frame').count()).toEqual(1);
@ -413,7 +413,7 @@ test.describe('Display Layout', () => {
await page.locator('div[title="Resize object width"] > input').click();
await page.locator('div[title="Resize object width"] > input').fill('70');
await page.locator('button[title="Save"]').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
const startDate = '2021-12-30 01:01:00.000Z';
@ -473,7 +473,7 @@ test.describe('Display Layout', () => {
await page.getByText('View type').click();
await page.getByText('Overlay Plot').click();
await page.getByLabel('Save').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Time to inspect some network traffic
@ -531,7 +531,7 @@ test.describe('Display Layout', () => {
await stateGeneratorTreeItem.click({ button: 'right' });
await page.getByLabel('Edit Properties...').click();
await page.getByLabel('State Duration (seconds)', { exact: true }).fill('0.1');
await page.getByLabel('Save').click();
await page.getByLabel('Save', { exact: true }).click();
// Create a Table for filtering ON values
const tableFilterOnValue = await createDomainObjectWithDefaults(page, {
@ -555,14 +555,14 @@ test.describe('Display Layout', () => {
await page.goto(tableFilterOnValue.url);
await stateGeneratorTreeItem.dragTo(page.getByLabel('Object View'));
await selectFilterOption(page, '1');
await page.getByLabel('Save').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Navigate to OFF filtering table and add state generator and setup filters
await page.goto(tableFilterOffValue.url);
await stateGeneratorTreeItem.dragTo(page.getByLabel('Object View'));
await selectFilterOption(page, '0');
await page.getByLabel('Save').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Navigate to the display layout and edit it
@ -586,7 +586,7 @@ test.describe('Display Layout', () => {
// eslint-disable-next-line playwright/no-force-option
force: true
});
await page.getByLabel('Save').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Get the tables so we can verify filtering is working as expected

View File

@ -54,8 +54,8 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat
await page.goto(exampleDataVisualizationSource.url);
await page.getByRole('tab', { name: 'Data Visualization' }).click();
await page.getByRole('cell', { name: /First Sine Wave Generator/ }).click();
await page.getByRole('tab', { name: 'Data Visualization' }).click();
await expect(page.getByText('Numeric Data')).toBeVisible();
await expect(
page.locator('span.plot-series-name', { hasText: 'First Sine Wave Generator Hz' })
@ -63,6 +63,7 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
await page.getByRole('cell', { name: /Second Sine Wave Generator/ }).click();
await page.getByRole('tab', { name: 'Data Visualization' }).click();
await expect(page.getByText('Numeric Data')).toBeVisible();
await expect(
page.locator('span.plot-series-name', { hasText: 'Second Sine Wave Generator Hz' })
@ -77,6 +78,8 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat
// ensure our new tab's title is correct
const newPage = await pagePromise;
await newPage.waitForLoadState();
await page.getByRole('tab', { name: 'Data Visualization' }).click();
// expect new tab title to contain 'Second Sine Wave Generator'
await expect(newPage).toHaveTitle('Second Sine Wave Generator');

View File

@ -53,7 +53,6 @@ test.describe('Testing LAD table configuration', () => {
test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => {
// Edit LAD table
await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// make sure headers are visible initially
await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeVisible();
@ -114,7 +113,6 @@ test.describe('Testing LAD table configuration', () => {
// Edit LAD table
await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// show timestamp column
await page.getByLabel('Timestamp', { exact: true }).check();
@ -142,7 +140,6 @@ test.describe('Testing LAD table configuration', () => {
// Edit LAD table
await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// show units, type, and WATCH columns
await page.getByLabel('Units').check();
@ -182,7 +179,6 @@ test.describe('Testing LAD table configuration', () => {
// Edit LAD table
await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// make sure Sine Wave headers are visible initially too
await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeVisible();

View File

@ -65,9 +65,11 @@ test.describe('Tagging in Notebooks @addInit', () => {
});
test('Can add tags with blank entry', async ({ page }) => {
await createDomainObjectWithDefaults(page, { type: 'Notebook' });
await page.getByRole('tab', { name: 'Annotations' }).click();
await enterTextEntry(page, '');
await page.getByRole('tab', { name: 'Annotations' }).click();
await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag")`).click();

View File

@ -47,8 +47,6 @@ test.describe('Notebook Tests with CouchDB @couchdb @network', () => {
});
test('Inspect Notebook Entry Network Requests', async ({ page }) => {
//Ensure we're on the annotations Tab in the inspector
await page.getByText('Annotations').click();
// Expand sidebar
await page.locator('.c-notebook__toggle-nav-button').click();
@ -86,6 +84,9 @@ test.describe('Notebook Tests with CouchDB @couchdb @network', () => {
await page.waitForLoadState('networkidle');
expect(notebookElementsRequests.length).toBeLessThanOrEqual(2);
//Ensure we're on the annotations Tab in the inspector
await page.getByText('Annotations').click();
// Add some tags
// Network Requests are for each tag creation are:
// 1) Getting the original path of the parent object
@ -180,8 +181,8 @@ test.describe('Notebook Tests with CouchDB @couchdb @network', () => {
type: 'issue',
description: 'https://github.com/akhenry/openmct-yamcs/issues/69'
});
await page.getByText('Annotations').click();
await nbUtils.enterTextEntry(page, 'First Entry');
await page.getByText('Annotations').click();
// Add three tags
await addTagAndAwaitNetwork(page, 'Science');

View File

@ -100,6 +100,9 @@ test.describe('Overlay Plot', () => {
await page.getByLabel('Expand By Default').check();
await page.getByLabel('Save').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.getByRole('tab', { name: 'Config' }).click();
// Assert that the legend is now open
await expect(page.getByLabel('Plot Legend Collapsed')).toBeHidden();
await expect(page.getByLabel('Plot Legend Expanded')).toBeVisible();
@ -111,6 +114,9 @@ test.describe('Overlay Plot', () => {
// Assert that the legend is expanded on page load
await page.reload();
await page.getByRole('tab', { name: 'Config' }).click();
await expect(page.getByLabel('Plot Legend Collapsed')).toBeHidden();
await expect(page.getByLabel('Plot Legend Expanded')).toBeVisible();
await expect(page.getByRole('cell', { name: 'Name' })).toBeVisible();

View File

@ -50,7 +50,7 @@ test.describe('Plots work in Previews', () => {
});
const layoutGridHolder = page.getByLabel('Test Display Layout Layout Grid');
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
await page.getByLabel('Save').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// right click on the plot and select view large
@ -67,7 +67,7 @@ test.describe('Plots work in Previews', () => {
await page.getByLabel('Move Sub-object Frame').click();
await page.getByText('View type').click();
await page.getByText('Overlay Plot').click();
await page.getByLabel('Save').click();
await page.getByLabel('Save', { exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await expect(
page.getByLabel('Test Display Layout Layout', { exact: true }).getByLabel('Plot Canvas')

View File

@ -152,14 +152,14 @@ test.describe('Stacked Plot', () => {
}) => {
await page.goto(stackedPlot.url);
await page.getByRole('tab', { name: 'Config' }).click();
// Click on the 1st plot
await page
.getByLabel('Stacked Plot Item Sine Wave Generator A')
.getByLabel('Plot Canvas')
.click();
await page.getByRole('tab', { name: 'Config' }).click();
// Assert that the inspector shows the Y Axis properties for swgA
await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
@ -172,6 +172,9 @@ test.describe('Stacked Plot', () => {
.getByLabel('Stacked Plot Item Sine Wave Generator B')
.getByLabel('Plot Canvas')
.click();
await page.getByRole('tab', { name: 'Config' }).click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
@ -184,6 +187,9 @@ test.describe('Stacked Plot', () => {
.getByLabel('Stacked Plot Item Sine Wave Generator C')
.getByLabel('Plot Canvas')
.click();
await page.getByRole('tab', { name: 'Config' }).click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
@ -194,7 +200,7 @@ test.describe('Stacked Plot', () => {
// Go into edit mode
await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'Config' }).click();
// await page.getByRole('tab', { name: 'Config' }).click();
// Click on the 1st plot
await page.getByLabel('Stacked Plot Item Sine Wave Generator A').click();
@ -233,11 +239,11 @@ test.describe('Stacked Plot', () => {
// Go into edit mode
await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'Config' }).click();
// Click on canvas for the 1st plot
await page.getByLabel(`Stacked Plot Item ${swgA.name}`).click();
await page.getByRole('tab', { name: 'Config' }).click();
// Expand config for the series
await page.getByLabel('Expand Sine Wave Generator A Plot Series Options').click();
@ -255,6 +261,8 @@ test.describe('Stacked Plot', () => {
// Click on canvas for the 1st plot
await page.getByLabel(`Stacked Plot Item ${swgA.name}`).click();
await page.getByRole('tab', { name: 'Config' }).click();
// Expand config for the series
await page.getByLabel('Expand Sine Wave Generator A Plot Series Options').click();

View File

@ -45,6 +45,8 @@ const setFontFamily = '"Andale Mono", sans-serif';
test.describe('Stacked Plot styling', () => {
let stackedPlot;
let overlayPlot1;
let overlayPlot2;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
@ -54,17 +56,30 @@ test.describe('Stacked Plot styling', () => {
name: 'StackedPlot1'
});
// create two overlay plots
overlayPlot1 = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: 'Overlay Plot 1',
parent: stackedPlot.uuid
});
overlayPlot2 = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: 'Overlay Plot 2',
parent: stackedPlot.uuid
});
// Create two SWGs and attach them to the Stacked Plot
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Sine Wave Generator 1',
parent: stackedPlot.uuid
parent: overlayPlot1.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Sine Wave Generator 2',
parent: stackedPlot.uuid
parent: overlayPlot2.uuid
});
});
@ -138,21 +153,21 @@ test.describe('Stacked Plot styling', () => {
NO_STYLE_RGBA,
NO_STYLE_RGBA,
hexToRGB(setTextColor),
page.getByLabel('Stacked Plot Item Sine Wave Generator 1')
page.getByLabel('Stacked Plot Item Overlay Plot 1')
);
await checkStyles(
NO_STYLE_RGBA,
NO_STYLE_RGBA,
hexToRGB(setTextColor),
page.getByLabel('Stacked Plot Item Sine Wave Generator 2')
page.getByLabel('Stacked Plot Item Overlay Plot 2')
);
await checkFontStyles(
setFontSize,
setFontWeight,
setFontFamily,
page.getByLabel('Stacked Plot Item Sine Wave Generator 1')
page.getByLabel('Stacked Plot Item Overlay Plot 1')
);
});
@ -169,19 +184,19 @@ test.describe('Stacked Plot styling', () => {
await page.getByRole('tab', { name: 'Styles' }).click();
//Check default styles for SWG1 and SWG2
//Check default styles for overlayPlot1 and overlayPlot2
await checkStyles(
NO_STYLE_RGBA,
NO_STYLE_RGBA,
hexToRGB(defaultTextColor),
page.getByLabel('Stacked Plot Item Sine Wave Generator 1')
page.getByLabel('Stacked Plot Item Overlay Plot 1')
);
await checkStyles(
NO_STYLE_RGBA,
NO_STYLE_RGBA,
hexToRGB(defaultTextColor),
page.getByLabel('Stacked Plot Item Sine Wave Generator 2')
page.getByLabel('Stacked Plot Item Overlay Plot 2')
);
// Set styles using setStyles function on StackedPlot1 but not StackedPlot2
@ -190,11 +205,11 @@ test.describe('Stacked Plot styling', () => {
setBorderColor,
setBackgroundColor,
setTextColor,
page.getByLabel('Stacked Plot Item Sine Wave Generator 1')
page.getByLabel('Stacked Plot Item Overlay Plot 1')
);
//Set Font Styles on SWG1 but not SWG2
await page.getByLabel('Stacked Plot Item Sine Wave Generator 1').click();
await page.getByLabel('Stacked Plot Item Overlay Plot 1').click();
//Set Font Size to 72
await page.getByLabel('Set Font Size').click();
await page.getByRole('menuitem', { name: '72px' }).click();

View File

@ -1,3 +1,5 @@
/* eslint-disable playwright/no-conditional-in-test */
/* eslint-disable playwright/no-conditional-expect */
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
@ -31,6 +33,104 @@ Enim nec dui nunc mattis. Cursus turpis massa tincidunt dui ut. Donec adipiscing
Proin libero nunc consequat interdum varius sit amet mattis vulputate. Metus dictum at tempor commodo ullamcorper a lacus vestibulum sed. Quisque non tellus orci ac auctor augue mauris. Id ornare arcu odio ut. Rhoncus est pellentesque elit ullamcorper dignissim. Senectus et netus et malesuada fames ac turpis egestas. Volutpat ac tincidunt vitae semper quis lectus nulla. Adipiscing elit duis tristique sollicitudin. Ipsum faucibus vitae aliquet nec ullamcorper sit. Gravida neque convallis a cras semper auctor neque vitae tempus. Porttitor leo a diam sollicitudin tempor id. Dictum non consectetur a erat nam at lectus. At volutpat diam ut venenatis tellus in. Morbi enim nunc faucibus a pellentesque sit amet. Cursus in hac habitasse platea. Sed augue lacus viverra vitae.
`;
const viewsTabsMatrix = {
Clock: {
Browse: ['Properties']
},
'Condition Set': {
Browse: ['Properties', 'Elements', 'Annotations'],
Edit: ['Elements', 'Properties']
},
'Condition Widget': {
Browse: ['Properties', 'Styles'],
Edit: ['Styles', 'Properties']
},
'Display Layout': {
Browse: ['Properties', 'Elements', 'Styles'],
Edit: ['Elements', 'Styles', 'Properties']
},
'Event Message Generator': {
Browse: ['Properties']
},
'Event Message Generator with Acknowledge': {
Browse: ['Properties']
},
'Example Imagery': {
Browse: ['Properties', 'Annotations']
},
'Flexible Layout': {
Browse: ['Properties', 'Elements', 'Styles'],
Edit: ['Elements', 'Styles', 'Properties']
},
Folder: {
Browse: ['Properties']
},
'Gantt Chart': {
Browse: ['Properties', 'Config', 'Elements'],
Edit: ['Config', 'Elements', 'Properties']
},
Gauge: {
Browse: ['Properties', 'Elements', 'Styles'],
Edit: ['Elements', 'Styles', 'Properties']
},
Graph: {
Browse: ['Properties', 'Config', 'Elements', 'Styles'],
Edit: ['Config', 'Elements', 'Styles', 'Properties']
},
Hyperlink: {
Browse: ['Properties'],
required: {
url: 'https://www.google.com',
displayText: 'Google'
}
},
'LAD Table': {
Browse: ['Properties', 'Config', 'Elements', 'Styles'],
Edit: ['Config', 'Elements', 'Styles', 'Properties']
},
'LAD Table Set': {
Browse: ['Properties', 'Config', 'Elements', 'Styles'],
Edit: ['Config', 'Elements', 'Styles', 'Properties']
},
Notebook: {
Browse: ['Properties']
},
'Overlay Plot': {
Browse: ['Properties', 'Config', 'Annotations', 'Styles'],
Edit: ['Config', 'Elements', 'Styles', 'Filters', 'Properties']
},
'Scatter Plot': {
Browse: ['Properties', 'Config', 'Elements', 'Styles'],
Edit: ['Config', 'Elements', 'Styles', 'Properties']
},
'Sine Wave Generator': {
Browse: ['Properties', 'Annotations']
},
'Stacked Plot': {
Browse: ['Properties', 'Config', 'Annotations', 'Elements', 'Styles'],
Edit: ['Config', 'Elements', 'Styles', 'Properties']
},
'Tabs View': {
Browse: ['Properties', 'Elements', 'Styles'],
Edit: ['Elements', 'Styles', 'Properties']
},
'Telemetry Table': {
Browse: ['Properties', 'Config', 'Elements', 'Styles'],
Edit: ['Config', 'Elements', 'Styles', 'Filters', 'Properties']
},
'Time List': {
Browse: ['Properties', 'Config', 'Elements'],
Edit: ['Config', 'Elements', 'Properties']
},
'Time Strip': {
Browse: ['Properties', 'Elements'],
Edit: ['Elements', 'Properties']
},
Timer: {
Browse: ['Properties']
}
};
test.describe('Inspector tests', () => {
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
@ -72,4 +172,49 @@ test.describe('Inspector tests', () => {
await expect(lastInspectorPropertyValue).toBeInViewport();
});
test(`Inspector tabs show the correct tabs per view and mode`, async ({ page }) => {
// loop through each view type
for (const view of Object.keys(viewsTabsMatrix)) {
const viewConfig = viewsTabsMatrix[view];
const createOptions = {
type: view,
name: view
};
// create and navigate to view;
const objectInfo = await createDomainObjectWithDefaults(
page,
createOptions,
viewConfig.required ? viewConfig.required : {}
);
await page.goto(objectInfo.url);
// verify correct number of tabs for browse mode
expect(await page.getByRole('tab').count()).toBe(Object.keys(viewConfig.Browse).length);
// verify correct order of tabs for browse mode
for (const [index, value] of Object.entries(viewConfig.Browse)) {
const tab = page.getByRole('tab').nth(index);
await expect(tab).toHaveText(value);
}
// enter Edit if necessary
if (viewConfig.Edit) {
await page.getByLabel('Edit Object').click();
// verify correct number of tabs for edit mode
expect(await page.getByRole('tab').count()).toBe(Object.keys(viewConfig.Edit).length);
// verify correct order of tabs for edit mode
for (const [index, value] of Object.entries(viewConfig.Edit)) {
const tab = page.getByRole('tab').nth(index);
await expect(tab).toHaveText(value);
}
await page.getByLabel('Save').first().click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
}
}
});
});

View File

@ -40,6 +40,9 @@ test.describe('Visual - Inspector @ally @clock', () => {
});
test('Inspector from overlay_plot_with_delay_storage @localStorage', async ({ page, theme }) => {
// navigate to the plot
await page.getByRole('gridcell', { name: 'Overlay Plot with 5s Delay' }).click();
//Expand the Inspector Pane
await page.getByRole('button', { name: 'Inspect' }).click();

View File

@ -83,7 +83,7 @@ test.describe('Grand Search @a11y', () => {
);
// Save and finish editing the Display Layout
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('button', { name: 'Save', exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Search for the object

View File

@ -100,9 +100,12 @@ test.describe('Flexible Layout styling @a11y', () => {
);
// Save Flexible Layout
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('button', { name: 'Save', exact: true }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Select styles tab
await page.getByRole('tab', { name: 'Styles' }).click();
await percySnapshot(
page,
`Saved Styled Flex Layout with Styled StackedPlot (theme: '${theme}')`
@ -124,17 +127,30 @@ test.describe('Stacked Plot styling @a11y', () => {
name: 'StackedPlot1'
});
// Create two SWGs and attach them to the Stacked Plot
// Create an overlay plots to hold the SWGs
const overlayPlot1 = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: 'Overlay Plot 1',
parent: stackedPlot.uuid
});
const overlayPlot2 = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: 'Overlay Plot 2',
parent: stackedPlot.uuid
});
// Create two SWGs and attach them to the overlay plots
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Sine Wave Generator 1',
parent: stackedPlot.uuid
parent: overlayPlot1.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Sine Wave Generator 2',
parent: stackedPlot.uuid
parent: overlayPlot2.uuid
});
});
@ -177,7 +193,7 @@ test.describe('Stacked Plot styling @a11y', () => {
setBorderColor,
setBackgroundColor,
setTextColor,
page.getByLabel('Stacked Plot Item Sine Wave Generator 1')
page.getByLabel('Stacked Plot Item Overlay Plot 1')
);
await percySnapshot(page, `Edit Mode StackedPlot with Styled SWG (theme: '${theme}')`);

View File

@ -582,4 +582,15 @@ export default class AnnotationAPI extends EventEmitter {
_.isEqual(targets, otherTargets)
);
}
/**
* Checks if the given type is annotatable
* @param {string} type The type to check
* @returns {boolean} Returns true if the type is annotatable
*/
isAnnotatableType(type) {
const types = this.openmct.types.getAllTypes();
return types[type]?.definition?.annotatable;
}
}

View File

@ -21,8 +21,11 @@
*****************************************************************************/
const PRIORITIES = Object.freeze({
HIGHEST: Infinity,
HIGH: 1000,
DEFAULT: 0,
LOW: -1000
LOW: -1000,
LOWEST: -Infinity
});
export default PRIORITIES;

View File

@ -284,6 +284,33 @@ export default class TelemetryAPI {
return value;
}
/**
* Determines whether a domain object has numeric telemetry data.
* A domain object has numeric telemetry if it:
* 1. Has a telemetry property
* 2. Has telemetry metadata with domain values (like timestamps)
* 3. Has range values (measurements) where at least one is numeric
*
* @method hasNumericTelemetry
* @param {import('openmct').DomainObject} domainObject The domain object to check
* @returns {boolean} True if the object has numeric telemetry, false otherwise
*/
hasNumericTelemetry(domainObject) {
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
return false;
}
const metadata = this.openmct.telemetry.getMetadata(domainObject);
const rangeValues = metadata.valuesForHints(['range']);
const domains = metadata.valuesForHints(['domain']);
return (
domains.length > 0 &&
rangeValues.length > 0 &&
!rangeValues.every((value) => value.format === 'string')
);
}
/**
* Generates a numeric hash value for an options object. The hash is consistent
* for equivalent option objects regardless of property order.

View File

@ -89,6 +89,17 @@ export default class TypeRegistry {
get(typeKey) {
return this.types[typeKey] || UNKNOWN_TYPE;
}
/**
* List all registered types.
* @returns {Type[]} all registered types
*/
getAllTypes() {
return this.types;
}
/**
* Import legacy types.
* @param {TypeDefinition[]} types the types to import
*/
importLegacyTypes(types) {
types
.filter((t) => this.get(t.key) === UNKNOWN_TYPE)

View File

@ -41,9 +41,10 @@ export default class LADTableConfiguration extends EventEmitter {
}
getConfiguration() {
const configuration = this.domainObject.configuration || {};
configuration.hiddenColumns = configuration.hiddenColumns || {};
const configuration = this.domainObject.configuration ?? {};
configuration.hiddenColumns = configuration.hiddenColumns ?? {};
configuration.isFixedLayout = configuration.isFixedLayout ?? true;
configuration.objectStyles = configuration.objectStyles ?? {};
return configuration;
}

View File

@ -27,7 +27,7 @@ import LadTableConfiguration from './components/LadTableConfiguration.vue';
export default function LADTableConfigurationViewProvider(openmct) {
return {
key: 'lad-table-configuration',
name: 'LAD Table Configuration',
name: 'Config',
canView(selection) {
if (selection.length !== 1 || selection[0].length === 0) {
return false;
@ -61,7 +61,7 @@ export default function LADTableConfigurationViewProvider(openmct) {
_destroy = destroy;
},
priority() {
return 1;
return openmct.editor.isEditing() ? openmct.priority.HIGH : openmct.priority.DEFAULT;
},
destroy() {
if (_destroy) {

View File

@ -22,28 +22,24 @@
<template>
<div class="c-inspect-properties">
<template v-if="isEditing">
<div class="c-inspect-properties__header">Table Column Visibility</div>
<ul class="c-inspect-properties__section">
<li v-for="(title, key) in headers" :key="key" class="c-inspect-properties__row">
<div class="c-inspect-properties__label" title="Show or hide column">
<label :for="key + 'ColumnControl'">{{ title }}</label>
</div>
<div class="c-inspect-properties__value">
<input
:id="key + 'ColumnControl'"
type="checkbox"
:checked="configuration.hiddenColumns[key] !== true"
@change="toggleColumn(key)"
/>
</div>
</li>
</ul>
</template>
<template v-else>
<div class="c-inspect-properties__header">LAD Table Configuration</div>
<div class="c-inspect-properties__row--span-all">Only available in edit mode.</div>
</template>
<div class="c-inspect-properties__header">Table Column Visibility</div>
<ul class="c-inspect-properties__section">
<li v-for="(title, key) in headers" :key="key" class="c-inspect-properties__row">
<div class="c-inspect-properties__label" title="Show or hide column">
<label :for="key + 'ColumnControl'">{{ title }}</label>
</div>
<div class="c-inspect-properties__value">
<input
v-if="isEditing"
:id="key + 'ColumnControl'"
type="checkbox"
:checked="configuration.hiddenColumns[key] !== true"
@change="toggleColumn(key)"
/>
<span v-if="!isEditing && configuration.hiddenColumns[key] !== true">Visible</span>
</div>
</li>
</ul>
</div>
</template>
@ -62,7 +58,8 @@ export default {
isEditing: this.openmct.editor.isEditing(),
configuration: ladTableConfiguration.getConfiguration(),
items: [],
ladTableObjects: []
ladTableObjects: [],
ladTelemetryObjects: {}
};
},
computed: {
@ -150,11 +147,14 @@ export default {
this.ladTableObjects.push(ladTable);
const composition = this.openmct.composition.get(ladTable.domainObject);
composition.on('add', this.addItem);
composition.on('remove', this.removeItem);
composition.load();
this.compositions.push({
composition
composition,
addCallback: this.addItem,
removeCallback: this.removeItem
});
},
removeLadTable(identifier) {

View File

@ -39,6 +39,9 @@ export default function plugin() {
cssClass: 'icon-tabular-lad',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
objectStyles: {}
};
}
});

View File

@ -41,7 +41,7 @@ export default function BarGraphInspectorViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
return openmct.editor.isEditing() ? openmct.priority.HIGH : openmct.priority.DEFAULT;
},
destroy: function () {
if (_destroy) {

View File

@ -40,7 +40,7 @@ export default function ScatterPlotInspectorViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
return openmct.editor.isEditing() ? openmct.priority.HIGH : openmct.priority.DEFAULT;
},
destroy: function () {
if (_destroy) {

View File

@ -0,0 +1,38 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function conditionWidgetStylesInterceptor(openmct) {
return {
appliesTo: (identifier, domainObject) => {
return domainObject?.type === 'conditionWidget' && !domainObject.configuration?.objectStyles;
},
invoke: (identifier, domainObject) => {
if (!domainObject.configuration) {
domainObject.configuration = {};
}
domainObject.configuration.objectStyles = {};
return domainObject;
}
};
}

View File

@ -20,11 +20,13 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import conditionWidgetStylesInterceptor from './conditionWidgetStylesInterceptor.js';
import ConditionWidgetViewProvider from './ConditionWidgetViewProvider.js';
export default function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new ConditionWidgetViewProvider(openmct));
openmct.objects.addGetInterceptor(conditionWidgetStylesInterceptor(openmct));
openmct.types.addType('conditionWidget', {
key: 'conditionWidget',
@ -34,7 +36,9 @@ export default function plugin() {
creatable: true,
cssClass: 'icon-condition-widget',
initialize(domainObject) {
domainObject.configuration = {};
domainObject.configuration = {
objectStyles: {}
};
domainObject.label = 'Condition Widget';
domainObject.conditionalLabel = '';
domainObject.url = '';

View File

@ -65,7 +65,9 @@ class AlphanumericFormatView {
}
priority() {
return 1;
return this.openmct.editor.isEditing()
? this.openmct.priority.DEFAULT
: this.openmct.priority.LOW;
}
destroy() {

View File

@ -31,7 +31,8 @@ export default function DisplayLayoutType() {
domainObject.composition = [];
domainObject.configuration = {
items: [],
layoutGrid: [10, 10]
layoutGrid: [10, 10],
objectStyles: {}
};
},
form: [

View File

@ -32,13 +32,16 @@
</div>
<div class="c-inspect-properties__value">
<input
v-if="isEditing"
id="telemetryPrintfFormat"
type="text"
:disabled="!isEditing"
:value="telemetryFormat"
:placeholder="nonMixedFormat ? '' : 'Mixed'"
@change="formatTelemetry"
/>
<template v-if="!isEditing && telemetryFormat?.length">
{{ telemetryFormat }}
</template>
</div>
</li>
</ul>

View File

@ -0,0 +1,40 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function displayLayoutStylesInterceptor(openmct) {
return {
appliesTo: (identifier, domainObject) => {
return domainObject?.type === 'layout';
},
invoke: (identifier, domainObject) => {
if (!domainObject.configuration) {
domainObject.configuration = {};
}
if (!domainObject.configuration.objectStyles) {
domainObject.configuration.objectStyles = {};
}
return domainObject;
}
};
}

View File

@ -25,6 +25,7 @@ import mount from 'utils/mount';
import CopyToClipboardAction from './actions/CopyToClipboardAction.js';
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
import DisplayLayout from './components/DisplayLayout.vue';
import displayLayoutStylesInterceptor from './displayLayoutStylesInterceptor.js';
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
import DisplayLayoutType from './DisplayLayoutType.js';
import DisplayLayoutDrawingObjectTypes from './DrawingObjectTypes.js';
@ -123,6 +124,7 @@ export default function DisplayLayoutPlugin(options) {
return 100;
}
});
openmct.objects.addGetInterceptor(displayLayoutStylesInterceptor(openmct));
openmct.types.addType('layout', DisplayLayoutType());
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct));
openmct.inspectorViews.addProvider(new AlphaNumericFormatViewProvider(openmct, options));

View File

@ -63,7 +63,7 @@ export default function FaultManagementInspectorViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
return openmct.priority.HIGH;
},
destroy: function () {
if (_destroy) {

View File

@ -43,8 +43,6 @@ export default class FiltersInspectorViewProvider {
let openmct = this.openmct;
let _destroy = null;
const domainObject = selection?.[0]?.[0]?.context?.item;
return {
show: function (element) {
const { destroy } = mount(
@ -69,13 +67,6 @@ export default class FiltersInspectorViewProvider {
if (isEditing) {
return true;
}
const metadata = openmct.telemetry.getMetadata(domainObject);
const metadataWithFilters = metadata
? metadata.valueMetadatas.filter((value) => value.filters)
: [];
return metadataWithFilters.length;
},
priority: function () {
return openmct.priority.DEFAULT;

View File

@ -38,6 +38,9 @@
@update-filters="persistFilters"
/>
</ul>
<span v-else>
This view doesn't include any parameters that have configured filter criteria.
</span>
</template>
<script>

View File

@ -158,6 +158,7 @@ export default {
this.composition.on('remove', this.removeChildObject);
this.composition.on('add', this.addFrame);
this.composition.load();
this.unObserveContainers = this.openmct.objects.observe(
this.domainObject,
'configuration.containers',

View File

@ -0,0 +1,40 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function flexibleLayoutStylesInterceptor(openmct) {
return {
appliesTo: (identifier, domainObject) => {
return domainObject?.type === 'flexible-layout';
},
invoke: (identifier, domainObject) => {
if (!domainObject.configuration) {
domainObject.configuration = {};
}
if (!domainObject.configuration.objectStyles) {
domainObject.configuration.objectStyles = {};
}
return domainObject;
}
};
}

View File

@ -20,6 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import flexibleLayoutStylesInterceptor from './flexibleLayoutStylesInterceptor.js';
import FlexibleLayoutViewProvider from './flexibleLayoutViewProvider.js';
import ToolBarProvider from './toolbarProvider.js';
import Container from './utils/container.js';
@ -37,11 +38,13 @@ export default function plugin() {
initialize: function (domainObject) {
domainObject.configuration = {
containers: [new Container(50), new Container(50)],
rowsLayout: false
rowsLayout: false,
objectStyles: {}
};
domainObject.composition = [];
}
});
openmct.objects.addGetInterceptor(flexibleLayoutStylesInterceptor(openmct));
let toolbar = ToolBarProvider(openmct);

View File

@ -21,28 +21,10 @@
*****************************************************************************/
export default function GaugeCompositionPolicy(openmct) {
function hasNumericTelemetry(domainObject) {
const hasTelemetry = openmct.telemetry.isTelemetryObject(domainObject);
if (!hasTelemetry) {
return false;
}
const metadata = openmct.telemetry.getMetadata(domainObject);
return metadata.values().length > 0 && hasDomainAndRange(metadata);
}
function hasDomainAndRange(metadata) {
return (
metadata.valuesForHints(['range']).length > 0 &&
metadata.valuesForHints(['domain']).length > 0
);
}
return {
allow: function (parent, child) {
if (parent.type === 'gauge') {
return hasNumericTelemetry(child);
return openmct.telemetry.hasNumericTelemetry(child);
}
return true;

View File

@ -24,6 +24,7 @@ import mount from 'utils/mount';
import GaugeFormController from './components/GaugeFormController.vue';
import GaugeCompositionPolicy from './GaugeCompositionPolicy.js';
import gaugeStylesInterceptor from './gaugeStylesInterceptor.js';
import GaugeViewProvider from './GaugeViewProvider.js';
export const GAUGE_TYPES = [
@ -37,7 +38,7 @@ export const GAUGE_TYPES = [
export default function () {
return function install(openmct) {
openmct.objectViews.addProvider(new GaugeViewProvider(openmct));
openmct.objects.addGetInterceptor(gaugeStylesInterceptor(openmct));
openmct.forms.addNewFormControl('gauge-controller', getGaugeFormController(openmct));
openmct.types.addType('gauge', {
name: 'Gauge',
@ -59,7 +60,8 @@ export default function () {
max: 100,
min: 0,
precision: 2
}
},
objectStyles: {}
};
},
form: [

View File

@ -0,0 +1,40 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function gaugeStylesInterceptor(openmct) {
return {
appliesTo: (identifier, domainObject) => {
return domainObject?.type === 'gauge';
},
invoke: (identifier, domainObject) => {
if (!domainObject.configuration) {
domainObject.configuration = {};
}
if (!domainObject.configuration.objectStyles) {
domainObject.configuration.objectStyles = {};
}
return domainObject;
}
};
}

View File

@ -30,17 +30,30 @@ export default function AnnotationsViewProvider(openmct) {
name: 'Annotations',
canView: function (selection) {
const availableTags = openmct.annotation.getAvailableTags();
const selectionContext = selection?.[0]?.[0]?.context;
const domainObject = selectionContext?.item;
const isLayoutItem = selectionContext?.layoutItem;
if (availableTags.length < 1) {
if (availableTags.length < 1 || isLayoutItem || !domainObject || openmct.editor.isEditing()) {
return false;
}
return selection.length;
const isAnnotatableType = openmct.annotation.isAnnotatableType(domainObject.type);
const metadata = openmct.telemetry.getMetadata(domainObject);
const hasImagery = metadata?.valuesForHints(['image']).length > 0;
const isNotebookEntry = selectionContext?.type === 'notebook-entry-selection';
const hasNumericTelemetry = openmct.telemetry.hasNumericTelemetry(domainObject);
return isAnnotatableType || hasImagery || hasNumericTelemetry || isNotebookEntry;
},
view: function (selection) {
let _destroy = null;
const domainObject = selection?.[0]?.[0]?.context?.item;
const selectionContext = selection?.[0]?.[0]?.context;
const isImageSelection = selectionContext?.type === 'clicked-on-image-selection';
const domainObject = selectionContext?.item;
const isNotebookEntry = selectionContext?.type === 'notebook-entry-selection';
const isConditionSet = domainObject?.type === 'conditionSet';
return {
show: function (element) {
@ -64,6 +77,14 @@ export default function AnnotationsViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
if (isNotebookEntry || isImageSelection) {
return openmct.priority.HIGHEST;
}
if (isConditionSet) {
return openmct.priority.LOW;
}
return openmct.priority.DEFAULT;
},
destroy: function () {

View File

@ -22,6 +22,7 @@
<template>
<li
v-if="allowDrag"
draggable="true"
:aria-label="`${elementObject.name} Element Item`"
:aria-grabbed="hover"
@ -47,6 +48,22 @@
/>
</div>
</li>
<li v-else :aria-label="`${elementObject.name} Element Item`">
<div
class="c-tree__item c-elements-pool__item js-elements-pool__item"
:class="{
'is-context-clicked': contextClickActive,
hover: hover,
'is-alias': isAlias
}"
>
<ObjectLabel
:domain-object="elementObject"
:object-path="[elementObject, domainObject]"
@context-click-active="setContextClickState"
/>
</div>
</li>
</template>
<script>
@ -74,6 +91,9 @@ export default {
},
allowDrop: {
type: Boolean
},
allowDrag: {
type: Boolean
}
},
emits: ['drop-custom', 'dragstart-custom'],

View File

@ -39,6 +39,7 @@
:key="element.identifier.key"
:index="index"
:element-object="element"
:allow-drag="isEditing"
:allow-drop="allowDrop"
@dragstart-custom="moveFrom(index)"
@drop-custom="moveTo(index)"

View File

@ -31,8 +31,9 @@ export default function ElementsViewProvider(openmct) {
canView: function (selection) {
const hasValidSelection = selection?.length;
const isOverlayPlot = selection?.[0]?.[0]?.context?.item?.type === 'telemetry.plot.overlay';
const isFolder = selection?.[0]?.[0]?.context?.item?.type === 'folder';
return hasValidSelection && !isOverlayPlot;
return hasValidSelection && !isOverlayPlot && !isFolder;
},
view: function (selection) {
let _destroy = null;
@ -62,10 +63,10 @@ export default function ElementsViewProvider(openmct) {
showTab: function (isEditing) {
const hasComposition = Boolean(domainObject && openmct.composition.get(domainObject));
return hasComposition && isEditing;
return hasComposition;
},
priority: function () {
return openmct.priority.DEFAULT;
return openmct.editor.isEditing() ? openmct.priority.DEFAULT : openmct.priority.LOW;
},
destroy: function () {
if (_destroy) {

View File

@ -30,7 +30,9 @@ export default function PropertiesViewProvider(openmct) {
name: 'Properties',
glyph: 'icon-info',
canView: function (selection) {
return selection.length > 0;
const domainObject = selection?.[0]?.[0]?.context?.item;
return domainObject && selection.length > 0;
},
view: function (selection) {
let _destroy = null;
@ -56,7 +58,7 @@ export default function PropertiesViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
return openmct.priority.DEFAULT;
return openmct.editor.isEditing() ? openmct.priority.LOW : openmct.priority.HIGH;
},
destroy: function () {
if (_destroy) {

View File

@ -25,7 +25,24 @@ import mount from 'utils/mount';
import StylesInspectorView from './StylesInspectorView.vue';
import stylesManager from './StylesManager.js';
const NON_STYLABLE_TYPES = ['folder', 'webPage', 'conditionSet', 'summary-widget', 'hyperlink'];
const NON_STYLABLE_TYPES = [
'clock',
'conditionSet',
'eventGenerator',
'eventGeneratorWithAcknowledge',
'example.imagery',
'folder',
'gantt-chart',
'generator',
'hyperlink',
'notebook',
'restricted-notebook',
'summary-widget',
'time-strip',
'timelist',
'timer',
'webPage'
];
function isLayoutObject(selection, objectType) {
//we allow conditionSets to be styled if they're part of a layout
@ -35,8 +52,8 @@ function isLayoutObject(selection, objectType) {
);
}
function isCreatableObject(object, type) {
return NON_STYLABLE_TYPES.indexOf(object.type) < 0 && type.definition.creatable;
function isCreatableObject(object, typeObject) {
return NON_STYLABLE_TYPES.indexOf(object.type) < 0 && typeObject.definition.creatable;
}
export default function StylesInspectorViewProvider(openmct) {
@ -47,12 +64,13 @@ export default function StylesInspectorViewProvider(openmct) {
canView: function (selection) {
const objectSelection = selection?.[0];
const objectContext = objectSelection?.[0]?.context;
const layoutItem = objectContext?.layoutItem;
const domainObject = objectContext?.item;
const hasStyles = domainObject?.configuration?.objectStyles;
const isFlexibleLayoutContainer =
domainObject?.type === 'flexible-layout' && objectContext.type === 'container';
const isLayoutItem = objectContext?.layoutItem;
if (layoutItem) {
if ((isLayoutItem || hasStyles) && !isFlexibleLayoutContainer) {
return true;
}
@ -60,10 +78,11 @@ export default function StylesInspectorViewProvider(openmct) {
return false;
}
const type = openmct.types.get(domainObject.type);
const typeObject = openmct.types.get(domainObject.type);
return (
isLayoutObject(objectSelection, domainObject.type) || isCreatableObject(domainObject, type)
isLayoutObject(objectSelection, domainObject.type) ||
isCreatableObject(domainObject, typeObject)
);
},
view: function (selection) {
@ -91,8 +110,11 @@ export default function StylesInspectorViewProvider(openmct) {
);
_destroy = destroy;
},
showTab: function (isEditing) {
return true;
},
priority: function () {
return openmct.priority.DEFAULT;
return openmct.editor.isEditing() ? openmct.priority.DEFAULT : openmct.priority.LOW;
},
destroy: function () {
if (_destroy) {

View File

@ -38,6 +38,7 @@ export default function MissingObjectInterceptor(openmct) {
}
return object;
}
},
priority: openmct.priority.HIGH
});
}

View File

@ -45,7 +45,7 @@ function myItemsInterceptor({ openmct, identifierObject, name }) {
return object;
},
priority: openmct.priority.HIGH
priority: openmct.priority.HIGHEST
};
}

View File

@ -62,7 +62,7 @@ export default function ActivityInspectorViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
return openmct.priority.HIGH;
},
destroy: function () {
if (_destroy) {

View File

@ -62,7 +62,7 @@ export default function GanttChartInspectorViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
return openmct.editor.isEditing() ? openmct.priority.HIGH : openmct.priority.DEFAULT;
},
destroy: function () {
if (_destroy) {

View File

@ -25,27 +25,6 @@ import mount from 'utils/mount';
import Plot from './PlotView.vue';
export default function PlotViewProvider(openmct) {
function hasNumericTelemetry(domainObject) {
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
return false;
}
let metadata = openmct.telemetry.getMetadata(domainObject);
return metadata.values().length > 0 && hasDomainAndNumericRange(metadata);
}
function hasDomainAndNumericRange(metadata) {
const rangeValues = metadata.valuesForHints(['range']);
const domains = metadata.valuesForHints(['domain']);
return (
domains.length > 0 &&
rangeValues.length > 0 &&
!rangeValues.every((value) => value.format === 'string')
);
}
function isCompactView(objectPath) {
let isChildOfTimeStrip = objectPath.find((object) => object.type === 'time-strip');
@ -57,7 +36,7 @@ export default function PlotViewProvider(openmct) {
name: 'Plot',
cssClass: 'icon-telemetry',
canView(domainObject, objectPath) {
return hasNumericTelemetry(domainObject);
return openmct.telemetry.hasNumericTelemetry(domainObject);
},
view: function (domainObject, objectPath) {

View File

@ -52,7 +52,7 @@ export default function PlotsInspectorViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
return openmct.priority.DEFAULT;
},
destroy: function () {
if (_destroy) {

View File

@ -50,7 +50,7 @@ export default function StackedPlotsInspectorViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
return openmct.editor.isEditing() ? openmct.priority.HIGH : openmct.priority.DEFAULT;
},
destroy: function () {
if (_destroy) {

View File

@ -1,25 +1,10 @@
export default function OverlayPlotCompositionPolicy(openmct) {
function hasNumericTelemetry(domainObject) {
const hasTelemetry = openmct.telemetry.isTelemetryObject(domainObject);
if (!hasTelemetry) {
return false;
}
let metadata = openmct.telemetry.getMetadata(domainObject);
return metadata.values().length > 0 && hasDomainAndRange(metadata);
}
function hasDomainAndRange(metadata) {
return (
metadata.valuesForHints(['range']).length > 0 &&
metadata.valuesForHints(['domain']).length > 0
);
}
return {
allow: function (parent, child) {
if (parent.type === 'telemetry.plot.overlay' && hasNumericTelemetry(child) === false) {
if (
parent.type === 'telemetry.plot.overlay' &&
!openmct.telemetry.hasNumericTelemetry(child)
) {
return false;
}

View File

@ -0,0 +1,41 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function overlayPlotStylesInterceptor(openmct) {
return {
appliesTo: (identifier, domainObject) => {
return (
domainObject?.type === 'telemetry.plot.overlay' &&
!domainObject?.configuration?.objectStyles
);
},
invoke: (identifier, domainObject) => {
if (!domainObject.configuration) {
domainObject.configuration = {};
}
domainObject.configuration.objectStyles = {};
return domainObject;
}
};
}

View File

@ -23,6 +23,7 @@ import PlotViewActions from './actions/ViewActions.js';
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider.js';
import StackedPlotsInspectorViewProvider from './inspector/StackedPlotsInspectorViewProvider.js';
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy.js';
import overlayPlotStylesInterceptor from './overlayPlot/overlayPlotStylesInterceptor.js';
import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider.js';
import PlotViewProvider from './PlotViewProvider.js';
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy.js';
@ -38,16 +39,20 @@ export default function () {
description:
'Combine multiple telemetry elements and view them together as a plot with common X and Y axes. Can be added to Display Layouts.',
creatable: true,
annotatable: true,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
//series is an array of objects of type: {identifier, series: {color...}, yAxis:{}}
series: []
series: [],
objectStyles: {}
};
},
priority: 891
});
openmct.objects.addGetInterceptor(overlayPlotStylesInterceptor(openmct));
openmct.types.addType('telemetry.plot.stacked', {
key: 'telemetry.plot.stacked',
name: 'Stacked Plot',
@ -55,12 +60,14 @@ export default function () {
description:
'Combine multiple telemetry elements and view them together as a plot with a common X axis and individual Y axes. Can be added to Display Layouts.',
creatable: true,
annotatable: true,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
series: [],
yAxis: {},
xAxis: {}
xAxis: {},
objectStyles: {}
};
},
priority: 890

View File

@ -23,14 +23,25 @@
export default function stackedPlotConfigurationInterceptor(openmct) {
openmct.objects.addGetInterceptor({
appliesTo: (identifier, domainObject) => {
return domainObject && domainObject.type === 'telemetry.plot.stacked';
return (
domainObject?.type === 'telemetry.plot.stacked' &&
(!domainObject.configuration?.series || !domainObject.configuration?.objectStyles)
);
},
invoke: (identifier, object) => {
if (object && object.configuration && object.configuration.series === undefined) {
object.configuration.series = [];
invoke: (identifier, domainObject) => {
if (!domainObject.configuration) {
domainObject.configuration = {};
}
return object;
if (!domainObject.configuration.series) {
domainObject.configuration.series = [];
}
if (!domainObject.configuration.objectStyles) {
domainObject.configuration.objectStyles = {};
}
return domainObject;
}
});
}

View File

@ -66,10 +66,10 @@ export default function TableConfigurationViewProvider(openmct, options) {
_destroy = destroy;
},
showTab: function (isEditing) {
return isEditing;
return true;
},
priority: function () {
return 1;
return openmct.editor.isEditing() ? openmct.priority.HIGH : openmct.priority.DEFAULT;
},
destroy: function () {
if (_destroy) {

View File

@ -71,7 +71,8 @@ export default function getTelemetryTableType(options) {
hiddenColumns: {},
telemetryMode,
persistModeChange,
rowLimit
rowLimit,
objectStyles: {}
};
}
};

View File

@ -21,53 +21,57 @@
-->
<template>
<div class="c-inspect-properties">
<template v-if="isEditing">
<div class="c-inspect-properties__header">Layout</div>
<ul class="c-inspect-properties__section">
<li class="c-inspect-properties__row">
<div class="c-inspect-properties__label" title="Auto-size table">
<label for="AutoSizeControl">Auto-size</label>
</div>
<div class="c-inspect-properties__value">
<input
id="AutoSizeControl"
type="checkbox"
:checked="configuration.autosize !== false"
@change="toggleAutosize()"
/>
</div>
</li>
<li class="c-inspect-properties__row">
<div class="c-inspect-properties__label" title="Show or hide headers">
<label for="header-visibility">Hide Header</label>
</div>
<div class="c-inspect-properties__value">
<input
id="header-visibility"
type="checkbox"
:checked="configuration.hideHeaders === true"
@change="toggleHeaderVisibility"
/>
</div>
</li>
</ul>
<div class="c-inspect-properties__header">Columns</div>
<ul class="c-inspect-properties__section">
<li v-for="(title, key) in headers" :key="key" class="c-inspect-properties__row">
<div class="c-inspect-properties__label" title="Show or hide column">
<label :for="key + 'ColumnControl'">{{ title }}</label>
</div>
<div class="c-inspect-properties__value">
<input
:id="key + 'ColumnControl'"
type="checkbox"
:checked="configuration.hiddenColumns[key] !== true"
@change="toggleColumn(key)"
/>
</div>
</li>
</ul>
</template>
<div class="c-inspect-properties__header">Layout</div>
<ul class="c-inspect-properties__section">
<li class="c-inspect-properties__row">
<div class="c-inspect-properties__label" title="Auto-size table">
<label for="AutoSizeControl">Auto-size</label>
</div>
<div class="c-inspect-properties__value">
<input
v-if="isEditing"
id="AutoSizeControl"
type="checkbox"
:checked="configuration.autosize !== false"
@change="toggleAutosize()"
/>
<span v-if="!isEditing && configuration.autosize !== false">Enabled</span>
</div>
</li>
<li class="c-inspect-properties__row">
<div class="c-inspect-properties__label" title="Show or hide headers">
<label for="header-visibility">Hide Header</label>
</div>
<div class="c-inspect-properties__value">
<input
v-if="isEditing"
id="header-visibility"
type="checkbox"
:checked="configuration.hideHeaders === true"
@change="toggleHeaderVisibility"
/>
<span v-if="!isEditing && configuration.hideHeaders === true">True</span>
</div>
</li>
</ul>
<div class="c-inspect-properties__header">Columns</div>
<ul class="c-inspect-properties__section">
<li v-for="(title, key) in headers" :key="key" class="c-inspect-properties__row">
<div class="c-inspect-properties__label" title="Show or hide column">
<label :for="key + 'ColumnControl'">{{ title }}</label>
</div>
<div class="c-inspect-properties__value">
<input
v-if="isEditing"
:id="key + 'ColumnControl'"
type="checkbox"
:checked="configuration.hiddenColumns[key] !== true"
@change="toggleColumn(key)"
/>
<span v-if="!isEditing && configuration.hiddenColumns[key] !== true">Visible</span>
</div>
</li>
</ul>
</div>
</template>

View File

@ -22,6 +22,7 @@
import { MODE } from './constants.js';
import TableConfigurationViewProvider from './TableConfigurationViewProvider.js';
import telemetryTableStylesInterceptor from './telemetryTableStylesInterceptor.js';
import getTelemetryTableType from './TelemetryTableType.js';
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
import TelemetryTableViewActions from './ViewActions.js';
@ -33,6 +34,7 @@ export default function plugin(
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct, options));
openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct, options));
openmct.types.addType('table', getTelemetryTableType(options));
openmct.objects.addGetInterceptor(telemetryTableStylesInterceptor(openmct));
openmct.composition.addPolicy((parent, child) => {
if (parent.type === 'table') {
return Object.prototype.hasOwnProperty.call(child, 'telemetry');

View File

@ -0,0 +1,38 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function telemetryTableStylesInterceptor(openmct) {
return {
appliesTo: (identifier, domainObject) => {
return (
domainObject?.type === 'table' &&
domainObject.configuration && // only applies to tables with existing configuration
!domainObject.configuration.objectStyles
);
},
invoke: (identifier, domainObject) => {
domainObject.configuration.objectStyles = {};
return domainObject;
}
};
}

View File

@ -23,22 +23,6 @@
const ALLOWED_TYPES = ['telemetry.plot.overlay', 'telemetry.plot.stacked', 'plan', 'gantt-chart'];
const DISALLOWED_TYPES = ['telemetry.plot.bar-graph', 'telemetry.plot.scatter-plot'];
export default function TimelineCompositionPolicy(openmct) {
function hasNumericTelemetry(domainObject, metadata) {
const hasTelemetry = openmct.telemetry.isTelemetryObject(domainObject);
if (!hasTelemetry || !metadata) {
return false;
}
return metadata.values().length > 0 && hasDomainAndRange(metadata);
}
function hasDomainAndRange(metadata) {
return (
metadata.valuesForHints(['range']).length > 0 &&
metadata.valuesForHints(['domain']).length > 0
);
}
function hasImageTelemetry(domainObject, metadata) {
if (!metadata) {
return false;
@ -54,7 +38,7 @@ export default function TimelineCompositionPolicy(openmct) {
if (
!DISALLOWED_TYPES.includes(child.type) &&
(hasNumericTelemetry(child, metadata) ||
(openmct.telemetry.hasNumericTelemetry(child) ||
hasImageTelemetry(child, metadata) ||
ALLOWED_TYPES.includes(child.type))
) {

View File

@ -28,7 +28,7 @@ import TimelistPropertiesView from './TimelistPropertiesView.vue';
export default function TimeListInspectorViewProvider(openmct) {
return {
key: 'timelist-inspector',
name: 'View Properties',
name: 'Config',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 0) {
return false;
@ -63,7 +63,7 @@ export default function TimeListInspectorViewProvider(openmct) {
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
return openmct.editor.isEditing() ? openmct.priority.HIGH : openmct.priority.DEFAULT;
},
destroy: function () {
if (_destroy) {

View File

@ -71,14 +71,18 @@ export default {
},
mounted() {
this.updateSelection();
this.openmct.editor.on('isEditing', this.updateSelection);
this.openmct.selection.on('change', this.updateSelection);
},
unmounted() {
this.openmct.editor.off('isEditing', this.updateSelection);
this.openmct.selection.off('change', this.updateSelection);
},
methods: {
updateSelection() {
const previousSelectedTab = this.selectedTab?.key;
const inspectorViews = this.openmct.inspectorViews.get(this.openmct.selection.get());
const isEditing = this.openmct.editor.isEditing();
this.tabs = inspectorViews.map((view) => {
return {
@ -88,6 +92,14 @@ export default {
showTab: view.showTab
};
});
const stylesTabIndex = this.tabs.findIndex((tab) => tab.key === 'stylesInspectorView');
if (isEditing && previousSelectedTab === 'stylesInspectorView' && stylesTabIndex !== -1) {
this.selectTab(this.tabs[stylesTabIndex]);
} else {
this.selectTab(this.visibleTabs[0]);
}
},
isSelected(tab) {
return this.selectedTab?.key === tab.key;

View File

@ -40,13 +40,15 @@ export default {
},
mounted() {
this.updateSelectionViews();
this.openmct.editor.on('isEditing', this.updateSelectionViews);
this.openmct.selection.on('change', this.updateSelectionViews);
},
unmounted() {
this.openmct.editor.off('isEditing', this.updateSelectionViews);
this.openmct.selection.off('change', this.updateSelectionViews);
},
methods: {
updateSelectionViews(selection) {
updateSelectionViews() {
this.clearViews();
this.selectedViews = this.openmct.inspectorViews.get(this.openmct.selection.get());
this.showViewsForTab();