Merge branch 'master' into mobile-itc

This commit is contained in:
Rukmini Bose (Ruki) 2024-04-18 10:18:05 -07:00 committed by GitHub
commit 5f427bde73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 808 additions and 335 deletions

View File

@ -0,0 +1,47 @@
/*****************************************************************************
* 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.
*****************************************************************************/
const isMac = process.platform === 'darwin';
const modifier = isMac ? 'Meta' : 'Control';
/**
* @param {import('@playwright/test').Page} page
*/
async function selectAll(page) {
await page.keyboard.press(`${modifier}+KeyA`);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function copy(page) {
await page.keyboard.press(`${modifier}+KeyC`);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function paste(page) {
await page.keyboard.press(`${modifier}+KeyV`);
}
export { copy, paste, selectAll };

View File

@ -0,0 +1,23 @@
/*****************************************************************************
* 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.
*****************************************************************************/
export * from './clipboard.js';

View File

@ -28,16 +28,28 @@ import { fileURLToPath } from 'url';
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
* @param {string} text
*/ */
async function enterTextEntry(page, text) { async function enterTextEntry(page, text) {
// Click the 'Add Notebook Entry' area await addNotebookEntry(page);
await page.locator(NOTEBOOK_DROP_AREA).click(); await enterTextInLastEntry(page, text);
// enter text
await page.getByLabel('Notebook Entry Input').last().fill(text);
await commitEntry(page); await commitEntry(page);
} }
/**
* @param {import('@playwright/test').Page} page
*/
async function addNotebookEntry(page) {
await page.locator(NOTEBOOK_DROP_AREA).click();
}
/**
* @param {import('@playwright/test').Page} page
*/
async function enterTextInLastEntry(page, text) {
await page.getByLabel('Notebook Entry Input').last().fill(text);
}
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
@ -140,10 +152,13 @@ async function createNotebookEntryAndTags(page, iterations = 1) {
} }
export { export {
addNotebookEntry,
commitEntry,
createNotebookAndEntry, createNotebookAndEntry,
createNotebookEntryAndTags, createNotebookEntryAndTags,
dragAndDropEmbed, dragAndDropEmbed,
enterTextEntry, enterTextEntry,
enterTextInLastEntry,
lockPage, lockPage,
startAndAddRestrictedNotebookObject startAndAddRestrictedNotebookObject
}; };

View File

@ -27,6 +27,7 @@ This test suite is dedicated to tests which verify the basic operations surround
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { createDomainObjectWithDefaults } from '../../../../appActions.js'; import { createDomainObjectWithDefaults } from '../../../../appActions.js';
import { copy, paste, selectAll } from '../../../../helper/hotkeys/hotkeys.js';
import * as nbUtils from '../../../../helper/notebookUtils.js'; import * as nbUtils from '../../../../helper/notebookUtils.js';
import { expect, streamToString, test } from '../../../../pluginFixtures.js'; import { expect, streamToString, test } from '../../../../pluginFixtures.js';
@ -546,4 +547,53 @@ test.describe('Notebook entry tests', () => {
); );
await expect(secondLineOfBlockquoteText).toBeVisible(); await expect(secondLineOfBlockquoteText).toBeVisible();
}); });
/**
* Paste into notebook entry tests
*/
test('Can paste text into a notebook entry', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7686'
});
const TEST_TEXT = 'This is a test';
const iterations = 20;
const EXPECTED_TEXT = TEST_TEXT.repeat(iterations);
await page.goto(notebookObject.url);
await nbUtils.addNotebookEntry(page);
await nbUtils.enterTextInLastEntry(page, TEST_TEXT);
await selectAll(page);
await copy(page);
for (let i = 0; i < iterations; i++) {
await paste(page);
}
await nbUtils.commitEntry(page);
await expect(page.locator(`text="${EXPECTED_TEXT}"`)).toBeVisible();
});
test('Prevents pasting text into selected notebook entry if not editing', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7686'
});
const TEST_TEXT = 'This is a test';
await page.goto(notebookObject.url);
await nbUtils.addNotebookEntry(page);
await nbUtils.enterTextInLastEntry(page, TEST_TEXT);
await selectAll(page);
await copy(page);
await paste(page);
await nbUtils.commitEntry(page);
// This should not paste text into the entry
await paste(page);
await expect(await page.locator(`text="${TEST_TEXT.repeat(1)}"`).count()).toEqual(1);
await expect(await page.locator(`text="${TEST_TEXT.repeat(2)}"`).count()).toEqual(0);
});
}); });

View File

@ -22,6 +22,10 @@
import TimeContext from './TimeContext.js'; import TimeContext from './TimeContext.js';
/**
* @typedef {import('./TimeAPI').TimeConductorBounds} TimeConductorBounds
*/
/** /**
* The GlobalContext handles getting and setting time of the openmct application in general. * The GlobalContext handles getting and setting time of the openmct application in general.
* Views will use this context unless they specify an alternate/independent time context * Views will use this context unless they specify an alternate/independent time context
@ -38,12 +42,10 @@ class GlobalTimeContext extends TimeContext {
* Get or set the start and end time of the time conductor. Basic validation * Get or set the start and end time of the time conductor. Basic validation
* of bounds is performed. * of bounds is performed.
* *
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds * @param {TimeConductorBounds} newBounds
* @throws {Error} Validation error * @throws {Error} Validation error
* @fires module:openmct.TimeAPI~bounds * @returns {TimeConductorBounds}
* @returns {module:openmct.TimeAPI~TimeConductorBounds} * @override
* @memberof module:openmct.TimeAPI#
* @method bounds
*/ */
bounds(newBounds) { bounds(newBounds) {
if (arguments.length > 0) { if (arguments.length > 0) {
@ -61,9 +63,9 @@ class GlobalTimeContext extends TimeContext {
/** /**
* Update bounds based on provided time and current offsets * Update bounds based on provided time and current offsets
* @private
* @param {number} timestamp A time from which bounds will be calculated * @param {number} timestamp A time from which bounds will be calculated
* using current offsets. * using current offsets.
* @override
*/ */
tick(timestamp) { tick(timestamp) {
super.tick.call(this, ...arguments); super.tick.call(this, ...arguments);
@ -81,11 +83,8 @@ class GlobalTimeContext extends TimeContext {
* be manipulated by the user from the time conductor or from other views. * be manipulated by the user from the time conductor or from other views.
* The time of interest can effectively be unset by assigning a value of * The time of interest can effectively be unset by assigning a value of
* 'undefined'. * 'undefined'.
* @fires module:openmct.TimeAPI~timeOfInterest
* @param newTOI * @param newTOI
* @returns {number} the current time of interest * @returns {number} the current time of interest
* @memberof module:openmct.TimeAPI#
* @method timeOfInterest
*/ */
timeOfInterest(newTOI) { timeOfInterest(newTOI) {
if (arguments.length > 0) { if (arguments.length > 0) {
@ -93,8 +92,7 @@ class GlobalTimeContext extends TimeContext {
/** /**
* The Time of Interest has moved. * The Time of Interest has moved.
* @event timeOfInterest * @event timeOfInterest
* @memberof module:openmct.TimeAPI~ * @property {number} timeOfInterest time of interest
* @property {number} Current time of interest
*/ */
this.emit('timeOfInterest', this.toi); this.emit('timeOfInterest', this.toi);
} }

View File

@ -23,19 +23,36 @@
import { MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants.js'; import { MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants.js';
import TimeContext from './TimeContext.js'; import TimeContext from './TimeContext.js';
/**
* @typedef {import('./TimeAPI.js').default} TimeAPI
* @typedef {import('./GlobalTimeContext.js').default} GlobalTimeContext
* @typedef {import('./TimeAPI.js').TimeSystem} TimeSystem
* @typedef {import('./TimeContext.js').Mode} Mode
* @typedef {import('./TimeContext.js').TimeConductorBounds} TimeConductorBounds
* @typedef {import('./TimeAPI.js').ClockOffsets} ClockOffsets
*/
/** /**
* The IndependentTimeContext handles getting and setting time of the openmct application in general. * 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. * Views will use the GlobalTimeContext unless they specify an alternate/independent time context here.
*/ */
class IndependentTimeContext extends TimeContext { class IndependentTimeContext extends TimeContext {
/**
* @param {import('openmct').OpenMCT} openmct - The Open MCT application instance.
* @param {TimeAPI & GlobalTimeContext} globalTimeContext - The global time context.
* @param {import('openmct').ObjectPath} objectPath - The path of objects.
*/
constructor(openmct, globalTimeContext, objectPath) { constructor(openmct, globalTimeContext, objectPath) {
super(); super();
/** @type {any} */
this.openmct = openmct; this.openmct = openmct;
/** @type {Function[]} */
this.unlisteners = []; this.unlisteners = [];
/** @type {TimeAPI & GlobalTimeContext | undefined} */
this.globalTimeContext = globalTimeContext; this.globalTimeContext = globalTimeContext;
// We always start with the global time context. /** @type {TimeAPI & GlobalTimeContext | undefined} */
// This upstream context will be undefined when an independent time context is added later.
this.upstreamTimeContext = this.globalTimeContext; this.upstreamTimeContext = this.globalTimeContext;
/** @type {Array<any>} */
this.objectPath = objectPath; this.objectPath = objectPath;
this.refreshContext = this.refreshContext.bind(this); this.refreshContext = this.refreshContext.bind(this);
this.resetContext = this.resetContext.bind(this); this.resetContext = this.resetContext.bind(this);
@ -47,6 +64,10 @@ class IndependentTimeContext extends TimeContext {
this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext); this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext);
} }
/**
* @deprecated
* @override
*/
bounds() { bounds() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
return this.upstreamTimeContext.bounds(...arguments); return this.upstreamTimeContext.bounds(...arguments);
@ -55,6 +76,9 @@ class IndependentTimeContext extends TimeContext {
} }
} }
/**
* @override
*/
getBounds() { getBounds() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
return this.upstreamTimeContext.getBounds(); return this.upstreamTimeContext.getBounds();
@ -63,6 +87,9 @@ class IndependentTimeContext extends TimeContext {
} }
} }
/**
* @override
*/
setBounds() { setBounds() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
return this.upstreamTimeContext.setBounds(...arguments); return this.upstreamTimeContext.setBounds(...arguments);
@ -71,6 +98,9 @@ class IndependentTimeContext extends TimeContext {
} }
} }
/**
* @override
*/
tick() { tick() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
return this.upstreamTimeContext.tick(...arguments); return this.upstreamTimeContext.tick(...arguments);
@ -79,6 +109,9 @@ class IndependentTimeContext extends TimeContext {
} }
} }
/**
* @override
*/
clockOffsets() { clockOffsets() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
return this.upstreamTimeContext.clockOffsets(...arguments); return this.upstreamTimeContext.clockOffsets(...arguments);
@ -87,6 +120,9 @@ class IndependentTimeContext extends TimeContext {
} }
} }
/**
* @override
*/
getClockOffsets() { getClockOffsets() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
return this.upstreamTimeContext.getClockOffsets(); return this.upstreamTimeContext.getClockOffsets();
@ -95,6 +131,9 @@ class IndependentTimeContext extends TimeContext {
} }
} }
/**
* @override
*/
setClockOffsets() { setClockOffsets() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
return this.upstreamTimeContext.setClockOffsets(...arguments); return this.upstreamTimeContext.setClockOffsets(...arguments);
@ -103,12 +142,24 @@ class IndependentTimeContext extends TimeContext {
} }
} }
/**
*
* @param {number} newTOI
* @returns {number}
*/
timeOfInterest(newTOI) { timeOfInterest(newTOI) {
return this.globalTimeContext.timeOfInterest(...arguments); return this.globalTimeContext.timeOfInterest(...arguments);
} }
/**
*
* @param {TimeSystem | string} timeSystemOrKey
* @param {TimeConductorBounds} bounds
* @returns {TimeSystem}
* @override
*/
timeSystem(timeSystemOrKey, bounds) { timeSystem(timeSystemOrKey, bounds) {
return this.globalTimeContext.timeSystem(...arguments); return this.globalTimeContext.setTimeSystem(...arguments);
} }
/** /**
@ -116,6 +167,7 @@ class IndependentTimeContext extends TimeContext {
* @returns {TimeSystem} The currently applied time system * @returns {TimeSystem} The currently applied time system
* @memberof module:openmct.TimeAPI# * @memberof module:openmct.TimeAPI#
* @method getTimeSystem * @method getTimeSystem
* @override
*/ */
getTimeSystem() { getTimeSystem() {
return this.globalTimeContext.getTimeSystem(); return this.globalTimeContext.getTimeSystem();
@ -246,6 +298,7 @@ class IndependentTimeContext extends TimeContext {
/** /**
* Get the current mode. * Get the current mode.
* @return {Mode} the current mode; * @return {Mode} the current mode;
* @override
*/ */
getMode() { getMode() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
@ -259,9 +312,8 @@ class IndependentTimeContext extends TimeContext {
* Set the mode to either fixed or realtime. * Set the mode to either fixed or realtime.
* *
* @param {Mode} mode The mode to activate * @param {Mode} mode The mode to activate
* @param {TimeBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width * @param {TimeConductorBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width
* @fires module:openmct.TimeAPI~clock * @return {Mode | undefined} the currently active mode;
* @return {Mode} the currently active mode;
*/ */
setMode(mode, offsetsOrBounds) { setMode(mode, offsetsOrBounds) {
if (!mode) { if (!mode) {
@ -299,6 +351,10 @@ class IndependentTimeContext extends TimeContext {
return this.mode; return this.mode;
} }
/**
* @returns {boolean}
* @override
*/
isRealTime() { isRealTime() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
return this.upstreamTimeContext.isRealTime(...arguments); return this.upstreamTimeContext.isRealTime(...arguments);
@ -307,6 +363,10 @@ class IndependentTimeContext extends TimeContext {
} }
} }
/**
* @returns {number}
* @override
*/
now() { now() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
return this.upstreamTimeContext.now(...arguments); return this.upstreamTimeContext.now(...arguments);
@ -343,6 +403,9 @@ class IndependentTimeContext extends TimeContext {
this.unlisteners = []; this.unlisteners = [];
} }
/**
* Reset the time context to the global time context
*/
resetContext() { resetContext() {
if (this.upstreamTimeContext) { if (this.upstreamTimeContext) {
this.stopFollowingTimeContext(); this.stopFollowingTimeContext();
@ -352,6 +415,7 @@ class IndependentTimeContext extends TimeContext {
/** /**
* Refresh the time context, following any upstream time contexts as necessary * Refresh the time context, following any upstream time contexts as necessary
* @param {string} [viewKey] The key of the view to refresh
*/ */
refreshContext(viewKey) { refreshContext(viewKey) {
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier); const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
@ -366,14 +430,21 @@ class IndependentTimeContext extends TimeContext {
this.followTimeContext(); this.followTimeContext();
// Emit bounds so that views that are changing context get the upstream bounds // Emit bounds so that views that are changing context get the upstream bounds
this.emit('bounds', this.bounds()); this.emit('bounds', this.getBounds());
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds()); this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
} }
/**
* @returns {boolean} True if this time context has an independent context, false otherwise
*/
hasOwnContext() { hasOwnContext() {
return this.upstreamTimeContext === undefined; return this.upstreamTimeContext === undefined;
} }
/**
* Get the upstream time context of this time context
* @returns {TimeAPI & GlobalTimeContext | undefined} The upstream time context
*/
getUpstreamContext() { getUpstreamContext() {
// If a view has an independent context, don't return an upstream context // 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 // Be aware that when a new independent time context is created, we assign the global context as default

View File

@ -26,30 +26,16 @@ import IndependentTimeContext from '@/api/time/IndependentTimeContext';
import GlobalTimeContext from './GlobalTimeContext.js'; import GlobalTimeContext from './GlobalTimeContext.js';
/** /**
* The public API for setting and querying the temporal state of the * @typedef {import('./TimeContext.js').default} TimeContext
* application. The concept of time is integral to Open MCT, and at least */
* one {@link TimeSystem}, as well as some default time bounds must be
* registered and enabled via {@link TimeAPI.addTimeSystem} and /**
* {@link TimeAPI.timeSystem} respectively for Open MCT to work. * @typedef {import('./TimeContext.js').TimeConductorBounds} TimeConductorBounds
* */
* Time-sensitive views will typically respond to changes to bounds or other
* properties of the time conductor and update the data displayed based on /**
* the temporal state of the application. The current time bounds are also * @typedef {import('./TimeContext.js').ClockOffsets} ClockOffsets
* used in queries for historical data.
*
* The TimeAPI extends the GlobalTimeContext which in turn extends the TimeContext/EventEmitter class. A number of events are
* fired when properties of the time conductor change, which are documented
* below.
*
* @interface
* @memberof module:openmct
*/ */
class TimeAPI extends GlobalTimeContext {
constructor(openmct) {
super();
this.openmct = openmct;
this.independentContexts = new Map();
}
/** /**
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open * A TimeSystem provides meaning to the values returned by the TimeAPI. Open
@ -74,10 +60,35 @@ class TimeAPI extends GlobalTimeContext {
* displaying a duration or relative span of time in this time system. * displaying a duration or relative span of time in this time system.
*/ */
/**
* The public API for setting and querying the temporal state of the
* application. The concept of time is integral to Open MCT, and at least
* one {@link TimeSystem}, as well as some default time bounds must be
* registered and enabled via {@link TimeAPI.addTimeSystem} and
* {@link TimeAPI.timeSystem} respectively for Open MCT to work.
*
* Time-sensitive views will typically respond to changes to bounds or other
* properties of the time conductor and update the data displayed based on
* the temporal state of the application. The current time bounds are also
* used in queries for historical data.
*
* The TimeAPI extends the GlobalTimeContext which in turn extends the TimeContext/EventEmitter class. A number of events are
* fired when properties of the time conductor change, which are documented
* below.
*
* @class
* @extends {GlobalTimeContext}
*/
class TimeAPI extends GlobalTimeContext {
constructor(openmct) {
super();
this.openmct = openmct;
this.independentContexts = new Map();
}
/** /**
* Register a new time system. Once registered it can activated using * Register a new time system. Once registered it can activated using
* {@link TimeAPI.timeSystem}, and can be referenced via its key in [Time Conductor configuration](@link https://github.com/nasa/openmct/blob/master/API.md#time-conductor). * {@link TimeAPI.timeSystem}, and can be referenced via its key in [Time Conductor configuration](@link https://github.com/nasa/openmct/blob/master/API.md#time-conductor).
* @memberof module:openmct.TimeAPI#
* @param {TimeSystem} timeSystem A time system object. * @param {TimeSystem} timeSystem A time system object.
*/ */
addTimeSystem(timeSystem) { addTimeSystem(timeSystem) {
@ -109,7 +120,6 @@ class TimeAPI extends GlobalTimeContext {
/** /**
* Register a new Clock. * Register a new Clock.
* @memberof module:openmct.TimeAPI#
* @param {Clock} clock * @param {Clock} clock
*/ */
addClock(clock) { addClock(clock) {
@ -117,9 +127,7 @@ class TimeAPI extends GlobalTimeContext {
} }
/** /**
* @memberof module:openmct.TimeAPI#
* @returns {Clock[]} * @returns {Clock[]}
* @memberof module:openmct.TimeAPI#
*/ */
getAllClocks() { getAllClocks() {
return Array.from(this.clocks.values()); return Array.from(this.clocks.values());
@ -128,11 +136,9 @@ class TimeAPI extends GlobalTimeContext {
/** /**
* Get or set an independent time context which follows the TimeAPI timeSystem, * Get or set an independent time context which follows the TimeAPI timeSystem,
* but with different offsets for a given domain object * but with different offsets for a given domain object
* @param {key | string} key The identifier key of the domain object these offsets are set for * @param {string} key The identifier key of the domain object these offsets are set for
* @param {ClockOffsets | TimeBounds} value This maintains a sliding time window of a fixed width that automatically updates * @param {ClockOffsets | TimeConductorBounds} value This maintains a sliding time window of a fixed width that automatically updates
* @param {key | string} clockKey the real time clock key currently in use * @param {key | string} clockKey the real time clock key currently in use
* @memberof module:openmct.TimeAPI#
* @method addIndependentTimeContext
*/ */
addIndependentContext(key, value, clockKey) { addIndependentContext(key, value, clockKey) {
let timeContext = this.getIndependentContext(key); let timeContext = this.getIndependentContext(key);
@ -159,9 +165,8 @@ class TimeAPI extends GlobalTimeContext {
/** /**
* Get the independent time context which follows the TimeAPI timeSystem, * Get the independent time context which follows the TimeAPI timeSystem,
* but with different offsets. * but with different offsets.
* @param {key | string} key The identifier key of the domain object these offsets * @param {string} key The identifier key of the domain object these offsets
* @memberof module:openmct.TimeAPI# * @returns {IndependentTimeContext} The independent time context
* @method getIndependentTimeContext
*/ */
getIndependentContext(key) { getIndependentContext(key) {
return this.independentContexts.get(key); return this.independentContexts.get(key);
@ -171,8 +176,7 @@ class TimeAPI extends GlobalTimeContext {
* Get the a timeContext for a view based on it's objectPath. If there is any object in the objectPath with an independent time context, it will be returned. * Get the a timeContext for a view based on it's objectPath. If there is any object in the objectPath with an independent time context, it will be returned.
* Otherwise, the global time context will be returned. * Otherwise, the global time context will be returned.
* @param {Array} objectPath The view's objectPath * @param {Array} objectPath The view's objectPath
* @memberof module:openmct.TimeAPI# * @returns {TimeContext | GlobalTimeContext} The time context
* @method getContextForView
*/ */
getContextForView(objectPath) { getContextForView(objectPath) {
if (!objectPath || !Array.isArray(objectPath)) { if (!objectPath || !Array.isArray(objectPath)) {

View File

@ -57,7 +57,7 @@ describe('The Time API', function () {
expect(api.timeOfInterest()).toBe(toi); expect(api.timeOfInterest()).toBe(toi);
}); });
it('Allows setting of valid bounds', function () { it('[Legacy TimeAPI]: Allows setting of valid bounds', function () {
bounds = { bounds = {
start: 0, start: 0,
end: 1 end: 1
@ -67,7 +67,17 @@ describe('The Time API', function () {
expect(api.bounds()).toEqual(bounds); expect(api.bounds()).toEqual(bounds);
}); });
it('Disallows setting of invalid bounds', function () { it('Allows setting of valid bounds', function () {
bounds = {
start: 0,
end: 1
};
expect(api.getBounds()).not.toBe(bounds);
expect(api.setBounds.bind(api, bounds)).not.toThrow();
expect(api.getBounds()).toEqual(bounds);
});
it('[Legacy TimeAPI]: Disallows setting of invalid bounds', function () {
bounds = { bounds = {
start: 1, start: 1,
end: 0 end: 0
@ -82,7 +92,22 @@ describe('The Time API', function () {
expect(api.bounds()).not.toEqual(bounds); expect(api.bounds()).not.toEqual(bounds);
}); });
it('Allows setting of previously registered time system with bounds', function () { it('Disallows setting of invalid bounds', function () {
bounds = {
start: 1,
end: 0
};
expect(api.getBounds()).not.toEqual(bounds);
expect(api.setBounds.bind(api, bounds)).toThrow();
expect(api.getBounds()).not.toEqual(bounds);
bounds = { start: 1 };
expect(api.getBounds()).not.toEqual(bounds);
expect(api.setBounds.bind(api, bounds)).toThrow();
expect(api.getBounds()).not.toEqual(bounds);
});
it('[Legacy TimeAPI]: Allows setting of previously registered time system with bounds', function () {
api.addTimeSystem(timeSystem); api.addTimeSystem(timeSystem);
expect(api.timeSystem()).not.toBe(timeSystem); expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () { expect(function () {
@ -91,7 +116,16 @@ describe('The Time API', function () {
expect(api.timeSystem()).toEqual(timeSystem); expect(api.timeSystem()).toEqual(timeSystem);
}); });
it('Disallows setting of time system without bounds', function () { it('Allows setting of previously registered time system with bounds', function () {
api.addTimeSystem(timeSystem);
expect(api.getTimeSystem()).not.toBe(timeSystem);
expect(function () {
api.setTimeSystem(timeSystem, bounds);
}).not.toThrow();
expect(api.getTimeSystem()).toEqual(timeSystem);
});
it('[Legacy TimeAPI]: Disallows setting of time system without bounds', function () {
api.addTimeSystem(timeSystem); api.addTimeSystem(timeSystem);
expect(api.timeSystem()).not.toBe(timeSystem); expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () { expect(function () {
@ -100,6 +134,32 @@ describe('The Time API', function () {
expect(api.timeSystem()).not.toBe(timeSystem); expect(api.timeSystem()).not.toBe(timeSystem);
}); });
it('Allows setting of time system without bounds', function () {
api.addTimeSystem(timeSystem);
expect(api.getTimeSystem()).not.toBe(timeSystem);
expect(function () {
api.setTimeSystem(timeSystemKey);
}).not.toThrow();
expect(api.getTimeSystem()).not.toBe(timeSystem);
});
it('Disallows setting of invalid time system', function () {
expect(function () {
api.setTimeSystem();
}).toThrow();
expect(function () {
api.setTimeSystem('invalidTimeSystemKey');
}).toThrow();
expect(function () {
api.setTimeSystem({
key: 'invalidTimeSystemKey'
});
}).toThrow();
expect(function () {
api.setTimeSystem(42);
}).toThrow();
});
it('allows setting of timesystem without bounds with clock', function () { it('allows setting of timesystem without bounds with clock', function () {
api.addTimeSystem(timeSystem); api.addTimeSystem(timeSystem);
api.addClock(clock); api.addClock(clock);
@ -114,7 +174,7 @@ describe('The Time API', function () {
expect(api.timeSystem()).toEqual(timeSystem); expect(api.timeSystem()).toEqual(timeSystem);
}); });
it('Emits an event when time system changes', function () { it('Emits a legacy event when time system changes', function () {
api.addTimeSystem(timeSystem); api.addTimeSystem(timeSystem);
expect(eventListener).not.toHaveBeenCalled(); expect(eventListener).not.toHaveBeenCalled();
api.on('timeSystem', eventListener); api.on('timeSystem', eventListener);
@ -122,6 +182,14 @@ describe('The Time API', function () {
expect(eventListener).toHaveBeenCalledWith(timeSystem); expect(eventListener).toHaveBeenCalledWith(timeSystem);
}); });
it('Emits an event when time system changes', function () {
api.addTimeSystem(timeSystem);
expect(eventListener).not.toHaveBeenCalled();
api.on('timeSystemChanged', eventListener);
api.timeSystem(timeSystemKey, bounds);
expect(eventListener).toHaveBeenCalledWith(timeSystem);
});
it('Emits an event when time of interest changes', function () { it('Emits an event when time of interest changes', function () {
expect(eventListener).not.toHaveBeenCalled(); expect(eventListener).not.toHaveBeenCalled();
api.on('timeOfInterest', eventListener); api.on('timeOfInterest', eventListener);
@ -129,13 +197,20 @@ describe('The Time API', function () {
expect(eventListener).toHaveBeenCalledWith(toi); expect(eventListener).toHaveBeenCalledWith(toi);
}); });
it('Emits an event when bounds change', function () { it('Emits a legacy event when bounds change', function () {
expect(eventListener).not.toHaveBeenCalled(); expect(eventListener).not.toHaveBeenCalled();
api.on('bounds', eventListener); api.on('bounds', eventListener);
api.bounds(bounds); api.bounds(bounds);
expect(eventListener).toHaveBeenCalledWith(bounds, false); expect(eventListener).toHaveBeenCalledWith(bounds, false);
}); });
it('Emits an event when bounds change', function () {
expect(eventListener).not.toHaveBeenCalled();
api.on('boundsChanged', eventListener);
api.bounds(bounds);
expect(eventListener).toHaveBeenCalledWith(bounds, false);
});
it('If bounds are set and TOI lies inside them, do not change TOI', function () { it('If bounds are set and TOI lies inside them, do not change TOI', function () {
api.timeOfInterest(6); api.timeOfInterest(6);
api.bounds({ api.bounds({
@ -154,13 +229,39 @@ describe('The Time API', function () {
expect(api.timeOfInterest()).toBeUndefined(); expect(api.timeOfInterest()).toBeUndefined();
}); });
it('Maintains delta during tick', function () {}); it('Maintains delta during tick', function () {
const initialBounds = { start: 100, end: 200 };
api.bounds(initialBounds);
const mockTickSource = jasmine.createSpyObj('mockTickSource', ['on', 'off', 'currentValue']);
mockTickSource.key = 'mct';
mockTickSource.currentValue.and.returnValue(150);
api.addClock(mockTickSource);
api.clock('mct', { start: 0, end: 100 });
it('Allows registered time system to be activated', function () {}); // Simulate a tick event
const tickCallback = mockTickSource.on.calls.mostRecent().args[1];
tickCallback(150);
const newBounds = api.bounds();
expect(newBounds.end - newBounds.start).toEqual(initialBounds.end - initialBounds.start);
});
it('Allows registered time system to be activated', function () {
api.addClock(clock);
api.clock(clockKey, { start: 0, end: 100 });
api.addTimeSystem(timeSystem);
api.timeSystem(timeSystemKey);
expect(api.timeSystem().key).toEqual(timeSystemKey);
});
it('Allows a registered tick source to be activated', function () { it('Allows a registered tick source to be activated', function () {
const mockTickSource = jasmine.createSpyObj('mockTickSource', ['on', 'off', 'currentValue']); const mockTickSource = jasmine.createSpyObj('mockTickSource', ['on', 'off', 'currentValue']);
mockTickSource.key = 'mockTickSource'; mockTickSource.key = 'mockTickSource';
mockTickSource.currentValue.and.returnValue(50);
api.addClock(mockTickSource);
api.clock(mockTickSource.key, { start: 0, end: 100 });
expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function));
}); });
describe(' when enabling a tick source', function () { describe(' when enabling a tick source', function () {
@ -184,7 +285,7 @@ describe('The Time API', function () {
api.addClock(anotherMockTickSource); api.addClock(anotherMockTickSource);
}); });
it('sets bounds based on current value', function () { it('[Legacy TimeAPI]: sets bounds based on current value', function () {
api.clock('mts', mockOffsets); api.clock('mts', mockOffsets);
expect(api.bounds()).toEqual({ expect(api.bounds()).toEqual({
start: 10, start: 10,
@ -192,23 +293,46 @@ describe('The Time API', function () {
}); });
}); });
it('a new tick listener is registered', function () { it('does not set bounds based on current value', function () {
api.setClock('mts');
expect(api.getBounds()).toEqual({});
});
it('does not set invalid clock', function () {
expect(function () {
api.setClock();
}).toThrow();
expect(function () {
api.setClock({});
}).toThrow();
expect(function () {
api.setClock('invalidClockKey');
}).toThrow();
});
it('[Legacy TimeAPI]: a new tick listener is registered', function () {
api.clock('mts', mockOffsets); api.clock('mts', mockOffsets);
expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function)); expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function));
}); });
it('a new tick listener is registered', function () {
api.setClock('mts', mockOffsets);
expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function));
});
it('listener of existing tick source is reregistered', function () { it('listener of existing tick source is reregistered', function () {
api.clock('mts', mockOffsets); api.clock('mts', mockOffsets);
api.clock('amts', mockOffsets); api.clock('amts', mockOffsets);
expect(mockTickSource.off).toHaveBeenCalledWith('tick', jasmine.any(Function)); expect(mockTickSource.off).toHaveBeenCalledWith('tick', jasmine.any(Function));
}); });
xit('Allows the active clock to be set and unset', function () { it('[Legacy TimeAPI]: Allows the active clock to be set and unset', function () {
expect(api.clock()).toBeUndefined(); expect(api.clock()).toBeUndefined();
api.clock('mts', mockOffsets); api.clock('mts', mockOffsets);
expect(api.clock()).toBeDefined(); expect(api.clock()).toBeDefined();
// api.stopClock(); // Unset the clock
// expect(api.clock()).toBeUndefined(); api.stopClock();
expect(api.clock()).toBeUndefined();
}); });
it('Provides a default time context', () => { it('Provides a default time context', () => {

View File

@ -20,26 +20,89 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import EventEmitter from 'EventEmitter'; import EventEmitter from 'eventemitter3';
import { FIXED_MODE_KEY, MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants.js'; import { FIXED_MODE_KEY, MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants.js';
/**
* @typedef {import('../../utils/clock/DefaultClock.js').default} Clock
*/
/**
* @typedef {import('./TimeAPI.js').TimeSystem} TimeSystem
*/
/**
* @typedef {Object} TimeConductorBounds
* @property {number} start The start time displayed by the time conductor
* in ms since epoch. Epoch determined by currently active time system
* @property {number} end The end time displayed by the time conductor in ms
* since epoch.
*/
/**
* Clock offsets are used to calculate temporal bounds when the system is
* ticking on a clock source.
*
* @typedef {Object} ClockOffsets
* @property {number} start A time span relative to the current value of the
* ticking clock, from which start bounds will be calculated. This value must
* be < 0. When a clock is active, bounds will be calculated automatically
* based on the value provided by the clock, and the defined clock offsets.
* @property {number} end A time span relative to the current value of the
* ticking clock, from which end bounds will be calculated. This value must
* be >= 0.
*/
/**
* @typedef {Object} ValidationResult
* @property {boolean} valid Result of the validation - true or false.
* @property {string} message An error message if valid is false.
*/
/**
* @typedef {'fixed' | 'realtime'} Mode The time conductor mode.
*/
/**
* @class TimeContext
* @extends EventEmitter
*/
class TimeContext extends EventEmitter { class TimeContext extends EventEmitter {
constructor() { constructor() {
super(); super();
//The Time System /**
* The time systems available to the TimeAPI.
* @type {Map<string, TimeSystem>}
*/
this.timeSystems = new Map(); this.timeSystems = new Map();
/**
* The currently applied time system.
* @type {TimeSystem | undefined}
*/
this.system = undefined; this.system = undefined;
/**
* The clocks available to the TimeAPI.
* @type {Map<string, import('../../utils/clock/DefaultClock.js').default>}
*/
this.clocks = new Map(); this.clocks = new Map();
/**
* The current bounds of the time conductor.
* @type {TimeConductorBounds}
*/
this.boundsVal = { this.boundsVal = {
start: undefined, start: undefined,
end: undefined end: undefined
}; };
/**
* The currently active clock.
* @type {Clock | undefined}
*/
this.activeClock = undefined; this.activeClock = undefined;
this.offsets = undefined; this.offsets = undefined;
this.mode = undefined; this.mode = undefined;
@ -51,11 +114,9 @@ class TimeContext extends EventEmitter {
/** /**
* Get or set the time system of the TimeAPI. * Get or set the time system of the TimeAPI.
* @param {TimeSystem | string} timeSystemOrKey * @param {TimeSystem | string} timeSystemOrKey
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds * @param {TimeConductorBounds} bounds
* @fires module:openmct.TimeAPI~timeSystem
* @returns {TimeSystem} The currently applied time system * @returns {TimeSystem} The currently applied time system
* @memberof module:openmct.TimeAPI# * @deprecated This method is deprecated. Use "getTimeSystem" and "setTimeSystem" instead.
* @method timeSystem
*/ */
timeSystem(timeSystemOrKey, bounds) { timeSystem(timeSystemOrKey, bounds) {
this.#warnMethodDeprecated('"timeSystem"', '"getTimeSystem" and "setTimeSystem"'); this.#warnMethodDeprecated('"timeSystem"', '"getTimeSystem" and "setTimeSystem"');
@ -101,11 +162,8 @@ class TimeContext extends EventEmitter {
* The time system used by the time * The time system used by the time
* conductor has changed. A change in Time System will always be * conductor has changed. A change in Time System will always be
* followed by a bounds event specifying new query bounds. * followed by a bounds event specifying new query bounds.
* * @type {TimeSystem}
* @event module:openmct.TimeAPI~timeSystem */
* @property {TimeSystem} The value of the currently applied
* Time System
* */
const system = this.#copy(this.system); const system = this.#copy(this.system);
this.emit('timeSystem', system); this.emit('timeSystem', system);
this.emit(TIME_CONTEXT_EVENTS.timeSystemChanged, system); this.emit(TIME_CONTEXT_EVENTS.timeSystemChanged, system);
@ -118,21 +176,11 @@ class TimeContext extends EventEmitter {
return this.system; return this.system;
} }
/**
* Clock offsets are used to calculate temporal bounds when the system is
* ticking on a clock source.
*
* @typedef {Object} ValidationResult
* @property {boolean} valid Result of the validation - true or false.
* @property {string} message An error message if valid is false.
*/
/** /**
* Validate the given bounds. This can be used for pre-validation of bounds, * Validate the given bounds. This can be used for pre-validation of bounds,
* for example by views validating user inputs. * for example by views validating user inputs.
* @param {TimeBounds} bounds The start and end time of the conductor. * @param {TimeConductorBounds} bounds The start and end time of the conductor.
* @returns {ValidationResult} A validation error, or true if valid * @returns {ValidationResult} A validation error, or true if valid
* @memberof module:openmct.TimeAPI#
* @method validateBounds
*/ */
validateBounds(bounds) { validateBounds(bounds) {
if ( if (
@ -162,12 +210,10 @@ class TimeContext extends EventEmitter {
* Get or set the start and end time of the time conductor. Basic validation * Get or set the start and end time of the time conductor. Basic validation
* of bounds is performed. * of bounds is performed.
* *
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds * @param {TimeConductorBounds} [newBounds] The new bounds to set. If not provided, current bounds will be returned.
* @throws {Error} Validation error * @throws {Error} Validation error
* @fires module:openmct.TimeAPI~bounds * @returns {TimeConductorBounds} The current bounds of the time conductor.
* @returns {module:openmct.TimeAPI~TimeConductorBounds} * @deprecated This method is deprecated. Use "getBounds" and "setBounds" instead.
* @memberof module:openmct.TimeAPI#
* @method bounds
*/ */
bounds(newBounds) { bounds(newBounds) {
this.#warnMethodDeprecated('"bounds"', '"getBounds" and "setBounds"'); this.#warnMethodDeprecated('"bounds"', '"getBounds" and "setBounds"');
@ -183,7 +229,6 @@ class TimeContext extends EventEmitter {
/** /**
* The start time, end time, or both have been updated. * The start time, end time, or both have been updated.
* @event bounds * @event bounds
* @memberof module:openmct.TimeAPI~
* @property {TimeConductorBounds} bounds The newly updated bounds * @property {TimeConductorBounds} bounds The newly updated bounds
* @property {boolean} [tick] `true` if the bounds update was due to * @property {boolean} [tick] `true` if the bounds update was due to
* a "tick" event (ie. was an automatic update), false otherwise. * a "tick" event (ie. was an automatic update), false otherwise.
@ -201,8 +246,6 @@ class TimeContext extends EventEmitter {
* offsets, for example by views validating user inputs. * offsets, for example by views validating user inputs.
* @param {ClockOffsets} offsets The start and end offsets from a 'now' value. * @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
* @returns {ValidationResult} A validation error, and true/false if valid or not * @returns {ValidationResult} A validation error, and true/false if valid or not
* @memberof module:openmct.TimeAPI#
* @method validateOffsets
*/ */
validateOffsets(offsets) { validateOffsets(offsets) {
if ( if (
@ -228,34 +271,13 @@ class TimeContext extends EventEmitter {
}; };
} }
/**
* @typedef {Object} TimeBounds
* @property {number} start The start time displayed by the time conductor
* in ms since epoch. Epoch determined by currently active time system
* @property {number} end The end time displayed by the time conductor in ms
* since epoch.
* @memberof module:openmct.TimeAPI~
*/
/**
* Clock offsets are used to calculate temporal bounds when the system is
* ticking on a clock source.
*
* @typedef {Object} ClockOffsets
* @property {number} start A time span relative to the current value of the
* ticking clock, from which start bounds will be calculated. This value must
* be < 0. When a clock is active, bounds will be calculated automatically
* based on the value provided by the clock, and the defined clock offsets.
* @property {number} end A time span relative to the current value of the
* ticking clock, from which end bounds will be calculated. This value must
* be >= 0.
*/
/** /**
* Get or set the currently applied clock offsets. If no parameter is provided, * Get or set the currently applied clock offsets. If no parameter is provided,
* the current value will be returned. If provided, the new value will be * the current value will be returned. If provided, the new value will be
* used as the new clock offsets. * used as the new clock offsets.
* @param {ClockOffsets} offsets * @param {ClockOffsets} [offsets] The new clock offsets to set. If not provided, current offsets will be returned.
* @returns {ClockOffsets} * @returns {ClockOffsets} The current clock offsets.
* @deprecated This method is deprecated. Use "getClockOffsets" and "setClockOffsets" instead.
*/ */
clockOffsets(offsets) { clockOffsets(offsets) {
this.#warnMethodDeprecated('"clockOffsets"', '"getClockOffsets" and "setClockOffsets"'); this.#warnMethodDeprecated('"clockOffsets"', '"getClockOffsets" and "setClockOffsets"');
@ -293,6 +315,7 @@ class TimeContext extends EventEmitter {
* Stop following the currently active clock. This will * Stop following the currently active clock. This will
* revert all views to showing a static time frame defined by the current * revert all views to showing a static time frame defined by the current
* bounds. * bounds.
* @deprecated This method is deprecated.
*/ */
stopClock() { stopClock() {
this.#warnMethodDeprecated('"stopClock"'); this.#warnMethodDeprecated('"stopClock"');
@ -304,12 +327,14 @@ class TimeContext extends EventEmitter {
* Set the active clock. Tick source will be immediately subscribed to * Set the active clock. Tick source will be immediately subscribed to
* and ticking will begin. Offsets from 'now' must also be provided. * and ticking will begin. Offsets from 'now' must also be provided.
* *
* @param {Clock || string} keyOrClock The clock to activate, or its key * @param {string|Clock} keyOrClock The clock to activate, or its key
* @param {ClockOffsets} offsets on each tick these will be used to calculate * @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 * the start and end bounds. This maintains a sliding time window of a fixed
* width that automatically updates. * width that automatically updates.
* @fires module:openmct.TimeAPI~clock * (Legacy) Emits a "clock" event with the new clock.
* @return {Clock} the currently active clock; * Emits a "clockChanged" event with the new clock.
* @return {Clock|undefined} the currently active clock; undefined if in fixed mode
* @deprecated This method is deprecated. Use "getClock" and "setClock" instead.
*/ */
clock(keyOrClock, offsets) { clock(keyOrClock, offsets) {
this.#warnMethodDeprecated('"clock"', '"getClock" and "setClock"'); this.#warnMethodDeprecated('"clock"', '"getClock" and "setClock"');
@ -339,7 +364,6 @@ class TimeContext extends EventEmitter {
/** /**
* The active clock has changed. * The active clock has changed.
* @event clock * @event clock
* @memberof module:openmct.TimeAPI~
* @property {Clock} clock The newly activated clock, or undefined * @property {Clock} clock The newly activated clock, or undefined
* if the system is no longer following a clock source * if the system is no longer following a clock source
*/ */
@ -361,7 +385,7 @@ class TimeContext extends EventEmitter {
} }
/** /**
* Update bounds based on provided time and current offsets * Update bounds based on provided time and current offsets.
* @param {number} timestamp A time from which bounds will be calculated * @param {number} timestamp A time from which bounds will be calculated
* using current offsets. * using current offsets.
*/ */
@ -385,8 +409,6 @@ class TimeContext extends EventEmitter {
/** /**
* Get the timestamp of the current clock * Get the timestamp of the current clock
* @returns {number} current timestamp of current clock regardless of mode * @returns {number} current timestamp of current clock regardless of mode
* @memberof module:openmct.TimeAPI#
* @method now
*/ */
now() { now() {
@ -396,8 +418,6 @@ class TimeContext extends EventEmitter {
/** /**
* Get the time system of the TimeAPI. * Get the time system of the TimeAPI.
* @returns {TimeSystem} The currently applied time system * @returns {TimeSystem} The currently applied time system
* @memberof module:openmct.TimeAPI#
* @method getTimeSystem
*/ */
getTimeSystem() { getTimeSystem() {
return this.system; return this.system;
@ -405,12 +425,9 @@ class TimeContext extends EventEmitter {
/** /**
* Set the time system of the TimeAPI. * Set the time system of the TimeAPI.
* @param {TimeSystem | string} timeSystemOrKey * Emits a "timeSystem" event with the new time system.
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds * @param {TimeSystem | string} timeSystemOrKey The time system to set, or its key
* @fires module:openmct.TimeAPI~timeSystem * @param {TimeConductorBounds} [bounds] Optional bounds to set
* @returns {TimeSystem} The currently applied time system
* @memberof module:openmct.TimeAPI#
* @method setTimeSystem
*/ */
setTimeSystem(timeSystemOrKey, bounds) { setTimeSystem(timeSystemOrKey, bounds) {
if (timeSystemOrKey === undefined) { if (timeSystemOrKey === undefined) {
@ -441,7 +458,6 @@ class TimeContext extends EventEmitter {
* conductor has changed. A change in Time System will always be * conductor has changed. A change in Time System will always be
* followed by a bounds event specifying new query bounds. * followed by a bounds event specifying new query bounds.
* *
* @event module:openmct.TimeAPI~timeSystem
* @property {TimeSystem} The value of the currently applied * @property {TimeSystem} The value of the currently applied
* Time System * Time System
* */ * */
@ -456,9 +472,7 @@ class TimeContext extends EventEmitter {
/** /**
* Get the start and end time of the time conductor. Basic validation * Get the start and end time of the time conductor. Basic validation
* of bounds is performed. * of bounds is performed.
* @returns {module:openmct.TimeAPI~TimeConductorBounds} * @returns {TimeConductorBounds} The current bounds of the time conductor.
* @memberof module:openmct.TimeAPI#
* @method bounds
*/ */
getBounds() { getBounds() {
//Return a copy to prevent direct mutation of time conductor bounds. //Return a copy to prevent direct mutation of time conductor bounds.
@ -469,12 +483,8 @@ class TimeContext extends EventEmitter {
* Set the start and end time of the time conductor. Basic validation * Set the start and end time of the time conductor. Basic validation
* of bounds is performed. * of bounds is performed.
* *
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds * @param {TimeConductorBounds} newBounds The new bounds to set.
* @throws {Error} Validation error * @throws {Error} Validation error if bounds are invalid
* @fires module:openmct.TimeAPI~bounds
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
* @memberof module:openmct.TimeAPI#
* @method bounds
*/ */
setBounds(newBounds) { setBounds(newBounds) {
const validationResult = this.validateBounds(newBounds); const validationResult = this.validateBounds(newBounds);
@ -487,7 +497,6 @@ class TimeContext extends EventEmitter {
/** /**
* The start time, end time, or both have been updated. * The start time, end time, or both have been updated.
* @event bounds * @event bounds
* @memberof module:openmct.TimeAPI~
* @property {TimeConductorBounds} bounds The newly updated bounds * @property {TimeConductorBounds} bounds The newly updated bounds
* @property {boolean} [tick] `true` if the bounds update was due to * @property {boolean} [tick] `true` if the bounds update was due to
* a "tick" event (i.e. was an automatic update), false otherwise. * a "tick" event (i.e. was an automatic update), false otherwise.
@ -498,7 +507,7 @@ class TimeContext extends EventEmitter {
/** /**
* Get the active clock. * Get the active clock.
* @return {Clock} the currently active clock; * @return {Clock|undefined} the currently active clock; undefined if in fixed mode.
*/ */
getClock() { getClock() {
return this.activeClock; return this.activeClock;
@ -509,9 +518,7 @@ class TimeContext extends EventEmitter {
* and the currently ticking will begin. * and the currently ticking will begin.
* Offsets from 'now', if provided, will be used to set realtime mode offsets * Offsets from 'now', if provided, will be used to set realtime mode offsets
* *
* @param {Clock || string} keyOrClock The clock to activate, or its key * @param {string|Clock} keyOrClock The clock to activate, or its key
* @fires module:openmct.TimeAPI~clock
* @return {Clock} the currently active clock;
*/ */
setClock(keyOrClock) { setClock(keyOrClock) {
let clock; let clock;
@ -540,7 +547,7 @@ class TimeContext extends EventEmitter {
* The active clock has changed. * The active clock has changed.
* @event clock * @event clock
* @memberof module:openmct.TimeAPI~ * @memberof module:openmct.TimeAPI~
* @property {Clock} clock The newly activated clock, or undefined * @property {TimeContext} clock The newly activated clock, or undefined
* if the system is no longer following a clock source * if the system is no longer following a clock source
*/ */
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock); this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
@ -549,7 +556,7 @@ class TimeContext extends EventEmitter {
/** /**
* Get the current mode. * Get the current mode.
* @return {Mode} the current mode; * @return {Mode} the current mode
*/ */
getMode() { getMode() {
return this.mode; return this.mode;
@ -559,9 +566,9 @@ class TimeContext extends EventEmitter {
* Set the mode to either fixed or realtime. * Set the mode to either fixed or realtime.
* *
* @param {Mode} mode The mode to activate * @param {Mode} mode The mode to activate
* @param {TimeBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width * @param {TimeConductorBounds|ClockOffsets} offsetsOrBounds A time window of a fixed width
* @fires module:openmct.TimeAPI~clock * @fires module:openmct.TimeAPI~clock
* @return {Mode} the currently active mode; * @return {Mode | undefined} the currently active mode
*/ */
setMode(mode, offsetsOrBounds) { setMode(mode, offsetsOrBounds) {
if (!mode) { if (!mode) {
@ -577,7 +584,6 @@ class TimeContext extends EventEmitter {
/** /**
* The active mode has changed. * The active mode has changed.
* @event modeChanged * @event modeChanged
* @memberof module:openmct.TimeAPI~
* @property {Mode} mode The newly activated mode * @property {Mode} mode The newly activated mode
*/ */
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode)); this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode));
@ -610,18 +616,15 @@ class TimeContext extends EventEmitter {
/** /**
* Get the currently applied clock offsets. * Get the currently applied clock offsets.
* @returns {ClockOffsets} * @returns {ClockOffsets} The current clock offsets.
*/ */
getClockOffsets() { getClockOffsets() {
return this.offsets; return this.offsets;
} }
/** /**
* Set the currently applied clock offsets. If no parameter is provided, * Set the currently applied clock offsets.
* the current value will be returned. If provided, the new value will be * @param {ClockOffsets} offsets The new clock offsets to set.
* used as the new clock offsets.
* @param {ClockOffsets} offsets
* @returns {ClockOffsets}
*/ */
setClockOffsets(offsets) { setClockOffsets(offsets) {
const validationResult = this.validateOffsets(offsets); const validationResult = this.validateOffsets(offsets);
@ -642,13 +645,20 @@ class TimeContext extends EventEmitter {
/** /**
* Event that is triggered when clock offsets change. * Event that is triggered when clock offsets change.
* @event clockOffsets * @event clockOffsets
* @memberof module:openmct.TimeAPI~
* @property {ClockOffsets} clockOffsets The newly activated clock * @property {ClockOffsets} clockOffsets The newly activated clock
* offsets. * offsets.
*/ */
this.emit(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.#copy(offsets)); this.emit(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.#copy(offsets));
} }
/**
* Prints a warning to the console when a deprecated method is used. Limits
* the number of times a warning is printed per unique method and newMethod
* combination.
* @param {string} method the deprecated method
* @param {string} [newMethod] the new method to use instead
* @returns
*/
#warnMethodDeprecated(method, newMethod) { #warnMethodDeprecated(method, newMethod) {
const MAX_CALLS = 1; // Only warn once per unique method and newMethod combination const MAX_CALLS = 1; // Only warn once per unique method and newMethod combination
@ -673,6 +683,11 @@ class TimeContext extends EventEmitter {
console.warn(message); console.warn(message);
} }
/**
* Deep copy an object.
* @param {object} object The object to copy
* @returns {object} The copied object
*/
#copy(object) { #copy(object) {
return JSON.parse(JSON.stringify(object)); return JSON.parse(JSON.stringify(object));
} }

View File

@ -212,7 +212,7 @@ export default {
this.openmct.time.on('timeSystem', this.updateTimeSystem); this.openmct.time.on('timeSystem', this.updateTimeSystem);
this.timestampKey = this.openmct.time.timeSystem().key; this.timestampKey = this.openmct.time.getTimeSystem().key;
this.valueMetadata = undefined; this.valueMetadata = undefined;

View File

@ -61,7 +61,7 @@ export default class URLTimeSettingsSynchronizer {
TIME_EVENTS.forEach((event) => { TIME_EVENTS.forEach((event) => {
this.openmct.time.on(event, this.setUrlFromTimeApi); this.openmct.time.on(event, this.setUrlFromTimeApi);
}); });
this.openmct.time.on('bounds', this.updateBounds); this.openmct.time.on('boundsChanged', this.updateBounds);
} }
destroy() { destroy() {
@ -73,7 +73,7 @@ export default class URLTimeSettingsSynchronizer {
TIME_EVENTS.forEach((event) => { TIME_EVENTS.forEach((event) => {
this.openmct.time.off(event, this.setUrlFromTimeApi); this.openmct.time.off(event, this.setUrlFromTimeApi);
}); });
this.openmct.time.off('bounds', this.updateBounds); this.openmct.time.off('boundsChanged', this.updateBounds);
} }
updateTimeSettings() { updateTimeSettings() {

View File

@ -115,11 +115,11 @@ export default {
this.followTimeContext(); this.followTimeContext();
}, },
followTimeContext() { followTimeContext() {
this.timeContext.on('bounds', this.refreshData); this.timeContext.on('boundsChanged', this.refreshData);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('bounds', this.refreshData); this.timeContext.off('boundsChanged', this.refreshData);
} }
}, },
addToComposition(telemetryObject) { addToComposition(telemetryObject) {
@ -253,7 +253,7 @@ export default {
}; };
}, },
getOptions() { getOptions() {
const { start, end } = this.timeContext.bounds(); const { start, end } = this.timeContext.getBounds();
return { return {
end, end,
@ -372,13 +372,13 @@ export default {
this.setTrace(key, telemetryObject.name, axisMetadata, xValues, yValues); this.setTrace(key, telemetryObject.name, axisMetadata, xValues, yValues);
}, },
isDataInTimeRange(datum, key, telemetryObject) { isDataInTimeRange(datum, key, telemetryObject) {
const timeSystemKey = this.timeContext.timeSystem().key; const timeSystemKey = this.timeContext.getTimeSystem().key;
const metadata = this.openmct.telemetry.getMetadata(telemetryObject); const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
let metadataValue = metadata.value(timeSystemKey) || { key: timeSystemKey }; let metadataValue = metadata.value(timeSystemKey) || { key: timeSystemKey };
let currentTimestamp = this.parse(key, metadataValue.key, datum); let currentTimestamp = this.parse(key, metadataValue.key, datum);
return currentTimestamp && this.timeContext.bounds().end >= currentTimestamp; return currentTimestamp && this.timeContext.getBounds().end >= currentTimestamp;
}, },
format(telemetryObjectKey, metadataKey, data) { format(telemetryObjectKey, metadataKey, data) {
const formats = this.telemetryObjectFormats[telemetryObjectKey]; const formats = this.telemetryObjectFormats[telemetryObjectKey];

View File

@ -105,11 +105,11 @@ export default {
this.followTimeContext(); this.followTimeContext();
}, },
followTimeContext() { followTimeContext() {
this.timeContext.on('bounds', this.reloadTelemetryOnBoundsChange); this.timeContext.on('boundsChanged', this.reloadTelemetryOnBoundsChange);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('bounds', this.reloadTelemetryOnBoundsChange); this.timeContext.off('boundsChanged', this.reloadTelemetryOnBoundsChange);
} }
}, },
addToComposition(telemetryObject) { addToComposition(telemetryObject) {
@ -306,7 +306,7 @@ export default {
this.trace = [trace]; this.trace = [trace];
}, },
getTimestampForDatum(datum, key, telemetryObject) { getTimestampForDatum(datum, key, telemetryObject) {
const timeSystemKey = this.timeContext.timeSystem().key; const timeSystemKey = this.timeContext.getTimeSystem().key;
const metadata = this.openmct.telemetry.getMetadata(telemetryObject); const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
let metadataValue = metadata.value(timeSystemKey) || { format: timeSystemKey }; let metadataValue = metadata.value(timeSystemKey) || { format: timeSystemKey };
@ -327,7 +327,7 @@ export default {
return formats[metadataKey].parse(datum); return formats[metadataKey].parse(datum);
}, },
getOptions() { getOptions() {
const { start, end } = this.timeContext.bounds(); const { start, end } = this.timeContext.getBounds();
return { return {
end, end,

View File

@ -245,7 +245,7 @@ export default class Condition extends EventEmitter {
latestTimestamp, latestTimestamp,
updatedCriterion.data, updatedCriterion.data,
this.timeSystems, this.timeSystems,
this.openmct.time.timeSystem() this.openmct.time.getTimeSystem()
); );
this.conditionManager.updateCurrentCondition(latestTimestamp); this.conditionManager.updateCurrentCondition(latestTimestamp);
} }
@ -309,7 +309,7 @@ export default class Condition extends EventEmitter {
latestTimestamp, latestTimestamp,
data, data,
this.timeSystems, this.timeSystems,
this.openmct.time.timeSystem() this.openmct.time.getTimeSystem()
); );
}); });

View File

@ -113,7 +113,7 @@ export default class ConditionManager extends EventEmitter {
{}, {},
{}, {},
this.timeSystems, this.timeSystems,
this.openmct.time.timeSystem() this.openmct.time.getTimeSystem()
); );
this.updateConditionResults({ id: id }); this.updateConditionResults({ id: id });
this.updateCurrentCondition(latestTimestamp); this.updateCurrentCondition(latestTimestamp);
@ -383,7 +383,7 @@ export default class ConditionManager extends EventEmitter {
latestTimestamp, latestTimestamp,
data, data,
this.timeSystems, this.timeSystems,
this.openmct.time.timeSystem() this.openmct.time.getTimeSystem()
); );
}); });

View File

@ -41,7 +41,7 @@ export default class StyleRuleManager extends EventEmitter {
}); });
this.initialize(styleConfigurationWithNoSelection); this.initialize(styleConfigurationWithNoSelection);
if (styleConfiguration.conditionSetIdentifier) { if (styleConfiguration.conditionSetIdentifier) {
this.openmct.time.on('bounds', this.refreshData); this.openmct.time.on('boundsChanged', this.refreshData);
this.subscribeToConditionSet(); this.subscribeToConditionSet();
} else { } else {
this.applyStaticStyle(); this.applyStaticStyle();
@ -216,7 +216,7 @@ export default class StyleRuleManager extends EventEmitter {
} }
if (!skipEventListeners) { if (!skipEventListeners) {
this.openmct.time.off('bounds', this.refreshData); this.openmct.time.off('boundsChanged', this.refreshData);
this.openmct.editor.off('isEditing', this.toggleSubscription); this.openmct.editor.off('isEditing', this.toggleSubscription);
} }

View File

@ -227,7 +227,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
return Promise.all(telemetryRequests).then((telemetryRequestsResults) => { return Promise.all(telemetryRequests).then((telemetryRequestsResults) => {
let latestTimestamp; let latestTimestamp;
const timeSystems = this.openmct.time.getAllTimeSystems(); const timeSystems = this.openmct.time.getAllTimeSystems();
const timeSystem = this.openmct.time.timeSystem(); const timeSystem = this.openmct.time.getTimeSystem();
telemetryRequestsResults.forEach((results, index) => { telemetryRequestsResults.forEach((results, index) => {
const latestDatum = const latestDatum =

View File

@ -280,7 +280,7 @@ export default {
await this.$nextTick(); await this.$nextTick();
}, },
formattedValueForCopy() { formattedValueForCopy() {
const timeFormatterKey = this.openmct.time.timeSystem().key; const timeFormatterKey = this.openmct.time.getTimeSystem().key;
const timeFormatter = this.formats[timeFormatterKey]; const timeFormatter = this.formats[timeFormatterKey];
const unit = this.unit ? ` ${this.unit}` : ''; const unit = this.unit ? ` ${this.unit}` : '';

View File

@ -164,7 +164,7 @@ describe('Gauge plugin', () => {
}); });
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() }); spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({ spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000, start: 1000,
end: 5000 end: 5000
}); });
@ -306,7 +306,7 @@ describe('Gauge plugin', () => {
}); });
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() }); spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({ spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000, start: 1000,
end: 5000 end: 5000
}); });
@ -448,7 +448,7 @@ describe('Gauge plugin', () => {
}); });
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() }); spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({ spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000, start: 1000,
end: 5000 end: 5000
}); });
@ -763,7 +763,7 @@ describe('Gauge plugin', () => {
}) })
}); });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({ spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000, start: 1000,
end: 5000 end: 5000
}); });

View File

@ -363,7 +363,7 @@ export default {
rangeLow: gaugeController.min, rangeLow: gaugeController.min,
gaugeType: gaugeController.gaugeType, gaugeType: gaugeController.gaugeType,
showUnits: gaugeController.showUnits, showUnits: gaugeController.showUnits,
activeTimeSystem: this.openmct.time.timeSystem(), activeTimeSystem: this.openmct.time.getTimeSystem(),
units: '' units: ''
}; };
}, },
@ -545,7 +545,7 @@ export default {
this.composition.load(); this.composition.load();
this.openmct.time.on('bounds', this.refreshData); this.openmct.time.on('boundsChanged', this.refreshData);
this.openmct.time.on('timeSystem', this.setTimeSystem); this.openmct.time.on('timeSystem', this.setTimeSystem);
this.setupClockChangedEvent((domainObject) => { this.setupClockChangedEvent((domainObject) => {
@ -561,7 +561,7 @@ export default {
this.unsubscribe(); this.unsubscribe();
} }
this.openmct.time.off('bounds', this.refreshData); this.openmct.time.off('boundsChanged', this.refreshData);
this.openmct.time.off('timeSystem', this.setTimeSystem); this.openmct.time.off('timeSystem', this.setTimeSystem);
}, },
methods: { methods: {
@ -726,7 +726,7 @@ export default {
return; return;
} }
const { start, end } = this.openmct.time.bounds(); const { start, end } = this.openmct.time.getBounds();
const parsedValue = this.timeFormatter.parse(this.datum); const parsedValue = this.timeFormatter.parse(this.datum);
const beforeStartOfBounds = parsedValue < start; const beforeStartOfBounds = parsedValue < start;

View File

@ -49,7 +49,7 @@ export default {
mixins: [imageryData], mixins: [imageryData],
inject: ['openmct', 'domainObject', 'objectPath'], inject: ['openmct', 'domainObject', 'objectPath'],
data() { data() {
let timeSystem = this.openmct.time.timeSystem(); let timeSystem = this.openmct.time.getTimeSystem();
this.metadata = {}; this.metadata = {};
this.requestCount = 0; this.requestCount = 0;
@ -109,12 +109,12 @@ export default {
this.stopFollowingTimeContext(); this.stopFollowingTimeContext();
this.timeContext = this.openmct.time.getContextForView(this.objectPath); this.timeContext = this.openmct.time.getContextForView(this.objectPath);
this.timeContext.on('timeSystem', this.setScaleAndPlotImagery); this.timeContext.on('timeSystem', this.setScaleAndPlotImagery);
this.timeContext.on('bounds', this.updateViewBounds); this.timeContext.on('boundsChanged', this.updateViewBounds);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('timeSystem', this.setScaleAndPlotImagery); this.timeContext.off('timeSystem', this.setScaleAndPlotImagery);
this.timeContext.off('bounds', this.updateViewBounds); this.timeContext.off('boundsChanged', this.updateViewBounds);
} }
}, },
expand(imageTimestamp) { expand(imageTimestamp) {
@ -148,10 +148,10 @@ export default {
return clientWidth; return clientWidth;
}, },
updateViewBounds(bounds, isTick) { updateViewBounds(bounds, isTick) {
this.viewBounds = this.timeContext.bounds(); this.viewBounds = this.timeContext.getBounds();
if (this.timeSystem === undefined) { if (this.timeSystem === undefined) {
this.timeSystem = this.timeContext.timeSystem(); this.timeSystem = this.timeContext.getTimeSystem();
} }
this.setScaleAndPlotImagery(this.timeSystem, !isTick); this.setScaleAndPlotImagery(this.timeSystem, !isTick);
@ -216,7 +216,7 @@ export default {
} }
if (timeSystem === undefined) { if (timeSystem === undefined) {
timeSystem = this.timeContext.timeSystem(); timeSystem = this.timeContext.getTimeSystem();
} }
if (timeSystem.isUTCBased) { if (timeSystem.isUTCBased) {

View File

@ -44,7 +44,7 @@ export default class RelatedTelemetry {
this.keys = telemetryKeys; this.keys = telemetryKeys;
this._timeFormatter = undefined; this._timeFormatter = undefined;
this._timeSystemChange(this.timeContext.timeSystem()); this._timeSystemChange(this.timeContext.getTimeSystem());
// grab related telemetry metadata // grab related telemetry metadata
for (let key of this.keys) { for (let key of this.keys) {
@ -110,10 +110,10 @@ export default class RelatedTelemetry {
// and set bounds. // and set bounds.
ephemeralContext.resetContext(); ephemeralContext.resetContext();
const newBounds = { const newBounds = {
start: this.timeContext.bounds().start, start: this.timeContext.getBounds().start,
end: this._parseTime(datum) end: this._parseTime(datum)
}; };
ephemeralContext.bounds(newBounds); ephemeralContext.setBounds(newBounds);
const options = { const options = {
start: newBounds.start, start: newBounds.start,

View File

@ -171,7 +171,7 @@ export default {
this.bounds = bounds; // setting bounds for ImageryView watcher this.bounds = bounds; // setting bounds for ImageryView watcher
}, },
timeSystemChanged() { timeSystemChanged() {
this.timeSystem = this.timeContext.timeSystem(); this.timeSystem = this.timeContext.getTimeSystem();
this.timeKey = this.timeSystem.key; this.timeKey = this.timeSystem.key;
this.timeFormatter = this.getFormatter(this.timeKey); this.timeFormatter = this.getFormatter(this.timeKey);
this.durationFormatter = this.getFormatter( this.durationFormatter = this.getFormatter(

View File

@ -144,24 +144,132 @@ export default class ImportAsJSONAction {
return Array.from(new Set([...objectIdentifiers, ...itemObjectReferences])); return Array.from(new Set([...objectIdentifiers, ...itemObjectReferences]));
} }
/**
* @private
* @param {Object} tree
* @param {string} namespace
* @returns {Object}
*/
_generateNewIdentifiers(tree, newNamespace) {
// For each domain object in the file, generate new ID, replace in tree
Object.keys(tree.openmct).forEach((domainObjectId) => {
const oldId = parseKeyString(domainObjectId);
/**
* Generates a map of old IDs to new IDs for efficient lookup during tree walking.
* This function considers cases where original namespaces are blank and updates those IDs as well.
*
* @param {Object} tree - The object tree containing the old IDs.
* @param {string} newNamespace - The namespace for the new IDs.
* @returns {Object} A map of old IDs to new IDs.
*/
_generateIdMap(tree, newNamespace) {
const idMap = {};
const keys = Object.keys(tree.openmct);
for (const oldIdKey of keys) {
const oldId = parseKeyString(oldIdKey);
const newId = { const newId = {
namespace: newNamespace, namespace: newNamespace,
key: uuid() key: uuid()
}; };
tree = this._rewriteId(oldId, newId, tree); const newIdKeyString = this.openmct.objects.makeKeyString(newId);
}, this);
// Update the map with the old and new ID key strings.
idMap[oldIdKey] = newIdKeyString;
// If the old namespace is blank, also map the non-namespaced ID.
if (!oldId.namespace) {
const nonNamespacedOldIdKey = oldId.key;
idMap[nonNamespacedOldIdKey] = newIdKeyString;
}
}
return idMap;
}
/**
* Walks through the object tree and updates IDs according to the provided ID map.
* @param {Object} obj - The current object being visited in the tree.
* @param {Object} idMap - A map of old IDs to new IDs for rewriting.
* @param {Object} importDialog - Optional progress dialog for import.
* @returns {Promise<Object>} The object with updated IDs.
*/
async _walkAndRewriteIds(obj, idMap, importDialog) {
// How many rewrites to do before yielding to the event loop
const UI_UPDATE_INTERVAL = 300;
// The percentage of the progress dialog to allocate to rewriting IDs
const PERCENT_OF_DIALOG = 80;
if (obj === null || obj === undefined) {
return obj;
}
if (typeof obj === 'string') {
const possibleId = idMap[obj];
if (possibleId) {
return possibleId;
} else {
return obj;
}
}
if (Object.hasOwn(obj, 'key') && Object.hasOwn(obj, 'namespace')) {
const oldId = this.openmct.objects.makeKeyString(obj);
const possibleId = idMap[oldId];
if (possibleId) {
const newIdParts = possibleId.split(':');
if (newIdParts.length >= 2) {
// new ID is namespaced, so update both the namespace and key
obj.namespace = newIdParts[0];
obj.key = newIdParts[1];
} else {
// old ID was not namespaced, so update the key only
obj.namespace = '';
obj.key = newIdParts[0];
}
}
return obj;
}
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
obj[i] = await this._walkAndRewriteIds(obj[i], idMap); // Process each item in the array
}
return obj;
}
if (typeof obj === 'object') {
const newObj = {};
const keys = Object.keys(obj);
let processedCount = 0;
for (const key of keys) {
const value = obj[key];
const possibleId = idMap[key];
const newKey = possibleId || key;
newObj[newKey] = await this._walkAndRewriteIds(value, idMap);
// Optionally update the importDialog here, after each property has been processed
if (importDialog) {
processedCount++;
if (processedCount % UI_UPDATE_INTERVAL === 0) {
// yield to the event loop to allow the UI to update
await new Promise((resolve) => setTimeout(resolve, 0));
const percentPersisted = Math.ceil(PERCENT_OF_DIALOG * (processedCount / keys.length));
const message = `Rewriting ${processedCount} of ${keys.length} imported objects.`;
importDialog.updateProgress(percentPersisted, message);
}
}
}
return newObj;
}
// Return the input as-is for types that are not objects, strings, or arrays
return obj;
}
/**
* @private
* @param {Object} tree
* @returns {Promise<Object>}
*/
async _generateNewIdentifiers(tree, newNamespace, importDialog) {
const idMap = this._generateIdMap(tree, newNamespace);
tree.rootId = idMap[tree.rootId];
tree.openmct = await this._walkAndRewriteIds(tree.openmct, idMap, importDialog);
return tree; return tree;
} }
/** /**
@ -170,9 +278,16 @@ export default class ImportAsJSONAction {
* @param {Object} objTree * @param {Object} objTree
*/ */
async _importObjectTree(domainObject, objTree) { async _importObjectTree(domainObject, objTree) {
// make rewriting objects IDs 80% of the progress bar
const importDialog = this.openmct.overlays.progressDialog({
progressPerc: 0,
message: `Importing ${Object.keys(objTree.openmct).length} objects`,
iconClass: 'info',
title: 'Importing'
});
const objectsToCreate = []; const objectsToCreate = [];
const namespace = domainObject.identifier.namespace; const namespace = domainObject.identifier.namespace;
const tree = this._generateNewIdentifiers(objTree, namespace); const tree = await this._generateNewIdentifiers(objTree, namespace, importDialog);
const rootId = tree.rootId; const rootId = tree.rootId;
const rootObj = tree.openmct[rootId]; const rootObj = tree.openmct[rootId];
@ -182,11 +297,24 @@ export default class ImportAsJSONAction {
this._deepInstantiate(rootObj, tree.openmct, [], objectsToCreate); this._deepInstantiate(rootObj, tree.openmct, [], objectsToCreate);
try { try {
await Promise.all(objectsToCreate.map(this._instantiate, this)); let persistedObjects = 0;
// make saving objects objects 20% of the progress bar
await Promise.all(
objectsToCreate.map(async (objectToCreate) => {
persistedObjects++;
const percentPersisted =
Math.ceil(20 * (persistedObjects / objectsToCreate.length)) + 80;
const message = `Saving ${persistedObjects} of ${objectsToCreate.length} imported objects.`;
importDialog.updateProgress(percentPersisted, message);
await this._instantiate(objectToCreate);
})
);
} catch (error) { } catch (error) {
this.openmct.notifications.error('Error saving objects'); this.openmct.notifications.error('Error saving objects');
throw error; throw error;
} finally {
importDialog.dismiss();
} }
const compositionCollection = this.openmct.composition.get(domainObject); const compositionCollection = this.openmct.composition.get(domainObject);
@ -194,7 +322,8 @@ export default class ImportAsJSONAction {
this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString); this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString);
compositionCollection.add(rootObj); compositionCollection.add(rootObj);
} else { } else {
const dialog = this.openmct.overlays.dialog({ importDialog.dismiss();
const cannotImportDialog = this.openmct.overlays.dialog({
iconClass: 'alert', iconClass: 'alert',
message: "We're sorry, but you cannot import that object type into this object.", message: "We're sorry, but you cannot import that object type into this object.",
buttons: [ buttons: [
@ -202,7 +331,7 @@ export default class ImportAsJSONAction {
label: 'Ok', label: 'Ok',
emphasis: true, emphasis: true,
callback: function () { callback: function () {
dialog.dismiss(); cannotImportDialog.dismiss();
} }
} }
] ]
@ -217,43 +346,7 @@ export default class ImportAsJSONAction {
_instantiate(model) { _instantiate(model) {
return this.openmct.objects.save(model); return this.openmct.objects.save(model);
} }
/**
* @private
* @param {Object} oldId
* @param {Object} newId
* @param {Object} tree
* @returns {Object}
*/
_rewriteId(oldId, newId, tree) {
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
const newTreeString = JSON.stringify(tree).replace(
new RegExp(oldIdKeyString, 'g'),
newIdKeyString
);
const newTree = JSON.parse(newTreeString, (key, value) => {
if (
value !== undefined &&
value !== null &&
Object.prototype.hasOwnProperty.call(value, 'key') &&
Object.prototype.hasOwnProperty.call(value, 'namespace')
) {
// first check if key is messed up from regex and contains a colon
// if it does, repair it
if (value.key.includes(':')) {
const splitKey = value.key.split(':');
value.key = splitKey[1];
value.namespace = splitKey[0];
}
// now check if we need to replace the id
if (value.key === oldId.key && value.namespace === oldId.namespace) {
return newId;
}
}
return value;
});
return newTree;
}
/** /**
* @private * @private
* @param {Object} domainObject * @param {Object} domainObject

View File

@ -111,7 +111,6 @@ describe('The import JSON action', function () {
}); });
it('protects against prototype pollution', (done) => { it('protects against prototype pollution', (done) => {
spyOn(console, 'warn');
spyOn(openmct.forms, 'showForm').and.callFake(returnResponseWithPrototypePollution); spyOn(openmct.forms, 'showForm').and.callFake(returnResponseWithPrototypePollution);
unObserve = openmct.objects.observe(folderObject, '*', callback); unObserve = openmct.objects.observe(folderObject, '*', callback);
@ -123,8 +122,6 @@ describe('The import JSON action', function () {
Object.prototype.hasOwnProperty.call(newObject, '__proto__') || Object.prototype.hasOwnProperty.call(newObject, '__proto__') ||
Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(newObject), 'toString'); Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(newObject), 'toString');
// warning from openmct.objects.get
expect(console.warn).not.toHaveBeenCalled();
expect(hasPollutedProto).toBeFalse(); expect(hasPollutedProto).toBeFalse();
done(); done();
@ -192,6 +189,12 @@ describe('The import JSON action', function () {
type: 'folder' type: 'folder'
}; };
spyOn(openmct.objects, 'save').and.callFake((model) => Promise.resolve(model)); spyOn(openmct.objects, 'save').and.callFake((model) => Promise.resolve(model));
spyOn(openmct.overlays, 'progressDialog').and.callFake(() => {
return {
updateProgress: () => {},
dismiss: () => {}
};
});
try { try {
await importFromJSONAction.onSave(targetDomainObject, { await importFromJSONAction.onSave(targetDomainObject, {
selectFile: { body: JSON.stringify(incomingObject) } selectFile: { body: JSON.stringify(incomingObject) }

View File

@ -680,7 +680,7 @@ export default {
} else if (domainObjectData) { } else if (domainObjectData) {
// plain domain object // plain domain object
const objectPath = JSON.parse(domainObjectData); const objectPath = JSON.parse(domainObjectData);
const bounds = this.openmct.time.bounds(); const bounds = this.openmct.time.getBounds();
const snapshotMeta = { const snapshotMeta = {
bounds, bounds,
link: null, link: null,

View File

@ -275,10 +275,10 @@ export default {
} }
const hash = this.embed.historicLink; const hash = this.embed.historicLink;
const bounds = this.openmct.time.bounds(); const bounds = this.openmct.time.getBounds();
const isTimeBoundChanged = const isTimeBoundChanged =
this.embed.bounds.start !== bounds.start || this.embed.bounds.end !== bounds.end; this.embed.bounds.start !== bounds.start || this.embed.bounds.end !== bounds.end;
const isFixedTimespanMode = !this.openmct.time.clock(); const isFixedTimespanMode = this.openmct.time.isFixed();
let message = ''; let message = '';
if (isTimeBoundChanged) { if (isTimeBoundChanged) {

View File

@ -31,7 +31,7 @@
@drop.capture="cancelEditMode" @drop.capture="cancelEditMode"
@drop.prevent="dropOnEntry" @drop.prevent="dropOnEntry"
@click="selectAndEmitEntry($event, entry)" @click="selectAndEmitEntry($event, entry)"
@paste="addImageFromPaste" @paste="handlePaste"
> >
<div class="c-ne__time-and-content"> <div class="c-ne__time-and-content">
<div class="c-ne__time-and-creator-and-delete"> <div class="c-ne__time-and-creator-and-delete">
@ -368,8 +368,30 @@ export default {
} }
}, },
methods: { methods: {
handlePaste(event) {
const clipboardItems = Array.from(
(event.clipboardData || event.originalEvent.clipboardData).items
);
const hasClipboardText = clipboardItems.some(
(clipboardItem) => clipboardItem.kind === 'string'
);
const clipboardImages = clipboardItems.filter(
(clipboardItem) => clipboardItem.kind === 'file' && clipboardItem.type.includes('image')
);
const hasClipboardImages = clipboardImages?.length > 0;
if (hasClipboardImages) {
if (hasClipboardText) {
console.warn('Image and text kinds found in paste. Only processing images.');
}
this.addImageFromPaste(clipboardImages, event);
} else if (hasClipboardText) {
this.addTextFromPaste(event);
}
},
async addNewEmbed(objectPath) { async addNewEmbed(objectPath) {
const bounds = this.openmct.time.bounds(); const bounds = this.openmct.time.getBounds();
const snapshotMeta = { const snapshotMeta = {
bounds, bounds,
link: null, link: null,
@ -384,32 +406,34 @@ export default {
this.manageEmbedLayout(); this.manageEmbedLayout();
}, },
async addImageFromPaste(event) { addTextFromPaste(event) {
const clipboardItems = Array.from( if (!this.editMode) {
(event.clipboardData || event.originalEvent.clipboardData).items
);
const hasImage = clipboardItems.some(
(clipboardItem) => clipboardItem.type.includes('image') && clipboardItem.kind === 'file'
);
// If the clipboard contained an image, prevent the paste event from reaching the textarea.
if (hasImage) {
event.preventDefault(); event.preventDefault();
} }
},
async addImageFromPaste(clipboardImages, event) {
event?.preventDefault();
let updated = false;
await Promise.all( await Promise.all(
Array.from(clipboardItems).map(async (clipboardItem) => { Array.from(clipboardImages).map(async (clipboardImage) => {
const isImage = clipboardItem.type.includes('image') && clipboardItem.kind === 'file'; const imageFile = clipboardImage.getAsFile();
if (isImage) {
const imageFile = clipboardItem.getAsFile();
const imageEmbed = await createNewImageEmbed(imageFile, this.openmct, imageFile?.name); const imageEmbed = await createNewImageEmbed(imageFile, this.openmct, imageFile?.name);
if (!this.entry.embeds) { if (!this.entry.embeds) {
this.entry.embeds = []; this.entry.embeds = [];
} }
this.entry.embeds.push(imageEmbed); this.entry.embeds.push(imageEmbed);
}
updated = true;
}) })
); );
if (updated) {
this.manageEmbedLayout(); this.manageEmbedLayout();
this.timestampAndUpdate(); this.timestampAndUpdate();
}
}, },
convertMarkDownToHtml(text = '') { convertMarkDownToHtml(text = '') {
let markDownHtml = this.marked.parse(text, { let markDownHtml = this.marked.parse(text, {

View File

@ -123,7 +123,7 @@ export default {
const objectPath = this.objectPath || this.openmct.router.path; const objectPath = this.objectPath || this.openmct.router.path;
const link = this.isPreview ? this.getPreviewObjectLink() : window.location.hash; const link = this.isPreview ? this.getPreviewObjectLink() : window.location.hash;
const snapshotMeta = { const snapshotMeta = {
bounds: this.openmct.time.bounds(), bounds: this.openmct.time.getBounds(),
link, link,
objectPath, objectPath,
openmct: this.openmct openmct: this.openmct

View File

@ -140,7 +140,7 @@ export function createNewImageEmbed(image, openmct, imageName = '') {
}; };
const embedMetaData = { const embedMetaData = {
bounds: openmct.time.bounds(), bounds: openmct.time.getBounds(),
link: null, link: null,
objectPath: null, objectPath: null,
openmct, openmct,

View File

@ -710,7 +710,7 @@ class CouchObjectProvider {
this.objectQueue[key].pending = true; this.objectQueue[key].pending = true;
const queued = this.objectQueue[key].dequeue(); const queued = this.objectQueue[key].dequeue();
let couchDocument = new CouchDocument(key, queued.model); let couchDocument = new CouchDocument(key, queued.model);
couchDocument.metadata.created = Date.now();
this.#enqueueForPersistence({ this.#enqueueForPersistence({
key, key,
document: couchDocument document: couchDocument

View File

@ -196,10 +196,10 @@ export default {
this.followTimeContext(); this.followTimeContext();
}, },
followTimeContext() { followTimeContext() {
this.updateViewBounds(this.timeContext.bounds()); this.updateViewBounds(this.timeContext.getBounds());
this.timeContext.on('timeSystem', this.setScaleAndGenerateActivities); this.timeContext.on('timeSystem', this.setScaleAndGenerateActivities);
this.timeContext.on('bounds', this.updateViewBounds); this.timeContext.on('boundsChanged', this.updateViewBounds);
}, },
loadComposition() { loadComposition() {
if (this.composition) { if (this.composition) {
@ -211,7 +211,7 @@ export default {
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('timeSystem', this.setScaleAndGenerateActivities); this.timeContext.off('timeSystem', this.setScaleAndGenerateActivities);
this.timeContext.off('bounds', this.updateViewBounds); this.timeContext.off('boundsChanged', this.updateViewBounds);
} }
}, },
showReplacePlanDialog(domainObject) { showReplacePlanDialog(domainObject) {
@ -319,7 +319,7 @@ export default {
} }
if (this.timeSystem === null) { if (this.timeSystem === null) {
this.timeSystem = this.openmct.time.timeSystem(); this.timeSystem = this.openmct.time.getTimeSystem();
} }
this.setScaleAndGenerateActivities(); this.setScaleAndGenerateActivities();
@ -344,7 +344,7 @@ export default {
} }
if (!timeSystem) { if (!timeSystem) {
timeSystem = this.openmct.time.timeSystem(); timeSystem = this.openmct.time.getTimeSystem();
} }
if (timeSystem.isUTCBased) { if (timeSystem.isUTCBased) {

View File

@ -116,7 +116,7 @@ export default {
} }
}, },
setFormatters() { setFormatters() {
let timeSystem = this.openmct.time.timeSystem(); let timeSystem = this.openmct.time.getTimeSystem();
this.timeFormatter = this.openmct.telemetry.getValueFormatter({ this.timeFormatter = this.openmct.telemetry.getValueFormatter({
format: timeSystem.timeFormat format: timeSystem.timeFormat
}).formatter; }).formatter;

View File

@ -661,7 +661,7 @@ export default {
this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth; this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth;
this.startLoading(); this.startLoading();
const bounds = this.timeContext.bounds(); const bounds = this.timeContext.getBounds();
const options = { const options = {
size: this.$parent.$refs.plotWrapper.offsetWidth, size: this.$parent.$refs.plotWrapper.offsetWidth,
domain: this.config.xAxis.get('key'), domain: this.config.xAxis.get('key'),
@ -1860,8 +1860,8 @@ export default {
}, },
showSynchronizeDialog() { showSynchronizeDialog() {
const isLocalClock = this.timeContext.clock(); const isFixedTimespanMode = this.timeContext.isFixed();
if (isLocalClock !== undefined) { if (!isFixedTimespanMode) {
const message = ` const message = `
This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds. This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds.
Do you want to continue? Do you want to continue?

View File

@ -614,7 +614,7 @@ export default {
const yAxisId = series.get('yAxisId') || mainYAxisId; const yAxisId = series.get('yAxisId') || mainYAxisId;
let offset = this.offset[yAxisId]; let offset = this.offset[yAxisId];
return new MCTChartAlarmLineSet(series, this, offset, this.openmct.time.bounds()); return new MCTChartAlarmLineSet(series, this, offset, this.openmct.time.getBounds());
}, },
pointSetForSeries(series) { pointSetForSeries(series) {
const mainYAxisId = this.config.yAxis.get('id'); const mainYAxisId = this.config.yAxis.get('id');

View File

@ -140,7 +140,7 @@ export default class PlotSeries extends Model {
//this triggers Model.destroy which in turn triggers destroy methods for other classes. //this triggers Model.destroy which in turn triggers destroy methods for other classes.
super.destroy(); super.destroy();
this.stopListening(); this.stopListening();
this.openmct.time.off('bounds', this.updateLimits); this.openmct.time.off('boundsChanged', this.updateLimits);
if (this.unsubscribe) { if (this.unsubscribe) {
this.unsubscribe(); this.unsubscribe();
@ -171,7 +171,7 @@ export default class PlotSeries extends Model {
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject); this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject); this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
this.limits = []; this.limits = [];
this.openmct.time.on('bounds', this.updateLimits); this.openmct.time.on('boundsChanged', this.updateLimits);
this.removeMutationListener = this.openmct.objects.observe( this.removeMutationListener = this.openmct.objects.observe(
this.domainObject, this.domainObject,
'name', 'name',

View File

@ -93,7 +93,7 @@ export default class XAxisModel extends Model {
* @override * @override
*/ */
defaultModel(options) { defaultModel(options) {
const bounds = options.openmct.time.bounds(); const bounds = options.openmct.time.getBounds();
const timeSystem = options.openmct.time.getTimeSystem(); const timeSystem = options.openmct.time.getTimeSystem();
const format = options.openmct.telemetry.getFormatter(timeSystem.timeFormat); const format = options.openmct.telemetry.getFormatter(timeSystem.timeFormat);

View File

@ -134,7 +134,7 @@ export default class RemoteClock extends DefaultClock {
* @private * @private
*/ */
_timeSystemChange() { _timeSystemChange() {
let timeSystem = this.openmct.time.timeSystem(); let timeSystem = this.openmct.time.getTimeSystem();
let timeKey = timeSystem.key; let timeKey = timeSystem.key;
let metadataValue = this.metadata.value(timeKey); let metadataValue = this.metadata.value(timeKey);
let timeFormatter = this.openmct.telemetry.getValueFormatter(metadataValue); let timeFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
@ -155,7 +155,7 @@ export default class RemoteClock extends DefaultClock {
#waitForReady() { #waitForReady() {
const waitForInitialTick = (resolve) => { const waitForInitialTick = (resolve) => {
if (this.lastTick > 0) { if (this.lastTick > 0) {
const offsets = this.openmct.time.clockOffsets(); const offsets = this.openmct.time.getClockOffsets();
resolve({ resolve({
start: this.lastTick + offsets.start, start: this.lastTick + offsets.start,
end: this.lastTick + offsets.end end: this.lastTick + offsets.end

View File

@ -62,7 +62,7 @@ SummaryWidgetEvaluator.prototype.subscribe = function (callback) {
} }
const updateCallback = function () { const updateCallback = function () {
const datum = this.evaluateState(realtimeStates, this.openmct.time.timeSystem().key); const datum = this.evaluateState(realtimeStates, this.openmct.time.getTimeSystem().key);
if (datum) { if (datum) {
callback(datum); callback(datum);
} }

View File

@ -611,11 +611,11 @@ describe('The Mean Telemetry Provider', function () {
} }
function createMockTimeApi() { function createMockTimeApi() {
return jasmine.createSpyObj('timeApi', ['timeSystem']); return jasmine.createSpyObj('timeApi', ['getTimeSystem']);
} }
function setTimeSystemTo(timeSystemKey) { function setTimeSystemTo(timeSystemKey) {
mockApi.time.timeSystem.and.returnValue({ mockApi.time.getTimeSystem.and.returnValue({
key: timeSystemKey key: timeSystemKey
}); });
} }

View File

@ -92,7 +92,7 @@ TelemetryAverager.prototype.calculateMean = function () {
* @private * @private
*/ */
TelemetryAverager.prototype.setDomainKeyAndFormatter = function () { TelemetryAverager.prototype.setDomainKeyAndFormatter = function () {
const domainKey = this.timeAPI.timeSystem().key; const domainKey = this.timeAPI.getTimeSystem().key;
if (domainKey !== this.domainKey) { if (domainKey !== this.domainKey) {
this.domainKey = domainKey; this.domainKey = domainKey;
this.domainFormatter = this.getFormatter(domainKey); this.domainFormatter = this.getFormatter(domainKey);

View File

@ -134,7 +134,7 @@ export default class TelemetryTable extends EventEmitter {
//If no persisted sort order, default to sorting by time system, descending. //If no persisted sort order, default to sorting by time system, descending.
sortOptions = sortOptions || { sortOptions = sortOptions || {
key: this.openmct.time.timeSystem().key, key: this.openmct.time.getTimeSystem().key,
direction: 'desc' direction: 'desc'
}; };

View File

@ -40,6 +40,8 @@ export default class TelemetryTableConfiguration extends EventEmitter {
'configuration', 'configuration',
this.objectMutated this.objectMutated
); );
this.notPersistable = !this.openmct.objects.isPersistable(this.domainObject.identifier);
} }
getConfiguration() { getConfiguration() {
@ -52,14 +54,19 @@ export default class TelemetryTableConfiguration extends EventEmitter {
// anything that doesn't have a telemetryMode existed before the change and should // anything that doesn't have a telemetryMode existed before the change and should
// take the properties of any passed in defaults or the defaults from the plugin // take the properties of any passed in defaults or the defaults from the plugin
configuration.telemetryMode = configuration.telemetryMode ?? this.defaultOptions.telemetryMode; configuration.telemetryMode = configuration.telemetryMode ?? this.defaultOptions.telemetryMode;
configuration.persistModeChange = configuration.persistModeChange = this.notPersistable
configuration.persistModeChange ?? this.defaultOptions.persistModeChange; ? false
: configuration.persistModeChange ?? this.defaultOptions.persistModeChange;
configuration.rowLimit = configuration.rowLimit ?? this.defaultOptions.rowLimit; configuration.rowLimit = configuration.rowLimit ?? this.defaultOptions.rowLimit;
return configuration; return configuration;
} }
updateConfiguration(configuration) { updateConfiguration(configuration) {
if (this.notPersistable) {
return;
}
this.openmct.objects.mutate(this.domainObject, 'configuration', configuration); this.openmct.objects.mutate(this.domainObject, 'configuration', configuration);
} }

View File

@ -546,7 +546,7 @@ export default {
this.table.tableRows.on('sort', this.throttledUpdateVisibleRows); this.table.tableRows.on('sort', this.throttledUpdateVisibleRows);
this.table.tableRows.on('filter', this.throttledUpdateVisibleRows); this.table.tableRows.on('filter', this.throttledUpdateVisibleRows);
this.openmct.time.on('bounds', this.boundsChanged); this.openmct.time.on('boundsChanged', this.boundsChanged);
//Default sort //Default sort
this.sortOptions = this.table.tableRows.sortBy(); this.sortOptions = this.table.tableRows.sortBy();
@ -579,7 +579,7 @@ export default {
this.table.configuration.off('change', this.updateConfiguration); this.table.configuration.off('change', this.updateConfiguration);
this.openmct.time.off('bounds', this.boundsChanged); this.openmct.time.off('boundsChanged', this.boundsChanged);
this.table.configuration.destroy(); this.table.configuration.destroy();

View File

@ -141,7 +141,6 @@ export default {
data() { data() {
const bounds = this.openmct.time.getBounds(); const bounds = this.openmct.time.getBounds();
const timeSystem = this.openmct.time.getTimeSystem(); const timeSystem = this.openmct.time.getTimeSystem();
// const isFixed = this.openmct.time.isFixed();
return { return {
timeSystem, timeSystem,

View File

@ -173,16 +173,16 @@ export default {
}); });
}, },
getBoundsForTimeSystem(timeSystem) { getBoundsForTimeSystem(timeSystem) {
const currentBounds = this.timeContext.bounds(); const currentBounds = this.timeContext.getBounds();
//TODO: Some kind of translation via an offset? of current bounds to target timeSystem //TODO: Some kind of translation via an offset? of current bounds to target timeSystem
return currentBounds; return currentBounds;
}, },
updateViewBounds() { updateViewBounds() {
const bounds = this.timeContext.bounds(); const bounds = this.timeContext.getBounds();
this.updateContentHeight(); this.updateContentHeight();
let currentTimeSystemIndex = this.timeSystems.findIndex( let currentTimeSystemIndex = this.timeSystems.findIndex(
(item) => item.timeSystem.key === this.openmct.time.timeSystem().key (item) => item.timeSystem.key === this.openmct.time.getTimeSystem().key
); );
if (currentTimeSystemIndex > -1) { if (currentTimeSystemIndex > -1) {
let currentTimeSystem = { let currentTimeSystem = {
@ -198,13 +198,13 @@ export default {
this.timeContext = this.openmct.time.getContextForView(this.objectPath); this.timeContext = this.openmct.time.getContextForView(this.objectPath);
this.getTimeSystems(); this.getTimeSystems();
this.updateViewBounds(); this.updateViewBounds();
this.timeContext.on('bounds', this.updateViewBounds); this.timeContext.on('boundsChanged', this.updateViewBounds);
this.timeContext.on('clock', this.updateViewBounds); this.timeContext.on('clockChanged', this.updateViewBounds);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('bounds', this.updateViewBounds); this.timeContext.off('boundsChanged', this.updateViewBounds);
this.timeContext.off('clock', this.updateViewBounds); this.timeContext.off('clockChanged', this.updateViewBounds);
} }
} }
} }

View File

@ -97,7 +97,7 @@ const headerItems = [
property: 'start', property: 'start',
name: 'Start Time', name: 'Start Time',
format: function (value, object, key, openmct, options = {}) { format: function (value, object, key, openmct, options = {}) {
const timeFormat = openmct.time.timeSystem().timeFormat; const timeFormat = openmct.time.getTimeSystem().timeFormat;
const timeFormatter = openmct.telemetry.getValueFormatter({ format: timeFormat }).formatter; const timeFormatter = openmct.telemetry.getValueFormatter({ format: timeFormat }).formatter;
if (options.skipDateForToday) { if (options.skipDateForToday) {
return timeFormatter.format(value, SAME_DAY_PRECISION_SECONDS); return timeFormatter.format(value, SAME_DAY_PRECISION_SECONDS);
@ -112,7 +112,7 @@ const headerItems = [
property: 'end', property: 'end',
name: 'End Time', name: 'End Time',
format: function (value, object, key, openmct, options = {}) { format: function (value, object, key, openmct, options = {}) {
const timeFormat = openmct.time.timeSystem().timeFormat; const timeFormat = openmct.time.getTimeSystem().timeFormat;
const timeFormatter = openmct.telemetry.getValueFormatter({ format: timeFormat }).formatter; const timeFormatter = openmct.telemetry.getValueFormatter({ format: timeFormat }).formatter;
if (options.skipDateForToday) { if (options.skipDateForToday) {
return timeFormatter.format(value, SAME_DAY_PRECISION_SECONDS); return timeFormatter.format(value, SAME_DAY_PRECISION_SECONDS);
@ -425,14 +425,14 @@ export default {
}, },
isActivityInBounds(activity) { isActivityInBounds(activity) {
const startInBounds = const startInBounds =
activity.start >= this.timeContext.bounds()?.start && activity.start >= this.timeContext.getBounds()?.start &&
activity.start <= this.timeContext.bounds()?.end; activity.start <= this.timeContext.getBounds()?.end;
const endInBounds = const endInBounds =
activity.end >= this.timeContext.bounds()?.start && activity.end >= this.timeContext.getBounds()?.start &&
activity.end <= this.timeContext.bounds()?.end; activity.end <= this.timeContext.getBounds()?.end;
const middleInBounds = const middleInBounds =
activity.start <= this.timeContext.bounds()?.start && activity.start <= this.timeContext.getBounds()?.start &&
activity.end >= this.timeContext.bounds()?.end; activity.end >= this.timeContext.getBounds()?.end;
return startInBounds || endInBounds || middleInBounds; return startInBounds || endInBounds || middleInBounds;
}, },

View File

@ -33,7 +33,7 @@ export default class StalenessUtils {
shouldUpdateStaleness(stalenessResponse, id) { shouldUpdateStaleness(stalenessResponse, id) {
const stalenessResponseTime = this.parseTime(stalenessResponse); const stalenessResponseTime = this.parseTime(stalenessResponse);
const { start } = this.openmct.time.bounds(); const { start } = this.openmct.time.getBounds();
const isStalenessInCurrentClock = stalenessResponseTime > start; const isStalenessInCurrentClock = stalenessResponseTime > start;
if (stalenessResponseTime > this.lastStalenessResponseTime && isStalenessInCurrentClock) { if (stalenessResponseTime > this.lastStalenessResponseTime && isStalenessInCurrentClock) {