diff --git a/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js b/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js index 87f608f528..e654ff213d 100644 --- a/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js @@ -108,4 +108,42 @@ test.describe('Plot Controls', () => { // Expect before and after plot points to match await expect(plotPixelSizeAtPause).toEqual(plotPixelSizeAfterWait); }); + + /* + Test to verify that switching a plot's time context from global to + its own independent time context and then back to global context works correctly. + + After switching from fixed time mode (ITC) to real time mode (global context), + the pause control for the plot should be available, indicating that it is following the right context. + */ + test('Plots follow the right time context', async ({ page }) => { + // Set global time conductor to real-time mode + await setRealTimeMode(page); + + // hover over plot for plot controls + await page.getByLabel('Plot Canvas').hover(); + // Ensure pause control is visible since global time conductor is in Real time mode. + await expect(page.getByTitle('Pause incoming real-time data')).toBeVisible(); + + // Toggle independent time conductor ON + await page.getByLabel('Enable Independent Time Conductor').click(); + + // Bring up the independent time conductor popup and switch to fixed time mode + await page.getByLabel('Independent Time Conductor Settings').click(); + await page.getByLabel('Independent Time Conductor Mode Menu').click(); + await page.getByRole('menuitem', { name: /Fixed Timespan/ }).click(); + + // hover over plot for plot controls + await page.getByLabel('Plot Canvas').hover(); + // Ensure pause control is no longer visible since the plot is following the independent time context + await expect(page.getByTitle('Pause incoming real-time data')).toBeHidden(); + + // Toggle independent time conductor OFF - Note that the global time conductor is still in Real time mode + await page.getByLabel('Disable Independent Time Conductor').click(); + + // hover over plot for plot controls + await page.getByLabel('Plot Canvas').hover(); + // Ensure pause control is visible since the global time conductor is in real time mode + await expect(page.getByTitle('Pause incoming real-time data')).toBeVisible(); + }); }); diff --git a/src/api/time/IndependentTimeContext.js b/src/api/time/IndependentTimeContext.js index 9f9edbcc4d..148bf52adf 100644 --- a/src/api/time/IndependentTimeContext.js +++ b/src/api/time/IndependentTimeContext.js @@ -359,6 +359,18 @@ class IndependentTimeContext extends TimeContext { } } + /** + * @returns {boolean} + * @override + */ + isFixed() { + if (this.upstreamTimeContext) { + return this.upstreamTimeContext.isFixed(...arguments); + } else { + return super.isFixed(...arguments); + } + } + /** * @returns {number} * @override @@ -400,7 +412,7 @@ class IndependentTimeContext extends TimeContext { } /** - * Reset the time context to the global time context + * Reset the time context from the global time context */ resetContext() { if (this.upstreamTimeContext) { @@ -428,6 +440,10 @@ class IndependentTimeContext extends TimeContext { // Emit bounds so that views that are changing context get the upstream bounds this.emit('bounds', this.getBounds()); this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds()); + // Also emit the mode in case it's different from previous time context + if (this.getMode()) { + this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.getMode())); + } } /** @@ -502,6 +518,10 @@ class IndependentTimeContext extends TimeContext { // Emit bounds so that views that are changing context get the upstream bounds this.emit('bounds', this.getBounds()); this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds()); + // Also emit the mode in case it's different from the global time context + if (this.getMode()) { + this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.getMode())); + } // now that the view's context is set, tell others to check theirs in case they were following this view's context. this.globalTimeContext.emit('refreshContext', viewKey); } diff --git a/src/api/time/TimeAPI.js b/src/api/time/TimeAPI.js index 3cc2d8b6e1..8b29a7c5ff 100644 --- a/src/api/time/TimeAPI.js +++ b/src/api/time/TimeAPI.js @@ -23,6 +23,7 @@ import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '@/api/time/constants'; import IndependentTimeContext from '@/api/time/IndependentTimeContext'; +import { TIME_CONTEXT_EVENTS } from './constants'; import GlobalTimeContext from './GlobalTimeContext.js'; /** @@ -142,7 +143,7 @@ class TimeAPI extends GlobalTimeContext { addIndependentContext(key, value, clockKey) { let timeContext = this.getIndependentContext(key); - //stop following upstream time context since the view has it's own + //stop following upstream time context since the view has its own timeContext.resetContext(); if (clockKey) { @@ -152,6 +153,9 @@ class TimeAPI extends GlobalTimeContext { timeContext.setMode(FIXED_MODE_KEY, value); } + // Also emit the mode in case it's different from the previous time context + timeContext.emit(TIME_CONTEXT_EVENTS.modeChanged, structuredClone(timeContext.getMode())); + // Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context this.emit('refreshContext', key); diff --git a/src/plugins/plot/MctPlot.vue b/src/plugins/plot/MctPlot.vue index 08e2a6f3d0..cd395974b8 100644 --- a/src/plugins/plot/MctPlot.vue +++ b/src/plugins/plot/MctPlot.vue @@ -539,6 +539,7 @@ export default { this.followTimeContext(); }, followTimeContext() { + this.updateMode(); this.updateDisplayBounds(this.timeContext.getBounds()); this.timeContext.on('modeChanged', this.updateMode); this.timeContext.on('boundsChanged', this.updateDisplayBounds); diff --git a/src/plugins/timeConductor/independent/IndependentTimeConductor.vue b/src/plugins/timeConductor/independent/IndependentTimeConductor.vue index 5ca91492a4..2ff26ee74e 100644 --- a/src/plugins/timeConductor/independent/IndependentTimeConductor.vue +++ b/src/plugins/timeConductor/independent/IndependentTimeConductor.vue @@ -243,12 +243,20 @@ export default { this.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this.setTimeOptionsMode); }, setTimeOptionsClock(clock) { + // If the user has persisted any time options, then don't override them with global settings. + if (this.independentTCEnabled) { + return; + } this.setTimeOptionsOffsets(); this.timeOptions.clock = clock.key; }, setTimeOptionsMode(mode) { - this.setTimeOptionsOffsets(); - this.timeOptions.mode = mode; + // If the user has persisted any time options, then don't override them with global settings. + if (this.independentTCEnabled) { + this.setTimeOptionsOffsets(); + this.timeOptions.mode = mode; + this.isFixed = this.timeOptions.mode === FIXED_MODE_KEY; + } }, setTimeOptionsOffsets() { this.timeOptions.clockOffsets =