Merge branch 'master' into time-conductor-options

This commit is contained in:
David Tsay 2024-04-17 10:44:56 -07:00
commit 10457a583e
46 changed files with 798 additions and 332 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 {string} text
*/
async function enterTextEntry(page, text) {
// Click the 'Add Notebook Entry' area
await page.locator(NOTEBOOK_DROP_AREA).click();
// enter text
await page.getByLabel('Notebook Entry Input').last().fill(text);
await addNotebookEntry(page);
await enterTextInLastEntry(page, text);
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
*/
@ -140,10 +152,13 @@ async function createNotebookEntryAndTags(page, iterations = 1) {
}
export {
addNotebookEntry,
commitEntry,
createNotebookAndEntry,
createNotebookEntryAndTags,
dragAndDropEmbed,
enterTextEntry,
enterTextInLastEntry,
lockPage,
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 { createDomainObjectWithDefaults } from '../../../../appActions.js';
import { copy, paste, selectAll } from '../../../../helper/hotkeys/hotkeys.js';
import * as nbUtils from '../../../../helper/notebookUtils.js';
import { expect, streamToString, test } from '../../../../pluginFixtures.js';
@ -546,4 +547,53 @@ test.describe('Notebook entry tests', () => {
);
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';
/**
* @typedef {import('./TimeAPI').TimeConductorBounds} TimeConductorBounds
*/
/**
* 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
@ -38,12 +42,10 @@ class GlobalTimeContext extends TimeContext {
* Get or set the start and end time of the time conductor. Basic validation
* of bounds is performed.
*
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
* @param {TimeConductorBounds} newBounds
* @throws {Error} Validation error
* @fires module:openmct.TimeAPI~bounds
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
* @memberof module:openmct.TimeAPI#
* @method bounds
* @returns {TimeConductorBounds}
* @override
*/
bounds(newBounds) {
if (arguments.length > 0) {
@ -61,9 +63,9 @@ class GlobalTimeContext extends TimeContext {
/**
* Update bounds based on provided time and current offsets
* @private
* @param {number} timestamp A time from which bounds will be calculated
* using current offsets.
* @override
*/
tick(timestamp) {
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.
* The time of interest can effectively be unset by assigning a value of
* 'undefined'.
* @fires module:openmct.TimeAPI~timeOfInterest
* @param newTOI
* @returns {number} the current time of interest
* @memberof module:openmct.TimeAPI#
* @method timeOfInterest
*/
timeOfInterest(newTOI) {
if (arguments.length > 0) {
@ -93,8 +92,7 @@ class GlobalTimeContext extends TimeContext {
/**
* The Time of Interest has moved.
* @event timeOfInterest
* @memberof module:openmct.TimeAPI~
* @property {number} Current time of interest
* @property {number} timeOfInterest time of interest
*/
this.emit('timeOfInterest', this.toi);
}

View File

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

View File

@ -25,6 +25,41 @@ import IndependentTimeContext from '@/api/time/IndependentTimeContext';
import GlobalTimeContext from './GlobalTimeContext.js';
/**
* @typedef {import('./TimeContext.js').default} TimeContext
*/
/**
* @typedef {import('./TimeContext.js').TimeConductorBounds} TimeConductorBounds
*/
/**
* @typedef {import('./TimeContext.js').ClockOffsets} ClockOffsets
*/
/**
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
* MCT supports multiple different types of time values, although all are
* intrinsically represented by numbers, the meaning of those numbers can
* differ depending on context.
*
* A default time system is provided by Open MCT in the form of the {@link UTCTimeSystem},
* which represents integer values as ms in the Unix epoch. An example of
* another time system might be "sols" for a Martian mission. TimeSystems do
* not address the issue of converting between time systems.
*
* @typedef {Object} TimeSystem
* @property {string} key A unique identifier
* @property {string} name A human-readable descriptor
* @property {string} [cssClass] Specify a css class defining an icon for
* this time system. This will be visible next to the time system in the
* menu in the Time Conductor
* @property {string} timeFormat The key of a format to use when displaying
* discrete timestamps from this time system
* @property {string} [durationFormat] The key of a format to use when
* 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
@ -41,8 +76,8 @@ import GlobalTimeContext from './GlobalTimeContext.js';
* fired when properties of the time conductor change, which are documented
* below.
*
* @interface
* @memberof module:openmct
* @class
* @extends {GlobalTimeContext}
*/
class TimeAPI extends GlobalTimeContext {
constructor(openmct) {
@ -51,33 +86,9 @@ class TimeAPI extends GlobalTimeContext {
this.independentContexts = new Map();
}
/**
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
* MCT supports multiple different types of time values, although all are
* intrinsically represented by numbers, the meaning of those numbers can
* differ depending on context.
*
* A default time system is provided by Open MCT in the form of the {@link UTCTimeSystem},
* which represents integer values as ms in the Unix epoch. An example of
* another time system might be "sols" for a Martian mission. TimeSystems do
* not address the issue of converting between time systems.
*
* @typedef {Object} TimeSystem
* @property {string} key A unique identifier
* @property {string} name A human-readable descriptor
* @property {string} [cssClass] Specify a css class defining an icon for
* this time system. This will be visible next to the time system in the
* menu in the Time Conductor
* @property {string} timeFormat The key of a format to use when displaying
* discrete timestamps from this time system
* @property {string} [durationFormat] The key of a format to use when
* displaying a duration or relative span of time in this time system.
*/
/**
* 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).
* @memberof module:openmct.TimeAPI#
* @param {TimeSystem} timeSystem A time system object.
*/
addTimeSystem(timeSystem) {
@ -109,7 +120,6 @@ class TimeAPI extends GlobalTimeContext {
/**
* Register a new Clock.
* @memberof module:openmct.TimeAPI#
* @param {Clock} clock
*/
addClock(clock) {
@ -117,9 +127,7 @@ class TimeAPI extends GlobalTimeContext {
}
/**
* @memberof module:openmct.TimeAPI#
* @returns {Clock[]}
* @memberof module:openmct.TimeAPI#
*/
getAllClocks() {
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,
* 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 {ClockOffsets | TimeBounds} value This maintains a sliding time window of a fixed width that automatically updates
* @param {string} key The identifier key of the domain object these offsets are set for
* @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
* @memberof module:openmct.TimeAPI#
* @method addIndependentTimeContext
*/
addIndependentContext(key, value, clockKey) {
let timeContext = this.getIndependentContext(key);
@ -159,9 +165,8 @@ class TimeAPI extends GlobalTimeContext {
/**
* Get the independent time context which follows the TimeAPI timeSystem,
* but with different offsets.
* @param {key | string} key The identifier key of the domain object these offsets
* @memberof module:openmct.TimeAPI#
* @method getIndependentTimeContext
* @param {string} key The identifier key of the domain object these offsets
* @returns {IndependentTimeContext} The independent time context
*/
getIndependentContext(key) {
return this.independentContexts.get(key);
@ -170,9 +175,8 @@ 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.
* Otherwise, the global time context will be returned.
* @param { Array } objectPath The view's objectPath
* @memberof module:openmct.TimeAPI#
* @method getContextForView
* @param {Array} objectPath The view's objectPath
* @returns {TimeContext | GlobalTimeContext} The time context
*/
getContextForView(objectPath) {
if (!objectPath || !Array.isArray(objectPath)) {

View File

@ -57,7 +57,7 @@ describe('The Time API', function () {
expect(api.timeOfInterest()).toBe(toi);
});
it('Allows setting of valid bounds', function () {
it('[Legacy TimeAPI]: Allows setting of valid bounds', function () {
bounds = {
start: 0,
end: 1
@ -67,7 +67,17 @@ describe('The Time API', function () {
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 = {
start: 1,
end: 0
@ -82,7 +92,22 @@ describe('The Time API', function () {
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);
expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () {
@ -91,7 +116,16 @@ describe('The Time API', function () {
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);
expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () {
@ -100,6 +134,32 @@ describe('The Time API', function () {
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 () {
api.addTimeSystem(timeSystem);
api.addClock(clock);
@ -114,7 +174,7 @@ describe('The Time API', function () {
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);
expect(eventListener).not.toHaveBeenCalled();
api.on('timeSystem', eventListener);
@ -122,6 +182,14 @@ describe('The Time API', function () {
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 () {
expect(eventListener).not.toHaveBeenCalled();
api.on('timeOfInterest', eventListener);
@ -129,13 +197,20 @@ describe('The Time API', function () {
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();
api.on('bounds', eventListener);
api.bounds(bounds);
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 () {
api.timeOfInterest(6);
api.bounds({
@ -154,13 +229,39 @@ describe('The Time API', function () {
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 () {
const mockTickSource = jasmine.createSpyObj('mockTickSource', ['on', 'off', 'currentValue']);
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 () {
@ -184,7 +285,7 @@ describe('The Time API', function () {
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);
expect(api.bounds()).toEqual({
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);
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 () {
api.clock('mts', mockOffsets);
api.clock('amts', mockOffsets);
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();
api.clock('mts', mockOffsets);
expect(api.clock()).toBeDefined();
// api.stopClock();
// expect(api.clock()).toBeUndefined();
// Unset the clock
api.stopClock();
expect(api.clock()).toBeUndefined();
});
it('Provides a default time context', () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -245,7 +245,7 @@ export default class Condition extends EventEmitter {
latestTimestamp,
updatedCriterion.data,
this.timeSystems,
this.openmct.time.timeSystem()
this.openmct.time.getTimeSystem()
);
this.conditionManager.updateCurrentCondition(latestTimestamp);
}
@ -309,7 +309,7 @@ export default class Condition extends EventEmitter {
latestTimestamp,
data,
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.openmct.time.timeSystem()
this.openmct.time.getTimeSystem()
);
this.updateConditionResults({ id: id });
this.updateCurrentCondition(latestTimestamp);
@ -383,7 +383,7 @@ export default class ConditionManager extends EventEmitter {
latestTimestamp,
data,
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);
if (styleConfiguration.conditionSetIdentifier) {
this.openmct.time.on('bounds', this.refreshData);
this.openmct.time.on('boundsChanged', this.refreshData);
this.subscribeToConditionSet();
} else {
this.applyStaticStyle();
@ -216,7 +216,7 @@ export default class StyleRuleManager extends EventEmitter {
}
if (!skipEventListeners) {
this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.off('boundsChanged', this.refreshData);
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) => {
let latestTimestamp;
const timeSystems = this.openmct.time.getAllTimeSystems();
const timeSystem = this.openmct.time.timeSystem();
const timeSystem = this.openmct.time.getTimeSystem();
telemetryRequestsResults.forEach((results, index) => {
const latestDatum =

View File

@ -280,7 +280,7 @@ export default {
await this.$nextTick();
},
formattedValueForCopy() {
const timeFormatterKey = this.openmct.time.timeSystem().key;
const timeFormatterKey = this.openmct.time.getTimeSystem().key;
const timeFormatter = this.formats[timeFormatterKey];
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, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({
spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000,
end: 5000
});
@ -306,7 +306,7 @@ describe('Gauge plugin', () => {
});
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({
spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000,
end: 5000
});
@ -448,7 +448,7 @@ describe('Gauge plugin', () => {
});
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({
spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000,
end: 5000
});
@ -763,7 +763,7 @@ describe('Gauge plugin', () => {
})
});
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
spyOn(openmct.time, 'bounds').and.returnValue({
spyOn(openmct.time, 'getBounds').and.returnValue({
start: 1000,
end: 5000
});

View File

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

View File

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

View File

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

View File

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

View File

@ -144,24 +144,132 @@ export default class ImportAsJSONAction {
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 = {
namespace: newNamespace,
key: uuid()
};
tree = this._rewriteId(oldId, newId, tree);
}, this);
const newIdKeyString = this.openmct.objects.makeKeyString(newId);
// 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;
}
/**
@ -170,9 +278,16 @@ export default class ImportAsJSONAction {
* @param {Object} 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 namespace = domainObject.identifier.namespace;
const tree = this._generateNewIdentifiers(objTree, namespace);
const tree = await this._generateNewIdentifiers(objTree, namespace, importDialog);
const rootId = tree.rootId;
const rootObj = tree.openmct[rootId];
@ -182,11 +297,24 @@ export default class ImportAsJSONAction {
this._deepInstantiate(rootObj, tree.openmct, [], objectsToCreate);
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) {
this.openmct.notifications.error('Error saving objects');
throw error;
} finally {
importDialog.dismiss();
}
const compositionCollection = this.openmct.composition.get(domainObject);
@ -194,7 +322,8 @@ export default class ImportAsJSONAction {
this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString);
compositionCollection.add(rootObj);
} else {
const dialog = this.openmct.overlays.dialog({
importDialog.dismiss();
const cannotImportDialog = this.openmct.overlays.dialog({
iconClass: 'alert',
message: "We're sorry, but you cannot import that object type into this object.",
buttons: [
@ -202,7 +331,7 @@ export default class ImportAsJSONAction {
label: 'Ok',
emphasis: true,
callback: function () {
dialog.dismiss();
cannotImportDialog.dismiss();
}
}
]
@ -217,43 +346,7 @@ export default class ImportAsJSONAction {
_instantiate(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
* @param {Object} domainObject

View File

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

View File

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

View File

@ -275,10 +275,10 @@ export default {
}
const hash = this.embed.historicLink;
const bounds = this.openmct.time.bounds();
const bounds = this.openmct.time.getBounds();
const isTimeBoundChanged =
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 = '';
if (isTimeBoundChanged) {

View File

@ -31,7 +31,7 @@
@drop.capture="cancelEditMode"
@drop.prevent="dropOnEntry"
@click="selectAndEmitEntry($event, entry)"
@paste="addImageFromPaste"
@paste="handlePaste"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time-and-creator-and-delete">
@ -368,8 +368,30 @@ export default {
}
},
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) {
const bounds = this.openmct.time.bounds();
const bounds = this.openmct.time.getBounds();
const snapshotMeta = {
bounds,
link: null,
@ -384,32 +406,34 @@ export default {
this.manageEmbedLayout();
},
async addImageFromPaste(event) {
const clipboardItems = Array.from(
(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) {
addTextFromPaste(event) {
if (!this.editMode) {
event.preventDefault();
}
},
async addImageFromPaste(clipboardImages, event) {
event?.preventDefault();
let updated = false;
await Promise.all(
Array.from(clipboardItems).map(async (clipboardItem) => {
const isImage = clipboardItem.type.includes('image') && clipboardItem.kind === 'file';
if (isImage) {
const imageFile = clipboardItem.getAsFile();
const imageEmbed = await createNewImageEmbed(imageFile, this.openmct, imageFile?.name);
if (!this.entry.embeds) {
this.entry.embeds = [];
}
this.entry.embeds.push(imageEmbed);
Array.from(clipboardImages).map(async (clipboardImage) => {
const imageFile = clipboardImage.getAsFile();
const imageEmbed = await createNewImageEmbed(imageFile, this.openmct, imageFile?.name);
if (!this.entry.embeds) {
this.entry.embeds = [];
}
this.entry.embeds.push(imageEmbed);
updated = true;
})
);
this.manageEmbedLayout();
this.timestampAndUpdate();
if (updated) {
this.manageEmbedLayout();
this.timestampAndUpdate();
}
},
convertMarkDownToHtml(text = '') {
let markDownHtml = this.marked.parse(text, {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -614,7 +614,7 @@ export default {
const yAxisId = series.get('yAxisId') || mainYAxisId;
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) {
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.
super.destroy();
this.stopListening();
this.openmct.time.off('bounds', this.updateLimits);
this.openmct.time.off('boundsChanged', this.updateLimits);
if (this.unsubscribe) {
this.unsubscribe();
@ -171,7 +171,7 @@ export default class PlotSeries extends Model {
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
this.limits = [];
this.openmct.time.on('bounds', this.updateLimits);
this.openmct.time.on('boundsChanged', this.updateLimits);
this.removeMutationListener = this.openmct.objects.observe(
this.domainObject,
'name',

View File

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

View File

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

View File

@ -62,7 +62,7 @@ SummaryWidgetEvaluator.prototype.subscribe = function (callback) {
}
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) {
callback(datum);
}

View File

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

View File

@ -92,7 +92,7 @@ TelemetryAverager.prototype.calculateMean = function () {
* @private
*/
TelemetryAverager.prototype.setDomainKeyAndFormatter = function () {
const domainKey = this.timeAPI.timeSystem().key;
const domainKey = this.timeAPI.getTimeSystem().key;
if (domainKey !== this.domainKey) {
this.domainKey = 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.
sortOptions = sortOptions || {
key: this.openmct.time.timeSystem().key,
key: this.openmct.time.getTimeSystem().key,
direction: 'desc'
};

View File

@ -546,7 +546,7 @@ export default {
this.table.tableRows.on('sort', 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
this.sortOptions = this.table.tableRows.sortBy();
@ -579,7 +579,7 @@ export default {
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();

View File

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

View File

@ -173,16 +173,16 @@ export default {
});
},
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
return currentBounds;
},
updateViewBounds() {
const bounds = this.timeContext.bounds();
const bounds = this.timeContext.getBounds();
this.updateContentHeight();
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) {
let currentTimeSystem = {
@ -198,13 +198,13 @@ export default {
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
this.getTimeSystems();
this.updateViewBounds();
this.timeContext.on('bounds', this.updateViewBounds);
this.timeContext.on('clock', this.updateViewBounds);
this.timeContext.on('boundsChanged', this.updateViewBounds);
this.timeContext.on('clockChanged', this.updateViewBounds);
},
stopFollowingTimeContext() {
if (this.timeContext) {
this.timeContext.off('bounds', this.updateViewBounds);
this.timeContext.off('clock', this.updateViewBounds);
this.timeContext.off('boundsChanged', this.updateViewBounds);
this.timeContext.off('clockChanged', this.updateViewBounds);
}
}
}

View File

@ -97,7 +97,7 @@ const headerItems = [
property: 'start',
name: 'Start Time',
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;
if (options.skipDateForToday) {
return timeFormatter.format(value, SAME_DAY_PRECISION_SECONDS);
@ -112,7 +112,7 @@ const headerItems = [
property: 'end',
name: 'End Time',
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;
if (options.skipDateForToday) {
return timeFormatter.format(value, SAME_DAY_PRECISION_SECONDS);
@ -425,14 +425,14 @@ export default {
},
isActivityInBounds(activity) {
const startInBounds =
activity.start >= this.timeContext.bounds()?.start &&
activity.start <= this.timeContext.bounds()?.end;
activity.start >= this.timeContext.getBounds()?.start &&
activity.start <= this.timeContext.getBounds()?.end;
const endInBounds =
activity.end >= this.timeContext.bounds()?.start &&
activity.end <= this.timeContext.bounds()?.end;
activity.end >= this.timeContext.getBounds()?.start &&
activity.end <= this.timeContext.getBounds()?.end;
const middleInBounds =
activity.start <= this.timeContext.bounds()?.start &&
activity.end >= this.timeContext.bounds()?.end;
activity.start <= this.timeContext.getBounds()?.start &&
activity.end >= this.timeContext.getBounds()?.end;
return startInBounds || endInBounds || middleInBounds;
},

View File

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