mirror of
https://github.com/nasa/openmct.git
synced 2025-01-18 10:46:42 +00:00
Decouple removal of independent time context for a view from refreshing context for other views (#6334)
* Decouple removing the context for a view from refreshing the context of views to get their upstream contexts * Add test for clicking on an item in the elements pool for a preview * Add test to ensure independent time context for items with no parents works as expected
This commit is contained in:
parent
5da1c9c0d7
commit
6d62e0e73c
@ -140,4 +140,61 @@ test.describe('Overlay Plot', () => {
|
||||
expect(yAxis3Group.getByRole('listitem', { name: swgB.name })).toBeTruthy();
|
||||
expect(yAxis3Group.getByRole('listitem').nth(0).getByText(swgB.name)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Clicking on an item in the elements pool brings up the plot preview with data points', async ({ page }) => {
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: "Overlay Plot"
|
||||
});
|
||||
|
||||
const swgA = await createDomainObjectWithDefaults(page, {
|
||||
type: "Sine Wave Generator",
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
|
||||
await page.goto(overlayPlot.url);
|
||||
await page.click('button[title="Edit"]');
|
||||
|
||||
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click();
|
||||
await page.locator('.js-overlay canvas').nth(1);
|
||||
const plotPixelSize = await getCanvasPixelsWithData(page);
|
||||
expect(plotPixelSize).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function getCanvasPixelsWithData(page) {
|
||||
const getTelemValuePromise = new Promise(resolve => page.exposeFunction('getCanvasValue', resolve));
|
||||
|
||||
await page.evaluate(() => {
|
||||
// The document canvas is where the plot points and lines are drawn.
|
||||
// The only way to access the canvas is using document (using page.evaluate)
|
||||
let data;
|
||||
let canvas;
|
||||
let ctx;
|
||||
canvas = document.querySelector('.js-overlay canvas');
|
||||
ctx = canvas.getContext('2d');
|
||||
data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
||||
const imageDataValues = Object.values(data);
|
||||
let plotPixels = [];
|
||||
// Each pixel consists of four values within the ImageData.data array. The for loop iterates by multiples of four.
|
||||
// The values associated with each pixel are R (red), G (green), B (blue), and A (alpha), in that order.
|
||||
for (let i = 0; i < imageDataValues.length;) {
|
||||
if (imageDataValues[i] > 0) {
|
||||
plotPixels.push({
|
||||
startIndex: i,
|
||||
endIndex: i + 3,
|
||||
value: `rgb(${imageDataValues[i]}, ${imageDataValues[i + 1]}, ${imageDataValues[i + 2]}, ${imageDataValues[i + 3]})`
|
||||
});
|
||||
}
|
||||
|
||||
i = i + 4;
|
||||
|
||||
}
|
||||
|
||||
window.getCanvasValue(plotPixels.length);
|
||||
});
|
||||
|
||||
return getTelemValuePromise;
|
||||
}
|
||||
|
@ -32,14 +32,18 @@ class IndependentTimeContext extends TimeContext {
|
||||
this.openmct = openmct;
|
||||
this.unlisteners = [];
|
||||
this.globalTimeContext = globalTimeContext;
|
||||
this.upstreamTimeContext = undefined;
|
||||
// We always start with the global time context.
|
||||
// This upstream context will be undefined when an independent time context is added later.
|
||||
this.upstreamTimeContext = this.globalTimeContext;
|
||||
this.objectPath = objectPath;
|
||||
this.refreshContext = this.refreshContext.bind(this);
|
||||
this.resetContext = this.resetContext.bind(this);
|
||||
this.removeIndependentContext = this.removeIndependentContext.bind(this);
|
||||
|
||||
this.refreshContext();
|
||||
|
||||
this.globalTimeContext.on('refreshContext', this.refreshContext);
|
||||
this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext);
|
||||
}
|
||||
|
||||
bounds(newBounds) {
|
||||
@ -202,10 +206,16 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
|
||||
getUpstreamContext() {
|
||||
// If a view has an independent context, don't return an upstream context
|
||||
// Be aware that when a new independent time context is created, we assign the global context as default
|
||||
if (this.hasOwnContext()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let timeContext = this.globalTimeContext;
|
||||
this.objectPath.some((item, index) => {
|
||||
const key = this.openmct.objects.makeKeyString(item.identifier);
|
||||
//first index is the view object itself
|
||||
// we're only interested in parents, not self, so index > 0
|
||||
const itemContext = this.globalTimeContext.independentContexts.get(key);
|
||||
if (index > 0 && itemContext && itemContext.hasOwnContext()) {
|
||||
//upstream time context
|
||||
@ -219,6 +229,43 @@ class IndependentTimeContext extends TimeContext {
|
||||
|
||||
return timeContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time context of a view to follow any upstream time contexts as necessary (defaulting to the global context)
|
||||
* This needs to be separate from refreshContext
|
||||
*/
|
||||
removeIndependentContext(viewKey) {
|
||||
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
|
||||
if (viewKey && key === viewKey) {
|
||||
//this is necessary as the upstream context gets reassigned after this
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
let timeContext = this.globalTimeContext;
|
||||
|
||||
this.objectPath.some((item, index) => {
|
||||
const objectKey = this.openmct.objects.makeKeyString(item.identifier);
|
||||
// we're only interested in any parents, not self, so index > 0
|
||||
const itemContext = this.globalTimeContext.independentContexts.get(objectKey);
|
||||
if (index > 0 && itemContext && itemContext.hasOwnContext()) {
|
||||
//upstream time context
|
||||
timeContext = itemContext;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.upstreamTimeContext = timeContext;
|
||||
|
||||
this.followTimeContext();
|
||||
|
||||
// Emit bounds so that views that are changing context get the upstream bounds
|
||||
this.emit('bounds', this.bounds());
|
||||
// now that the view's context is set, tell others to check theirs in case they were following this view's context.
|
||||
this.globalTimeContext.emit('refreshContext', viewKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default IndependentTimeContext;
|
||||
|
@ -149,7 +149,7 @@ class TimeAPI extends GlobalTimeContext {
|
||||
|
||||
return () => {
|
||||
//follow any upstream time context
|
||||
this.emit('refreshContext');
|
||||
this.emit('removeOwnContext', key);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -117,22 +117,58 @@ describe("The Independent Time API", function () {
|
||||
});
|
||||
|
||||
it("uses an object's independent time context if the parent doesn't have one", () => {
|
||||
const domainObjectKey2 = `${domainObjectKey}-2`;
|
||||
const domainObjectKey3 = `${domainObjectKey}-3`;
|
||||
let timeContext = api.getContextForView([{
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: domainObjectKey
|
||||
}
|
||||
}, {
|
||||
}]);
|
||||
let timeContext2 = api.getContextForView([{
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'blah'
|
||||
key: domainObjectKey2
|
||||
}
|
||||
}]);
|
||||
let timeContext3 = api.getContextForView([{
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: domainObjectKey3
|
||||
}
|
||||
}]);
|
||||
// all bounds follow global time context
|
||||
expect(timeContext.bounds()).toEqual(bounds);
|
||||
expect(timeContext2.bounds()).toEqual(bounds);
|
||||
expect(timeContext3.bounds()).toEqual(bounds);
|
||||
// only first item has own context
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
||||
expect(timeContext2.bounds()).toEqual(bounds);
|
||||
expect(timeContext3.bounds()).toEqual(bounds);
|
||||
// first and second item have own context
|
||||
let destroyTimeContext2 = api.addIndependentContext(domainObjectKey2, independentBounds);
|
||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
||||
expect(timeContext2.bounds()).toEqual(independentBounds);
|
||||
expect(timeContext3.bounds()).toEqual(bounds);
|
||||
// all items have own time context
|
||||
let destroyTimeContext3 = api.addIndependentContext(domainObjectKey3, independentBounds);
|
||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
||||
expect(timeContext2.bounds()).toEqual(independentBounds);
|
||||
expect(timeContext3.bounds()).toEqual(independentBounds);
|
||||
//remove own contexts one at a time - should revert to global time context
|
||||
destroyTimeContext();
|
||||
expect(timeContext.bounds()).toEqual(bounds);
|
||||
expect(timeContext2.bounds()).toEqual(independentBounds);
|
||||
expect(timeContext3.bounds()).toEqual(independentBounds);
|
||||
destroyTimeContext2();
|
||||
expect(timeContext.bounds()).toEqual(bounds);
|
||||
expect(timeContext2.bounds()).toEqual(bounds);
|
||||
expect(timeContext3.bounds()).toEqual(independentBounds);
|
||||
destroyTimeContext3();
|
||||
expect(timeContext.bounds()).toEqual(bounds);
|
||||
expect(timeContext2.bounds()).toEqual(bounds);
|
||||
expect(timeContext3.bounds()).toEqual(bounds);
|
||||
});
|
||||
|
||||
it("Allows setting of valid bounds", function () {
|
||||
|
Loading…
Reference in New Issue
Block a user