Compare commits
52 Commits
fix-lad-co
...
fix-timeli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ccf333fc6 | ||
|
|
f3fc2c3471 | ||
|
|
2f715492e5 | ||
|
|
2f6a1840ac | ||
|
|
ef86e023e9 | ||
|
|
f8186e4b4e | ||
|
|
4e0c364d89 | ||
|
|
f3bed9c651 | ||
|
|
4d93907d58 | ||
|
|
6f656a6783 | ||
|
|
767fb6c5fd | ||
|
|
b0a0b4bb58 | ||
|
|
340f4a9e79 | ||
|
|
3007b28b0f | ||
|
|
20789601b4 | ||
|
|
a56cfed732 | ||
|
|
7ec2c4475b | ||
|
|
8f59b16465 | ||
|
|
36cfb1d515 | ||
|
|
2ff7132e90 | ||
|
|
d0ca398e01 | ||
|
|
59278e8a06 | ||
|
|
c8377f392b | ||
|
|
29df748f2b | ||
|
|
665ba6dae1 | ||
|
|
f39f8df4e2 | ||
|
|
4aa572d489 | ||
|
|
0b24c4f2c5 | ||
|
|
e4657f79cd | ||
|
|
f2059406e0 | ||
|
|
3e3dc7dd83 | ||
|
|
50742c4f82 | ||
|
|
2f04add2a3 | ||
|
|
0ce5060246 | ||
|
|
00353cdccf | ||
|
|
a1ac209d74 | ||
|
|
bdd8477b54 | ||
|
|
f690f36bfb | ||
|
|
e174f075df | ||
|
|
8cf12db104 | ||
|
|
453b1f3009 | ||
|
|
201c669328 | ||
|
|
1b7fb9b952 | ||
|
|
a3c5450205 | ||
|
|
8831b75c5d | ||
|
|
8fe0472af2 | ||
|
|
6cb5c47f3a | ||
|
|
eff0cc96b9 | ||
|
|
6ac7f24c63 | ||
|
|
39463c515f | ||
|
|
25c0dab346 | ||
|
|
3714958627 |
@@ -175,11 +175,11 @@ workflows:
|
||||
overall-circleci-commit-status: #These jobs run on every commit
|
||||
jobs:
|
||||
- lint:
|
||||
name: node14-lint
|
||||
node-version: lts/fermium
|
||||
name: node16-lint
|
||||
node-version: lts/gallium
|
||||
- unit-test:
|
||||
name: node18-chrome
|
||||
node-version: "18"
|
||||
node-version: lts/hydrogen
|
||||
- e2e-test:
|
||||
name: e2e-stable
|
||||
node-version: lts/gallium
|
||||
@@ -191,15 +191,12 @@ workflows:
|
||||
|
||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||
jobs:
|
||||
- unit-test:
|
||||
name: node14-chrome-nightly
|
||||
node-version: lts/fermium
|
||||
- unit-test:
|
||||
name: node16-chrome-nightly
|
||||
node-version: lts/gallium
|
||||
- unit-test:
|
||||
name: node18-chrome
|
||||
node-version: "18"
|
||||
node-version: lts/hydrogen
|
||||
- npm-audit:
|
||||
node-version: lts/gallium
|
||||
- e2e-test:
|
||||
|
||||
1
.github/workflows/pr-platform.yml
vendored
1
.github/workflows/pr-platform.yml
vendored
@@ -16,7 +16,6 @@ jobs:
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
node_version:
|
||||
- 14
|
||||
- 16
|
||||
- 18
|
||||
architecture:
|
||||
|
||||
@@ -52,10 +52,9 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
||||
|
||||
//Set object identifier from url
|
||||
conditionSetUrl = page.url();
|
||||
console.log('conditionSetUrl ' + conditionSetUrl);
|
||||
|
||||
getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0];
|
||||
console.debug('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl);
|
||||
console.debug(`getConditionSetIdentifierFromUrl: ${getConditionSetIdentifierFromUrl}`);
|
||||
await page.close();
|
||||
});
|
||||
|
||||
@@ -246,4 +245,81 @@ test.describe('Basic Condition Set Use', () => {
|
||||
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
|
||||
});
|
||||
test('ConditionSet should output blank instead of the default value', async ({ page }) => {
|
||||
//Navigate to baseURL
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
// Click the object specified by 'type'
|
||||
await page.click(`li[role='menuitem']:text("Sine Wave Generator")`);
|
||||
await page.getByRole('spinbutton', { name: 'Loading Delay (ms)' }).fill('8000');
|
||||
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
|
||||
await nameInput.fill("Delayed Sine Wave Generator");
|
||||
|
||||
// Click OK button and wait for Navigate event
|
||||
await Promise.all([
|
||||
page.waitForLoadState(),
|
||||
page.click('[aria-label="Save"]'),
|
||||
// Wait for Save Banner to appear
|
||||
page.waitForSelector('.c-message-banner__message')
|
||||
]);
|
||||
|
||||
// Create a new condition set
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Condition Set',
|
||||
name: "Test Blank Output of Condition Set"
|
||||
});
|
||||
// Change the object to edit mode
|
||||
await page.locator('[title="Edit"]').click();
|
||||
|
||||
// Click Add Condition button twice
|
||||
await page.locator('#addCondition').click();
|
||||
await page.locator('#addCondition').click();
|
||||
await page.locator('#conditionCollection').getByRole('textbox').nth(0).fill('First Condition');
|
||||
await page.locator('#conditionCollection').getByRole('textbox').nth(1).fill('Second Condition');
|
||||
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
|
||||
// Add the Sine Wave Generator to the Condition Set and save changes
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { name: "Delayed Sine Wave Generator"});
|
||||
const conditionCollection = await page.locator('#conditionCollection');
|
||||
|
||||
await sineWaveGeneratorTreeItem.dragTo(conditionCollection);
|
||||
|
||||
const firstCriterionTelemetry = await page.locator('[aria-label="Criterion Telemetry Selection"] >> nth=0');
|
||||
firstCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' });
|
||||
|
||||
const secondCriterionTelemetry = await page.locator('[aria-label="Criterion Telemetry Selection"] >> nth=1');
|
||||
secondCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' });
|
||||
|
||||
const firstCriterionMetadata = await page.locator('[aria-label="Criterion Metadata Selection"] >> nth=0');
|
||||
firstCriterionMetadata.selectOption({ label: 'Sine' });
|
||||
|
||||
const secondCriterionMetadata = await page.locator('[aria-label="Criterion Metadata Selection"] >> nth=1');
|
||||
secondCriterionMetadata.selectOption({ label: 'Sine' });
|
||||
|
||||
const firstCriterionComparison = await page.locator('[aria-label="Criterion Comparison Selection"] >> nth=0');
|
||||
firstCriterionComparison.selectOption({ label: 'is greater than or equal to' });
|
||||
|
||||
const secondCriterionComparison = await page.locator('[aria-label="Criterion Comparison Selection"] >> nth=1');
|
||||
secondCriterionComparison.selectOption({ label: 'is less than' });
|
||||
|
||||
const firstCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=0');
|
||||
await firstCriterionInput.fill("0");
|
||||
|
||||
const secondCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=1');
|
||||
await secondCriterionInput.fill("0");
|
||||
|
||||
const saveButtonLocator = page.locator('button[title="Save"]');
|
||||
await saveButtonLocator.click();
|
||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||
|
||||
const outputValue = await page.locator('[aria-label="Current Output Value"]');
|
||||
await expect(outputValue).toHaveText('---');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
const { test, expect } = require('../../../../pluginFixtures');
|
||||
const utils = require('../../../../helper/faultUtils');
|
||||
const { selectInspectorTab } = require('../../../../appActions');
|
||||
|
||||
test.describe('The Fault Management Plugin using example faults', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -38,6 +39,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
||||
test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector @unstable', async ({ page }) => {
|
||||
await utils.selectFaultItem(page, 1);
|
||||
|
||||
await selectInspectorTab(page, 'Fault Management Configuration');
|
||||
const selectedFaultName = await page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname').textContent();
|
||||
const inspectorFaultNameCount = await page.locator(`.c-inspector__properties >> :text("${selectedFaultName}")`).count();
|
||||
|
||||
@@ -52,6 +54,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
||||
const selectedRows = page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname');
|
||||
expect.soft(await selectedRows.count()).toEqual(2);
|
||||
|
||||
await selectInspectorTab(page, 'Fault Management Configuration');
|
||||
const firstSelectedFaultName = await selectedRows.nth(0).textContent();
|
||||
const secondSelectedFaultName = await selectedRows.nth(1).textContent();
|
||||
const firstNameInInspectorCount = await page.locator(`.c-inspector__properties >> :text("${firstSelectedFaultName}")`).count();
|
||||
|
||||
@@ -27,26 +27,29 @@ test.describe('Testing LAD table configuration', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
// Create Sine Wave Generator
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
name: "Test Sine Wave Generator"
|
||||
});
|
||||
|
||||
// Create LAD table
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
const ladTable = await createDomainObjectWithDefaults(page, {
|
||||
type: 'LAD Table',
|
||||
name: "Test LAD Table"
|
||||
});
|
||||
|
||||
// Create Sine Wave Generator
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
name: "Test Sine Wave Generator",
|
||||
parent: ladTable.uuid
|
||||
});
|
||||
|
||||
await page.goto(ladTable.url);
|
||||
});
|
||||
test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => {
|
||||
// Edit LAD table
|
||||
await page.locator('[title="Edit"]').click();
|
||||
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// Add the Sine Wave Generator to the LAD table and save changes
|
||||
await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
|
||||
// // Expand the 'My Items' folder in the left tree
|
||||
// await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// // Add the Sine Wave Generator to the LAD table and save changes
|
||||
// await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
|
||||
// select configuration tab in inspector
|
||||
await selectInspectorTab(page, 'LAD Table Configuration');
|
||||
|
||||
@@ -113,6 +116,24 @@ test.describe('Testing LAD table configuration', () => {
|
||||
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
|
||||
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('LAD Tables don\'t allow selection of rows but does show context click menus', async ({ page }) => {
|
||||
const cell = await page.locator('.js-first-data');
|
||||
const userSelectable = await cell.evaluate((el) => {
|
||||
return window.getComputedStyle(el).getPropertyValue('user-select');
|
||||
});
|
||||
|
||||
expect(userSelectable).toBe('none');
|
||||
// Right-click on the LAD table row
|
||||
await cell.click({
|
||||
button: 'right'
|
||||
});
|
||||
const menuOptions = page.locator('.c-menu ul');
|
||||
await expect.soft(menuOptions).toContainText('View Full Datum');
|
||||
await expect.soft(menuOptions).toContainText('View Historical Data');
|
||||
await expect.soft(menuOptions).toContainText('Remove');
|
||||
// await page.locator('li[title="Remove this object from its containing object."]').click();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Testing LAD table @unstable', () => {
|
||||
|
||||
@@ -377,4 +377,31 @@ test.describe('Notebook entry tests', () => {
|
||||
expect.soft(await sanitizedLink.count()).toBe(1);
|
||||
expect(await unsanitizedLink.count()).toBe(0);
|
||||
});
|
||||
test('can export notebook as text', async ({ page }) => {
|
||||
await nbUtils.enterTextEntry(page, `Foo bar entry`);
|
||||
// Click on 3 Dot Menu
|
||||
await page.locator('button[title="More options"]').click();
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
|
||||
await page.getByRole('menuitem', { name: /Export Notebook as Text/ }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
const download = await downloadPromise;
|
||||
const readStream = await download.createReadStream();
|
||||
const exportedText = await streamToString(readStream);
|
||||
expect(exportedText).toContain('Foo bar entry');
|
||||
});
|
||||
test.fixme('can export multiple notebook entries as text ', async ({ page }) => {});
|
||||
test.fixme('can export all notebook entry metdata', async ({ page }) => {});
|
||||
test.fixme('can export all notebook tags', async ({ page }) => {});
|
||||
test.fixme('can export all notebook snapshots', async ({ page }) => {});
|
||||
|
||||
async function streamToString(readable) {
|
||||
let result = '';
|
||||
for await (const chunk of readable) {
|
||||
result += chunk;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 19 KiB |
@@ -268,6 +268,9 @@ async function getCanvasPixelsWithData(page) {
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function assertLimitLinesExistAndAreVisible(page) {
|
||||
// Wait for plot series data to load
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
// Wait for limit lines to be created
|
||||
await page.waitForSelector('.js-limit-area', { state: 'attached' });
|
||||
const limitLineCount = await page.locator('.c-plot-limit-line').count();
|
||||
// There should be 10 limit lines created by default
|
||||
|
||||
@@ -115,6 +115,9 @@ test.describe('Stacked Plot', () => {
|
||||
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
|
||||
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
|
||||
|
||||
// collapse inspector
|
||||
await page.locator('.l-shell__pane-inspector .l-pane__collapse-button').click();
|
||||
|
||||
// Save (exit edit mode)
|
||||
await page.locator('button[title="Save"]').click();
|
||||
await page.locator('li[title="Save and Finish Editing"]').click();
|
||||
|
||||
@@ -28,6 +28,14 @@ const { test, expect } = require('../../../../pluginFixtures');
|
||||
const { createDomainObjectWithDefaults, setRealTimeMode, setFixedTimeMode } = require('../../../../appActions');
|
||||
|
||||
test.describe('Plot Tagging', () => {
|
||||
/**
|
||||
* 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, yEnd}) {
|
||||
await canvas.hover({trial: true});
|
||||
|
||||
@@ -64,12 +72,20 @@ test.describe('Plot Tagging', () => {
|
||||
await page.getByText('Science').click();
|
||||
}
|
||||
|
||||
async function testTelemetryItem(page, canvas, telemetryItem) {
|
||||
/**
|
||||
* 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});
|
||||
|
||||
@@ -85,19 +101,31 @@ test.describe('Plot Tagging', () => {
|
||||
await expect(page.getByText('Driving')).toBeHidden();
|
||||
}
|
||||
|
||||
async function basicTagsTests(page, canvas) {
|
||||
// Search for Science
|
||||
/**
|
||||
* 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();
|
||||
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");
|
||||
|
||||
// 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();
|
||||
|
||||
await expect(page.locator('[aria-label="Tags Inspector"]')).toContainText("Science");
|
||||
await expect(page.locator('[aria-label="Tags Inspector"]')).not.toContainText("Driving");
|
||||
// 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();
|
||||
@@ -109,12 +137,13 @@ test.describe('Plot Tagging', () => {
|
||||
page.reload(),
|
||||
page.waitForLoadState('networkidle')
|
||||
]);
|
||||
// wait for plot progress bar to disappear
|
||||
await page.locator('.l-view-section.c-progress-bar').waitFor({ state: 'detached' });
|
||||
// wait for plots to load
|
||||
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
|
||||
|
||||
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: {
|
||||
@@ -171,8 +200,23 @@ test.describe('Plot Tagging', () => {
|
||||
// changing to fixed time mode rebuilds canvas?
|
||||
canvas = page.locator('canvas').nth(1);
|
||||
|
||||
await basicTagsTests(page, canvas);
|
||||
await testTelemetryItem(page, canvas, alphaSineWave);
|
||||
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 }) => {
|
||||
@@ -187,7 +231,7 @@ test.describe('Plot Tagging', () => {
|
||||
xEnd: 700,
|
||||
yEnd: 480
|
||||
});
|
||||
await basicTagsTests(page, canvas);
|
||||
await basicTagsTests(page);
|
||||
});
|
||||
|
||||
test('Tags work with Stacked Plots', async ({ page }) => {
|
||||
@@ -217,7 +261,7 @@ test.describe('Plot Tagging', () => {
|
||||
xEnd: 700,
|
||||
yEnd: 215
|
||||
});
|
||||
await basicTagsTests(page, canvas);
|
||||
await testTelemetryItem(page, canvas, alphaSineWave);
|
||||
await basicTagsTests(page);
|
||||
await testTelemetryItem(page, alphaSineWave);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -191,7 +191,7 @@ test.describe('Recent Objects', () => {
|
||||
expect(await clockBreadcrumbs.count()).toBe(2);
|
||||
expect(await clockBreadcrumbs.nth(0).innerText()).not.toEqual(await clockBreadcrumbs.nth(1).innerText());
|
||||
});
|
||||
test("Enforces a limit of 20 recent objects", async ({ page }) => {
|
||||
test("Enforces a limit of 20 recent objects and clears the recent objects", async ({ page }) => {
|
||||
// Creating 21 objects takes a while, so increase the timeout
|
||||
test.slow();
|
||||
|
||||
@@ -242,6 +242,15 @@ test.describe('Recent Objects', () => {
|
||||
|
||||
// Assert that the Clock treeitem is no longer highlighted
|
||||
await expect(lastClockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
|
||||
|
||||
// Click the aria-label="Clear Recently Viewed" button
|
||||
await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
|
||||
|
||||
// Click on the "OK" button in the confirmation dialog
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
// Assert that the list is empty
|
||||
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
|
||||
});
|
||||
|
||||
function assertInitialRecentObjectsListState() {
|
||||
|
||||
@@ -63,7 +63,7 @@ test.describe('Grand Search', () => {
|
||||
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(`Clock A ${myItemsFolderName} Red Folder Blue Folder`);
|
||||
|
||||
// Click [aria-label="OpenMCT Search"] a >> nth=0
|
||||
await page.locator('[aria-label="OpenMCT Search"] a').first().click();
|
||||
await page.locator('[aria-label="Search Result"] >> nth=0').click();
|
||||
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
|
||||
|
||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||
|
||||
@@ -50,7 +50,6 @@ test.describe('Main Tree', () => {
|
||||
|
||||
await expandTreePaneItemByName(page, folder.name);
|
||||
await assertTreeItemIsVisible(page, clock.name);
|
||||
|
||||
});
|
||||
|
||||
test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @2p', async ({ page, openmctConfig }) => {
|
||||
|
||||
@@ -138,6 +138,7 @@ test.describe('Performance tests', () => {
|
||||
await page.evaluate(() => window.performance.mark("notebook-search-processed"));
|
||||
|
||||
//Clear Search
|
||||
await page.locator('.c-search.c-notebook__search .c-search__input').hover();
|
||||
await page.locator('.c-search.c-notebook__search .c-search__clear-input').click();
|
||||
await page.evaluate(() => window.performance.mark("notebook-search-processed"));
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
const { test } = require('../../../pluginFixtures');
|
||||
const { setBoundsToSpanAllActivities } = require('../../../helper/planningUtils');
|
||||
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions');
|
||||
const { test } = require('../../pluginFixtures');
|
||||
const { setBoundsToSpanAllActivities } = require('../../helper/planningUtils');
|
||||
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../appActions');
|
||||
const percySnapshot = require('@percy/playwright');
|
||||
const examplePlanLarge = require('../../../test-data/ExamplePlan_Large.json');
|
||||
const examplePlanLarge = require('../../test-data/examplePlans/ExamplePlan_Large.json');
|
||||
|
||||
test.describe('Visual - Planning', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -33,6 +33,8 @@ export default function (staticFaults = false) {
|
||||
return Promise.resolve(faultsData);
|
||||
},
|
||||
subscribe(domainObject, callback) {
|
||||
callback({ type: 'global-alarm-status' });
|
||||
|
||||
return () => {};
|
||||
},
|
||||
supportsRequest(domainObject) {
|
||||
|
||||
32
package.json
32
package.json
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "2.2.0-SNAPSHOT",
|
||||
"version": "2.2.1-SNAPSHOT",
|
||||
"description": "The Open MCT core platform",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.18.9",
|
||||
"@braintree/sanitize-url": "6.0.2",
|
||||
"@percy/cli": "1.17.0",
|
||||
"@percy/cli": "1.21.0",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.29.0",
|
||||
"@types/eventemitter3": "1.2.0",
|
||||
"@types/jasmine": "4.3.1",
|
||||
"@types/lodash": "4.14.191",
|
||||
"@types/lodash": "4.14.192",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
"codecov": "3.8.3",
|
||||
@@ -20,10 +20,10 @@
|
||||
"d3-axis": "3.0.0",
|
||||
"d3-scale": "3.3.0",
|
||||
"d3-selection": "3.0.0",
|
||||
"eslint": "8.36.0",
|
||||
"eslint-plugin-compat": "4.1.1",
|
||||
"eslint": "8.37.0",
|
||||
"eslint-plugin-compat": "4.1.2",
|
||||
"eslint-plugin-playwright": "0.12.0",
|
||||
"eslint-plugin-vue": "9.9.0",
|
||||
"eslint-plugin-vue": "9.10.0",
|
||||
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
|
||||
"eventemitter3": "1.2.0",
|
||||
"file-saver": "2.0.5",
|
||||
@@ -38,35 +38,35 @@
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
"karma-jasmine": "5.1.0",
|
||||
"karma-junit-reporter": "2.0.1",
|
||||
"karma-sourcemap-loader": "0.3.8",
|
||||
"karma-sourcemap-loader": "0.4.0",
|
||||
"karma-spec-reporter": "0.0.36",
|
||||
"karma-webpack": "5.0.0",
|
||||
"kdbush": "^3.0.0",
|
||||
"kdbush": "3.0.0",
|
||||
"location-bar": "3.0.1",
|
||||
"lodash": "4.17.21",
|
||||
"mini-css-extract-plugin": "2.7.2",
|
||||
"mini-css-extract-plugin": "2.7.5",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.3.2",
|
||||
"moment-timezone": "0.5.41",
|
||||
"nyc": "15.1.0",
|
||||
"painterro": "1.2.78",
|
||||
"playwright-core": "1.29.0",
|
||||
"plotly.js-basic-dist": "2.17.0",
|
||||
"plotly.js-gl2d-dist": "2.17.1",
|
||||
"plotly.js-basic-dist": "2.20.0",
|
||||
"plotly.js-gl2d-dist": "2.20.0",
|
||||
"printj": "1.3.1",
|
||||
"resolve-url-loader": "5.0.0",
|
||||
"sanitize-html": "2.10.0",
|
||||
"sass": "1.57.1",
|
||||
"sass-loader": "13.2.0",
|
||||
"sass": "1.59.3",
|
||||
"sass-loader": "13.2.2",
|
||||
"sinon": "15.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"style-loader": "3.3.2",
|
||||
"typescript": "4.9.5",
|
||||
"uuid": "9.0.0",
|
||||
"vue": "2.6.14",
|
||||
"vue-eslint-parser": "9.1.0",
|
||||
"vue-loader": "15.9.8",
|
||||
"vue-template-compiler": "2.6.14",
|
||||
"webpack": "5.74.0",
|
||||
"webpack": "5.77.0",
|
||||
"webpack-cli": "5.0.0",
|
||||
"webpack-dev-server": "4.11.1",
|
||||
"webpack-merge": "5.8.0"
|
||||
@@ -107,7 +107,7 @@
|
||||
"url": "https://github.com/nasa/openmct.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1"
|
||||
"node": ">=16.20.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"Firefox ESR",
|
||||
|
||||
@@ -21,18 +21,31 @@
|
||||
*****************************************************************************/
|
||||
|
||||
export default class FaultManagementAPI {
|
||||
/**
|
||||
* @param {import("openmct").OpenMCT} openmct
|
||||
*/
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} provider
|
||||
*/
|
||||
addProvider(provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
supportsActions() {
|
||||
return this.provider?.acknowledgeFault !== undefined && this.provider?.shelveFault !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../objects/ObjectAPI").DomainObject} domainObject
|
||||
* @returns {Promise.<FaultAPIResponse[]>}
|
||||
*/
|
||||
request(domainObject) {
|
||||
if (!this.provider?.supportsRequest(domainObject)) {
|
||||
return Promise.reject();
|
||||
@@ -41,6 +54,11 @@ export default class FaultManagementAPI {
|
||||
return this.provider.request(domainObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../objects/ObjectAPI").DomainObject} domainObject
|
||||
* @param {Function} callback
|
||||
* @returns {Function} unsubscribe
|
||||
*/
|
||||
subscribe(domainObject, callback) {
|
||||
if (!this.provider?.supportsSubscribe(domainObject)) {
|
||||
return Promise.reject();
|
||||
@@ -49,58 +67,55 @@ export default class FaultManagementAPI {
|
||||
return this.provider.subscribe(domainObject, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Fault} fault
|
||||
* @param {*} ackData
|
||||
*/
|
||||
acknowledgeFault(fault, ackData) {
|
||||
return this.provider.acknowledgeFault(fault, ackData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Fault} fault
|
||||
* @param {*} shelveData
|
||||
* @returns {Promise.<T>}
|
||||
*/
|
||||
shelveFault(fault, shelveData) {
|
||||
return this.provider.shelveFault(fault, shelveData);
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {object} Fault
|
||||
* @property {string} type
|
||||
* @property {object} fault
|
||||
* @property {boolean} fault.acknowledged
|
||||
* @property {object} fault.currentValueInfo
|
||||
* @property {number} fault.currentValueInfo.value
|
||||
* @property {string} fault.currentValueInfo.rangeCondition
|
||||
* @property {string} fault.currentValueInfo.monitoringResult
|
||||
* @property {string} fault.id
|
||||
* @property {string} fault.name
|
||||
* @property {string} fault.namespace
|
||||
* @property {number} fault.seqNum
|
||||
* @property {string} fault.severity
|
||||
* @property {boolean} fault.shelved
|
||||
* @property {string} fault.shortDescription
|
||||
* @property {string} fault.triggerTime
|
||||
* @property {object} fault.triggerValueInfo
|
||||
* @property {number} fault.triggerValueInfo.value
|
||||
* @property {string} fault.triggerValueInfo.rangeCondition
|
||||
* @property {string} fault.triggerValueInfo.monitoringResult
|
||||
* @example
|
||||
* {
|
||||
* "type": "",
|
||||
* "fault": {
|
||||
* "acknowledged": true,
|
||||
* "currentValueInfo": {
|
||||
* "value": 0,
|
||||
* "rangeCondition": "",
|
||||
* "monitoringResult": ""
|
||||
* },
|
||||
* "id": "",
|
||||
* "name": "",
|
||||
* "namespace": "",
|
||||
* "seqNum": 0,
|
||||
* "severity": "",
|
||||
* "shelved": true,
|
||||
* "shortDescription": "",
|
||||
* "triggerTime": "",
|
||||
* "triggerValueInfo": {
|
||||
* "value": 0,
|
||||
* "rangeCondition": "",
|
||||
* "monitoringResult": ""
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
/**
|
||||
* @typedef {object} TriggerValueInfo
|
||||
* @property {number} value
|
||||
* @property {string} rangeCondition
|
||||
* @property {string} monitoringResult
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} CurrentValueInfo
|
||||
* @property {number} value
|
||||
* @property {string} rangeCondition
|
||||
* @property {string} monitoringResult
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} Fault
|
||||
* @property {boolean} acknowledged
|
||||
* @property {CurrentValueInfo} currentValueInfo
|
||||
* @property {string} id
|
||||
* @property {string} name
|
||||
* @property {string} namespace
|
||||
* @property {number} seqNum
|
||||
* @property {string} severity
|
||||
* @property {boolean} shelved
|
||||
* @property {string} shortDescription
|
||||
* @property {string} triggerTime
|
||||
* @property {TriggerValueInfo} triggerValueInfo
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} FaultAPIResponse
|
||||
* @property {string} type
|
||||
* @property {Fault} fault
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideOptions && filteredOptions.length > 0"
|
||||
class="c-menu c-input--autocomplete__options"
|
||||
class="c-menu c-input--autocomplete__options js-autocomplete-options"
|
||||
aria-label="Autocomplete Options"
|
||||
@blur="hideOptions = true"
|
||||
>
|
||||
|
||||
@@ -415,7 +415,10 @@ export default class NotificationAPI extends EventEmitter {
|
||||
for (; i < this.notifications.length; i++) {
|
||||
notification = this.notifications[i];
|
||||
|
||||
if (!notification.model.minimized
|
||||
const isNotificationMinimized = notification.model.minimized
|
||||
|| notification?.model?.options?.minimized;
|
||||
|
||||
if (!isNotificationMinimized
|
||||
&& notification !== this.activeNotification) {
|
||||
return notification;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
}
|
||||
|
||||
.c-object-label__name {
|
||||
filter: $objectLabelNameFilter;
|
||||
color: $objectLabelNameColorFg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import DefaultMetadataProvider from './DefaultMetadataProvider';
|
||||
import objectUtils from 'objectUtils';
|
||||
|
||||
export default class TelemetryAPI {
|
||||
#isGreedyLAD;
|
||||
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
@@ -44,8 +45,8 @@ export default class TelemetryAPI {
|
||||
this.requestProviders = [];
|
||||
this.subscriptionProviders = [];
|
||||
this.valueFormatterCache = new WeakMap();
|
||||
|
||||
this.requestInterceptorRegistry = new TelemetryRequestInterceptorRegistry();
|
||||
this.#isGreedyLAD = true;
|
||||
}
|
||||
|
||||
abortAllRequests() {
|
||||
@@ -226,6 +227,31 @@ export default class TelemetryAPI {
|
||||
return modifiedRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set greedy LAD. For stategy "latest" telemetry in
|
||||
* realtime mode the start bound will be ignored if true and
|
||||
* there is no new data to replace the existing data.
|
||||
* defaults to true
|
||||
*
|
||||
* To turn off greedy LAD:
|
||||
* openmct.telemetry.greedyLAD(false);
|
||||
*
|
||||
* @method greedyLAD
|
||||
* @returns {boolean} if greedyLAD is active or not
|
||||
* @memberof module:openmct.TelemetryAPI#
|
||||
*/
|
||||
greedyLAD(isGreedy) {
|
||||
if (arguments.length > 0) {
|
||||
if (isGreedy !== true && isGreedy !== false) {
|
||||
throw new Error('Error setting greedyLAD. Greedy LAD only accepts true or false values');
|
||||
}
|
||||
|
||||
this.#isGreedyLAD = isGreedy;
|
||||
}
|
||||
|
||||
return this.#isGreedyLAD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request telemetry collection for a domain object.
|
||||
* The `options` argument allows you to specify filters
|
||||
|
||||
@@ -30,8 +30,8 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
/**
|
||||
* Creates a Telemetry Collection
|
||||
*
|
||||
* @param {object} openmct - Openm MCT
|
||||
* @param {object} domainObject - Domain Object to user for telemetry collection
|
||||
* @param {OpenMCT} openmct - Open MCT
|
||||
* @param {module:openmct.DomainObject} domainObject - Domain Object to use for telemetry collection
|
||||
* @param {object} options - Any options passed in for request/subscribe
|
||||
*/
|
||||
constructor(openmct, domainObject, options) {
|
||||
@@ -50,6 +50,7 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this.lastBounds = undefined;
|
||||
this.requestAbort = undefined;
|
||||
this.isStrategyLatest = this.options.strategy === 'latest';
|
||||
this.dataOutsideTimeBounds = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,9 +130,6 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this.emit('requestStarted');
|
||||
const modifiedOptions = await this.openmct.telemetry.applyRequestInterceptors(this.domainObject, options);
|
||||
historicalData = await historicalProvider.request(this.domainObject, modifiedOptions);
|
||||
if (!historicalData || !historicalData.length) {
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name !== 'AbortError') {
|
||||
console.error('Error requesting telemetry data...');
|
||||
@@ -142,6 +140,10 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this.emit('requestEnded');
|
||||
this.requestAbort = undefined;
|
||||
|
||||
if (!historicalData || !historicalData.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._processNewTelemetry(historicalData);
|
||||
|
||||
}
|
||||
@@ -184,6 +186,7 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
let afterEndOfBounds;
|
||||
let added = [];
|
||||
let addedIndices = [];
|
||||
let hasDataBeforeStartBound = false;
|
||||
|
||||
// loop through, sort and dedupe
|
||||
for (let datum of data) {
|
||||
@@ -191,7 +194,7 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
||||
afterEndOfBounds = parsedValue > this.lastBounds.end;
|
||||
|
||||
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
||||
if (!afterEndOfBounds && (!beforeStartOfBounds || (this.isStrategyLatest && this.openmct.telemetry.greedyLAD()))) {
|
||||
let isDuplicate = false;
|
||||
let startIndex = this._sortedIndex(datum);
|
||||
let endIndex = undefined;
|
||||
@@ -218,6 +221,10 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this.boundedTelemetry.splice(index, 0, datum);
|
||||
addedIndices.push(index);
|
||||
added.push(datum);
|
||||
|
||||
if (!hasDataBeforeStartBound && beforeStartOfBounds) {
|
||||
hasDataBeforeStartBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (afterEndOfBounds) {
|
||||
@@ -226,12 +233,18 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
}
|
||||
|
||||
if (added.length) {
|
||||
// if latest strategy is requested, we need to check if the value is the latest unmitted value
|
||||
// if latest strategy is requested, we need to check if the value is the latest unemitted value
|
||||
if (this.isStrategyLatest) {
|
||||
this.boundedTelemetry = [this.boundedTelemetry[this.boundedTelemetry.length - 1]];
|
||||
|
||||
// if true, then this value has yet to be emitted
|
||||
if (this.boundedTelemetry[0] !== latestBoundedDatum) {
|
||||
if (hasDataBeforeStartBound) {
|
||||
this._handleDataOutsideBounds();
|
||||
} else {
|
||||
this._handleDataInsideBounds();
|
||||
}
|
||||
|
||||
this.emit('add', this.boundedTelemetry);
|
||||
}
|
||||
} else {
|
||||
@@ -294,6 +307,17 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
let added = [];
|
||||
let testDatum = {};
|
||||
|
||||
if (endChanged) {
|
||||
testDatum[this.timeKey] = bounds.end;
|
||||
// Calculate the new index of the last item in bounds
|
||||
endIndex = _.sortedLastIndexBy(
|
||||
this.futureBuffer,
|
||||
testDatum,
|
||||
datum => this.parseTime(datum)
|
||||
);
|
||||
added = this.futureBuffer.splice(0, endIndex);
|
||||
}
|
||||
|
||||
if (startChanged) {
|
||||
testDatum[this.timeKey] = bounds.start;
|
||||
|
||||
@@ -307,20 +331,19 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
);
|
||||
discarded = this.boundedTelemetry.splice(0, startIndex);
|
||||
} else if (this.parseTime(testDatum) > this.parseTime(this.boundedTelemetry[0])) {
|
||||
discarded = this.boundedTelemetry;
|
||||
this.boundedTelemetry = [];
|
||||
}
|
||||
}
|
||||
// if greedyLAD is active and there is no new data to replace, don't discard
|
||||
const isGreedyLAD = this.openmct.telemetry.greedyLAD();
|
||||
const shouldRemove = (!isGreedyLAD || (isGreedyLAD && added.length > 0));
|
||||
|
||||
if (endChanged) {
|
||||
testDatum[this.timeKey] = bounds.end;
|
||||
// Calculate the new index of the last item in bounds
|
||||
endIndex = _.sortedLastIndexBy(
|
||||
this.futureBuffer,
|
||||
testDatum,
|
||||
datum => this.parseTime(datum)
|
||||
);
|
||||
added = this.futureBuffer.splice(0, endIndex);
|
||||
if (shouldRemove) {
|
||||
discarded = this.boundedTelemetry;
|
||||
this.boundedTelemetry = [];
|
||||
// since it IS strategy latest, we can assume there will be at least 1 datum
|
||||
// unless no data was returned in the first request, we need to account for that
|
||||
} else if (this.boundedTelemetry.length === 1) {
|
||||
this._handleDataOutsideBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (discarded.length > 0) {
|
||||
@@ -331,6 +354,8 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
if (!this.isStrategyLatest) {
|
||||
this.boundedTelemetry = [...this.boundedTelemetry, ...added];
|
||||
} else {
|
||||
this._handleDataInsideBounds();
|
||||
|
||||
added = [added[added.length - 1]];
|
||||
this.boundedTelemetry = added;
|
||||
}
|
||||
@@ -345,6 +370,20 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
|
||||
}
|
||||
|
||||
_handleDataInsideBounds() {
|
||||
if (this.dataOutsideTimeBounds) {
|
||||
this.dataOutsideTimeBounds = false;
|
||||
this.emit('dataInsideTimeBounds');
|
||||
}
|
||||
}
|
||||
|
||||
_handleDataOutsideBounds() {
|
||||
if (!this.dataOutsideTimeBounds) {
|
||||
this.dataOutsideTimeBounds = true;
|
||||
this.emit('dataOutsideTimeBounds');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* whenever the time system is updated need to update related values in
|
||||
* the Telemetry Collection and reset the telemetry collection
|
||||
|
||||
@@ -121,8 +121,9 @@ define([
|
||||
}
|
||||
|
||||
TelemetryValueFormatter.prototype.parse = function (datum) {
|
||||
const isDatumArray = Array.isArray(datum);
|
||||
if (_.isObject(datum)) {
|
||||
const objectDatum = datum[this.valueMetadata.source];
|
||||
const objectDatum = isDatumArray ? datum : datum[this.valueMetadata.source];
|
||||
if (Array.isArray(objectDatum)) {
|
||||
return objectDatum.map((item) => {
|
||||
return this.formatter.parse(item);
|
||||
@@ -136,8 +137,9 @@ define([
|
||||
};
|
||||
|
||||
TelemetryValueFormatter.prototype.format = function (datum) {
|
||||
const isDatumArray = Array.isArray(datum);
|
||||
if (_.isObject(datum)) {
|
||||
const objectDatum = datum[this.valueMetadata.source];
|
||||
const objectDatum = isDatumArray ? datum : datum[this.valueMetadata.source];
|
||||
if (Array.isArray(objectDatum)) {
|
||||
return objectDatum.map((item) => {
|
||||
return this.formatter.format(item);
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
const expandColumns = {
|
||||
name: 'Expand Columns',
|
||||
key: 'expand-columns',
|
||||
key: 'lad-expand-columns',
|
||||
description: "Increase column widths to fit currently available data.",
|
||||
cssClass: 'icon-arrows-right-left labeled',
|
||||
invoke: (objectPath, view) => {
|
||||
@@ -34,7 +34,7 @@ const expandColumns = {
|
||||
|
||||
const autosizeColumns = {
|
||||
name: 'Autosize Columns',
|
||||
key: 'autosize-columns',
|
||||
key: 'lad-autosize-columns',
|
||||
description: "Automatically size columns to fit the table into the available space.",
|
||||
cssClass: 'icon-expand labeled',
|
||||
invoke: (objectPath, view) => {
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Vue from 'vue';
|
||||
import LadRow from './LADRow.vue';
|
||||
import StalenessUtils from '@/utils/staleness';
|
||||
|
||||
@@ -115,7 +115,23 @@ export default {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
watch: {
|
||||
configuration: {
|
||||
handler(newVal) {
|
||||
if (this.viewActionsCollection) {
|
||||
if (newVal.isFixedLayout) {
|
||||
this.viewActionsCollection.show(['lad-expand-columns']);
|
||||
this.viewActionsCollection.hide(['lad-autosize-columns']);
|
||||
} else {
|
||||
this.viewActionsCollection.show(['lad-autosize-columns']);
|
||||
this.viewActionsCollection.hide(['lad-expand-columns']);
|
||||
}
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.ladTableConfiguration.on('change', this.handleConfigurationChange);
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.composition.on('add', this.addItem);
|
||||
@@ -123,6 +139,9 @@ export default {
|
||||
this.composition.on('reorder', this.reorder);
|
||||
this.composition.load();
|
||||
this.stalenessSubscription = {};
|
||||
await Vue.nextTick();
|
||||
this.viewActionsCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
|
||||
this.initializeViewActions();
|
||||
},
|
||||
destroyed() {
|
||||
this.ladTableConfiguration.off('change', this.handleConfigurationChange);
|
||||
@@ -208,6 +227,16 @@ export default {
|
||||
},
|
||||
toggleFixedLayout() {
|
||||
this.configuration.isFixedLayout = !this.configuration.isFixedLayout;
|
||||
},
|
||||
initializeViewActions() {
|
||||
if (this.configuration.isFixedLayout) {
|
||||
this.viewActionsCollection.show(['lad-expand-columns']);
|
||||
this.viewActionsCollection.hide(['lad-autosize-columns']);
|
||||
|
||||
} else {
|
||||
this.viewActionsCollection.hide(['lad-expand-columns']);
|
||||
this.viewActionsCollection.show(['lad-autosize-columns']);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,6 +93,11 @@ export default class ConditionManager extends EventEmitter {
|
||||
);
|
||||
this.updateConditionResults({id: id});
|
||||
this.updateCurrentCondition(latestTimestamp);
|
||||
|
||||
if (Object.keys(this.telemetryObjects).length === 0) {
|
||||
// no telemetry objects
|
||||
this.emit('noTelemetryObjects');
|
||||
}
|
||||
}
|
||||
|
||||
initialize() {
|
||||
@@ -102,6 +107,11 @@ export default class ConditionManager extends EventEmitter {
|
||||
this.initCondition(conditionConfiguration, index);
|
||||
});
|
||||
}
|
||||
|
||||
if (Object.keys(this.telemetryObjects).length === 0) {
|
||||
// no telemetry objects
|
||||
this.emit('noTelemetryObjects');
|
||||
}
|
||||
}
|
||||
|
||||
updateConditionTelemetryObjects() {
|
||||
|
||||
@@ -35,7 +35,9 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
}
|
||||
|
||||
if (styleConfiguration) {
|
||||
this.initialize(styleConfiguration);
|
||||
// We don't set the selectedConditionId here because we want condition set computation to happen before we apply any selected style
|
||||
const styleConfigurationWithNoSelection = Object.assign(styleConfiguration, {selectedConditionId: ''});
|
||||
this.initialize(styleConfigurationWithNoSelection);
|
||||
if (styleConfiguration.conditionSetIdentifier) {
|
||||
this.openmct.time.on("bounds", this.refreshData);
|
||||
this.subscribeToConditionSet();
|
||||
@@ -57,14 +59,18 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
this.applySelectedConditionStyle();
|
||||
}
|
||||
} else if (this.conditionSetIdentifier) {
|
||||
//reset the selected style and let the condition set output determine what it should be
|
||||
this.selectedConditionId = undefined;
|
||||
this.currentStyle = undefined;
|
||||
this.updateDomainObjectStyle();
|
||||
this.subscribeToConditionSet();
|
||||
}
|
||||
}
|
||||
|
||||
initialize(styleConfiguration) {
|
||||
this.conditionSetIdentifier = styleConfiguration.conditionSetIdentifier;
|
||||
this.staticStyle = styleConfiguration.staticStyle;
|
||||
this.selectedConditionId = styleConfiguration.selectedConditionId;
|
||||
this.staticStyle = styleConfiguration.staticStyle;
|
||||
this.defaultConditionId = styleConfiguration.defaultConditionId;
|
||||
this.updateConditionStylesMap(styleConfiguration.styles || []);
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
<span class="c-cdef__controls">
|
||||
<select
|
||||
v-model="condition.configuration.trigger"
|
||||
aria-label="Condition Trigger"
|
||||
@change="persist"
|
||||
>
|
||||
<option
|
||||
|
||||
@@ -114,15 +114,11 @@ export default {
|
||||
telemetryObjs: [],
|
||||
moveIndex: undefined,
|
||||
isDragging: false,
|
||||
defaultOutput: undefined,
|
||||
dragCounter: 0,
|
||||
currentConditionId: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
defaultOutput(newOutput, oldOutput) {
|
||||
this.$emit('updateDefaultOutput', newOutput);
|
||||
},
|
||||
testData: {
|
||||
handler() {
|
||||
this.updateTestData();
|
||||
@@ -158,7 +154,7 @@ export default {
|
||||
this.observeForChanges();
|
||||
this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
|
||||
this.conditionManager.on('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
|
||||
this.updateDefaultCondition();
|
||||
this.conditionManager.on('noTelemetryObjects', this.emitNoTelemetryObjectEvent);
|
||||
this.stalenessSubscription = {};
|
||||
},
|
||||
methods: {
|
||||
@@ -166,18 +162,16 @@ export default {
|
||||
this.currentConditionId = data.conditionId;
|
||||
this.$emit('conditionSetResultUpdated', data);
|
||||
},
|
||||
emitNoTelemetryObjectEvent(data) {
|
||||
this.currentConditionId = '';
|
||||
this.$emit('noTelemetryObjects');
|
||||
},
|
||||
observeForChanges() {
|
||||
this.stopObservingForChanges = this.openmct.objects.observe(this.domainObject, 'configuration.conditionCollection', (newConditionCollection) => {
|
||||
//this forces children to re-render
|
||||
this.conditionCollection = newConditionCollection.map(condition => condition);
|
||||
this.updateDefaultCondition();
|
||||
});
|
||||
},
|
||||
updateDefaultCondition() {
|
||||
const defaultCondition = this.domainObject.configuration.conditionCollection
|
||||
.find(conditionConfiguration => conditionConfiguration.isDefault);
|
||||
this.defaultOutput = defaultCondition.configuration.output;
|
||||
},
|
||||
setMoveIndex(index) {
|
||||
this.moveIndex = index;
|
||||
this.isDragging = true;
|
||||
|
||||
@@ -28,12 +28,15 @@
|
||||
<section class="c-cs__current-output c-section">
|
||||
<div class="c-cs__content c-cs__current-output-value">
|
||||
<span class="c-cs__current-output-value__label">Current Output</span>
|
||||
<span class="c-cs__current-output-value__value">
|
||||
<span
|
||||
class="c-cs__current-output-value__value"
|
||||
aria-label="Current Output Value"
|
||||
>
|
||||
<template v-if="currentConditionOutput">
|
||||
{{ currentConditionOutput }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ defaultConditionOutput }}
|
||||
---
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
@@ -51,7 +54,7 @@
|
||||
:is-editing="isEditing"
|
||||
:test-data="testData"
|
||||
@conditionSetResultUpdated="updateCurrentOutput"
|
||||
@updateDefaultOutput="updateDefaultOutput"
|
||||
@noTelemetryObjects="updateCurrentOutput('---')"
|
||||
@telemetryUpdated="updateTelemetry"
|
||||
@telemetryStaleness="handleStaleness"
|
||||
/>
|
||||
@@ -75,7 +78,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
currentConditionOutput: '',
|
||||
defaultConditionOutput: '',
|
||||
telemetryObjs: [],
|
||||
testData: {},
|
||||
staleObjects: []
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<select
|
||||
ref="telemetrySelect"
|
||||
v-model="criterion.telemetry"
|
||||
aria-label="Criterion Telemetry Selection"
|
||||
@change="updateMetadataOptions"
|
||||
>
|
||||
<option value="">- Select Telemetry -</option>
|
||||
@@ -50,6 +51,7 @@
|
||||
<select
|
||||
ref="metadataSelect"
|
||||
v-model="criterion.metadata"
|
||||
aria-label="Criterion Metadata Selection"
|
||||
@change="updateOperations"
|
||||
>
|
||||
<option value="">- Select Field -</option>
|
||||
@@ -69,6 +71,7 @@
|
||||
>
|
||||
<select
|
||||
v-model="criterion.operation"
|
||||
aria-label="Criterion Comparison Selection"
|
||||
@change="updateInputVisibilityAndValues"
|
||||
>
|
||||
<option value="">- Select Comparison -</option>
|
||||
@@ -89,6 +92,7 @@
|
||||
<input
|
||||
v-model="criterion.input[inputIndex]"
|
||||
class="c-cdef__control__input"
|
||||
aria-label="Criterion Input"
|
||||
:type="setInputType"
|
||||
@change="persist"
|
||||
>
|
||||
@@ -103,6 +107,7 @@
|
||||
>
|
||||
<select
|
||||
v-model="criterion.input[0]"
|
||||
aria-label="Criterion Else Selection"
|
||||
@change="persist"
|
||||
>
|
||||
<option
|
||||
|
||||
@@ -307,6 +307,8 @@ export default {
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
} else {
|
||||
//reset the selectedConditionID so that the condition set computation can drive it.
|
||||
this.applySelectedConditionStyle('');
|
||||
this.subscribeToConditionSet();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -80,11 +80,9 @@
|
||||
.is-editing & {
|
||||
cursor: pointer;
|
||||
pointer-events: initial;
|
||||
transition: $transOut;
|
||||
|
||||
&:hover {
|
||||
background: rgba($colorBodyFg, 0.1);
|
||||
transition: $transIn;
|
||||
}
|
||||
|
||||
&.is-current {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
:is="urlDefined ? 'a' : 'span'"
|
||||
class="c-condition-widget u-style-receiver js-style-receiver"
|
||||
:href="url"
|
||||
:target="url ? '_BLANK' : ''"
|
||||
>
|
||||
<div class="c-condition-widget__label">
|
||||
{{ label }}
|
||||
|
||||
@@ -72,7 +72,7 @@ export default {
|
||||
this.isEditing = isEditing;
|
||||
},
|
||||
formatTelemetry(event) {
|
||||
let newFormat = event.currentTarget.value;
|
||||
const newFormat = event.currentTarget.value;
|
||||
this.openmct.selection.get().forEach(selectionPath => {
|
||||
selectionPath[0].context.updateTelemetryFormat(newFormat);
|
||||
});
|
||||
|
||||
@@ -193,7 +193,7 @@ export default {
|
||||
},
|
||||
telemetryValue() {
|
||||
if (!this.datum) {
|
||||
return;
|
||||
return '---';
|
||||
}
|
||||
|
||||
return this.formatter && this.formatter.format(this.datum);
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
*[s-selected-parent] {
|
||||
> .l-layout {
|
||||
// When main shell layout is the parent
|
||||
@include displayMarquee(deeppink);
|
||||
@include displayMarquee(deeppink); // TEMP
|
||||
}
|
||||
> * > * > * {
|
||||
// When a sub-layout is the parent
|
||||
|
||||
@@ -45,18 +45,15 @@
|
||||
|
||||
// Has-complex-content objects
|
||||
.c-so-view.has-complex-content {
|
||||
transition: $transOut;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
@include transition($prop: transform, $dur: $transOutTime, $delay: $moveBarOutDelay);
|
||||
|
||||
> .c-so-view__local-controls {
|
||||
transition: transform 250ms ease-in-out;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
@include transition($prop: transform, $dur: 250ms, $delay: $moveBarOutDelay);
|
||||
}
|
||||
|
||||
+ .c-frame__move-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.l-layout {
|
||||
@@ -65,13 +62,11 @@
|
||||
> .l-layout__frame {
|
||||
> .c-so-view.has-complex-content {
|
||||
> .c-so-view__local-controls {
|
||||
transition: transform $transOutTime ease-in-out;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
@include transition($prop: transform, $dur: $transOutTime, $delay: $moveBarOutDelay);
|
||||
}
|
||||
|
||||
+ .c-frame__move-bar {
|
||||
transition: $transOut;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
@include transition($prop: height, $delay: $moveBarOutDelay);
|
||||
@include userSelectNone();
|
||||
background: $editFrameMovebarColorBg;
|
||||
box-shadow: rgba(black, 0.3) 0 2px;
|
||||
@@ -103,18 +98,17 @@
|
||||
|
||||
&:hover {
|
||||
> .c-so-view.has-complex-content {
|
||||
transition: $transIn;
|
||||
transition: $transInTransform;
|
||||
transition-delay: 0s;
|
||||
|
||||
> .c-so-view__local-controls {
|
||||
transform: translateY($editFrameMovebarH);
|
||||
transition: transform $transInTime ease-in-out;
|
||||
@include transition(height, $transOutTime);
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
+ .c-frame__move-bar {
|
||||
transition: $transIn;
|
||||
transition-delay: 0s;
|
||||
@include transition(height);
|
||||
height: $editFrameMovebarH;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,6 @@ export default {
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.updateFaultList();
|
||||
|
||||
this.unsubscribe = this.openmct.faults
|
||||
.subscribe(this.domainObject, this.updateFault);
|
||||
},
|
||||
@@ -68,7 +66,11 @@ export default {
|
||||
this.openmct.faults
|
||||
.request(this.domainObject)
|
||||
.then(faultsData => {
|
||||
this.faultsList = faultsData.map(fd => fd.fault);
|
||||
if (faultsData?.length > 0) {
|
||||
this.faultsList = faultsData.map(fd => fd.fault);
|
||||
} else {
|
||||
this.faultsList = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,11 +244,10 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 0 ($margin * 2) + $size;
|
||||
transition: $transOut;
|
||||
|
||||
&:before {
|
||||
// The visible resize line
|
||||
background: $editUIColor;
|
||||
background-color: $editUIColor;
|
||||
content: '';
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
@@ -270,10 +269,9 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition: $transOut;
|
||||
&:before {
|
||||
// The visible resize line
|
||||
background: $editUIColorHov;
|
||||
background-color: $editUIColorHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
.c-grid-item {
|
||||
// Mobile-first
|
||||
@include button($bg: $colorItemBg, $fg: $colorItemFg);
|
||||
@include cControlHov();
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: $interiorMarginLg;
|
||||
@@ -142,15 +143,10 @@
|
||||
body.desktop & {
|
||||
$transOutMs: 300ms;
|
||||
flex-flow: column nowrap;
|
||||
transition: $transOutMs ease-in-out;
|
||||
|
||||
&:hover {
|
||||
filter: $filterItemHoverFg;
|
||||
transition: $transIn;
|
||||
|
||||
.c-grid-item__type-icon {
|
||||
transform: scale(1.1);
|
||||
transition: $transInBounce;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +167,6 @@
|
||||
font-size: floor(math.div($gridItemDesk, 3));
|
||||
margin: $interiorMargin 22.5% $interiorMargin * 3 22.5%;
|
||||
order: 2;
|
||||
transform-origin: center;
|
||||
transition: all $transOutMs ease-in-out;
|
||||
}
|
||||
|
||||
&__details {
|
||||
|
||||
@@ -322,7 +322,7 @@ export default {
|
||||
rgba(125,125,125,.2) 8px
|
||||
)`
|
||||
) : ''}`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${this.imageTranslateY / 2}px)`,
|
||||
transition: `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`,
|
||||
width: `${this.sizedImageWidth}px`,
|
||||
height: `${this.sizedImageHeight}px`
|
||||
@@ -709,7 +709,7 @@ export default {
|
||||
getVisibleLayerStyles(layer) {
|
||||
return {
|
||||
backgroundImage: `url(${layer.source})`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
|
||||
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${this.imageTranslateY / 2}px)`,
|
||||
transition: `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`
|
||||
};
|
||||
},
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: $interiorMarginSm;
|
||||
&.animate-scroll {
|
||||
scroll-behavior: smooth;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
left: $interiorMargin; top: $interiorMargin;
|
||||
z-index: 70;
|
||||
z-index: 10;
|
||||
background: $colorLocalControlOvrBg;
|
||||
border-radius: $basicCr;
|
||||
align-items: center;
|
||||
@@ -495,7 +495,6 @@
|
||||
&:hover {
|
||||
z-index: 2;
|
||||
|
||||
filter: brightness(1) contrast(1) !important;
|
||||
[class*='__image-handle'] {
|
||||
background-color: $colorBodyFg;
|
||||
}
|
||||
@@ -519,9 +518,4 @@
|
||||
display: block;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
&:hover div.c-imagery-tsv__image-wrapper {
|
||||
// TODO CH: convert to theme constants
|
||||
filter: brightness(0.5) contrast(0.7);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,17 @@
|
||||
import Annotations from './AnnotationsInspectorView.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function ElementsViewProvider(openmct) {
|
||||
export default function AnnotationsViewProvider(openmct) {
|
||||
return {
|
||||
key: 'annotationsView',
|
||||
name: 'Annotations',
|
||||
canView: function (selection) {
|
||||
const availableTags = openmct.annotation.getAvailableTags();
|
||||
|
||||
if (availableTags.length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selection.length;
|
||||
},
|
||||
view: function (selection) {
|
||||
|
||||
@@ -177,10 +177,9 @@ export default {
|
||||
if (this.$refs.TagEditor) {
|
||||
const clickedInsideTagEditor = this.$refs.TagEditor.contains(event.target);
|
||||
if (!clickedInsideTagEditor) {
|
||||
// Remove last tag when user clicks outside of TagSelection
|
||||
this.addedTags.pop();
|
||||
// Hide TagSelection and show "Add Tag" button
|
||||
this.userAddingTag = false;
|
||||
this.tagsChanged();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,6 +13,24 @@
|
||||
|
||||
/******************************* TAGS */
|
||||
.c-tag {
|
||||
/* merge conflict in 5247
|
||||
border-radius: 10px; //TODO: convert to theme constant
|
||||
display: inline-flex;
|
||||
padding: 1px 10px; //TODO: convert to theme constant
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
&__remove-btn {
|
||||
color: inherit !important;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
padding: 1px !important;
|
||||
@include transition(opacity);
|
||||
width: 0;
|
||||
*/
|
||||
border-radius: $tagBorderRadius;
|
||||
display: inline-flex;
|
||||
overflow: hidden;
|
||||
@@ -28,15 +46,15 @@
|
||||
transition: $transIn;
|
||||
width: 0;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* SEARCH RESULTS */
|
||||
&.--is-not-search-match {
|
||||
opacity: 0.5;
|
||||
}
|
||||
/* SEARCH RESULTS */
|
||||
&.--is-not-search-match {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.c-tag-holder {
|
||||
@@ -51,6 +69,31 @@
|
||||
|
||||
/******************************* TAGS IN INSPECTOR / TAG SELECTION & APPLICATION */
|
||||
.c-tag-applier {
|
||||
/* merge conflict in fix-repaint-5247
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
&__add-btn {
|
||||
&:before {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
.c-tag {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-right: 3px !important;
|
||||
|
||||
&__remove-btn {
|
||||
display: block;
|
||||
}
|
||||
*/
|
||||
$tagApplierPadding: 3px 6px;
|
||||
@include tagHolder;
|
||||
grid-column: 1 / 3;
|
||||
@@ -81,7 +124,6 @@
|
||||
min-height: auto !important;
|
||||
padding: $tagApplierPadding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-tag-btn__label {
|
||||
@@ -90,6 +132,21 @@
|
||||
|
||||
/******************************* HOVERS */
|
||||
.has-tag-applier {
|
||||
/* merge conflict in fix-repaint-5247
|
||||
$p: opacity, width;
|
||||
// Apply this class to all components that should trigger tag removal btn on hover
|
||||
.c-tag__remove-btn {
|
||||
@include transition($prop: $p, $dur: $transOutTime);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.c-tag__remove-btn {
|
||||
width: 1.1em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
// Apply this class to all components that should trigger tag removal btn on hover
|
||||
&:hover {
|
||||
.c-tag {
|
||||
@@ -120,3 +177,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,13 @@
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__item {
|
||||
$m: 1px;
|
||||
cursor: pointer;
|
||||
margin: 0 $interiorMarginSm $interiorMarginSm 0;
|
||||
margin: 0 $m $m 0;
|
||||
|
||||
.c-object-label {
|
||||
padding: 0;
|
||||
transition: $transOut;
|
||||
border-radius: $smallCr;
|
||||
padding: 2px 3px;
|
||||
|
||||
&__type-icon {
|
||||
width: auto;
|
||||
@@ -39,9 +40,8 @@
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition: $transIn;
|
||||
filter: $filterHov;
|
||||
@include hover() {
|
||||
background: $colorItemTreeHoverBg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class MoveAction {
|
||||
}
|
||||
|
||||
navigateTo(objectPath) {
|
||||
let urlPath = objectPath.reverse()
|
||||
const urlPath = objectPath.reverse()
|
||||
.map(object => this.openmct.objects.makeKeyString(object.identifier))
|
||||
.join("/");
|
||||
|
||||
@@ -53,8 +53,8 @@ export default class MoveAction {
|
||||
}
|
||||
|
||||
addToNewParent(child, newParent) {
|
||||
let newParentKeyString = this.openmct.objects.makeKeyString(newParent.identifier);
|
||||
let compositionCollection = this.openmct.composition.get(newParent);
|
||||
const newParentKeyString = this.openmct.objects.makeKeyString(newParent.identifier);
|
||||
const compositionCollection = this.openmct.composition.get(newParent);
|
||||
|
||||
this.openmct.objects.mutate(child, 'location', newParentKeyString);
|
||||
compositionCollection.add(child);
|
||||
@@ -63,11 +63,7 @@ export default class MoveAction {
|
||||
async onSave(changes) {
|
||||
this.startTransaction();
|
||||
|
||||
let inNavigationPath = this.inNavigationPath(this.object);
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
const inNavigationPath = this.inNavigationPath(this.object);
|
||||
const parentDomainObjectpath = changes.location || [this.parent];
|
||||
const parent = parentDomainObjectpath[0];
|
||||
|
||||
@@ -91,12 +87,15 @@ export default class MoveAction {
|
||||
}
|
||||
|
||||
let newObjectPath;
|
||||
|
||||
if (parentDomainObjectpath) {
|
||||
newObjectPath = parentDomainObjectpath && [this.object].concat(parentDomainObjectpath);
|
||||
} else {
|
||||
const root = await this.openmct.objects.getRoot();
|
||||
const rootCompositionCollection = this.openmct.composition.get(root);
|
||||
const rootComposition = await rootCompositionCollection.load();
|
||||
const rootChildCount = rootComposition.length;
|
||||
newObjectPath = await this.openmct.objects.getOriginalPath(this.object.identifier);
|
||||
let root = await this.openmct.objects.getRoot();
|
||||
let rootChildCount = root.composition.length;
|
||||
|
||||
// if not multiple root children, remove root from path
|
||||
if (rootChildCount < 2) {
|
||||
@@ -108,8 +107,7 @@ export default class MoveAction {
|
||||
}
|
||||
|
||||
removeFromOldParent(child) {
|
||||
let compositionCollection = this.openmct.composition.get(this.oldParent);
|
||||
|
||||
const compositionCollection = this.openmct.composition.get(this.oldParent);
|
||||
compositionCollection.remove(child);
|
||||
}
|
||||
|
||||
@@ -166,9 +164,9 @@ export default class MoveAction {
|
||||
return false;
|
||||
}
|
||||
|
||||
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
|
||||
|
||||
const objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
|
||||
const parentCandidateComposition = parentCandidate.composition;
|
||||
|
||||
if (parentCandidateComposition && parentCandidateComposition.indexOf(objectKeystring) !== -1) {
|
||||
return false;
|
||||
}
|
||||
@@ -178,20 +176,18 @@ export default class MoveAction {
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
let parent = objectPath[1];
|
||||
let parentType = parent && this.openmct.types.get(parent.type);
|
||||
let child = objectPath[0];
|
||||
let childType = child && this.openmct.types.get(child.type);
|
||||
let isPersistable = this.openmct.objects.isPersistable(child.identifier);
|
||||
const parent = objectPath[1];
|
||||
const parentType = parent && this.openmct.types.get(parent.type);
|
||||
const child = objectPath[0];
|
||||
const childType = child && this.openmct.types.get(child.type);
|
||||
const isPersistable = this.openmct.objects.isPersistable(child.identifier);
|
||||
|
||||
if (child.locked || (parent && parent.locked) || !isPersistable) {
|
||||
if (parent?.locked || !isPersistable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parentType
|
||||
&& parentType.definition.creatable
|
||||
&& childType
|
||||
&& childType.definition.creatable
|
||||
return parentType?.definition.creatable
|
||||
&& childType?.definition.creatable
|
||||
&& Array.isArray(parent.composition);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ describe("The Move Action plugin", () => {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "child-folder-object"
|
||||
}
|
||||
},
|
||||
location: "parent-folder-object"
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
@@ -90,6 +91,31 @@ describe("The Move Action plugin", () => {
|
||||
expect(moveAction).toBeDefined();
|
||||
});
|
||||
|
||||
describe("when determining the object is applicable", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(moveAction, 'appliesTo').and.callThrough();
|
||||
});
|
||||
|
||||
it("should be true when the parent is creatable and has composition", () => {
|
||||
let applies = moveAction.appliesTo([childObject, parentObject]);
|
||||
expect(applies).toBe(true);
|
||||
});
|
||||
|
||||
it("should be true when the child is locked and not an alias", () => {
|
||||
childObject.locked = true;
|
||||
let applies = moveAction.appliesTo([childObject, parentObject]);
|
||||
expect(applies).toBe(true);
|
||||
});
|
||||
|
||||
it("should still be true when the child is locked and is an alias", () => {
|
||||
childObject.locked = true;
|
||||
childObject.location = 'another-parent-folder-object';
|
||||
let applies = moveAction.appliesTo([childObject, parentObject]);
|
||||
expect(applies).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when moving an object to a new parent and removing from the old parent", () => {
|
||||
let unObserve;
|
||||
beforeEach((done) => {
|
||||
|
||||
155
src/plugins/notebook/actions/ExportNotebookAsTextAction.js
Normal file
155
src/plugins/notebook/actions/ExportNotebookAsTextAction.js
Normal file
@@ -0,0 +1,155 @@
|
||||
import {saveAs} from 'saveAs';
|
||||
import Moment from 'moment';
|
||||
|
||||
const UNKNOWN_USER = 'Unknown';
|
||||
const UNKNOWN_TIME = 'Unknown';
|
||||
|
||||
export default class ExportNotebookAsTextAction {
|
||||
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
|
||||
this.cssClass = 'icon-export';
|
||||
this.description = 'Exports notebook contents as a text file';
|
||||
this.group = "action";
|
||||
this.key = 'exportNotebookAsText';
|
||||
this.name = 'Export Notebook as Text';
|
||||
this.priority = 1;
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
this.showForm(objectPath);
|
||||
}
|
||||
|
||||
getTagName(tagId, availableTags) {
|
||||
const foundTag = availableTags.find(tag => tag.id === tagId);
|
||||
if (foundTag) {
|
||||
return foundTag.label;
|
||||
} else {
|
||||
return tagId;
|
||||
}
|
||||
}
|
||||
|
||||
getTagsForEntry(entry, domainObjectKeyString, annotations) {
|
||||
const foundTags = [];
|
||||
annotations.forEach(annotation => {
|
||||
const target = annotation.targets?.[domainObjectKeyString];
|
||||
if (target?.entryId === entry.id) {
|
||||
annotation.tags.forEach(tag => {
|
||||
if (!foundTags.includes(tag)) {
|
||||
foundTags.push(tag);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return foundTags;
|
||||
}
|
||||
|
||||
formatTimeStamp(timestamp) {
|
||||
if (timestamp) {
|
||||
return `${Moment.utc(timestamp).format('YYYY-MM-DD HH:mm:ss')} UTC`;
|
||||
} else {
|
||||
return UNKNOWN_TIME;
|
||||
}
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
const domainObject = objectPath[0];
|
||||
const type = this.openmct.types.get(domainObject.type);
|
||||
|
||||
return type?.definition?.name === 'Notebook';
|
||||
}
|
||||
|
||||
async onSave(changes, objectPath) {
|
||||
const availableTags = this.openmct.annotation.getAvailableTags();
|
||||
const identifier = objectPath[0].identifier;
|
||||
const domainObject = await this.openmct.objects.get(identifier);
|
||||
let foundAnnotations = [];
|
||||
// only load annotations if there are tags
|
||||
if (availableTags.length) {
|
||||
foundAnnotations = await this.openmct.annotation.getAnnotations(domainObject.identifier);
|
||||
}
|
||||
|
||||
let notebookAsText = `# ${domainObject.name}\n\n`;
|
||||
|
||||
if (changes.exportMetaData) {
|
||||
const createdTimestamp = domainObject.created;
|
||||
const createdBy = domainObject.createdBy ?? UNKNOWN_USER;
|
||||
const modifiedBy = domainObject.modifiedBy ?? UNKNOWN_USER;
|
||||
const modifiedTimestamp = domainObject.modified ?? domainObject.created;
|
||||
notebookAsText += `Created on ${this.formatTimeStamp(createdTimestamp)} by user ${createdBy}\n\n`;
|
||||
notebookAsText += `Updated on ${this.formatTimeStamp(modifiedTimestamp)} by user ${modifiedBy}\n\n`;
|
||||
}
|
||||
|
||||
const notebookSections = domainObject.configuration.sections;
|
||||
const notebookEntries = domainObject.configuration.entries;
|
||||
|
||||
notebookSections.forEach(section => {
|
||||
notebookAsText += `## ${section.name}\n\n`;
|
||||
|
||||
const notebookPages = section.pages;
|
||||
|
||||
notebookPages.forEach(page => {
|
||||
notebookAsText += `### ${page.name}\n\n`;
|
||||
|
||||
const notebookPageEntries = notebookEntries[section.id]?.[page.id];
|
||||
notebookPageEntries.forEach(entry => {
|
||||
if (changes.exportMetaData) {
|
||||
const createdTimestamp = entry.createdOn;
|
||||
const createdBy = entry.createdBy ?? UNKNOWN_USER;
|
||||
const modifiedBy = entry.modifiedBy ?? UNKNOWN_USER;
|
||||
const modifiedTimestamp = entry.modified ?? entry.created;
|
||||
notebookAsText += `Created on ${this.formatTimeStamp(createdTimestamp)} by user ${createdBy}\n\n`;
|
||||
notebookAsText += `Updated on ${this.formatTimeStamp(modifiedTimestamp)} by user ${modifiedBy}\n\n`;
|
||||
}
|
||||
|
||||
if (changes.exportTags) {
|
||||
const domainObjectKeyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
const tags = this.getTagsForEntry(entry, domainObjectKeyString, foundAnnotations);
|
||||
const tagNames = tags.map(tag => this.getTagName(tag, availableTags));
|
||||
if (tagNames) {
|
||||
notebookAsText += `Tags: ${tagNames.join(', ')}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
notebookAsText += `${entry.text}\n\n`;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const blob = new Blob([notebookAsText], {type: "text/markdown"});
|
||||
const fileName = domainObject.name + '.md';
|
||||
saveAs(blob, fileName);
|
||||
}
|
||||
|
||||
async showForm(objectPath) {
|
||||
const formStructure = {
|
||||
title: "Export Notebook Text",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "exportMetaData",
|
||||
control: "toggleSwitch",
|
||||
name: "Include Metadata (created/modified, etc.)",
|
||||
required: true,
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Include Tags",
|
||||
control: "toggleSwitch",
|
||||
required: true,
|
||||
key: 'exportTags',
|
||||
value: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const changes = await this.openmct.forms.showForm(formStructure);
|
||||
|
||||
return this.onSave(changes, objectPath);
|
||||
}
|
||||
}
|
||||
@@ -236,7 +236,7 @@ export default {
|
||||
sidebarCoversEntries: false,
|
||||
filteredAndSortedEntries: [],
|
||||
notebookAnnotations: {},
|
||||
selectedEntryId: '',
|
||||
selectedEntryId: undefined,
|
||||
activeTransaction: false,
|
||||
savingTransaction: false
|
||||
};
|
||||
@@ -381,8 +381,10 @@ export default {
|
||||
});
|
||||
},
|
||||
updateSelection(selection) {
|
||||
if (selection?.[0]?.[0]?.context?.targetDetails?.entryId === undefined) {
|
||||
this.selectedEntryId = '';
|
||||
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
if (selection?.[0]?.[0]?.context?.targetDetails?.[keyString]?.entryId === undefined) {
|
||||
this.selectedEntryId = undefined;
|
||||
}
|
||||
},
|
||||
async loadAnnotations() {
|
||||
@@ -522,6 +524,8 @@ export default {
|
||||
this.openmct.notifications.alert('Warning: unable to delete entry');
|
||||
console.error(`unable to delete entry ${entryId} from section ${this.selectedSection}, page ${this.selectedPage}`);
|
||||
|
||||
this.cancelTransaction();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -534,10 +538,15 @@ export default {
|
||||
emphasis: true,
|
||||
callback: () => {
|
||||
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||
entries.splice(entryPos, 1);
|
||||
this.updateEntries(entries);
|
||||
this.filterAndSortEntries();
|
||||
this.removeAnnotations(entryId);
|
||||
if (entries) {
|
||||
entries.splice(entryPos, 1);
|
||||
this.updateEntries(entries);
|
||||
this.filterAndSortEntries();
|
||||
this.removeAnnotations(entryId);
|
||||
} else {
|
||||
this.cancelTransaction();
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
class="c-ne__remove c-icon-button c-icon-button--major icon-trash"
|
||||
title="Delete this entry"
|
||||
tabindex="-1"
|
||||
@click="deleteEntry"
|
||||
@click.stop.prevent="deleteEntry"
|
||||
>
|
||||
</button>
|
||||
</span>
|
||||
@@ -466,7 +466,10 @@ export default {
|
||||
if (!this.isSelectedEntry) {
|
||||
$event.preventDefault();
|
||||
// blur the previous focused entry if clicking on non selected entry input
|
||||
document.activeElement.blur();
|
||||
const focusedElementId = document.activeElement?.id;
|
||||
if (focusedElementId !== this.entry.id) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}
|
||||
},
|
||||
editingEntry() {
|
||||
|
||||
@@ -76,13 +76,6 @@
|
||||
}
|
||||
|
||||
.c-list__item {
|
||||
@include hover() {
|
||||
[class*="__menu-indicator"] {
|
||||
opacity: 0.7;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
@@ -92,10 +85,10 @@
|
||||
}
|
||||
|
||||
&__menu-indicator {
|
||||
// Not sure this is being used
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.8em;
|
||||
opacity: 0;
|
||||
transition: $transOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import CopyToNotebookAction from './actions/CopyToNotebookAction';
|
||||
import ExportNotebookAsTextAction from './actions/ExportNotebookAsTextAction';
|
||||
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
|
||||
import NotebookViewProvider from './NotebookViewProvider';
|
||||
import NotebookType from './NotebookType';
|
||||
@@ -80,6 +81,7 @@ function installBaseNotebookFunctionality(openmct) {
|
||||
};
|
||||
openmct.types.addType('notebookSnapshotImage', notebookSnapshotImageType);
|
||||
openmct.actions.register(new CopyToNotebookAction(openmct));
|
||||
openmct.actions.register(new ExportNotebookAsTextAction(openmct));
|
||||
|
||||
const notebookSnapshotIndicator = new Vue ({
|
||||
components: {
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="loaded"
|
||||
ref="plot"
|
||||
class="gl-plot"
|
||||
:class="{ 'js-series-data-loaded' : seriesDataLoaded }"
|
||||
>
|
||||
<slot></slot>
|
||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
||||
@@ -347,6 +349,9 @@ export default {
|
||||
const parentLeftTickWidth = this.parentYTickWidth.leftTickWidth;
|
||||
|
||||
return parentLeftTickWidth || leftTickWidth;
|
||||
},
|
||||
seriesDataLoaded() {
|
||||
return ((this.pending === 0) && this.loaded);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -400,7 +405,7 @@ export default {
|
||||
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.updateStatus);
|
||||
|
||||
this.openmct.objectViews.on('clearData', this.clearData);
|
||||
this.$on('loadingUpdated', this.loadAnnotations);
|
||||
this.$on('loadingComplete', this.loadAnnotations);
|
||||
this.openmct.selection.on('change', this.updateSelection);
|
||||
this.setTimeContext();
|
||||
|
||||
@@ -412,10 +417,11 @@ export default {
|
||||
this.openmct.selection.off('change', this.updateSelection);
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
document.removeEventListener('keyup', this.handleKeyUp);
|
||||
document.body.removeEventListener('click', this.cancelSelection);
|
||||
this.destroy();
|
||||
},
|
||||
methods: {
|
||||
updateSelection(selection) {
|
||||
async updateSelection(selection) {
|
||||
const selectionContext = selection?.[0]?.[0]?.context?.item;
|
||||
// on clicking on a search result we highlight the annotation and zoom - we know it's an annotation result when isAnnotationSearchResult === true
|
||||
// We shouldn't zoom when we're selecting existing annotations to view them or creating new annotations.
|
||||
@@ -434,15 +440,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentXaxis = this.config.xAxis.get('displayRange');
|
||||
const currentYaxis = this.config.yAxis.get('displayRange');
|
||||
|
||||
// when there is no plot data, the ranges can be undefined
|
||||
// in which case we should not perform selection
|
||||
if (!currentXaxis || !currentYaxis) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.waitForAxesToLoad();
|
||||
const selectedAnnotations = selection?.[0]?.[0]?.context?.annotations;
|
||||
//This section is only for the annotations search results entry to displaying annotations
|
||||
if (isAnnotationSearchResult) {
|
||||
@@ -452,10 +450,39 @@ export default {
|
||||
//This section is common to all entry points for annotation display
|
||||
this.prepareExistingAnnotationSelection(selectedAnnotations);
|
||||
},
|
||||
cancelSelection(event) {
|
||||
if (this.$refs?.plot) {
|
||||
const clickedInsidePlot = this.$refs.plot.contains(event.target);
|
||||
const clickedInsideInspector = event.target.closest('.js-inspector') !== null;
|
||||
const clickedOption = event.target.closest('.js-autocomplete-options') !== null;
|
||||
if (!clickedInsidePlot && !clickedInsideInspector && !clickedOption) {
|
||||
this.rectangles = [];
|
||||
this.annotationSelections = [];
|
||||
this.selectPlot();
|
||||
document.body.removeEventListener('click', this.cancelSelection);
|
||||
}
|
||||
}
|
||||
},
|
||||
waitForAxesToLoad() {
|
||||
return new Promise(resolve => {
|
||||
// When there is no plot data, the ranges can be undefined
|
||||
// in which case we should not perform selection.
|
||||
const currentXaxis = this.config.xAxis.get('displayRange');
|
||||
const currentYaxis = this.config.yAxis.get('displayRange');
|
||||
if (!currentXaxis || !currentYaxis) {
|
||||
this.$once('loadingComplete', () => {
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
},
|
||||
showAnnotationsFromSearchResults(selectedAnnotations) {
|
||||
//Start section
|
||||
|
||||
if (selectedAnnotations?.length) {
|
||||
// pause the plot if we haven't already so we can actually display
|
||||
// the annotations
|
||||
this.freeze();
|
||||
// just use first annotation
|
||||
const boundingBoxes = Object.values(selectedAnnotations[0].targets);
|
||||
let minX = Number.MAX_SAFE_INTEGER;
|
||||
@@ -672,6 +699,9 @@ export default {
|
||||
stopLoading() {
|
||||
this.pending -= 1;
|
||||
this.updateLoading();
|
||||
if (this.pending === 0) {
|
||||
this.$emit('loadingComplete');
|
||||
}
|
||||
},
|
||||
|
||||
updateLoading() {
|
||||
@@ -1265,6 +1295,8 @@ export default {
|
||||
}
|
||||
|
||||
this.openmct.selection.select(selection, true);
|
||||
|
||||
document.body.addEventListener('click', this.cancelSelection);
|
||||
},
|
||||
selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event) {
|
||||
let targetDomainObjects = {};
|
||||
@@ -1687,6 +1719,9 @@ export default {
|
||||
},
|
||||
|
||||
resumeRealtimeData() {
|
||||
// remove annotation selections
|
||||
this.rectangles = [];
|
||||
|
||||
this.clearPanZoomHistory();
|
||||
this.userViewportChangeEnd();
|
||||
},
|
||||
|
||||
@@ -603,19 +603,20 @@ export default {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
//There has to be at least one yAxis
|
||||
const yAxisIds = [mainYAxisId].concat(this.config.additionalYAxes.map(yAxis => yAxis.get('id')));
|
||||
// Repeat drawing for all yAxes
|
||||
yAxisIds.forEach((id) => {
|
||||
if (this.canDraw(id)) {
|
||||
this.updateViewport(id);
|
||||
this.drawSeries(id);
|
||||
this.drawRectangles(id);
|
||||
this.drawHighlights(id);
|
||||
|
||||
// only draw these in fixed time mode or plot is paused
|
||||
if (this.annotationViewingAndEditingAllowed) {
|
||||
this.drawAnnotatedPoints(id);
|
||||
this.drawAnnotationSelections(id);
|
||||
}
|
||||
// Repeat drawing for all yAxes
|
||||
yAxisIds.filter(this.canDraw).forEach((id, yAxisIndex) => {
|
||||
this.updateViewport(id);
|
||||
this.drawSeries(id);
|
||||
if (yAxisIndex === 0) {
|
||||
this.drawRectangles(id);
|
||||
}
|
||||
|
||||
this.drawHighlights(id);
|
||||
// only draw these in fixed time mode or plot is paused
|
||||
if (this.annotationViewingAndEditingAllowed) {
|
||||
this.drawAnnotatedPoints(id);
|
||||
this.drawAnnotationSelections(id);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ export default class RemoteClock extends DefaultClock {
|
||||
|
||||
this.timeTelemetryObject = undefined;
|
||||
this.parseTime = undefined;
|
||||
this.formatTime = undefined;
|
||||
this.metadata = undefined;
|
||||
|
||||
this.lastTick = 0;
|
||||
@@ -137,6 +138,10 @@ export default class RemoteClock extends DefaultClock {
|
||||
this.parseTime = (datum) => {
|
||||
return timeFormatter.parse(datum);
|
||||
};
|
||||
|
||||
this.formatTime = (datum) => {
|
||||
return timeFormatter.format(datum);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@@ -20,6 +20,8 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
const SPECIAL_MESSAGE_TYPES = ['layout', 'flexible-layout'];
|
||||
|
||||
export default class RemoveAction {
|
||||
#transaction;
|
||||
|
||||
@@ -39,28 +41,37 @@ export default class RemoveAction {
|
||||
}
|
||||
|
||||
async invoke(objectPath) {
|
||||
let object = objectPath[0];
|
||||
let parent = objectPath[1];
|
||||
const child = objectPath[0];
|
||||
const parent = objectPath[1];
|
||||
|
||||
try {
|
||||
await this.showConfirmDialog(object);
|
||||
await this.showConfirmDialog(child, parent);
|
||||
} catch (error) {
|
||||
return; // form canceled, exit invoke
|
||||
}
|
||||
|
||||
await this.removeFromComposition(parent, object);
|
||||
await this.removeFromComposition(parent, child, objectPath);
|
||||
|
||||
if (this.inNavigationPath(object)) {
|
||||
if (this.inNavigationPath(child)) {
|
||||
this.navigateTo(objectPath.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
showConfirmDialog(object) {
|
||||
showConfirmDialog(child, parent) {
|
||||
let message = 'Warning! This action will remove this object. Are you sure you want to continue?';
|
||||
|
||||
if (SPECIAL_MESSAGE_TYPES.includes(parent.type)) {
|
||||
const type = this.openmct.types.get(parent.type);
|
||||
const typeName = type.definition.name;
|
||||
|
||||
message = `Warning! This action will remove this item from the ${typeName}. Are you sure you want to continue?`;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let dialog = this.openmct.overlays.dialog({
|
||||
title: `Remove ${object.name}`,
|
||||
const dialog = this.openmct.overlays.dialog({
|
||||
title: `Remove ${child.name}`,
|
||||
iconClass: 'alert',
|
||||
message: 'Warning! This action will remove this object. Are you sure you want to continue?',
|
||||
message,
|
||||
buttons: [
|
||||
{
|
||||
label: 'OK',
|
||||
@@ -94,13 +105,13 @@ export default class RemoveAction {
|
||||
this.openmct.router.navigate('#/browse/' + urlPath);
|
||||
}
|
||||
|
||||
async removeFromComposition(parent, child) {
|
||||
async removeFromComposition(parent, child, objectPath) {
|
||||
this.startTransaction();
|
||||
|
||||
const composition = this.openmct.composition.get(parent);
|
||||
composition.remove(child);
|
||||
|
||||
if (!this.isAlias(child, parent)) {
|
||||
if (!this.openmct.objects.isObjectPathToALink(child, objectPath)) {
|
||||
this.openmct.objects.mutate(child, 'location', null);
|
||||
}
|
||||
|
||||
@@ -111,18 +122,6 @@ export default class RemoveAction {
|
||||
await this.saveTransaction();
|
||||
}
|
||||
|
||||
isAlias(child, parent) {
|
||||
if (parent === undefined) {
|
||||
// then it's a root item, not an alias
|
||||
return false;
|
||||
}
|
||||
|
||||
const parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
|
||||
const childLocation = child.location;
|
||||
|
||||
return childLocation !== parentKeyString;
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
const parent = objectPath[1];
|
||||
const parentType = parent && this.openmct.types.get(parent.type);
|
||||
@@ -130,9 +129,9 @@ export default class RemoveAction {
|
||||
const locked = child.locked ? child.locked : parent && parent.locked;
|
||||
const isEditing = this.openmct.editor.isEditing();
|
||||
const isPersistable = this.openmct.objects.isPersistable(child.identifier);
|
||||
const isAlias = this.isAlias(child, parent);
|
||||
const isLink = this.openmct.objects.isObjectPathToALink(child, objectPath);
|
||||
|
||||
if (locked || (!isPersistable && !isAlias)) {
|
||||
if (!isLink && (locked || !isPersistable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -142,9 +141,8 @@ export default class RemoveAction {
|
||||
}
|
||||
}
|
||||
|
||||
return parentType
|
||||
&& parentType.definition.creatable
|
||||
&& Array.isArray(parent.composition);
|
||||
return parentType?.definition.creatable
|
||||
&& Array.isArray(parent?.composition);
|
||||
}
|
||||
|
||||
startTransaction() {
|
||||
|
||||
@@ -52,6 +52,10 @@ describe("The Remove Action plugin", () => {
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
folder: {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "parent-folder-object"
|
||||
},
|
||||
name: "Parent Folder",
|
||||
composition: [childObject.identifier]
|
||||
}
|
||||
@@ -116,10 +120,18 @@ describe("The Remove Action plugin", () => {
|
||||
expect(applies).toBe(true);
|
||||
});
|
||||
|
||||
it("should be false when the child is locked", () => {
|
||||
it("should be false when the child is locked and not an alias", () => {
|
||||
childObject.locked = true;
|
||||
childObject.location = 'parent-folder-object';
|
||||
let applies = removeAction.appliesTo([childObject, parentObject]);
|
||||
expect(applies).toBe(false);
|
||||
});
|
||||
|
||||
it("should be true when the child is locked and IS an alias", () => {
|
||||
childObject.locked = true;
|
||||
childObject.location = 'other-folder-object';
|
||||
let applies = removeAction.appliesTo([childObject, parentObject]);
|
||||
expect(applies).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ define([
|
||||
'./TelemetryTableColumn',
|
||||
'./TelemetryTableUnitColumn',
|
||||
'./TelemetryTableConfiguration',
|
||||
'@/utils/staleness'
|
||||
'../../utils/staleness'
|
||||
], function (
|
||||
EventEmitter,
|
||||
_,
|
||||
|
||||
@@ -88,7 +88,7 @@ define([], function () {
|
||||
}
|
||||
|
||||
getContextMenuActions() {
|
||||
return ['viewDatumAction'];
|
||||
return ['viewDatumAction', 'viewHistoricalData'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -175,14 +175,22 @@ export default {
|
||||
getDatum() {
|
||||
return this.row.fullDatum;
|
||||
},
|
||||
showContextMenu: function (event) {
|
||||
showContextMenu: async function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.updateViewContext();
|
||||
this.markRow(event);
|
||||
|
||||
const contextualDomainObject = await this.row.getContextualDomainObject?.(this.openmct, this.row.objectKeyString);
|
||||
|
||||
let objectPath = this.objectPath;
|
||||
if (contextualDomainObject) {
|
||||
objectPath = objectPath.slice();
|
||||
objectPath.unshift(contextualDomainObject);
|
||||
}
|
||||
|
||||
const actions = this.row.getContextMenuActions().map(key => this.openmct.actions.getAction(key));
|
||||
const menuItems = this.openmct.menus.actionsToMenuItems(actions, this.objectPath, this.currentView);
|
||||
const menuItems = this.openmct.menus.actionsToMenuItems(actions, objectPath, this.currentView);
|
||||
if (menuItems.length) {
|
||||
this.openmct.menus.showMenu(event.x, event.y, menuItems);
|
||||
}
|
||||
|
||||
@@ -49,12 +49,10 @@
|
||||
background-size: 3px 30%;
|
||||
background-color: $colorBodyBgSubtle;
|
||||
box-shadow: inset rgba(black, 0.4) 0 1px 1px;
|
||||
transition: $transOut;
|
||||
|
||||
svg text {
|
||||
fill: $colorBodyFg;
|
||||
stroke: $colorBodyBgSubtle;
|
||||
transition: $transOut;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
&:hover,
|
||||
&:active {
|
||||
cursor: col-resize;
|
||||
filter: $timeConductorAxisHoverFilter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,7 +268,6 @@
|
||||
grid-column-gap: 3px;
|
||||
grid-row-gap: 4px;
|
||||
align-items: start;
|
||||
filter: $filterMenu;
|
||||
box-shadow: $shdwMenu;
|
||||
padding: $interiorMargin;
|
||||
position: absolute;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
:header-items="headerItems"
|
||||
:default-sort="defaultSort"
|
||||
class="sticky"
|
||||
@sortChanged="updateDefaultSort"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,9 +39,8 @@
|
||||
import {getValidatedData} from "../plan/util";
|
||||
import ListView from '../../ui/components/List/ListView.vue';
|
||||
import {getPreciseDuration} from "../../utils/duration";
|
||||
import ticker from 'utils/clock/Ticker';
|
||||
import {SORT_ORDER_OPTIONS} from "./constants";
|
||||
|
||||
import _ from 'lodash';
|
||||
import moment from "moment";
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@@ -53,16 +53,26 @@ const headerItems = [
|
||||
isSortable: true,
|
||||
property: 'start',
|
||||
name: 'Start Time',
|
||||
format: function (value, object) {
|
||||
return `${moment(value).format(TIME_FORMAT)}Z`;
|
||||
format: function (value, object, key, openmct) {
|
||||
const clock = openmct.time.clock();
|
||||
if (clock && clock.formatTime) {
|
||||
return clock.formatTime(value);
|
||||
} else {
|
||||
return `${moment(value).format(TIME_FORMAT)}Z`;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
defaultDirection: true,
|
||||
isSortable: true,
|
||||
property: 'end',
|
||||
name: 'End Time',
|
||||
format: function (value, object) {
|
||||
return `${moment(value).format(TIME_FORMAT)}Z`;
|
||||
format: function (value, object, key, openmct) {
|
||||
const clock = openmct.time.clock();
|
||||
if (clock && clock.formatTime) {
|
||||
return clock.formatTime(value);
|
||||
} else {
|
||||
return `${moment(value).format(TIME_FORMAT)}Z`;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
defaultDirection: false,
|
||||
@@ -119,7 +129,8 @@ export default {
|
||||
this.unlistenConfig = this.openmct.objects.observe(this.domainObject, 'configuration', this.setViewFromConfig);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
|
||||
this.status = this.openmct.status.get(this.domainObject.identifier);
|
||||
this.unlistenTicker = ticker.listen(this.clearPreviousActivities);
|
||||
|
||||
this.updateTimestamp = _.throttle(this.updateTimestamp, 1000);
|
||||
this.openmct.time.on('bounds', this.updateTimestamp);
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
|
||||
@@ -144,10 +155,6 @@ export default {
|
||||
this.unlistenConfig();
|
||||
}
|
||||
|
||||
if (this.unlistenTicker) {
|
||||
this.unlistenTicker();
|
||||
}
|
||||
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
@@ -192,8 +199,8 @@ export default {
|
||||
}
|
||||
},
|
||||
updateTimestamp(_bounds, isTick) {
|
||||
if (isTick === true) {
|
||||
this.timestamp = this.openmct.time.clock().currentValue();
|
||||
if (isTick === true && this.openmct.time.clock() !== undefined) {
|
||||
this.updateTimeStampAndListActivities(this.openmct.time.clock().currentValue());
|
||||
}
|
||||
},
|
||||
setViewFromClock(newClock) {
|
||||
@@ -202,12 +209,11 @@ export default {
|
||||
if (isFixedTime) {
|
||||
this.hideAll = false;
|
||||
this.showAll = true;
|
||||
// clear invokes listActivities
|
||||
this.clearPreviousActivities(this.openmct.time.bounds()?.start);
|
||||
this.updateTimeStampAndListActivities(this.openmct.time.bounds()?.start);
|
||||
} else {
|
||||
this.setSort();
|
||||
this.setViewBounds();
|
||||
this.listActivities();
|
||||
this.updateTimeStampAndListActivities(this.openmct.time.clock().currentValue());
|
||||
}
|
||||
},
|
||||
addItem(domainObject) {
|
||||
@@ -340,18 +346,15 @@ export default {
|
||||
groups.forEach((key) => {
|
||||
activities = activities.concat(this.planData[key]);
|
||||
});
|
||||
activities = activities.sort(this.sortByProperty);
|
||||
activities = activities.filter(this.filterActivities);
|
||||
activities = this.applyStyles(activities);
|
||||
this.setScrollTop();
|
||||
// sort by start time
|
||||
this.planActivities = activities.sort(this.sortByStartTime);
|
||||
this.planActivities = activities;
|
||||
},
|
||||
clearPreviousActivities(time) {
|
||||
if (time instanceof Date) {
|
||||
this.timestamp = time.getTime();
|
||||
} else {
|
||||
this.timestamp = time;
|
||||
}
|
||||
updateTimeStampAndListActivities(time) {
|
||||
this.timestamp = time;
|
||||
|
||||
this.listActivities();
|
||||
},
|
||||
@@ -471,16 +474,29 @@ export default {
|
||||
const sortOrder = SORT_ORDER_OPTIONS[this.domainObject.configuration.sortOrderIndex];
|
||||
const property = sortOrder.property;
|
||||
const direction = sortOrder.direction.toLowerCase() === 'asc';
|
||||
this.defaultSort = {
|
||||
this.updateDefaultSort({
|
||||
property,
|
||||
defaultDirection: direction
|
||||
direction
|
||||
});
|
||||
},
|
||||
updateDefaultSort(sortOption) {
|
||||
this.defaultSort = {
|
||||
property: sortOption.property,
|
||||
defaultDirection: sortOption.direction
|
||||
};
|
||||
},
|
||||
sortByStartTime(a, b) {
|
||||
const numA = parseInt(a.start, 10);
|
||||
const numB = parseInt(b.start, 10);
|
||||
sortByProperty(a, b) {
|
||||
const property = this.defaultSort.property;
|
||||
const defaultDirection = this.defaultSort.defaultDirection;
|
||||
|
||||
return numA - numB;
|
||||
const numA = parseInt(a[property], 10);
|
||||
const numB = parseInt(b[property], 10);
|
||||
|
||||
if (defaultDirection) {
|
||||
return numA - numB;
|
||||
} else {
|
||||
return numB - numA;
|
||||
}
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
|
||||
@@ -72,7 +72,7 @@ $colorHeadBg: #262626;
|
||||
$colorHeadFg: $colorBodyFg;
|
||||
$colorKey: #0099cc;
|
||||
$colorKeyFg: #fff;
|
||||
$colorKeyHov: #26d8ff;
|
||||
$colorKeyHov: lighten($colorKey, 10%);
|
||||
$colorKeyFilter: invert(36%) sepia(76%) saturate(2514%) hue-rotate(170deg) brightness(99%) contrast(101%);
|
||||
$colorKeyFilterHov: invert(63%) sepia(88%) saturate(3029%) hue-rotate(154deg) brightness(101%) contrast(100%);
|
||||
$colorKeySelectedBg: $colorKey;
|
||||
@@ -86,7 +86,7 @@ $colorSelectedFg: pullForward($colorBodyFg, 20%);
|
||||
|
||||
// Object labels
|
||||
$objectLabelTypeIconOpacity: 0.7;
|
||||
$objectLabelNameFilter: brightness(1.3);
|
||||
$objectLabelNameColorFg: lighten($colorBodyFg, 10%);
|
||||
|
||||
// Layout
|
||||
$shellMainPad: 4px 0;
|
||||
@@ -135,7 +135,7 @@ $colorPausedFg: #333;
|
||||
|
||||
// Base variations
|
||||
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
|
||||
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
|
||||
$colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%);
|
||||
$colorKeySubtle: pushBack($colorKey, 10%);
|
||||
|
||||
// Time Colors
|
||||
@@ -202,6 +202,7 @@ $colorTabsHolderBg: rgba(black, 0.2);
|
||||
// Buttons and Controls
|
||||
$colorBtnBg: pullForward($colorBodyBg, 10%);
|
||||
$colorBtnBgHov: pullForward($colorBtnBg, 10%);
|
||||
$shdwBtnHov: inset rgba(white, 10%) 0 0 0 100px;
|
||||
$colorBtnFg: pullForward($colorBodyFg, 10%);
|
||||
$colorBtnReverseFg: pullForward($colorBtnFg, 10%);
|
||||
$colorBtnReverseBg: pullForward($colorBtnBg, 10%);
|
||||
@@ -333,6 +334,12 @@ $colorLimitCyanBg: #4BA6B3;
|
||||
$colorLimitCyanFg: #D3FAFF;
|
||||
$colorLimitCyanIc: #6BEDFF;
|
||||
|
||||
// Events
|
||||
$colorEventPurpleFg: #6433ff;
|
||||
$colorEventRedFg: #cc0000;
|
||||
$colorEventOrangeFg: orange;
|
||||
$colorEventYellowFg: #ffcc00;
|
||||
|
||||
// Bubble colors
|
||||
$colorInfoBubbleBg: #dddddd;
|
||||
$colorInfoBubbleFg: #666;
|
||||
@@ -464,6 +471,8 @@ $transInTime: 50ms;
|
||||
$transOutTime: 250ms;
|
||||
$transIn: all $transInTime ease-in-out;
|
||||
$transOut: all $transOutTime ease-in-out;
|
||||
$transInTransform: transform $transInTime ease-in-out;
|
||||
$transOutTransform: transform $transOutTime ease-in-out;
|
||||
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
|
||||
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
|
||||
|
||||
@@ -337,6 +337,12 @@ $colorLimitCyanBg: #4BA6B3;
|
||||
$colorLimitCyanFg: #D3FAFF;
|
||||
$colorLimitCyanIc: #6BEDFF;
|
||||
|
||||
// Events
|
||||
$colorEventPurpleFg: #6433ff;
|
||||
$colorEventRedFg: #cc0000;
|
||||
$colorEventOrangeFg: orange;
|
||||
$colorEventYellowFg: #ffcc00;
|
||||
|
||||
// Bubble colors
|
||||
$colorInfoBubbleBg: #dddddd;
|
||||
$colorInfoBubbleFg: #666;
|
||||
@@ -468,6 +474,8 @@ $transInTime: 50ms;
|
||||
$transOutTime: 250ms;
|
||||
$transIn: all $transInTime ease-in-out;
|
||||
$transOut: all $transOutTime ease-in-out;
|
||||
$transInTransform: transform $transInTime ease-in-out;
|
||||
$transOutTransform: transform $transOutTime ease-in-out;
|
||||
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
|
||||
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ $mobileOverlayMargin: 20px;
|
||||
$mobileMenuIconD: 25px;
|
||||
$phoneItemH: floor(math.div($gridItemMobile, 4));
|
||||
$tabletItemH: floor(math.div($gridItemMobile, 3));
|
||||
$shellTimeConductorMobileH: 90px;
|
||||
|
||||
/************************** MOBILE TREE MENU DIMENSIONS */
|
||||
$mobileTreeItemH: 35px;
|
||||
|
||||
@@ -86,7 +86,7 @@ $colorSelectedFg: pullForward($colorBodyFg, 10%);
|
||||
|
||||
// Object labels
|
||||
$objectLabelTypeIconOpacity: 0.5;
|
||||
$objectLabelNameFilter: brightness(0.9);
|
||||
$objectLabelNameColorFg: darken($colorBodyFg, 10%);
|
||||
|
||||
// Layout
|
||||
$shellMainPad: 4px 0;
|
||||
@@ -135,7 +135,7 @@ $colorPausedFg: #fff;
|
||||
|
||||
// Base variations
|
||||
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
|
||||
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
|
||||
$colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%);
|
||||
$colorKeySubtle: pushBack($colorKey, 20%);
|
||||
|
||||
// Time Colors
|
||||
@@ -202,6 +202,7 @@ $colorTabsHolderBg: rgba($colorBodyFg, 0.2);
|
||||
// Buttons and Controls
|
||||
$colorBtnBg: #aaa;
|
||||
$colorBtnBgHov: pullForward($colorBtnBg, 10%);
|
||||
$shdwBtnHov: inset rgba(white, 10%) 0 0 0 20px;
|
||||
$colorBtnFg: #fff;
|
||||
$colorBtnReverseFg: $colorBodyBg;
|
||||
$colorBtnReverseBg: $colorBodyFg;
|
||||
@@ -333,6 +334,12 @@ $colorLimitCyanBg: #4BA6B3;
|
||||
$colorLimitCyanFg: #D3FAFF;
|
||||
$colorLimitCyanIc: #1795c0;
|
||||
|
||||
// Events
|
||||
$colorEventPurpleFg: #6433ff;
|
||||
$colorEventRedFg: #cc0000;
|
||||
$colorEventOrangeFg: orange;
|
||||
$colorEventYellowFg: #ffcc00;
|
||||
|
||||
// Bubble colors
|
||||
$colorInfoBubbleBg: $colorMenuBg;
|
||||
$colorInfoBubbleFg: #666;
|
||||
@@ -464,6 +471,8 @@ $transInTime: 50ms;
|
||||
$transOutTime: 250ms;
|
||||
$transIn: all $transInTime ease-in-out;
|
||||
$transOut: all $transOutTime ease-in-out;
|
||||
$transInTransform: transform $transInTime ease-in-out;
|
||||
$transOutTransform: transform $transOutTime ease-in-out;
|
||||
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
|
||||
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@ $overlayOuterMarginDialog: (5%, 20%);
|
||||
$overlayInnerMargin: 25px;
|
||||
$mainViewPad: 0px;
|
||||
$treeNavArrowD: 20px;
|
||||
$shellMainBrowseBarH: 22px;
|
||||
$shellTimeConductorH: 55px;
|
||||
$shellToolBarH: 29px;
|
||||
/*************** Items */
|
||||
$itemPadLR: 5px;
|
||||
$gridItemDesk: 175px;
|
||||
|
||||
@@ -565,7 +565,7 @@ select {
|
||||
}
|
||||
|
||||
@include hover() {
|
||||
filter: $filterHov;
|
||||
box-shadow: $shdwBtnHov;
|
||||
}
|
||||
|
||||
&.is-current {
|
||||
@@ -725,11 +725,6 @@ select {
|
||||
width: $d;
|
||||
height: $d;
|
||||
text-align: center;
|
||||
transition: $transOut;
|
||||
|
||||
&:hover {
|
||||
transition: $transIn;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
border-width: 1px;
|
||||
@@ -1084,7 +1079,7 @@ input[type="range"] {
|
||||
// Controls that are in close proximity to an element they effect
|
||||
&--show-on-hover {
|
||||
// Hidden by default; requires a hover 1 - 3 levels above to display
|
||||
transition: $transOut;
|
||||
@include transition(opacity, $transOutTime);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -1096,7 +1091,7 @@ input[type="range"] {
|
||||
> * > * > .c-local-controls--show-on-hover,
|
||||
> * > * > * > .c-local-controls--show-on-hover
|
||||
{
|
||||
transition: $transIn;
|
||||
@include transition(opacity);
|
||||
opacity: 1;
|
||||
pointer-events: inherit;
|
||||
|
||||
@@ -1109,6 +1104,7 @@ input[type="range"] {
|
||||
.c-drop-hint {
|
||||
// Used in Tabs View, Flexible Grid Layouts
|
||||
@include abs();
|
||||
@include transition($prop: background-color, $dur: $transOutTime);
|
||||
background-color: $colorDropHintBg;
|
||||
color: $colorDropHintFg;
|
||||
border-radius: $basicCr;
|
||||
@@ -1117,7 +1113,6 @@ input[type="range"] {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: $transOut;
|
||||
z-index: 50;
|
||||
|
||||
&:not(.c-drop-hint--always-show) {
|
||||
@@ -1142,13 +1137,11 @@ input[type="range"] {
|
||||
.is-dragging &,
|
||||
&.is-dragging {
|
||||
pointer-events: inherit;
|
||||
transition: $transIn;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.is-mouse-over &,
|
||||
&.is-mouse-over {
|
||||
transition: $transIn;
|
||||
background-color: $colorDropHintBgHov;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
margin-right: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
transition: $transOut;
|
||||
@include transition($prop: width, $dur: $transOutTime);
|
||||
width: 0;
|
||||
|
||||
.c-icon-button:before { font-size: 1em; }
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
&:hover {
|
||||
.c-timer__controls {
|
||||
transition: $transOut; // On purpose: want this to take a bit longer
|
||||
@include transition($prop: width, $dur: $transOutTime); // On purpose: want this to take a bit longer
|
||||
margin-right: $interiorMargin;
|
||||
width: $ctrlW * 2;
|
||||
}
|
||||
@@ -168,7 +168,6 @@
|
||||
.widget-rules-wrapper,
|
||||
.widget-rule-content,
|
||||
.w-widget-test-data-content {
|
||||
transition: $transIn;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
@@ -430,7 +429,6 @@
|
||||
|
||||
&:hover {
|
||||
.l-autoflow-header .s-button.change-column-width {
|
||||
transition: $transIn;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -444,7 +442,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
.s-button.change-column-width {
|
||||
transition: $transOut;
|
||||
@include transition($prop: opacity, $dur: $transOutTime);
|
||||
opacity: 0;
|
||||
}
|
||||
.l-filter {
|
||||
@@ -737,7 +735,7 @@ body.desktop {
|
||||
&:hover {
|
||||
&:after {
|
||||
background-color: $colorSplitterHover !important;
|
||||
transiiton: background-color, 150ms;
|
||||
@include transition($prop: background-color, $dur: 150ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,13 @@
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
@mixin transition($prop: all, $dur: $transInTime, $timing: ease-in-out, $delay: 0ms) {
|
||||
transition-property: $prop;
|
||||
transition-duration: $dur;
|
||||
transition-timing-function: $timing;
|
||||
transition-delay: $delay;
|
||||
}
|
||||
|
||||
/************************** VISUALS */
|
||||
@mixin ancillaryIcon($d, $c) {
|
||||
// Used for small icons used in combination with larger icons,
|
||||
@@ -463,22 +470,12 @@
|
||||
}
|
||||
|
||||
@mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) {
|
||||
// Is this being used? Remove if not.
|
||||
background: $bg;
|
||||
color: $fg;
|
||||
border-radius: $radius;
|
||||
box-shadow: $shdw;
|
||||
}
|
||||
|
||||
@mixin buttonBehavior() {
|
||||
// Assign transition timings
|
||||
transition: $transOut;
|
||||
|
||||
@include hover() {
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin cControl() {
|
||||
$fs: 1em;
|
||||
@include userSelectNone();
|
||||
@@ -515,8 +512,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin cControlHov($styleConst: $shdwBtnHov) {
|
||||
transition: box-shadow $transOutTime;
|
||||
|
||||
@include hover() {
|
||||
transition: box-shadow $transInTime;
|
||||
box-shadow: $styleConst !important;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin cButton() {
|
||||
@include cControl();
|
||||
@include cControlHov();
|
||||
@include themedButton();
|
||||
border-radius: $controlCr;
|
||||
color: $colorBtnFg;
|
||||
@@ -528,10 +535,6 @@
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
@include hover() {
|
||||
filter: $filterHov;
|
||||
}
|
||||
|
||||
&[class*="--major"],
|
||||
&[class*='is-active']{
|
||||
background: $colorBtnMajorBg;
|
||||
@@ -546,11 +549,11 @@
|
||||
|
||||
@mixin cClickIcon() {
|
||||
@include cControl();
|
||||
@include cControlHov();
|
||||
color: $colorBodyFg;
|
||||
cursor: pointer;
|
||||
padding: 4px; // Bigger hit area
|
||||
opacity: 0.7;
|
||||
transition: $transOut;
|
||||
transform-origin: center;
|
||||
|
||||
&[class*="--major"] {
|
||||
@@ -560,7 +563,6 @@
|
||||
|
||||
@include hover() {
|
||||
transform: scale(1.1);
|
||||
transition: $transIn;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -584,21 +586,14 @@
|
||||
// Padding is included to facilitate a bigger hit area
|
||||
// Make the icon bigger relative to its container
|
||||
@include cControl();
|
||||
@include cControlHov();
|
||||
@include cClickIconButtonLayout();
|
||||
background: none;
|
||||
color: $colorClickIconButton;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
transition: $transOut;
|
||||
border-radius: $controlCr;
|
||||
|
||||
@include hover() {
|
||||
transition: $transIn;
|
||||
background: $colorClickIconButtonBgHov;
|
||||
//color: $colorClickIconButtonFgHov;
|
||||
filter: $filterHov;
|
||||
}
|
||||
|
||||
&[class*="--major"] {
|
||||
color: $colorBtnMajorBg !important;
|
||||
}
|
||||
@@ -654,10 +649,6 @@
|
||||
border-color: $colorFg;
|
||||
}
|
||||
|
||||
@include hover {
|
||||
filter: $filterHov;
|
||||
}
|
||||
|
||||
&--up, &--prev {
|
||||
&:before {
|
||||
transform: translate(-30%, -50%) rotate(135deg);
|
||||
|
||||
@@ -192,3 +192,12 @@ tr {
|
||||
@include isStatus($glyph: $glyph-icon-alert-rect, $color: $colorWarningLo);
|
||||
}
|
||||
}
|
||||
|
||||
.is-event {
|
||||
&--purple { color: $colorEventPurpleFg !important; }
|
||||
&--red { color: $colorEventRedFg !important; }
|
||||
&--orange { color: $colorEventOrangeFg !important; }
|
||||
&--yellow { color: $colorEventYellowFg !important; }
|
||||
&--no-style { color: inherit; }
|
||||
}
|
||||
|
||||
|
||||
@@ -208,9 +208,19 @@ div.c-table {
|
||||
}
|
||||
th, td {
|
||||
width: 33%; // Needed to prevent size jumping as values dynamically update
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:hover {
|
||||
background: $colorItemTreeHoverBg;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
user-select: none; // Table supports context-click to display Actions menu, don't allow text selection.
|
||||
|
||||
&.is-stale {
|
||||
@include isStaleElement();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
// eslint-disable-next-line you-dont-need-lodash-underscore/get
|
||||
let value = _.get(this.item, property.key);
|
||||
if (property.format) {
|
||||
value = property.format(value, this.item, property.key);
|
||||
value = property.format(value, this.item, property.key, this.openmct);
|
||||
}
|
||||
|
||||
values.push({
|
||||
|
||||
@@ -136,6 +136,11 @@ export default {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this.$emit('sortChanged', {
|
||||
property: this.sortBy,
|
||||
defaultDirection: this.ascending
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
.c-list-view {
|
||||
tbody tr {
|
||||
background: $colorListItemBg;
|
||||
transition: $transOut;
|
||||
}
|
||||
|
||||
td {
|
||||
@@ -22,8 +21,6 @@
|
||||
|
||||
&:hover {
|
||||
background: $colorListItemBgHov;
|
||||
filter: $filterHov;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,6 +286,7 @@ export default {
|
||||
this.openmct.objectViews.on('clearData', this.clearData);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.updateStyle(this.styleRuleManager?.currentStyle);
|
||||
this.getActionCollection();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
&__name {
|
||||
filter: $objectLabelNameFilter;
|
||||
color: $objectLabelNameColorFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: auto; left: 0;
|
||||
z-index: 2;
|
||||
z-index: 10;
|
||||
|
||||
.c-object-label {
|
||||
visibility: hidden;
|
||||
@@ -99,6 +99,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.c-so-view--flexible-layout,
|
||||
&.c-so-view--layout {
|
||||
// For sub-layouts with hidden frames, completely hide the header to avoid overlapping buttons
|
||||
> .c-so-view__header {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
color: $objectLabelNameColorFg;
|
||||
display: inline;
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@mixin visibleRegexButton {
|
||||
opacity: 1;
|
||||
padding: 1px 3px;
|
||||
width: 24px;
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
.c-search {
|
||||
@@ -31,7 +31,7 @@
|
||||
overflow: hidden;
|
||||
padding: 1px 0;
|
||||
transform-origin: left;
|
||||
transition: $transOut;
|
||||
@include transition($prop: min-width, $dur: $transOutTime);
|
||||
width: 0;
|
||||
|
||||
&.is-active {
|
||||
@@ -54,8 +54,19 @@
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.c-search__clear-input {
|
||||
display: block;
|
||||
&:before {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='search'] {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@include hover {
|
||||
.c-search__clear-input {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +77,9 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include hover {
|
||||
.c-search__use-regex {
|
||||
@include visibleRegexButton();
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-inspector">
|
||||
<div class="c-inspector js-inspector">
|
||||
<object-name />
|
||||
<InspectorTabs
|
||||
:selection="selection"
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
}
|
||||
|
||||
&__selected {
|
||||
.c-object-label__name {
|
||||
filter: $objectLabelNameFilter;
|
||||
}
|
||||
|
||||
.c-object-label__type-icon {
|
||||
opacity: $objectLabelTypeIconOpacity;
|
||||
}
|
||||
|
||||
@@ -93,9 +93,18 @@
|
||||
:persist-position="true"
|
||||
>
|
||||
<RecentObjectsList
|
||||
ref="recentObjectsList"
|
||||
class="l-shell__tree"
|
||||
@openAndScrollTo="openAndScrollTo($event)"
|
||||
/>
|
||||
<button
|
||||
slot="controls"
|
||||
class="c-icon-button icon-clear-data"
|
||||
aria-label="Clear Recently Viewed"
|
||||
title="Clear Recently Viewed"
|
||||
@click="handleClearRecentObjects"
|
||||
>
|
||||
</button>
|
||||
</pane>
|
||||
</multipane>
|
||||
</pane>
|
||||
@@ -279,6 +288,9 @@ export default {
|
||||
handleTreeReset() {
|
||||
this.triggerReset = !this.triggerReset;
|
||||
},
|
||||
handleClearRecentObjects() {
|
||||
this.$refs.recentObjectsList.clearRecentObjects();
|
||||
},
|
||||
onStartResizing() {
|
||||
this.isResizing = true;
|
||||
},
|
||||
|
||||
@@ -191,6 +191,33 @@ export default {
|
||||
shouldTrackCompositionFor(domainObject, navigationPath) {
|
||||
return this.compositionCollections[navigationPath] === undefined
|
||||
&& this.openmct.composition.supportsComposition(domainObject);
|
||||
},
|
||||
/**
|
||||
* Clears the Recent Objects list in localStorage and in the component.
|
||||
* Before clearing, prompts the user to confirm the action with a dialog.
|
||||
*/
|
||||
clearRecentObjects() {
|
||||
const dialog = this.openmct.overlays.dialog({
|
||||
title: 'Clear Recently Viewed Objects',
|
||||
iconClass: 'alert',
|
||||
message: 'This action will clear the Recently Viewed Objects list. Are you sure you want to continue?',
|
||||
buttons: [
|
||||
{
|
||||
label: 'OK',
|
||||
callback: () => {
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEY__RECENT_OBJECTS);
|
||||
this.recents = [];
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
z-index: 70;
|
||||
|
||||
[class*="__icon"] {
|
||||
filter: $colorKeyFilter;
|
||||
filter: $colorKeyFilter;
|
||||
}
|
||||
|
||||
[class*="__item-description"] {
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
min-height: 0;
|
||||
max-height: 15%;
|
||||
overflow: hidden;
|
||||
transition: $transIn;
|
||||
@include transition(min-height);
|
||||
|
||||
&.is-expanded {
|
||||
min-height: 100px;
|
||||
@@ -65,8 +65,7 @@
|
||||
}
|
||||
|
||||
&__pane-tree,
|
||||
&__pane-inspector,
|
||||
&__pane-main {
|
||||
&__pane-inspector {
|
||||
.l-pane__contents {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
@@ -74,8 +73,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__pane-tree,
|
||||
&__pane-main {
|
||||
&__pane-tree {
|
||||
.l-pane__contents {
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
@@ -89,6 +87,37 @@
|
||||
|
||||
&__pane-main {
|
||||
.l-pane__header { display: none; }
|
||||
|
||||
.l-pane__contents {
|
||||
.l-shell__main-.l-shell__main-view-browse-bar {
|
||||
position: relative;
|
||||
}
|
||||
// Using `position: absolute` due to flex having repaint issues with the Time Conductor, #5247
|
||||
.l-shell__time-conductor,
|
||||
.l-shell__main-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: auto;
|
||||
width: auto;
|
||||
|
||||
}
|
||||
|
||||
.l-shell__main-container {
|
||||
top: $shellMainBrowseBarH + $interiorMarginLg;
|
||||
bottom: $shellTimeConductorH + $interiorMargin;
|
||||
}
|
||||
|
||||
@include phonePortrait() {
|
||||
.l-shell__main-container {
|
||||
bottom: $shellTimeConductorMobileH + $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
.l-shell__time-conductor {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.mobile & {
|
||||
@@ -307,6 +336,7 @@
|
||||
height: $p + 24px; // Need to standardize the height
|
||||
justify-content: space-between;
|
||||
padding: $p;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__resizing {
|
||||
@@ -332,6 +362,7 @@
|
||||
box-shadow: $colorBodyBg 0 0 0 1px, $editUIAreaShdw;
|
||||
margin-left: $m;
|
||||
margin-right: $m;
|
||||
top: $shellToolBarH + $shellMainBrowseBarH + $interiorMarginLg !important;
|
||||
|
||||
&[s-selected] {
|
||||
// Provide a clearer selection context articulation for the main edit area
|
||||
@@ -408,10 +439,6 @@
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.c-object-label__name {
|
||||
filter: $objectLabelNameFilter;
|
||||
}
|
||||
|
||||
.c-object-label__type-icon {
|
||||
opacity: $objectLabelTypeIconOpacity;
|
||||
}
|
||||
@@ -433,7 +460,7 @@
|
||||
|
||||
/************************** DRAWER */
|
||||
.c-drawer {
|
||||
/* New sliding overlay or push element to contain things
|
||||
/* Sliding overlay or push element to contain things
|
||||
* Designed for mobile and compact desktop scenarios
|
||||
* Variations:
|
||||
* --overlays: position absolute, overlays neighboring elements
|
||||
@@ -442,7 +469,8 @@
|
||||
* &.is-expanded: applied when expanded.
|
||||
*/
|
||||
|
||||
transition: $transOut;
|
||||
$transProps: width, min-width, height, min-height;
|
||||
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
@@ -455,11 +483,12 @@
|
||||
}
|
||||
|
||||
&.c-drawer--align-left {
|
||||
@include transition($prop: $transProps, $dur: $transOutTime);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.c-drawer--align-top {
|
||||
// Need anything here?
|
||||
@include transition($prop: $transProps, $dur: $transOutTime);
|
||||
}
|
||||
|
||||
&.c-drawer--overlays {
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
@include hover {
|
||||
background: $colorItemTreeHoverBg;
|
||||
filter: $filterHov;
|
||||
//filter: $filterHov; // FILTER REMOVAL, CONVERT TO THEME CONSTANT
|
||||
}
|
||||
|
||||
&.is-navigated-object,
|
||||
@@ -192,14 +192,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__item__label {
|
||||
@include desktop {
|
||||
&:hover {
|
||||
filter: $filterHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-editing .is-navigated-object {
|
||||
|
||||
@@ -355,17 +355,18 @@ export default {
|
||||
this.abortItemLoad(path);
|
||||
}
|
||||
|
||||
let pathIndex = this.openTreeItems.indexOf(path);
|
||||
const pathIndex = this.openTreeItems.indexOf(path);
|
||||
|
||||
if (pathIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.treeItems = this.treeItems.filter((checkItem) => {
|
||||
if (checkItem.navigationPath !== path
|
||||
&& checkItem.navigationPath.includes(path)) {
|
||||
this.destroyObserverByPath(checkItem.navigationPath);
|
||||
this.destroyMutableByPath(checkItem.navigationPath);
|
||||
this.treeItems = this.treeItems.filter((item) => {
|
||||
const otherPath = item.navigationPath;
|
||||
if (otherPath !== path
|
||||
&& this.isTreeItemAChildOf(otherPath, path)) {
|
||||
this.destroyObserverByPath(otherPath);
|
||||
this.destroyMutableByPath(otherPath);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -450,13 +451,14 @@ export default {
|
||||
|
||||
}, Promise.resolve()).then(() => {
|
||||
if (this.isSelectorTree) {
|
||||
let item = this.getTreeItemByPath(navigationPath);
|
||||
// If item is missing due to error in object creation,
|
||||
// walk up the navigationPath until we find an item
|
||||
let item = this.getTreeItemByPath(navigationPath);
|
||||
while (!item) {
|
||||
while (!item && navigationPath !== '') {
|
||||
const startIndex = 0;
|
||||
const endIndex = navigationPath.lastIndexOf('/');
|
||||
navigationPath = navigationPath.substring(startIndex, endIndex);
|
||||
|
||||
item = this.getTreeItemByPath(navigationPath);
|
||||
}
|
||||
|
||||
@@ -959,6 +961,24 @@ export default {
|
||||
isTreeItemPathOpen(path) {
|
||||
return this.openTreeItems.includes(path);
|
||||
},
|
||||
isTreeItemAChildOf(childNavigationPath, parentNavigationPath) {
|
||||
const childPathKeys = childNavigationPath.split('/');
|
||||
const parentPathKeys = parentNavigationPath.split('/');
|
||||
|
||||
// If child path is shorter than or same length as
|
||||
// the parent path, then it's not a child.
|
||||
if (childPathKeys.length <= parentPathKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < parentPathKeys.length; i++) {
|
||||
if (childPathKeys[i] !== parentPathKeys[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
getElementStyleValue(el, style) {
|
||||
if (!el) {
|
||||
return;
|
||||
|
||||
@@ -107,10 +107,10 @@
|
||||
/************************************************ DESKTOP STYLES */
|
||||
body.desktop & {
|
||||
&__handle {
|
||||
background: $colorSplitterBg;
|
||||
background-color: $colorSplitterBg;
|
||||
display: block;
|
||||
position: absolute;
|
||||
transition: $transOut;
|
||||
@include transition(background-color, $transOutTime);
|
||||
|
||||
&:before {
|
||||
// Extended hit area
|
||||
@@ -121,8 +121,8 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $colorSplitterHover;
|
||||
transition: $transIn;
|
||||
background-color: $colorSplitterHover;
|
||||
@include transition(background-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,14 +142,14 @@
|
||||
|
||||
[class*="expand-button"] {
|
||||
display: none; // Hidden by default
|
||||
background: $splitterCollapsedBtnColorBg;
|
||||
background-color: $splitterCollapsedBtnColorBg;
|
||||
color: $splitterCollapsedBtnColorFg;
|
||||
font-size: 0.9em;
|
||||
|
||||
&:hover {
|
||||
background: $splitterCollapsedBtnColorBgHov;
|
||||
background-color: $splitterCollapsedBtnColorBgHov;
|
||||
color: inherit;
|
||||
transition: $transIn;
|
||||
@include transition(background-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
|
||||
.l-pane {
|
||||
&__handle {
|
||||
background: $colorSplitterHover;
|
||||
background-color: $colorSplitterHover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,9 +129,11 @@ export default {
|
||||
mounted() {
|
||||
this.previewAction = new PreviewAction(this.openmct);
|
||||
this.previewAction.on('isVisible', this.togglePreviewState);
|
||||
this.clickedPlotAnnotation = this.clickedPlotAnnotation.bind(this);
|
||||
},
|
||||
destroyed() {
|
||||
this.previewAction.off('isVisible', this.togglePreviewState);
|
||||
this.openmct.selection.off('change', this.clickedPlotAnnotation);
|
||||
},
|
||||
methods: {
|
||||
clickedResult(event) {
|
||||
@@ -141,15 +143,19 @@ export default {
|
||||
this.preview(objectPath);
|
||||
} else {
|
||||
const resultUrl = identifierToString(this.openmct, objectPath);
|
||||
if (!this.openmct.router.isNavigatedObject(objectPath)) {
|
||||
// if we're not on the correct page, navigate to the object,
|
||||
// then wait for the selection event to fire before issuing a new selection
|
||||
if (this.result.annotationType === this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL) {
|
||||
this.openmct.selection.on('change', this.clickedPlotAnnotation);
|
||||
}
|
||||
|
||||
this.openmct.router.navigate(resultUrl);
|
||||
}
|
||||
|
||||
if (this.result.annotationType === this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL) {
|
||||
//wait a beat for the navigation
|
||||
setTimeout(() => {
|
||||
this.openmct.router.navigate(resultUrl);
|
||||
} else {
|
||||
// if this is the navigated object, then we are already on the correct page
|
||||
// and just need to issue the selection event
|
||||
this.clickedPlotAnnotation();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
},
|
||||
preview(objectPath) {
|
||||
@@ -158,6 +164,8 @@ export default {
|
||||
}
|
||||
},
|
||||
clickedPlotAnnotation() {
|
||||
this.openmct.selection.off('change', this.clickedPlotAnnotation);
|
||||
|
||||
const targetDetails = {};
|
||||
const targetDomainObjects = {};
|
||||
Object.entries(this.result.targets).forEach(([key, value]) => {
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
.s-button,
|
||||
.c-button {
|
||||
// Make <a> in label look like buttons
|
||||
transition: $transIn;
|
||||
background: transparent;
|
||||
@include transition(background-color);
|
||||
background-color: transparent;
|
||||
border: 1px solid rgba($colorIndicatorMenuFg, 0.5);
|
||||
border-radius: $controlCr;
|
||||
box-sizing: border-box;
|
||||
@@ -53,8 +53,8 @@
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
padding: 0 2px;
|
||||
&:hover {
|
||||
background: rgba($colorIndicatorMenuFg, 0.1);
|
||||
@include hover {
|
||||
background-color: rgba($colorIndicatorMenuFg, 0.1);
|
||||
border-color: rgba($colorIndicatorMenuFg, 0.75);
|
||||
color: $colorIndicatorMenuFgHov;
|
||||
}
|
||||
|
||||
@@ -49,9 +49,19 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
highlightedText() {
|
||||
let regex = new RegExp(`(?<!<[^>]*)(${this.highlight})`, 'gi');
|
||||
const highlight = this.highlight;
|
||||
|
||||
return this.text.replace(regex, `<span class="${this.highlightClass}">${this.highlight}</span>`);
|
||||
const normalCharsRegex = /^[^A-Za-z0-9]+$/g;
|
||||
|
||||
const newHighLight = normalCharsRegex.test(highlight)
|
||||
? `\\${highlight}`
|
||||
: highlight;
|
||||
|
||||
const highlightRegex = new RegExp(`(?<!<[^>]*)(${newHighLight})`, 'gi');
|
||||
|
||||
const replacement = `<span class="${this.highlightClass}">${highlight}</span>`;
|
||||
|
||||
return this.text.replace(highlightRegex, replacement);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user