mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
6 Commits
remove-dep
...
mobile-itc
Author | SHA1 | Date | |
---|---|---|---|
5f427bde73 | |||
5a06b51c5a | |||
ef8b353d01 | |||
6c5b925454 | |||
e91aba2e37 | |||
523be2c3ab |
47
e2e/helper/hotkeys/clipboard.js
Normal file
47
e2e/helper/hotkeys/clipboard.js
Normal 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 };
|
23
e2e/helper/hotkeys/hotkeys.js
Normal file
23
e2e/helper/hotkeys/hotkeys.js
Normal 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';
|
@ -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
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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', () => {
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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: {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) }
|
||||
|
@ -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', () => {
|
||||
|
@ -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) {
|
||||
|
@ -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, {
|
||||
|
@ -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) {
|
||||
|
@ -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?
|
||||
|
@ -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',
|
||||
|
@ -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) => {
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -454,11 +454,13 @@
|
||||
color: $colorTimeRealtimeFg;
|
||||
}
|
||||
}
|
||||
.c-ctrl-wrapper--menus-up{ // A bit hacky, but we are rewriting the CSS class here for ITC such that the calendar opens at the bottom to avoid cutoff
|
||||
.c-menu {
|
||||
top: auto;
|
||||
bottom: revert !important;
|
||||
};
|
||||
body.desktop & {
|
||||
.c-ctrl-wrapper--menus-up{ // A bit hacky, but we are rewriting the CSS class here for ITC such that the calendar opens at the bottom to avoid cutoff. This is for DESKTOP-ONLY
|
||||
.c-menu {
|
||||
top: auto;
|
||||
bottom: revert !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user