mirror of
https://github.com/nasa/openmct.git
synced 2024-12-24 07:16:39 +00:00
Merge branch 'release/3.0.1' into fix/VIPEROMCT-388
This commit is contained in:
commit
a8e32e2214
@ -13,7 +13,7 @@ module.exports = {
|
|||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:compat/recommended',
|
'plugin:compat/recommended',
|
||||||
'plugin:vue/recommended',
|
'plugin:vue/vue3-recommended',
|
||||||
'plugin:you-dont-need-lodash-underscore/compatible',
|
'plugin:you-dont-need-lodash-underscore/compatible',
|
||||||
'plugin:prettier/recommended'
|
'plugin:prettier/recommended'
|
||||||
],
|
],
|
||||||
@ -28,6 +28,8 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
'vue/no-deprecated-dollar-listeners-api': 'warn',
|
||||||
|
'vue/no-deprecated-events-api': 'warn',
|
||||||
'vue/no-v-for-template-key': 'off',
|
'vue/no-v-for-template-key': 'off',
|
||||||
'vue/no-v-for-template-key-on-child': 'error',
|
'vue/no-v-for-template-key-on-child': 'error',
|
||||||
'prettier/prettier': 'error',
|
'prettier/prettier': 'error',
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
* @property {string} type the type of domain object to create (e.g.: "Sine Wave Generator").
|
* @property {string} type the type of domain object to create (e.g.: "Sine Wave Generator").
|
||||||
* @property {string} [name] the desired name of the created domain object.
|
* @property {string} [name] the desired name of the created domain object.
|
||||||
* @property {string | import('../src/api/objects/ObjectAPI').Identifier} [parent] the Identifier or uuid of the parent object.
|
* @property {string | import('../src/api/objects/ObjectAPI').Identifier} [parent] the Identifier or uuid of the parent object.
|
||||||
|
* @property {Object<string, string>} [customParameters] any additional parameters to be passed to the domain object's form. E.g. '[aria-label="Data Rate (hz)"]': {'0.1'}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,7 +66,10 @@ const { expect } = require('@playwright/test');
|
|||||||
* @param {CreateObjectOptions} options
|
* @param {CreateObjectOptions} options
|
||||||
* @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object.
|
* @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', customParameters = {} }
|
||||||
|
) {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
name = `${type}:${genUuid()}`;
|
name = `${type}:${genUuid()}`;
|
||||||
}
|
}
|
||||||
@ -94,6 +98,13 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
|
|||||||
await notesInput.fill(page.testNotes);
|
await notesInput.fill(page.testNotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there are any further parameters, fill them in
|
||||||
|
for (const [key, value] of Object.entries(customParameters)) {
|
||||||
|
const input = page.locator(`form[name="mctForm"] ${key}`);
|
||||||
|
await input.fill('');
|
||||||
|
await input.fill(value);
|
||||||
|
}
|
||||||
|
|
||||||
// Click OK button and wait for Navigate event
|
// Click OK button and wait for Navigate event
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForLoadState(),
|
page.waitForLoadState(),
|
||||||
@ -177,7 +188,7 @@ async function createPlanFromJSON(page, { name, json, parent = 'mine' }) {
|
|||||||
await page.click(`li:text("Plan")`);
|
await page.click(`li:text("Plan")`);
|
||||||
|
|
||||||
// Modify the name input field of the domain object to accept 'name'
|
// Modify the name input field of the domain object to accept 'name'
|
||||||
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
|
const nameInput = page.getByLabel('Title', { exact: true });
|
||||||
await nameInput.fill('');
|
await nameInput.fill('');
|
||||||
await nameInput.fill(name);
|
await nameInput.fill(name);
|
||||||
|
|
||||||
@ -410,8 +421,18 @@ async function setEndOffset(page, offset) {
|
|||||||
await setTimeConductorOffset(page, offset);
|
await setTimeConductorOffset(page, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time conductor bounds in fixed time mode
|
||||||
|
*
|
||||||
|
* NOTE: Unless explicitly testing the Time Conductor itself, it is advised to instead
|
||||||
|
* navigate directly to the object with the desired time bounds using `navigateToObjectWithFixedTimeBounds()`.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {string} startDate
|
||||||
|
* @param {string} endDate
|
||||||
|
*/
|
||||||
async function setTimeConductorBounds(page, startDate, endDate) {
|
async function setTimeConductorBounds(page, startDate, endDate) {
|
||||||
// Bring up the time conductor popup
|
// Bring up the time conductor popup
|
||||||
|
expect(await page.locator('.l-shell__time-conductor.c-compact-tc').count()).toBe(1);
|
||||||
await page.click('.l-shell__time-conductor.c-compact-tc');
|
await page.click('.l-shell__time-conductor.c-compact-tc');
|
||||||
|
|
||||||
await setTimeBounds(page, startDate, endDate);
|
await setTimeBounds(page, startDate, endDate);
|
||||||
@ -419,20 +440,31 @@ async function setTimeConductorBounds(page, startDate, endDate) {
|
|||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the independent time conductor bounds in fixed time mode
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {string} startDate
|
||||||
|
* @param {string} endDate
|
||||||
|
*/
|
||||||
async function setIndependentTimeConductorBounds(page, startDate, endDate) {
|
async function setIndependentTimeConductorBounds(page, startDate, endDate) {
|
||||||
// Activate Independent Time Conductor in Fixed Time Mode
|
// Activate Independent Time Conductor in Fixed Time Mode
|
||||||
await page.getByRole('switch').click();
|
await page.getByRole('switch').click();
|
||||||
|
|
||||||
// Bring up the time conductor popup
|
// Bring up the time conductor popup
|
||||||
await page.click('.c-conductor-holder--compact .c-compact-tc');
|
await page.click('.c-conductor-holder--compact .c-compact-tc');
|
||||||
|
await expect(page.locator('.itc-popout')).toBeInViewport();
|
||||||
await expect(page.locator('.itc-popout')).toBeVisible();
|
|
||||||
|
|
||||||
await setTimeBounds(page, startDate, endDate);
|
await setTimeBounds(page, startDate, endDate);
|
||||||
|
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the bounds of the visible conductor in fixed time mode
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {string} startDate
|
||||||
|
* @param {string} endDate
|
||||||
|
*/
|
||||||
async function setTimeBounds(page, startDate, endDate) {
|
async function setTimeBounds(page, startDate, endDate) {
|
||||||
if (startDate) {
|
if (startDate) {
|
||||||
// Fill start time
|
// Fill start time
|
||||||
@ -549,6 +581,21 @@ async function getCanvasPixels(page, canvasSelector) {
|
|||||||
return getTelemValuePromise;
|
return getTelemValuePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {string} myItemsFolderName
|
||||||
|
* @param {string} url
|
||||||
|
* @param {string} newName
|
||||||
|
*/
|
||||||
|
async function renameObjectFromContextMenu(page, url, newName) {
|
||||||
|
await openObjectTreeContextMenu(page, url);
|
||||||
|
await page.click('li:text("Edit Properties")');
|
||||||
|
const nameInput = page.getByLabel('Title', { exact: true });
|
||||||
|
await nameInput.fill('');
|
||||||
|
await nameInput.fill(newName);
|
||||||
|
await page.click('[aria-label="Save"]');
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
@ -567,5 +614,6 @@ module.exports = {
|
|||||||
setTimeConductorBounds,
|
setTimeConductorBounds,
|
||||||
setIndependentTimeConductorBounds,
|
setIndependentTimeConductorBounds,
|
||||||
selectInspectorTab,
|
selectInspectorTab,
|
||||||
waitForPlotsToRender
|
waitForPlotsToRender,
|
||||||
|
renameObjectFromContextMenu
|
||||||
};
|
};
|
||||||
|
@ -260,6 +260,7 @@ test.describe('Display Layout', () => {
|
|||||||
test('When multiple plots are contained in a layout, we only ask for annotations once @couchdb', async ({
|
test('When multiple plots are contained in a layout, we only ask for annotations once @couchdb', async ({
|
||||||
page
|
page
|
||||||
}) => {
|
}) => {
|
||||||
|
await setFixedTimeMode(page);
|
||||||
// Create another Sine Wave Generator
|
// Create another Sine Wave Generator
|
||||||
const anotherSineWaveObject = await createDomainObjectWithDefaults(page, {
|
const anotherSineWaveObject = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Sine Wave Generator'
|
type: 'Sine Wave Generator'
|
||||||
@ -316,10 +317,20 @@ test.describe('Display Layout', () => {
|
|||||||
|
|
||||||
// wait for annotations requests to be batched and requested
|
// wait for annotations requests to be batched and requested
|
||||||
await page.waitForLoadState('networkidle');
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
// Network requests for the composite telemetry with multiple items should be:
|
// Network requests for the composite telemetry with multiple items should be:
|
||||||
// 1. a single batched request for annotations
|
// 1. a single batched request for annotations
|
||||||
expect(networkRequests.length).toBe(1);
|
expect(networkRequests.length).toBe(1);
|
||||||
|
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
networkRequests = [];
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
// wait for annotations to not load (if we have any, we've got a problem)
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
// In real time mode, we don't fetch annotations at all
|
||||||
|
expect(networkRequests.length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -29,6 +29,10 @@ const {
|
|||||||
test.describe('Flexible Layout', () => {
|
test.describe('Flexible Layout', () => {
|
||||||
let sineWaveObject;
|
let sineWaveObject;
|
||||||
let clockObject;
|
let clockObject;
|
||||||
|
let treePane;
|
||||||
|
let sineWaveGeneratorTreeItem;
|
||||||
|
let clockTreeItem;
|
||||||
|
let flexibleLayout;
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
@ -41,23 +45,27 @@ test.describe('Flexible Layout', () => {
|
|||||||
clockObject = await createDomainObjectWithDefaults(page, {
|
clockObject = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Clock'
|
type: 'Clock'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create a Flexible Layout
|
||||||
|
flexibleLayout = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Flexible Layout'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define the Sine Wave Generator and Clock tree items
|
||||||
|
treePane = page.getByRole('tree', {
|
||||||
|
name: 'Main Tree'
|
||||||
|
});
|
||||||
|
sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
||||||
|
name: new RegExp(sineWaveObject.name)
|
||||||
|
});
|
||||||
|
clockTreeItem = treePane.getByRole('treeitem', {
|
||||||
|
name: new RegExp(clockObject.name)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
test('panes have the appropriate draggable attribute while in Edit and Browse modes', async ({
|
test('panes have the appropriate draggable attribute while in Edit and Browse modes', async ({
|
||||||
page
|
page
|
||||||
}) => {
|
}) => {
|
||||||
const treePane = page.getByRole('tree', {
|
await page.goto(flexibleLayout.url);
|
||||||
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'
|
|
||||||
});
|
|
||||||
// Edit Flexible Layout
|
// Edit Flexible Layout
|
||||||
await page.locator('[title="Edit"]').click();
|
await page.locator('[title="Edit"]').click();
|
||||||
|
|
||||||
@ -78,19 +86,79 @@ test.describe('Flexible Layout', () => {
|
|||||||
dragWrapper = page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
|
dragWrapper = page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
|
||||||
await expect(dragWrapper).toHaveAttribute('draggable', 'false');
|
await expect(dragWrapper).toHaveAttribute('draggable', 'false');
|
||||||
});
|
});
|
||||||
|
test('changing toolbar settings in edit mode is immediately reflected and persists upon save', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/6942'
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(flexibleLayout.url);
|
||||||
|
|
||||||
|
// 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 sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
||||||
|
await clockTreeItem.dragTo(page.locator('.c-fl__container.is-empty'));
|
||||||
|
|
||||||
|
// Click on the first frame to select it
|
||||||
|
await page.locator('.c-fl-container__frame').first().click();
|
||||||
|
await expect(page.locator('.c-fl-container__frame > .c-frame').first()).toHaveAttribute(
|
||||||
|
's-selected',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert the toolbar is visible
|
||||||
|
await expect(page.locator('.c-toolbar')).toBeInViewport();
|
||||||
|
|
||||||
|
// Assert the layout is in columns orientation
|
||||||
|
expect(await page.locator('.c-fl--rows').count()).toEqual(0);
|
||||||
|
|
||||||
|
// Change the layout to rows orientation
|
||||||
|
await page.getByTitle('Columns layout').click();
|
||||||
|
|
||||||
|
// Assert the layout is in rows orientation
|
||||||
|
expect(await page.locator('.c-fl--rows').count()).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Assert the frame of the first item is visible
|
||||||
|
await expect(page.locator('.c-so-view').first()).not.toHaveClass(/c-so-view--no-frame/);
|
||||||
|
|
||||||
|
// Hide the frame of the first item
|
||||||
|
await page.getByTitle('Frame visible').click();
|
||||||
|
|
||||||
|
// Assert the frame is hidden
|
||||||
|
await expect(page.locator('.c-so-view').first()).toHaveClass(/c-so-view--no-frame/);
|
||||||
|
|
||||||
|
// Assert there are 2 containers
|
||||||
|
expect(await page.locator('.c-fl-container').count()).toEqual(2);
|
||||||
|
|
||||||
|
// Add a container
|
||||||
|
await page.getByTitle('Add Container').click();
|
||||||
|
|
||||||
|
// Assert there are 3 containers
|
||||||
|
expect(await page.locator('.c-fl-container').count()).toEqual(3);
|
||||||
|
|
||||||
|
// Save Flexible Layout
|
||||||
|
await page.locator('button[title="Save"]').click();
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
|
// Nav away and back
|
||||||
|
await page.goto(sineWaveObject.url);
|
||||||
|
await page.goto(flexibleLayout.url);
|
||||||
|
|
||||||
|
// Wait for the first frame to be visible so we know the layout has loaded
|
||||||
|
await expect(page.locator('.c-fl-container').nth(0)).toBeInViewport();
|
||||||
|
|
||||||
|
// Assert the settings have persisted
|
||||||
|
expect(await page.locator('.c-fl-container').count()).toEqual(3);
|
||||||
|
expect(await page.locator('.c-fl--rows').count()).toBeGreaterThan(0);
|
||||||
|
await expect(page.locator('.c-so-view').first()).toHaveClass(/c-so-view--no-frame/);
|
||||||
|
});
|
||||||
test('items in a flexible layout can be removed with object tree context menu when viewing the flexible layout', async ({
|
test('items in a flexible layout can be removed with object tree context menu when viewing the flexible layout', async ({
|
||||||
page
|
page
|
||||||
}) => {
|
}) => {
|
||||||
const treePane = page.getByRole('tree', {
|
await page.goto(flexibleLayout.url);
|
||||||
name: 'Main Tree'
|
|
||||||
});
|
|
||||||
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
|
|
||||||
name: new RegExp(sineWaveObject.name)
|
|
||||||
});
|
|
||||||
// Create a Display Layout
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
|
||||||
type: 'Flexible Layout'
|
|
||||||
});
|
|
||||||
// Edit Flexible Layout
|
// Edit Flexible Layout
|
||||||
await page.locator('[title="Edit"]').click();
|
await page.locator('[title="Edit"]').click();
|
||||||
|
|
||||||
@ -121,17 +189,7 @@ test.describe('Flexible Layout', () => {
|
|||||||
type: 'issue',
|
type: 'issue',
|
||||||
description: 'https://github.com/nasa/openmct/issues/3117'
|
description: 'https://github.com/nasa/openmct/issues/3117'
|
||||||
});
|
});
|
||||||
const treePane = page.getByRole('tree', {
|
await page.goto(flexibleLayout.url);
|
||||||
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'
|
|
||||||
});
|
|
||||||
// Edit Flexible Layout
|
// Edit Flexible Layout
|
||||||
await page.locator('[title="Edit"]').click();
|
await page.locator('[title="Edit"]').click();
|
||||||
|
|
||||||
@ -167,19 +225,13 @@ test.describe('Flexible Layout', () => {
|
|||||||
const exampleImageryObject = await createDomainObjectWithDefaults(page, {
|
const exampleImageryObject = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Example Imagery'
|
type: 'Example Imagery'
|
||||||
});
|
});
|
||||||
// Create a Flexible Layout
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
await page.goto(flexibleLayout.url);
|
||||||
type: 'Flexible Layout'
|
// Edit Flexible Layout
|
||||||
});
|
|
||||||
// Edit Display Layout
|
|
||||||
await page.locator('[title="Edit"]').click();
|
await page.locator('[title="Edit"]').click();
|
||||||
|
|
||||||
// Expand the 'My Items' folder in the left tree
|
// Expand the 'My Items' folder in the left tree
|
||||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||||
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
|
||||||
const treePane = page.getByRole('tree', {
|
|
||||||
name: 'Main Tree'
|
|
||||||
});
|
|
||||||
const exampleImageryTreeItem = treePane.getByRole('treeitem', {
|
const exampleImageryTreeItem = treePane.getByRole('treeitem', {
|
||||||
name: new RegExp(exampleImageryObject.name)
|
name: new RegExp(exampleImageryObject.name)
|
||||||
});
|
});
|
||||||
|
@ -79,25 +79,25 @@ test.describe('Example Imagery Object', () => {
|
|||||||
// Test independent fixed time with global fixed time
|
// Test independent fixed time with global fixed time
|
||||||
// flip on independent time conductor
|
// flip on independent time conductor
|
||||||
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
|
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
|
||||||
|
|
||||||
|
// Adding in delay to address flakiness of ITC test-- button event handlers not registering in time
|
||||||
|
await expect(page.locator('#independentTCToggle')).toBeChecked();
|
||||||
|
await expect(page.locator('.c-compact-tc').first()).toBeVisible();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Independent Time Conductor Settings' }).click();
|
await page.getByRole('button', { name: 'Independent Time Conductor Settings' }).click();
|
||||||
await page.getByRole('textbox', { name: 'Start date' }).fill('');
|
|
||||||
await page.getByRole('textbox', { name: 'Start date' }).fill('2021-12-30');
|
await page.getByRole('textbox', { name: 'Start date' }).fill('2021-12-30');
|
||||||
await page.keyboard.press('Tab');
|
await page.keyboard.press('Tab');
|
||||||
await page.getByRole('textbox', { name: 'Start time' }).fill('');
|
await page.getByRole('textbox', { name: 'Start time' }).fill('01:01:00');
|
||||||
await page.getByRole('textbox', { name: 'Start time' }).type('01:01:00');
|
|
||||||
await page.keyboard.press('Tab');
|
await page.keyboard.press('Tab');
|
||||||
await page.getByRole('textbox', { name: 'End date' }).fill('');
|
await page.getByRole('textbox', { name: 'End date' }).fill('2021-12-30');
|
||||||
await page.getByRole('textbox', { name: 'End date' }).type('2021-12-30');
|
|
||||||
await page.keyboard.press('Tab');
|
await page.keyboard.press('Tab');
|
||||||
await page.getByRole('textbox', { name: 'End time' }).fill('');
|
await page.getByRole('textbox', { name: 'End time' }).fill('01:11:00');
|
||||||
await page.getByRole('textbox', { name: 'End time' }).type('01:11:00');
|
|
||||||
await page.keyboard.press('Tab');
|
await page.keyboard.press('Tab');
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
// expect(await page.getByRole('button', { name: 'Submit time bounds' }).isEnabled()).toBe(true);
|
|
||||||
// await page.getByRole('button', { name: 'Submit time bounds' }).click();
|
|
||||||
|
|
||||||
// check image date
|
// check image date
|
||||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
await expect(page.getByText('2021-12-30 01:01:00.000Z').first()).toBeVisible();
|
||||||
|
|
||||||
// flip it off
|
// flip it off
|
||||||
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
|
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
|
||||||
@ -106,9 +106,12 @@ test.describe('Example Imagery Object', () => {
|
|||||||
|
|
||||||
// Test independent fixed time with global realtime
|
// Test independent fixed time with global realtime
|
||||||
await setRealTimeMode(page);
|
await setRealTimeMode(page);
|
||||||
|
await expect(
|
||||||
|
page.getByRole('switch', { name: 'Enable Independent Time Conductor' })
|
||||||
|
).toBeEnabled();
|
||||||
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
|
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
|
||||||
// check image date to be in the past
|
// check image date to be in the past
|
||||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
await expect(page.getByText('2021-12-30 01:01:00.000Z').first()).toBeVisible();
|
||||||
// flip it off
|
// flip it off
|
||||||
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
|
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
|
||||||
// timestamp shouldn't be in the past anymore
|
// timestamp shouldn't be in the past anymore
|
||||||
|
@ -29,10 +29,11 @@ const {
|
|||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
setRealTimeMode,
|
setRealTimeMode,
|
||||||
setFixedTimeMode,
|
setFixedTimeMode,
|
||||||
waitForPlotsToRender
|
waitForPlotsToRender,
|
||||||
|
selectInspectorTab
|
||||||
} = require('../../../../appActions');
|
} = require('../../../../appActions');
|
||||||
|
|
||||||
test.describe.fixme('Plot Tagging', () => {
|
test.describe('Plot Tagging', () => {
|
||||||
/**
|
/**
|
||||||
* Given a canvas and a set of points, tags the points on the canvas.
|
* Given a canvas and a set of points, tags the points on the canvas.
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
@ -41,7 +42,7 @@ test.describe.fixme('Plot Tagging', () => {
|
|||||||
* @param {Number} yEnd a telemetry item with a plot
|
* @param {Number} yEnd a telemetry item with a plot
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async function createTags({ page, canvas, xEnd, yEnd }) {
|
async function createTags({ page, canvas, xEnd = 700, yEnd = 480 }) {
|
||||||
await canvas.hover({ trial: true });
|
await canvas.hover({ trial: true });
|
||||||
|
|
||||||
//Alt+Shift Drag Start to select some points to tag
|
//Alt+Shift Drag Start to select some points to tag
|
||||||
@ -90,15 +91,17 @@ test.describe.fixme('Plot Tagging', () => {
|
|||||||
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||||
|
|
||||||
const canvas = page.locator('canvas').nth(1);
|
const canvas = page.locator('canvas').nth(1);
|
||||||
|
//Wait for canvas to stabilize.
|
||||||
|
await waitForPlotsToRender(page);
|
||||||
|
|
||||||
//Wait for canvas to stablize.
|
await expect(canvas).toBeInViewport();
|
||||||
await canvas.hover({ trial: true });
|
await canvas.hover({ trial: true });
|
||||||
|
|
||||||
// click on the tagged plot point
|
// click on the tagged plot point
|
||||||
await canvas.click({
|
await canvas.click({
|
||||||
position: {
|
position: {
|
||||||
x: 325,
|
x: 100,
|
||||||
y: 377
|
y: 100
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -146,7 +149,10 @@ test.describe.fixme('Plot Tagging', () => {
|
|||||||
// wait for plots to load
|
// wait for plots to load
|
||||||
await waitForPlotsToRender(page);
|
await waitForPlotsToRender(page);
|
||||||
|
|
||||||
await page.getByText('Annotations').click();
|
await expect(page.getByRole('tab', { name: 'Annotations' })).not.toHaveClass(/is-current/);
|
||||||
|
await selectInspectorTab(page, 'Annotations');
|
||||||
|
await expect(page.getByRole('tab', { name: 'Annotations' })).toHaveClass(/is-current/);
|
||||||
|
|
||||||
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||||
|
|
||||||
const canvas = page.locator('canvas').nth(1);
|
const canvas = page.locator('canvas').nth(1);
|
||||||
@ -171,8 +177,6 @@ test.describe.fixme('Plot Tagging', () => {
|
|||||||
type: 'issue',
|
type: 'issue',
|
||||||
description: 'https://github.com/nasa/openmct/issues/6822'
|
description: 'https://github.com/nasa/openmct/issues/6822'
|
||||||
});
|
});
|
||||||
//Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
|
|
||||||
test.slow();
|
|
||||||
|
|
||||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Overlay Plot'
|
type: 'Overlay Plot'
|
||||||
@ -181,13 +185,19 @@ test.describe.fixme('Plot Tagging', () => {
|
|||||||
const alphaSineWave = await createDomainObjectWithDefaults(page, {
|
const alphaSineWave = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Sine Wave Generator',
|
type: 'Sine Wave Generator',
|
||||||
name: 'Alpha Sine Wave',
|
name: 'Alpha Sine Wave',
|
||||||
parent: overlayPlot.uuid
|
parent: overlayPlot.uuid,
|
||||||
|
customParameters: {
|
||||||
|
'[aria-label="Data Rate (hz)"]': '0.01'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Sine Wave Generator',
|
type: 'Sine Wave Generator',
|
||||||
name: 'Beta Sine Wave',
|
name: 'Beta Sine Wave',
|
||||||
parent: overlayPlot.uuid
|
parent: overlayPlot.uuid,
|
||||||
|
customParameters: {
|
||||||
|
'[aria-label="Data Rate (hz)"]': '0.02'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.goto(overlayPlot.url);
|
await page.goto(overlayPlot.url);
|
||||||
@ -200,9 +210,7 @@ test.describe.fixme('Plot Tagging', () => {
|
|||||||
|
|
||||||
await createTags({
|
await createTags({
|
||||||
page,
|
page,
|
||||||
canvas,
|
canvas
|
||||||
xEnd: 700,
|
|
||||||
yEnd: 480
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await setFixedTimeMode(page);
|
await setFixedTimeMode(page);
|
||||||
@ -232,15 +240,15 @@ test.describe.fixme('Plot Tagging', () => {
|
|||||||
|
|
||||||
test('Tags work with Plot View of telemetry items', async ({ page }) => {
|
test('Tags work with Plot View of telemetry items', async ({ page }) => {
|
||||||
await createDomainObjectWithDefaults(page, {
|
await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Sine Wave Generator'
|
type: 'Sine Wave Generator',
|
||||||
|
customParameters: {
|
||||||
|
'[aria-label="Data Rate (hz)"]': '0.01'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const canvas = page.locator('canvas').nth(1);
|
const canvas = page.locator('canvas').nth(1);
|
||||||
await createTags({
|
await createTags({
|
||||||
page,
|
page,
|
||||||
canvas,
|
canvas
|
||||||
xEnd: 700,
|
|
||||||
yEnd: 480
|
|
||||||
});
|
});
|
||||||
await basicTagsTests(page);
|
await basicTagsTests(page);
|
||||||
});
|
});
|
||||||
@ -253,13 +261,19 @@ test.describe.fixme('Plot Tagging', () => {
|
|||||||
const alphaSineWave = await createDomainObjectWithDefaults(page, {
|
const alphaSineWave = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Sine Wave Generator',
|
type: 'Sine Wave Generator',
|
||||||
name: 'Alpha Sine Wave',
|
name: 'Alpha Sine Wave',
|
||||||
parent: stackedPlot.uuid
|
parent: stackedPlot.uuid,
|
||||||
|
customParameters: {
|
||||||
|
'[aria-label="Data Rate (hz)"]': '0.01'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Sine Wave Generator',
|
type: 'Sine Wave Generator',
|
||||||
name: 'Beta Sine Wave',
|
name: 'Beta Sine Wave',
|
||||||
parent: stackedPlot.uuid
|
parent: stackedPlot.uuid,
|
||||||
|
customParameters: {
|
||||||
|
'[aria-label="Data Rate (hz)"]': '0.02'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.goto(stackedPlot.url);
|
await page.goto(stackedPlot.url);
|
||||||
|
@ -59,59 +59,57 @@ test.describe('Recent Objects', () => {
|
|||||||
await page.mouse.move(0, 100);
|
await page.mouse.move(0, 100);
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
});
|
});
|
||||||
test.fixme(
|
test('Navigated objects show up in recents, object renames and deletions are reflected', async ({
|
||||||
'Navigated objects show up in recents, object renames and deletions are reflected',
|
page
|
||||||
async ({ page }) => {
|
}) => {
|
||||||
test.info().annotations.push({
|
test.info().annotations.push({
|
||||||
type: 'issue',
|
type: 'issue',
|
||||||
description: 'https://github.com/nasa/openmct/issues/6818'
|
description: 'https://github.com/nasa/openmct/issues/6818'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify that both created objects appear in the list and are in the correct order
|
// Verify that both created objects appear in the list and are in the correct order
|
||||||
await assertInitialRecentObjectsListState();
|
await assertInitialRecentObjectsListState();
|
||||||
|
|
||||||
// Navigate to the folder by clicking on the main object name in the recent objects list item
|
// Navigate to the folder by clicking on the main object name in the recent objects list item
|
||||||
await page.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}?*`);
|
await page.waitForURL(`**/${folderA.uuid}?*`);
|
||||||
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
|
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
|
||||||
|
|
||||||
// Rename
|
// Rename
|
||||||
folderA.name = `${folderA.name}-NEW!`;
|
folderA.name = `${folderA.name}-NEW!`;
|
||||||
await page.locator('.l-browse-bar__object-name').fill('');
|
await page.locator('.l-browse-bar__object-name').fill('');
|
||||||
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
|
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
// Verify rename has been applied in recent objects list item and objects paths
|
// Verify rename has been applied in recent objects list item and objects paths
|
||||||
expect(
|
expect(
|
||||||
await page
|
|
||||||
.getByRole('navigation', {
|
|
||||||
name: clock.name
|
|
||||||
})
|
|
||||||
.locator('a')
|
|
||||||
.filter({
|
|
||||||
hasText: folderA.name
|
|
||||||
})
|
|
||||||
.count()
|
|
||||||
).toBeGreaterThan(0);
|
|
||||||
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
|
|
||||||
|
|
||||||
// Delete
|
|
||||||
await page.click('button[title="Show selected item in tree"]');
|
|
||||||
// Delete the folder via the left tree pane treeitem context menu
|
|
||||||
await page
|
await page
|
||||||
.getByRole('treeitem', { name: new RegExp(folderA.name) })
|
.getByRole('navigation', {
|
||||||
|
name: clock.name
|
||||||
|
})
|
||||||
.locator('a')
|
.locator('a')
|
||||||
.click({
|
.filter({
|
||||||
button: 'right'
|
hasText: folderA.name
|
||||||
});
|
})
|
||||||
await page.getByRole('menuitem', { name: /Remove/ }).click();
|
.count()
|
||||||
await page.getByRole('button', { name: 'OK' }).click();
|
).toBeGreaterThan(0);
|
||||||
|
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
|
||||||
|
|
||||||
// Verify that the folder and clock are no longer in the recent objects list
|
await page.click('button[title="Show selected item in tree"]');
|
||||||
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
|
// Delete the folder via the left tree pane treeitem context menu
|
||||||
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
|
await page
|
||||||
}
|
.getByRole('treeitem', { name: new RegExp(folderA.name) })
|
||||||
);
|
.locator('a')
|
||||||
|
.click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
|
await page.getByRole('menuitem', { name: /Remove/ }).click();
|
||||||
|
await page.getByRole('button', { name: 'OK' }).click();
|
||||||
|
|
||||||
|
// Verify that the folder and clock are no longer in the recent objects list
|
||||||
|
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
|
||||||
|
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
test('Clicking on an object in the path of a recent object navigates to the object', async ({
|
test('Clicking on an object in the path of a recent object navigates to the object', async ({
|
||||||
page,
|
page,
|
||||||
|
78
e2e/tests/functional/renaming.e2e.spec.js
Normal file
78
e2e/tests/functional/renaming.e2e.spec.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test suite is dedicated to tests for renaming objects, and their global application effects.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('../../baseFixtures.js');
|
||||||
|
const {
|
||||||
|
createDomainObjectWithDefaults,
|
||||||
|
renameObjectFromContextMenu
|
||||||
|
} = require('../../appActions.js');
|
||||||
|
|
||||||
|
test.describe('Renaming objects', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('When renaming objects, the browse bar and various components all update', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
const folder = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Folder'
|
||||||
|
});
|
||||||
|
// Create a new 'Clock' object with default settings
|
||||||
|
const clock = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Clock',
|
||||||
|
parent: folder.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rename
|
||||||
|
clock.name = `${clock.name}-NEW!`;
|
||||||
|
await renameObjectFromContextMenu(page, clock.url, clock.name);
|
||||||
|
// check inspector for new name
|
||||||
|
const titleValue = await page
|
||||||
|
.getByLabel('Title inspector properties')
|
||||||
|
.getByLabel('inspector property value')
|
||||||
|
.textContent();
|
||||||
|
expect(titleValue).toBe(clock.name);
|
||||||
|
// check browse bar for new name
|
||||||
|
await expect(page.locator(`.l-browse-bar >> text=${clock.name}`)).toBeVisible();
|
||||||
|
// check tree item for new name
|
||||||
|
await expect(
|
||||||
|
page.getByRole('listitem', {
|
||||||
|
name: clock.name
|
||||||
|
})
|
||||||
|
).toBeVisible();
|
||||||
|
// check recent objects for new name
|
||||||
|
await expect(
|
||||||
|
page.getByRole('navigation', {
|
||||||
|
name: clock.name
|
||||||
|
})
|
||||||
|
).toBeVisible();
|
||||||
|
// check title for new name
|
||||||
|
const title = await page.title();
|
||||||
|
expect(title).toBe(clock.name);
|
||||||
|
});
|
||||||
|
});
|
@ -23,7 +23,7 @@
|
|||||||
const { test, expect } = require('../../pluginFixtures.js');
|
const { test, expect } = require('../../pluginFixtures.js');
|
||||||
const {
|
const {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
openObjectTreeContextMenu
|
renameObjectFromContextMenu
|
||||||
} = require('../../appActions.js');
|
} = require('../../appActions.js');
|
||||||
|
|
||||||
test.describe('Main Tree', () => {
|
test.describe('Main Tree', () => {
|
||||||
@ -249,18 +249,3 @@ async function expandTreePaneItemByName(page, name) {
|
|||||||
});
|
});
|
||||||
await treeItem.locator('.c-disclosure-triangle').click();
|
await treeItem.locator('.c-disclosure-triangle').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('@playwright/test').Page} page
|
|
||||||
* @param {string} myItemsFolderName
|
|
||||||
* @param {string} url
|
|
||||||
* @param {string} newName
|
|
||||||
*/
|
|
||||||
async function renameObjectFromContextMenu(page, url, newName) {
|
|
||||||
await openObjectTreeContextMenu(page, url);
|
|
||||||
await page.click('li:text("Edit Properties")');
|
|
||||||
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
|
|
||||||
await nameInput.fill('');
|
|
||||||
await nameInput.fill(newName);
|
|
||||||
await page.click('[aria-label="Save"]');
|
|
||||||
}
|
|
||||||
|
273
e2e/tests/performance/tagging.perf.spec.js
Normal file
273
e2e/tests/performance/tagging.perf.spec.js
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2023, 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 plot tagging performance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('../../pluginFixtures');
|
||||||
|
const {
|
||||||
|
createDomainObjectWithDefaults,
|
||||||
|
setRealTimeMode,
|
||||||
|
setFixedTimeMode,
|
||||||
|
waitForPlotsToRender
|
||||||
|
} = require('../../appActions');
|
||||||
|
|
||||||
|
test.describe.fixme('Plot Tagging Performance', () => {
|
||||||
|
/**
|
||||||
|
* Given a canvas and a set of points, tags the points on the canvas.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {HTMLCanvasElement} canvas a telemetry item with a plot
|
||||||
|
* @param {Number} xEnd a telemetry item with a plot
|
||||||
|
* @param {Number} yEnd a telemetry item with a plot
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
async function createTags({ page, canvas, xEnd = 700, yEnd = 480 }) {
|
||||||
|
await canvas.hover({ trial: true });
|
||||||
|
|
||||||
|
//Alt+Shift Drag Start to select some points to tag
|
||||||
|
await page.keyboard.down('Alt');
|
||||||
|
await page.keyboard.down('Shift');
|
||||||
|
|
||||||
|
await canvas.dragTo(canvas, {
|
||||||
|
sourcePosition: {
|
||||||
|
x: 1,
|
||||||
|
y: 1
|
||||||
|
},
|
||||||
|
targetPosition: {
|
||||||
|
x: xEnd,
|
||||||
|
y: yEnd
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Alt Drag End
|
||||||
|
await page.keyboard.up('Alt');
|
||||||
|
await page.keyboard.up('Shift');
|
||||||
|
|
||||||
|
//Wait for canvas to stablize.
|
||||||
|
await canvas.hover({ trial: true });
|
||||||
|
|
||||||
|
// add some tags
|
||||||
|
await page.getByText('Annotations').click();
|
||||||
|
await page.getByRole('button', { name: /Add Tag/ }).click();
|
||||||
|
await page.getByPlaceholder('Type to select tag').click();
|
||||||
|
await page.getByText('Driving').click();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /Add Tag/ }).click();
|
||||||
|
await page.getByPlaceholder('Type to select tag').click();
|
||||||
|
await page.getByText('Science').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a telemetry item (e.g., a Sine Wave Generator) with a plot, tests that the plot can be tagged.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {import('../../../../appActions').CreatedObjectInfo} telemetryItem a telemetry item with a plot
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
async function testTelemetryItem(page, telemetryItem) {
|
||||||
|
// Check that telemetry item also received the tag
|
||||||
|
await page.goto(telemetryItem.url);
|
||||||
|
|
||||||
|
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||||
|
|
||||||
|
const canvas = page.locator('canvas').nth(1);
|
||||||
|
|
||||||
|
//Wait for canvas to stablize.
|
||||||
|
await canvas.hover({ trial: true });
|
||||||
|
|
||||||
|
// click on the tagged plot point
|
||||||
|
await canvas.click({
|
||||||
|
position: {
|
||||||
|
x: 100,
|
||||||
|
y: 100
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByText('Science')).toBeVisible();
|
||||||
|
await expect(page.getByText('Driving')).toBeHidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a page, tests that tags are searchable, deletable, and persist across reloads.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
async function basicTagsTests(page) {
|
||||||
|
// Search for Driving
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||||
|
|
||||||
|
// Clicking elsewhere should cause annotation selection to be cleared
|
||||||
|
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||||
|
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('driv');
|
||||||
|
// click on the search result
|
||||||
|
await page
|
||||||
|
.getByRole('searchbox', { name: 'OpenMCT Search' })
|
||||||
|
.getByText(/Sine Wave/)
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Delete Driving
|
||||||
|
await page.hover('[aria-label="Tag"]:has-text("Driving")');
|
||||||
|
await page.locator('[aria-label="Remove tag Driving"]').click();
|
||||||
|
|
||||||
|
// Search for Science
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||||
|
await expect(page.locator('[aria-label="Search Result"]').nth(0)).toContainText('Science');
|
||||||
|
await expect(page.locator('[aria-label="Search Result"]').nth(0)).not.toContainText('Drilling');
|
||||||
|
|
||||||
|
// Search for Driving
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('driv');
|
||||||
|
await expect(page.getByText('No results found')).toBeVisible();
|
||||||
|
|
||||||
|
//Reload Page
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||||
|
// wait for plots to load
|
||||||
|
await waitForPlotsToRender(page);
|
||||||
|
|
||||||
|
await page.getByText('Annotations').click();
|
||||||
|
await expect(page.getByText('No tags to display for this item')).toBeVisible();
|
||||||
|
|
||||||
|
const canvas = page.locator('canvas').nth(1);
|
||||||
|
// click on the tagged plot point
|
||||||
|
await canvas.click({
|
||||||
|
position: {
|
||||||
|
x: 100,
|
||||||
|
y: 100
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByText('Science')).toBeVisible();
|
||||||
|
await expect(page.getByText('Driving')).toBeHidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Tags work with Overlay Plots', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/6822'
|
||||||
|
});
|
||||||
|
//Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
|
||||||
|
test.slow();
|
||||||
|
|
||||||
|
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Overlay Plot'
|
||||||
|
});
|
||||||
|
|
||||||
|
const alphaSineWave = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
name: 'Alpha Sine Wave',
|
||||||
|
parent: overlayPlot.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
name: 'Beta Sine Wave',
|
||||||
|
parent: overlayPlot.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(overlayPlot.url);
|
||||||
|
|
||||||
|
let canvas = page.locator('canvas').nth(1);
|
||||||
|
|
||||||
|
// Switch to real-time mode
|
||||||
|
// Adding tags should pause the plot
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
|
||||||
|
await createTags({
|
||||||
|
page,
|
||||||
|
canvas
|
||||||
|
});
|
||||||
|
|
||||||
|
await setFixedTimeMode(page);
|
||||||
|
|
||||||
|
await basicTagsTests(page);
|
||||||
|
await testTelemetryItem(page, alphaSineWave);
|
||||||
|
|
||||||
|
// set to real time mode
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
|
||||||
|
// Search for Science
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||||
|
// click on the search result
|
||||||
|
await page
|
||||||
|
.getByRole('searchbox', { name: 'OpenMCT Search' })
|
||||||
|
.getByText('Alpha Sine Wave')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
// wait for plots to load
|
||||||
|
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||||
|
// expect plot to be paused
|
||||||
|
await expect(page.locator('[title="Resume displaying real-time data"]')).toBeVisible();
|
||||||
|
|
||||||
|
await setFixedTimeMode(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Tags work with Plot View of telemetry items', async ({ page }) => {
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator'
|
||||||
|
});
|
||||||
|
const canvas = page.locator('canvas').nth(1);
|
||||||
|
await createTags({
|
||||||
|
page,
|
||||||
|
canvas
|
||||||
|
});
|
||||||
|
await basicTagsTests(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Tags work with Stacked Plots', async ({ page }) => {
|
||||||
|
const stackedPlot = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Stacked Plot'
|
||||||
|
});
|
||||||
|
|
||||||
|
const alphaSineWave = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
name: 'Alpha Sine Wave',
|
||||||
|
parent: stackedPlot.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
name: 'Beta Sine Wave',
|
||||||
|
parent: stackedPlot.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(stackedPlot.url);
|
||||||
|
|
||||||
|
const canvas = page.locator('canvas').nth(1);
|
||||||
|
|
||||||
|
await createTags({
|
||||||
|
page,
|
||||||
|
canvas,
|
||||||
|
xEnd: 700,
|
||||||
|
yEnd: 215
|
||||||
|
});
|
||||||
|
await basicTagsTests(page);
|
||||||
|
await testTelemetryItem(page, alphaSineWave);
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "3.0.0-SNAPSHOT",
|
"version": "3.0.0",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "7.22.5",
|
"@babel/eslint-parser": "7.22.5",
|
||||||
@ -80,7 +80,8 @@
|
|||||||
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
|
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
|
||||||
"start": "npx webpack serve --config ./.webpack/webpack.dev.js",
|
"start": "npx webpack serve --config ./.webpack/webpack.dev.js",
|
||||||
"start:coverage": "npx webpack serve --config ./.webpack/webpack.coverage.js",
|
"start:coverage": "npx webpack serve --config ./.webpack/webpack.coverage.js",
|
||||||
"lint": "eslint example src e2e --ext .js,.vue openmct.js --max-warnings=0",
|
"lint": "eslint example src e2e --ext .js openmct.js --max-warnings=0 && eslint example src --ext .vue",
|
||||||
|
"lint:spelling": "cspell \"**/*.{js,md,vue}\" --show-context --gitignore",
|
||||||
"lint:fix": "eslint example src e2e --ext .js,.vue openmct.js --fix",
|
"lint:fix": "eslint example src e2e --ext .js,.vue openmct.js --fix",
|
||||||
"build:prod": "webpack --config ./.webpack/webpack.prod.js",
|
"build:prod": "webpack --config ./.webpack/webpack.prod.js",
|
||||||
"build:dev": "webpack --config ./.webpack/webpack.dev.js",
|
"build:dev": "webpack --config ./.webpack/webpack.dev.js",
|
||||||
|
@ -22,9 +22,9 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="form-row c-form__row" :class="[{ first: first }, cssClass]" @onChange="onChange">
|
<div class="form-row c-form__row" :class="[{ first: first }, cssClass]" @onChange="onChange">
|
||||||
<div class="c-form-row__label" :title="row.description">
|
<label class="c-form-row__label" :title="row.description" :for="`form-${row.key}`">
|
||||||
{{ row.name }}
|
{{ row.name }}
|
||||||
</div>
|
</label>
|
||||||
<div class="c-form-row__state-indicator" :class="reqClass"></div>
|
<div class="c-form-row__state-indicator" :class="reqClass"></div>
|
||||||
<div v-if="row.control" ref="rowElement" class="c-form-row__controls"></div>
|
<div v-if="row.control" ref="rowElement" class="c-form-row__controls"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +23,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="form-control shell">
|
<span class="form-control shell">
|
||||||
<span class="field control" :class="model.cssClass">
|
<span class="field control" :class="model.cssClass">
|
||||||
<input v-model="field" type="text" :size="model.size" @input="updateText()" />
|
<input
|
||||||
|
:id="`form-${model.key}`"
|
||||||
|
v-model="field"
|
||||||
|
:name="model.key"
|
||||||
|
type="text"
|
||||||
|
:size="model.size"
|
||||||
|
@input="updateText()"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -554,28 +554,34 @@ export default class ObjectAPI {
|
|||||||
*/
|
*/
|
||||||
async getTelemetryPath(identifier, telemetryIdentifier) {
|
async getTelemetryPath(identifier, telemetryIdentifier) {
|
||||||
const objectDetails = await this.get(identifier);
|
const objectDetails = await this.get(identifier);
|
||||||
const telemetryPath = [];
|
let telemetryPath = [];
|
||||||
if (objectDetails.composition && !['folder'].includes(objectDetails.type)) {
|
if (objectDetails?.type === 'folder') {
|
||||||
let sourceTelemetry = objectDetails.composition[0];
|
return telemetryPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sourceTelemetry = null;
|
||||||
|
if (telemetryIdentifier && utils.identifierEquals(identifier, telemetryIdentifier)) {
|
||||||
|
sourceTelemetry = identifier;
|
||||||
|
} else if (objectDetails.composition) {
|
||||||
|
sourceTelemetry = objectDetails.composition[0];
|
||||||
if (telemetryIdentifier) {
|
if (telemetryIdentifier) {
|
||||||
sourceTelemetry = objectDetails.composition.find(
|
sourceTelemetry = objectDetails.composition.find((telemetrySource) =>
|
||||||
(telemetrySource) =>
|
utils.identifierEquals(telemetrySource, telemetryIdentifier)
|
||||||
this.makeKeyString(telemetrySource) === this.makeKeyString(telemetryIdentifier)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const compositionElement = await this.get(sourceTelemetry);
|
|
||||||
if (!['yamcs.telemetry', 'generator'].includes(compositionElement.type)) {
|
|
||||||
return telemetryPath;
|
|
||||||
}
|
|
||||||
const telemetryKey = compositionElement.identifier.key;
|
|
||||||
const telemetryPathObjects = await this.getOriginalPath(telemetryKey);
|
|
||||||
telemetryPathObjects.forEach((pathObject) => {
|
|
||||||
if (pathObject.type === 'root') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
telemetryPath.unshift(pathObject.name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const compositionElement = await this.get(sourceTelemetry);
|
||||||
|
if (!['yamcs.telemetry', 'generator', 'yamcs.aggregate'].includes(compositionElement.type)) {
|
||||||
|
return telemetryPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const telemetryPathObjects = await this.getOriginalPath(compositionElement.identifier);
|
||||||
|
telemetryPath = telemetryPathObjects
|
||||||
|
.reverse()
|
||||||
|
.filter((pathObject) => pathObject.type !== 'root')
|
||||||
|
.map((pathObject) => pathObject.name);
|
||||||
|
|
||||||
return telemetryPath;
|
return telemetryPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,13 +57,22 @@ class TooltipAPI {
|
|||||||
* @private for platform-internal use
|
* @private for platform-internal use
|
||||||
*/
|
*/
|
||||||
showTooltip(tooltip) {
|
showTooltip(tooltip) {
|
||||||
|
this.removeAllTooltips();
|
||||||
|
this.activeToolTips.push(tooltip);
|
||||||
|
tooltip.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API method to allow for removing all tooltips
|
||||||
|
*/
|
||||||
|
removeAllTooltips() {
|
||||||
|
if (!this.activeToolTips?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (let i = this.activeToolTips.length - 1; i > -1; i--) {
|
for (let i = this.activeToolTips.length - 1; i > -1; i--) {
|
||||||
this.activeToolTips[i].destroy();
|
this.activeToolTips[i].destroy();
|
||||||
this.activeToolTips.splice(i, 1);
|
this.activeToolTips.splice(i, 1);
|
||||||
}
|
}
|
||||||
this.activeToolTips.push(tooltip);
|
|
||||||
|
|
||||||
tooltip.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
width: auto;
|
width: auto;
|
||||||
padding: $interiorMargin;
|
padding: $interiorMargin;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-tooltip {
|
.c-tooltip {
|
||||||
|
@ -68,7 +68,12 @@ define([], function () {
|
|||||||
this.updateRowData.bind(this)
|
this.updateRowData.bind(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.openmct.telemetry.request(this.domainObject, { size: 1 }).then(
|
const options = {
|
||||||
|
size: 1,
|
||||||
|
strategy: 'latest',
|
||||||
|
timeContext: this.openmct.time.getContextForView([])
|
||||||
|
};
|
||||||
|
this.openmct.telemetry.request(this.domainObject, options).then(
|
||||||
function (history) {
|
function (history) {
|
||||||
if (!this.initialized && history.length > 0) {
|
if (!this.initialized && history.length > 0) {
|
||||||
this.updateRowData(history[history.length - 1]);
|
this.updateRowData(history[history.length - 1]);
|
||||||
|
@ -98,9 +98,11 @@ export default function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getScatterPlotFormControl(openmct) {
|
function getScatterPlotFormControl(openmct) {
|
||||||
|
let destroyComponent;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
show(element, model, onChange) {
|
show(element, model, onChange) {
|
||||||
const { vNode } = mount(
|
const { vNode, destroy } = mount(
|
||||||
{
|
{
|
||||||
el: element,
|
el: element,
|
||||||
components: {
|
components: {
|
||||||
@ -122,8 +124,12 @@ export default function () {
|
|||||||
element
|
element
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
destroyComponent = destroy;
|
||||||
|
|
||||||
return vNode;
|
return vNode;
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
destroyComponent();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ export default function plugin(appliesToObjects, options = { indicator: true })
|
|||||||
|
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
if (installIndicator) {
|
if (installIndicator) {
|
||||||
const { vNode } = mount(
|
const { vNode, destroy } = mount(
|
||||||
{
|
{
|
||||||
components: {
|
components: {
|
||||||
GlobalClearIndicator
|
GlobalClearIndicator
|
||||||
@ -49,7 +49,8 @@ export default function plugin(appliesToObjects, options = { indicator: true })
|
|||||||
let indicator = {
|
let indicator = {
|
||||||
element: vNode.el,
|
element: vNode.el,
|
||||||
key: 'global-clear-indicator',
|
key: 'global-clear-indicator',
|
||||||
priority: openmct.priority.DEFAULT
|
priority: openmct.priority.DEFAULT,
|
||||||
|
destroy: destroy
|
||||||
};
|
};
|
||||||
|
|
||||||
openmct.indicators.add(indicator);
|
openmct.indicators.add(indicator);
|
||||||
|
@ -201,9 +201,11 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
requestLAD(telemetryObjects, requestOptions) {
|
requestLAD(telemetryObjects, requestOptions) {
|
||||||
|
//We pass in the global time context here
|
||||||
let options = {
|
let options = {
|
||||||
strategy: 'latest',
|
strategy: 'latest',
|
||||||
size: 1
|
size: 1,
|
||||||
|
timeContext: this.openmct.time.getContextForView([])
|
||||||
};
|
};
|
||||||
|
|
||||||
if (requestOptions !== undefined) {
|
if (requestOptions !== undefined) {
|
||||||
|
@ -189,9 +189,11 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
requestLAD(telemetryObjects, requestOptions) {
|
requestLAD(telemetryObjects, requestOptions) {
|
||||||
|
//We pass in the global time context here
|
||||||
let options = {
|
let options = {
|
||||||
strategy: 'latest',
|
strategy: 'latest',
|
||||||
size: 1
|
size: 1,
|
||||||
|
timeContext: this.openmct.time.getContextForView([])
|
||||||
};
|
};
|
||||||
|
|
||||||
if (requestOptions !== undefined) {
|
if (requestOptions !== undefined) {
|
||||||
|
@ -83,13 +83,19 @@ describe('The telemetry criterion', function () {
|
|||||||
});
|
});
|
||||||
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
|
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
|
||||||
|
|
||||||
openmct.time = jasmine.createSpyObj('timeAPI', ['timeSystem', 'bounds', 'getAllTimeSystems']);
|
openmct.time = jasmine.createSpyObj('timeAPI', [
|
||||||
|
'timeSystem',
|
||||||
|
'bounds',
|
||||||
|
'getAllTimeSystems',
|
||||||
|
'getContextForView'
|
||||||
|
]);
|
||||||
openmct.time.timeSystem.and.returnValue({ key: 'system' });
|
openmct.time.timeSystem.and.returnValue({ key: 'system' });
|
||||||
openmct.time.bounds.and.returnValue({
|
openmct.time.bounds.and.returnValue({
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1
|
end: 1
|
||||||
});
|
});
|
||||||
openmct.time.getAllTimeSystems.and.returnValue([{ key: 'system' }]);
|
openmct.time.getAllTimeSystems.and.returnValue([{ key: 'system' }]);
|
||||||
|
openmct.time.getContextForView.and.returnValue({});
|
||||||
|
|
||||||
testCriterionDefinition = {
|
testCriterionDefinition = {
|
||||||
id: 'test-criterion-id',
|
id: 'test-criterion-id',
|
||||||
|
@ -20,14 +20,14 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<layout-frame
|
<LayoutFrame
|
||||||
:item="item"
|
:item="item"
|
||||||
:grid-size="gridSize"
|
:grid-size="gridSize"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||||
@endMove="() => $emit('endMove')"
|
@endMove="() => $emit('endMove')"
|
||||||
>
|
>
|
||||||
<object-frame
|
<ObjectFrame
|
||||||
v-if="domainObject"
|
v-if="domainObject"
|
||||||
ref="objectFrame"
|
ref="objectFrame"
|
||||||
:domain-object="domainObject"
|
:domain-object="domainObject"
|
||||||
@ -37,7 +37,7 @@
|
|||||||
:layout-font-size="item.fontSize"
|
:layout-font-size="item.fontSize"
|
||||||
:layout-font="item.font"
|
:layout-font="item.font"
|
||||||
/>
|
/>
|
||||||
</layout-frame>
|
</LayoutFrame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -221,6 +221,8 @@ export default class DuplicateTask {
|
|||||||
// parse reviver to replace identifiers
|
// parse reviver to replace identifiers
|
||||||
clonedParent = JSON.parse(clonedParent, (key, value) => {
|
clonedParent = JSON.parse(clonedParent, (key, value) => {
|
||||||
if (
|
if (
|
||||||
|
value !== null &&
|
||||||
|
value !== undefined &&
|
||||||
Object.prototype.hasOwnProperty.call(value, 'key') &&
|
Object.prototype.hasOwnProperty.call(value, 'key') &&
|
||||||
Object.prototype.hasOwnProperty.call(value, 'namespace') &&
|
Object.prototype.hasOwnProperty.call(value, 'namespace') &&
|
||||||
value.key === oldId.key &&
|
value.key === oldId.key &&
|
||||||
|
@ -37,7 +37,6 @@
|
|||||||
<template v-for="(container, index) in containers" :key="`component-${container.id}`">
|
<template v-for="(container, index) in containers" :key="`component-${container.id}`">
|
||||||
<drop-hint
|
<drop-hint
|
||||||
v-if="index === 0 && containers.length > 1"
|
v-if="index === 0 && containers.length > 1"
|
||||||
:key="`hint-top-${container.id}`"
|
|
||||||
class="c-fl-frame__drop-hint"
|
class="c-fl-frame__drop-hint"
|
||||||
:index="-1"
|
:index="-1"
|
||||||
:allow-drop="allowContainerDrop"
|
:allow-drop="allowContainerDrop"
|
||||||
@ -59,7 +58,6 @@
|
|||||||
|
|
||||||
<resize-handle
|
<resize-handle
|
||||||
v-if="index !== containers.length - 1"
|
v-if="index !== containers.length - 1"
|
||||||
:key="`handle-${container.id}`"
|
|
||||||
:index="index"
|
:index="index"
|
||||||
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
|
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@ -70,7 +68,6 @@
|
|||||||
|
|
||||||
<drop-hint
|
<drop-hint
|
||||||
v-if="containers.length > 1"
|
v-if="containers.length > 1"
|
||||||
:key="`hint-bottom-${container.id}`"
|
|
||||||
class="c-fl-frame__drop-hint"
|
class="c-fl-frame__drop-hint"
|
||||||
:index="index"
|
:index="index"
|
||||||
:allow-drop="allowContainerDrop"
|
:allow-drop="allowContainerDrop"
|
||||||
@ -137,15 +134,16 @@ export default {
|
|||||||
ResizeHandle,
|
ResizeHandle,
|
||||||
DropHint
|
DropHint
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'objectPath', 'layoutObject'],
|
inject: ['openmct', 'objectPath', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
isEditing: Boolean
|
isEditing: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
domainObject: this.layoutObject,
|
|
||||||
newFrameLocation: [],
|
newFrameLocation: [],
|
||||||
identifierMap: {}
|
identifierMap: {},
|
||||||
|
containers: this.domainObject.configuration.containers,
|
||||||
|
rowsLayout: this.domainObject.configuration.rowsLayout
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -156,22 +154,22 @@ export default {
|
|||||||
return 'Columns';
|
return 'Columns';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containers() {
|
|
||||||
return this.domainObject.configuration.containers;
|
|
||||||
},
|
|
||||||
rowsLayout() {
|
|
||||||
return this.domainObject.configuration.rowsLayout;
|
|
||||||
},
|
|
||||||
allContainersAreEmpty() {
|
allContainersAreEmpty() {
|
||||||
return this.containers.every((container) => container.frames.length === 0);
|
return this.containers.every((container) => container.frames.length === 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
created() {
|
||||||
this.buildIdentifierMap();
|
this.buildIdentifierMap();
|
||||||
this.composition = this.openmct.composition.get(this.domainObject);
|
this.composition = this.openmct.composition.get(this.domainObject);
|
||||||
this.composition.on('remove', this.removeChildObject);
|
this.composition.on('remove', this.removeChildObject);
|
||||||
this.composition.on('add', this.addFrame);
|
this.composition.on('add', this.addFrame);
|
||||||
this.composition.load();
|
this.composition.load();
|
||||||
|
this.openmct.objects.observe(this.domainObject, 'configuration.containers', (containers) => {
|
||||||
|
this.containers = containers;
|
||||||
|
});
|
||||||
|
this.openmct.objects.observe(this.domainObject, 'configuration.rowsLayout', (rowsLayout) => {
|
||||||
|
this.rowsLayout = rowsLayout;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.composition.off('remove', this.removeChildObject);
|
this.composition.off('remove', this.removeChildObject);
|
||||||
@ -211,20 +209,16 @@ export default {
|
|||||||
let container = this.containers.filter((c) => c.id === containerId)[0];
|
let container = this.containers.filter((c) => c.id === containerId)[0];
|
||||||
let containerIndex = this.containers.indexOf(container);
|
let containerIndex = this.containers.indexOf(container);
|
||||||
|
|
||||||
/*
|
// remove associated domainObjects from composition
|
||||||
remove associated domainObjects from composition
|
|
||||||
*/
|
|
||||||
container.frames.forEach((f) => {
|
container.frames.forEach((f) => {
|
||||||
this.removeFromComposition(f.domainObjectIdentifier);
|
this.removeFromComposition(f.domainObjectIdentifier);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.containers.splice(containerIndex, 1);
|
this.containers.splice(containerIndex, 1);
|
||||||
|
|
||||||
/*
|
// add a container when there are no containers in the FL,
|
||||||
add a container when there are no containers in the FL,
|
// to prevent user from not being able to add a frame via
|
||||||
to prevent user from not being able to add a frame via
|
// drag and drop.
|
||||||
drag and drop.
|
|
||||||
*/
|
|
||||||
if (this.containers.length === 0) {
|
if (this.containers.length === 0) {
|
||||||
this.containers.push(new Container(100));
|
this.containers.push(new Container(100));
|
||||||
}
|
}
|
||||||
|
@ -47,17 +47,16 @@ export default class FlexibleLayoutViewProvider {
|
|||||||
let component = null;
|
let component = null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
show: function (element, isEditing) {
|
show(element, isEditing) {
|
||||||
const { vNode, destroy } = mount(
|
const { vNode, destroy } = mount(
|
||||||
{
|
{
|
||||||
el: element,
|
|
||||||
components: {
|
components: {
|
||||||
FlexibleLayoutComponent
|
FlexibleLayoutComponent
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct: openmct,
|
openmct,
|
||||||
objectPath,
|
objectPath,
|
||||||
layoutObject: domainObject
|
domainObject
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -75,7 +74,7 @@ export default class FlexibleLayoutViewProvider {
|
|||||||
component = vNode.componentInstance;
|
component = vNode.componentInstance;
|
||||||
_destroy = destroy;
|
_destroy = destroy;
|
||||||
},
|
},
|
||||||
getSelectionContext: function () {
|
getSelectionContext() {
|
||||||
return {
|
return {
|
||||||
item: domainObject,
|
item: domainObject,
|
||||||
addContainer: component.$refs.flexibleLayout.addContainer,
|
addContainer: component.$refs.flexibleLayout.addContainer,
|
||||||
@ -84,10 +83,10 @@ export default class FlexibleLayoutViewProvider {
|
|||||||
type: 'flexible-layout'
|
type: 'flexible-layout'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onEditModeChange: function (isEditing) {
|
onEditModeChange(isEditing) {
|
||||||
component.isEditing = isEditing;
|
component.isEditing = isEditing;
|
||||||
},
|
},
|
||||||
destroy: function (element) {
|
destroy() {
|
||||||
if (_destroy) {
|
if (_destroy) {
|
||||||
_destroy();
|
_destroy();
|
||||||
component = null;
|
component = null;
|
||||||
|
@ -33,6 +33,10 @@ describe('the plugin', function () {
|
|||||||
let mockComposition;
|
let mockComposition;
|
||||||
|
|
||||||
const testViewObject = {
|
const testViewObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: '',
|
||||||
|
key: 'test-object'
|
||||||
|
},
|
||||||
id: 'test-object',
|
id: 'test-object',
|
||||||
type: 'flexible-layout',
|
type: 'flexible-layout',
|
||||||
configuration: {
|
configuration: {
|
||||||
@ -116,6 +120,10 @@ describe('the plugin', function () {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
flexibleLayoutItem = {
|
flexibleLayoutItem = {
|
||||||
|
identifier: {
|
||||||
|
namespace: '',
|
||||||
|
key: 'test-object'
|
||||||
|
},
|
||||||
id: 'test-object',
|
id: 'test-object',
|
||||||
type: 'flexible-layout',
|
type: 'flexible-layout',
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@ -167,9 +167,11 @@ export default function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getGaugeFormController(openmct) {
|
function getGaugeFormController(openmct) {
|
||||||
|
let destroyComponent;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
show(element, model, onChange) {
|
show(element, model, onChange) {
|
||||||
const { vNode } = mount(
|
const { vNode, destroy } = mount(
|
||||||
{
|
{
|
||||||
el: element,
|
el: element,
|
||||||
components: {
|
components: {
|
||||||
@ -191,8 +193,12 @@ export default function () {
|
|||||||
element
|
element
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
destroyComponent = destroy;
|
||||||
|
|
||||||
return vNode.componentInstance;
|
return vNode.componentInstance;
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
destroyComponent();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -638,7 +638,11 @@ export default {
|
|||||||
|
|
||||||
this.valueKey = this.metadata.valuesForHints(['range'])[0].source;
|
this.valueKey = this.metadata.valuesForHints(['range'])[0].source;
|
||||||
|
|
||||||
this.openmct.telemetry.request(domainObject, { strategy: 'latest' }).then((values) => {
|
const options = {
|
||||||
|
strategy: 'latest',
|
||||||
|
timeContext: this.openmct.time.getContextForView([])
|
||||||
|
};
|
||||||
|
this.openmct.telemetry.request(domainObject, options).then((values) => {
|
||||||
const length = values.length;
|
const length = values.length;
|
||||||
this.updateValue(values[length - 1]);
|
this.updateValue(values[length - 1]);
|
||||||
});
|
});
|
||||||
|
@ -98,6 +98,9 @@ export default {
|
|||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
}
|
}
|
||||||
|
if (this.destroyImageryContainer) {
|
||||||
|
this.destroyImageryContainer();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
@ -237,7 +240,10 @@ export default {
|
|||||||
imageryContainer = existingContainer;
|
imageryContainer = existingContainer;
|
||||||
imageryContainer.style.maxWidth = `${containerWidth}px`;
|
imageryContainer.style.maxWidth = `${containerWidth}px`;
|
||||||
} else {
|
} else {
|
||||||
const { vNode } = mount(
|
if (this.destroyImageryContainer) {
|
||||||
|
this.destroyImageryContainer();
|
||||||
|
}
|
||||||
|
const { vNode, destroy } = mount(
|
||||||
{
|
{
|
||||||
components: {
|
components: {
|
||||||
SwimLane
|
SwimLane
|
||||||
@ -257,6 +263,7 @@ export default {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.destroyImageryContainer = destroy;
|
||||||
const component = vNode.componentInstance;
|
const component = vNode.componentInstance;
|
||||||
this.$refs.imageryHolder.appendChild(component.$el);
|
this.$refs.imageryHolder.appendChild(component.$el);
|
||||||
|
|
||||||
|
@ -21,11 +21,11 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<li class="c-inspect-properties__row">
|
<li class="c-inspect-properties__row" :aria-label="`${detail.name} inspector properties`">
|
||||||
<div class="c-inspect-properties__label">
|
<div class="c-inspect-properties__label" aria-label="inspector property name">
|
||||||
{{ detail.name }}
|
{{ detail.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="c-inspect-properties__value">
|
<div class="c-inspect-properties__value" aria-label="inspector property value">
|
||||||
{{ detail.value }}
|
{{ detail.value }}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -73,9 +73,43 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
this.nameChangeListeners = {};
|
||||||
await this.createPathBreadCrumb();
|
await this.createPathBreadCrumb();
|
||||||
},
|
},
|
||||||
|
unmounted() {
|
||||||
|
Object.values(this.nameChangeListeners).forEach((unlisten) => {
|
||||||
|
unlisten();
|
||||||
|
});
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
updateObjectPathName(keyString, newName) {
|
||||||
|
this.pathBreadCrumb = this.pathBreadCrumb.map((pathObject) => {
|
||||||
|
if (this.openmct.objects.makeKeyString(pathObject.domainObject.identifier) === keyString) {
|
||||||
|
return {
|
||||||
|
...pathObject,
|
||||||
|
domainObject: { ...pathObject.domainObject, name: newName }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return pathObject;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeNameListenerFor(domainObject) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
if (this.nameChangeListeners[keyString]) {
|
||||||
|
this.nameChangeListeners[keyString]();
|
||||||
|
delete this.nameChangeListeners[keyString];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addNameListenerFor(domainObject) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
if (!this.nameChangeListeners[keyString]) {
|
||||||
|
this.nameChangeListeners[keyString] = this.openmct.objects.observe(
|
||||||
|
domainObject,
|
||||||
|
'name',
|
||||||
|
this.updateObjectPathName.bind(this, keyString)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
async createPathBreadCrumb() {
|
async createPathBreadCrumb() {
|
||||||
if (!this.domainObject && this.parentDomainObject) {
|
if (!this.domainObject && this.parentDomainObject) {
|
||||||
this.setPathBreadCrumb([this.parentDomainObject]);
|
this.setPathBreadCrumb([this.parentDomainObject]);
|
||||||
@ -98,7 +132,15 @@ export default {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.pathBreadCrumb.forEach((pathObject) => {
|
||||||
|
this.removeNameListenerFor(pathObject.domainObject);
|
||||||
|
});
|
||||||
|
|
||||||
this.pathBreadCrumb = pathBreadCrumb;
|
this.pathBreadCrumb = pathBreadCrumb;
|
||||||
|
|
||||||
|
this.pathBreadCrumb.forEach((pathObject) => {
|
||||||
|
this.addNameListenerFor(pathObject.domainObject);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -230,7 +230,22 @@ export default {
|
|||||||
return `detail-${component}`;
|
return `detail-${component}`;
|
||||||
},
|
},
|
||||||
updateSelection(selection) {
|
updateSelection(selection) {
|
||||||
|
this.removeListener();
|
||||||
this.selection.splice(0, this.selection.length, ...selection);
|
this.selection.splice(0, this.selection.length, ...selection);
|
||||||
|
if (this.domainObject) {
|
||||||
|
this.addListener();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeListener() {
|
||||||
|
if (this.nameListener) {
|
||||||
|
this.nameListener();
|
||||||
|
this.nameListener = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addListener() {
|
||||||
|
this.nameListener = this.openmct.objects.observe(this.context?.item, 'name', (newValue) => {
|
||||||
|
this.context.item = { ...this.context?.item, name: newValue };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -90,7 +90,10 @@ export default {
|
|||||||
drawerElement.innerHTML = '<div></div>';
|
drawerElement.innerHTML = '<div></div>';
|
||||||
const divElement = document.querySelector('.l-shell__drawer div');
|
const divElement = document.querySelector('.l-shell__drawer div');
|
||||||
|
|
||||||
mount(
|
if (this.destroySnapshotContainer) {
|
||||||
|
this.destroySnapshotContainer();
|
||||||
|
}
|
||||||
|
const { destroy } = mount(
|
||||||
{
|
{
|
||||||
el: divElement,
|
el: divElement,
|
||||||
components: {
|
components: {
|
||||||
@ -113,6 +116,7 @@ export default {
|
|||||||
element: divElement
|
element: divElement
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
this.destroySnapshotContainer = destroy;
|
||||||
},
|
},
|
||||||
updateSnapshotIndicatorTitle() {
|
updateSnapshotIndicatorTitle() {
|
||||||
const snapshotCount = this.snapshotContainer.getSnapshots().length;
|
const snapshotCount = this.snapshotContainer.getSnapshots().length;
|
||||||
|
@ -83,7 +83,7 @@ function installBaseNotebookFunctionality(openmct) {
|
|||||||
openmct.actions.register(new CopyToNotebookAction(openmct));
|
openmct.actions.register(new CopyToNotebookAction(openmct));
|
||||||
openmct.actions.register(new ExportNotebookAsTextAction(openmct));
|
openmct.actions.register(new ExportNotebookAsTextAction(openmct));
|
||||||
|
|
||||||
const { vNode } = mount(
|
const { vNode, destroy } = mount(
|
||||||
{
|
{
|
||||||
components: {
|
components: {
|
||||||
NotebookSnapshotIndicator
|
NotebookSnapshotIndicator
|
||||||
@ -102,7 +102,8 @@ function installBaseNotebookFunctionality(openmct) {
|
|||||||
const indicator = {
|
const indicator = {
|
||||||
element: vNode.el,
|
element: vNode.el,
|
||||||
key: 'notebook-snapshot-indicator',
|
key: 'notebook-snapshot-indicator',
|
||||||
priority: openmct.priority.DEFAULT
|
priority: openmct.priority.DEFAULT,
|
||||||
|
destroy: destroy
|
||||||
};
|
};
|
||||||
|
|
||||||
openmct.indicators.add(indicator);
|
openmct.indicators.add(indicator);
|
||||||
|
@ -24,7 +24,7 @@ import NotificationIndicator from './components/NotificationIndicator.vue';
|
|||||||
|
|
||||||
export default function plugin() {
|
export default function plugin() {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
const { vNode } = mount(
|
const { vNode, destroy } = mount(
|
||||||
{
|
{
|
||||||
components: {
|
components: {
|
||||||
NotificationIndicator
|
NotificationIndicator
|
||||||
@ -42,7 +42,8 @@ export default function plugin() {
|
|||||||
let indicator = {
|
let indicator = {
|
||||||
key: 'notifications-indicator',
|
key: 'notifications-indicator',
|
||||||
element: vNode.el,
|
element: vNode.el,
|
||||||
priority: openmct.priority.DEFAULT
|
priority: openmct.priority.DEFAULT,
|
||||||
|
destroy: destroy
|
||||||
};
|
};
|
||||||
openmct.indicators.add(indicator);
|
openmct.indicators.add(indicator);
|
||||||
};
|
};
|
||||||
|
@ -248,6 +248,7 @@ export default {
|
|||||||
highlights: [],
|
highlights: [],
|
||||||
annotatedPoints: [],
|
annotatedPoints: [],
|
||||||
annotationSelections: [],
|
annotationSelections: [],
|
||||||
|
annotationsEverLoaded: false,
|
||||||
lockHighlightPoint: false,
|
lockHighlightPoint: false,
|
||||||
yKeyOptions: [],
|
yKeyOptions: [],
|
||||||
yAxisLabel: '',
|
yAxisLabel: '',
|
||||||
@ -396,7 +397,11 @@ export default {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.openmct.objectViews.on('clearData', this.clearData);
|
this.openmct.objectViews.on('clearData', this.clearData);
|
||||||
this.$on('loadingComplete', this.loadAnnotations);
|
this.$on('loadingComplete', () => {
|
||||||
|
if (this.annotationViewingAndEditingAllowed) {
|
||||||
|
this.loadAnnotations();
|
||||||
|
}
|
||||||
|
});
|
||||||
this.openmct.selection.on('change', this.updateSelection);
|
this.openmct.selection.on('change', this.updateSelection);
|
||||||
this.yAxisListWithRange = [this.config.yAxis, ...this.config.additionalYAxes];
|
this.yAxisListWithRange = [this.config.yAxis, ...this.config.additionalYAxes];
|
||||||
|
|
||||||
@ -640,6 +645,7 @@ export default {
|
|||||||
if (rawAnnotationsForPlot) {
|
if (rawAnnotationsForPlot) {
|
||||||
this.annotatedPoints = this.findAnnotationPoints(rawAnnotationsForPlot);
|
this.annotatedPoints = this.findAnnotationPoints(rawAnnotationsForPlot);
|
||||||
}
|
}
|
||||||
|
this.annotationsEverLoaded = true;
|
||||||
},
|
},
|
||||||
loadSeriesData(series) {
|
loadSeriesData(series) {
|
||||||
//this check ensures that duplicate requests don't happen on load
|
//this check ensures that duplicate requests don't happen on load
|
||||||
@ -793,6 +799,7 @@ export default {
|
|||||||
};
|
};
|
||||||
this.config.xAxis.set('range', newRange);
|
this.config.xAxis.set('range', newRange);
|
||||||
if (!isTick) {
|
if (!isTick) {
|
||||||
|
this.annotatedPoints = [];
|
||||||
this.clearPanZoomHistory();
|
this.clearPanZoomHistory();
|
||||||
this.synchronizeIfBoundsMatch();
|
this.synchronizeIfBoundsMatch();
|
||||||
this.loadMoreData(newRange, true);
|
this.loadMoreData(newRange, true);
|
||||||
@ -1789,6 +1796,9 @@ export default {
|
|||||||
});
|
});
|
||||||
this.config.xAxis.set('frozen', true);
|
this.config.xAxis.set('frozen', true);
|
||||||
this.setStatus();
|
this.setStatus();
|
||||||
|
if (!this.annotationsEverLoaded) {
|
||||||
|
this.loadAnnotations();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
resumeRealtimeData() {
|
resumeRealtimeData() {
|
||||||
|
@ -826,56 +826,32 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
annotatedPointWithinRange(annotatedPoint, xRange, yRange) {
|
|
||||||
if (!yRange) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const xValue = annotatedPoint.series.getXVal(annotatedPoint.point);
|
|
||||||
const yValue = annotatedPoint.series.getYVal(annotatedPoint.point);
|
|
||||||
|
|
||||||
return (
|
|
||||||
xValue > xRange.min && xValue < xRange.max && yValue > yRange.min && yValue < yRange.max
|
|
||||||
);
|
|
||||||
},
|
|
||||||
drawAnnotatedPoints(yAxisId) {
|
drawAnnotatedPoints(yAxisId) {
|
||||||
// we should do this by series, and then plot all the points at once instead
|
// we should do this by series, and then plot all the points at once instead
|
||||||
// of doing it one by one
|
// of doing it one by one
|
||||||
if (this.annotatedPoints && this.annotatedPoints.length) {
|
if (this.annotatedPoints && this.annotatedPoints.length) {
|
||||||
const uniquePointsToDraw = [];
|
const uniquePointsToDraw = [];
|
||||||
const xRange = this.config.xAxis.get('displayRange');
|
|
||||||
let yRange;
|
|
||||||
if (yAxisId === this.config.yAxis.get('id')) {
|
|
||||||
yRange = this.config.yAxis.get('displayRange');
|
|
||||||
} else if (this.config.additionalYAxes.length) {
|
|
||||||
const yAxisForId = this.config.additionalYAxes.find(
|
|
||||||
(yAxis) => yAxis.get('id') === yAxisId
|
|
||||||
);
|
|
||||||
yRange = yAxisForId.get('displayRange');
|
|
||||||
}
|
|
||||||
|
|
||||||
const annotatedPoints = this.annotatedPoints.filter(
|
const annotatedPoints = this.annotatedPoints.filter(
|
||||||
this.matchByYAxisId.bind(this, yAxisId)
|
this.matchByYAxisId.bind(this, yAxisId)
|
||||||
);
|
);
|
||||||
annotatedPoints.forEach((annotatedPoint) => {
|
annotatedPoints.forEach((annotatedPoint) => {
|
||||||
// if the annotation is outside the range, don't draw it
|
// annotation points are all within range (checked in MctPlot with FlatBush), so we don't need to check
|
||||||
if (this.annotatedPointWithinRange(annotatedPoint, xRange, yRange)) {
|
const canvasXValue = this.offset[yAxisId].xVal(
|
||||||
const canvasXValue = this.offset[yAxisId].xVal(
|
annotatedPoint.point,
|
||||||
annotatedPoint.point,
|
annotatedPoint.series
|
||||||
annotatedPoint.series
|
);
|
||||||
);
|
const canvasYValue = this.offset[yAxisId].yVal(
|
||||||
const canvasYValue = this.offset[yAxisId].yVal(
|
annotatedPoint.point,
|
||||||
annotatedPoint.point,
|
annotatedPoint.series
|
||||||
annotatedPoint.series
|
);
|
||||||
);
|
const pointToDraw = new Float32Array([canvasXValue, canvasYValue]);
|
||||||
const pointToDraw = new Float32Array([canvasXValue, canvasYValue]);
|
const drawnPoint = uniquePointsToDraw.some((rawPoint) => {
|
||||||
const drawnPoint = uniquePointsToDraw.some((rawPoint) => {
|
return rawPoint[0] === pointToDraw[0] && rawPoint[1] === pointToDraw[1];
|
||||||
return rawPoint[0] === pointToDraw[0] && rawPoint[1] === pointToDraw[1];
|
});
|
||||||
});
|
if (!drawnPoint) {
|
||||||
if (!drawnPoint) {
|
uniquePointsToDraw.push(pointToDraw);
|
||||||
uniquePointsToDraw.push(pointToDraw);
|
this.drawAnnotatedPoint(annotatedPoint, pointToDraw);
|
||||||
this.drawAnnotatedPoint(annotatedPoint, pointToDraw);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ export default {
|
|||||||
this.composition.load();
|
this.composition.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { vNode } = mount(
|
const { vNode, destroy } = mount(
|
||||||
{
|
{
|
||||||
components: {
|
components: {
|
||||||
Plot
|
Plot
|
||||||
@ -249,6 +249,7 @@ export default {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.component = vNode.componentInstance;
|
this.component = vNode.componentInstance;
|
||||||
|
this._destroy = destroy;
|
||||||
|
|
||||||
if (this.isEditing) {
|
if (this.isEditing) {
|
||||||
this.setSelection();
|
this.setSelection();
|
||||||
|
@ -62,6 +62,13 @@ export default class RemoteClock extends DefaultClock {
|
|||||||
this.openmct.objects
|
this.openmct.objects
|
||||||
.get(this.identifier)
|
.get(this.identifier)
|
||||||
.then((domainObject) => {
|
.then((domainObject) => {
|
||||||
|
// The start method is called when at least one listener registers with the clock.
|
||||||
|
// When the clock is changed, listeners are unregistered from the clock and the stop method is called.
|
||||||
|
// Sometimes, the objects.get call above does not resolve before the stop method is called.
|
||||||
|
// So when we proceed with the clock subscription below, we first need to ensure that there is at least one listener for our clock.
|
||||||
|
if (this.eventNames().length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.openmct.time.on('timeSystem', this._timeSystemChange);
|
this.openmct.time.on('timeSystem', this._timeSystemChange);
|
||||||
this.timeTelemetryObject = domainObject;
|
this.timeTelemetryObject = domainObject;
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||||
|
@ -92,7 +92,7 @@ export default {
|
|||||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setViewFromTimeSystem);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setViewFromTimeSystem);
|
||||||
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeUnmount() {
|
||||||
clearInterval(this.resizeTimer);
|
clearInterval(this.resizeTimer);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -58,7 +58,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: function () {
|
data() {
|
||||||
const activeClock = this.getActiveClock();
|
const activeClock = this.getActiveClock();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -66,11 +66,11 @@ export default {
|
|||||||
clocks: []
|
clocks: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted() {
|
||||||
this.loadClocks(this.configuration.menuOptions);
|
this.loadClocks(this.configuration.menuOptions);
|
||||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
},
|
},
|
||||||
destroyed: function () {
|
unmounted() {
|
||||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -102,7 +102,7 @@ export default {
|
|||||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.updateTimeSystem);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.updateTimeSystem);
|
||||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.modeChanged, this.updateMode);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.modeChanged, this.updateMode);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeUnmount() {
|
||||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.boundsChanged, this.addTimespan);
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.boundsChanged, this.addTimespan);
|
||||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.addTimespan);
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.addTimespan);
|
||||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.updateTimeSystem);
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.updateTimeSystem);
|
||||||
|
@ -184,7 +184,7 @@ export default {
|
|||||||
this.$emit('popupLoaded');
|
this.$emit('popupLoaded');
|
||||||
this.setTimeContext();
|
this.setTimeContext();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeUnmount() {
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -75,7 +75,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeUnmount() {
|
||||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
@ -194,7 +194,7 @@ export default {
|
|||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
created() {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
@ -36,7 +36,7 @@ export default {
|
|||||||
this.timeConductorOptionsHolder = this.$el;
|
this.timeConductorOptionsHolder = this.$el;
|
||||||
this.timeConductorOptionsHolder.addEventListener('click', this.showPopup);
|
this.timeConductorOptionsHolder.addEventListener('click', this.showPopup);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeUnmount() {
|
||||||
this.clearPopup();
|
this.clearPopup();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -157,7 +157,7 @@ export default {
|
|||||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
||||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
|
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeUnmount() {
|
||||||
this.clearAllValidation();
|
this.clearAllValidation();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -173,7 +173,7 @@ export default {
|
|||||||
this.setOffsets();
|
this.setOffsets();
|
||||||
document.addEventListener('click', this.hide);
|
document.addEventListener('click', this.hide);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeUnmount() {
|
||||||
document.removeEventListener('click', this.hide);
|
document.removeEventListener('click', this.hide);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -43,9 +43,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import raf from 'utils/raf';
|
import raf from 'utils/raf';
|
||||||
|
import throttle from '../../../utils/throttle';
|
||||||
|
|
||||||
const moment = require('moment-timezone');
|
const moment = require('moment-timezone');
|
||||||
const momentDurationFormatSetup = require('moment-duration-format');
|
const momentDurationFormatSetup = require('moment-duration-format');
|
||||||
|
const refreshRateSeconds = 2;
|
||||||
|
|
||||||
momentDurationFormatSetup(moment);
|
momentDurationFormatSetup(moment);
|
||||||
|
|
||||||
@ -68,38 +70,21 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
relativeTimestamp() {
|
|
||||||
let relativeTimestamp;
|
|
||||||
if (this.configuration && this.configuration.timestamp) {
|
|
||||||
relativeTimestamp = moment(this.configuration.timestamp).toDate();
|
|
||||||
} else if (this.configuration && this.configuration.timestamp === undefined) {
|
|
||||||
relativeTimestamp = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return relativeTimestamp;
|
|
||||||
},
|
|
||||||
timeDelta() {
|
timeDelta() {
|
||||||
return this.lastTimestamp - this.relativeTimestamp;
|
if (this.configuration.pausedTime) {
|
||||||
|
return Date.parse(this.configuration.pausedTime) - this.startTimeMs;
|
||||||
|
} else {
|
||||||
|
return this.lastTimestamp - this.startTimeMs;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startTimeMs() {
|
||||||
|
return Date.parse(this.configuration.timestamp);
|
||||||
},
|
},
|
||||||
timeTextValue() {
|
timeTextValue() {
|
||||||
if (isNaN(this.timeDelta)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const toWholeSeconds = Math.abs(Math.floor(this.timeDelta / 1000) * 1000);
|
const toWholeSeconds = Math.abs(Math.floor(this.timeDelta / 1000) * 1000);
|
||||||
|
|
||||||
return moment.duration(toWholeSeconds, 'ms').format(this.format, { trim: false });
|
return moment.duration(toWholeSeconds, 'ms').format(this.format, { trim: false });
|
||||||
},
|
},
|
||||||
pausedTime() {
|
|
||||||
let pausedTime;
|
|
||||||
if (this.configuration && this.configuration.pausedTime) {
|
|
||||||
pausedTime = moment(this.configuration.pausedTime).toDate();
|
|
||||||
} else if (this.configuration && this.configuration.pausedTime === undefined) {
|
|
||||||
pausedTime = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pausedTime;
|
|
||||||
},
|
|
||||||
timerState() {
|
timerState() {
|
||||||
let timerState = 'started';
|
let timerState = 'started';
|
||||||
if (this.configuration && this.configuration.timerState) {
|
if (this.configuration && this.configuration.timerState) {
|
||||||
@ -179,13 +164,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.unobserve = this.openmct.objects.observe(
|
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
|
||||||
this.domainObject,
|
this.configuration = domainObject.configuration;
|
||||||
'configuration',
|
});
|
||||||
(configuration) => {
|
|
||||||
this.configuration = configuration;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (!this.configuration?.timerState) {
|
if (!this.configuration?.timerState) {
|
||||||
const timerAction = !this.relativeTimestamp ? 'stop' : 'start';
|
const timerAction = !this.relativeTimestamp ? 'stop' : 'start';
|
||||||
@ -193,6 +174,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.handleTick = raf(this.handleTick);
|
this.handleTick = raf(this.handleTick);
|
||||||
|
this.refreshTimerObject = throttle(this.refreshTimerObject, refreshRateSeconds * 1000);
|
||||||
this.openmct.time.on('tick', this.handleTick);
|
this.openmct.time.on('tick', this.handleTick);
|
||||||
|
|
||||||
this.viewActionsCollection = this.openmct.actions.getActionsCollection(
|
this.viewActionsCollection = this.openmct.actions.getActionsCollection(
|
||||||
@ -210,15 +192,11 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleTick() {
|
handleTick() {
|
||||||
const isTimerRunning = !['paused', 'stopped'].includes(this.timerState);
|
this.lastTimestamp = new Date(this.openmct.time.now());
|
||||||
|
this.refreshTimerObject();
|
||||||
if (isTimerRunning) {
|
},
|
||||||
this.lastTimestamp = new Date(this.openmct.time.now());
|
refreshTimerObject() {
|
||||||
}
|
this.openmct.objects.refresh(this.domainObject);
|
||||||
|
|
||||||
if (this.timerState === 'paused' && !this.lastTimestamp) {
|
|
||||||
this.lastTimestamp = this.pausedTime;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
restartTimer() {
|
restartTimer() {
|
||||||
this.triggerAction('timer.restart');
|
this.triggerAction('timer.restart');
|
||||||
|
@ -25,7 +25,7 @@ import UserIndicator from './components/UserIndicator.vue';
|
|||||||
|
|
||||||
export default function UserIndicatorPlugin() {
|
export default function UserIndicatorPlugin() {
|
||||||
function addIndicator(openmct) {
|
function addIndicator(openmct) {
|
||||||
const { vNode } = mount(
|
const { vNode, destroy } = mount(
|
||||||
{
|
{
|
||||||
components: {
|
components: {
|
||||||
UserIndicator
|
UserIndicator
|
||||||
@ -43,7 +43,8 @@ export default function UserIndicatorPlugin() {
|
|||||||
openmct.indicators.add({
|
openmct.indicators.add({
|
||||||
key: 'user-indicator',
|
key: 'user-indicator',
|
||||||
element: vNode.el,
|
element: vNode.el,
|
||||||
priority: openmct.priority.HIGH
|
priority: openmct.priority.HIGH,
|
||||||
|
destroy: destroy
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
this.nameChangeListeners = {};
|
||||||
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
|
||||||
if (keyString && this.keyString !== keyString) {
|
if (keyString && this.keyString !== keyString) {
|
||||||
@ -108,8 +109,16 @@ export default {
|
|||||||
// remove ROOT and object itself from path
|
// remove ROOT and object itself from path
|
||||||
this.orderedPath = pathWithDomainObject.slice(1, pathWithDomainObject.length - 1).reverse();
|
this.orderedPath = pathWithDomainObject.slice(1, pathWithDomainObject.length - 1).reverse();
|
||||||
}
|
}
|
||||||
|
this.orderedPath.forEach((pathObject) => {
|
||||||
|
this.addNameListenerFor(pathObject.domainObject);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
unmounted() {
|
||||||
|
Object.values(this.nameChangeListeners).forEach((unlisten) => {
|
||||||
|
unlisten();
|
||||||
|
});
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* Generate the hash url for the given object path, removing the '/ROOT' prefix if present.
|
* Generate the hash url for the given object path, removing the '/ROOT' prefix if present.
|
||||||
@ -120,6 +129,34 @@ export default {
|
|||||||
const path = `/browse/${this.openmct.objects.getRelativePath(objectPath)}`;
|
const path = `/browse/${this.openmct.objects.getRelativePath(objectPath)}`;
|
||||||
|
|
||||||
return path.replace('ROOT/', '');
|
return path.replace('ROOT/', '');
|
||||||
|
},
|
||||||
|
updateObjectPathName(keyString, newName) {
|
||||||
|
this.orderedPath = this.orderedPath.map((pathObject) => {
|
||||||
|
if (this.openmct.objects.makeKeyString(pathObject.domainObject.identifier) === keyString) {
|
||||||
|
return {
|
||||||
|
...pathObject,
|
||||||
|
domainObject: { ...pathObject.domainObject, name: newName }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return pathObject;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeNameListenerFor(domainObject) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
if (this.nameChangeListeners[keyString]) {
|
||||||
|
this.nameChangeListeners[keyString]();
|
||||||
|
delete this.nameChangeListeners[keyString];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addNameListenerFor(domainObject) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
if (!this.nameChangeListeners[keyString]) {
|
||||||
|
this.nameChangeListeners[keyString] = this.openmct.objects.observe(
|
||||||
|
domainObject,
|
||||||
|
'name',
|
||||||
|
this.updateObjectPathName.bind(this, keyString)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -104,12 +104,19 @@ export default {
|
|||||||
if (this.statusUnsubscribe) {
|
if (this.statusUnsubscribe) {
|
||||||
this.statusUnsubscribe();
|
this.statusUnsubscribe();
|
||||||
}
|
}
|
||||||
|
if (this.nameUnsubscribe) {
|
||||||
|
this.nameUnsubscribe();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateSelection(selection) {
|
updateSelection(selection) {
|
||||||
if (this.statusUnsubscribe) {
|
if (this.statusUnsubscribe) {
|
||||||
this.statusUnsubscribe();
|
this.statusUnsubscribe();
|
||||||
this.statusUnsubscribe = undefined;
|
this.statusUnsubscribe = null;
|
||||||
|
}
|
||||||
|
if (this.nameUnsubscribe) {
|
||||||
|
this.nameUnsubscribe();
|
||||||
|
this.nameUnsubscribe = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection.length === 0 || selection[0].length === 0) {
|
if (selection.length === 0 || selection[0].length === 0) {
|
||||||
@ -132,6 +139,11 @@ export default {
|
|||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
this.status = this.openmct.status.get(this.keyString);
|
this.status = this.openmct.status.get(this.keyString);
|
||||||
this.statusUnsubscribe = this.openmct.status.observe(this.keyString, this.updateStatus);
|
this.statusUnsubscribe = this.openmct.status.observe(this.keyString, this.updateStatus);
|
||||||
|
this.nameUnsubscribe = this.openmct.objects.observe(
|
||||||
|
this.domainObject,
|
||||||
|
'name',
|
||||||
|
this.updateName
|
||||||
|
);
|
||||||
} else if (selection[0][0].context.layoutItem) {
|
} else if (selection[0][0].context.layoutItem) {
|
||||||
this.layoutItem = selection[0][0].context.layoutItem;
|
this.layoutItem = selection[0][0].context.layoutItem;
|
||||||
}
|
}
|
||||||
@ -144,6 +156,9 @@ export default {
|
|||||||
},
|
},
|
||||||
updateStatus(status) {
|
updateStatus(status) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
},
|
||||||
|
updateName(newName) {
|
||||||
|
this.domainObject = { ...this.domainObject, name: newName };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -59,13 +59,47 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.compositionCollections = {};
|
this.compositionCollections = {};
|
||||||
|
this.nameChangeListeners = {};
|
||||||
this.openmct.router.on('change:path', this.onPathChange);
|
this.openmct.router.on('change:path', this.onPathChange);
|
||||||
this.getSavedRecentItems();
|
this.getSavedRecentItems();
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.openmct.router.off('change:path', this.onPathChange);
|
this.openmct.router.off('change:path', this.onPathChange);
|
||||||
|
Object.values(this.nameChangeListeners).forEach((unlisten) => {
|
||||||
|
unlisten();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
addNameListenerFor(domainObject) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
if (!this.nameChangeListeners[keyString]) {
|
||||||
|
this.nameChangeListeners[keyString] = this.openmct.objects.observe(
|
||||||
|
domainObject,
|
||||||
|
'name',
|
||||||
|
this.updateRecentObjectName.bind(this, keyString)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateRecentObjectName(keyString, newName) {
|
||||||
|
this.recents = this.recents.map((recentObject) => {
|
||||||
|
if (
|
||||||
|
this.openmct.objects.makeKeyString(recentObject.domainObject.identifier) === keyString
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...recentObject,
|
||||||
|
domainObject: { ...recentObject.domainObject, name: newName }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return recentObject;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeNameListenerFor(domainObject) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
if (this.nameChangeListeners[keyString]) {
|
||||||
|
this.nameChangeListeners[keyString]();
|
||||||
|
delete this.nameChangeListeners[keyString];
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Add a composition collection to the map and register its remove handler
|
* Add a composition collection to the map and register its remove handler
|
||||||
* @param {string} navigationPath
|
* @param {string} navigationPath
|
||||||
@ -112,6 +146,7 @@ export default {
|
|||||||
// Get composition collections and add composition listeners for composable objects
|
// Get composition collections and add composition listeners for composable objects
|
||||||
savedRecents.forEach((recentObject) => {
|
savedRecents.forEach((recentObject) => {
|
||||||
const { domainObject, navigationPath } = recentObject;
|
const { domainObject, navigationPath } = recentObject;
|
||||||
|
this.addNameListenerFor(domainObject);
|
||||||
if (this.shouldTrackCompositionFor(domainObject)) {
|
if (this.shouldTrackCompositionFor(domainObject)) {
|
||||||
this.compositionCollections[navigationPath] = {};
|
this.compositionCollections[navigationPath] = {};
|
||||||
this.compositionCollections[navigationPath].collection =
|
this.compositionCollections[navigationPath].collection =
|
||||||
@ -161,6 +196,8 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.addNameListenerFor(domainObject);
|
||||||
|
|
||||||
// Move the object to the top if its already existing in the recents list
|
// Move the object to the top if its already existing in the recents list
|
||||||
const existingIndex = this.recents.findIndex((recentObject) => {
|
const existingIndex = this.recents.findIndex((recentObject) => {
|
||||||
return navigationPath === recentObject.navigationPath;
|
return navigationPath === recentObject.navigationPath;
|
||||||
@ -179,6 +216,7 @@ export default {
|
|||||||
while (this.recents.length > MAX_RECENT_ITEMS) {
|
while (this.recents.length > MAX_RECENT_ITEMS) {
|
||||||
const poppedRecentItem = this.recents.pop();
|
const poppedRecentItem = this.recents.pop();
|
||||||
this.removeCompositionListenerFor(poppedRecentItem.navigationPath);
|
this.removeCompositionListenerFor(poppedRecentItem.navigationPath);
|
||||||
|
this.removeNameListenerFor(poppedRecentItem.domainObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setSavedRecentItems();
|
this.setSavedRecentItems();
|
||||||
@ -236,6 +274,9 @@ export default {
|
|||||||
label: 'OK',
|
label: 'OK',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
localStorage.removeItem(LOCAL_STORAGE_KEY__RECENT_OBJECTS);
|
localStorage.removeItem(LOCAL_STORAGE_KEY__RECENT_OBJECTS);
|
||||||
|
Object.values(this.nameChangeListeners).forEach((unlisten) => {
|
||||||
|
unlisten();
|
||||||
|
});
|
||||||
this.recents = [];
|
this.recents = [];
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
this.$emit('setClearButtonDisabled', true);
|
this.$emit('setClearButtonDisabled', true);
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
<div :style="childrenHeightStyles">
|
<div :style="childrenHeightStyles">
|
||||||
<tree-item
|
<tree-item
|
||||||
v-for="(treeItem, index) in visibleItems"
|
v-for="(treeItem, index) in visibleItems"
|
||||||
:key="`${treeItem.navigationPath}-${index}`"
|
:key="`${treeItem.navigationPath}-${index}-${treeItem.object.name}`"
|
||||||
:node="treeItem"
|
:node="treeItem"
|
||||||
:is-selector-tree="isSelectorTree"
|
:is-selector-tree="isSelectorTree"
|
||||||
:selected-item="selectedItem"
|
:selected-item="selectedItem"
|
||||||
|
@ -37,9 +37,13 @@ define([], function () {
|
|||||||
openmct.layout.$refs.browseBar.viewKey = viewProvider.key;
|
openmct.layout.$refs.browseBar.viewKey = viewProvider.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDocumentTitleOnNameMutation(domainObject) {
|
function updateDocumentTitleOnNameMutation(newName) {
|
||||||
if (typeof domainObject.name === 'string' && domainObject.name !== document.title) {
|
if (typeof newName === 'string' && newName !== document.title) {
|
||||||
document.title = domainObject.name;
|
document.title = newName;
|
||||||
|
openmct.layout.$refs.browseBar.domainObject = {
|
||||||
|
...openmct.layout.$refs.browseBar.domainObject,
|
||||||
|
name: newName
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +84,11 @@ define([], function () {
|
|||||||
let currentProvider = openmct.objectViews.getByProviderKey(currentViewKey);
|
let currentProvider = openmct.objectViews.getByProviderKey(currentViewKey);
|
||||||
document.title = browseObject.name; //change document title to current object in main view
|
document.title = browseObject.name; //change document title to current object in main view
|
||||||
// assign listener to global for later clearing
|
// assign listener to global for later clearing
|
||||||
unobserve = openmct.objects.observe(browseObject, '*', updateDocumentTitleOnNameMutation);
|
unobserve = openmct.objects.observe(
|
||||||
|
browseObject,
|
||||||
|
'name',
|
||||||
|
updateDocumentTitleOnNameMutation
|
||||||
|
);
|
||||||
|
|
||||||
if (currentProvider && currentProvider.canView(browseObject, openmct.router.path)) {
|
if (currentProvider && currentProvider.canView(browseObject, openmct.router.path)) {
|
||||||
viewObject(browseObject, currentProvider);
|
viewObject(browseObject, currentProvider);
|
||||||
|
34
src/utils/throttle.js
Normal file
34
src/utils/throttle.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Creates a throttled function that only invokes the provided function at most once every
|
||||||
|
* specified number of milliseconds. Subsequent calls within the waiting period will be ignored.
|
||||||
|
* @param {Function} func The function to throttle.
|
||||||
|
* @param {number} wait The number of milliseconds to wait between successive calls to the function.
|
||||||
|
* @return {Function} Returns the new throttled function.
|
||||||
|
*/
|
||||||
|
export default function throttle(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
let result;
|
||||||
|
let previous = 0;
|
||||||
|
|
||||||
|
return function (...args) {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
const remaining = wait - (now - previous);
|
||||||
|
|
||||||
|
if (remaining <= 0 || remaining > wait) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = now;
|
||||||
|
result = func(...args);
|
||||||
|
} else if (!timeout) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
previous = new Date().getTime();
|
||||||
|
timeout = null;
|
||||||
|
result = func(...args);
|
||||||
|
}, remaining);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user