Compare commits

..

16 Commits

Author SHA1 Message Date
David Tsay
e624821ab1 add useClockOffsets composable
documentation update to useTimeBounds
2024-04-17 17:15:40 -07:00
David Tsay
15126fd550 add useTimeBounds composable 2024-04-17 16:12:36 -07:00
David Tsay
5adc86c71e add useTimeMode composable
fix listener in useTimeSystem
2024-04-17 14:16:25 -07:00
David Tsay
a11fcc3231 return reactive isUTCBased 2024-04-17 11:32:10 -07:00
David Tsay
d0a77637c0 extend useTimeSystem to return reactive formatters 2024-04-17 11:22:31 -07:00
David Tsay
eeda4c62fc forgot to push useTimeSystem 2024-04-17 10:46:34 -07:00
David Tsay
10457a583e Merge branch 'master' into time-conductor-options 2024-04-17 10:44:56 -07:00
Jesse Mazzella
5a06b51c5a refactor: remove the final .bounds() call (#7695)
 refactor: Use getBounds method instead of bounds in IndependentTimeContext.
2024-04-17 16:19:21 +00:00
David Tsay
ff605a0a0d create useTimeSystem composable 2024-04-16 16:44:32 -07:00
Scott Bell
ef8b353d01 Improve performance of JSON import and add progress bar (#7654)
* add progress bar

* improves performance a lot

* fix test

* remove old function

* fix legit bug from test - well done tests

* add constant, better comments

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-04-16 21:23:31 +00:00
Jesse Mazzella
6c5b925454 chore: remove all usage of deprecated Time API methods (#7688)
* chore: remove all usage of deprecated Time API methods

* test: update unit test

* docs: Fix spacing and add clarity to TimeConductorBounds definition.

* test: add unit test coverage for new time api methods
2024-04-16 21:12:09 +00:00
David Tsay
e91aba2e37 Handle paste events for images and text properly (#7679)
* enable eval source maps for debugging

* split image and text paste handling
better event handling

* change back source maps

* image takes precedence over text

* break up notebook entry functions for re-use

* create hotkeys utils
add clipboard functions

* add notebook paste test

* add test for pasting to selected but not editing entry

* link tests to issue

* jsdoc addition

* jsdocs

* no need to import then export

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* fix changed path

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-04-16 13:54:40 -07:00
David Tsay
f56a8a1f5d Revert "script, template, style"
This reverts commit c8f8a098f2.
2024-04-15 19:43:22 -07:00
David Tsay
c8f8a098f2 script, template, style 2024-04-15 18:22:58 -07:00
David Tsay
31be2cba3d use vue component naming convention 2024-04-15 16:04:48 -07:00
David Tsay
eeb4f995a4 add missing licensing comment 2024-04-15 15:52:35 -07:00
34 changed files with 841 additions and 132 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

@@ -430,7 +430,7 @@ 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());
}

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,7 +20,7 @@
* 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';
@@ -34,7 +34,7 @@ import { FIXED_MODE_KEY, MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from '.
/**
* @typedef {Object} TimeConductorBounds
* @property {number } start The start time displayed by the time conductor
* @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.
@@ -426,8 +426,8 @@ class TimeContext extends EventEmitter {
/**
* Set the time system of the TimeAPI.
* Emits a "timeSystem" event with the new time system.
* @param {TimeSystem | string} timeSystemOrKey
* @param {TimeConductorBounds} bounds
* @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) {

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) {

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) {

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

@@ -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

@@ -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: {

View File

@@ -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) {

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

@@ -61,7 +61,7 @@ describe('The local time', () => {
});
it('can be set to be the main time system', () => {
expect(openmct.time.getTimeSystem().key).toBe(LOCAL_SYSTEM_KEY);
expect(openmct.time.timeSystem().key).toBe(LOCAL_SYSTEM_KEY);
});
it('uses the local-format time format', () => {

View File

@@ -278,7 +278,7 @@ export default {
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,6 +368,28 @@ 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.getBounds();
const snapshotMeta = {
@@ -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

@@ -199,7 +199,7 @@ export default {
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) {

View File

@@ -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

@@ -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

@@ -149,6 +149,8 @@ export default class RemoteClock extends DefaultClock {
/**
* Waits for the clock to have a non-default tick value.
*
* @private
*/
#waitForReady() {
const waitForInitialTick = (resolve) => {

View File

@@ -611,7 +611,7 @@ describe('The Mean Telemetry Provider', function () {
}
function createMockTimeApi() {
return jasmine.createSpyObj('timeApi', ['getTimeSystem', 'setTimeSystem']);
return jasmine.createSpyObj('timeApi', ['getTimeSystem']);
}
function setTimeSystemTo(timeSystemKey) {

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

@@ -74,6 +74,7 @@
<script>
import _ from 'lodash';
import { inject, onMounted, provide } from 'vue';
import {
FIXED_MODE_KEY,
@@ -90,6 +91,7 @@ import ConductorModeIcon from './ConductorModeIcon.vue';
import ConductorPopUp from './ConductorPopUp.vue';
import conductorPopUpManager from './conductorPopUpManager.js';
import ConductorTimeSystem from './ConductorTimeSystem.vue';
import { useTimeSystem } from './useTimeSystem.js';
const DEFAULT_DURATION_FORMATTER = 'duration';
@@ -106,6 +108,16 @@ export default {
},
mixins: [conductorPopUpManager],
inject: ['openmct', 'configuration'],
setup() {
const openmct = inject('openmct');
const { observeTimeSystem, timeSystemKey } = useTimeSystem(openmct);
onMounted(() => observeTimeSystem());
provide('timeSystemKey', timeSystemKey);
return { timeSystemKey };
},
data() {
const isFixed = this.openmct.time.isFixed();
const bounds = this.openmct.time.getBounds();

View File

@@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<template>
<time-popup-fixed
<TimePopupFixed
v-if="readOnly === false"
:input-bounds="bounds"
:input-time-system="timeSystem"

View File

@@ -1,3 +1,25 @@
<!--
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.
-->
<template>
<form ref="fixedDeltaInput">
<div class="c-tc-input-popup__input-grid">

View File

@@ -1,3 +1,25 @@
<!--
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.
-->
<template>
<form ref="deltaInput">
<div class="c-tc-input-popup__input-grid">

View File

@@ -0,0 +1,59 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { onBeforeUnmount, shallowRef } from 'vue';
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
/**
* Provides reactive `offsets`,
* as well as a function to observe and update offsets changes,
* which automatically stops observing when the component is unmounted.
*
* @param {OpenMCT} openmct the Open MCT API
* @returns {{
* observeClockOffsets: () => void,
* offsets: import('vue').Ref<object>,
* }}
*/
export function useClockOffsets(openmct, options) {
let stopObservingClockOffsets;
const offsets = shallowRef(openmct.time.getClockOffsets());
onBeforeUnmount(() => stopObservingClockOffsets?.());
function observeClockOffsets() {
openmct.time.on(TIME_CONTEXT_EVENTS.clockOffsetsChanged, updateClockOffsets);
stopObservingClockOffsets = () =>
openmct.time.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, updateClockOffsets);
}
function updateClockOffsets(_offsets) {
offsets.value = _offsets;
}
return {
observeClockOffsets,
offsets
};
}

View File

@@ -0,0 +1,64 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { onBeforeUnmount, ref, shallowRef } from 'vue';
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
import throttle from '../../utils/throttle.js';
/**
* Provides reactive `bounds`,
* as well as a function to observe and update bounds changes,
* which automatically stops observing when the component is unmounted.
*
* @param {OpenMCT} openmct the Open MCT API
* @returns {{
* observeTimeBounds: () => void,
* bounds: import('vue').Ref<object>,
* isTick: import('vue').Ref<boolean>
* }}
*/
export function useTimeBounds(openmct, options) {
let stopObservingTimeBounds;
const bounds = shallowRef(openmct.time.getBounds());
const isTick = ref(false);
onBeforeUnmount(() => stopObservingTimeBounds?.());
function observeTimeBounds(milliseconds = 300) {
openmct.time.on(TIME_CONTEXT_EVENTS.boundsChanged, throttle(updateTimeBounds), milliseconds);
stopObservingTimeBounds = () =>
openmct.time.off(TIME_CONTEXT_EVENTS.boundsChanged, throttle(updateTimeBounds), milliseconds);
}
function updateTimeBounds(_timeBounds, _isTick) {
bounds.value = _timeBounds;
isTick.value = _isTick;
}
return {
observeTimeBounds,
isTick,
bounds
};
}

View File

@@ -0,0 +1,66 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { computed, onBeforeUnmount, ref } from 'vue';
import {
FIXED_MODE_KEY,
REALTIME_MODE_KEY,
TIME_CONTEXT_EVENTS
} from '../../api/time/constants.js';
/**
* Provides reactive `isFixedTimeMode` and `isRealTimeMode`,
* as well as a function to observe and update the component's time mode,
* which automatically stops observing when the component is unmounted.
*
* @param {OpenMCT} openmct the Open MCT API
* @returns {{
* observeTimeMode: () => void,
* isFixedTimeMode: import('vue').Ref<boolean>,
* isRealTimeMode: import('vue').Ref<boolean>
* }}
*/
export function useTimeMode(openmct, options) {
let stopObservingTimeMode;
const timeMode = ref(openmct.time.getMode());
const isFixedTimeMode = computed(() => timeMode.value === FIXED_MODE_KEY);
const isRealTimeMode = computed(() => timeMode.value === REALTIME_MODE_KEY);
onBeforeUnmount(() => stopObservingTimeMode?.());
function observeTimeMode() {
openmct.time.on(TIME_CONTEXT_EVENTS.modeChanged, updateTimeMode);
stopObservingTimeMode = () => openmct.time.off(TIME_CONTEXT_EVENTS.modeChanged, updateTimeMode);
}
function updateTimeMode(_timeMode) {
timeMode.value = _timeMode;
}
return {
observeTimeMode,
isFixedTimeMode,
isRealTimeMode
};
}

View File

@@ -0,0 +1,83 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { onBeforeUnmount, ref } from 'vue';
const DEFAULT_DURATION_FORMATTER = 'duration';
/**
* Provides a reactive destructuring of the component's current time system,
* as well as a function to observe and update the component's time system,
* which automatically stops observing when the component is unmounted.
*
* @param {OpenMCT} openmct the Open MCT API
* @returns {{
* observeTimeSystem: () => void,
* timeSystemKey: import('vue').Ref<string>,
* timeSystemFormatter: import('vue').Ref<() => void>,
* timeSystemDurationFormatter: import('vue').Ref<() => void>,
* isTimeSystemUTCBased: import('vue').Ref<boolean>
* }}
*/
export function useTimeSystem(openmct, options) {
let stopObservingTimeSystem;
const currentTimeSystem = openmct.time.getTimeSystem();
const timeSystemKey = ref(currentTimeSystem.key);
const timeSystemFormatter = ref(getFormatter(openmct, currentTimeSystem.timeFormat));
const timeSystemDurationFormatter = ref(
getFormatter(openmct, currentTimeSystem.durationFormat || DEFAULT_DURATION_FORMATTER)
);
const isTimeSystemUTCBased = ref(currentTimeSystem.isUTCBased);
onBeforeUnmount(() => stopObservingTimeSystem?.());
function observeTimeSystem() {
openmct.time.on('timeSystemChanged', updateTimeSystem);
stopObservingTimeSystem = () => openmct.time.off('timeSystemChanged', updateTimeSystem);
}
function updateTimeSystem(timeSystem) {
timeSystemKey.value = timeSystem.key;
timeSystemFormatter.value = getFormatter(openmct, timeSystem.timeFormat);
timeSystemDurationFormatter.value = getFormatter(
openmct,
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
);
isTimeSystemUTCBased.value = timeSystem.isUTCBased;
}
return {
observeTimeSystem,
timeSystemKey,
timeSystemFormatter,
timeSystemDurationFormatter,
isTimeSystemUTCBased
};
}
function getFormatter(openmct, key) {
return openmct.telemetry.getValueFormatter({
format: key
}).formatter;
}

View File

@@ -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);
}
}
}