mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 05:07:52 +00:00
Independent time contexts follow upstream contexts as needed (#4556)
* Update independent time context APIs to follow upstream time contexts as necessary * Removes boilerplate from views.
This commit is contained in:
parent
82ea23e20c
commit
2488072d6b
@ -20,18 +20,66 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import TimeContext from "./TimeContext";
|
||||
import TimeContext, { TIME_CONTEXT_EVENTS } from "./TimeContext";
|
||||
|
||||
/**
|
||||
* The IndependentTimeContext handles getting and setting time of the openmct application in general.
|
||||
* Views will use the GlobalTimeContext unless they specify an alternate/independent time context here.
|
||||
*/
|
||||
class IndependentTimeContext extends TimeContext {
|
||||
constructor(globalTimeContext, key) {
|
||||
constructor(openmct, globalTimeContext, objectPath) {
|
||||
super();
|
||||
this.key = key;
|
||||
|
||||
this.openmct = openmct;
|
||||
this.unlisteners = [];
|
||||
this.globalTimeContext = globalTimeContext;
|
||||
this.upstreamTimeContext = undefined;
|
||||
this.objectPath = objectPath;
|
||||
this.refreshContext = this.refreshContext.bind(this);
|
||||
this.resetContext = this.resetContext.bind(this);
|
||||
|
||||
this.refreshContext();
|
||||
|
||||
this.globalTimeContext.on('refreshContext', this.refreshContext);
|
||||
}
|
||||
|
||||
bounds(newBounds) {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.bounds(...arguments);
|
||||
} else {
|
||||
return super.bounds(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
tick(timestamp) {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.tick(...arguments);
|
||||
} else {
|
||||
return super.tick(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
clockOffsets(offsets) {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.clockOffsets(...arguments);
|
||||
} else {
|
||||
return super.clockOffsets(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
stopClock() {
|
||||
if (this.upstreamTimeContext) {
|
||||
this.upstreamTimeContext.stopClock();
|
||||
} else {
|
||||
super.stopClock();
|
||||
}
|
||||
}
|
||||
|
||||
timeOfInterest(newTOI) {
|
||||
return this.globalTimeContext.timeOfInterest(...arguments);
|
||||
}
|
||||
|
||||
timeSystem(timeSystemOrKey, bounds) {
|
||||
return this.globalTimeContext.timeSystem(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,6 +95,10 @@ class IndependentTimeContext extends TimeContext {
|
||||
* @return {Clock} the currently active clock;
|
||||
*/
|
||||
clock(keyOrClock, offsets) {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.clock(...arguments);
|
||||
}
|
||||
|
||||
if (arguments.length === 2) {
|
||||
let clock;
|
||||
|
||||
@ -89,6 +141,81 @@ class IndependentTimeContext extends TimeContext {
|
||||
|
||||
return this.activeClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes this time context to follow another time context (either the global context, or another upstream time context)
|
||||
* This allows views to have their own time context which points to the appropriate upstream context as necessary, achieving nesting.
|
||||
* @param {*} upstreamTimeContext
|
||||
*/
|
||||
followTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
if (this.upstreamTimeContext) {
|
||||
TIME_CONTEXT_EVENTS.forEach((eventName) => {
|
||||
const thisTimeContext = this;
|
||||
this.upstreamTimeContext.on(eventName, passthrough);
|
||||
this.unlisteners.push(() => this.upstreamTimeContext.off(eventName, passthrough));
|
||||
function passthrough() {
|
||||
thisTimeContext.emit(eventName, ...arguments);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops following any upstream time context
|
||||
*/
|
||||
stopFollowingTimeContext() {
|
||||
this.unlisteners.forEach(unlisten => unlisten());
|
||||
}
|
||||
|
||||
resetContext() {
|
||||
if (this.upstreamTimeContext) {
|
||||
this.stopFollowingTimeContext();
|
||||
this.upstreamTimeContext = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the time context, following any upstream time contexts as necessary
|
||||
*/
|
||||
refreshContext(viewKey) {
|
||||
//TODO: find a better way to skip upstream context for the view that just got an independent time context
|
||||
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
|
||||
if (viewKey && key === viewKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.upstreamTimeContext = this.getUpstreamContext();
|
||||
this.followTimeContext();
|
||||
|
||||
// Emit bounds so that views that are changing context get the upstream bounds
|
||||
this.emit('bounds', this.upstreamTimeContext.bounds());
|
||||
}
|
||||
|
||||
hasOwnContext() {
|
||||
return this.upstreamTimeContext === undefined;
|
||||
}
|
||||
|
||||
getUpstreamContext() {
|
||||
let timeContext = this.globalTimeContext;
|
||||
|
||||
this.objectPath.some((item, index) => {
|
||||
const key = this.openmct.objects.makeKeyString(item.identifier);
|
||||
//last index is the view object itself
|
||||
const itemContext = this.globalTimeContext.independentContexts.get(key);
|
||||
if (index > 0 && itemContext && itemContext.hasOwnContext()) {
|
||||
//upstream time context
|
||||
timeContext = itemContext;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return timeContext;
|
||||
}
|
||||
}
|
||||
|
||||
export default IndependentTimeContext;
|
||||
|
@ -133,11 +133,9 @@ class TimeAPI extends GlobalTimeContext {
|
||||
* @method addIndependentTimeContext
|
||||
*/
|
||||
addIndependentContext(key, value, clockKey) {
|
||||
let timeContext = this.independentContexts.get(key);
|
||||
if (!timeContext) {
|
||||
timeContext = new IndependentTimeContext(this, key);
|
||||
this.independentContexts.set(key, timeContext);
|
||||
}
|
||||
let timeContext = this.getIndependentContext(key);
|
||||
//stop following upstream time context since the view has it's own
|
||||
timeContext.resetContext();
|
||||
|
||||
if (clockKey) {
|
||||
timeContext.clock(clockKey, value);
|
||||
@ -146,11 +144,12 @@ class TimeAPI extends GlobalTimeContext {
|
||||
timeContext.bounds(value);
|
||||
}
|
||||
|
||||
this.emit('timeContext', key);
|
||||
// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
|
||||
this.emit('refreshContext', key);
|
||||
|
||||
return () => {
|
||||
this.independentContexts.delete(key);
|
||||
timeContext.emit('timeContext', key);
|
||||
//follow any upstream time context
|
||||
this.emit('refreshContext');
|
||||
};
|
||||
}
|
||||
|
||||
@ -173,16 +172,24 @@ class TimeAPI extends GlobalTimeContext {
|
||||
* @method getContextForView
|
||||
*/
|
||||
getContextForView(objectPath = []) {
|
||||
let timeContext = this;
|
||||
const viewKey = objectPath.length && this.openmct.objects.makeKeyString(objectPath[0].identifier);
|
||||
|
||||
objectPath.forEach(item => {
|
||||
const key = this.openmct.objects.makeKeyString(item.identifier);
|
||||
if (this.independentContexts.get(key)) {
|
||||
timeContext = this.independentContexts.get(key);
|
||||
if (viewKey) {
|
||||
let viewTimeContext = this.getIndependentContext(viewKey);
|
||||
if (viewTimeContext) {
|
||||
this.independentContexts.delete(viewKey);
|
||||
} else {
|
||||
viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
|
||||
}
|
||||
});
|
||||
|
||||
return timeContext;
|
||||
// return a new IndependentContext in case the objectPath is different
|
||||
this.independentContexts.set(viewKey, viewTimeContext);
|
||||
|
||||
return viewTimeContext;
|
||||
}
|
||||
|
||||
// always follow the global time context
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,13 @@
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export const TIME_CONTEXT_EVENTS = [
|
||||
'bounds',
|
||||
'clock',
|
||||
'timeSystem',
|
||||
'clockOffsets'
|
||||
];
|
||||
|
||||
class TimeContext extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
@ -46,7 +53,7 @@ class TimeContext extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Get or set the time system of the TimeAPI.
|
||||
* @param {TimeSystem | string} timeSystem
|
||||
* @param {TimeSystem | string} timeSystemOrKey
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||
* @fires module:openmct.TimeAPI~timeSystem
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
|
@ -58,26 +58,31 @@ describe("The Independent Time API", function () {
|
||||
});
|
||||
|
||||
it("Creates an independent time context", () => {
|
||||
let timeContext = api.getContextForView([{
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: domainObjectKey
|
||||
}
|
||||
}]);
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
let timeContext = api.getIndependentContext(domainObjectKey);
|
||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
||||
destroyTimeContext();
|
||||
});
|
||||
|
||||
it("Gets an independent time context given the objectPath", () => {
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
let timeContext = api.getContextForView([{
|
||||
let timeContext = api.getContextForView([{ identifier: domainObjectKey },
|
||||
{
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'blah'
|
||||
}
|
||||
}, { identifier: domainObjectKey }]);
|
||||
}]);
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
||||
destroyTimeContext();
|
||||
});
|
||||
|
||||
it("defaults to the global time context given the objectPath", () => {
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
let timeContext = api.getContextForView([{
|
||||
identifier: {
|
||||
namespace: '',
|
||||
@ -85,7 +90,24 @@ describe("The Independent Time API", function () {
|
||||
}
|
||||
}]);
|
||||
expect(timeContext.bounds()).toEqual(bounds);
|
||||
});
|
||||
|
||||
it("follows a parent time context given the objectPath", () => {
|
||||
let timeContext = api.getContextForView([{
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'blah'
|
||||
}
|
||||
}, {
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: domainObjectKey
|
||||
}
|
||||
}]);
|
||||
let destroyTimeContext = api.addIndependentContext('blah', independentBounds);
|
||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
||||
destroyTimeContext();
|
||||
expect(timeContext.bounds()).toEqual(bounds);
|
||||
});
|
||||
|
||||
it("Allows setting of valid bounds", function () {
|
||||
@ -93,8 +115,8 @@ describe("The Independent Time API", function () {
|
||||
start: 0,
|
||||
end: 1
|
||||
};
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
expect(timeContext.bounds()).not.toEqual(bounds);
|
||||
timeContext.bounds(bounds);
|
||||
expect(timeContext.bounds()).toEqual(bounds);
|
||||
@ -107,8 +129,8 @@ describe("The Independent Time API", function () {
|
||||
end: 0
|
||||
};
|
||||
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
expect(timeContext.bounds()).not.toBe(bounds);
|
||||
|
||||
expect(timeContext.bounds.bind(timeContext, bounds)).toThrow();
|
||||
@ -122,8 +144,8 @@ describe("The Independent Time API", function () {
|
||||
});
|
||||
|
||||
it("Emits an event when bounds change", function () {
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
timeContext.on('bounds', eventListener);
|
||||
timeContext.bounds(bounds);
|
||||
@ -131,6 +153,14 @@ describe("The Independent Time API", function () {
|
||||
destroyTimeContext();
|
||||
});
|
||||
|
||||
it("Emits an event when bounds change on the global context", function () {
|
||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
timeContext.on('bounds', eventListener);
|
||||
timeContext.bounds(bounds);
|
||||
expect(eventListener).toHaveBeenCalledWith(bounds, false);
|
||||
});
|
||||
|
||||
describe(" when using real time clock", function () {
|
||||
const mockOffsets = {
|
||||
start: 10,
|
||||
@ -138,8 +168,8 @@ describe("The Independent Time API", function () {
|
||||
};
|
||||
|
||||
it("Emits an event when bounds change based on current value", function () {
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
timeContext.clock('someClockKey', mockOffsets);
|
||||
timeContext.on('bounds', eventListener);
|
||||
|
@ -112,13 +112,11 @@ export default {
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on("timeSystem", this.setScaleAndPlotImagery);
|
||||
this.timeContext.on("bounds", this.updateViewBounds);
|
||||
this.timeContext.on("timeContext", this.setTimeContext);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off("timeSystem", this.setScaleAndPlotImagery);
|
||||
this.timeContext.off("bounds", this.updateViewBounds);
|
||||
this.timeContext.off("timeContext", this.setTimeContext);
|
||||
}
|
||||
},
|
||||
expand(index) {
|
||||
|
@ -501,13 +501,11 @@ export default {
|
||||
//listen
|
||||
this.timeContext.on('timeSystem', this.trackDuration);
|
||||
this.timeContext.on('clock', this.trackDuration);
|
||||
this.timeContext.on("timeContext", this.setTimeContext);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off("timeSystem", this.trackDuration);
|
||||
this.timeContext.off("clock", this.trackDuration);
|
||||
this.timeContext.off("timeContext", this.setTimeContext);
|
||||
}
|
||||
},
|
||||
boundsChange(bounds, isTick) {
|
||||
|
@ -62,13 +62,11 @@ export default {
|
||||
this.timeContext.on('bounds', this.boundsChange);
|
||||
this.boundsChange(this.timeContext.bounds());
|
||||
this.timeContext.on('timeSystem', this.timeSystemChange);
|
||||
this.timeContext.on("timeContext", this.setDataTimeContext);
|
||||
},
|
||||
stopFollowingDataTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.boundsChange);
|
||||
this.timeContext.off('timeSystem', this.timeSystemChange);
|
||||
this.timeContext.off("timeContext", this.setDataTimeContext);
|
||||
}
|
||||
},
|
||||
datumIsNotValid(datum) {
|
||||
|
@ -120,7 +120,6 @@ export default {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
||||
this.timeContext.on("timeContext", this.setTimeContext);
|
||||
this.followTimeContext();
|
||||
},
|
||||
followTimeContext() {
|
||||
@ -133,7 +132,6 @@ export default {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off("timeSystem", this.setScaleAndPlotActivities);
|
||||
this.timeContext.off("bounds", this.updateViewBounds);
|
||||
this.timeContext.off("timeContext", this.setTimeContext);
|
||||
}
|
||||
},
|
||||
observeForChanges(mutatedObject) {
|
||||
|
@ -283,7 +283,6 @@ export default {
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
||||
this.timeContext.on('timeContext', this.setTimeContext);
|
||||
this.followTimeContext();
|
||||
|
||||
},
|
||||
@ -297,7 +296,6 @@ export default {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off("clock", this.updateRealTime);
|
||||
this.timeContext.off("bounds", this.updateDisplayBounds);
|
||||
this.timeContext.off("timeContext", this.setTimeContext);
|
||||
}
|
||||
},
|
||||
getConfig() {
|
||||
|
@ -110,7 +110,6 @@ export default {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? [{identifier: this.keyString}] : []);
|
||||
this.timeContext.on('timeContext', this.setTimeContext);
|
||||
|
||||
this.handleNewBounds(this.timeContext.bounds());
|
||||
this.timeContext.on('bounds', this.handleNewBounds);
|
||||
@ -120,7 +119,6 @@ export default {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.handleNewBounds);
|
||||
this.timeContext.off('clock', this.clearAllValidation);
|
||||
this.timeContext.off('timeContext', this.setTimeContext);
|
||||
}
|
||||
},
|
||||
handleNewBounds(bounds) {
|
||||
|
@ -138,13 +138,11 @@ export default {
|
||||
this.timeContext.off('bounds', this.handleNewBounds);
|
||||
this.timeContext.off('clock', this.clearAllValidation);
|
||||
this.timeContext.off('clockOffsets', this.setViewFromOffsets);
|
||||
this.timeContext.off('timeContext', this.setTimeContext);
|
||||
}
|
||||
},
|
||||
setTimeContext() {
|
||||
this.stopFollowingTime();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? [{identifier: this.keyString}] : []);
|
||||
this.timeContext.on('timeContext', this.setTimeContext);
|
||||
this.followTime();
|
||||
},
|
||||
handleNewBounds(bounds) {
|
||||
|
@ -150,12 +150,10 @@ export default {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView([this.domainObject]);
|
||||
this.timeContext.on('timeContext', this.setTimeContext);
|
||||
this.timeContext.on('clock', this.setTimeOptions);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('timeContext', this.setTimeContext);
|
||||
this.timeContext.off('clock', this.setTimeOptions);
|
||||
}
|
||||
},
|
||||
@ -212,7 +210,17 @@ export default {
|
||||
}
|
||||
|
||||
const key = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
const timeContext = this.openmct.time.getIndependentContext(key);
|
||||
if (!timeContext.hasOwnContext()) {
|
||||
this.unregisterIndependentTime = this.openmct.time.addIndependentContext(key, offsets, this.isFixed ? undefined : this.mode.key);
|
||||
} else {
|
||||
if (this.isFixed) {
|
||||
timeContext.stopClock();
|
||||
timeContext.bounds(offsets);
|
||||
} else {
|
||||
timeContext.clock(this.mode.key, offsets);
|
||||
}
|
||||
}
|
||||
},
|
||||
destroyIndependentTime() {
|
||||
if (this.unregisterIndependentTime) {
|
||||
|
@ -161,14 +161,12 @@ export default {
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on('timeContext', this.setTimeContext);
|
||||
this.updateViewBounds(this.timeContext.bounds());
|
||||
this.timeContext.on('bounds', this.updateViewBounds);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.updateViewBounds);
|
||||
this.timeContext.off('timeContext', this.setTimeContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ export default {
|
||||
return objectType.definition;
|
||||
},
|
||||
isPersistable() {
|
||||
let persistable = this.openmct.objects.isPersistable(this.domainObject.identifier);
|
||||
let persistable = this.domainObject.identifier && this.openmct.objects.isPersistable(this.domainObject.identifier);
|
||||
|
||||
return persistable;
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user