mirror of
https://github.com/nasa/openmct.git
synced 2025-06-18 23:28:14 +00:00
449 lines
14 KiB
JavaScript
449 lines
14 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2024, 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 { MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants.js';
|
|
import TimeContext from './TimeContext.js';
|
|
|
|
/**
|
|
* 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(openmct, globalTimeContext, objectPath) {
|
|
super();
|
|
this.openmct = openmct;
|
|
this.unlisteners = [];
|
|
this.globalTimeContext = globalTimeContext;
|
|
// 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() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.bounds(...arguments);
|
|
} else {
|
|
return super.bounds(...arguments);
|
|
}
|
|
}
|
|
|
|
getBounds() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.getBounds();
|
|
} else {
|
|
return super.getBounds();
|
|
}
|
|
}
|
|
|
|
setBounds() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.setBounds(...arguments);
|
|
} else {
|
|
return super.setBounds(...arguments);
|
|
}
|
|
}
|
|
|
|
tick() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.tick(...arguments);
|
|
} else {
|
|
return super.tick(...arguments);
|
|
}
|
|
}
|
|
|
|
clockOffsets() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.clockOffsets(...arguments);
|
|
} else {
|
|
return super.clockOffsets(...arguments);
|
|
}
|
|
}
|
|
|
|
getClockOffsets() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.getClockOffsets();
|
|
} else {
|
|
return super.getClockOffsets();
|
|
}
|
|
}
|
|
|
|
setClockOffsets() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.setClockOffsets(...arguments);
|
|
} else {
|
|
return super.setClockOffsets(...arguments);
|
|
}
|
|
}
|
|
|
|
timeOfInterest(newTOI) {
|
|
return this.globalTimeContext.timeOfInterest(...arguments);
|
|
}
|
|
|
|
timeSystem(timeSystemOrKey, bounds) {
|
|
return this.globalTimeContext.timeSystem(...arguments);
|
|
}
|
|
|
|
/**
|
|
* Get the time system of the TimeAPI.
|
|
* @returns {TimeSystem} The currently applied time system
|
|
* @memberof module:openmct.TimeAPI#
|
|
* @method getTimeSystem
|
|
*/
|
|
getTimeSystem() {
|
|
return this.globalTimeContext.getTimeSystem();
|
|
}
|
|
|
|
/**
|
|
* Set the active clock. Tick source will be immediately subscribed to
|
|
* and ticking will begin. Offsets from 'now' must also be provided.
|
|
*
|
|
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
|
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
|
* the start and end bounds. This maintains a sliding time window of a fixed
|
|
* width that automatically updates.
|
|
* @fires module:openmct.TimeAPI~clock
|
|
* @return {Clock} the currently active clock;
|
|
*/
|
|
clock(keyOrClock, offsets) {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.clock(...arguments);
|
|
}
|
|
|
|
if (arguments.length === 2) {
|
|
let clock;
|
|
|
|
if (typeof keyOrClock === 'string') {
|
|
clock = this.globalTimeContext.clocks.get(keyOrClock);
|
|
if (clock === undefined) {
|
|
throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
|
|
}
|
|
} else if (typeof keyOrClock === 'object') {
|
|
clock = keyOrClock;
|
|
if (!this.globalTimeContext.clocks.has(clock.key)) {
|
|
throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
|
|
}
|
|
}
|
|
|
|
const previousClock = this.activeClock;
|
|
if (previousClock !== undefined) {
|
|
previousClock.off('tick', this.tick);
|
|
}
|
|
|
|
this.activeClock = clock;
|
|
|
|
/**
|
|
* The active clock has changed.
|
|
* @event clock
|
|
* @memberof module:openmct.TimeAPI~
|
|
* @property {Clock} clock The newly activated clock, or undefined
|
|
* if the system is no longer following a clock source
|
|
*/
|
|
this.emit('clock', this.activeClock);
|
|
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
|
|
|
if (this.activeClock !== undefined) {
|
|
//set the mode here or isRealtime will be false even if we're in clock mode
|
|
this.setMode(REALTIME_MODE_KEY);
|
|
|
|
this.clockOffsets(offsets);
|
|
this.activeClock.on('tick', this.tick);
|
|
}
|
|
} else if (arguments.length === 1) {
|
|
throw 'When setting the clock, clock offsets must also be provided';
|
|
}
|
|
|
|
return this.activeClock;
|
|
}
|
|
|
|
/**
|
|
* Get the active clock.
|
|
* @return {Clock} the currently active clock;
|
|
*/
|
|
getClock() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.getClock();
|
|
}
|
|
|
|
return this.activeClock;
|
|
}
|
|
|
|
/**
|
|
* Set the active clock. Tick source will be immediately subscribed to
|
|
* and the currently ticking will begin.
|
|
* Offsets from 'now', if provided, will be used to set realtime mode offsets
|
|
*
|
|
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
|
* @fires module:openmct.TimeAPI~clock
|
|
* @return {Clock} the currently active clock;
|
|
*/
|
|
setClock(keyOrClock) {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.setClock(...arguments);
|
|
}
|
|
|
|
let clock;
|
|
|
|
if (typeof keyOrClock === 'string') {
|
|
clock = this.globalTimeContext.clocks.get(keyOrClock);
|
|
if (clock === undefined) {
|
|
throw `Unknown clock ${keyOrClock}. Has it been registered with 'addClock'?`;
|
|
}
|
|
} else if (typeof keyOrClock === 'object') {
|
|
clock = keyOrClock;
|
|
if (!this.globalTimeContext.clocks.has(clock.key)) {
|
|
throw `Unknown clock ${keyOrClock.key}. Has it been registered with 'addClock'?`;
|
|
}
|
|
}
|
|
|
|
const previousClock = this.activeClock;
|
|
if (previousClock) {
|
|
previousClock.off('tick', this.tick);
|
|
}
|
|
|
|
this.activeClock = clock;
|
|
this.activeClock.on('tick', this.tick);
|
|
|
|
/**
|
|
* The active clock has changed.
|
|
* @event clock
|
|
* @memberof module:openmct.TimeAPI~
|
|
* @property {Clock} clock The newly activated clock, or undefined
|
|
* if the system is no longer following a clock source
|
|
*/
|
|
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
|
|
|
return this.activeClock;
|
|
}
|
|
|
|
/**
|
|
* Get the current mode.
|
|
* @return {Mode} the current mode;
|
|
*/
|
|
getMode() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.getMode();
|
|
}
|
|
|
|
return this.mode;
|
|
}
|
|
|
|
/**
|
|
* Set the mode to either fixed or realtime.
|
|
*
|
|
* @param {Mode} mode The mode to activate
|
|
* @param {TimeBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width
|
|
* @fires module:openmct.TimeAPI~clock
|
|
* @return {Mode} the currently active mode;
|
|
*/
|
|
setMode(mode, offsetsOrBounds) {
|
|
if (!mode) {
|
|
return;
|
|
}
|
|
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.setMode(...arguments);
|
|
}
|
|
|
|
if (mode === MODES.realtime && this.activeClock === undefined) {
|
|
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
|
|
}
|
|
|
|
if (mode !== this.mode) {
|
|
this.mode = mode;
|
|
/**
|
|
* The active mode has changed.
|
|
* @event modeChanged
|
|
* @memberof module:openmct.TimeAPI~
|
|
* @property {Mode} mode The newly activated mode
|
|
*/
|
|
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode));
|
|
}
|
|
|
|
//We are also going to set bounds here
|
|
if (offsetsOrBounds !== undefined) {
|
|
if (this.mode === REALTIME_MODE_KEY) {
|
|
this.setClockOffsets(offsetsOrBounds);
|
|
} else {
|
|
this.setBounds(offsetsOrBounds);
|
|
}
|
|
}
|
|
|
|
return this.mode;
|
|
}
|
|
|
|
isRealTime() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.isRealTime(...arguments);
|
|
} else {
|
|
return super.isRealTime(...arguments);
|
|
}
|
|
}
|
|
|
|
now() {
|
|
if (this.upstreamTimeContext) {
|
|
return this.upstreamTimeContext.now(...arguments);
|
|
} else {
|
|
return super.now(...arguments);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
followTimeContext() {
|
|
this.stopFollowingTimeContext();
|
|
if (this.upstreamTimeContext) {
|
|
Object.values(TIME_CONTEXT_EVENTS).forEach((eventName) => {
|
|
const thisTimeContext = this;
|
|
this.upstreamTimeContext.on(eventName, passthrough);
|
|
this.unlisteners.push(() => {
|
|
thisTimeContext.upstreamTimeContext.off(eventName, passthrough);
|
|
});
|
|
function passthrough() {
|
|
thisTimeContext.emit(eventName, ...arguments);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops following any upstream time context
|
|
*/
|
|
stopFollowingTimeContext() {
|
|
this.unlisteners.forEach((unlisten) => unlisten());
|
|
this.unlisteners = [];
|
|
}
|
|
|
|
resetContext() {
|
|
if (this.upstreamTimeContext) {
|
|
this.stopFollowingTimeContext();
|
|
this.upstreamTimeContext = undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh the time context, following any upstream time contexts as necessary
|
|
*/
|
|
refreshContext(viewKey) {
|
|
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
|
|
if (viewKey && key === viewKey) {
|
|
return;
|
|
}
|
|
|
|
//this is necessary as the upstream context gets reassigned after this
|
|
this.stopFollowingTimeContext();
|
|
|
|
this.upstreamTimeContext = this.getUpstreamContext();
|
|
this.followTimeContext();
|
|
|
|
// Emit bounds so that views that are changing context get the upstream bounds
|
|
this.emit('bounds', this.bounds());
|
|
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
|
|
}
|
|
|
|
hasOwnContext() {
|
|
return this.upstreamTimeContext === undefined;
|
|
}
|
|
|
|
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);
|
|
// 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
|
|
timeContext = itemContext;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
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();
|
|
if (this.activeClock !== undefined) {
|
|
this.activeClock.off('tick', this.tick);
|
|
}
|
|
|
|
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.getBounds());
|
|
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
#copy(object) {
|
|
return JSON.parse(JSON.stringify(object));
|
|
}
|
|
}
|
|
|
|
export default IndependentTimeContext;
|