Compare commits
6 Commits
hide-gripp
...
v2.1.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8e70cd831 | ||
|
|
4958594336 | ||
|
|
a96490da22 | ||
|
|
532cec1531 | ||
|
|
a11a4a23e1 | ||
|
|
1e4d585e9d |
@@ -2,7 +2,7 @@ version: 2.1
|
||||
executors:
|
||||
pw-focal-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:v1.29.0-focal
|
||||
- image: mcr.microsoft.com/playwright:v1.25.2-focal
|
||||
environment:
|
||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
|
||||
|
||||
2
.github/workflows/e2e-couchdb.yml
vendored
2
.github/workflows/e2e-couchdb.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: npx playwright@1.29.0 install
|
||||
- run: npx playwright@1.25.2 install
|
||||
- run: npm install
|
||||
- run: sh src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh
|
||||
- run: npm run test:e2e:couchdb
|
||||
|
||||
2
.github/workflows/e2e-pr.yml
vendored
2
.github/workflows/e2e-pr.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: npx playwright@1.29.0 install
|
||||
- run: npx playwright@1.25.2 install
|
||||
- run: npx playwright install chrome-beta
|
||||
- run: npm install
|
||||
- run: npm run test:e2e:full
|
||||
|
||||
@@ -8,7 +8,7 @@ This document is designed to capture on the What, Why, and How's of writing and
|
||||
|
||||
1. [Getting Started](#getting-started)
|
||||
2. [Types of Testing](#types-of-e2e-testing)
|
||||
3. [Architecture](#test-architecture-and-ci)
|
||||
3. [Architecture](#architecture)
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -400,23 +400,3 @@ A single e2e test in Open MCT is extended to run:
|
||||
- Tests won't start because 'Error: <http://localhost:8080/># is already used...'
|
||||
This error will appear when running the tests locally. Sometimes, the webserver is left in an orphaned state and needs to be cleaned up. To clear up the orphaned webserver, execute the following from your Terminal:
|
||||
```lsof -n -i4TCP:8080 | awk '{print$2}' | tail -1 | xargs kill -9```
|
||||
|
||||
### Upgrading Playwright
|
||||
|
||||
In order to upgrade from one version of Playwright to another, the version should be updated in several places in both `openmct` and `openmct-yamcs` repos. An easy way to identify these locations is to search for the current version in all files and find/replace.
|
||||
|
||||
For reference, all of the locations where the version should be updated are listed below:
|
||||
|
||||
#### **In `openmct`:**
|
||||
|
||||
- `package.json`
|
||||
- Both packages `@playwright/test` and `playwright-core` should be updated to the same target version.
|
||||
- `.circleci/config.yml`
|
||||
- `.github/workflows/e2e-couchdb.yml`
|
||||
- `.github/workflows/e2e-pr.yml`
|
||||
|
||||
#### **In `openmct-yamcs`:**
|
||||
|
||||
- `package.json`
|
||||
- `@playwright/test` should be updated to the target version.
|
||||
- `.github/workflows/yamcs-quickstart-e2e.yml`
|
||||
|
||||
@@ -44,6 +44,7 @@ async function createNotebookAndEntry(page, iterations = 1) {
|
||||
const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = ${iteration}`;
|
||||
await page.locator(entryLocator).click();
|
||||
await page.locator(entryLocator).fill(`Entry ${iteration}`);
|
||||
await page.locator(entryLocator).press('Enter');
|
||||
}
|
||||
|
||||
return notebook;
|
||||
|
||||
@@ -156,7 +156,7 @@ async function turnOffAutoscale(page) {
|
||||
await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click();
|
||||
|
||||
// uncheck autoscale
|
||||
await page.getByRole('listitem').filter({ hasText: 'Auto scale' }).getByRole('checkbox').uncheck();
|
||||
await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"] >> nth=1').uncheck();
|
||||
|
||||
// save
|
||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||
|
||||
@@ -205,8 +205,7 @@ async function enableEditMode(page) {
|
||||
*/
|
||||
async function enableLogMode(page) {
|
||||
// turn on log mode
|
||||
await page.getByRole('listitem').filter({ hasText: 'Log mode' }).getByRole('checkbox').check();
|
||||
// await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().check();
|
||||
await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().check();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +213,7 @@ async function enableLogMode(page) {
|
||||
*/
|
||||
async function disableLogMode(page) {
|
||||
// turn off log mode
|
||||
await page.getByRole('listitem').filter({ hasText: 'Log mode' }).getByRole('checkbox').uncheck();
|
||||
await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().uncheck();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
Tests to verify log plot functionality. Note this test suite if very much under active development and should not
|
||||
necessarily be used for reference when writing new tests in this area.
|
||||
*/
|
||||
|
||||
const { test, expect } = require('../../../../pluginFixtures');
|
||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
||||
|
||||
test.describe('Overlay Plot', () => {
|
||||
test('Plot legend color is in sync with plot series color', async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: "Overlay Plot"
|
||||
});
|
||||
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
|
||||
await page.goto(overlayPlot.url);
|
||||
|
||||
// navigate to plot series color palette
|
||||
await page.click('.l-browse-bar__actions__edit');
|
||||
await page.locator('li.c-tree__item.menus-to-left .c-disclosure-triangle').click();
|
||||
await page.locator('.c-click-swatch--menu').click();
|
||||
await page.locator('.c-palette__item[style="background: rgb(255, 166, 61);"]').click();
|
||||
|
||||
// gets color for swatch located in legend
|
||||
const element = await page.waitForSelector('.plot-series-color-swatch');
|
||||
const color = await element.evaluate((el) => {
|
||||
return window.getComputedStyle(el).getPropertyValue('background-color');
|
||||
});
|
||||
|
||||
expect(color).toBe('rgb(255, 166, 61)');
|
||||
});
|
||||
test('The elements pool supports dragging series into multiple y-axis buckets', async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: "Overlay Plot"
|
||||
});
|
||||
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
name: 'swg a',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
name: 'swg b',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
name: 'swg c',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
name: 'swg d',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
name: 'swg e',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
|
||||
await page.goto(overlayPlot.url);
|
||||
await page.click('button[title="Edit"]');
|
||||
|
||||
// Expand the elements pool vertically
|
||||
await page.locator('.l-pane__handle').nth(2).hover({ trial: true });
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(0, 100);
|
||||
await page.mouse.up();
|
||||
|
||||
await page.locator('#inspector-elements-tree >> text=swg a').dragTo(page.locator('[aria-label="Element Item Group"]').nth(1));
|
||||
await page.locator('#inspector-elements-tree >> text=swg c').dragTo(page.locator('[aria-label="Element Item Group"]').nth(1));
|
||||
await page.locator('#inspector-elements-tree >> text=swg e').dragTo(page.locator('[aria-label="Element Item Group"]').nth(1));
|
||||
await page.locator('#inspector-elements-tree >> text=swg b').dragTo(page.locator('[aria-label="Element Item Group"]').nth(2));
|
||||
const elementsTree = await page.locator('#inspector-elements-tree').allInnerTexts();
|
||||
expect(elementsTree.join('').split('\n')).toEqual([
|
||||
"Y Axis 1",
|
||||
"swg d",
|
||||
"Y Axis 2",
|
||||
"swg e",
|
||||
"swg c",
|
||||
"swg a",
|
||||
"Y Axis 3",
|
||||
"swg b"
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "2.1.5-SNAPSHOT",
|
||||
"version": "2.1.5",
|
||||
"description": "The Open MCT core platform",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.18.9",
|
||||
"@braintree/sanitize-url": "6.0.2",
|
||||
"@percy/cli": "1.16.0",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.29.0",
|
||||
"@playwright/test": "1.25.2",
|
||||
"@types/jasmine": "4.3.1",
|
||||
"@types/lodash": "4.14.191",
|
||||
"babel-loader": "9.1.0",
|
||||
@@ -48,7 +48,7 @@
|
||||
"moment-timezone": "0.5.40",
|
||||
"nyc": "15.1.0",
|
||||
"painterro": "1.2.78",
|
||||
"playwright-core": "1.29.0",
|
||||
"playwright-core": "1.25.2",
|
||||
"plotly.js-basic-dist": "2.14.0",
|
||||
"plotly.js-gl2d-dist": "2.14.0",
|
||||
"printj": "1.3.1",
|
||||
|
||||
@@ -193,23 +193,27 @@ export default class ObjectAPI {
|
||||
* @memberof module:openmct.ObjectProvider#
|
||||
* @param {string} key the key for the domain object to load
|
||||
* @param {AbortController.signal} abortSignal (optional) signal to abort fetch requests
|
||||
* @param {boolean} forceRemote defaults to false. If true, will skip cached and
|
||||
* dirty/in-transaction objects use and the provider.get method
|
||||
* @returns {Promise} a promise which will resolve when the domain object
|
||||
* has been saved, or be rejected if it cannot be saved
|
||||
*/
|
||||
get(identifier, abortSignal) {
|
||||
get(identifier, abortSignal, forceRemote = false) {
|
||||
let keystring = this.makeKeyString(identifier);
|
||||
|
||||
if (this.cache[keystring] !== undefined) {
|
||||
return this.cache[keystring];
|
||||
}
|
||||
if (!forceRemote) {
|
||||
if (this.cache[keystring] !== undefined) {
|
||||
return this.cache[keystring];
|
||||
}
|
||||
|
||||
identifier = utils.parseKeyString(identifier);
|
||||
identifier = utils.parseKeyString(identifier);
|
||||
|
||||
if (this.isTransactionActive()) {
|
||||
let dirtyObject = this.transaction.getDirtyObject(identifier);
|
||||
if (this.isTransactionActive()) {
|
||||
let dirtyObject = this.transaction.getDirtyObject(identifier);
|
||||
|
||||
if (dirtyObject) {
|
||||
return Promise.resolve(dirtyObject);
|
||||
if (dirtyObject) {
|
||||
return Promise.resolve(dirtyObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +395,6 @@ export default class ObjectAPI {
|
||||
lastPersistedTime = domainObject.persisted;
|
||||
const persistedTime = Date.now();
|
||||
this.#mutate(domainObject, 'persisted', persistedTime);
|
||||
|
||||
savedObjectPromise = provider.update(domainObject);
|
||||
}
|
||||
|
||||
@@ -399,7 +402,7 @@ export default class ObjectAPI {
|
||||
savedObjectPromise.then(response => {
|
||||
savedResolve(response);
|
||||
}).catch((error) => {
|
||||
if (lastPersistedTime !== undefined) {
|
||||
if (!isNewObject) {
|
||||
this.#mutate(domainObject, 'persisted', lastPersistedTime);
|
||||
}
|
||||
|
||||
@@ -412,11 +415,12 @@ export default class ObjectAPI {
|
||||
|
||||
return result.catch(async (error) => {
|
||||
if (error instanceof this.errors.Conflict) {
|
||||
this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
|
||||
// Synchronized objects will resolve their own conflicts
|
||||
if (this.SYNCHRONIZED_OBJECT_TYPES.includes(domainObject.type)) {
|
||||
this.openmct.notifications.info(`Conflict detected while saving "${this.makeKeyString(domainObject.name)}", attempting to resolve`);
|
||||
} else {
|
||||
this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
|
||||
|
||||
// Synchronized objects will resolve their own conflicts, so
|
||||
// bypass the refresh here and throw the error.
|
||||
if (!this.SYNCHRONIZED_OBJECT_TYPES.includes(domainObject.type)) {
|
||||
if (this.isTransactionActive()) {
|
||||
this.endTransaction();
|
||||
}
|
||||
|
||||
@@ -788,7 +788,7 @@ export default {
|
||||
}
|
||||
},
|
||||
persistVisibleLayers() {
|
||||
if (this.domainObject.configuration) {
|
||||
if (this.domainObject.configuration && this.openmct.objects.supportsMutation(this.domainObject.identifier)) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.layers', this.layers);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ function copyRelatedMetadata(metadata) {
|
||||
return copiedMetadata;
|
||||
}
|
||||
|
||||
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
|
||||
export default class RelatedTelemetry {
|
||||
|
||||
constructor(openmct, domainObject, telemetryKeys) {
|
||||
@@ -88,9 +89,31 @@ export default class RelatedTelemetry {
|
||||
this[key].historicalDomainObject = await this._openmct.objects.get(this[key].historical.telemetryObjectId);
|
||||
|
||||
this[key].requestLatestFor = async (datum) => {
|
||||
const options = {
|
||||
// We need to create a throwaway time context and pass it along
|
||||
// as a request option. We do this to "trick" the Time API
|
||||
// into thinking we are in fixed time mode in order to bypass this logic:
|
||||
// https://github.com/akhenry/openmct-yamcs/blob/1060d42ebe43bf346dac0f6a8068cb288ade4ba4/src/providers/historical-telemetry-provider.js#L59
|
||||
// Context: https://github.com/akhenry/openmct-yamcs/pull/217
|
||||
const ephemeralContext = new IndependentTimeContext(
|
||||
this._openmct,
|
||||
this._openmct.time,
|
||||
[this[key].historicalDomainObject]
|
||||
);
|
||||
|
||||
// Stop following the global context, stop the clock,
|
||||
// and set bounds.
|
||||
ephemeralContext.resetContext();
|
||||
const newBounds = {
|
||||
start: this._openmct.time.bounds().start,
|
||||
end: this._parseTime(datum),
|
||||
end: this._parseTime(datum)
|
||||
};
|
||||
ephemeralContext.stopClock();
|
||||
ephemeralContext.bounds(newBounds);
|
||||
|
||||
const options = {
|
||||
start: newBounds.start,
|
||||
end: newBounds.end,
|
||||
timeContext: ephemeralContext,
|
||||
strategy: 'latest'
|
||||
};
|
||||
let results = await this._openmct.telemetry
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<Sidebar
|
||||
ref="sidebar"
|
||||
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
|
||||
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
|
||||
:class="sidebarClasses"
|
||||
:default-page-id="defaultPageId"
|
||||
:selected-page-id="getSelectedPageId()"
|
||||
:default-section-id="defaultSectionId"
|
||||
@@ -123,6 +123,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedPage && !selectedPage.isLocked"
|
||||
:class="{ 'disabled': activeTransaction }"
|
||||
class="c-notebook__drag-area icon-plus"
|
||||
@click="newEntry()"
|
||||
@dragover="dragOver"
|
||||
@@ -133,6 +134,11 @@
|
||||
To start a new entry, click here or drag and drop any object
|
||||
</span>
|
||||
</div>
|
||||
<progress-bar
|
||||
v-if="savingTransaction"
|
||||
class="c-telemetry-table__progress-bar"
|
||||
:model="{ progressPerc: undefined }"
|
||||
/>
|
||||
<div
|
||||
v-if="selectedPage && selectedPage.isLocked"
|
||||
class="c-notebook__page-locked"
|
||||
@@ -183,6 +189,7 @@ import NotebookEntry from './NotebookEntry.vue';
|
||||
import Search from '@/ui/components/search.vue';
|
||||
import SearchResults from './SearchResults.vue';
|
||||
import Sidebar from './Sidebar.vue';
|
||||
import ProgressBar from '../../../ui/components/ProgressBar.vue';
|
||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSectionId, setDefaultNotebookPageId } from '../utils/notebook-storage';
|
||||
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
||||
import { saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from '../utils/notebook-image';
|
||||
@@ -200,7 +207,8 @@ export default {
|
||||
NotebookEntry,
|
||||
Search,
|
||||
SearchResults,
|
||||
Sidebar
|
||||
Sidebar,
|
||||
ProgressBar
|
||||
},
|
||||
inject: ['agent', 'openmct', 'snapshotContainer'],
|
||||
props: {
|
||||
@@ -225,7 +233,9 @@ export default {
|
||||
showNav: false,
|
||||
sidebarCoversEntries: false,
|
||||
filteredAndSortedEntries: [],
|
||||
notebookAnnotations: {}
|
||||
notebookAnnotations: {},
|
||||
activeTransaction: false,
|
||||
savingTransaction: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -270,6 +280,20 @@ export default {
|
||||
|
||||
return this.sections[0];
|
||||
},
|
||||
sidebarClasses() {
|
||||
let sidebarClasses = [];
|
||||
if (this.showNav) {
|
||||
sidebarClasses.push('is-expanded');
|
||||
}
|
||||
|
||||
if (this.sidebarCoversEntries) {
|
||||
sidebarClasses.push('c-drawer--overlays');
|
||||
} else {
|
||||
sidebarClasses.push('c-drawer--push');
|
||||
}
|
||||
|
||||
return sidebarClasses;
|
||||
},
|
||||
showLockButton() {
|
||||
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||
|
||||
@@ -297,6 +321,8 @@ export default {
|
||||
this.formatSidebar();
|
||||
this.setSectionAndPageFromUrl();
|
||||
|
||||
this.transaction = null;
|
||||
|
||||
window.addEventListener('orientationchange', this.formatSidebar);
|
||||
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
|
||||
this.filterAndSortEntries();
|
||||
@@ -749,6 +775,7 @@ export default {
|
||||
return section.id;
|
||||
},
|
||||
async newEntry(embed = null) {
|
||||
this.startTransaction();
|
||||
this.resetSearch();
|
||||
const notebookStorage = this.createNotebookStorageObject();
|
||||
this.updateDefaultNotebook(notebookStorage);
|
||||
@@ -891,20 +918,34 @@ export default {
|
||||
},
|
||||
startTransaction() {
|
||||
if (!this.openmct.objects.isTransactionActive()) {
|
||||
this.activeTransaction = true;
|
||||
this.transaction = this.openmct.objects.startTransaction();
|
||||
}
|
||||
},
|
||||
async saveTransaction() {
|
||||
if (this.transaction !== undefined) {
|
||||
await this.transaction.commit();
|
||||
this.openmct.objects.endTransaction();
|
||||
if (this.transaction !== null) {
|
||||
this.savingTransaction = true;
|
||||
try {
|
||||
await this.transaction.commit();
|
||||
} finally {
|
||||
this.endTransaction();
|
||||
}
|
||||
}
|
||||
},
|
||||
async cancelTransaction() {
|
||||
if (this.transaction !== undefined) {
|
||||
await this.transaction.cancel();
|
||||
this.openmct.objects.endTransaction();
|
||||
if (this.transaction !== null) {
|
||||
try {
|
||||
await this.transaction.cancel();
|
||||
} finally {
|
||||
this.endTransaction();
|
||||
}
|
||||
}
|
||||
},
|
||||
endTransaction() {
|
||||
this.openmct.objects.endTransaction();
|
||||
this.transaction = null;
|
||||
this.savingTransaction = false;
|
||||
this.activeTransaction = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,19 +74,22 @@ async function resolveNotebookTagConflicts(localAnnotation, openmct) {
|
||||
|
||||
async function resolveNotebookEntryConflicts(localMutable, openmct) {
|
||||
if (localMutable.configuration.entries) {
|
||||
const FORCE_REMOTE = true;
|
||||
const localEntries = structuredClone(localMutable.configuration.entries);
|
||||
const remoteMutable = await openmct.objects.getMutable(localMutable.identifier);
|
||||
applyLocalEntries(remoteMutable, localEntries, openmct);
|
||||
openmct.objects.destroyMutable(remoteMutable);
|
||||
const remoteObject = await openmct.objects.get(localMutable.identifier, undefined, FORCE_REMOTE);
|
||||
|
||||
return applyLocalEntries(remoteObject, localEntries, openmct);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyLocalEntries(mutable, entries, openmct) {
|
||||
function applyLocalEntries(remoteObject, entries, openmct) {
|
||||
let shouldSave = false;
|
||||
|
||||
Object.entries(entries).forEach(([sectionKey, pagesInSection]) => {
|
||||
Object.entries(pagesInSection).forEach(([pageKey, localEntries]) => {
|
||||
const remoteEntries = mutable.configuration.entries[sectionKey][pageKey];
|
||||
const remoteEntries = remoteObject.configuration.entries[sectionKey][pageKey];
|
||||
const mergedEntries = [].concat(remoteEntries);
|
||||
let shouldMutate = false;
|
||||
|
||||
@@ -110,8 +113,13 @@ function applyLocalEntries(mutable, entries, openmct) {
|
||||
});
|
||||
|
||||
if (shouldMutate) {
|
||||
openmct.objects.mutate(mutable, `configuration.entries.${sectionKey}.${pageKey}`, mergedEntries);
|
||||
shouldSave = true;
|
||||
openmct.objects.mutate(remoteObject, `configuration.entries.${sectionKey}.${pageKey}`, mergedEntries);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (shouldSave) {
|
||||
return openmct.objects.save(remoteObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ export default function () {
|
||||
}
|
||||
|
||||
let wrappedFunction = openmct.objects.get;
|
||||
openmct.objects.get = function migrate(identifier) {
|
||||
return wrappedFunction.apply(openmct.objects, [identifier])
|
||||
openmct.objects.get = function migrate() {
|
||||
return wrappedFunction.apply(openmct.objects, [...arguments])
|
||||
.then(function (object) {
|
||||
if (needsMigration(object)) {
|
||||
migrateObject(object)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
connected = false;
|
||||
// stop listening for events
|
||||
couchEventSource.removeEventListener('message', self.onCouchMessage);
|
||||
couchEventSource.close();
|
||||
console.debug('🚪 Closed couch connection 🚪');
|
||||
|
||||
return;
|
||||
|
||||
@@ -96,8 +96,13 @@ class CouchObjectProvider {
|
||||
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
||||
//TODO: Optimize this so that we don't 'get' the object if it's current revision (from this.objectQueue) is the same as the one we already have.
|
||||
let observersForObject = this.observers[keyString];
|
||||
let isInTransaction = false;
|
||||
|
||||
if (observersForObject) {
|
||||
if (this.openmct.objects.isTransactionActive()) {
|
||||
isInTransaction = this.openmct.objects.transaction.getDirtyObject(objectIdentifier);
|
||||
}
|
||||
|
||||
if (observersForObject && !isInTransaction) {
|
||||
observersForObject.forEach(async (observer) => {
|
||||
const updatedObject = await this.get(objectIdentifier);
|
||||
if (this.isSynchronizedObject(updatedObject)) {
|
||||
@@ -219,7 +224,12 @@ class CouchObjectProvider {
|
||||
console.error(error.message);
|
||||
throw new Error(`CouchDB Error - No response"`);
|
||||
} else {
|
||||
console.error(error.message);
|
||||
if (body?.model && isNotebookOrAnnotationType(body.model)) {
|
||||
// warn since we handle conflicts for notebooks
|
||||
console.warn(error.message);
|
||||
} else {
|
||||
console.error(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -34,26 +34,23 @@
|
||||
@legendHoverChanged="legendHoverChanged"
|
||||
/>
|
||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
||||
<div
|
||||
v-if="seriesModels.length"
|
||||
class="u-contents"
|
||||
>
|
||||
<y-axis
|
||||
v-for="(yAxis, index) in yAxesIds"
|
||||
:id="yAxis.id"
|
||||
:key="`yAxis-${index}`"
|
||||
:multiple-left-axes="multipleLeftAxes"
|
||||
:position="yAxis.id > 2 ? 'right' : 'left'"
|
||||
:class="{'plot-yaxis-right': yAxis.id > 2}"
|
||||
:tick-width="yAxis.tickWidth"
|
||||
:plot-left-tick-width="yAxis.id > 2 ? yAxis.tickWidth: plotLeftTickWidth"
|
||||
@yKeyChanged="setYAxisKey"
|
||||
@tickWidthChanged="onTickWidthChange"
|
||||
/>
|
||||
</div>
|
||||
<y-axis
|
||||
v-if="seriesModels.length > 0"
|
||||
:tick-width="tickWidth"
|
||||
:single-series="seriesModels.length === 1"
|
||||
:has-same-range-value="hasSameRangeValue"
|
||||
:series-model="seriesModels[0]"
|
||||
:style="{
|
||||
left: (plotWidth - tickWidth) + 'px'
|
||||
}"
|
||||
@yKeyChanged="setYAxisKey"
|
||||
@tickWidthChanged="onTickWidthChange"
|
||||
/>
|
||||
<div
|
||||
class="gl-plot-wrapper-display-area-and-x-axis"
|
||||
:style="xAxisStyle"
|
||||
:style="{
|
||||
left: (plotWidth + 20) + 'px'
|
||||
}"
|
||||
>
|
||||
|
||||
<div class="gl-plot-display-area has-local-controls has-cursor-guides">
|
||||
@@ -72,12 +69,9 @@
|
||||
/>
|
||||
|
||||
<mct-ticks
|
||||
v-for="(yAxis, index) in yAxesIds"
|
||||
v-show="gridLines"
|
||||
:key="`yAxis-gridlines-${index}`"
|
||||
:axis-type="'yAxis'"
|
||||
:position="'bottom'"
|
||||
:axis-id="yAxis.id"
|
||||
@plotTickWidth="onTickWidthChange"
|
||||
/>
|
||||
|
||||
@@ -220,7 +214,6 @@ import YAxis from "./axis/YAxis.vue";
|
||||
import _ from "lodash";
|
||||
|
||||
const OFFSET_THRESHOLD = 10;
|
||||
const AXES_PADDING = 20;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -276,6 +269,7 @@ export default {
|
||||
altPressed: false,
|
||||
highlights: [],
|
||||
lockHighlightPoint: false,
|
||||
tickWidth: 0,
|
||||
yKeyOptions: [],
|
||||
yAxisLabel: '',
|
||||
rectangles: [],
|
||||
@@ -290,31 +284,12 @@ export default {
|
||||
isTimeOutOfSync: false,
|
||||
showLimitLineLabels: this.limitLineLabels,
|
||||
isFrozenOnMouseDown: false,
|
||||
hasSameRangeValue: true,
|
||||
cursorGuide: this.initCursorGuide,
|
||||
gridLines: this.initGridLines,
|
||||
yAxes: []
|
||||
gridLines: this.initGridLines
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
xAxisStyle() {
|
||||
const rightAxis = this.yAxesIds.find(yAxis => yAxis.id > 2);
|
||||
const leftOffset = this.multipleLeftAxes ? 2 * AXES_PADDING : AXES_PADDING;
|
||||
let style = {
|
||||
left: `${this.plotLeftTickWidth + leftOffset}px`
|
||||
};
|
||||
|
||||
if (rightAxis) {
|
||||
style.right = `${rightAxis.tickWidth + AXES_PADDING}px`;
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
yAxesIds() {
|
||||
return this.yAxes.filter(yAxis => yAxis.seriesCount > 0);
|
||||
},
|
||||
multipleLeftAxes() {
|
||||
return this.yAxes.filter(yAxis => yAxis.seriesCount > 0 && yAxis.id <= 2).length > 1;
|
||||
},
|
||||
isNestedWithinAStackedPlot() {
|
||||
const isNavigatedObject = this.openmct.router.isNavigatedObject([this.domainObject].concat(this.path));
|
||||
|
||||
@@ -337,17 +312,8 @@ export default {
|
||||
return 'plot-legend-collapsed';
|
||||
}
|
||||
},
|
||||
plotLeftTickWidth() {
|
||||
let leftTickWidth = 0;
|
||||
this.yAxes.forEach((yAxis) => {
|
||||
if (yAxis.id > 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
leftTickWidth = leftTickWidth + yAxis.tickWidth;
|
||||
});
|
||||
|
||||
return this.plotTickWidth || leftTickWidth;
|
||||
plotWidth() {
|
||||
return this.plotTickWidth || this.tickWidth;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -376,20 +342,6 @@ export default {
|
||||
|
||||
this.config = this.getConfig();
|
||||
this.legend = this.config.legend;
|
||||
this.yAxes = [{
|
||||
id: this.config.yAxis.id,
|
||||
seriesCount: 0,
|
||||
tickWidth: 0
|
||||
}];
|
||||
if (this.config.additionalYAxes) {
|
||||
this.yAxes = this.yAxes.concat(this.config.additionalYAxes.map(yAxis => {
|
||||
return {
|
||||
id: yAxis.id,
|
||||
seriesCount: 0,
|
||||
tickWidth: 0
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
if (this.isNestedWithinAStackedPlot) {
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
@@ -431,10 +383,8 @@ export default {
|
||||
},
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
||||
this.followTimeContext();
|
||||
|
||||
},
|
||||
followTimeContext() {
|
||||
this.updateDisplayBounds(this.timeContext.bounds());
|
||||
@@ -467,13 +417,12 @@ export default {
|
||||
return config;
|
||||
},
|
||||
addSeries(series, index) {
|
||||
const yAxisId = series.get('yAxisId');
|
||||
this.updateAxisUsageCount(yAxisId, 1);
|
||||
this.$set(this.seriesModels, index, series);
|
||||
this.listenTo(series, 'change:xKey', (xKey) => {
|
||||
this.setDisplayRange(series, xKey);
|
||||
}, this);
|
||||
this.listenTo(series, 'change:yKey', () => {
|
||||
this.checkSameRangeValue();
|
||||
this.loadSeriesData(series);
|
||||
}, this);
|
||||
|
||||
@@ -481,21 +430,20 @@ export default {
|
||||
this.loadSeriesData(series);
|
||||
}, this);
|
||||
|
||||
this.checkSameRangeValue();
|
||||
this.loadSeriesData(series);
|
||||
},
|
||||
|
||||
removeSeries(plotSeries, index) {
|
||||
const yAxisId = plotSeries.get('yAxisId');
|
||||
this.updateAxisUsageCount(yAxisId, -1);
|
||||
this.seriesModels.splice(index, 1);
|
||||
this.stopListening(plotSeries);
|
||||
checkSameRangeValue() {
|
||||
this.hasSameRangeValue = this.seriesModels.every((model) => {
|
||||
return model.get('yKey') === this.seriesModels[0].get('yKey');
|
||||
});
|
||||
},
|
||||
|
||||
updateAxisUsageCount(yAxisId, updateCountBy) {
|
||||
const foundYAxis = this.yAxes.find(yAxis => yAxis.id === yAxisId);
|
||||
if (foundYAxis) {
|
||||
foundYAxis.seriesCount = foundYAxis.seriesCount + updateCountBy;
|
||||
}
|
||||
removeSeries(plotSeries, index) {
|
||||
this.seriesModels.splice(index, 1);
|
||||
this.checkSameRangeValue();
|
||||
this.stopListening(plotSeries);
|
||||
},
|
||||
|
||||
loadSeriesData(series) {
|
||||
@@ -725,7 +673,6 @@ export default {
|
||||
|
||||
// Setup canvas etc.
|
||||
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
|
||||
//TODO: handle yScale, zoom/pan for all yAxes
|
||||
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
|
||||
|
||||
this.pan = undefined;
|
||||
@@ -743,9 +690,6 @@ export default {
|
||||
|
||||
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
||||
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
|
||||
this.config.additionalYAxes.forEach(yAxis => {
|
||||
this.listenTo(yAxis, 'change:displayRange', this.onYAxisChange, this);
|
||||
});
|
||||
},
|
||||
|
||||
onXAxisChange(displayBounds) {
|
||||
@@ -760,24 +704,20 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
onTickWidthChange(data, fromDifferentObject) {
|
||||
const {width, yAxisId} = data;
|
||||
if (yAxisId) {
|
||||
const index = this.yAxes.findIndex(yAxis => yAxis.id === yAxisId);
|
||||
if (fromDifferentObject) {
|
||||
onTickWidthChange(width, fromDifferentObject) {
|
||||
if (fromDifferentObject) {
|
||||
// Always accept tick width if it comes from a different object.
|
||||
this.yAxes[index].tickWidth = width;
|
||||
} else {
|
||||
this.tickWidth = width;
|
||||
} else {
|
||||
// Otherwise, only accept tick with if it's larger.
|
||||
const newWidth = Math.max(width, this.yAxes[index].tickWidth);
|
||||
if (newWidth !== this.yAxes[index].tickWidth) {
|
||||
this.yAxes[index].tickWidth = newWidth;
|
||||
}
|
||||
const newWidth = Math.max(width, this.tickWidth);
|
||||
if (newWidth !== this.tickWidth) {
|
||||
this.tickWidth = newWidth;
|
||||
}
|
||||
|
||||
const id = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.$emit('plotTickWidth', this.yAxes[index].tickWidth, id);
|
||||
}
|
||||
|
||||
const id = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.$emit('plotTickWidth', this.tickWidth, id);
|
||||
},
|
||||
|
||||
trackMousePosition(event) {
|
||||
@@ -1168,9 +1108,8 @@ export default {
|
||||
this.userViewportChangeEnd();
|
||||
},
|
||||
|
||||
setYAxisKey(yKey, yAxisId) {
|
||||
const seriesForYAxis = this.config.series.models.filter((model => model.get('yAxisId') === yAxisId));
|
||||
seriesForYAxis.forEach(model => model.set('yKey', yKey));
|
||||
setYAxisKey(yKey) {
|
||||
this.config.series.models[0].set('yKey', yKey);
|
||||
},
|
||||
|
||||
pause() {
|
||||
|
||||
@@ -103,12 +103,6 @@ export default {
|
||||
return 6;
|
||||
}
|
||||
},
|
||||
axisId: {
|
||||
type: Number,
|
||||
default() {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
position: {
|
||||
required: true,
|
||||
type: String,
|
||||
@@ -151,15 +145,7 @@ export default {
|
||||
throw new Error('config is missing');
|
||||
}
|
||||
|
||||
if (this.axisType === 'yAxis') {
|
||||
if (this.axisId && this.axisId !== config.yAxis.id) {
|
||||
return config.additionalYAxes.find(axis => axis.id === this.axisId);
|
||||
} else {
|
||||
return config.yAxis;
|
||||
}
|
||||
} else {
|
||||
return config[this.axisType];
|
||||
}
|
||||
return config[this.axisType];
|
||||
},
|
||||
/**
|
||||
* Determine whether ticks should be regenerated for a given range.
|
||||
@@ -272,10 +258,7 @@ export default {
|
||||
}, 0));
|
||||
|
||||
this.tickWidth = tickWidth;
|
||||
this.$emit('plotTickWidth', {
|
||||
width: tickWidth,
|
||||
yAxisId: this.axisType === 'yAxis' ? this.axisId : ''
|
||||
});
|
||||
this.$emit('plotTickWidth', tickWidth);
|
||||
this.shouldCheckWidth = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="loaded"
|
||||
class="gl-plot-axis-area gl-plot-y has-local-controls js-plot-y-axis"
|
||||
:style="yAxisStyle"
|
||||
class="gl-plot-axis-area gl-plot-y has-local-controls"
|
||||
:style="{
|
||||
width: (tickWidth + 20) + 'px'
|
||||
}"
|
||||
>
|
||||
|
||||
<div
|
||||
@@ -50,7 +52,6 @@
|
||||
</select>
|
||||
|
||||
<mct-ticks
|
||||
:axis-id="id"
|
||||
:axis-type="'yAxis'"
|
||||
class="gl-plot-ticks"
|
||||
:position="'top'"
|
||||
@@ -62,10 +63,6 @@
|
||||
<script>
|
||||
import MctTicks from "../MctTicks.vue";
|
||||
import configStore from "../configuration/ConfigStore";
|
||||
import eventHelpers from "../lib/eventHelpers";
|
||||
|
||||
const AXIS_PADDING = 20;
|
||||
const AXIS_OFFSET = 5;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -73,10 +70,22 @@ export default {
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
singleSeries: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
hasSameRangeValue: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
seriesModel: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
tickWidth: {
|
||||
@@ -84,132 +93,37 @@ export default {
|
||||
default() {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
plotLeftTickWidth: {
|
||||
type: Number,
|
||||
default() {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
multipleLeftAxes: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'left';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
this.seriesModels = [];
|
||||
|
||||
return {
|
||||
yAxisLabel: 'none',
|
||||
loaded: false,
|
||||
yKeyOptions: [],
|
||||
hasSameRangeValue: true,
|
||||
singleSeries: true,
|
||||
mainYAxisId: null,
|
||||
hasAdditionalYAxes: false
|
||||
loaded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canShowYAxisLabel() {
|
||||
return this.singleSeries === true || this.hasSameRangeValue === true;
|
||||
},
|
||||
yAxisStyle() {
|
||||
let style = {
|
||||
width: `${this.tickWidth + AXIS_PADDING}px`
|
||||
};
|
||||
const multipleAxesPadding = this.multipleLeftAxes ? AXIS_PADDING : 0;
|
||||
|
||||
if (this.position === 'right') {
|
||||
style.left = `-${this.tickWidth + AXIS_PADDING}px`;
|
||||
} else {
|
||||
const thisIsTheSecondLeftAxis = (this.id - 1) > 0;
|
||||
if (this.multipleLeftAxes && thisIsTheSecondLeftAxis) {
|
||||
style.left = `${ this.plotLeftTickWidth - this.tickWidth - multipleAxesPadding - AXIS_OFFSET }px`;
|
||||
style['border-right'] = `1px solid`;
|
||||
} else {
|
||||
style.left = `${this.plotLeftTickWidth - this.tickWidth + multipleAxesPadding}px`;
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.initAxisAndSeriesConfig();
|
||||
this.yAxis = this.getYAxisFromConfig();
|
||||
this.loaded = true;
|
||||
this.setUpYAxisOptions();
|
||||
},
|
||||
methods: {
|
||||
initAxisAndSeriesConfig() {
|
||||
getYAxisFromConfig() {
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
let config = configStore.get(configId);
|
||||
if (config) {
|
||||
this.mainYAxisId = config.yAxis.id;
|
||||
this.hasAdditionalYAxes = config?.additionalYAxes.length;
|
||||
if (this.id && this.id !== this.mainYAxisId) {
|
||||
this.yAxis = config.additionalYAxes.find(yAxis => yAxis.id === this.id);
|
||||
} else {
|
||||
this.yAxis = config.yAxis;
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
||||
this.listenTo(this.config.series, 'reorder', this.addOrRemoveSeries, this);
|
||||
|
||||
this.config.series.models.forEach(this.addSeries, this);
|
||||
return config.yAxis;
|
||||
}
|
||||
},
|
||||
addOrRemoveSeries(series) {
|
||||
const yAxisId = this.series.get('yAxisId');
|
||||
if (yAxisId === this.id) {
|
||||
this.addSeries(series);
|
||||
} else {
|
||||
this.removeSeries(series);
|
||||
}
|
||||
},
|
||||
addSeries(series, index) {
|
||||
const yAxisId = series.get('yAxisId');
|
||||
const seriesIndex = this.seriesModels.findIndex(model => this.openmct.objects.areIdsEqual(model.get('identifier'), series.get('identifier')));
|
||||
|
||||
if (yAxisId === this.id && seriesIndex < 0) {
|
||||
this.seriesModels.push(series);
|
||||
this.checkRangeValueAndSingleSeries();
|
||||
this.setUpYAxisOptions();
|
||||
}
|
||||
},
|
||||
removeSeries(plotSeries) {
|
||||
const seriesIndex = this.seriesModels.findIndex(model => this.openmct.objects.areIdsEqual(model.get('identifier'), plotSeries.get('identifier')));
|
||||
if (seriesIndex > -1) {
|
||||
this.seriesModels.splice(seriesIndex, 1);
|
||||
this.checkRangeValueAndSingleSeries();
|
||||
this.setUpYAxisOptions();
|
||||
}
|
||||
},
|
||||
checkRangeValueAndSingleSeries() {
|
||||
this.hasSameRangeValue = this.seriesModels.every((model) => {
|
||||
return model.get('yKey') === this.seriesModels[0].get('yKey');
|
||||
});
|
||||
this.singleSeries = this.seriesModels.length === 1;
|
||||
},
|
||||
setUpYAxisOptions() {
|
||||
this.yKeyOptions = [];
|
||||
if (!this.seriesModels.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const seriesModel = this.seriesModels[0];
|
||||
if (seriesModel.metadata) {
|
||||
this.yKeyOptions = seriesModel.metadata
|
||||
if (this.seriesModel.metadata) {
|
||||
this.yKeyOptions = this.seriesModel.metadata
|
||||
.valuesForHints(['range'])
|
||||
.map(function (o) {
|
||||
return {
|
||||
@@ -221,22 +135,22 @@ export default {
|
||||
|
||||
// set yAxisLabel if none is set yet
|
||||
if (this.yAxisLabel === 'none') {
|
||||
this.yAxisLabel = this.yAxis.get('label');
|
||||
let yKey = this.seriesModel.model.yKey;
|
||||
let yKeyModel = this.yKeyOptions.filter(o => o.key === yKey)[0];
|
||||
|
||||
this.yAxisLabel = yKeyModel ? yKeyModel.name : '';
|
||||
}
|
||||
},
|
||||
toggleYAxisLabel() {
|
||||
let yAxisObject = this.yKeyOptions.filter(o => o.name === this.yAxisLabel)[0];
|
||||
|
||||
if (yAxisObject) {
|
||||
this.$emit('yKeyChanged', yAxisObject.key, this.id);
|
||||
this.$emit('yKeyChanged', yAxisObject.key);
|
||||
this.yAxis.set('label', this.yAxisLabel);
|
||||
}
|
||||
},
|
||||
onTickWidthChange(data) {
|
||||
this.$emit('tickWidthChanged', {
|
||||
width: data.width,
|
||||
yAxisId: this.id
|
||||
});
|
||||
onTickWidthChange(width) {
|
||||
this.$emit('tickWidthChanged', width);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,21 +98,7 @@ export default {
|
||||
this.limitLines = [];
|
||||
this.pointSets = [];
|
||||
this.alarmSets = [];
|
||||
const yAxisId = this.config.yAxis.get('id');
|
||||
this.offset = {
|
||||
[yAxisId]: {}
|
||||
};
|
||||
this.listenTo(this.config.yAxis, 'change:key', this.resetOffsetAndSeriesDataForYAxis.bind(this, yAxisId), this);
|
||||
this.listenTo(this.config.yAxis, 'change', this.updateLimitsAndDraw);
|
||||
if (this.config.additionalYAxes.length) {
|
||||
this.config.additionalYAxes.forEach(yAxis => {
|
||||
const id = yAxis.get('id');
|
||||
this.offset[id] = {};
|
||||
this.listenTo(yAxis, 'change', this.updateLimitsAndDraw);
|
||||
this.listenTo(this.config.yAxis, 'change:key', this.resetOffsetAndSeriesDataForYAxis.bind(this, id), this);
|
||||
});
|
||||
}
|
||||
|
||||
this.offset = {};
|
||||
this.seriesElements = new WeakMap();
|
||||
this.seriesLimits = new WeakMap();
|
||||
|
||||
@@ -125,7 +111,8 @@ export default {
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
||||
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
||||
|
||||
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
|
||||
this.listenTo(this.config.yAxis, 'change', this.updateLimitsAndDraw);
|
||||
this.listenTo(this.config.xAxis, 'change', this.updateLimitsAndDraw);
|
||||
this.config.series.forEach(this.onSeriesAdd, this);
|
||||
this.$emit('chartLoaded');
|
||||
@@ -237,31 +224,25 @@ export default {
|
||||
this.limitLines.forEach(line => line.destroy());
|
||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
||||
},
|
||||
resetOffsetAndSeriesDataForYAxis(yAxisId) {
|
||||
delete this.offset[yAxisId].x;
|
||||
delete this.offset[yAxisId].y;
|
||||
delete this.offset[yAxisId].xVal;
|
||||
delete this.offset[yAxisId].yVal;
|
||||
delete this.offset[yAxisId].xKey;
|
||||
delete this.offset[yAxisId].yKey;
|
||||
|
||||
const lines = this.lines.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||
lines.forEach(function (line) {
|
||||
clearOffset() {
|
||||
delete this.offset.x;
|
||||
delete this.offset.y;
|
||||
delete this.offset.xVal;
|
||||
delete this.offset.yVal;
|
||||
delete this.offset.xKey;
|
||||
delete this.offset.yKey;
|
||||
this.lines.forEach(function (line) {
|
||||
line.reset();
|
||||
});
|
||||
const limitLines = this.limitLines.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||
limitLines.forEach(function (line) {
|
||||
this.limitLines.forEach(function (line) {
|
||||
line.reset();
|
||||
});
|
||||
const pointSets = this.pointSets.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||
pointSets.forEach(function (pointSet) {
|
||||
this.pointSets.forEach(function (pointSet) {
|
||||
pointSet.reset();
|
||||
});
|
||||
},
|
||||
setOffset(offsetPoint, index, series) {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||
if (this.offset[yAxisId].x && this.offset[yAxisId].y) {
|
||||
if (this.offset.x && this.offset.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -270,20 +251,19 @@ export default {
|
||||
y: series.getYVal(offsetPoint)
|
||||
};
|
||||
|
||||
this.offset[yAxisId].x = function (x) {
|
||||
this.offset.x = function (x) {
|
||||
return x - offsets.x;
|
||||
}.bind(this);
|
||||
this.offset[yAxisId].y = function (y) {
|
||||
this.offset.y = function (y) {
|
||||
return y - offsets.y;
|
||||
}.bind(this);
|
||||
this.offset[yAxisId].xVal = function (point, pSeries) {
|
||||
return this.offset[yAxisId].x(pSeries.getXVal(point));
|
||||
this.offset.xVal = function (point, pSeries) {
|
||||
return this.offset.x(pSeries.getXVal(point));
|
||||
}.bind(this);
|
||||
this.offset[yAxisId].yVal = function (point, pSeries) {
|
||||
return this.offset[yAxisId].y(pSeries.getYVal(point));
|
||||
this.offset.yVal = function (point, pSeries) {
|
||||
return this.offset.y(pSeries.getYVal(point));
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
initializeCanvas(canvas, overlay) {
|
||||
this.canvas = canvas;
|
||||
this.overlay = overlay;
|
||||
@@ -331,15 +311,11 @@ export default {
|
||||
this.clearLimitLines(series);
|
||||
},
|
||||
lineForSeries(series) {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||
let offset = this.offset[yAxisId];
|
||||
|
||||
if (series.get('interpolate') === 'linear') {
|
||||
return new MCTChartLineLinear(
|
||||
series,
|
||||
this,
|
||||
offset
|
||||
this.offset
|
||||
);
|
||||
}
|
||||
|
||||
@@ -347,45 +323,33 @@ export default {
|
||||
return new MCTChartLineStepAfter(
|
||||
series,
|
||||
this,
|
||||
offset
|
||||
this.offset
|
||||
);
|
||||
}
|
||||
},
|
||||
limitLineForSeries(series) {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||
let offset = this.offset[yAxisId];
|
||||
|
||||
return new MCTChartAlarmLineSet(
|
||||
series,
|
||||
this,
|
||||
offset,
|
||||
this.offset,
|
||||
this.openmct.time.bounds()
|
||||
);
|
||||
},
|
||||
pointSetForSeries(series) {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||
let offset = this.offset[yAxisId];
|
||||
|
||||
if (series.get('markers')) {
|
||||
return new MCTChartPointSet(
|
||||
series,
|
||||
this,
|
||||
offset
|
||||
this.offset
|
||||
);
|
||||
}
|
||||
},
|
||||
alarmPointSetForSeries(series) {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||
let offset = this.offset[yAxisId];
|
||||
|
||||
if (series.get('alarmMarkers')) {
|
||||
return new MCTChartAlarmPointSet(
|
||||
series,
|
||||
this,
|
||||
offset
|
||||
this.offset
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -446,8 +410,8 @@ export default {
|
||||
this.seriesLimits.delete(series);
|
||||
}
|
||||
},
|
||||
canDraw(yAxisId) {
|
||||
if (!this.offset[yAxisId] || !this.offset[yAxisId].x || !this.offset[yAxisId].y) {
|
||||
canDraw() {
|
||||
if (!this.offset.x || !this.offset.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -470,31 +434,16 @@ export default {
|
||||
}
|
||||
|
||||
this.drawAPI.clear();
|
||||
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);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateViewport(yAxisId) {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
const xRange = this.config.xAxis.get('displayRange');
|
||||
let yRange;
|
||||
if (yAxisId === mainYAxisId) {
|
||||
yRange = this.config.yAxis.get('displayRange');
|
||||
} else {
|
||||
if (this.config.additionalYAxes.length) {
|
||||
const yAxisForId = this.config.additionalYAxes.find(yAxis => yAxis.get('id') === yAxisId);
|
||||
yRange = yAxisForId.get('displayRange');
|
||||
}
|
||||
if (this.canDraw()) {
|
||||
this.updateViewport();
|
||||
this.drawSeries();
|
||||
this.drawRectangles();
|
||||
this.drawHighlights();
|
||||
}
|
||||
},
|
||||
updateViewport() {
|
||||
const xRange = this.config.xAxis.get('displayRange');
|
||||
const yRange = this.config.yAxis.get('displayRange');
|
||||
|
||||
if (!xRange || !yRange) {
|
||||
return;
|
||||
@@ -505,10 +454,9 @@ export default {
|
||||
yRange.max - yRange.min
|
||||
];
|
||||
|
||||
let origin;
|
||||
origin = [
|
||||
this.offset[yAxisId].x(xRange.min),
|
||||
this.offset[yAxisId].y(yRange.min)
|
||||
const origin = [
|
||||
this.offset.x(xRange.min),
|
||||
this.offset.y(yRange.min)
|
||||
];
|
||||
|
||||
this.drawAPI.setDimensions(
|
||||
@@ -516,66 +464,38 @@ export default {
|
||||
origin
|
||||
);
|
||||
},
|
||||
matchByYAxisId(id, item) {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
let matchesId = false;
|
||||
|
||||
const series = item.series;
|
||||
if (series) {
|
||||
const seriesYAxisId = series.get('yAxisId') || mainYAxisId;
|
||||
|
||||
matchesId = seriesYAxisId === id;
|
||||
}
|
||||
|
||||
return matchesId;
|
||||
},
|
||||
drawSeries(id) {
|
||||
const lines = this.lines.filter(this.matchByYAxisId.bind(this, id));
|
||||
lines.forEach(this.drawLine, this);
|
||||
const pointSets = this.pointSets.filter(this.matchByYAxisId.bind(this, id));
|
||||
pointSets.forEach(this.drawPoints, this);
|
||||
const alarmSets = this.alarmSets.filter(this.matchByYAxisId.bind(this, id));
|
||||
alarmSets.forEach(this.drawAlarmPoints, this);
|
||||
drawSeries() {
|
||||
this.lines.forEach(this.drawLine, this);
|
||||
this.pointSets.forEach(this.drawPoints, this);
|
||||
this.alarmSets.forEach(this.drawAlarmPoints, this);
|
||||
},
|
||||
drawLimitLines() {
|
||||
this.config.series.models.forEach(series => {
|
||||
const yAxisId = series.get('yAxisId');
|
||||
this.drawLimitLinesForSeries(yAxisId, series);
|
||||
});
|
||||
},
|
||||
drawLimitLinesForSeries(yAxisId, series) {
|
||||
if (!this.canDraw(yAxisId)) {
|
||||
return;
|
||||
if (this.canDraw()) {
|
||||
this.updateViewport();
|
||||
|
||||
if (!this.drawAPI.origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
||||
let limitPointOverlap = [];
|
||||
this.limitLines.forEach((limitLine) => {
|
||||
let limitContainerEl = this.$refs.limitArea;
|
||||
limitLine.limits.forEach((limit) => {
|
||||
const showLabels = this.showLabels(limit.seriesKey);
|
||||
if (showLabels) {
|
||||
const overlap = this.getLimitOverlap(limit, limitPointOverlap);
|
||||
limitPointOverlap.push(overlap);
|
||||
let limitLabelEl = this.getLimitLabel(limit, overlap);
|
||||
limitContainerEl.appendChild(limitLabelEl);
|
||||
}
|
||||
|
||||
let limitEl = this.getLimitElement(limit);
|
||||
limitContainerEl.appendChild(limitEl);
|
||||
|
||||
}, this);
|
||||
});
|
||||
}
|
||||
|
||||
this.updateViewport(yAxisId);
|
||||
|
||||
if (!this.drawAPI.origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
||||
let limitPointOverlap = [];
|
||||
this.limitLines.forEach((limitLine) => {
|
||||
let limitContainerEl = this.$refs.limitArea;
|
||||
limitLine.limits.forEach((limit) => {
|
||||
if (!series.includes(limit.seriesKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const showLabels = this.showLabels(limit.seriesKey);
|
||||
if (showLabels) {
|
||||
const overlap = this.getLimitOverlap(limit, limitPointOverlap);
|
||||
limitPointOverlap.push(overlap);
|
||||
let limitLabelEl = this.getLimitLabel(limit, overlap);
|
||||
limitContainerEl.appendChild(limitLabelEl);
|
||||
}
|
||||
|
||||
let limitEl = this.getLimitElement(limit);
|
||||
limitContainerEl.appendChild(limitEl);
|
||||
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
showLabels(seriesKey) {
|
||||
return this.showLimitLineLabels.seriesKey
|
||||
@@ -657,25 +577,22 @@ export default {
|
||||
);
|
||||
},
|
||||
drawLine(chartElement, disconnected) {
|
||||
if (chartElement) {
|
||||
this.drawAPI.drawLine(
|
||||
chartElement.getBuffer(),
|
||||
chartElement.color().asRGBAArray(),
|
||||
chartElement.count,
|
||||
disconnected
|
||||
);
|
||||
}
|
||||
this.drawAPI.drawLine(
|
||||
chartElement.getBuffer(),
|
||||
chartElement.color().asRGBAArray(),
|
||||
chartElement.count,
|
||||
disconnected
|
||||
);
|
||||
},
|
||||
drawHighlights(yAxisId) {
|
||||
drawHighlights() {
|
||||
if (this.highlights && this.highlights.length) {
|
||||
const highlights = this.highlights.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||
highlights.forEach(this.drawHighlight.bind(this, yAxisId), this);
|
||||
this.highlights.forEach(this.drawHighlight, this);
|
||||
}
|
||||
},
|
||||
drawHighlight(yAxisId, highlight) {
|
||||
drawHighlight(highlight) {
|
||||
const points = new Float32Array([
|
||||
this.offset[yAxisId].xVal(highlight.point, highlight.series),
|
||||
this.offset[yAxisId].yVal(highlight.point, highlight.series)
|
||||
this.offset.xVal(highlight.point, highlight.series),
|
||||
this.offset.yVal(highlight.point, highlight.series)
|
||||
]);
|
||||
|
||||
const color = highlight.series.get('color').asRGBAArray();
|
||||
@@ -684,21 +601,20 @@ export default {
|
||||
|
||||
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
|
||||
},
|
||||
drawRectangles(yAxisId) {
|
||||
drawRectangles() {
|
||||
if (this.rectangles) {
|
||||
const rectangles = this.rectangles.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||
rectangles.forEach(this.drawRectangle.bind(this, yAxisId), this);
|
||||
this.rectangles.forEach(this.drawRectangle, this);
|
||||
}
|
||||
},
|
||||
drawRectangle(yAxisId, rect) {
|
||||
drawRectangle(rect) {
|
||||
this.drawAPI.drawSquare(
|
||||
[
|
||||
this.offset[yAxisId].x(rect.start.x),
|
||||
this.offset[yAxisId].y(rect.start.y)
|
||||
this.offset.x(rect.start.x),
|
||||
this.offset.y(rect.start.y)
|
||||
],
|
||||
[
|
||||
this.offset[yAxisId].x(rect.end.x),
|
||||
this.offset[yAxisId].y(rect.end.y)
|
||||
this.offset.x(rect.end.x),
|
||||
this.offset.y(rect.end.y)
|
||||
],
|
||||
rect.color
|
||||
);
|
||||
|
||||
@@ -27,10 +27,6 @@ import XAxisModel from "./XAxisModel";
|
||||
import YAxisModel from "./YAxisModel";
|
||||
import LegendModel from "./LegendModel";
|
||||
|
||||
const MAX_Y_AXES = 3;
|
||||
const MAIN_Y_AXES_ID = 1;
|
||||
const MAX_ADDITIONAL_AXES = MAX_Y_AXES - 1;
|
||||
|
||||
/**
|
||||
* PlotConfiguration model stores the configuration of a plot and some
|
||||
* limited state. The indiidual parts of the plot configuration model
|
||||
@@ -62,35 +58,8 @@ export default class PlotConfigurationModel extends Model {
|
||||
this.yAxis = new YAxisModel({
|
||||
model: options.model.yAxis,
|
||||
plot: this,
|
||||
openmct: options.openmct,
|
||||
id: options.model.yAxis.id || MAIN_Y_AXES_ID
|
||||
openmct: options.openmct
|
||||
});
|
||||
//Add any axes in addition to the main yAxis above - we must always have at least 1 y-axis
|
||||
//Addition axes ids will be the MAIN_Y_AXES_ID + x where x is between 1 and MAX_ADDITIONAL_AXES
|
||||
this.additionalYAxes = [];
|
||||
if (Array.isArray(options.model.additionalYAxes)) {
|
||||
const maxLength = Math.min(MAX_ADDITIONAL_AXES, options.model.additionalYAxes.length);
|
||||
for (let yAxisCount = 0; yAxisCount < maxLength; yAxisCount++) {
|
||||
const yAxis = options.model.additionalYAxes[yAxisCount];
|
||||
this.additionalYAxes.push(new YAxisModel({
|
||||
model: yAxis,
|
||||
plot: this,
|
||||
openmct: options.openmct,
|
||||
id: yAxis.id || (MAIN_Y_AXES_ID + yAxisCount + 1)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// If the saved options config doesn't include information about all the additional axes, we initialize the remaining here
|
||||
for (let axesCount = this.additionalYAxes.length; axesCount < MAX_ADDITIONAL_AXES; axesCount++) {
|
||||
this.additionalYAxes.push(new YAxisModel({
|
||||
plot: this,
|
||||
openmct: options.openmct,
|
||||
id: MAIN_Y_AXES_ID + axesCount + 1
|
||||
}));
|
||||
}
|
||||
// end add additional axes
|
||||
|
||||
this.legend = new LegendModel({
|
||||
model: options.model.legend,
|
||||
plot: this,
|
||||
@@ -112,9 +81,6 @@ export default class PlotConfigurationModel extends Model {
|
||||
}
|
||||
|
||||
this.yAxis.listenToSeriesCollection(this.series);
|
||||
this.additionalYAxes.forEach(yAxis => {
|
||||
yAxis.listenToSeriesCollection(this.series);
|
||||
});
|
||||
this.legend.listenToSeriesCollection(this.series);
|
||||
|
||||
this.listenTo(this, 'destroy', this.onDestroy, this);
|
||||
@@ -179,7 +145,6 @@ export default class PlotConfigurationModel extends Model {
|
||||
domainObject: options.domainObject,
|
||||
xAxis: {},
|
||||
yAxis: _.cloneDeep(options.domainObject.configuration?.yAxis ?? {}),
|
||||
additionalYAxes: _.cloneDeep(options.domainObject.configuration?.additionalYAxes ?? []),
|
||||
legend: _.cloneDeep(options.domainObject.configuration?.legend ?? {})
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,8 +118,7 @@ export default class PlotSeries extends Model {
|
||||
markerShape: 'point',
|
||||
markerSize: 2.0,
|
||||
alarmMarkers: true,
|
||||
limitLines: false,
|
||||
yAxisId: options.model.yAxisId || 1
|
||||
limitLines: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -135,44 +135,18 @@ export default class YAxisModel extends Model {
|
||||
}
|
||||
}
|
||||
resetStats() {
|
||||
//TODO: do we need the series id here?
|
||||
this.unset('stats');
|
||||
this.getSeriesForYAxis(this.seriesCollection).forEach(series => {
|
||||
this.seriesCollection.forEach(series => {
|
||||
if (series.has('stats')) {
|
||||
this.updateStats(series.get('stats'));
|
||||
}
|
||||
});
|
||||
}
|
||||
getSeriesForYAxis(seriesCollection) {
|
||||
return seriesCollection.filter(series => {
|
||||
const seriesYAxisId = series.get('yAxisId') || 1;
|
||||
|
||||
return seriesYAxisId === this.id;
|
||||
});
|
||||
}
|
||||
|
||||
getYAxisForId(id) {
|
||||
const plotModel = this.plot.get('domainObject');
|
||||
let yAxis;
|
||||
if (this.id === 1) {
|
||||
yAxis = plotModel.configuration?.yAxis;
|
||||
} else {
|
||||
if (plotModel.configuration?.additionalYAxes) {
|
||||
yAxis = plotModel.configuration.additionalYAxes.find(additionalYAxis => additionalYAxis.id === id);
|
||||
}
|
||||
}
|
||||
|
||||
return yAxis;
|
||||
}
|
||||
/**
|
||||
* @param {import('./PlotSeries').default} series
|
||||
*/
|
||||
trackSeries(series) {
|
||||
this.listenTo(series, 'change:stats', seriesStats => {
|
||||
if (series.get('yAxisId') !== this.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!seriesStats) {
|
||||
this.resetStats();
|
||||
} else {
|
||||
@@ -180,10 +154,6 @@ export default class YAxisModel extends Model {
|
||||
}
|
||||
});
|
||||
this.listenTo(series, 'change:yKey', () => {
|
||||
if (series.get('yAxisId') !== this.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateFromSeries(this.seriesCollection);
|
||||
});
|
||||
}
|
||||
@@ -282,40 +252,14 @@ export default class YAxisModel extends Model {
|
||||
// Update the series collection labels and formatting
|
||||
this.updateFromSeries(this.seriesCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given series collection, get the metadata of the current yKey for each series.
|
||||
* Then return first available value of the given property from the metadata.
|
||||
* @param {import('./SeriesCollection').default} series
|
||||
* @param {String} property
|
||||
*/
|
||||
getMetadataValueByProperty(series, property) {
|
||||
return series.map(s => (s.metadata ? s.metadata.value(s.get('yKey'))[property] : ''))
|
||||
.reduce((a, b) => {
|
||||
if (a === undefined) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (a === b) {
|
||||
return a;
|
||||
}
|
||||
|
||||
return '';
|
||||
}, undefined);
|
||||
}
|
||||
/**
|
||||
* Update yAxis format, values, and label from known series.
|
||||
* @param {import('./SeriesCollection').default} seriesCollection
|
||||
*/
|
||||
updateFromSeries(seriesCollection) {
|
||||
const seriesForThisYAxis = this.getSeriesForYAxis(seriesCollection);
|
||||
if (!seriesForThisYAxis.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const yAxis = this.getYAxisForId(this.id);
|
||||
const label = yAxis?.label;
|
||||
const sampleSeries = seriesForThisYAxis[0];
|
||||
const plotModel = this.plot.get('domainObject');
|
||||
const label = plotModel.configuration?.yAxis?.label;
|
||||
const sampleSeries = seriesCollection.first();
|
||||
if (!sampleSeries || !sampleSeries.metadata) {
|
||||
if (!label) {
|
||||
this.unset('label');
|
||||
@@ -335,17 +279,41 @@ export default class YAxisModel extends Model {
|
||||
}
|
||||
|
||||
this.set('values', yMetadata.values);
|
||||
|
||||
if (!label) {
|
||||
const labelName = this.getMetadataValueByProperty(seriesForThisYAxis, 'name');
|
||||
const labelName = seriesCollection
|
||||
.map(s => (s.metadata ? s.metadata.value(s.get('yKey')).name : ''))
|
||||
.reduce((a, b) => {
|
||||
if (a === undefined) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (a === b) {
|
||||
return a;
|
||||
}
|
||||
|
||||
return '';
|
||||
}, undefined);
|
||||
|
||||
if (labelName) {
|
||||
this.set('label', labelName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//if the name is not available, set the units as the label
|
||||
const labelUnits = this.getMetadataValueByProperty(seriesForThisYAxis, 'units');
|
||||
const labelUnits = seriesCollection
|
||||
.map(s => (s.metadata ? s.metadata.value(s.get('yKey')).units : ''))
|
||||
.reduce((a, b) => {
|
||||
if (a === undefined) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (a === b) {
|
||||
return a;
|
||||
}
|
||||
|
||||
return '';
|
||||
}, undefined);
|
||||
|
||||
if (labelUnits) {
|
||||
this.set('label', labelUnits);
|
||||
|
||||
@@ -363,8 +331,7 @@ export default class YAxisModel extends Model {
|
||||
frozen: false,
|
||||
autoscale: true,
|
||||
logMode: options.model?.logMode ?? false,
|
||||
autoscalePadding: 0.1,
|
||||
id: options.id
|
||||
autoscalePadding: 0.1
|
||||
|
||||
// 'range' is not specified here, it is undefined at first. When the
|
||||
// user turns off autoscale, the current 'displayRange' is used for
|
||||
|
||||
@@ -36,21 +36,20 @@
|
||||
/>
|
||||
</ul>
|
||||
<div
|
||||
v-if="plotSeries.length && !isStackedPlotObject"
|
||||
v-if="plotSeries.length"
|
||||
class="grid-properties"
|
||||
>
|
||||
<ul
|
||||
v-for="(yAxis, index) in yAxesWithSeries"
|
||||
:key="`yAxis-${index}`"
|
||||
v-if="!isStackedPlotObject"
|
||||
class="l-inspector-part js-yaxis-properties"
|
||||
>
|
||||
<h2 title="Y axis settings for this object">Y Axis {{ yAxis.id }}</h2>
|
||||
<h2 title="Y axis settings for this object">Y Axis</h2>
|
||||
<li class="grid-row">
|
||||
<div
|
||||
class="grid-cell label"
|
||||
title="Manually override how the Y axis is labeled."
|
||||
>Label</div>
|
||||
<div class="grid-cell value">{{ yAxis.label ? yAxis.label : "Not defined" }}</div>
|
||||
<div class="grid-cell value">{{ label ? label : "Not defined" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div
|
||||
@@ -58,7 +57,7 @@
|
||||
title="Enable log mode."
|
||||
>Log mode</div>
|
||||
<div class="grid-cell value">
|
||||
{{ yAxis.logMode ? "Enabled" : "Disabled" }}
|
||||
{{ logMode ? "Enabled" : "Disabled" }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
@@ -67,36 +66,32 @@
|
||||
title="Automatically scale the Y axis to keep all values in view."
|
||||
>Auto scale</div>
|
||||
<div class="grid-cell value">
|
||||
{{ yAxis.autoscale ? "Enabled: " + yAxis.autoscalePadding : "Disabled" }}
|
||||
{{ autoscale ? "Enabled: " + autoscalePadding : "Disabled" }}
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
v-if="!yAxis.autoscale && yAxis.rangeMin"
|
||||
v-if="!autoscale && rangeMin"
|
||||
class="grid-row"
|
||||
>
|
||||
<div
|
||||
class="grid-cell label"
|
||||
title="Minimum Y axis value."
|
||||
>Minimum value</div>
|
||||
<div class="grid-cell value">{{ yAxis.rangeMin }}</div>
|
||||
<div class="grid-cell value">{{ rangeMin }}</div>
|
||||
</li>
|
||||
<li
|
||||
v-if="!yAxis.autoscale && yAxis.rangeMax"
|
||||
v-if="!autoscale && rangeMax"
|
||||
class="grid-row"
|
||||
>
|
||||
<div
|
||||
class="grid-cell label"
|
||||
title="Maximum Y axis value."
|
||||
>Maximum value</div>
|
||||
<div class="grid-cell value">{{ yAxis.rangeMax }}</div>
|
||||
<div class="grid-cell value">{{ rangeMax }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
v-if="plotSeries.length && (isStackedPlotObject || !isNestedWithinAStackedPlot)"
|
||||
class="grid-properties"
|
||||
>
|
||||
<ul
|
||||
v-if="isStackedPlotObject || !isNestedWithinAStackedPlot"
|
||||
class="l-inspector-part js-legend-properties"
|
||||
>
|
||||
<h2 title="Legend settings for this object">Legend</h2>
|
||||
@@ -162,6 +157,12 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
label: '',
|
||||
autoscale: '',
|
||||
logMode: false,
|
||||
autoscalePadding: '',
|
||||
rangeMin: '',
|
||||
rangeMax: '',
|
||||
position: '',
|
||||
hideLegendWhenSmall: '',
|
||||
expandByDefault: '',
|
||||
@@ -172,8 +173,7 @@ export default {
|
||||
showMaximumWhenExpanded: '',
|
||||
showUnitsWhenExpanded: '',
|
||||
loaded: false,
|
||||
plotSeries: [],
|
||||
yAxes: []
|
||||
plotSeries: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -182,18 +182,13 @@ export default {
|
||||
},
|
||||
isStackedPlotObject() {
|
||||
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
|
||||
},
|
||||
yAxesWithSeries() {
|
||||
return this.yAxes.filter(yAxis => yAxis.seriesCount > 0);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.initYAxesConfiguration();
|
||||
|
||||
this.registerListeners();
|
||||
this.initLegendConfiguration();
|
||||
this.initConfiguration();
|
||||
this.loaded = true;
|
||||
|
||||
},
|
||||
@@ -201,38 +196,18 @@ export default {
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
initYAxesConfiguration() {
|
||||
initConfiguration() {
|
||||
if (this.config) {
|
||||
let range = this.config.yAxis.get('range');
|
||||
this.label = this.config.yAxis.get('label');
|
||||
this.autoscale = this.config.yAxis.get('autoscale');
|
||||
this.logMode = this.config.yAxis.get('logMode');
|
||||
this.autoscalePadding = this.config.yAxis.get('autoscalePadding');
|
||||
const range = this.config.yAxis.get('range');
|
||||
if (range) {
|
||||
this.rangeMin = range.min;
|
||||
this.rangeMax = range.max;
|
||||
}
|
||||
|
||||
this.yAxes.push({
|
||||
id: this.config.yAxis.id,
|
||||
seriesCount: 0,
|
||||
label: this.config.yAxis.get('label'),
|
||||
autoscale: this.config.yAxis.get('autoscale'),
|
||||
logMode: this.config.yAxis.get('logMode'),
|
||||
autoscalePadding: this.config.yAxis.get('autoscalePadding'),
|
||||
rangeMin: range ? range.min : '',
|
||||
rangeMax: range ? range.max : ''
|
||||
});
|
||||
this.config.additionalYAxes.forEach(yAxis => {
|
||||
range = yAxis.get('range');
|
||||
|
||||
this.yAxes.push({
|
||||
id: yAxis.id,
|
||||
seriesCount: 0,
|
||||
label: yAxis.get('label'),
|
||||
autoscale: yAxis.get('autoscale'),
|
||||
logMode: yAxis.get('logMode'),
|
||||
autoscalePadding: yAxis.get('autoscalePadding'),
|
||||
rangeMin: range ? range.min : '',
|
||||
rangeMax: range ? range.max : ''
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
initLegendConfiguration() {
|
||||
if (this.config) {
|
||||
this.position = this.config.legend.get('position');
|
||||
this.hideLegendWhenSmall = this.config.legend.get('hideLegendWhenSmall');
|
||||
this.expandByDefault = this.config.legend.get('expandByDefault');
|
||||
@@ -254,44 +229,18 @@ export default {
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
||||
}
|
||||
},
|
||||
|
||||
setYAxisLabel(yAxisId) {
|
||||
const found = this.yAxes.find(yAxis => yAxis.id === yAxisId);
|
||||
if (found && found.seriesCount > 0) {
|
||||
const mainYAxisId = this.config.yAxis.id;
|
||||
if (mainYAxisId === yAxisId) {
|
||||
found.label = this.config.yAxis.get('label');
|
||||
} else {
|
||||
const additionalYAxis = this.config.additionalYAxes.find(axis => axis.id === yAxisId);
|
||||
if (additionalYAxis) {
|
||||
found.label = additionalYAxis.get('label');
|
||||
}
|
||||
}
|
||||
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||
}
|
||||
},
|
||||
|
||||
addSeries(series, index) {
|
||||
const yAxisId = series.get('yAxisId');
|
||||
this.updateAxisUsageCount(yAxisId, 1);
|
||||
this.$set(this.plotSeries, index, series);
|
||||
this.setYAxisLabel(yAxisId);
|
||||
this.initConfiguration();
|
||||
},
|
||||
|
||||
removeSeries(plotSeries, index) {
|
||||
const yAxisId = plotSeries.get('yAxisId');
|
||||
this.updateAxisUsageCount(yAxisId, -1);
|
||||
this.plotSeries.splice(index, 1);
|
||||
this.setYAxisLabel(yAxisId);
|
||||
},
|
||||
|
||||
updateAxisUsageCount(yAxisId, updateCount) {
|
||||
const foundYAxis = this.yAxes.find(yAxis => yAxis.id === yAxisId);
|
||||
if (foundYAxis) {
|
||||
foundYAxis.seriesCount = foundYAxis.seriesCount + updateCount;
|
||||
}
|
||||
resetAllSeries() {
|
||||
this.plotSeries = [];
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,10 +40,8 @@
|
||||
</li>
|
||||
</ul>
|
||||
<y-axis-form
|
||||
v-for="(yAxisId, index) in yAxesIds"
|
||||
:id="yAxisId.id"
|
||||
:key="`yAxis-${index}`"
|
||||
class="grid-properties js-yaxis-grid-properties"
|
||||
v-if="plotSeries.length && !isStackedPlotObject"
|
||||
class="grid-properties"
|
||||
:y-axis="config.yAxis"
|
||||
@seriesUpdated="updateSeriesConfigForObject"
|
||||
/>
|
||||
@@ -78,7 +76,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
yAxes: [],
|
||||
plotSeries: [],
|
||||
loaded: false
|
||||
};
|
||||
@@ -89,27 +86,11 @@ export default {
|
||||
},
|
||||
isStackedPlotObject() {
|
||||
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
|
||||
},
|
||||
yAxesIds() {
|
||||
return !this.isStackedPlotObject && this.yAxes.filter(yAxis => yAxis.seriesCount > 0);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.yAxes = [{
|
||||
id: this.config.yAxis.id,
|
||||
seriesCount: 0
|
||||
}];
|
||||
if (this.config.additionalYAxes) {
|
||||
this.yAxes = this.yAxes.concat(this.config.additionalYAxes.map(yAxis => {
|
||||
return {
|
||||
id: yAxis.id,
|
||||
seriesCount: 0
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
this.loaded = true;
|
||||
},
|
||||
@@ -126,47 +107,16 @@ export default {
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
||||
},
|
||||
|
||||
findYAxisForId(yAxisId) {
|
||||
return this.yAxes.find(yAxis => yAxis.id === yAxisId);
|
||||
},
|
||||
|
||||
setYAxisLabel(yAxisId) {
|
||||
const found = this.findYAxisForId(yAxisId);
|
||||
if (found && found.seriesCount > 0) {
|
||||
const mainYAxisId = this.config.yAxis.id;
|
||||
if (mainYAxisId === yAxisId) {
|
||||
found.label = this.config.yAxis.get('label');
|
||||
} else {
|
||||
const additionalYAxis = this.config.additionalYAxes.find(axis => axis.id === yAxisId);
|
||||
if (additionalYAxis) {
|
||||
found.label = additionalYAxis.get('label');
|
||||
}
|
||||
}
|
||||
}
|
||||
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||
},
|
||||
|
||||
addSeries(series, index) {
|
||||
const yAxisId = series.get('yAxisId');
|
||||
this.updateAxisUsageCount(yAxisId, 1);
|
||||
this.$set(this.plotSeries, index, series);
|
||||
this.setYAxisLabel(yAxisId);
|
||||
},
|
||||
|
||||
removeSeries(series, index) {
|
||||
const yAxisId = series.get('yAxisId');
|
||||
this.updateAxisUsageCount(yAxisId, -1);
|
||||
this.plotSeries.splice(index, 1);
|
||||
this.setYAxisLabel(yAxisId);
|
||||
},
|
||||
|
||||
updateAxisUsageCount(yAxisId, updateCount) {
|
||||
const foundYAxis = this.findYAxisForId(yAxisId);
|
||||
if (foundYAxis) {
|
||||
foundYAxis.seriesCount = foundYAxis.seriesCount + updateCount;
|
||||
}
|
||||
resetAllSeries() {
|
||||
this.plotSeries = [];
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
},
|
||||
|
||||
updateSeriesConfigForObject(config) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-if="loaded">
|
||||
<div>
|
||||
<ul class="l-inspector-part">
|
||||
<h2>Y Axis {{ id }}</h2>
|
||||
<h2>Y Axis</h2>
|
||||
<li class="grid-row">
|
||||
<div
|
||||
class="grid-cell label"
|
||||
@@ -25,7 +25,6 @@
|
||||
<!-- eslint-disable-next-line vue/html-self-closing -->
|
||||
<input
|
||||
v-model="logMode"
|
||||
class="js-log-mode-input"
|
||||
type="checkbox"
|
||||
@change="updateForm('logMode')"
|
||||
/>
|
||||
@@ -104,72 +103,52 @@
|
||||
<script>
|
||||
import { objectPath } from "./formUtil";
|
||||
import _ from "lodash";
|
||||
import eventHelpers from "../../lib/eventHelpers";
|
||||
import configStore from "../../configuration/ConfigStore";
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
required: true
|
||||
yAxis: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
yAxis: null,
|
||||
label: '',
|
||||
autoscale: '',
|
||||
logMode: false,
|
||||
autoscalePadding: '',
|
||||
rangeMin: '',
|
||||
rangeMax: '',
|
||||
validationErrors: {},
|
||||
loaded: false
|
||||
validationErrors: {}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.getConfig();
|
||||
this.loaded = true;
|
||||
this.initFields();
|
||||
this.initialize();
|
||||
this.initFormValues();
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
const config = configStore.get(configId);
|
||||
if (config) {
|
||||
const mainYAxisId = config.yAxis.id;
|
||||
this.isAdditionalYAxis = this.id !== mainYAxisId;
|
||||
if (this.isAdditionalYAxis) {
|
||||
this.additionalYAxes = config.additionalYAxes;
|
||||
this.yAxis = config.additionalYAxes.find(yAxis => yAxis.id === this.id);
|
||||
} else {
|
||||
this.yAxis = config.yAxis;
|
||||
}
|
||||
}
|
||||
},
|
||||
initFields() {
|
||||
const prefix = `configuration.${this.getPrefix()}`;
|
||||
initialize: function () {
|
||||
this.fields = {
|
||||
label: {
|
||||
objectPath: `${prefix}.label`
|
||||
objectPath: 'configuration.yAxis.label'
|
||||
},
|
||||
autoscale: {
|
||||
coerce: Boolean,
|
||||
objectPath: `${prefix}.autoscale`
|
||||
objectPath: 'configuration.yAxis.autoscale'
|
||||
},
|
||||
autoscalePadding: {
|
||||
coerce: Number,
|
||||
objectPath: `${prefix}.autoscalePadding`
|
||||
objectPath: 'configuration.yAxis.autoscalePadding'
|
||||
},
|
||||
logMode: {
|
||||
coerce: Boolean,
|
||||
objectPath: `${prefix}.logMode`
|
||||
objectPath: 'configuration.yAxis.logMode'
|
||||
},
|
||||
range: {
|
||||
objectPath: `${prefix}.range'`,
|
||||
objectPath: 'configuration.yAxis.range',
|
||||
coerce: function coerceRange(range) {
|
||||
const newRange = {
|
||||
min: -1,
|
||||
@@ -223,25 +202,6 @@ export default {
|
||||
this.rangeMin = range?.min;
|
||||
this.rangeMax = range?.max;
|
||||
},
|
||||
getPrefix() {
|
||||
let prefix = 'yAxis';
|
||||
if (this.isAdditionalYAxis) {
|
||||
let index = -1;
|
||||
if (this.additionalYAxes) {
|
||||
index = this.additionalYAxes.findIndex((yAxis) => {
|
||||
return yAxis.id === this.id;
|
||||
});
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
prefix = `additionalYAxes[${index}]`;
|
||||
}
|
||||
|
||||
return prefix;
|
||||
},
|
||||
updateForm(formKey) {
|
||||
let newVal;
|
||||
if (formKey === 'range') {
|
||||
@@ -271,42 +231,18 @@ export default {
|
||||
this.yAxis.set(formKey, newVal);
|
||||
// Then we mutate the domain object configuration to persist the settings
|
||||
if (path) {
|
||||
if (this.isAdditionalYAxis) {
|
||||
if (this.domainObject.configuration && this.domainObject.configuration.series) {
|
||||
//update the id
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
`configuration.${this.getPrefix()}.id`,
|
||||
this.id
|
||||
);
|
||||
//update the yAxes values
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
path(this.domainObject, this.yAxis),
|
||||
newVal
|
||||
);
|
||||
} else {
|
||||
this.$emit('seriesUpdated', {
|
||||
identifier: this.domainObject.identifier,
|
||||
path: `${this.getPrefix()}.${formKey}`,
|
||||
id: this.id,
|
||||
value: newVal
|
||||
});
|
||||
}
|
||||
if (!this.domainObject.configuration || !this.domainObject.configuration.series) {
|
||||
this.$emit('seriesUpdated', {
|
||||
identifier: this.domainObject.identifier,
|
||||
path: `yAxis.${formKey}`,
|
||||
value: newVal
|
||||
});
|
||||
} else {
|
||||
if (this.domainObject.configuration && this.domainObject.configuration.series) {
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
path(this.domainObject, this.yAxis),
|
||||
newVal
|
||||
);
|
||||
} else {
|
||||
this.$emit('seriesUpdated', {
|
||||
identifier: this.domainObject.identifier,
|
||||
path: `${this.getPrefix()}.${formKey}`,
|
||||
value: newVal
|
||||
});
|
||||
}
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
path(this.domainObject, this.yAxis),
|
||||
newVal
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,504 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} from "utils/testing";
|
||||
import PlotVuePlugin from "../plugin";
|
||||
import Vue from "vue";
|
||||
import Plot from "../Plot.vue";
|
||||
import configStore from "../configuration/ConfigStore";
|
||||
import EventEmitter from "EventEmitter";
|
||||
import PlotOptions from "../inspector/PlotOptions.vue";
|
||||
|
||||
describe("the plugin", function () {
|
||||
let element;
|
||||
let child;
|
||||
let openmct;
|
||||
let telemetryPromise;
|
||||
let telemetryPromiseResolve;
|
||||
let mockObjectPath;
|
||||
let overlayPlotObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-plot"
|
||||
},
|
||||
type: "telemetry.plot.overlay",
|
||||
name: "Test Overlay Plot",
|
||||
composition: [],
|
||||
configuration: {
|
||||
series: []
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach((done) => {
|
||||
mockObjectPath = [
|
||||
{
|
||||
name: 'mock folder',
|
||||
type: 'fake-folder',
|
||||
identifier: {
|
||||
key: 'mock-folder',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'mock parent folder',
|
||||
type: 'time-strip',
|
||||
identifier: {
|
||||
key: 'mock-parent-folder',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
];
|
||||
const testTelemetry = [
|
||||
{
|
||||
'utc': 1,
|
||||
'some-key': 'some-value 1',
|
||||
'some-other-key': 'some-other-value 1',
|
||||
'some-key2': 'some-value2 1',
|
||||
'some-other-key2': 'some-other-value2 1'
|
||||
},
|
||||
{
|
||||
'utc': 2,
|
||||
'some-key': 'some-value 2',
|
||||
'some-other-key': 'some-other-value 2',
|
||||
'some-key2': 'some-value2 2',
|
||||
'some-other-key2': 'some-other-value2 2'
|
||||
},
|
||||
{
|
||||
'utc': 3,
|
||||
'some-key': 'some-value 3',
|
||||
'some-other-key': 'some-other-value 3',
|
||||
'some-key2': 'some-value2 2',
|
||||
'some-other-key2': 'some-other-value2 2'
|
||||
}
|
||||
];
|
||||
|
||||
const timeSystem = {
|
||||
timeSystemKey: 'utc',
|
||||
bounds: {
|
||||
start: 0,
|
||||
end: 4
|
||||
}
|
||||
};
|
||||
|
||||
openmct = createOpenMct(timeSystem);
|
||||
|
||||
telemetryPromise = new Promise((resolve) => {
|
||||
telemetryPromiseResolve = resolve;
|
||||
});
|
||||
|
||||
spyOn(openmct.telemetry, 'request').and.callFake(() => {
|
||||
telemetryPromiseResolve(testTelemetry);
|
||||
|
||||
return telemetryPromise;
|
||||
});
|
||||
|
||||
openmct.install(new PlotVuePlugin());
|
||||
|
||||
element = document.createElement("div");
|
||||
element.style.width = "640px";
|
||||
element.style.height = "480px";
|
||||
child = document.createElement("div");
|
||||
child.style.width = "640px";
|
||||
child.style.height = "480px";
|
||||
element.appendChild(child);
|
||||
document.body.appendChild(element);
|
||||
|
||||
spyOn(window, 'ResizeObserver').and.returnValue({
|
||||
observe() {},
|
||||
unobserve() {},
|
||||
disconnect() {}
|
||||
});
|
||||
|
||||
openmct.types.addType("test-object", {
|
||||
creatable: true
|
||||
});
|
||||
|
||||
spyOnBuiltins(["requestAnimationFrame"]);
|
||||
window.requestAnimationFrame.and.callFake((callBack) => {
|
||||
callBack();
|
||||
});
|
||||
|
||||
openmct.router.path = [overlayPlotObject];
|
||||
openmct.on("start", done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach((done) => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
configStore.deleteAll();
|
||||
resetApplicationState(openmct).then(done).catch(done);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
openmct.router.path = null;
|
||||
});
|
||||
|
||||
describe("the plot views", () => {
|
||||
it("provides an overlay plot view for objects with telemetry", () => {
|
||||
const testTelemetryObject = {
|
||||
id: "test-object",
|
||||
type: "telemetry.plot.overlay",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key"
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
||||
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-overlay");
|
||||
expect(plotView).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("The overlay plot view with multiple axes", () => {
|
||||
let testTelemetryObject;
|
||||
let testTelemetryObject2;
|
||||
let config;
|
||||
let component;
|
||||
let mockComposition;
|
||||
|
||||
afterAll(() => {
|
||||
component.$destroy();
|
||||
openmct.router.path = null;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
testTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
testTelemetryObject2 = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object2"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object2",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-key2",
|
||||
name: "Some attribute2",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key2",
|
||||
name: "Another attribute2",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
overlayPlotObject.composition = [
|
||||
{
|
||||
identifier: testTelemetryObject.identifier
|
||||
},
|
||||
{
|
||||
identifier: testTelemetryObject2.identifier
|
||||
}
|
||||
];
|
||||
overlayPlotObject.configuration.series = [
|
||||
{
|
||||
identifier: testTelemetryObject.identifier,
|
||||
yAxisId: 1
|
||||
},
|
||||
{
|
||||
identifier: testTelemetryObject2.identifier,
|
||||
yAxisId: 3
|
||||
}
|
||||
];
|
||||
overlayPlotObject.configuration.additionalYAxes = [
|
||||
{
|
||||
label: 'Test Object Label',
|
||||
id: 2
|
||||
},
|
||||
{
|
||||
label: 'Test Object 2 Label',
|
||||
id: 3
|
||||
}
|
||||
];
|
||||
mockComposition = new EventEmitter();
|
||||
mockComposition.load = () => {
|
||||
mockComposition.emit('add', testTelemetryObject);
|
||||
mockComposition.emit('add', testTelemetryObject2);
|
||||
|
||||
return [testTelemetryObject, testTelemetryObject2];
|
||||
};
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
let viewContainer = document.createElement("div");
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
Plot
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: overlayPlotObject,
|
||||
composition: openmct.composition.get(overlayPlotObject),
|
||||
path: [overlayPlotObject]
|
||||
},
|
||||
template: '<plot ref="plotComponent"></plot>'
|
||||
});
|
||||
|
||||
return telemetryPromise
|
||||
.then(Vue.nextTick())
|
||||
.then(() => {
|
||||
const configId = openmct.objects.makeKeyString(overlayPlotObject.identifier);
|
||||
config = configStore.get(configId);
|
||||
});
|
||||
});
|
||||
|
||||
it("Renders multiple Y-axis for the telemetry objects", (done) => {
|
||||
config.yAxis.set('displayRange', {
|
||||
min: 10,
|
||||
max: 20
|
||||
});
|
||||
Vue.nextTick(() => {
|
||||
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper");
|
||||
expect(yAxisElement.length).toBe(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the inspector view', () => {
|
||||
let inspectorComponent;
|
||||
let viewComponentObject;
|
||||
let selection;
|
||||
beforeEach((done) => {
|
||||
selection = [
|
||||
[
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
id: overlayPlotObject.identifier.key,
|
||||
identifier: overlayPlotObject.identifier,
|
||||
type: overlayPlotObject.type,
|
||||
configuration: overlayPlotObject.configuration,
|
||||
composition: overlayPlotObject.composition
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
let viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
inspectorComponent = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
PlotOptions
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: selection[0][0].context.item,
|
||||
path: [selection[0][0].context.item]
|
||||
},
|
||||
template: '<plot-options/>'
|
||||
});
|
||||
|
||||
Vue.nextTick(() => {
|
||||
viewComponentObject = inspectorComponent.$root.$children[0];
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.router.path = null;
|
||||
});
|
||||
|
||||
describe('in edit mode', () => {
|
||||
let editOptionsEl;
|
||||
|
||||
beforeEach((done) => {
|
||||
viewComponentObject.setEditState(true);
|
||||
Vue.nextTick(() => {
|
||||
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows multiple yAxis options', () => {
|
||||
const yAxisProperties = editOptionsEl.querySelectorAll(".js-yaxis-grid-properties .l-inspector-part h2");
|
||||
expect(yAxisProperties.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('saves yAxis options', () => {
|
||||
//toggle log mode and save
|
||||
config.additionalYAxes[1].set('displayRange', {
|
||||
min: 10,
|
||||
max: 20
|
||||
});
|
||||
const yAxisProperties = editOptionsEl.querySelectorAll(".js-log-mode-input");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
yAxisProperties[1].dispatchEvent(clickEvent);
|
||||
|
||||
expect(config.additionalYAxes[1].get('logMode')).toEqual(true);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("The overlay plot view with single axes", () => {
|
||||
let testTelemetryObject;
|
||||
let config;
|
||||
let component;
|
||||
let mockComposition;
|
||||
|
||||
afterAll(() => {
|
||||
component.$destroy();
|
||||
openmct.router.path = null;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
testTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
overlayPlotObject.composition = [
|
||||
{
|
||||
identifier: testTelemetryObject.identifier
|
||||
}
|
||||
];
|
||||
overlayPlotObject.configuration.series = [
|
||||
{
|
||||
identifier: testTelemetryObject.identifier
|
||||
}
|
||||
];
|
||||
mockComposition = new EventEmitter();
|
||||
mockComposition.load = () => {
|
||||
mockComposition.emit('add', testTelemetryObject);
|
||||
|
||||
return [testTelemetryObject];
|
||||
};
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
let viewContainer = document.createElement("div");
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
Plot
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: overlayPlotObject,
|
||||
composition: openmct.composition.get(overlayPlotObject),
|
||||
path: [overlayPlotObject]
|
||||
},
|
||||
template: '<plot ref="plotComponent"></plot>'
|
||||
});
|
||||
|
||||
return telemetryPromise
|
||||
.then(Vue.nextTick())
|
||||
.then(() => {
|
||||
const configId = openmct.objects.makeKeyString(overlayPlotObject.identifier);
|
||||
config = configStore.get(configId);
|
||||
});
|
||||
});
|
||||
|
||||
it("Renders single Y-axis for the telemetry object", (done) => {
|
||||
config.yAxis.set('displayRange', {
|
||||
min: 10,
|
||||
max: 20
|
||||
});
|
||||
Vue.nextTick(() => {
|
||||
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper");
|
||||
expect(yAxisElement.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -28,8 +28,6 @@ import EventEmitter from "EventEmitter";
|
||||
import PlotOptions from "./inspector/PlotOptions.vue";
|
||||
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
|
||||
|
||||
const TEST_KEY_ID = 'test-key';
|
||||
|
||||
describe("the plugin", function () {
|
||||
let element;
|
||||
let child;
|
||||
@@ -406,20 +404,6 @@ describe("the plugin", function () {
|
||||
expect(options[1].value).toBe("Another attribute");
|
||||
});
|
||||
|
||||
it("Updates the Y-axis label when changed", () => {
|
||||
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
|
||||
const config = configStore.get(configId);
|
||||
const yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y")[0].__vue__;
|
||||
config.yAxis.seriesCollection.models.forEach((plotSeries) => {
|
||||
expect(plotSeries.model.yKey).toBe('some-key');
|
||||
});
|
||||
|
||||
yAxisElement.$emit('yKeyChanged', TEST_KEY_ID, 1);
|
||||
config.yAxis.seriesCollection.models.forEach((plotSeries) => {
|
||||
expect(plotSeries.model.yKey).toBe(TEST_KEY_ID);
|
||||
});
|
||||
});
|
||||
|
||||
it('hides the pause and play controls', () => {
|
||||
let pauseEl = element.querySelectorAll(".c-button-set .icon-pause");
|
||||
let playEl = element.querySelectorAll(".c-button-set .icon-arrow-right");
|
||||
|
||||
@@ -593,8 +593,6 @@ mct-plot {
|
||||
.plot-legend-left .gl-plot-legend { margin-right: $interiorMargin; }
|
||||
.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; }
|
||||
|
||||
.gl-plot .plot-yaxis-right.gl-plot-y { margin-left: 100%; }
|
||||
|
||||
.gl-plot,
|
||||
.c-plot {
|
||||
&.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; }
|
||||
|
||||
@@ -101,7 +101,8 @@ export default {
|
||||
if (nowMarker) {
|
||||
nowMarker.classList.remove('hidden');
|
||||
nowMarker.style.height = this.contentHeight + 'px';
|
||||
const now = this.xScale(Date.now());
|
||||
const nowTimeStamp = this.openmct.time.clock().currentValue();
|
||||
const now = this.xScale(nowTimeStamp);
|
||||
nowMarker.style.left = now + this.offset + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
draggable="true"
|
||||
@dragstart="emitDragStartEvent"
|
||||
@dragenter="onDragenter"
|
||||
@dragover.prevent
|
||||
@dragover="onDragover"
|
||||
@dragleave="onDragleave"
|
||||
@drop="emitDropEvent"
|
||||
>
|
||||
@@ -38,7 +38,6 @@
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="showGrippy"
|
||||
class="c-elements-pool__grippy c-grippy c-grippy--vertical-drag"
|
||||
></span>
|
||||
<object-label
|
||||
@@ -82,10 +81,6 @@ export default {
|
||||
},
|
||||
allowDrop: {
|
||||
type: Boolean
|
||||
},
|
||||
showGrippy: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -98,8 +93,11 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onDragover(event) {
|
||||
event.preventDefault();
|
||||
},
|
||||
emitDropEvent(event) {
|
||||
this.$emit('drop-custom', event);
|
||||
this.$emit('drop-custom', this.index);
|
||||
this.hover = false;
|
||||
},
|
||||
emitDragStartEvent(event) {
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="c-elements-pool__group"
|
||||
:class="{
|
||||
'hover': hover
|
||||
}"
|
||||
:allow-drop="allowDrop"
|
||||
@dragover.prevent
|
||||
@dragenter="onDragEnter"
|
||||
@dragleave.stop="onDragLeave"
|
||||
@drop="emitDrop"
|
||||
>
|
||||
<ul>
|
||||
<div>
|
||||
<span class="c-elements-pool__grippy c-grippy c-grippy--vertical-drag"></span>
|
||||
<div
|
||||
class="c-tree__item__type-icon c-object-label__type-icon"
|
||||
>
|
||||
<span
|
||||
class="is-status__indicator"
|
||||
></span>
|
||||
</div>
|
||||
<div
|
||||
class="c-tree__item__name c-object-label__name"
|
||||
aria-label="Element Item Group"
|
||||
>
|
||||
{{ label }}
|
||||
</div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
parentObject: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: () => {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
allowDrop: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragCounter: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hover() {
|
||||
return this.dragCounter > 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emitDrop(event) {
|
||||
this.dragCounter = 0;
|
||||
this.$emit('drop-group', event);
|
||||
},
|
||||
onDragEnter(event) {
|
||||
this.dragCounter++;
|
||||
},
|
||||
onDragLeave(event) {
|
||||
this.dragCounter--;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -65,8 +65,8 @@ import ElementItem from './ElementItem.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Search,
|
||||
ElementItem
|
||||
'Search': Search,
|
||||
'ElementItem': ElementItem
|
||||
},
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
|
||||
@@ -56,12 +56,7 @@
|
||||
handle="before"
|
||||
label="Elements"
|
||||
>
|
||||
<plot-elements-pool
|
||||
v-if="isOverlayPlot"
|
||||
/>
|
||||
<elements-pool
|
||||
v-else
|
||||
/>
|
||||
<elements-pool />
|
||||
</pane>
|
||||
</multipane>
|
||||
<multipane
|
||||
@@ -88,7 +83,6 @@
|
||||
import multipane from '../layout/multipane.vue';
|
||||
import pane from '../layout/pane.vue';
|
||||
import ElementsPool from './ElementsPool.vue';
|
||||
import PlotElementsPool from './PlotElementsPool.vue';
|
||||
import Location from './Location.vue';
|
||||
import Properties from './details/Properties.vue';
|
||||
import ObjectName from './ObjectName.vue';
|
||||
@@ -105,7 +99,6 @@ export default {
|
||||
multipane,
|
||||
pane,
|
||||
ElementsPool,
|
||||
PlotElementsPool,
|
||||
Properties,
|
||||
ObjectName,
|
||||
Location,
|
||||
@@ -125,7 +118,6 @@ export default {
|
||||
return {
|
||||
hasComposition: false,
|
||||
showStyles: false,
|
||||
isOverlayPlot: false,
|
||||
tabbedViews: [{
|
||||
key: '__properties',
|
||||
name: 'Properties'
|
||||
@@ -159,7 +151,6 @@ export default {
|
||||
let parentObject = selection[0][0].context.item;
|
||||
|
||||
this.hasComposition = Boolean(parentObject && this.openmct.composition.get(parentObject));
|
||||
this.isOverlayPlot = selection[0][0].context.item.type === 'telemetry.plot.overlay';
|
||||
}
|
||||
},
|
||||
refreshTabs(selection) {
|
||||
|
||||
@@ -1,330 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-elements-pool">
|
||||
<Search
|
||||
class="c-elements-pool__search"
|
||||
:value="currentSearch"
|
||||
@input="applySearch"
|
||||
@clear="applySearch"
|
||||
/>
|
||||
<div
|
||||
class="c-elements-pool__elements"
|
||||
>
|
||||
<ul
|
||||
v-if="hasElements"
|
||||
id="inspector-elements-tree"
|
||||
class="c-tree c-elements-pool__tree"
|
||||
>
|
||||
<element-item-group
|
||||
v-for="(yAxis, index) in yAxes"
|
||||
:key="`element-group-yaxis-${yAxis.id}`"
|
||||
:parent-object="parentObject"
|
||||
:allow-drop="allowDrop"
|
||||
:label="`Y Axis ${yAxis.id}`"
|
||||
@drop-group="moveTo($event, 0, yAxis.id)"
|
||||
>
|
||||
<li
|
||||
class="js-first-place"
|
||||
@drop="moveTo($event, 0, yAxis.id)"
|
||||
></li>
|
||||
<element-item
|
||||
v-for="(element, elemIndex) in yAxis.elements"
|
||||
:key="element.identifier.key"
|
||||
:index="elemIndex"
|
||||
:element-object="element"
|
||||
:parent-object="parentObject"
|
||||
:allow-drop="allowDrop"
|
||||
:show-grippy="false"
|
||||
@dragstart-custom="moveFrom($event, yAxis.id)"
|
||||
@drop-custom="moveTo($event, index, yAxis.id)"
|
||||
/>
|
||||
<li
|
||||
v-if="yAxis.elements.length > 0"
|
||||
class="js-last-place"
|
||||
@drop="moveTo($event, yAxis.elements.length, yAxis.id)"
|
||||
></li>
|
||||
</element-item-group>
|
||||
</ul>
|
||||
<div
|
||||
v-if="!hasElements"
|
||||
>
|
||||
No contained elements
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import Search from '../components/search.vue';
|
||||
import ElementItem from './ElementItem.vue';
|
||||
import ElementItemGroup from './ElementItemGroup.vue';
|
||||
import configStore from '../../plugins/plot/configuration/ConfigStore';
|
||||
|
||||
const Y_AXIS_1 = 1;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Search,
|
||||
ElementItemGroup,
|
||||
ElementItem
|
||||
},
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
yAxes: [],
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
parentObject: undefined,
|
||||
currentSearch: '',
|
||||
selection: [],
|
||||
contextClickTracker: {},
|
||||
allowDrop: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasElements() {
|
||||
for (const yAxis of this.yAxes) {
|
||||
if (yAxis.elements.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const selection = this.openmct.selection.get();
|
||||
if (selection && selection.length > 0) {
|
||||
this.showSelection(selection);
|
||||
}
|
||||
|
||||
this.openmct.selection.on('change', this.showSelection);
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
destroyed() {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
this.openmct.selection.off('change', this.showSelection);
|
||||
|
||||
this.unlistenComposition();
|
||||
},
|
||||
methods: {
|
||||
setEditState(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
this.showSelection(this.openmct.selection.get());
|
||||
},
|
||||
showSelection(selection) {
|
||||
if (_.isEqual(this.selection, selection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selection = selection;
|
||||
this.elementsCache = {};
|
||||
this.listeners = [];
|
||||
this.parentObject = selection && selection[0] && selection[0][0].context.item;
|
||||
|
||||
this.unlistenComposition();
|
||||
|
||||
if (this.parentObject) {
|
||||
this.setYAxisIds();
|
||||
this.composition = this.openmct.composition.get(this.parentObject);
|
||||
|
||||
if (this.composition) {
|
||||
this.composition.load();
|
||||
this.registerCompositionListeners();
|
||||
}
|
||||
}
|
||||
},
|
||||
unlistenComposition() {
|
||||
if (this.compositionUnlistener) {
|
||||
this.compositionUnlistener();
|
||||
}
|
||||
},
|
||||
registerCompositionListeners() {
|
||||
this.composition.on('add', this.addElement);
|
||||
this.composition.on('remove', this.removeElement);
|
||||
this.composition.on('reorder', this.reorderElements);
|
||||
|
||||
this.compositionUnlistener = () => {
|
||||
this.composition.off('add', this.addElement);
|
||||
this.composition.off('remove', this.removeElement);
|
||||
this.composition.off('reorder', this.reorderElements);
|
||||
delete this.compositionUnlistener;
|
||||
};
|
||||
},
|
||||
setYAxisIds() {
|
||||
const configId = this.openmct.objects.makeKeyString(this.parentObject.identifier);
|
||||
this.config = configStore.get(configId);
|
||||
this.yAxes.push({
|
||||
id: this.config.yAxis.id,
|
||||
elements: this.parentObject.configuration.series.filter(
|
||||
series => series.yAxisId === this.config.yAxis.id
|
||||
)
|
||||
});
|
||||
if (this.config.additionalYAxes) {
|
||||
this.config.additionalYAxes.forEach(yAxis => {
|
||||
this.yAxes.push({
|
||||
id: yAxis.id,
|
||||
elements: this.parentObject.configuration.series.filter(
|
||||
series => series.yAxisId === yAxis.id
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
addElement(element) {
|
||||
// Get the index of the corresponding element in the series list
|
||||
const seriesIndex = this.parentObject.configuration.series.findIndex(
|
||||
series => this.openmct.objects.areIdsEqual(series.identifier, element.identifier)
|
||||
);
|
||||
const keyString = this.openmct.objects.makeKeyString(element.identifier);
|
||||
|
||||
const wasDraggedOntoPlot = this.parentObject.configuration.series[seriesIndex].yAxisId === undefined;
|
||||
const yAxisId = wasDraggedOntoPlot
|
||||
? Y_AXIS_1
|
||||
: this.parentObject.configuration.series[seriesIndex].yAxisId;
|
||||
|
||||
if (wasDraggedOntoPlot) {
|
||||
const insertIndex = this.yAxes[0].elements.length;
|
||||
// Insert the element at the end of the first YAxis bucket
|
||||
this.composition.reorder(seriesIndex, insertIndex);
|
||||
}
|
||||
|
||||
// Store the element in the cache and set its yAxisId
|
||||
this.elementsCache[keyString] = JSON.parse(JSON.stringify(element));
|
||||
if (this.elementsCache[keyString].yAxisId !== yAxisId) {
|
||||
// Mutate the YAxisId on the domainObject itself
|
||||
this.updateCacheAndMutate(element, yAxisId);
|
||||
}
|
||||
|
||||
this.applySearch(this.currentSearch);
|
||||
},
|
||||
reorderElements() {
|
||||
this.applySearch(this.currentSearch);
|
||||
},
|
||||
removeElement(identifier) {
|
||||
const keyString = this.openmct.objects.makeKeyString(identifier);
|
||||
delete this.elementsCache[keyString];
|
||||
this.applySearch(this.currentSearch);
|
||||
},
|
||||
applySearch(input) {
|
||||
this.currentSearch = input;
|
||||
this.yAxes.forEach(yAxis => {
|
||||
yAxis.elements = this.filterForSearchAndAxis(input, yAxis.id);
|
||||
});
|
||||
},
|
||||
filterForSearchAndAxis(input, yAxisId) {
|
||||
return this.parentObject.composition.map((id) =>
|
||||
this.elementsCache[this.openmct.objects.makeKeyString(id)]
|
||||
).filter((element) => {
|
||||
return element !== undefined
|
||||
&& element.name.toLowerCase().search(input) !== -1
|
||||
&& element.yAxisId === yAxisId;
|
||||
});
|
||||
},
|
||||
moveFrom(elementIndex, groupIndex) {
|
||||
this.allowDrop = true;
|
||||
this.moveFromIndex = elementIndex;
|
||||
this.moveFromYAxisId = groupIndex;
|
||||
},
|
||||
moveTo(event, moveToIndex, moveToYAxisId) {
|
||||
// FIXME: If the user starts the drag by clicking outside of the <object-label/> element,
|
||||
// domain object information will not be set on the dataTransfer data. To prevent errors,
|
||||
// we simply short-circuit here if the data is not set.
|
||||
const serializedDomainObject = event.dataTransfer.getData('openmct/composable-domain-object');
|
||||
if (!serializedDomainObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
const domainObject = JSON.parse(serializedDomainObject);
|
||||
this.updateCacheAndMutate(domainObject, moveToYAxisId);
|
||||
|
||||
const moveFromIndex = this.moveFromIndex;
|
||||
|
||||
this.moveAndReorderElement(moveFromIndex, moveToIndex, moveToYAxisId);
|
||||
},
|
||||
updateCacheAndMutate(domainObject, yAxisId) {
|
||||
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
const index = this.parentObject.configuration.series.findIndex(
|
||||
series => series.identifier.key === domainObject.identifier.key
|
||||
);
|
||||
|
||||
// Handle the case of dragging an element directly into the Elements Pool
|
||||
if (!this.elementsCache[keyString]) {
|
||||
// Update the series list locally so our CompositionAdd handler can
|
||||
// take care of the rest.
|
||||
this.parentObject.configuration.series.push({
|
||||
identifier: domainObject.identifier,
|
||||
yAxisId
|
||||
});
|
||||
this.composition.add(domainObject);
|
||||
this.elementsCache[keyString] = JSON.parse(JSON.stringify(domainObject));
|
||||
}
|
||||
|
||||
this.elementsCache[keyString].yAxisId = yAxisId;
|
||||
const shouldMutate = this.parentObject.configuration.series?.[index]?.yAxisId !== yAxisId;
|
||||
if (shouldMutate) {
|
||||
this.openmct.objects.mutate(
|
||||
this.parentObject,
|
||||
`configuration.series[${index}].yAxisId`,
|
||||
yAxisId
|
||||
);
|
||||
}
|
||||
},
|
||||
moveAndReorderElement(moveFromIndex, moveToIndex, moveToYAxisId) {
|
||||
if (!this.allowDrop) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the corresponding indexes of the from/to yAxes in the yAxes list
|
||||
const moveFromYAxisIndex = this.yAxes.findIndex(yAxis => yAxis.id === this.moveFromYAxisId);
|
||||
const moveToYAxisIndex = this.yAxes.findIndex(yAxis => yAxis.id === moveToYAxisId);
|
||||
|
||||
// Calculate the actual indexes of the elements in the composition array
|
||||
// based on which bucket and index they are being moved from/to.
|
||||
// Then, trigger a composition reorder.
|
||||
for (let yAxisId = 0; yAxisId < moveFromYAxisIndex; yAxisId++) {
|
||||
const lesserYAxisBucketLength = this.yAxes[yAxisId].elements.length;
|
||||
// Add the lengths of preceding buckets to calculate the actual 'from' index
|
||||
moveFromIndex = moveFromIndex + lesserYAxisBucketLength;
|
||||
}
|
||||
|
||||
for (let yAxisId = 0; yAxisId < moveToYAxisIndex; yAxisId++) {
|
||||
const greaterYAxisBucketLength = this.yAxes[yAxisId].elements.length;
|
||||
// Add the lengths of subsequent buckets to calculate the actual 'to' index
|
||||
moveToIndex = moveToIndex + greaterYAxisBucketLength;
|
||||
}
|
||||
|
||||
// Adjust the index by 1 if we're moving from one bucket to another
|
||||
if (this.moveFromYAxisId !== moveToYAxisId && moveToIndex > 0) {
|
||||
moveToIndex--;
|
||||
}
|
||||
|
||||
// Reorder the composition array according to the calculated indexes
|
||||
this.composition.reorder(moveFromIndex, moveToIndex);
|
||||
|
||||
this.allowDrop = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -21,11 +21,6 @@
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__group {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__elements {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
Reference in New Issue
Block a user