mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
50 Commits
docs-relea
...
itc-mode-d
Author | SHA1 | Date | |
---|---|---|---|
a07eb07090 | |||
9f55e4c98e | |||
17a1a74eed | |||
ec95faa57f | |||
488902f436 | |||
0db9a5e2a7 | |||
1950839f80 | |||
0ddb49c11d | |||
5f6cf7b0df | |||
295acdb36b | |||
d68a58f7e0 | |||
4a2d903050 | |||
7aa3762684 | |||
74cfab0ec0 | |||
1ebf566bb7 | |||
784113012e | |||
0260e06222 | |||
266470a755 | |||
d851b01363 | |||
f41df68458 | |||
ac5c579874 | |||
204fb8c0e0 | |||
c605cd7a17 | |||
a5db0f3b71 | |||
62483583eb | |||
6c63773641 | |||
e27e315784 | |||
5dc82742bf | |||
bd4d30f481 | |||
b8322a8311 | |||
2d6c6a6b38 | |||
f4e747a85e | |||
9ccdbcede8 | |||
2e04c686f4 | |||
508c2ebd87 | |||
892963aa0e | |||
34864771b3 | |||
2f1eb7f1bc | |||
2506dfb25d | |||
6ec07490ff | |||
04c76a0d0d | |||
e5d701dea2 | |||
f22f826546 | |||
fc83d88670 | |||
47e4c3af67 | |||
d0cc125867 | |||
65be53ba18 | |||
1e302e9f5e | |||
9e174c40ed | |||
453311272e |
@ -20,13 +20,15 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import TimeContext, { TIME_CONTEXT_EVENTS } from "./TimeContext";
|
import TimeContext from "./TimeContext";
|
||||||
|
import { TIME_CONTEXT_EVENTS } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The IndependentTimeContext handles getting and setting time of the openmct application in general.
|
* The IndependentTimeContext handles getting and setting time of the openmct application in general.
|
||||||
* Views will use the GlobalTimeContext unless they specify an alternate/independent time context here.
|
* Views will use the GlobalTimeContext unless they specify an alternate/independent time context here.
|
||||||
*/
|
*/
|
||||||
class IndependentTimeContext extends TimeContext {
|
class IndependentTimeContext extends TimeContext {
|
||||||
|
|
||||||
constructor(openmct, globalTimeContext, objectPath) {
|
constructor(openmct, globalTimeContext, objectPath) {
|
||||||
super();
|
super();
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
@ -46,7 +48,7 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext);
|
this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds(newBounds) {
|
bounds() {
|
||||||
if (this.upstreamTimeContext) {
|
if (this.upstreamTimeContext) {
|
||||||
return this.upstreamTimeContext.bounds(...arguments);
|
return this.upstreamTimeContext.bounds(...arguments);
|
||||||
} else {
|
} else {
|
||||||
@ -54,7 +56,23 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tick(timestamp) {
|
getBounds() {
|
||||||
|
if (this.upstreamTimeContext) {
|
||||||
|
return this.upstreamTimeContext.getBounds();
|
||||||
|
} else {
|
||||||
|
return super.getBounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setBounds() {
|
||||||
|
if (this.upstreamTimeContext) {
|
||||||
|
return this.upstreamTimeContext.setBounds(...arguments);
|
||||||
|
} else {
|
||||||
|
return super.setBounds(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
if (this.upstreamTimeContext) {
|
if (this.upstreamTimeContext) {
|
||||||
return this.upstreamTimeContext.tick(...arguments);
|
return this.upstreamTimeContext.tick(...arguments);
|
||||||
} else {
|
} else {
|
||||||
@ -62,7 +80,7 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clockOffsets(offsets) {
|
clockOffsets() {
|
||||||
if (this.upstreamTimeContext) {
|
if (this.upstreamTimeContext) {
|
||||||
return this.upstreamTimeContext.clockOffsets(...arguments);
|
return this.upstreamTimeContext.clockOffsets(...arguments);
|
||||||
} else {
|
} else {
|
||||||
@ -70,11 +88,27 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getClockOffsets() {
|
||||||
|
if (this.upstreamTimeContext) {
|
||||||
|
return this.upstreamTimeContext.getClockOffsets();
|
||||||
|
} else {
|
||||||
|
return super.getClockOffsets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setClockOffsets() {
|
||||||
|
if (this.upstreamTimeContext) {
|
||||||
|
return this.upstreamTimeContext.setClockOffsets(...arguments);
|
||||||
|
} else {
|
||||||
|
return super.setClockOffsets(...arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stopClock() {
|
stopClock() {
|
||||||
if (this.upstreamTimeContext) {
|
if (this.upstreamTimeContext) {
|
||||||
this.upstreamTimeContext.stopClock();
|
// this.upstreamTimeContext.stopClock();
|
||||||
} else {
|
} else {
|
||||||
super.stopClock();
|
// super.stopClock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +120,16 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
return this.globalTimeContext.timeSystem(...arguments);
|
return this.globalTimeContext.timeSystem(...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time system of the TimeAPI.
|
||||||
|
* @returns {TimeSystem} The currently applied time system
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method getTimeSystem
|
||||||
|
*/
|
||||||
|
getTimeSystem() {
|
||||||
|
return this.globalTimeContext.getTimeSystem();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the active clock. Tick source will be immediately subscribed to
|
* Set the active clock. Tick source will be immediately subscribed to
|
||||||
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
||||||
@ -146,6 +190,121 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
return this.activeClock;
|
return this.activeClock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active clock.
|
||||||
|
* @return {Clock} the currently active clock;
|
||||||
|
*/
|
||||||
|
getClock() {
|
||||||
|
if (this.upstreamTimeContext) {
|
||||||
|
return this.upstreamTimeContext.getClock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.activeClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active clock. Tick source will be immediately subscribed to
|
||||||
|
* and the currently ticking will begin.
|
||||||
|
* Offsets from 'now', if provided, will be used to set realtime mode offsets
|
||||||
|
*
|
||||||
|
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
||||||
|
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
||||||
|
* the start and end bounds when in realtime mode.
|
||||||
|
* This maintains a sliding time window of a fixed width that automatically updates.
|
||||||
|
* @fires module:openmct.TimeAPI~clock
|
||||||
|
* @return {Clock} the currently active clock;
|
||||||
|
*/
|
||||||
|
setClock(keyOrClock, offsets) {
|
||||||
|
if (this.upstreamTimeContext) {
|
||||||
|
return this.upstreamTimeContext.setClock(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
let clock;
|
||||||
|
|
||||||
|
if (typeof keyOrClock === 'string') {
|
||||||
|
clock = this.globalTimeContext.clocks.get(keyOrClock);
|
||||||
|
if (clock === undefined) {
|
||||||
|
throw `Unknown clock ${keyOrClock}. Has it been registered with 'addClock'?`;
|
||||||
|
}
|
||||||
|
} else if (typeof keyOrClock === 'object') {
|
||||||
|
clock = keyOrClock;
|
||||||
|
if (!this.globalTimeContext.clocks.has(clock.key)) {
|
||||||
|
throw `Unknown clock ${keyOrClock.key}. Has it been registered with 'addClock'?`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.setMode(REALTIME_MODE_KEY);
|
||||||
|
|
||||||
|
const previousClock = this.activeClock;
|
||||||
|
if (previousClock) {
|
||||||
|
previousClock.off('tick', this.tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeClock = clock;
|
||||||
|
this.activeClock.on('tick', this.tick);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active clock has changed.
|
||||||
|
* @event clock
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {Clock} clock The newly activated clock, or undefined
|
||||||
|
* if the system is no longer following a clock source
|
||||||
|
*/
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
||||||
|
|
||||||
|
if (offsets !== undefined) {
|
||||||
|
this.setClockOffsets(offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.activeClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current mode.
|
||||||
|
* @return {Mode} the current mode;
|
||||||
|
*/
|
||||||
|
getMode() {
|
||||||
|
if (this.upstreamTimeContext) {
|
||||||
|
return this.upstreamTimeContext.getMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the mode to either fixed or realtime.
|
||||||
|
*
|
||||||
|
* @param {Mode} mode The mode to activate
|
||||||
|
* @fires module:openmct.TimeAPI~clock
|
||||||
|
* @return {Mode} the currently active mode;
|
||||||
|
*/
|
||||||
|
setMode(mode) {
|
||||||
|
if (!mode || mode === this.mode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.upstreamTimeContext) {
|
||||||
|
return this.upstreamTimeContext.setMode(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mode = mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
||||||
|
* @event clock
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {Clock} clock The newly activated clock, or undefined
|
||||||
|
* if the system is no longer following a clock source
|
||||||
|
*/
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode));
|
||||||
|
|
||||||
|
return this.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
#copy(object) {
|
||||||
|
return JSON.parse(JSON.stringify(object));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Causes this time context to follow another time context (either the global context, or another upstream time context)
|
* Causes this time context to follow another time context (either the global context, or another upstream time context)
|
||||||
* This allows views to have their own time context which points to the appropriate upstream context as necessary, achieving nesting.
|
* This allows views to have their own time context which points to the appropriate upstream context as necessary, achieving nesting.
|
||||||
@ -153,7 +312,7 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
followTimeContext() {
|
followTimeContext() {
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
if (this.upstreamTimeContext) {
|
if (this.upstreamTimeContext) {
|
||||||
TIME_CONTEXT_EVENTS.forEach((eventName) => {
|
Object.values(TIME_CONTEXT_EVENTS).forEach((eventName) => {
|
||||||
const thisTimeContext = this;
|
const thisTimeContext = this;
|
||||||
this.upstreamTimeContext.on(eventName, passthrough);
|
this.upstreamTimeContext.on(eventName, passthrough);
|
||||||
this.unlisteners.push(() => {
|
this.unlisteners.push(() => {
|
||||||
@ -187,6 +346,7 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
*/
|
*/
|
||||||
refreshContext(viewKey) {
|
refreshContext(viewKey) {
|
||||||
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
|
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
|
||||||
|
|
||||||
if (viewKey && key === viewKey) {
|
if (viewKey && key === viewKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -199,6 +359,7 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
|
|
||||||
// Emit bounds so that views that are changing context get the upstream bounds
|
// Emit bounds so that views that are changing context get the upstream bounds
|
||||||
this.emit('bounds', this.bounds());
|
this.emit('bounds', this.bounds());
|
||||||
|
this.emit('boundsChanged', this.getBounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
hasOwnContext() {
|
hasOwnContext() {
|
||||||
@ -236,6 +397,7 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
*/
|
*/
|
||||||
removeIndependentContext(viewKey) {
|
removeIndependentContext(viewKey) {
|
||||||
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
|
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
|
||||||
|
|
||||||
if (viewKey && key === viewKey) {
|
if (viewKey && key === viewKey) {
|
||||||
//this is necessary as the upstream context gets reassigned after this
|
//this is necessary as the upstream context gets reassigned after this
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
@ -261,7 +423,8 @@ class IndependentTimeContext extends TimeContext {
|
|||||||
this.followTimeContext();
|
this.followTimeContext();
|
||||||
|
|
||||||
// Emit bounds so that views that are changing context get the upstream bounds
|
// Emit bounds so that views that are changing context get the upstream bounds
|
||||||
this.emit('bounds', this.bounds());
|
this.emit('bounds', this.getBounds());
|
||||||
|
this.emit('boundsChanged', this.getBounds());
|
||||||
// now that the view's context is set, tell others to check theirs in case they were following this view's context.
|
// 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);
|
this.globalTimeContext.emit('refreshContext', viewKey);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
import GlobalTimeContext from "./GlobalTimeContext";
|
import GlobalTimeContext from "./GlobalTimeContext";
|
||||||
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
|
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
|
||||||
|
import {FIXED_MODE_KEY, REALTIME_MODE_KEY} from "@/api/time/constants";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The public API for setting and querying the temporal state of the
|
* The public API for setting and querying the temporal state of the
|
||||||
@ -134,14 +135,27 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
*/
|
*/
|
||||||
addIndependentContext(key, value, clockKey) {
|
addIndependentContext(key, value, clockKey) {
|
||||||
let timeContext = this.getIndependentContext(key);
|
let timeContext = this.getIndependentContext(key);
|
||||||
|
let upstreamClock;
|
||||||
|
if (timeContext.upstreamTimeContext) {
|
||||||
|
upstreamClock = timeContext.upstreamTimeContext.getClock();
|
||||||
|
}
|
||||||
|
|
||||||
//stop following upstream time context since the view has it's own
|
//stop following upstream time context since the view has it's own
|
||||||
timeContext.resetContext();
|
timeContext.resetContext();
|
||||||
|
|
||||||
if (clockKey) {
|
if (clockKey) {
|
||||||
timeContext.clock(clockKey, value);
|
timeContext.setMode(REALTIME_MODE_KEY);
|
||||||
|
timeContext.setClock(clockKey, value);
|
||||||
} else {
|
} else {
|
||||||
|
timeContext.setMode(FIXED_MODE_KEY);
|
||||||
|
//TODO: Should the clock be stopped here?
|
||||||
timeContext.stopClock();
|
timeContext.stopClock();
|
||||||
timeContext.bounds(value);
|
//upstream clock was active, but now we don't have one
|
||||||
|
if (upstreamClock) {
|
||||||
|
timeContext.emit('clockChanged', timeContext.activeClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeContext.setBounds(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
|
// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
|
||||||
@ -179,12 +193,14 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
const viewKey = objectPath.length && this.openmct.objects.makeKeyString(objectPath[0].identifier);
|
const viewKey = objectPath.length && this.openmct.objects.makeKeyString(objectPath[0].identifier);
|
||||||
|
|
||||||
if (!viewKey) {
|
if (!viewKey) {
|
||||||
// Return the global time context
|
// Return the global time contex
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewTimeContext = this.getIndependentContext(viewKey);
|
let viewTimeContext = this.getIndependentContext(viewKey);
|
||||||
|
|
||||||
if (!viewTimeContext) {
|
if (!viewTimeContext) {
|
||||||
|
console.log('no view context for viewKey', viewKey, this.openmct.objects.getRelativePath(objectPath));
|
||||||
// If the context doesn't exist yet, create it.
|
// If the context doesn't exist yet, create it.
|
||||||
viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
|
viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
|
||||||
this.independentContexts.set(viewKey, viewTimeContext);
|
this.independentContexts.set(viewKey, viewTimeContext);
|
||||||
@ -193,6 +209,8 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
const currentPath = this.openmct.objects.getRelativePath(viewTimeContext.objectPath);
|
const currentPath = this.openmct.objects.getRelativePath(viewTimeContext.objectPath);
|
||||||
const newPath = this.openmct.objects.getRelativePath(objectPath);
|
const newPath = this.openmct.objects.getRelativePath(objectPath);
|
||||||
|
|
||||||
|
console.log('view context exists, paths match?', !(currentPath !== newPath), currentPath, newPath, 'view key: ', viewKey);
|
||||||
|
|
||||||
if (currentPath !== newPath) {
|
if (currentPath !== newPath) {
|
||||||
// If the path has changed, update the context.
|
// If the path has changed, update the context.
|
||||||
this.independentContexts.delete(viewKey);
|
this.independentContexts.delete(viewKey);
|
||||||
|
@ -21,13 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import EventEmitter from 'EventEmitter';
|
import EventEmitter from 'EventEmitter';
|
||||||
|
import { TIME_CONTEXT_EVENTS, MODES, REALTIME_MODE_KEY } from './constants';
|
||||||
export const TIME_CONTEXT_EVENTS = [
|
|
||||||
'bounds',
|
|
||||||
'clock',
|
|
||||||
'timeSystem',
|
|
||||||
'clockOffsets'
|
|
||||||
];
|
|
||||||
|
|
||||||
class TimeContext extends EventEmitter {
|
class TimeContext extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -47,6 +41,7 @@ class TimeContext extends EventEmitter {
|
|||||||
|
|
||||||
this.activeClock = undefined;
|
this.activeClock = undefined;
|
||||||
this.offsets = undefined;
|
this.offsets = undefined;
|
||||||
|
this.mode = undefined;
|
||||||
|
|
||||||
this.tick = this.tick.bind(this);
|
this.tick = this.tick.bind(this);
|
||||||
}
|
}
|
||||||
@ -61,6 +56,8 @@ class TimeContext extends EventEmitter {
|
|||||||
* @method timeSystem
|
* @method timeSystem
|
||||||
*/
|
*/
|
||||||
timeSystem(timeSystemOrKey, bounds) {
|
timeSystem(timeSystemOrKey, bounds) {
|
||||||
|
this.#warnMethodDeprecated('"timeSystem"', '"getTimeSystem" and "setTimeSystem"');
|
||||||
|
|
||||||
if (arguments.length >= 1) {
|
if (arguments.length >= 1) {
|
||||||
if (arguments.length === 1 && !this.activeClock) {
|
if (arguments.length === 1 && !this.activeClock) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -91,7 +88,7 @@ class TimeContext extends EventEmitter {
|
|||||||
throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
|
throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.system = timeSystem;
|
this.system = this.#copy(timeSystem);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The time system used by the time
|
* The time system used by the time
|
||||||
@ -102,7 +99,7 @@ class TimeContext extends EventEmitter {
|
|||||||
* @property {TimeSystem} The value of the currently applied
|
* @property {TimeSystem} The value of the currently applied
|
||||||
* Time System
|
* Time System
|
||||||
* */
|
* */
|
||||||
this.emit('timeSystem', this.system);
|
this.emit('timeSystem', this.#copy(this.system));
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
this.bounds(bounds);
|
this.bounds(bounds);
|
||||||
}
|
}
|
||||||
@ -163,6 +160,8 @@ class TimeContext extends EventEmitter {
|
|||||||
* @method bounds
|
* @method bounds
|
||||||
*/
|
*/
|
||||||
bounds(newBounds) {
|
bounds(newBounds) {
|
||||||
|
this.#warnMethodDeprecated('"bounds"', '"getBounds" and "setBounds"');
|
||||||
|
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
const validationResult = this.validateBounds(newBounds);
|
const validationResult = this.validateBounds(newBounds);
|
||||||
if (validationResult.valid !== true) {
|
if (validationResult.valid !== true) {
|
||||||
@ -170,7 +169,7 @@ class TimeContext extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Create a copy to avoid direct mutation of conductor bounds
|
//Create a copy to avoid direct mutation of conductor bounds
|
||||||
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
|
this.boundsVal = this.#copy(newBounds);
|
||||||
/**
|
/**
|
||||||
* The start time, end time, or both have been updated.
|
* The start time, end time, or both have been updated.
|
||||||
* @event bounds
|
* @event bounds
|
||||||
@ -183,7 +182,7 @@ class TimeContext extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
return this.#copy(this.boundsVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -247,6 +246,8 @@ class TimeContext extends EventEmitter {
|
|||||||
* @returns {ClockOffsets}
|
* @returns {ClockOffsets}
|
||||||
*/
|
*/
|
||||||
clockOffsets(offsets) {
|
clockOffsets(offsets) {
|
||||||
|
this.#warnMethodDeprecated('"clockOffsets"', '"getClockOffsets" and "setClockOffsets"');
|
||||||
|
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
|
|
||||||
const validationResult = this.validateOffsets(offsets);
|
const validationResult = this.validateOffsets(offsets);
|
||||||
@ -278,11 +279,14 @@ class TimeContext extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the currently active clock from ticking, and unset it. This will
|
* Stop following the currently active clock. This will
|
||||||
* revert all views to showing a static time frame defined by the current
|
* revert all views to showing a static time frame defined by the current
|
||||||
* bounds.
|
* bounds.
|
||||||
*/
|
*/
|
||||||
stopClock() {
|
stopClock() {
|
||||||
|
console.log('stop clock');
|
||||||
|
this.#warnMethodDeprecated('"stopClock"');
|
||||||
|
|
||||||
if (this.activeClock) {
|
if (this.activeClock) {
|
||||||
this.clock(undefined, undefined);
|
this.clock(undefined, undefined);
|
||||||
}
|
}
|
||||||
@ -301,6 +305,8 @@ class TimeContext extends EventEmitter {
|
|||||||
* @return {Clock} the currently active clock;
|
* @return {Clock} the currently active clock;
|
||||||
*/
|
*/
|
||||||
clock(keyOrClock, offsets) {
|
clock(keyOrClock, offsets) {
|
||||||
|
this.#warnMethodDeprecated('"clock"', '"getClock" and "setClock"');
|
||||||
|
|
||||||
if (arguments.length === 2) {
|
if (arguments.length === 2) {
|
||||||
let clock;
|
let clock;
|
||||||
|
|
||||||
@ -354,25 +360,300 @@ class TimeContext extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBounds = {
|
if (this.mode === REALTIME_MODE_KEY) {
|
||||||
start: timestamp + this.offsets.start,
|
const newBounds = {
|
||||||
end: timestamp + this.offsets.end
|
start: timestamp + this.offsets.start,
|
||||||
};
|
end: timestamp + this.offsets.end
|
||||||
|
};
|
||||||
|
|
||||||
this.boundsVal = newBounds;
|
this.boundsVal = newBounds;
|
||||||
this.emit('bounds', this.boundsVal, true);
|
// "bounds" will be deprecated in a future release
|
||||||
|
this.emit('bounds', this.boundsVal, true);
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if this time context is in real-time mode or not.
|
* Get the time system of the TimeAPI.
|
||||||
|
* @returns {TimeSystem} The currently applied time system
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method getTimeSystem
|
||||||
|
*/
|
||||||
|
getTimeSystem() {
|
||||||
|
return this.system;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time system of the TimeAPI.
|
||||||
|
* @param {TimeSystem | string} timeSystemOrKey
|
||||||
|
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||||
|
* @fires module:openmct.TimeAPI~timeSystem
|
||||||
|
* @returns {TimeSystem} The currently applied time system
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method setTimeSystem
|
||||||
|
*/
|
||||||
|
setTimeSystem(timeSystemOrKey, bounds) {
|
||||||
|
if (!this.isRealTime() && !bounds) {
|
||||||
|
throw new Error(
|
||||||
|
'Must specify bounds when changing time system without an active clock.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeSystemOrKey === undefined) {
|
||||||
|
throw 'Please provide a time system';
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeSystem;
|
||||||
|
|
||||||
|
if (typeof timeSystemOrKey === 'string') {
|
||||||
|
timeSystem = this.timeSystems.get(timeSystemOrKey);
|
||||||
|
|
||||||
|
if (timeSystem === undefined) {
|
||||||
|
throw `Unknown time system ${timeSystemOrKey}. Has it been registered with 'addTimeSystem'?`;
|
||||||
|
}
|
||||||
|
} else if (typeof timeSystemOrKey === 'object') {
|
||||||
|
timeSystem = timeSystemOrKey;
|
||||||
|
|
||||||
|
if (!this.timeSystems.has(timeSystem.key)) {
|
||||||
|
throw `Unknown time system ${timeSystemOrKey.key}. Has it been registered with 'addTimeSystem'?`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw 'Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.system = this.#copy(timeSystem);
|
||||||
|
/**
|
||||||
|
* The time system used by the time
|
||||||
|
* conductor has changed. A change in Time System will always be
|
||||||
|
* followed by a bounds event specifying new query bounds.
|
||||||
|
*
|
||||||
|
* @event module:openmct.TimeAPI~timeSystem
|
||||||
|
* @property {TimeSystem} The value of the currently applied
|
||||||
|
* Time System
|
||||||
|
* */
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.timeSystemChanged, this.#copy(this.system));
|
||||||
|
|
||||||
|
if (bounds) {
|
||||||
|
this.setBounds(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.system;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the start and end time of the time conductor. Basic validation
|
||||||
|
* of bounds is performed.
|
||||||
|
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method bounds
|
||||||
|
*/
|
||||||
|
getBounds() {
|
||||||
|
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||||
|
return this.#copy(this.boundsVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the start and end time of the time conductor. Basic validation
|
||||||
|
* of bounds is performed.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||||
|
* @throws {Error} Validation error
|
||||||
|
* @fires module:openmct.TimeAPI~bounds
|
||||||
|
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method bounds
|
||||||
|
*/
|
||||||
|
setBounds(newBounds) {
|
||||||
|
const validationResult = this.validateBounds(newBounds);
|
||||||
|
if (validationResult.valid !== true) {
|
||||||
|
throw new Error(validationResult.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a copy to avoid direct mutation of conductor bounds
|
||||||
|
this.boundsVal = this.#copy(newBounds);
|
||||||
|
/**
|
||||||
|
* The start time, end time, or both have been updated.
|
||||||
|
* @event bounds
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {TimeConductorBounds} bounds The newly updated bounds
|
||||||
|
* @property {boolean} [tick] `true` if the bounds update was due to
|
||||||
|
* a "tick" event (i.e. was an automatic update), false otherwise.
|
||||||
|
*/
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, false);
|
||||||
|
|
||||||
|
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||||
|
return this.#copy(this.boundsVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active clock.
|
||||||
|
* @return {Clock} the currently active clock;
|
||||||
|
*/
|
||||||
|
getClock() {
|
||||||
|
return this.activeClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active clock. Tick source will be immediately subscribed to
|
||||||
|
* and the currently ticking will begin.
|
||||||
|
* Offsets from 'now', if provided, will be used to set realtime mode offsets
|
||||||
|
*
|
||||||
|
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
||||||
|
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
||||||
|
* the start and end bounds when in realtime mode.
|
||||||
|
* This maintains a sliding time window of a fixed width that automatically updates.
|
||||||
|
* @fires module:openmct.TimeAPI~clock
|
||||||
|
* @return {Clock} the currently active clock;
|
||||||
|
*/
|
||||||
|
setClock(keyOrClock, offsets) {
|
||||||
|
let clock;
|
||||||
|
|
||||||
|
if (typeof keyOrClock === 'string') {
|
||||||
|
clock = this.clocks.get(keyOrClock);
|
||||||
|
if (clock === undefined) {
|
||||||
|
throw `Unknown clock ${keyOrClock}. Has it been registered with 'addClock'?`;
|
||||||
|
}
|
||||||
|
} else if (typeof keyOrClock === 'object') {
|
||||||
|
clock = keyOrClock;
|
||||||
|
if (!this.clocks.has(clock.key)) {
|
||||||
|
throw `Unknown clock ${keyOrClock.key}. Has it been registered with 'addClock'?`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.setMode(REALTIME_MODE_KEY);
|
||||||
|
|
||||||
|
const previousClock = this.activeClock;
|
||||||
|
if (previousClock) {
|
||||||
|
previousClock.off('tick', this.tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeClock = clock;
|
||||||
|
this.activeClock.on('tick', this.tick);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active clock has changed.
|
||||||
|
* @event clock
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {Clock} clock The newly activated clock, or undefined
|
||||||
|
* if the system is no longer following a clock source
|
||||||
|
*/
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
||||||
|
|
||||||
|
if (offsets !== undefined) {
|
||||||
|
this.setClockOffsets(offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.activeClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current mode.
|
||||||
|
* @return {Mode} the current mode;
|
||||||
|
*/
|
||||||
|
getMode() {
|
||||||
|
return this.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the mode to either fixed or realtime.
|
||||||
|
*
|
||||||
|
* @param {Mode} mode The mode to activate
|
||||||
|
* @fires module:openmct.TimeAPI~clock
|
||||||
|
* @return {Mode} the currently active mode;
|
||||||
|
*/
|
||||||
|
setMode(mode) {
|
||||||
|
if (!mode || mode === this.mode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mode = mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
||||||
|
* @event clock
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {Clock} clock The newly activated clock, or undefined
|
||||||
|
* if the system is no longer following a clock source
|
||||||
|
*/
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode));
|
||||||
|
|
||||||
|
return this.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this time context is in realtime mode or not.
|
||||||
* @returns {boolean} true if this context is in real-time mode, false if not
|
* @returns {boolean} true if this context is in real-time mode, false if not
|
||||||
*/
|
*/
|
||||||
isRealTime() {
|
isRealTime() {
|
||||||
if (this.clock()) {
|
return this.mode === MODES.realtime;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this time context is in fixed mode or not.
|
||||||
|
* @returns {boolean} true if this context is in fixed mode, false if not
|
||||||
|
*/
|
||||||
|
isFixed() {
|
||||||
|
return this.mode === MODES.fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently applied clock offsets.
|
||||||
|
* @returns {ClockOffsets}
|
||||||
|
*/
|
||||||
|
getClockOffsets() {
|
||||||
|
return this.offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the currently applied clock offsets. If no parameter is provided,
|
||||||
|
* the current value will be returned. If provided, the new value will be
|
||||||
|
* used as the new clock offsets.
|
||||||
|
* @param {ClockOffsets} offsets
|
||||||
|
* @returns {ClockOffsets}
|
||||||
|
*/
|
||||||
|
setClockOffsets(offsets) {
|
||||||
|
const validationResult = this.validateOffsets(offsets);
|
||||||
|
if (validationResult.valid !== true) {
|
||||||
|
throw new Error(validationResult.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
this.offsets = this.#copy(offsets);
|
||||||
|
|
||||||
|
const currentValue = this.activeClock.currentValue();
|
||||||
|
const newBounds = {
|
||||||
|
start: currentValue + offsets.start,
|
||||||
|
end: currentValue + offsets.end
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setBounds(newBounds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event that is triggered when clock offsets change.
|
||||||
|
* @event clockOffsets
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {ClockOffsets} clockOffsets The newly activated clock
|
||||||
|
* offsets.
|
||||||
|
*/
|
||||||
|
this.emit(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.#copy(offsets));
|
||||||
|
|
||||||
|
return this.offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
#warnMethodDeprecated(method, newMethod) {
|
||||||
|
let message = `[DEPRECATION WARNING]: The ${method} API method is deprecated and will be removed in a future version of Open MCT.`;
|
||||||
|
|
||||||
|
if (newMethod) {
|
||||||
|
message += ` Please use the ${newMethod} API method(s) instead.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add docs and point to them in warning.
|
||||||
|
// For more information and migration instructions, visit [link to documentation or migration guide].
|
||||||
|
|
||||||
|
console.warn(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#copy(object) {
|
||||||
|
return JSON.parse(JSON.stringify(object));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
src/api/time/constants.js
Normal file
22
src/api/time/constants.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export const TIME_CONTEXT_EVENTS = {
|
||||||
|
//old API events - to be deprecated
|
||||||
|
bounds: 'bounds',
|
||||||
|
clock: 'clock',
|
||||||
|
timeSystem: 'timeSystem',
|
||||||
|
clockOffsets: 'clockOffsets',
|
||||||
|
//new API events
|
||||||
|
tick: 'tick',
|
||||||
|
modeChanged: 'modeChanged',
|
||||||
|
boundsChanged: 'boundsChanged',
|
||||||
|
clockChanged: 'clockChanged',
|
||||||
|
timeSystemChanged: 'timeSystemChanged',
|
||||||
|
clockOffsetsChanged: 'clockOffsetsChanged'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const REALTIME_MODE_KEY = 'realtime';
|
||||||
|
export const FIXED_MODE_KEY = 'fixed';
|
||||||
|
|
||||||
|
export const MODES = {
|
||||||
|
[FIXED_MODE_KEY]: FIXED_MODE_KEY,
|
||||||
|
[REALTIME_MODE_KEY]: REALTIME_MODE_KEY
|
||||||
|
};
|
@ -20,14 +20,15 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
const TIME_EVENTS = ['timeSystemChanged', 'modeChanged', 'clockChanged', 'clockOffsetsChanged'];
|
||||||
const SEARCH_MODE = 'tc.mode';
|
const SEARCH_MODE = 'tc.mode';
|
||||||
const SEARCH_TIME_SYSTEM = 'tc.timeSystem';
|
const SEARCH_TIME_SYSTEM = 'tc.timeSystem';
|
||||||
const SEARCH_START_BOUND = 'tc.startBound';
|
const SEARCH_START_BOUND = 'tc.startBound';
|
||||||
const SEARCH_END_BOUND = 'tc.endBound';
|
const SEARCH_END_BOUND = 'tc.endBound';
|
||||||
const SEARCH_START_DELTA = 'tc.startDelta';
|
const SEARCH_START_DELTA = 'tc.startDelta';
|
||||||
const SEARCH_END_DELTA = 'tc.endDelta';
|
const SEARCH_END_DELTA = 'tc.endDelta';
|
||||||
const MODE_FIXED = 'fixed';
|
|
||||||
|
import { FIXED_MODE_KEY } from "../../api/time/constants";
|
||||||
|
|
||||||
export default class URLTimeSettingsSynchronizer {
|
export default class URLTimeSettingsSynchronizer {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
@ -67,7 +68,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTimeSettings() {
|
updateTimeSettings() {
|
||||||
let timeParameters = this.parseParametersFromUrl();
|
const timeParameters = this.parseParametersFromUrl();
|
||||||
|
|
||||||
if (this.areTimeParametersValid(timeParameters)) {
|
if (this.areTimeParametersValid(timeParameters)) {
|
||||||
this.setTimeApiFromUrl(timeParameters);
|
this.setTimeApiFromUrl(timeParameters);
|
||||||
@ -78,21 +79,18 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseParametersFromUrl() {
|
parseParametersFromUrl() {
|
||||||
let searchParams = this.openmct.router.getAllSearchParams();
|
const searchParams = this.openmct.router.getAllSearchParams();
|
||||||
|
const mode = searchParams.get(SEARCH_MODE);
|
||||||
let mode = searchParams.get(SEARCH_MODE);
|
const timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
||||||
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
const startBound = parseInt(searchParams.get(SEARCH_START_BOUND), 10);
|
||||||
|
const endBound = parseInt(searchParams.get(SEARCH_END_BOUND), 10);
|
||||||
let startBound = parseInt(searchParams.get(SEARCH_START_BOUND), 10);
|
const bounds = {
|
||||||
let endBound = parseInt(searchParams.get(SEARCH_END_BOUND), 10);
|
|
||||||
let bounds = {
|
|
||||||
start: startBound,
|
start: startBound,
|
||||||
end: endBound
|
end: endBound
|
||||||
};
|
};
|
||||||
|
const startOffset = parseInt(searchParams.get(SEARCH_START_DELTA), 10);
|
||||||
let startOffset = parseInt(searchParams.get(SEARCH_START_DELTA), 10);
|
const endOffset = parseInt(searchParams.get(SEARCH_END_DELTA), 10);
|
||||||
let endOffset = parseInt(searchParams.get(SEARCH_END_DELTA), 10);
|
const clockOffsets = {
|
||||||
let clockOffsets = {
|
|
||||||
start: 0 - startOffset,
|
start: 0 - startOffset,
|
||||||
end: endOffset
|
end: endOffset
|
||||||
};
|
};
|
||||||
@ -106,30 +104,30 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTimeApiFromUrl(timeParameters) {
|
setTimeApiFromUrl(timeParameters) {
|
||||||
if (timeParameters.mode === 'fixed') {
|
const timeSystem = this.openmct.time.getTimeSystem();
|
||||||
if (this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
|
|
||||||
this.openmct.time.timeSystem(
|
if (timeParameters.mode === FIXED_MODE_KEY) {
|
||||||
timeParameters.timeSystem,
|
// should update timesystem
|
||||||
timeParameters.bounds
|
if (timeSystem.key !== timeParameters.timeSystem) {
|
||||||
);
|
this.openmct.time.setTimeSystem(timeParameters.timeSystem, timeParameters.bounds);
|
||||||
} else if (!this.areStartAndEndEqual(this.openmct.time.bounds(), timeParameters.bounds)) {
|
} else if (!this.areStartAndEndEqual(this.openmct.time.getBounds(), timeParameters.bounds)) {
|
||||||
this.openmct.time.bounds(timeParameters.bounds);
|
this.openmct.time.setBounds(timeParameters.bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.openmct.time.clock()) {
|
this.openmct.time.setMode('fixed');
|
||||||
this.openmct.time.stopClock();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (!this.openmct.time.clock()
|
const clock = this.openmct.time.getClock();
|
||||||
|| this.openmct.time.clock().key !== timeParameters.mode) {
|
console.log('setting as realtime', clock?.key, timeParameters.mode);
|
||||||
this.openmct.time.clock(timeParameters.mode, timeParameters.clockOffsets);
|
if (clock?.key !== timeParameters.mode) {
|
||||||
} else if (!this.areStartAndEndEqual(this.openmct.time.clockOffsets(), timeParameters.clockOffsets)) {
|
this.openmct.time.setClock(timeParameters.mode, timeParameters.clockOffsets);
|
||||||
this.openmct.time.clockOffsets(timeParameters.clockOffsets);
|
} else if (!this.areStartAndEndEqual(this.openmct.time.getClockOffsets(), timeParameters.clockOffsets)) {
|
||||||
|
this.openmct.time.setClockOffsets(timeParameters.clockOffsets);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.openmct.time.timeSystem()
|
this.openmct.time.setMode('realtime');
|
||||||
|| this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
|
|
||||||
this.openmct.time.timeSystem(timeParameters.timeSystem);
|
if (timeSystem?.key !== timeParameters.timeSystem) {
|
||||||
|
this.openmct.time.setTimeSystem(timeParameters.timeSystem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,13 +139,14 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setUrlFromTimeApi() {
|
setUrlFromTimeApi() {
|
||||||
let searchParams = this.openmct.router.getAllSearchParams();
|
const searchParams = this.openmct.router.getAllSearchParams();
|
||||||
let clock = this.openmct.time.clock();
|
const clock = this.openmct.time.getClock();
|
||||||
let bounds = this.openmct.time.bounds();
|
const mode = this.openmct.time.getMode();
|
||||||
let clockOffsets = this.openmct.time.clockOffsets();
|
const bounds = this.openmct.time.getBounds();
|
||||||
|
const clockOffsets = this.openmct.time.getClockOffsets();
|
||||||
|
|
||||||
if (clock === undefined) {
|
if (mode === FIXED_MODE_KEY) {
|
||||||
searchParams.set(SEARCH_MODE, MODE_FIXED);
|
searchParams.set(SEARCH_MODE, FIXED_MODE_KEY);
|
||||||
searchParams.set(SEARCH_START_BOUND, bounds.start);
|
searchParams.set(SEARCH_START_BOUND, bounds.start);
|
||||||
searchParams.set(SEARCH_END_BOUND, bounds.end);
|
searchParams.set(SEARCH_END_BOUND, bounds.end);
|
||||||
|
|
||||||
@ -168,8 +167,9 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
searchParams.delete(SEARCH_END_BOUND);
|
searchParams.delete(SEARCH_END_BOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.getTimeSystem().key);
|
||||||
this.openmct.router.setAllSearchParams(searchParams);
|
// this.openmct.router.setAllSearchParams(searchParams);
|
||||||
|
this.openmct.router.updateParams(searchParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
areTimeParametersValid(timeParameters) {
|
areTimeParametersValid(timeParameters) {
|
||||||
@ -178,7 +178,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
if (this.isModeValid(timeParameters.mode)
|
if (this.isModeValid(timeParameters.mode)
|
||||||
&& this.isTimeSystemValid(timeParameters.timeSystem)) {
|
&& this.isTimeSystemValid(timeParameters.timeSystem)) {
|
||||||
|
|
||||||
if (timeParameters.mode === 'fixed') {
|
if (timeParameters.mode === FIXED_MODE_KEY) {
|
||||||
isValid = this.areStartAndEndValid(timeParameters.bounds);
|
isValid = this.areStartAndEndValid(timeParameters.bounds);
|
||||||
} else {
|
} else {
|
||||||
isValid = this.areStartAndEndValid(timeParameters.clockOffsets);
|
isValid = this.areStartAndEndValid(timeParameters.clockOffsets);
|
||||||
@ -200,8 +200,9 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
|
|
||||||
isTimeSystemValid(timeSystem) {
|
isTimeSystemValid(timeSystem) {
|
||||||
let isValid = timeSystem !== undefined;
|
let isValid = timeSystem !== undefined;
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
let timeSystemObject = this.openmct.time.timeSystems.get(timeSystem);
|
const timeSystemObject = this.openmct.time.timeSystems.get(timeSystem);
|
||||||
isValid = timeSystemObject !== undefined;
|
isValid = timeSystemObject !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +218,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
if (mode.toLowerCase() === MODE_FIXED) {
|
if (mode.toLowerCase() === FIXED_MODE_KEY) {
|
||||||
isValid = true;
|
isValid = true;
|
||||||
} else {
|
} else {
|
||||||
isValid = this.openmct.time.clocks.get(mode) !== undefined;
|
isValid = this.openmct.time.clocks.get(mode) !== undefined;
|
||||||
@ -228,7 +229,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
areStartAndEndEqual(firstBounds, secondBounds) {
|
areStartAndEndEqual(firstBounds, secondBounds) {
|
||||||
return firstBounds.start === secondBounds.start
|
return firstBounds?.start === secondBounds.start
|
||||||
&& firstBounds.end === secondBounds.end;
|
&& firstBounds?.end === secondBounds.end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,11 +103,11 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
followTimeContext() {
|
followTimeContext() {
|
||||||
this.timeContext.on('bounds', this.refreshData);
|
this.timeContext.on('boundsChanged', this.refreshData);
|
||||||
},
|
},
|
||||||
stopFollowingTimeContext() {
|
stopFollowingTimeContext() {
|
||||||
if (this.timeContext) {
|
if (this.timeContext) {
|
||||||
this.timeContext.off('bounds', this.refreshData);
|
this.timeContext.off('boundsChanged', this.refreshData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addToComposition(telemetryObject) {
|
addToComposition(telemetryObject) {
|
||||||
|
@ -288,7 +288,7 @@ export default {
|
|||||||
seriesModels: [],
|
seriesModels: [],
|
||||||
legend: {},
|
legend: {},
|
||||||
pending: 0,
|
pending: 0,
|
||||||
isRealTime: this.openmct.time.clock() !== undefined,
|
isRealTime: this.openmct.time.isRealTime(),
|
||||||
loaded: false,
|
loaded: false,
|
||||||
isTimeOutOfSync: false,
|
isTimeOutOfSync: false,
|
||||||
isFrozenOnMouseDown: false,
|
isFrozenOnMouseDown: false,
|
||||||
@ -369,7 +369,7 @@ export default {
|
|||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.handleKeyDown);
|
||||||
document.addEventListener('keyup', this.handleKeyUp);
|
document.addEventListener('keyup', this.handleKeyUp);
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
this.updateRealTime = this.updateRealTime.bind(this);
|
this.updateMode = this.updateMode.bind(this);
|
||||||
this.updateDisplayBounds = this.updateDisplayBounds.bind(this);
|
this.updateDisplayBounds = this.updateDisplayBounds.bind(this);
|
||||||
this.setTimeContext = this.setTimeContext.bind(this);
|
this.setTimeContext = this.setTimeContext.bind(this);
|
||||||
|
|
||||||
@ -533,19 +533,20 @@ export default {
|
|||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
|
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
this.timeContext = this.openmct.time.getContextForView(this.path);
|
||||||
|
console.log('time context mctplot', this.timeContext, this.path);
|
||||||
this.followTimeContext();
|
this.followTimeContext();
|
||||||
|
|
||||||
},
|
},
|
||||||
followTimeContext() {
|
followTimeContext() {
|
||||||
this.updateDisplayBounds(this.timeContext.bounds());
|
this.updateDisplayBounds(this.timeContext.getBounds());
|
||||||
this.timeContext.on('clock', this.updateRealTime);
|
this.timeContext.on('modeChanged', this.updateMode);
|
||||||
this.timeContext.on('bounds', this.updateDisplayBounds);
|
this.timeContext.on('boundsChanged', this.updateDisplayBounds);
|
||||||
this.synchronized(true);
|
this.synchronized(true);
|
||||||
},
|
},
|
||||||
stopFollowingTimeContext() {
|
stopFollowingTimeContext() {
|
||||||
if (this.timeContext) {
|
if (this.timeContext) {
|
||||||
this.timeContext.off("clock", this.updateRealTime);
|
this.timeContext.off("clockChanged", this.updateMode);
|
||||||
this.timeContext.off("bounds", this.updateDisplayBounds);
|
this.timeContext.off("boundsChanged", this.updateDisplayBounds);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getConfig() {
|
getConfig() {
|
||||||
@ -753,8 +754,8 @@ export default {
|
|||||||
const displayRange = series.getDisplayRange(xKey);
|
const displayRange = series.getDisplayRange(xKey);
|
||||||
this.config.xAxis.set('range', displayRange);
|
this.config.xAxis.set('range', displayRange);
|
||||||
},
|
},
|
||||||
updateRealTime(clock) {
|
updateMode() {
|
||||||
this.isRealTime = clock !== undefined;
|
this.isRealTime = this.timeContext.isRealTime();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -815,13 +816,13 @@ export default {
|
|||||||
* displays can update accordingly.
|
* displays can update accordingly.
|
||||||
*/
|
*/
|
||||||
synchronized(value) {
|
synchronized(value) {
|
||||||
const isLocalClock = this.timeContext.clock();
|
const isRealTime = this.timeContext.isRealTime();
|
||||||
|
|
||||||
if (typeof value !== 'undefined') {
|
if (typeof value !== 'undefined') {
|
||||||
this._synchronized = value;
|
this._synchronized = value;
|
||||||
this.isTimeOutOfSync = value !== true;
|
this.isTimeOutOfSync = value !== true;
|
||||||
|
|
||||||
const isUnsynced = isLocalClock && !value;
|
const isUnsynced = isRealTime && !value;
|
||||||
this.setStatus(isUnsynced);
|
this.setStatus(isUnsynced);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,17 +86,17 @@ export default {
|
|||||||
this.xAxis = this.getXAxisFromConfig();
|
this.xAxis = this.getXAxisFromConfig();
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.setUpXAxisOptions();
|
this.setUpXAxisOptions();
|
||||||
this.openmct.time.on('timeSystem', this.syncXAxisToTimeSystem);
|
this.openmct.time.on('timeSystemChanged', this.syncXAxisToTimeSystem);
|
||||||
this.listenTo(this.xAxis, 'change', this.setUpXAxisOptions);
|
this.listenTo(this.xAxis, 'change', this.setUpXAxisOptions);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.openmct.time.off('timeSystem', this.syncXAxisToTimeSystem);
|
this.openmct.time.off('timeSystemChanged', this.syncXAxisToTimeSystem);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isEnabledXKeyToggle() {
|
isEnabledXKeyToggle() {
|
||||||
const isSinglePlot = this.xKeyOptions && this.xKeyOptions.length > 1 && this.seriesModel;
|
const isSinglePlot = this.xKeyOptions && this.xKeyOptions.length > 1 && this.seriesModel;
|
||||||
const isFrozen = this.xAxis.get('frozen');
|
const isFrozen = this.xAxis.get('frozen');
|
||||||
const inRealTimeMode = this.openmct.time.clock();
|
const inRealTimeMode = this.openmct.time.getClock();
|
||||||
|
|
||||||
return isSinglePlot && !isFrozen && !inRealTimeMode;
|
return isSinglePlot && !isFrozen && !inRealTimeMode;
|
||||||
},
|
},
|
||||||
|
@ -21,60 +21,71 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="c-conductor"
|
ref="timeConductorOptionsHolder"
|
||||||
|
class="c-compact-tc is-expanded"
|
||||||
:class="[
|
:class="[
|
||||||
{ 'is-zooming': isZooming },
|
{ 'is-zooming': isZooming },
|
||||||
{ 'is-panning': isPanning },
|
{ 'is-panning': isPanning },
|
||||||
{ 'alt-pressed': altPressed },
|
{ 'alt-pressed': altPressed },
|
||||||
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
|
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
|
||||||
]"
|
]"
|
||||||
|
style="overflow: visible"
|
||||||
>
|
>
|
||||||
<div class="c-conductor__time-bounds">
|
<ConductorModeIcon class="c-conductor__mode-icon" />
|
||||||
<conductor-inputs-fixed
|
<!-- TODO - NEED TO ADD MODE, CLOCK AND TIMESYSTEM VIEW ONLY INFORMATION HERE -->
|
||||||
v-if="isFixed"
|
<conductor-inputs-fixed
|
||||||
:input-bounds="viewBounds"
|
v-if="isFixed"
|
||||||
@updated="saveFixedOffsets"
|
:input-bounds="viewBounds"
|
||||||
/>
|
:read-only="true"
|
||||||
<conductor-inputs-realtime
|
/>
|
||||||
v-else
|
<conductor-inputs-realtime
|
||||||
:input-bounds="viewBounds"
|
v-else
|
||||||
@updated="saveClockOffsets"
|
:input-bounds="viewBounds"
|
||||||
/>
|
:read-only="true"
|
||||||
<ConductorModeIcon class="c-conductor__mode-icon" />
|
/>
|
||||||
<conductor-axis
|
<conductor-axis
|
||||||
class="c-conductor__ticks"
|
v-if="isFixed"
|
||||||
:view-bounds="viewBounds"
|
class="c-conductor__ticks"
|
||||||
:is-fixed="isFixed"
|
:view-bounds="viewBounds"
|
||||||
:alt-pressed="altPressed"
|
:is-fixed="isFixed"
|
||||||
@endPan="endPan"
|
:alt-pressed="altPressed"
|
||||||
@endZoom="endZoom"
|
@endPan="endPan"
|
||||||
@panAxis="pan"
|
@endZoom="endZoom"
|
||||||
@zoomAxis="zoom"
|
@panAxis="pan"
|
||||||
/>
|
@zoomAxis="zoom"
|
||||||
</div>
|
/>
|
||||||
<div class="c-conductor__controls">
|
<div
|
||||||
<ConductorMode class="c-conductor__mode-select" />
|
v-else
|
||||||
<ConductorTimeSystem class="c-conductor__time-system-select" />
|
class="u-flex-spreader"
|
||||||
<ConductorHistory
|
></div>
|
||||||
class="c-conductor__history-select"
|
<div class="c-not-button c-not-button--compact c-compact-tc__gear icon-gear"></div>
|
||||||
:offsets="openmct.time.clockOffsets()"
|
|
||||||
:bounds="bounds"
|
<conductor-pop-up
|
||||||
:time-system="timeSystem"
|
v-if="showConductorPopup"
|
||||||
:mode="timeMode"
|
ref="conductorPopup"
|
||||||
/>
|
:bottom="false"
|
||||||
</div>
|
:position-x="positionX"
|
||||||
|
:position-y="positionY"
|
||||||
|
:is-fixed="isFixed"
|
||||||
|
@popupLoaded="initializePopup"
|
||||||
|
@modeUpdated="saveMode"
|
||||||
|
@clockUpdated="saveClock"
|
||||||
|
@fixedBoundsUpdated="saveFixedBounds"
|
||||||
|
@clockOffsetsUpdated="saveClockOffsets"
|
||||||
|
@dismiss="clearPopup"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import ConductorMode from './ConductorMode.vue';
|
import { FIXED_MODE_KEY, TIME_CONTEXT_EVENTS } from '../../api/time/constants';
|
||||||
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
|
||||||
import ConductorAxis from './ConductorAxis.vue';
|
import ConductorAxis from './ConductorAxis.vue';
|
||||||
import ConductorModeIcon from './ConductorModeIcon.vue';
|
import ConductorModeIcon from './ConductorModeIcon.vue';
|
||||||
import ConductorHistory from './ConductorHistory.vue';
|
|
||||||
import ConductorInputsFixed from "./ConductorInputsFixed.vue";
|
import ConductorInputsFixed from "./ConductorInputsFixed.vue";
|
||||||
import ConductorInputsRealtime from "./ConductorInputsRealtime.vue";
|
import ConductorInputsRealtime from "./ConductorInputsRealtime.vue";
|
||||||
|
import conductorPopUpManager from "./conductorPopUpManager";
|
||||||
|
import ConductorPopUp from "./ConductorPopUp.vue";
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
|
||||||
@ -82,24 +93,24 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
ConductorInputsRealtime,
|
ConductorInputsRealtime,
|
||||||
ConductorInputsFixed,
|
ConductorInputsFixed,
|
||||||
ConductorMode,
|
|
||||||
ConductorTimeSystem,
|
|
||||||
ConductorAxis,
|
ConductorAxis,
|
||||||
ConductorModeIcon,
|
ConductorModeIcon,
|
||||||
ConductorHistory
|
ConductorPopUp
|
||||||
},
|
},
|
||||||
|
mixins: [conductorPopUpManager],
|
||||||
inject: ['openmct', 'configuration'],
|
inject: ['openmct', 'configuration'],
|
||||||
data() {
|
data() {
|
||||||
let bounds = this.openmct.time.bounds();
|
const isFixed = this.openmct.time.isFixed();
|
||||||
let offsets = this.openmct.time.clockOffsets();
|
const bounds = this.openmct.time.getBounds();
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
const offsets = this.openmct.time.getClockOffsets();
|
||||||
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
const timeSystem = this.openmct.time.getTimeSystem();
|
||||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
const timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
|
const durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timeSystem: timeSystem,
|
timeSystem,
|
||||||
timeFormatter: timeFormatter,
|
timeFormatter,
|
||||||
durationFormatter: durationFormatter,
|
durationFormatter,
|
||||||
offsets: {
|
offsets: {
|
||||||
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
|
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
|
||||||
end: offsets && durationFormatter.format(Math.abs(offsets.end))
|
end: offsets && durationFormatter.format(Math.abs(offsets.end))
|
||||||
@ -116,37 +127,41 @@ export default {
|
|||||||
start: bounds.start,
|
start: bounds.start,
|
||||||
end: bounds.end
|
end: bounds.end
|
||||||
},
|
},
|
||||||
isFixed: this.openmct.time.clock() === undefined,
|
isFixed,
|
||||||
isUTCBased: timeSystem.isUTCBased,
|
isUTCBased: timeSystem.isUTCBased,
|
||||||
showDatePicker: false,
|
showDatePicker: false,
|
||||||
|
showConductorPopup: false,
|
||||||
altPressed: false,
|
altPressed: false,
|
||||||
isPanning: false,
|
isPanning: false,
|
||||||
isZooming: false,
|
isZooming: false
|
||||||
showTCInputStart: false,
|
|
||||||
showTCInputEnd: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
timeMode() {
|
|
||||||
return this.isFixed ? 'fixed' : 'realtime';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.handleKeyDown);
|
||||||
document.addEventListener('keyup', this.handleKeyUp);
|
document.addEventListener('keyup', this.handleKeyUp);
|
||||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
|
||||||
this.openmct.time.on('bounds', _.throttle(this.handleNewBounds, 300));
|
this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem()));
|
||||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
|
||||||
this.openmct.time.on('clock', this.setViewFromClock);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.boundsChanged, _.throttle(this.handleNewBounds, 300));
|
||||||
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||||
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.modeChanged, this.setMode);
|
||||||
|
// this.openmct.time.on('clockChanged', this.setViewFromClock);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
document.removeEventListener('keydown', this.handleKeyDown);
|
document.removeEventListener('keydown', this.handleKeyDown);
|
||||||
document.removeEventListener('keyup', this.handleKeyUp);
|
document.removeEventListener('keyup', this.handleKeyUp);
|
||||||
|
|
||||||
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.boundsChanged, _.throttle(this.handleNewBounds, 300));
|
||||||
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||||
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.modeChanged, this.setMode);
|
||||||
|
// this.openmct.time.off('clockChanged', this.setViewFromClock);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleNewBounds(bounds) {
|
handleNewBounds(bounds, isTick) {
|
||||||
this.setBounds(bounds);
|
if (this.openmct.time.isRealTime() || !isTick) {
|
||||||
this.setViewFromBounds(bounds);
|
this.setBounds(bounds);
|
||||||
|
this.setViewFromBounds(bounds);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setBounds(bounds) {
|
setBounds(bounds) {
|
||||||
this.bounds = bounds;
|
this.bounds = bounds;
|
||||||
@ -195,9 +210,8 @@ export default {
|
|||||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
this.isUTCBased = timeSystem.isUTCBased;
|
this.isUTCBased = timeSystem.isUTCBased;
|
||||||
},
|
},
|
||||||
setViewFromClock(clock) {
|
setMode(mode) {
|
||||||
// this.clearAllValidation();
|
this.isFixed = mode === FIXED_MODE_KEY;
|
||||||
this.isFixed = clock === undefined;
|
|
||||||
},
|
},
|
||||||
setViewFromBounds(bounds) {
|
setViewFromBounds(bounds) {
|
||||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||||
@ -210,11 +224,20 @@ export default {
|
|||||||
format: key
|
format: key
|
||||||
}).formatter;
|
}).formatter;
|
||||||
},
|
},
|
||||||
saveClockOffsets(offsets) {
|
saveFixedBounds(bounds) {
|
||||||
this.openmct.time.clockOffsets(offsets);
|
|
||||||
},
|
|
||||||
saveFixedOffsets(bounds) {
|
|
||||||
this.openmct.time.bounds(bounds);
|
this.openmct.time.bounds(bounds);
|
||||||
|
},
|
||||||
|
saveClockOffsets(offsets) {
|
||||||
|
this.openmct.time.setClockOffsets(offsets);
|
||||||
|
},
|
||||||
|
saveClock(clockOptions) {
|
||||||
|
this.openmct.time.setClock(clockOptions.clockKey, clockOptions.offsets);
|
||||||
|
},
|
||||||
|
saveMode(mode) {
|
||||||
|
this.openmct.time.setMode(mode);
|
||||||
|
},
|
||||||
|
copy(object) {
|
||||||
|
return JSON.parse(JSON.stringify(object));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -38,6 +38,7 @@ import * as d3Selection from 'd3-selection';
|
|||||||
import * as d3Axis from 'd3-axis';
|
import * as d3Axis from 'd3-axis';
|
||||||
import * as d3Scale from 'd3-scale';
|
import * as d3Scale from 'd3-scale';
|
||||||
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
||||||
|
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants';
|
||||||
|
|
||||||
const PADDING = 1;
|
const PADDING = 1;
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
@ -92,13 +93,16 @@ export default {
|
|||||||
this.axisElement = vis.append("g")
|
this.axisElement = vis.append("g")
|
||||||
.attr("class", "axis");
|
.attr("class", "axis");
|
||||||
|
|
||||||
this.setViewFromTimeSystem(this.openmct.time.timeSystem());
|
this.setViewFromTimeSystem(this.openmct.time.getTimeSystem());
|
||||||
this.setAxisDimensions();
|
this.setAxisDimensions();
|
||||||
this.setScale();
|
this.setScale();
|
||||||
|
|
||||||
//Respond to changes in conductor
|
//Respond to changes in conductor
|
||||||
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setViewFromTimeSystem);
|
||||||
setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.resizeTimer);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setAxisDimensions() {
|
setAxisDimensions() {
|
||||||
@ -113,7 +117,7 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
let timeSystem = this.openmct.time.getTimeSystem();
|
||||||
|
|
||||||
if (timeSystem.isUTCBased) {
|
if (timeSystem.isUTCBased) {
|
||||||
this.xScale.domain(
|
this.xScale.domain(
|
||||||
@ -153,7 +157,7 @@ export default {
|
|||||||
this.setScale();
|
this.setScale();
|
||||||
},
|
},
|
||||||
getActiveFormatter() {
|
getActiveFormatter() {
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
let timeSystem = this.openmct.time.getTimeSystem();
|
||||||
|
|
||||||
if (this.isFixed) {
|
if (this.isFixed) {
|
||||||
return this.getFormatter(timeSystem.timeFormat);
|
return this.getFormatter(timeSystem.timeFormat);
|
||||||
@ -224,7 +228,7 @@ export default {
|
|||||||
this.inPanMode = false;
|
this.inPanMode = false;
|
||||||
},
|
},
|
||||||
getPanBounds() {
|
getPanBounds() {
|
||||||
const bounds = this.openmct.time.bounds();
|
const bounds = this.openmct.time.getBounds();
|
||||||
const deltaTime = bounds.end - bounds.start;
|
const deltaTime = bounds.end - bounds.start;
|
||||||
const deltaX = this.dragX - this.dragStartX;
|
const deltaX = this.dragX - this.dragStartX;
|
||||||
const percX = deltaX / this.width;
|
const percX = deltaX / this.width;
|
||||||
@ -291,7 +295,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
scaleToBounds(value) {
|
scaleToBounds(value) {
|
||||||
const bounds = this.openmct.time.bounds();
|
const bounds = this.openmct.time.getBounds();
|
||||||
const timeDelta = bounds.end - bounds.start;
|
const timeDelta = bounds.end - bounds.start;
|
||||||
const valueDelta = value - this.left;
|
const valueDelta = value - this.left;
|
||||||
const offset = valueDelta / this.width * timeDelta;
|
const offset = valueDelta / this.width * timeDelta;
|
||||||
|
142
src/plugins/timeConductor/ConductorClock.vue
Normal file
142
src/plugins/timeConductor/ConductorClock.vue
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2023, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="clockButton"
|
||||||
|
class="c-tc-input-popup__options"
|
||||||
|
>
|
||||||
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
|
<button
|
||||||
|
class="c-button--menu c-button--compact js-clock-button"
|
||||||
|
:class="[
|
||||||
|
buttonCssClass,
|
||||||
|
selectedClock.cssClass
|
||||||
|
]"
|
||||||
|
@click.prevent.stop="showClocksMenu"
|
||||||
|
>
|
||||||
|
<span class="c-button__label">{{ selectedClock.name }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import modeMixin from './mode-mixin';
|
||||||
|
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [modeMixin],
|
||||||
|
inject: {
|
||||||
|
openmct: 'openmct',
|
||||||
|
configuration: {
|
||||||
|
from: 'configuration',
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
const activeClock = this.getActiveClock();
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedClock: activeClock ? this.getClockMetadata(activeClock) : undefined,
|
||||||
|
clocks: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.loadClocks(this.configuration.menuOptions);
|
||||||
|
// this.setOffsets();
|
||||||
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// setOffsets() {
|
||||||
|
// if (!this.openmct.time.getClockOffsets()) {
|
||||||
|
// const activeClock = this.getActiveClock();
|
||||||
|
// const clockConfig = this.getMatchingConfig({
|
||||||
|
// clock: activeClock.key
|
||||||
|
// });
|
||||||
|
|
||||||
|
// this.openmct.time.setClockOffsets(clockConfig.clockOffsets);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
showClocksMenu() {
|
||||||
|
const elementBoundingClientRect = this.$refs.clockButton.getBoundingClientRect();
|
||||||
|
const x = elementBoundingClientRect.x;
|
||||||
|
const y = elementBoundingClientRect.y;
|
||||||
|
|
||||||
|
const menuOptions = {
|
||||||
|
menuClass: 'c-conductor__clock-menu',
|
||||||
|
placement: this.openmct.menus.menuPlacement.TOP_RIGHT
|
||||||
|
};
|
||||||
|
|
||||||
|
this.dismiss = this.openmct.menus.showSuperMenu(x, y, this.clocks, menuOptions);
|
||||||
|
},
|
||||||
|
setClock(clockKey) {
|
||||||
|
const option = {
|
||||||
|
clockKey
|
||||||
|
};
|
||||||
|
let configuration = this.getMatchingConfig({
|
||||||
|
clock: clockKey,
|
||||||
|
timeSystem: this.openmct.time.getTimeSystem().key
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configuration === undefined) {
|
||||||
|
configuration = this.getMatchingConfig({
|
||||||
|
clock: clockKey
|
||||||
|
});
|
||||||
|
|
||||||
|
option.timeSystem = configuration.timeSystem;
|
||||||
|
option.bounds = configuration.bounds;
|
||||||
|
|
||||||
|
// this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsets = this.openmct.time.getClockOffsets() ?? configuration.clockOffsets;
|
||||||
|
option.offsets = offsets;
|
||||||
|
|
||||||
|
this.$emit('clockUpdated', option);
|
||||||
|
},
|
||||||
|
getMatchingConfig(options) {
|
||||||
|
const matchers = {
|
||||||
|
clock(config) {
|
||||||
|
return options.clock === config.clock;
|
||||||
|
},
|
||||||
|
timeSystem(config) {
|
||||||
|
return options.timeSystem === config.timeSystem;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function configMatches(config) {
|
||||||
|
return Object.keys(options).reduce((match, option) => {
|
||||||
|
return match && matchers[option](config);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.configuration.menuOptions.filter(configMatches)[0];
|
||||||
|
},
|
||||||
|
setViewFromClock(clock) {
|
||||||
|
this.activeClock = clock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -26,8 +26,9 @@
|
|||||||
>
|
>
|
||||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
<button
|
<button
|
||||||
|
class="c-button--menu c-button--compact c-history-button icon-history"
|
||||||
|
:class="buttonCssClass"
|
||||||
aria-label="Time Conductor History"
|
aria-label="Time Conductor History"
|
||||||
class="c-button--menu c-history-button icon-history"
|
|
||||||
@click.prevent.stop="showHistoryMenu"
|
@click.prevent.stop="showHistoryMenu"
|
||||||
>
|
>
|
||||||
<span class="c-button__label">History</span>
|
<span class="c-button__label">History</span>
|
||||||
@ -64,6 +65,13 @@ export default {
|
|||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
buttonCssClass: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -81,7 +89,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
fixedHistory: {},
|
fixedHistory: {},
|
||||||
presets: [],
|
presets: [],
|
||||||
isFixed: this.openmct.time.clock() === undefined
|
isFixed: this.openmct.time.getClock() === undefined
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -95,7 +103,7 @@ export default {
|
|||||||
},
|
},
|
||||||
storageKey() {
|
storageKey() {
|
||||||
let key = LOCAL_STORAGE_HISTORY_KEY_FIXED;
|
let key = LOCAL_STORAGE_HISTORY_KEY_FIXED;
|
||||||
if (!this.isFixed) {
|
if (this.openmct.time.isRealTime()) {
|
||||||
key = LOCAL_STORAGE_HISTORY_KEY_REALTIME;
|
key = LOCAL_STORAGE_HISTORY_KEY_REALTIME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,8 +114,8 @@ export default {
|
|||||||
bounds: {
|
bounds: {
|
||||||
handler() {
|
handler() {
|
||||||
// only for fixed time since we track offsets for realtime
|
// only for fixed time since we track offsets for realtime
|
||||||
if (this.isFixed) {
|
this.updateMode();
|
||||||
this.updateMode();
|
if (this.openmct.time.isFixed()) {
|
||||||
this.addTimespan();
|
this.addTimespan();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -116,7 +124,9 @@ export default {
|
|||||||
offsets: {
|
offsets: {
|
||||||
handler() {
|
handler() {
|
||||||
this.updateMode();
|
this.updateMode();
|
||||||
this.addTimespan();
|
if (this.openmct.time.isRealTime()) {
|
||||||
|
this.addTimespan();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
},
|
},
|
||||||
@ -140,7 +150,6 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateMode() {
|
updateMode() {
|
||||||
this.isFixed = this.openmct.time.clock() === undefined;
|
|
||||||
this.getHistoryFromLocalStorage();
|
this.getHistoryFromLocalStorage();
|
||||||
this.initializeHistoryIfNoHistory();
|
this.initializeHistoryIfNoHistory();
|
||||||
},
|
},
|
||||||
@ -151,7 +160,7 @@ export default {
|
|||||||
const startTime = this.formatTime(timespan.start);
|
const startTime = this.formatTime(timespan.start);
|
||||||
const description = `${this.formatTime(timespan.start, descriptionDateFormat)} - ${this.formatTime(timespan.end, descriptionDateFormat)}`;
|
const description = `${this.formatTime(timespan.start, descriptionDateFormat)} - ${this.formatTime(timespan.end, descriptionDateFormat)}`;
|
||||||
|
|
||||||
if (this.timeSystem.isUTCBased && !this.openmct.time.clock()) {
|
if (this.timeSystem.isUTCBased && !this.openmct.time.isRealTime()) {
|
||||||
name = `${startTime} ${millisecondsToDHMS(timespan.end - timespan.start)}`;
|
name = `${startTime} ${millisecondsToDHMS(timespan.end - timespan.start)}`;
|
||||||
} else {
|
} else {
|
||||||
name = description;
|
name = description;
|
||||||
@ -201,10 +210,11 @@ export default {
|
|||||||
},
|
},
|
||||||
addTimespan() {
|
addTimespan() {
|
||||||
const key = this.timeSystem.key;
|
const key = this.timeSystem.key;
|
||||||
|
const isFixed = this.openmct.time.isFixed();
|
||||||
let [...currentHistory] = this[this.currentHistory][key] || [];
|
let [...currentHistory] = this[this.currentHistory][key] || [];
|
||||||
const timespan = {
|
const timespan = {
|
||||||
start: this.isFixed ? this.bounds.start : this.offsets.start,
|
start: isFixed ? this.bounds.start : this.offsets.start,
|
||||||
end: this.isFixed ? this.bounds.end : this.offsets.end
|
end: isFixed ? this.bounds.end : this.offsets.end
|
||||||
};
|
};
|
||||||
|
|
||||||
// no dupes
|
// no dupes
|
||||||
@ -219,10 +229,10 @@ export default {
|
|||||||
this.persistHistoryToLocalStorage();
|
this.persistHistoryToLocalStorage();
|
||||||
},
|
},
|
||||||
selectTimespan(timespan) {
|
selectTimespan(timespan) {
|
||||||
if (this.isFixed) {
|
if (this.openmct.time.isFixed()) {
|
||||||
this.openmct.time.bounds(timespan);
|
this.openmct.time.getBounds(timespan);
|
||||||
} else {
|
} else {
|
||||||
this.openmct.time.clockOffsets(timespan);
|
this.openmct.time.getClockOffsets(timespan);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectPresetBounds(bounds) {
|
selectPresetBounds(bounds) {
|
||||||
@ -259,7 +269,7 @@ export default {
|
|||||||
let format = this.timeSystem.timeFormat;
|
let format = this.timeSystem.timeFormat;
|
||||||
let isNegativeOffset = false;
|
let isNegativeOffset = false;
|
||||||
|
|
||||||
if (!this.isFixed) {
|
if (!this.openmct.time.isFixed()) {
|
||||||
if (time < 0) {
|
if (time < 0) {
|
||||||
isNegativeOffset = true;
|
isNegativeOffset = true;
|
||||||
}
|
}
|
||||||
|
@ -1,78 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<form
|
<time-popup-fixed
|
||||||
ref="fixedDeltaInput"
|
v-if="readOnly === false"
|
||||||
class="c-conductor__inputs"
|
:input-bounds="bounds"
|
||||||
|
:input-time-system="timeSystem"
|
||||||
|
@focus.native="$event.target.select()"
|
||||||
|
@update="setBoundsFromView"
|
||||||
|
@dismiss="dismiss"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="c-compact-tc__bounds"
|
||||||
>
|
>
|
||||||
<div
|
<div class="c-compact-tc__bounds__value">{{ formattedBounds.start }}</div>
|
||||||
class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed"
|
<div class="c-compact-tc__bounds__start-end-sep icon-arrows-right-left"></div>
|
||||||
>
|
<div class="c-compact-tc__bounds__value">{{ formattedBounds.end }}</div>
|
||||||
<!-- Fixed start -->
|
</div>
|
||||||
<div class="c-conductor__start-fixed__label">
|
|
||||||
Start
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
ref="startDate"
|
|
||||||
v-model="formattedBounds.start"
|
|
||||||
class="c-input--datetime"
|
|
||||||
type="text"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
@change="validateAllBounds('startDate'); submitForm()"
|
|
||||||
>
|
|
||||||
<date-picker
|
|
||||||
v-if="isUTCBased"
|
|
||||||
class="c-ctrl-wrapper--menus-left"
|
|
||||||
:bottom="keyString !== undefined"
|
|
||||||
:default-date-time="formattedBounds.start"
|
|
||||||
:formatter="timeFormatter"
|
|
||||||
@date-selected="startDateSelected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
|
|
||||||
<!-- Fixed end and RT 'last update' display -->
|
|
||||||
<div class="c-conductor__end-fixed__label">
|
|
||||||
End
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
ref="endDate"
|
|
||||||
v-model="formattedBounds.end"
|
|
||||||
class="c-input--datetime"
|
|
||||||
type="text"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
@change="validateAllBounds('endDate'); submitForm()"
|
|
||||||
>
|
|
||||||
<date-picker
|
|
||||||
v-if="isUTCBased"
|
|
||||||
class="c-ctrl-wrapper--menus-left"
|
|
||||||
:bottom="keyString !== undefined"
|
|
||||||
:default-date-time="formattedBounds.end"
|
|
||||||
:formatter="timeFormatter"
|
|
||||||
@date-selected="endDateSelected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import TimePopupFixed from "./timePopupFixed.vue";
|
||||||
import DatePicker from "./DatePicker.vue";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { TIME_CONTEXT_EVENTS } from "../../api/time/constants";
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
// const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
DatePicker
|
TimePopupFixed
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
keyString: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inputBounds: {
|
inputBounds: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default() {
|
default() {
|
||||||
@ -84,18 +41,29 @@ export default {
|
|||||||
default() {
|
default() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
readOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
compact: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
const timeSystem = this.openmct.time.getTimeSystem();
|
||||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
// let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
const timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
let bounds = this.bounds || this.openmct.time.bounds();
|
let bounds = this.inputBounds || this.openmct.time.getBounds();
|
||||||
|
console.log('fixed input bounds', this.inputBounds);
|
||||||
return {
|
return {
|
||||||
showTCInputStart: true,
|
timeSystem: timeSystem,
|
||||||
showTCInputEnd: true,
|
// durationFormatter,
|
||||||
durationFormatter,
|
|
||||||
timeFormatter,
|
timeFormatter,
|
||||||
bounds: {
|
bounds: {
|
||||||
start: bounds.start,
|
start: bounds.start,
|
||||||
@ -109,8 +77,15 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
keyString() {
|
objectPath: {
|
||||||
this.setTimeContext();
|
handler(newPath, oldPath) {
|
||||||
|
if (newPath === oldPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setTimeContext();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
},
|
},
|
||||||
inputBounds: {
|
inputBounds: {
|
||||||
handler(newBounds) {
|
handler(newBounds) {
|
||||||
@ -122,40 +97,30 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
||||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
||||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||||
this.setTimeContext();
|
this.setTimeContext();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.clearAllValidation();
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? this.objectPath : []);
|
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||||
|
|
||||||
this.handleNewBounds(this.timeContext.bounds());
|
this.handleNewBounds(this.timeContext.getBounds());
|
||||||
this.timeContext.on('bounds', this.handleNewBounds);
|
this.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||||
this.timeContext.on('clock', this.clearAllValidation);
|
|
||||||
},
|
},
|
||||||
stopFollowingTimeContext() {
|
stopFollowingTimeContext() {
|
||||||
if (this.timeContext) {
|
if (this.timeContext) {
|
||||||
this.timeContext.off('bounds', this.handleNewBounds);
|
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||||
this.timeContext.off('clock', this.clearAllValidation);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleNewBounds(bounds) {
|
handleNewBounds(bounds) {
|
||||||
this.setBounds(bounds);
|
this.setBounds(bounds);
|
||||||
this.setViewFromBounds(bounds);
|
this.setViewFromBounds(bounds);
|
||||||
},
|
},
|
||||||
clearAllValidation() {
|
|
||||||
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
|
|
||||||
},
|
|
||||||
clearValidationForInput(input) {
|
|
||||||
input.setCustomValidity('');
|
|
||||||
input.title = '';
|
|
||||||
},
|
|
||||||
setBounds(bounds) {
|
setBounds(bounds) {
|
||||||
this.bounds = bounds;
|
this.bounds = bounds;
|
||||||
},
|
},
|
||||||
@ -166,8 +131,8 @@ export default {
|
|||||||
setTimeSystem(timeSystem) {
|
setTimeSystem(timeSystem) {
|
||||||
this.timeSystem = timeSystem;
|
this.timeSystem = timeSystem;
|
||||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
this.durationFormatter = this.getFormatter(
|
// this.durationFormatter = this.getFormatter(
|
||||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
// timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
this.isUTCBased = timeSystem.isUTCBased;
|
this.isUTCBased = timeSystem.isUTCBased;
|
||||||
},
|
},
|
||||||
getFormatter(key) {
|
getFormatter(key) {
|
||||||
@ -175,116 +140,15 @@ export default {
|
|||||||
format: key
|
format: key
|
||||||
}).formatter;
|
}).formatter;
|
||||||
},
|
},
|
||||||
setBoundsFromView($event) {
|
setBoundsFromView(bounds) {
|
||||||
if (this.$refs.fixedDeltaInput.checkValidity()) {
|
console.log('conductor fixed bounds set bounds from view', bounds);
|
||||||
let start = this.timeFormatter.parse(this.formattedBounds.start);
|
this.$emit('boundsUpdated', {
|
||||||
let end = this.timeFormatter.parse(this.formattedBounds.end);
|
start: bounds.start,
|
||||||
|
end: bounds.end
|
||||||
this.$emit('updated', {
|
|
||||||
start: start,
|
|
||||||
end: end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($event) {
|
|
||||||
$event.preventDefault();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submitForm() {
|
|
||||||
// Allow Vue model to catch up to user input.
|
|
||||||
// Submitting form will cause validation messages to display (but only if triggered by button click)
|
|
||||||
this.$nextTick(() => this.setBoundsFromView());
|
|
||||||
},
|
|
||||||
validateAllBounds(ref) {
|
|
||||||
if (!this.areBoundsFormatsValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let validationResult = {
|
|
||||||
valid: true
|
|
||||||
};
|
|
||||||
const currentInput = this.$refs[ref];
|
|
||||||
|
|
||||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
|
||||||
let boundsValues = {
|
|
||||||
start: this.timeFormatter.parse(this.formattedBounds.start),
|
|
||||||
end: this.timeFormatter.parse(this.formattedBounds.end)
|
|
||||||
};
|
|
||||||
//TODO: Do we need limits here? We have conductor limits disabled right now
|
|
||||||
// const limit = this.getBoundsLimit();
|
|
||||||
const limit = false;
|
|
||||||
|
|
||||||
if (this.timeSystem.isUTCBased && limit
|
|
||||||
&& boundsValues.end - boundsValues.start > limit) {
|
|
||||||
if (input === currentInput) {
|
|
||||||
validationResult = {
|
|
||||||
valid: false,
|
|
||||||
message: "Start and end difference exceeds allowable limit"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (input === currentInput) {
|
|
||||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleValidationResults(input, validationResult);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
areBoundsFormatsValid() {
|
dismiss() {
|
||||||
let validationResult = {
|
this.$emit('dismissInputsFixed');
|
||||||
valid: true
|
|
||||||
};
|
|
||||||
|
|
||||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
|
||||||
const formattedDate = input === this.$refs.startDate
|
|
||||||
? this.formattedBounds.start
|
|
||||||
: this.formattedBounds.end
|
|
||||||
;
|
|
||||||
|
|
||||||
if (!this.timeFormatter.validate(formattedDate)) {
|
|
||||||
validationResult = {
|
|
||||||
valid: false,
|
|
||||||
message: 'Invalid date'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleValidationResults(input, validationResult);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getBoundsLimit() {
|
|
||||||
const configuration = this.configuration.menuOptions
|
|
||||||
.filter(option => option.timeSystem === this.timeSystem.key)
|
|
||||||
.find(option => option.limit);
|
|
||||||
|
|
||||||
const limit = configuration ? configuration.limit : undefined;
|
|
||||||
|
|
||||||
return limit;
|
|
||||||
},
|
|
||||||
handleValidationResults(input, validationResult) {
|
|
||||||
if (validationResult.valid !== true) {
|
|
||||||
input.setCustomValidity(validationResult.message);
|
|
||||||
input.title = validationResult.message;
|
|
||||||
} else {
|
|
||||||
input.setCustomValidity('');
|
|
||||||
input.title = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$refs.fixedDeltaInput.reportValidity();
|
|
||||||
|
|
||||||
return validationResult.valid;
|
|
||||||
},
|
|
||||||
startDateSelected(date) {
|
|
||||||
this.formattedBounds.start = this.timeFormatter.format(date);
|
|
||||||
this.validateAllBounds('startDate');
|
|
||||||
this.submitForm();
|
|
||||||
},
|
|
||||||
endDateSelected(date) {
|
|
||||||
this.formattedBounds.end = this.timeFormatter.format(date);
|
|
||||||
this.validateAllBounds('endDate');
|
|
||||||
this.submitForm();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,95 +1,44 @@
|
|||||||
<template>
|
<template>
|
||||||
<form
|
<time-popup-realtime
|
||||||
ref="deltaInput"
|
v-if="readOnly === false"
|
||||||
class="c-conductor__inputs"
|
:offsets="offsets"
|
||||||
|
@focus.native="$event.target.select()"
|
||||||
|
@update="timePopUpdate"
|
||||||
|
@dismiss="dismiss"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="c-compact-tc__bounds"
|
||||||
>
|
>
|
||||||
|
<div class="c-compact-tc__bounds__value icon-minus">{{ offsets.start }}</div>
|
||||||
<div
|
<div
|
||||||
class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta"
|
v-if="compact"
|
||||||
>
|
class="c-compact-tc__bounds__start-end-sep icon-arrows-right-left"
|
||||||
<!-- RT start -->
|
></div>
|
||||||
<div class="c-direction-indicator icon-minus"></div>
|
|
||||||
<time-popup
|
|
||||||
v-if="showTCInputStart"
|
|
||||||
class="pr-tc-input-menu--start"
|
|
||||||
:bottom="keyString !== undefined"
|
|
||||||
:type="'start'"
|
|
||||||
:offset="offsets.start"
|
|
||||||
@focus.native="$event.target.select()"
|
|
||||||
@hide="hideAllTimePopups"
|
|
||||||
@update="timePopUpdate"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
ref="startOffset"
|
|
||||||
class="c-button c-conductor__delta-button"
|
|
||||||
title="Set the time offset after now"
|
|
||||||
data-testid="conductor-start-offset-button"
|
|
||||||
@click.prevent.stop="showTimePopupStart"
|
|
||||||
>
|
|
||||||
{{ offsets.start }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
|
|
||||||
<!-- RT 'last update' display -->
|
|
||||||
<div class="c-conductor__end-fixed__label">
|
|
||||||
Current
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
ref="endDate"
|
|
||||||
v-model="formattedCurrentValue"
|
|
||||||
class="c-input--datetime"
|
|
||||||
type="text"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
:disabled="true"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
|
v-else
|
||||||
|
class="c-compact-tc__current-update"
|
||||||
>
|
>
|
||||||
<!-- RT end -->
|
LAST UPDATE {{ formattedBounds.end }}
|
||||||
<div class="c-direction-indicator icon-plus"></div>
|
|
||||||
<time-popup
|
|
||||||
v-if="showTCInputEnd"
|
|
||||||
class="pr-tc-input-menu--end"
|
|
||||||
:bottom="keyString !== undefined"
|
|
||||||
:type="'end'"
|
|
||||||
:offset="offsets.end"
|
|
||||||
@focus.native="$event.target.select()"
|
|
||||||
@hide="hideAllTimePopups"
|
|
||||||
@update="timePopUpdate"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
ref="endOffset"
|
|
||||||
class="c-button c-conductor__delta-button"
|
|
||||||
title="Set the time offset preceding now"
|
|
||||||
data-testid="conductor-end-offset-button"
|
|
||||||
@click.prevent.stop="showTimePopupEnd"
|
|
||||||
>
|
|
||||||
{{ offsets.end }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div class="c-compact-tc__bounds__value icon-plus">{{ offsets.end }}</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import timePopup from "./timePopup.vue";
|
import TimePopupRealtime from "./timePopupRealtime.vue";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { TIME_CONTEXT_EVENTS } from "../../api/time/constants";
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
timePopup
|
TimePopupRealtime
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
keyString: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
objectPath: {
|
objectPath: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default() {
|
default() {
|
||||||
@ -101,15 +50,27 @@ export default {
|
|||||||
default() {
|
default() {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
readOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
compact: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
const timeSystem = this.openmct.time.getTimeSystem();
|
||||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
const durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
const timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
let bounds = this.bounds || this.openmct.time.bounds();
|
const bounds = this.bounds ?? this.openmct.time.getBounds();
|
||||||
let offsets = this.openmct.time.clockOffsets();
|
const offsets = this.offsets ?? this.openmct.time.getClockOffsets();
|
||||||
let currentValue = this.openmct.time.clock()?.currentValue();
|
const currentValue = this.openmct.time.getClock()?.currentValue();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showTCInputStart: false,
|
showTCInputStart: false,
|
||||||
@ -134,8 +95,15 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
keyString() {
|
objectPath: {
|
||||||
this.setTimeContext();
|
handler(newPath, oldPath) {
|
||||||
|
if (newPath === oldPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setTimeContext();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
},
|
},
|
||||||
inputBounds: {
|
inputBounds: {
|
||||||
handler(newBounds) {
|
handler(newBounds) {
|
||||||
@ -146,45 +114,50 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
||||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem()));
|
||||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||||
this.setTimeContext();
|
this.setTimeContext();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||||
this.stopFollowingTime();
|
this.stopFollowingTime();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
followTime() {
|
followTime() {
|
||||||
this.handleNewBounds(this.timeContext.bounds());
|
const bounds = this.timeContext ? this.timeContext.getBounds() : this.openmct.time.getBounds();
|
||||||
this.setViewFromOffsets(this.timeContext.clockOffsets());
|
const offsets = this.timeContext ? this.timeContext.getClockOffsets() : this.openmct.time.getClockOffsets();
|
||||||
this.timeContext.on('bounds', this.handleNewBounds);
|
|
||||||
this.timeContext.on('clock', this.clearAllValidation);
|
this.handleNewBounds(bounds);
|
||||||
this.timeContext.on('clockOffsets', this.setViewFromOffsets);
|
this.setViewFromOffsets(offsets);
|
||||||
|
|
||||||
|
if (this.timeContext) {
|
||||||
|
this.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||||
|
this.timeContext.on(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||||
|
} else {
|
||||||
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||||
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
stopFollowingTime() {
|
stopFollowingTime() {
|
||||||
if (this.timeContext) {
|
if (this.timeContext) {
|
||||||
this.timeContext.off('bounds', this.handleNewBounds);
|
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||||
this.timeContext.off('clock', this.clearAllValidation);
|
this.timeContext.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||||
this.timeContext.off('clockOffsets', this.setViewFromOffsets);
|
} else {
|
||||||
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||||
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
this.stopFollowingTime();
|
this.stopFollowingTime();
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? this.objectPath : []);
|
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||||
this.followTime();
|
this.followTime();
|
||||||
},
|
},
|
||||||
handleNewBounds(bounds) {
|
handleNewBounds(bounds, isTick) {
|
||||||
this.setBounds(bounds);
|
if (this.timeContext.isRealTime() || !isTick) {
|
||||||
this.setViewFromBounds(bounds);
|
this.setBounds(bounds);
|
||||||
this.updateCurrentValue();
|
this.setViewFromBounds(bounds);
|
||||||
},
|
this.updateCurrentValue();
|
||||||
clearAllValidation() {
|
}
|
||||||
[this.$refs.startOffset, this.$refs.endOffset].forEach(this.clearValidationForInput);
|
|
||||||
},
|
|
||||||
clearValidationForInput(input) {
|
|
||||||
input.setCustomValidity('');
|
|
||||||
input.title = '';
|
|
||||||
},
|
},
|
||||||
setViewFromOffsets(offsets) {
|
setViewFromOffsets(offsets) {
|
||||||
if (offsets) {
|
if (offsets) {
|
||||||
@ -200,7 +173,7 @@ export default {
|
|||||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||||
},
|
},
|
||||||
updateCurrentValue() {
|
updateCurrentValue() {
|
||||||
const currentValue = this.openmct.time.clock()?.currentValue();
|
const currentValue = this.openmct.time.getClock()?.currentValue();
|
||||||
|
|
||||||
if (currentValue !== undefined) {
|
if (currentValue !== undefined) {
|
||||||
this.setCurrentValue(currentValue);
|
this.setCurrentValue(currentValue);
|
||||||
@ -222,86 +195,25 @@ export default {
|
|||||||
format: key
|
format: key
|
||||||
}).formatter;
|
}).formatter;
|
||||||
},
|
},
|
||||||
hideAllTimePopups() {
|
timePopUpdate({ start, end }) {
|
||||||
this.showTCInputStart = false;
|
this.offsets.start = [start.hours, start.minutes, start.seconds].join(':');
|
||||||
this.showTCInputEnd = false;
|
this.offsets.end = [end.hours, end.minutes, end.seconds].join(':');
|
||||||
},
|
|
||||||
showTimePopupStart() {
|
|
||||||
this.hideAllTimePopups();
|
|
||||||
this.showTCInputStart = !this.showTCInputStart;
|
|
||||||
},
|
|
||||||
showTimePopupEnd() {
|
|
||||||
this.hideAllTimePopups();
|
|
||||||
this.showTCInputEnd = !this.showTCInputEnd;
|
|
||||||
},
|
|
||||||
timePopUpdate({ type, hours, minutes, seconds }) {
|
|
||||||
this.offsets[type] = [hours, minutes, seconds].join(':');
|
|
||||||
this.setOffsetsFromView();
|
this.setOffsetsFromView();
|
||||||
this.hideAllTimePopups();
|
|
||||||
},
|
},
|
||||||
setOffsetsFromView($event) {
|
setOffsetsFromView() {
|
||||||
if (this.$refs.deltaInput.checkValidity()) {
|
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
||||||
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
||||||
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
|
||||||
|
|
||||||
this.$emit('updated', {
|
this.$emit('offsetsUpdated', {
|
||||||
start: startOffset,
|
start: startOffset,
|
||||||
end: endOffset
|
end: endOffset
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($event) {
|
|
||||||
$event.preventDefault();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
validateAllBounds(ref) {
|
|
||||||
if (!this.areBoundsFormatsValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let validationResult = {
|
|
||||||
valid: true
|
|
||||||
};
|
|
||||||
const currentInput = this.$refs[ref];
|
|
||||||
|
|
||||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
|
||||||
let boundsValues = {
|
|
||||||
start: this.timeFormatter.parse(this.formattedBounds.start),
|
|
||||||
end: this.timeFormatter.parse(this.formattedBounds.end)
|
|
||||||
};
|
|
||||||
//TODO: Do we need limits here? We have conductor limits disabled right now
|
|
||||||
// const limit = this.getBoundsLimit();
|
|
||||||
const limit = false;
|
|
||||||
|
|
||||||
if (this.timeSystem.isUTCBased && limit
|
|
||||||
&& boundsValues.end - boundsValues.start > limit) {
|
|
||||||
if (input === currentInput) {
|
|
||||||
validationResult = {
|
|
||||||
valid: false,
|
|
||||||
message: "Start and end difference exceeds allowable limit"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (input === currentInput) {
|
|
||||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleValidationResults(input, validationResult);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleValidationResults(input, validationResult) {
|
dismiss() {
|
||||||
if (validationResult.valid !== true) {
|
this.$emit('dismissInputsRealtime');
|
||||||
input.setCustomValidity(validationResult.message);
|
},
|
||||||
input.title = validationResult.message;
|
copy(object) {
|
||||||
} else {
|
return JSON.parse(JSON.stringify(object));
|
||||||
input.setCustomValidity('');
|
|
||||||
input.title = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return validationResult.valid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -22,11 +22,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="modeButton"
|
ref="modeButton"
|
||||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
class="c-tc-input-popup__options"
|
||||||
>
|
>
|
||||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
<button
|
<button
|
||||||
class="c-button--menu c-mode-button"
|
class="c-button--menu c-button--compact js-mode-button"
|
||||||
|
:class="[
|
||||||
|
buttonCssClass,
|
||||||
|
selectedMode.cssClass
|
||||||
|
]"
|
||||||
@click.prevent.stop="showModesMenu"
|
@click.prevent.stop="showModesMenu"
|
||||||
>
|
>
|
||||||
<span class="c-button__label">{{ selectedMode.name }}</span>
|
<span class="c-button__label">{{ selectedMode.name }}</span>
|
||||||
@ -36,32 +40,23 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import toggleMixin from '../../ui/mixins/toggle-mixin';
|
import modeMixin from './mode-mixin';
|
||||||
|
|
||||||
|
const TEST_IDS = true;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [toggleMixin],
|
mixins: [modeMixin],
|
||||||
inject: ['openmct', 'configuration'],
|
inject: ['openmct', 'configuration'],
|
||||||
data: function () {
|
data: function () {
|
||||||
let activeClock = this.openmct.time.clock();
|
const mode = this.openmct.time.getMode();
|
||||||
if (activeClock !== undefined) {
|
|
||||||
//Create copy of active clock so the time API does not get reactified.
|
|
||||||
activeClock = Object.create(activeClock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedMode: this.getModeOptionForClock(activeClock),
|
selectedMode: this.getModeMetadata(mode, TEST_IDS),
|
||||||
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
|
modes: []
|
||||||
modes: [],
|
|
||||||
hoveredMode: {}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
this.loadClocksFromConfiguration();
|
this.loadModes();
|
||||||
|
|
||||||
this.openmct.time.on('clock', this.setViewFromClock);
|
|
||||||
},
|
|
||||||
destroyed: function () {
|
|
||||||
this.openmct.time.off('clock', this.setViewFromClock);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showModesMenu() {
|
showModesMenu() {
|
||||||
@ -74,108 +69,11 @@ export default {
|
|||||||
placement: this.openmct.menus.menuPlacement.TOP_RIGHT
|
placement: this.openmct.menus.menuPlacement.TOP_RIGHT
|
||||||
};
|
};
|
||||||
|
|
||||||
this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
this.dismiss = this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
||||||
},
|
},
|
||||||
|
setMode(modeKey) {
|
||||||
loadClocksFromConfiguration() {
|
this.selectedMode = this.getModeMetadata(modeKey, TEST_IDS);
|
||||||
let clocks = this.configuration.menuOptions
|
this.$emit('modeUpdated', modeKey);
|
||||||
.map(menuOption => menuOption.clock)
|
|
||||||
.filter(isDefinedAndUnique)
|
|
||||||
.map(this.getClock);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Populate the modes menu with metadata from the available clocks
|
|
||||||
* "Fixed Mode" is always first, and has no defined clock
|
|
||||||
*/
|
|
||||||
this.modes = [undefined]
|
|
||||||
.concat(clocks)
|
|
||||||
.map(this.getModeOptionForClock);
|
|
||||||
|
|
||||||
function isDefinedAndUnique(key, index, array) {
|
|
||||||
return key !== undefined && array.indexOf(key) === index;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getModeOptionForClock(clock) {
|
|
||||||
if (clock === undefined) {
|
|
||||||
const key = 'fixed';
|
|
||||||
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
name: 'Fixed Timespan',
|
|
||||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
|
||||||
cssClass: 'icon-tabular',
|
|
||||||
testId: 'conductor-modeOption-fixed',
|
|
||||||
onItemClicked: () => this.setOption(key)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const key = clock.key;
|
|
||||||
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
name: clock.name,
|
|
||||||
description: "Monitor streaming data in real-time. The Time "
|
|
||||||
+ "Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
|
||||||
cssClass: clock.cssClass || 'icon-clock',
|
|
||||||
testId: 'conductor-modeOption-realtime',
|
|
||||||
onItemClicked: () => this.setOption(key)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getClock(key) {
|
|
||||||
return this.openmct.time.getAllClocks().filter(function (clock) {
|
|
||||||
return clock.key === key;
|
|
||||||
})[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
setOption(clockKey) {
|
|
||||||
if (clockKey === 'fixed') {
|
|
||||||
clockKey = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let configuration = this.getMatchingConfig({
|
|
||||||
clock: clockKey,
|
|
||||||
timeSystem: this.openmct.time.timeSystem().key
|
|
||||||
});
|
|
||||||
|
|
||||||
if (configuration === undefined) {
|
|
||||||
configuration = this.getMatchingConfig({
|
|
||||||
clock: clockKey
|
|
||||||
});
|
|
||||||
|
|
||||||
this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clockKey === undefined) {
|
|
||||||
this.openmct.time.stopClock();
|
|
||||||
} else {
|
|
||||||
const offsets = this.openmct.time.clockOffsets() || configuration.clockOffsets;
|
|
||||||
this.openmct.time.clock(clockKey, offsets);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getMatchingConfig(options) {
|
|
||||||
const matchers = {
|
|
||||||
clock(config) {
|
|
||||||
return options.clock === config.clock;
|
|
||||||
},
|
|
||||||
timeSystem(config) {
|
|
||||||
return options.timeSystem === config.timeSystem;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function configMatches(config) {
|
|
||||||
return Object.keys(options).reduce((match, option) => {
|
|
||||||
return match && matchers[option](config);
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.configuration.menuOptions.filter(configMatches)[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
setViewFromClock(clock) {
|
|
||||||
this.selectedMode = this.getModeOptionForClock(clock);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,17 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
<div class="c-clock-symbol">
|
<div class="c-clock-symbol">
|
||||||
|
<svg
|
||||||
|
class="c-clock-symbol__outer"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 0L3 0C1.34315 0 0 1.34315 0 3V13C0 14.6569 1.34315 16 3 16H6V13H3V3H6V0Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10 13H13V3H10V0H13C14.6569 0 16 1.34315 16 3V13C16 14.6569 14.6569 16 13 16H10V13Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
<div class="hand-little"></div>
|
<div class="hand-little"></div>
|
||||||
<div class="hand-big"></div>
|
<div class="hand-big"></div>
|
||||||
</div>
|
</div>
|
||||||
|
261
src/plugins/timeConductor/ConductorPopUp.vue
Normal file
261
src/plugins/timeConductor/ConductorPopUp.vue
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="c-tc-input-popup"
|
||||||
|
:class="modeClass"
|
||||||
|
:style="position"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c-tc-input-popup__options"
|
||||||
|
>
|
||||||
|
<IndependentMode
|
||||||
|
v-if="isIndependent"
|
||||||
|
class="c-button--compact c-conductor__mode-select"
|
||||||
|
:mode="timeOptionMode"
|
||||||
|
:button-css-class="'c-button--compact'"
|
||||||
|
@independentModeUpdated="saveIndependentMode"
|
||||||
|
/>
|
||||||
|
<ConductorMode
|
||||||
|
v-else
|
||||||
|
class="c-conductor__mode-select"
|
||||||
|
:button-css-class="'c-icon-button'"
|
||||||
|
@modeUpdated="saveMode"
|
||||||
|
/>
|
||||||
|
<IndependentClock
|
||||||
|
v-if="isIndependent"
|
||||||
|
class="c-conductor__mode-select"
|
||||||
|
:clock="timeOptionClock"
|
||||||
|
:button-css-class="'c-icon-button'"
|
||||||
|
@independentClockUpdated="saveIndependentClock"
|
||||||
|
/>
|
||||||
|
<ConductorClock
|
||||||
|
v-else
|
||||||
|
class="c-conductor__mode-select"
|
||||||
|
:button-css-class="'c-icon-button'"
|
||||||
|
@clockUpdated="saveClock"
|
||||||
|
/>
|
||||||
|
<!-- TODO: Time system and history must work even with ITC later -->
|
||||||
|
<ConductorTimeSystem
|
||||||
|
v-if="!isIndependent"
|
||||||
|
class="c-conductor__time-system-select"
|
||||||
|
:button-css-class="'c-icon-button'"
|
||||||
|
/>
|
||||||
|
<ConductorHistory
|
||||||
|
v-if="!isIndependent"
|
||||||
|
class="c-conductor__history-select"
|
||||||
|
:button-css-class="'c-icon-button'"
|
||||||
|
:offsets="timeOffsets"
|
||||||
|
:bounds="bounds"
|
||||||
|
:time-system="timeSystem"
|
||||||
|
:mode="timeMode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<conductor-inputs-fixed
|
||||||
|
v-if="isFixed"
|
||||||
|
:input-bounds="bounds"
|
||||||
|
:object-path="objectPath"
|
||||||
|
@boundsUpdated="saveFixedBounds"
|
||||||
|
@dismissInputsFixed="dismiss"
|
||||||
|
/>
|
||||||
|
<conductor-inputs-realtime
|
||||||
|
v-else
|
||||||
|
:input-bounds="bounds"
|
||||||
|
:object-path="objectPath"
|
||||||
|
@offsetsUpdated="saveClockOffsets"
|
||||||
|
@dismissInputsRealtime="dismiss"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import ConductorMode from './ConductorMode.vue';
|
||||||
|
import ConductorClock from './ConductorClock.vue';
|
||||||
|
import IndependentMode from './independent/IndependentMode.vue';
|
||||||
|
import IndependentClock from './independent/IndependentClock.vue';
|
||||||
|
import ConductorTimeSystem from "./ConductorTimeSystem.vue";
|
||||||
|
import ConductorHistory from "./ConductorHistory.vue";
|
||||||
|
import ConductorInputsFixed from "./ConductorInputsFixed.vue";
|
||||||
|
import ConductorInputsRealtime from "./ConductorInputsRealtime.vue";
|
||||||
|
import { TIME_CONTEXT_EVENTS, REALTIME_MODE_KEY, FIXED_MODE_KEY } from '../../api/time/constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
components: {
|
||||||
|
ConductorMode,
|
||||||
|
ConductorClock,
|
||||||
|
IndependentMode,
|
||||||
|
IndependentClock,
|
||||||
|
ConductorTimeSystem,
|
||||||
|
ConductorHistory,
|
||||||
|
ConductorInputsFixed,
|
||||||
|
ConductorInputsRealtime
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
openmct: 'openmct',
|
||||||
|
configuration: {
|
||||||
|
from: 'configuration',
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
positionX: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
positionY: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isFixed: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isIndependent: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeOptions: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bottom: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
objectPath: {
|
||||||
|
type: Array,
|
||||||
|
default() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
const bounds = this.openmct.time.getBounds();
|
||||||
|
const timeSystem = this.openmct.time.getTimeSystem();
|
||||||
|
// const isFixed = this.openmct.time.isFixed();
|
||||||
|
|
||||||
|
return {
|
||||||
|
timeSystem,
|
||||||
|
bounds: {
|
||||||
|
start: bounds.start,
|
||||||
|
end: bounds.end
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
position() {
|
||||||
|
const position = {
|
||||||
|
left: `${this.positionX}px`
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.isIndependent) {
|
||||||
|
position.top = `${this.positionY}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
},
|
||||||
|
timeOffsets() {
|
||||||
|
return this.isFixed ? this.openmct.time.getBounds() : this.openmct.time.getClockOffsets();
|
||||||
|
},
|
||||||
|
timeMode() {
|
||||||
|
return this.isFixed ? FIXED_MODE_KEY : REALTIME_MODE_KEY;
|
||||||
|
},
|
||||||
|
modeClass() {
|
||||||
|
const value = this.bottom ? 'c-tc-input-popup--bottom' : '';
|
||||||
|
|
||||||
|
return this.isFixed ? `${value} c-tc-input-popup--fixed-mode` : `${value} c-tc-input-popup--realtime-mode`;
|
||||||
|
},
|
||||||
|
timeOptionMode() {
|
||||||
|
return this.timeOptions?.mode;
|
||||||
|
},
|
||||||
|
timeOptionClock() {
|
||||||
|
return this.timeOptions?.clock;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
objectPath: {
|
||||||
|
handler(newPath, oldPath) {
|
||||||
|
//domain object or view has probably changed
|
||||||
|
if (newPath === oldPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setTimeContext();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$emit('popupLoaded');
|
||||||
|
this.setTimeContext();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopFollowingTimeContext();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setTimeContext() {
|
||||||
|
if (this.timeContext) {
|
||||||
|
this.stopFollowingTimeContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||||
|
|
||||||
|
this.timeContext.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
|
this.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this.setBounds);
|
||||||
|
this.timeContext.on(TIME_CONTEXT_EVENTS.modeChanged, this.setMode);
|
||||||
|
|
||||||
|
this.setViewFromClock(this.timeContext.getClock());
|
||||||
|
this.setBounds(this.timeContext.getBounds());
|
||||||
|
},
|
||||||
|
stopFollowingTimeContext() {
|
||||||
|
this.timeContext.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
|
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.setBounds);
|
||||||
|
this.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this.setMode);
|
||||||
|
},
|
||||||
|
setViewFromClock() {
|
||||||
|
this.bounds = this.isFixed ? this.timeContext.getBounds() : this.openmct.time.getClockOffsets();
|
||||||
|
console.log('set view from clock popup', this.bounds);
|
||||||
|
},
|
||||||
|
setBounds(bounds, isTick) {
|
||||||
|
if (this.isFixed || !isTick) {
|
||||||
|
console.log('set bounds popup', bounds);
|
||||||
|
this.bounds = bounds;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setMode(mode) {
|
||||||
|
// this.isFixed = mode === FIXED_MODE_KEY;
|
||||||
|
},
|
||||||
|
saveFixedBounds(bounds) {
|
||||||
|
this.$emit('fixedBoundsUpdated', bounds);
|
||||||
|
},
|
||||||
|
saveClockOffsets(offsets) {
|
||||||
|
this.$emit('clockOffsetsUpdated', offsets);
|
||||||
|
},
|
||||||
|
saveClock(clockOptions) {
|
||||||
|
this.$emit('clockUpdated', clockOptions);
|
||||||
|
},
|
||||||
|
saveMode(mode) {
|
||||||
|
this.$emit('modeUpdated', mode);
|
||||||
|
},
|
||||||
|
saveIndependentMode(mode) {
|
||||||
|
this.$emit('independentModeUpdated', mode);
|
||||||
|
},
|
||||||
|
saveIndependentClock(clockKey) {
|
||||||
|
this.$emit('independentClockUpdated', clockKey);
|
||||||
|
},
|
||||||
|
dismiss() {
|
||||||
|
this.$emit('dismiss');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
@ -26,33 +26,46 @@
|
|||||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="c-button--menu c-time-system-button"
|
class="c-button--menu c-button--compact c-time-system-button"
|
||||||
:class="selectedTimeSystem.cssClass"
|
:class="[
|
||||||
|
buttonCssClass
|
||||||
|
]"
|
||||||
@click.prevent.stop="showTimeSystemMenu"
|
@click.prevent.stop="showTimeSystemMenu"
|
||||||
>
|
>
|
||||||
<span class="c-button__label">{{ selectedTimeSystem.name }}</span>
|
{{ selectedTimeSystem.name }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'configuration'],
|
inject: ['openmct', 'configuration'],
|
||||||
|
props: {
|
||||||
|
buttonCssClass: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
let activeClock = this.openmct.time.clock();
|
let activeClock = this.openmct.time.getClock();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
|
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())),
|
||||||
timeSystems: this.getValidTimesystemsForClock(activeClock)
|
timeSystems: this.getValidTimesystemsForClock(activeClock)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
this.openmct.time.on('timeSystem', this.setViewFromTimeSystem);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSysteChanged, this.setViewFromTimeSystem);
|
||||||
this.openmct.time.on('clock', this.setViewFromClock);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
},
|
},
|
||||||
destroyed: function () {
|
destroyed: function () {
|
||||||
this.openmct.time.off('timeSystem', this.setViewFromTimeSystem);
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setViewFromTimeSystem);
|
||||||
this.openmct.time.on('clock', this.setViewFromClock);
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showTimeSystemMenu() {
|
showTimeSystemMenu() {
|
||||||
@ -78,7 +91,7 @@ export default {
|
|||||||
},
|
},
|
||||||
setTimeSystemFromView(timeSystem) {
|
setTimeSystemFromView(timeSystem) {
|
||||||
if (timeSystem.key !== this.selectedTimeSystem.key) {
|
if (timeSystem.key !== this.selectedTimeSystem.key) {
|
||||||
let activeClock = this.openmct.time.clock();
|
let activeClock = this.openmct.time.getClock();
|
||||||
let configuration = this.getMatchingConfig({
|
let configuration = this.getMatchingConfig({
|
||||||
clock: activeClock && activeClock.key,
|
clock: activeClock && activeClock.key,
|
||||||
timeSystem: timeSystem.key
|
timeSystem: timeSystem.key
|
||||||
@ -87,15 +100,15 @@ export default {
|
|||||||
let bounds;
|
let bounds;
|
||||||
|
|
||||||
if (this.selectedTimeSystem.isUTCBased && timeSystem.isUTCBased) {
|
if (this.selectedTimeSystem.isUTCBased && timeSystem.isUTCBased) {
|
||||||
bounds = this.openmct.time.bounds();
|
bounds = this.openmct.time.getBounds();
|
||||||
} else {
|
} else {
|
||||||
bounds = configuration.bounds;
|
bounds = configuration.bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.time.timeSystem(timeSystem.key, bounds);
|
this.openmct.time.setTimeSystem(timeSystem.key, bounds);
|
||||||
} else {
|
} else {
|
||||||
this.openmct.time.timeSystem(timeSystem.key);
|
this.openmct.time.setTimeSystem(timeSystem.key);
|
||||||
this.openmct.time.clockOffsets(configuration.clockOffsets);
|
this.openmct.time.setClockOffsets(configuration.clockOffsets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -124,7 +137,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setViewFromClock(clock) {
|
setViewFromClock(clock) {
|
||||||
let activeClock = this.openmct.time.clock();
|
let activeClock = this.openmct.time.getClock();
|
||||||
this.timeSystems = this.getValidTimesystemsForClock(activeClock);
|
this.timeSystems = this.getValidTimesystemsForClock(activeClock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,11 +57,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.is-realtime-mode & {
|
.is-realtime-mode & {
|
||||||
$c: 1px solid rgba($colorTime, 0.7);
|
$c: 1px solid rgba($colorTimeRealtime, 0.7);
|
||||||
border-left: $c;
|
border-left: $c;
|
||||||
border-right: $c;
|
border-right: $c;
|
||||||
svg text {
|
svg text {
|
||||||
fill: $colorTime;
|
fill: $colorTimeRealtime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,22 +33,17 @@
|
|||||||
|
|
||||||
|
|
||||||
.c-clock-symbol {
|
.c-clock-symbol {
|
||||||
$c: $colorBtnBg; //$colorObjHdrIc;
|
$c: rgba($colorBodyFg, 0.5);
|
||||||
$d: 18px;
|
$d: 16px;
|
||||||
height: $d;
|
height: $d;
|
||||||
width: $d;
|
width: $d;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&:before {
|
&__outer {
|
||||||
font-family: symbolsfont;
|
// SVG brackets shape
|
||||||
color: $c;
|
|
||||||
content: $glyph-icon-brackets;
|
|
||||||
font-size: $d;
|
|
||||||
line-height: normal;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1;
|
fill: $c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clock hands
|
// Clock hands
|
||||||
@ -93,14 +88,15 @@
|
|||||||
// Modes
|
// Modes
|
||||||
.is-realtime-mode &,
|
.is-realtime-mode &,
|
||||||
.is-lad-mode & {
|
.is-lad-mode & {
|
||||||
&:before {
|
$c: $colorTimeRealtimeFgSubtle;
|
||||||
|
.c-clock-symbol__outer {
|
||||||
// Brackets icon
|
// Brackets icon
|
||||||
color: $colorTime;
|
fill: $c;
|
||||||
}
|
}
|
||||||
div[class*="hand"] {
|
div[class*="hand"] {
|
||||||
animation-name: clock-hands;
|
animation-name: clock-hands;
|
||||||
&:before {
|
&:before {
|
||||||
background: $colorTime;
|
background: $c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
.c-conductor__mode-menu {
|
.c-conductor__mode-menu {
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
min-height: 250px;
|
min-height: 50px;
|
||||||
z-index: 70;
|
//We don't need the z-index now that we're using the popup
|
||||||
|
//z-index: 70;
|
||||||
|
|
||||||
[class*="__icon"] {
|
[class*="__icon"] {
|
||||||
filter: $colorKeyFilter;
|
filter: $colorKeyFilter;
|
||||||
|
@ -9,10 +9,15 @@
|
|||||||
/*********************************************** CONDUCTOR LAYOUT */
|
/*********************************************** CONDUCTOR LAYOUT */
|
||||||
.c-conductor {
|
.c-conductor {
|
||||||
&__inputs {
|
&__inputs {
|
||||||
display: contents;
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__time-bounds {
|
/* &__time-bounds {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-column-gap: $interiorMargin;
|
grid-column-gap: $interiorMargin;
|
||||||
grid-row-gap: $interiorMargin;
|
grid-row-gap: $interiorMargin;
|
||||||
@ -39,16 +44,17 @@
|
|||||||
grid-area: tc-end;
|
grid-area: tc-end;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
&__ticks {
|
&__ticks {
|
||||||
grid-area: tc-ticks;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__controls {
|
&__controls {
|
||||||
grid-area: tc-controls;
|
grid-area: tc-controls;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-left: $interiorMargin;
|
margin-left: $interiorMargin;
|
||||||
}
|
}
|
||||||
@ -107,7 +113,8 @@
|
|||||||
background: rgba($timeConductorActiveBg, 0.4);
|
background: rgba($timeConductorActiveBg, 0.4);
|
||||||
border-left-color: $timeConductorActiveBg;
|
border-left-color: $timeConductorActiveBg;
|
||||||
border-right-color: $timeConductorActiveBg;
|
border-right-color: $timeConductorActiveBg;
|
||||||
top: 0; bottom: 0;
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +130,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.phone.portrait & {
|
/* body.phone.portrait & {
|
||||||
.c-conductor__time-bounds {
|
.c-conductor__time-bounds {
|
||||||
grid-row-gap: $interiorMargin;
|
grid-row-gap: $interiorMargin;
|
||||||
grid-template-rows: auto auto;
|
grid-template-rows: auto auto;
|
||||||
@ -160,8 +167,8 @@
|
|||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"tc-mode-icon tc-start tc-start"
|
"tc-mode-icon tc-start tc-start"
|
||||||
"tc-mode-icon tc-end tc-end"
|
"tc-mode-icon tc-end tc-end"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.is-realtime-mode {
|
&.is-realtime-mode {
|
||||||
.c-conductor__time-bounds {
|
.c-conductor__time-bounds {
|
||||||
@ -174,21 +181,20 @@
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-conductor-holder--compact {
|
.c-conductor-holder--compact {
|
||||||
min-height: 22px;
|
//min-height: 22px;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.c-conductor {
|
.c-conductor {
|
||||||
&__inputs,
|
&__inputs,
|
||||||
&__time-bounds {
|
&__time-bounds {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 0 1 auto;
|
||||||
.c-toggle-switch {
|
overflow: hidden;
|
||||||
// Used in independent Time Conductor
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__inputs {
|
&__inputs {
|
||||||
@ -218,38 +224,32 @@
|
|||||||
margin-right: $interiorMarginSm;
|
margin-right: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-direction-indicator {
|
|
||||||
// Holds realtime-mode + and - symbols
|
|
||||||
font-size: 0.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:invalid {
|
input:invalid {
|
||||||
background: rgba($colorFormInvalid, 0.5);
|
background: rgba($colorFormInvalid, 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-realtime-mode {
|
.is-realtime-mode {
|
||||||
.c-conductor__controls button,
|
|
||||||
.c-conductor__delta-button {
|
.c-conductor__delta-button {
|
||||||
@include themedButton($colorTimeBg);
|
//@include themedButton($colorTimeRealtimeBg);
|
||||||
color: $colorTimeFg;
|
color: $colorTimeRealtimeFg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-conductor-input {
|
.c-conductor-input {
|
||||||
&:before {
|
&:before {
|
||||||
color: $colorTime;
|
color: $colorTimeRealtimeFgSubtle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-conductor__end-fixed {
|
.c-conductor__end-fixed {
|
||||||
// Displays last RT udpate
|
// Displays last RT update
|
||||||
color: $colorTime;
|
color: $colorTimeRealtimeFgSubtle;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
// Remove input look
|
// Remove input look
|
||||||
background: none;
|
background: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
color: $colorTime;
|
color: $colorTimeRealtimeFgSubtle;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
@ -259,6 +259,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Do we need this?
|
||||||
[class^='pr-tc-input-menu'] {
|
[class^='pr-tc-input-menu'] {
|
||||||
// Uses ^= here to target both start and end menus
|
// Uses ^= here to target both start and end menus
|
||||||
background: $colorBodyBg;
|
background: $colorBodyBg;
|
||||||
@ -281,30 +282,250 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-shell__time-conductor .pr-tc-input-menu--end {
|
.l-shell__time-conductor .c-tc-input-popup--end {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pr-time-label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
[class^='pr-time'] {
|
&:before {
|
||||||
&[class*='label'] {
|
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
opacity: 0.6;
|
margin-right: $interiorMarginSm;
|
||||||
text-transform: uppercase;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-time-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[class*='controls'] {
|
input {
|
||||||
|
height: 22px;
|
||||||
|
line-height: 1em;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--date input {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--time input {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--buttons {
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__start-end-sep {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--input-and-button {
|
||||||
|
@include wrappedInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************** COMPACT TIME CONDUCTOR */
|
||||||
|
.c-compact-tc,
|
||||||
|
.c-tc-input-popup {
|
||||||
|
[class*='start-end-sep'] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-compact-tc {
|
||||||
|
border-radius: $controlCr;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px $interiorMarginSm;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bounds,
|
||||||
|
&__bounds__value {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
input {
|
> * + * {
|
||||||
height: 22px;
|
margin-left: $interiorMargin;
|
||||||
line-height: 22px;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bounds {
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bounds__value {
|
||||||
|
@include ellipsize();
|
||||||
|
color: $colorTimeRealtimeFg;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
font-size: 0.85em;
|
||||||
margin-right: $interiorMarginSm;
|
margin-right: $interiorMarginSm;
|
||||||
font-size: 1.25em;
|
}
|
||||||
width: 42px;
|
}
|
||||||
|
|
||||||
|
&__current-update {
|
||||||
|
@include ellipsize();
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-direction-indicator {
|
||||||
|
// Holds realtime-mode + and - symbols
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-toggle-switch,
|
||||||
|
.c-clock-symbol {
|
||||||
|
// Used in independent Time Conductor
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-so-view & {
|
||||||
|
// Time Conductor in a Layout frame
|
||||||
|
.c-clock-symbol {
|
||||||
|
$h: 14px;
|
||||||
|
height: $h;
|
||||||
|
width: $h;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='button'] {
|
||||||
|
$p: 0px;
|
||||||
|
padding: $p $p + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-fixed-mode.is-expanded {
|
||||||
|
&.c-compact-tc,
|
||||||
|
.c-tc-input-popup {
|
||||||
|
background: $colorTimeFixedBg;
|
||||||
|
color: $colorTimeFixedFgSubtle;
|
||||||
|
|
||||||
|
em,
|
||||||
|
.pr-time-label:before {
|
||||||
|
color: $colorTimeFixedFg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bounds__valuelue {
|
||||||
|
color: $colorTimeFixedFg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__time-value {
|
||||||
|
color: $colorTimeFixedFg;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='c-button'] {
|
||||||
|
background: $colorTimeFixedBtnBg;
|
||||||
|
color: $colorTimeFixedBtnFg;
|
||||||
|
|
||||||
|
[class*='label'] {
|
||||||
|
color: $colorTimeRealtimeFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-realtime-mode.is-expanded {
|
||||||
|
&.c-compact-tc,
|
||||||
|
.c-tc-input-popup {
|
||||||
|
background: rgba($colorTimeRealtimeBg, 1);
|
||||||
|
color: $colorTimeRealtimeFgSubtle;
|
||||||
|
|
||||||
|
em,
|
||||||
|
.pr-time-label:before {
|
||||||
|
color: $colorTimeRealtimeFg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bounds__valuelue {
|
||||||
|
color: $colorTimeRealtimeFg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__time-value {
|
||||||
|
color: $colorTimeRealtimeFg;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='c-button'] {
|
||||||
|
background: $colorTimeRealtimeBtnBg;
|
||||||
|
color: $colorTimeRealtimeBtnFg;
|
||||||
|
|
||||||
|
[class*='label'] {
|
||||||
|
color: $colorTimeRealtimeFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-compact-tc {
|
||||||
|
&.l-shell__time-conductor {
|
||||||
|
// Main view
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************** INPUTS POPUP DIALOG */
|
||||||
|
.c-tc-input-popup {
|
||||||
|
@include menuOuter();
|
||||||
|
padding: $interiorMarginLg;
|
||||||
|
position: absolute;
|
||||||
|
width: min-content;
|
||||||
|
bottom: 35px;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-top: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='--bottom'] {
|
||||||
|
bottom: auto;
|
||||||
|
top: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__options {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--fixed-mode {
|
||||||
|
.c-tc-input-popup__input-grid {
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 2fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--realtime-mode {
|
||||||
|
.c-tc-input-popup__input-grid {
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 2fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-column-gap: 3px;
|
||||||
|
grid-row-gap: $interiorMargin;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
83
src/plugins/timeConductor/conductorPopUpManager.js
Normal file
83
src/plugins/timeConductor/conductorPopUpManager.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2023, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
import raf from '@/utils/raf';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'configuration'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showConductorPopup: false,
|
||||||
|
positionX: 0,
|
||||||
|
positionY: 0,
|
||||||
|
conductorPopup: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.positionBox = raf(this.positionBox);
|
||||||
|
this.timeConductorOptionsHolder = this.$el;
|
||||||
|
this.timeConductorOptionsHolder.addEventListener('click', this.showPopup);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initializePopup() {
|
||||||
|
this.conductorPopup = this.$refs.conductorPopup.$el;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
window.addEventListener('resize', this.positionBox);
|
||||||
|
document.addEventListener('click', this.handleClickAway);
|
||||||
|
this.positionBox();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showPopup() {
|
||||||
|
if (this.conductorPopup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showConductorPopup = true;
|
||||||
|
},
|
||||||
|
positionBox() {
|
||||||
|
const timeConductorOptionsBox = this.timeConductorOptionsHolder.getBoundingClientRect();
|
||||||
|
const offsetTop = this.conductorPopup.getBoundingClientRect().height;
|
||||||
|
|
||||||
|
//TODO: PositionY should be calculated to be top or bottom based on the location of the conductor options
|
||||||
|
this.positionY = timeConductorOptionsBox.top - offsetTop;
|
||||||
|
this.positionX = 0;
|
||||||
|
},
|
||||||
|
clearPopup() {
|
||||||
|
this.showConductorPopup = false;
|
||||||
|
this.conductorPopup = null;
|
||||||
|
|
||||||
|
document.removeEventListener('click', this.handleClickAway);
|
||||||
|
window.removeEventListener('resize', this.positionBox);
|
||||||
|
},
|
||||||
|
handleClickAway(clickAwayEvent) {
|
||||||
|
if (this.canClose(clickAwayEvent)) {
|
||||||
|
clickAwayEvent.stopPropagation();
|
||||||
|
this.clearPopup();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
canClose(clickAwayEvent) {
|
||||||
|
const isChildMenu = clickAwayEvent.target.closest('.c-menu') !== null;
|
||||||
|
const isPopupElementItem = this.timeConductorOptionsHolder.contains(clickAwayEvent.target);
|
||||||
|
|
||||||
|
return !isChildMenu && !isPopupElementItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
134
src/plugins/timeConductor/independent/IndependentClock.vue
Normal file
134
src/plugins/timeConductor/independent/IndependentClock.vue
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2023, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="clockMenuButton"
|
||||||
|
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||||
|
>
|
||||||
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
|
<button
|
||||||
|
v-if="selectedClock"
|
||||||
|
class="c-icon-button c-button--menu js-clock-button"
|
||||||
|
:class="[
|
||||||
|
buttonCssClass,
|
||||||
|
selectedClock.cssClass
|
||||||
|
]"
|
||||||
|
@click.prevent.stop="showClocksMenu"
|
||||||
|
>
|
||||||
|
<span class="c-button__label">{{ selectedClock.name }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import toggleMixin from '../../../ui/mixins/toggle-mixin';
|
||||||
|
import modeMixin from '../mode-mixin';
|
||||||
|
import { TIME_CONTEXT_EVENTS, FIXED_MODE_KEY } from '../../../api/time/constants'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [toggleMixin, modeMixin],
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
clock: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
const activeClock = this.getActiveClock();
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedClock: activeClock ? this.getClockMetadata(activeClock) : undefined,
|
||||||
|
clocks: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
clock(newClock, oldClock) {
|
||||||
|
this.setViewFromClock(newClock);
|
||||||
|
},
|
||||||
|
enabled(newValue, oldValue) {
|
||||||
|
if (newValue !== undefined && (newValue !== oldValue) && (newValue === true)) {
|
||||||
|
this.setViewFromClock(this.clock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.openmct.time.off(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.loadClocks(this.getMenuOptions());
|
||||||
|
this.setViewFromClock(this.clock);
|
||||||
|
|
||||||
|
this.openmct.time.on(TIME_CONTEXT_EVENTS.clockChanged, this.setViewFromClock);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showClocksMenu() {
|
||||||
|
const elementBoundingClientRect = this.$refs.clockMenuButton.getBoundingClientRect();
|
||||||
|
const x = elementBoundingClientRect.x;
|
||||||
|
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
|
||||||
|
|
||||||
|
const menuOptions = {
|
||||||
|
menuClass: 'c-conductor__clock-menu',
|
||||||
|
placement: this.openmct.menus.menuPlacement.BOTTOM_RIGHT
|
||||||
|
};
|
||||||
|
this.openmct.menus.showSuperMenu(x, y, this.clocks, menuOptions);
|
||||||
|
},
|
||||||
|
getMenuOptions() {
|
||||||
|
let currentGlobalClock = this.getActiveClock();
|
||||||
|
|
||||||
|
//Create copy of active clock so the time API does not get reactified.
|
||||||
|
currentGlobalClock = Object.assign({}, {
|
||||||
|
name: currentGlobalClock.name,
|
||||||
|
clock: currentGlobalClock.key,
|
||||||
|
timeSystem: this.openmct.time.timeSystem().key
|
||||||
|
});
|
||||||
|
|
||||||
|
return [currentGlobalClock];
|
||||||
|
},
|
||||||
|
setClock(clockKey) {
|
||||||
|
this.setViewFromClock(clockKey);
|
||||||
|
|
||||||
|
this.$emit('independentClockUpdated', clockKey);
|
||||||
|
},
|
||||||
|
setViewFromClock(clockOrKey) {
|
||||||
|
let clock = clockOrKey;
|
||||||
|
|
||||||
|
if (!clock.key) {
|
||||||
|
clock = this.getClock(clockOrKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if global clock changes, reload and pull it
|
||||||
|
this.loadModes(this.getMenuOptions());
|
||||||
|
this.selectedClock = this.getClockMetadata(clock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
127
src/plugins/timeConductor/independent/IndependentMode.vue
Normal file
127
src/plugins/timeConductor/independent/IndependentMode.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2023, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref="modeMenuButton"
|
||||||
|
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||||
|
>
|
||||||
|
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||||
|
<button
|
||||||
|
class="c-icon-button c-button--menu js-mode-button"
|
||||||
|
:class="[
|
||||||
|
buttonCssClass,
|
||||||
|
selectedMode.cssClass
|
||||||
|
]"
|
||||||
|
@click.prevent.stop="showModesMenu"
|
||||||
|
>
|
||||||
|
<span class="c-button__label">{{ selectedMode.name }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import toggleMixin from '../../../ui/mixins/toggle-mixin';
|
||||||
|
import modeMixin from '../mode-mixin';
|
||||||
|
import { TIME_CONTEXT_EVENTS, REALTIME_MODE_KEY, FIXED_MODE_KEY } from '../../../api/time/constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [toggleMixin, modeMixin],
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
selectedMode: this.getModeMetadata(this.mode),
|
||||||
|
modes: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
mode: {
|
||||||
|
handler(newMode) {
|
||||||
|
this.setViewFromMode(newMode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled(newValue, oldValue) {
|
||||||
|
if (newValue !== undefined && (newValue !== oldValue) && (newValue === true)) {
|
||||||
|
this.setViewFromMode(this.mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.loadModes();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showModesMenu() {
|
||||||
|
const elementBoundingClientRect = this.$refs.modeMenuButton.getBoundingClientRect();
|
||||||
|
const x = elementBoundingClientRect.x;
|
||||||
|
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
|
||||||
|
|
||||||
|
const menuOptions = {
|
||||||
|
menuClass: 'c-conductor__mode-menu',
|
||||||
|
placement: this.openmct.menus.menuPlacement.BOTTOM_RIGHT
|
||||||
|
};
|
||||||
|
this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
||||||
|
},
|
||||||
|
getMenuOptions() {
|
||||||
|
let menuOptions = [{
|
||||||
|
name: 'Fixed Timespan',
|
||||||
|
timeSystem: 'utc'
|
||||||
|
}];
|
||||||
|
let currentGlobalClock = this.getActiveClock();
|
||||||
|
if (currentGlobalClock !== undefined) {
|
||||||
|
//Create copy of active clock so the time API does not get reactified.
|
||||||
|
currentGlobalClock = Object.assign({}, {
|
||||||
|
name: currentGlobalClock.name,
|
||||||
|
clock: currentGlobalClock.key,
|
||||||
|
timeSystem: this.openmct.time.timeSystem().key
|
||||||
|
});
|
||||||
|
|
||||||
|
menuOptions.push(currentGlobalClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return menuOptions;
|
||||||
|
},
|
||||||
|
setViewFromMode(mode) {
|
||||||
|
this.selectedMode = this.getModeMetadata(mode);
|
||||||
|
},
|
||||||
|
setMode(mode) {
|
||||||
|
this.setViewFromMode(mode);
|
||||||
|
|
||||||
|
this.$emit('independentModeUpdated', mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -21,68 +21,85 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="c-conductor"
|
ref="timeConductorOptionsHolder"
|
||||||
|
class="c-compact-tc"
|
||||||
:class="[
|
:class="[
|
||||||
isFixed ? 'is-fixed-mode' : independentTCEnabled ? 'is-realtime-mode' : 'is-fixed-mode'
|
isFixed ? 'is-fixed-mode' : independentTCEnabled ? 'is-realtime-mode' : 'is-fixed-mode',
|
||||||
|
{ 'is-expanded' : independentTCEnabled }
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="c-conductor__time-bounds">
|
<toggle-switch
|
||||||
<toggle-switch
|
id="independentTCToggle"
|
||||||
id="independentTCToggle"
|
class="c-toggle-switch--mini"
|
||||||
:checked="independentTCEnabled"
|
:checked="independentTCEnabled"
|
||||||
:title="`${independentTCEnabled ? 'Disable' : 'Enable'} independent Time Conductor`"
|
:title="toggleTitle"
|
||||||
@change="toggleIndependentTC"
|
@change="toggleIndependentTC"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ConductorModeIcon />
|
<ConductorModeIcon v-if="independentTCEnabled" />
|
||||||
|
|
||||||
<div
|
<conductor-inputs-fixed
|
||||||
v-if="timeOptions && independentTCEnabled"
|
v-if="showFixedInputs"
|
||||||
class="c-conductor__controls"
|
class="c-compact-tc__bounds--fixed"
|
||||||
>
|
:object-path="objectPath"
|
||||||
<Mode
|
:read-only="true"
|
||||||
v-if="mode"
|
:compact="true"
|
||||||
class="c-conductor__mode-select"
|
/>
|
||||||
:key-string="domainObject.identifier.key"
|
|
||||||
:mode="timeOptions.mode"
|
|
||||||
:enabled="independentTCEnabled"
|
|
||||||
@modeChanged="saveMode"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<conductor-inputs-fixed
|
<conductor-inputs-realtime
|
||||||
v-if="isFixed"
|
v-if="showRealtimeInputs"
|
||||||
:key-string="domainObject.identifier.key"
|
class="c-compact-tc__bounds--real-time"
|
||||||
:object-path="objectPath"
|
:object-path="objectPath"
|
||||||
@updated="saveFixedOffsets"
|
:read-only="true"
|
||||||
/>
|
:compact="true"
|
||||||
|
/>
|
||||||
|
<div class="c-not-button c-not-button--compact c-compact-tc__gear icon-gear"></div>
|
||||||
|
|
||||||
<conductor-inputs-realtime
|
<conductor-pop-up
|
||||||
v-else
|
v-if="showConductorPopup"
|
||||||
:key-string="domainObject.identifier.key"
|
ref="conductorPopup"
|
||||||
:object-path="objectPath"
|
:object-path="objectPath"
|
||||||
@updated="saveClockOffsets"
|
:is-independent="true"
|
||||||
/>
|
:time-options="timeOptions"
|
||||||
</div>
|
:is-fixed="isFixed"
|
||||||
</div>
|
:bottom="true"
|
||||||
|
:position-x="positionX"
|
||||||
|
:position-y="positionY"
|
||||||
|
@popupLoaded="initializePopup"
|
||||||
|
@independentModeUpdated="saveMode"
|
||||||
|
@independentClockUpdated="saveClock"
|
||||||
|
@fixedBoundsUpdated="saveFixedBounds"
|
||||||
|
@clockOffsetsUpdated="saveClockOffsets"
|
||||||
|
@dismiss="clearPopup"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { TIME_CONTEXT_EVENTS, FIXED_MODE_KEY } from '../../../api/time/constants';
|
||||||
import ConductorInputsFixed from "../ConductorInputsFixed.vue";
|
import ConductorInputsFixed from "../ConductorInputsFixed.vue";
|
||||||
import ConductorInputsRealtime from "../ConductorInputsRealtime.vue";
|
import ConductorInputsRealtime from "../ConductorInputsRealtime.vue";
|
||||||
import ConductorModeIcon from "@/plugins/timeConductor/ConductorModeIcon.vue";
|
import ConductorModeIcon from "@/plugins/timeConductor/ConductorModeIcon.vue";
|
||||||
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
||||||
import Mode from "./Mode.vue";
|
import ConductorPopUp from '../ConductorPopUp.vue';
|
||||||
|
import independentTimeConductorPopUpManager from "./independentTimeConductorPopUpManager";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Mode,
|
|
||||||
ConductorModeIcon,
|
ConductorModeIcon,
|
||||||
ConductorInputsRealtime,
|
ConductorInputsRealtime,
|
||||||
ConductorInputsFixed,
|
ConductorInputsFixed,
|
||||||
|
ConductorPopUp,
|
||||||
ToggleSwitch
|
ToggleSwitch
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
mixins: [independentTimeConductorPopUpManager],
|
||||||
|
inject: {
|
||||||
|
openmct: 'openmct',
|
||||||
|
configuration: {
|
||||||
|
from: 'configuration',
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -94,42 +111,83 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
const fixedOffsets = this.openmct.time.getBounds();
|
||||||
|
const clockOffsets = this.openmct.time.getClockOffsets();
|
||||||
|
const clock = this.openmct.time.getClock().key;
|
||||||
|
const mode = this.openmct.time.getMode();
|
||||||
|
const timeOptions = this.domainObject.configuration.timeOptions ?? {
|
||||||
|
clockOffsets,
|
||||||
|
fixedOffsets
|
||||||
|
};
|
||||||
|
|
||||||
|
timeOptions.clock = timeOptions.clock ?? clock;
|
||||||
|
timeOptions.mode = timeOptions.mode ?? mode;
|
||||||
|
|
||||||
|
// check for older configurations that stored a key
|
||||||
|
if (timeOptions.mode.key) {
|
||||||
|
timeOptions.mode = timeOptions.mode.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFixed = timeOptions.mode === FIXED_MODE_KEY;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timeOptions: this.domainObject.configuration.timeOptions || {
|
timeOptions,
|
||||||
clockOffsets: this.openmct.time.clockOffsets(),
|
isFixed,
|
||||||
fixedOffsets: this.openmct.time.bounds()
|
independentTCEnabled: this.domainObject.configuration.useIndependentTime === true,
|
||||||
},
|
viewBounds: {
|
||||||
mode: undefined,
|
start: fixedOffsets.start,
|
||||||
independentTCEnabled: this.domainObject.configuration.useIndependentTime === true
|
end: fixedOffsets.end
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isFixed() {
|
toggleTitle() {
|
||||||
if (!this.mode || !this.mode.key) {
|
return `${this.independentTCEnabled ? 'Disable' : 'Enable'} independent Time Conductor`;
|
||||||
return this.openmct.time.clock() === undefined;
|
},
|
||||||
} else {
|
showFixedInputs() {
|
||||||
return this.mode.key === 'fixed';
|
return this.isFixed && this.independentTCEnabled;
|
||||||
}
|
},
|
||||||
|
showRealtimeInputs() {
|
||||||
|
return !this.isFixed && this.independentTCEnabled;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
handler(domainObject) {
|
handler(domainObject) {
|
||||||
const key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
const key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
|
||||||
if (key !== this.keyString) {
|
if (key !== this.keyString) {
|
||||||
//domain object has changed
|
//domain object has changed
|
||||||
this.destroyIndependentTime();
|
this.destroyIndependentTime();
|
||||||
|
|
||||||
this.independentTCEnabled = domainObject.configuration.useIndependentTime === true;
|
this.independentTCEnabled = domainObject.configuration.useIndependentTime === true;
|
||||||
this.timeOptions = domainObject.configuration.timeOptions || {
|
this.timeOptions = domainObject.configuration.timeOptions ?? {
|
||||||
clockOffsets: this.openmct.time.clockOffsets(),
|
clockOffsets: this.openmct.time.getClockOffsets(),
|
||||||
fixedOffsets: this.openmct.time.bounds()
|
fixedOffsets: this.openmct.time.getBounds()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// these may not be set due to older configurations
|
||||||
|
this.timeOptions.clock = this.timeOptions.clock ?? this.openmct.time.getClock().key;
|
||||||
|
this.timeOptions.mode = this.timeOptions.mode ?? this.openmct.time.getMode();
|
||||||
|
|
||||||
|
// check for older configurations that stored a key
|
||||||
|
if (this.timeOptions.mode.key) {
|
||||||
|
this.timeOptions.mode = this.timeOptions.mode.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isFixed = this.timeOptions.mode === FIXED_MODE_KEY;
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
|
},
|
||||||
|
objectPath: {
|
||||||
|
handler(newPath, oldPath) {
|
||||||
|
//domain object or view has probably changed
|
||||||
|
this.setTimeContext();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -144,84 +202,85 @@ export default {
|
|||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
this.setTimeContext();
|
this.setTimeContext();
|
||||||
|
|
||||||
if (this.timeOptions.mode) {
|
|
||||||
this.mode = this.timeOptions.mode;
|
|
||||||
} else {
|
|
||||||
if (this.timeContext.clock() === undefined) {
|
|
||||||
this.timeOptions.mode = this.mode = { key: 'fixed' };
|
|
||||||
} else {
|
|
||||||
this.timeOptions.mode = this.mode = { key: Object.create(this.timeContext.clock()).key};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.independentTCEnabled) {
|
if (this.independentTCEnabled) {
|
||||||
this.registerIndependentTimeOffsets();
|
this.registerIndependentTimeOffsets();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleIndependentTC() {
|
toggleIndependentTC() {
|
||||||
this.independentTCEnabled = !this.independentTCEnabled;
|
this.independentTCEnabled = !this.independentTCEnabled;
|
||||||
|
|
||||||
if (this.independentTCEnabled) {
|
if (this.independentTCEnabled) {
|
||||||
this.registerIndependentTimeOffsets();
|
this.registerIndependentTimeOffsets();
|
||||||
} else {
|
} else {
|
||||||
|
this.clearPopup();
|
||||||
this.destroyIndependentTime();
|
this.destroyIndependentTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('stateChanged', this.independentTCEnabled);
|
this.$emit('stateChanged', this.independentTCEnabled); // no longer use this, but may be used elsewhere
|
||||||
|
this.openmct.objects.mutate(this.domainObject, 'configuration.useIndependentTime', this.independentTCEnabled);
|
||||||
},
|
},
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
this.stopFollowingTimeContext();
|
if (this.timeContext) {
|
||||||
|
this.stopFollowingTimeContext();
|
||||||
|
}
|
||||||
|
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||||
this.timeContext.on('clock', this.setTimeOptions);
|
this.timeContext.on(TIME_CONTEXT_EVENTS.clockChanged, this.setTimeOptionsClock);
|
||||||
|
this.timeContext.on(TIME_CONTEXT_EVENTS.modeChanged, this.setTimeOptionsMode);
|
||||||
},
|
},
|
||||||
stopFollowingTimeContext() {
|
stopFollowingTimeContext() {
|
||||||
if (this.timeContext) {
|
this.timeContext.off(TIME_CONTEXT_EVENTS.clockChanged, this.setTimeOptionsClock);
|
||||||
this.timeContext.off('clock', this.setTimeOptions);
|
this.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this.setTimeOptionsMode);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setTimeOptions(clock) {
|
setTimeOptionsClock(clock) {
|
||||||
this.timeOptions.clockOffsets = this.timeOptions.clockOffsets || this.timeContext.clockOffsets();
|
this.setTimeOptionsOffsets();
|
||||||
this.timeOptions.fixedOffsets = this.timeOptions.fixedOffsets || this.timeContext.bounds();
|
this.timeOptions.clock = clock.key;
|
||||||
|
|
||||||
if (!this.timeOptions.mode) {
|
|
||||||
this.mode = this.timeContext.clock() === undefined ? {key: 'fixed'} : {key: Object.create(this.timeContext.clock()).key};
|
|
||||||
this.registerIndependentTimeOffsets();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
saveFixedOffsets(offsets) {
|
setTimeOptionsMode(mode) {
|
||||||
const newOptions = Object.assign({}, this.timeOptions, {
|
this.setTimeOptionsOffsets();
|
||||||
fixedOffsets: offsets
|
this.timeOptions.mode = mode;
|
||||||
|
},
|
||||||
|
setTimeOptionsOffsets() {
|
||||||
|
this.timeOptions.clockOffsets = this.timeOptions.clockOffsets ?? this.timeContext.getClockOffsets();
|
||||||
|
this.timeOptions.fixedOffsets = this.timeOptions.fixedOffsets ?? this.timeContext.getBounds();
|
||||||
|
},
|
||||||
|
saveFixedBounds(bounds) {
|
||||||
|
const newOptions = this.updateTimeOptionProperty({
|
||||||
|
fixedOffsets: bounds
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateTimeOptions(newOptions);
|
this.updateTimeOptions(newOptions);
|
||||||
},
|
},
|
||||||
saveClockOffsets(offsets) {
|
saveClockOffsets(offsets) {
|
||||||
const newOptions = Object.assign({}, this.timeOptions, {
|
const newOptions = this.updateTimeOptionProperty({
|
||||||
clockOffsets: offsets
|
clockOffsets: offsets
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateTimeOptions(newOptions);
|
this.updateTimeOptions(newOptions);
|
||||||
},
|
},
|
||||||
saveMode(mode) {
|
saveMode(mode) {
|
||||||
this.mode = mode;
|
this.isFixed = mode === FIXED_MODE_KEY;
|
||||||
const newOptions = Object.assign({}, this.timeOptions, {
|
const newOptions = this.updateTimeOptionProperty({
|
||||||
mode: this.mode
|
mode: mode
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.updateTimeOptions(newOptions);
|
||||||
|
},
|
||||||
|
saveClock(clock) {
|
||||||
|
const newOptions = this.updateTimeOptionProperty({
|
||||||
|
clock
|
||||||
|
});
|
||||||
|
|
||||||
this.updateTimeOptions(newOptions);
|
this.updateTimeOptions(newOptions);
|
||||||
},
|
},
|
||||||
updateTimeOptions(options) {
|
updateTimeOptions(options) {
|
||||||
this.timeOptions = options;
|
this.timeOptions = options;
|
||||||
if (!this.timeOptions.mode) {
|
|
||||||
this.timeOptions.mode = this.mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.registerIndependentTimeOffsets();
|
this.registerIndependentTimeOffsets();
|
||||||
this.$emit('updated', this.timeOptions);
|
this.$emit('updated', this.timeOptions); // no longer use this, but may be used elsewhere
|
||||||
|
this.openmct.objects.mutate(this.domainObject, 'configuration.timeOptions', this.timeOptions);
|
||||||
},
|
},
|
||||||
registerIndependentTimeOffsets() {
|
registerIndependentTimeOffsets() {
|
||||||
if (!this.timeOptions.mode) {
|
const timeContext = this.openmct.time.getIndependentContext(this.keyString);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let offsets;
|
let offsets;
|
||||||
|
|
||||||
if (this.isFixed) {
|
if (this.isFixed) {
|
||||||
@ -234,15 +293,21 @@ export default {
|
|||||||
offsets = this.timeOptions.clockOffsets;
|
offsets = this.timeOptions.clockOffsets;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeContext = this.openmct.time.getIndependentContext(this.keyString);
|
|
||||||
if (!timeContext.hasOwnContext()) {
|
if (!timeContext.hasOwnContext()) {
|
||||||
this.unregisterIndependentTime = this.openmct.time.addIndependentContext(this.keyString, offsets, this.isFixed ? undefined : this.mode.key);
|
this.unregisterIndependentTime = this.openmct.time.addIndependentContext(
|
||||||
|
this.keyString,
|
||||||
|
offsets,
|
||||||
|
this.isFixed ? undefined : this.timeOptions.clock,
|
||||||
|
this.timeOptions.mode
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (this.isFixed) {
|
timeContext.setMode(this.timeOptions.mode);
|
||||||
|
if (timeContext.isFixed()) {
|
||||||
|
//TODO: Do we need to stopClock here? I think technically we should never stop the clock, ever
|
||||||
timeContext.stopClock();
|
timeContext.stopClock();
|
||||||
timeContext.bounds(offsets);
|
timeContext.setBounds(offsets);
|
||||||
} else {
|
} else {
|
||||||
timeContext.clock(this.mode.key, offsets);
|
timeContext.setClock(this.timeOptions.clock, offsets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -250,6 +315,9 @@ export default {
|
|||||||
if (this.unregisterIndependentTime) {
|
if (this.unregisterIndependentTime) {
|
||||||
this.unregisterIndependentTime();
|
this.unregisterIndependentTime();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
updateTimeOptionProperty(option) {
|
||||||
|
return Object.assign({}, this.timeOptions, option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,225 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2023, 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.
|
|
||||||
*****************************************************************************/
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
v-if="modes.length > 1"
|
|
||||||
ref="modeMenuButton"
|
|
||||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
|
||||||
>
|
|
||||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
|
||||||
<button
|
|
||||||
v-if="selectedMode"
|
|
||||||
class="c-button--menu c-mode-button"
|
|
||||||
@click.prevent.stop="showModesMenu"
|
|
||||||
>
|
|
||||||
<span class="c-button__label">{{ selectedMode.name }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import toggleMixin from '../../../ui/mixins/toggle-mixin';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mixins: [toggleMixin],
|
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
|
||||||
mode: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
let clock;
|
|
||||||
if (this.mode && this.mode.key === 'fixed') {
|
|
||||||
clock = undefined;
|
|
||||||
} else {
|
|
||||||
//We want the clock from the global time context here
|
|
||||||
clock = this.openmct.time.clock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clock !== undefined) {
|
|
||||||
//Create copy of active clock so the time API does not get reactified.
|
|
||||||
clock = Object.create(clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
selectedMode: this.getModeOptionForClock(clock),
|
|
||||||
modes: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
mode: {
|
|
||||||
deep: true,
|
|
||||||
handler(newMode) {
|
|
||||||
if (newMode) {
|
|
||||||
this.setViewFromClock(newMode.key === 'fixed' ? undefined : newMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled(newValue, oldValue) {
|
|
||||||
if (newValue !== undefined && (newValue !== oldValue) && (newValue === true)) {
|
|
||||||
this.setViewFromClock(this.mode.key === 'fixed' ? undefined : this.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted: function () {
|
|
||||||
if (this.mode) {
|
|
||||||
this.setViewFromClock(this.mode.key === 'fixed' ? undefined : this.mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.followTimeConductor();
|
|
||||||
},
|
|
||||||
destroyed: function () {
|
|
||||||
this.stopFollowTimeConductor();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
followTimeConductor() {
|
|
||||||
this.openmct.time.on('clock', this.setViewFromClock);
|
|
||||||
},
|
|
||||||
stopFollowTimeConductor() {
|
|
||||||
this.openmct.time.off('clock', this.setViewFromClock);
|
|
||||||
},
|
|
||||||
showModesMenu() {
|
|
||||||
const elementBoundingClientRect = this.$refs.modeMenuButton.getBoundingClientRect();
|
|
||||||
const x = elementBoundingClientRect.x;
|
|
||||||
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
|
|
||||||
|
|
||||||
const menuOptions = {
|
|
||||||
menuClass: 'c-conductor__mode-menu',
|
|
||||||
placement: this.openmct.menus.menuPlacement.BOTTOM_RIGHT
|
|
||||||
};
|
|
||||||
this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
|
||||||
},
|
|
||||||
|
|
||||||
getMenuOptions() {
|
|
||||||
let clocks = [{
|
|
||||||
name: 'Fixed Timespan',
|
|
||||||
timeSystem: 'utc'
|
|
||||||
}];
|
|
||||||
let currentGlobalClock = this.openmct.time.clock();
|
|
||||||
if (currentGlobalClock !== undefined) {
|
|
||||||
//Create copy of active clock so the time API does not get reactified.
|
|
||||||
currentGlobalClock = Object.assign({}, {
|
|
||||||
name: currentGlobalClock.name,
|
|
||||||
clock: currentGlobalClock.key,
|
|
||||||
timeSystem: this.openmct.time.timeSystem().key
|
|
||||||
});
|
|
||||||
|
|
||||||
clocks.push(currentGlobalClock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clocks;
|
|
||||||
},
|
|
||||||
loadClocks() {
|
|
||||||
let clocks = this.getMenuOptions()
|
|
||||||
.map(menuOption => menuOption.clock)
|
|
||||||
.filter(isDefinedAndUnique)
|
|
||||||
.map(this.getClock);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Populate the modes menu with metadata from the available clocks
|
|
||||||
* "Fixed Mode" is always first, and has no defined clock
|
|
||||||
*/
|
|
||||||
this.modes = [undefined]
|
|
||||||
.concat(clocks)
|
|
||||||
.map(this.getModeOptionForClock);
|
|
||||||
|
|
||||||
function isDefinedAndUnique(key, index, array) {
|
|
||||||
return key !== undefined && array.indexOf(key) === index;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getModeOptionForClock(clock) {
|
|
||||||
if (clock === undefined) {
|
|
||||||
const key = 'fixed';
|
|
||||||
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
name: 'Fixed Timespan',
|
|
||||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
|
||||||
cssClass: 'icon-tabular',
|
|
||||||
onItemClicked: () => this.setOption(key)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const key = clock.key;
|
|
||||||
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
name: clock.name,
|
|
||||||
description: "Monitor streaming data in real-time. The Time "
|
|
||||||
+ "Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
|
||||||
cssClass: clock.cssClass || 'icon-clock',
|
|
||||||
onItemClicked: () => this.setOption(key)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getClock(key) {
|
|
||||||
return this.openmct.time.getAllClocks().filter(function (clock) {
|
|
||||||
return clock.key === key;
|
|
||||||
})[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
setOption(clockKey) {
|
|
||||||
let key = clockKey;
|
|
||||||
if (clockKey === 'fixed') {
|
|
||||||
key = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchingOptions = this.getMenuOptions().filter(option => option.clock === key);
|
|
||||||
const clock = matchingOptions.length && matchingOptions[0].clock ? Object.assign({}, matchingOptions[0], { key: matchingOptions[0].clock }) : undefined;
|
|
||||||
this.selectedMode = this.getModeOptionForClock(clock);
|
|
||||||
|
|
||||||
if (this.mode) {
|
|
||||||
this.$emit('modeChanged', { key: clockKey });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setViewFromClock(clock) {
|
|
||||||
this.loadClocks();
|
|
||||||
//retain the mode chosen by the user
|
|
||||||
if (this.mode) {
|
|
||||||
let found = this.modes.find(mode => mode.key === this.selectedMode.key);
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
found = this.modes.find(mode => mode.key === clock && clock.key);
|
|
||||||
this.setOption(found ? this.getModeOptionForClock(clock).key : this.getModeOptionForClock().key);
|
|
||||||
} else if (this.mode.key !== this.selectedMode.key) {
|
|
||||||
this.setOption(this.selectedMode.key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.setOption(this.getModeOptionForClock(clock).key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -0,0 +1,115 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2023, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
import raf from '@/utils/raf';
|
||||||
|
import debounce from '@/utils/debounce';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showConductorPopup: false,
|
||||||
|
positionX: 0,
|
||||||
|
positionY: 0,
|
||||||
|
conductorPopup: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.positionBox = debounce(raf(this.positionBox), 250);
|
||||||
|
this.timeConductorOptionsHolder = this.$el;
|
||||||
|
this.timeConductorOptionsHolder.addEventListener('click', this.showPopup);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearPopup();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initializePopup() {
|
||||||
|
this.conductorPopup = this.$refs.conductorPopup.$el;
|
||||||
|
document.body.appendChild(this.conductorPopup); // remove from container as it (and it's ancestors) have overflow:hidden
|
||||||
|
this.$nextTick(() => {
|
||||||
|
window.addEventListener('resize', this.positionBox);
|
||||||
|
document.addEventListener('click', this.handleClickAway);
|
||||||
|
this.positionBox();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showPopup(clickEvent) {
|
||||||
|
const isToggle = clickEvent.target.classList.contains('c-toggle-switch__slider');
|
||||||
|
|
||||||
|
// no current popup, itc toggled
|
||||||
|
if (!this.conductorPopup && !isToggle) {
|
||||||
|
this.showConductorPopup = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClickAway(clickEvent) {
|
||||||
|
const isToggle = clickEvent.target.classList.contains('c-toggle-switch__slider');
|
||||||
|
|
||||||
|
if (!isToggle && this.canClose(clickEvent)) {
|
||||||
|
clickEvent.stopPropagation();
|
||||||
|
this.clearPopup();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
positionBox() {
|
||||||
|
if (!this.conductorPopup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeConductorOptionsBox = this.timeConductorOptionsHolder.getBoundingClientRect();
|
||||||
|
const topHalf = timeConductorOptionsBox.top < (window.innerHeight / 2);
|
||||||
|
const padding = 5;
|
||||||
|
|
||||||
|
this.positionX = timeConductorOptionsBox.left;
|
||||||
|
|
||||||
|
if (topHalf) {
|
||||||
|
this.positionY = timeConductorOptionsBox.bottom + this.conductorPopup.clientHeight + padding;
|
||||||
|
} else {
|
||||||
|
this.positionY = timeConductorOptionsBox.top - padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetTop = this.conductorPopup.getBoundingClientRect().height;
|
||||||
|
|
||||||
|
const popupRight = this.positionX + this.conductorPopup.clientWidth;
|
||||||
|
const offsetLeft = Math.min(window.innerWidth - popupRight, 0);
|
||||||
|
|
||||||
|
this.positionX = this.positionX + offsetLeft;
|
||||||
|
this.positionY = this.positionY - offsetTop;
|
||||||
|
},
|
||||||
|
clearPopup() {
|
||||||
|
if (!this.conductorPopup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.conductorPopup.parentNode === document.body) {
|
||||||
|
document.body.removeChild(this.conductorPopup);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showConductorPopup = false;
|
||||||
|
this.conductorPopup = null;
|
||||||
|
|
||||||
|
document.removeEventListener('click', this.handleClickAway);
|
||||||
|
window.removeEventListener('resize', this.positionBox);
|
||||||
|
},
|
||||||
|
canClose(clickAwayEvent) {
|
||||||
|
const isChildMenu = clickAwayEvent.target.closest('.c-menu') !== null;
|
||||||
|
const isPopupElementItem = this.timeConductorOptionsHolder.contains(clickAwayEvent.target);
|
||||||
|
|
||||||
|
return !isChildMenu && !isPopupElementItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
88
src/plugins/timeConductor/mode-mixin.js
Normal file
88
src/plugins/timeConductor/mode-mixin.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '../../api/time/constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
buttonCssClass: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadModes() {
|
||||||
|
this.modes = [FIXED_MODE_KEY, REALTIME_MODE_KEY].map(this.getModeMetadata);
|
||||||
|
},
|
||||||
|
loadClocks(menuOptions) {
|
||||||
|
let clocks;
|
||||||
|
|
||||||
|
if (menuOptions) {
|
||||||
|
clocks = menuOptions
|
||||||
|
.map(menuOption => menuOption.clock)
|
||||||
|
.filter(isDefinedAndUnique)
|
||||||
|
.map(this.getClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clocks = clocks.map(this.getClockMetadata);
|
||||||
|
|
||||||
|
function isDefinedAndUnique(key, index, array) {
|
||||||
|
return key !== undefined && array.indexOf(key) === index;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getActiveClock() {
|
||||||
|
const activeClock = this.openmct.time.getClock();
|
||||||
|
|
||||||
|
//Create copy of active clock so the time API does not get reactified.
|
||||||
|
return Object.create(activeClock);
|
||||||
|
},
|
||||||
|
getClock(key) {
|
||||||
|
return this.openmct.time.getAllClocks().find(clock => clock.key === key);
|
||||||
|
},
|
||||||
|
getModeMetadata(mode, testIds = false) {
|
||||||
|
let modeOptions;
|
||||||
|
const key = mode;
|
||||||
|
|
||||||
|
if (key === FIXED_MODE_KEY) {
|
||||||
|
modeOptions = {
|
||||||
|
key,
|
||||||
|
name: 'Fixed Timespan',
|
||||||
|
description: 'Query and explore data that falls between two fixed datetimes.',
|
||||||
|
cssClass: 'icon-tabular',
|
||||||
|
onItemClicked: () => this.setMode(key)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (testIds) {
|
||||||
|
modeOptions.testId = 'conductor-modeOption-fixed';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
modeOptions = {
|
||||||
|
key,
|
||||||
|
name: 'Real-Time',
|
||||||
|
description: 'Monitor streaming data in real-time. The Time Conductor and displays will automatically advance themselves based on the active clock.',
|
||||||
|
cssClass: 'icon-clock',
|
||||||
|
onItemClicked: () => this.setMode(key)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (testIds) {
|
||||||
|
modeOptions.testId = 'conductor-modeOption-realtime';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modeOptions;
|
||||||
|
},
|
||||||
|
getClockMetadata(clock) {
|
||||||
|
const key = clock.key;
|
||||||
|
const clockOptions = {
|
||||||
|
key,
|
||||||
|
name: clock.name,
|
||||||
|
description: "Monitor streaming data in real-time. The Time "
|
||||||
|
+ "Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
||||||
|
cssClass: clock.cssClass || 'icon-clock',
|
||||||
|
onItemClicked: () => this.setClock(key)
|
||||||
|
};
|
||||||
|
|
||||||
|
return clockOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -109,11 +109,20 @@ export default function (config) {
|
|||||||
throwIfError(configResult);
|
throwIfError(configResult);
|
||||||
|
|
||||||
const defaults = config.menuOptions[0];
|
const defaults = config.menuOptions[0];
|
||||||
if (defaults.clock) {
|
const defaultClock = defaults.clock;
|
||||||
openmct.time.clock(defaults.clock, defaults.clockOffsets);
|
|
||||||
openmct.time.timeSystem(defaults.timeSystem, openmct.time.bounds());
|
if (defaultClock) {
|
||||||
|
openmct.time.setClock(defaults.clock, defaults.clockOffsets);
|
||||||
|
openmct.time.setTimeSystem(defaults.timeSystem, openmct.time.getBounds());
|
||||||
} else {
|
} else {
|
||||||
openmct.time.timeSystem(defaults.timeSystem, defaults.bounds);
|
// always have an active clock, regardless of mode
|
||||||
|
const firstClock = config.menuOptions.find(option => option.clock);
|
||||||
|
|
||||||
|
if (firstClock) {
|
||||||
|
openmct.time.setClock(firstClock.clock, firstClock.clockOffsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
openmct.time.setTimeSystem(defaults.timeSystem, defaults.bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
openmct.on('start', function () {
|
openmct.on('start', function () {
|
||||||
|
@ -106,7 +106,7 @@ describe('time conductor', () => {
|
|||||||
|
|
||||||
describe('in realtime mode', () => {
|
describe('in realtime mode', () => {
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
const switcher = appHolder.querySelector('.c-mode-button');
|
const switcher = appHolder.querySelector('.js-mode-button');
|
||||||
const clickEvent = createMouseEvent("click");
|
const clickEvent = createMouseEvent("click");
|
||||||
|
|
||||||
switcher.dispatchEvent(clickEvent);
|
switcher.dispatchEvent(clickEvent);
|
||||||
|
@ -1,181 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="pr-tc-input-menu"
|
|
||||||
:class="{'pr-tc-input-menu--bottom' : bottom === true}"
|
|
||||||
@keydown.enter.prevent
|
|
||||||
@keyup.enter.prevent="submit"
|
|
||||||
@keydown.esc.prevent
|
|
||||||
@keyup.esc.prevent="hide"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<div class="pr-time-label__hrs">Hrs</div>
|
|
||||||
<div class="pr-time-label__mins">Mins</div>
|
|
||||||
<div class="pr-time-label__secs">Secs</div>
|
|
||||||
|
|
||||||
<div class="pr-time-controls">
|
|
||||||
<input
|
|
||||||
ref="inputHrs"
|
|
||||||
v-model="inputHrs"
|
|
||||||
class="pr-time-controls__hrs"
|
|
||||||
step="1"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
max="23"
|
|
||||||
title="Enter 0 - 23"
|
|
||||||
@change="validate()"
|
|
||||||
@keyup="validate()"
|
|
||||||
@focusin="selectAll($event)"
|
|
||||||
@focusout="format('inputHrs')"
|
|
||||||
@wheel="increment($event, 'inputHrs')"
|
|
||||||
>
|
|
||||||
:
|
|
||||||
</div>
|
|
||||||
<div class="pr-time-controls">
|
|
||||||
<input
|
|
||||||
ref="inputMins"
|
|
||||||
v-model="inputMins"
|
|
||||||
type="number"
|
|
||||||
class="pr-time-controls__mins"
|
|
||||||
min="0"
|
|
||||||
max="59"
|
|
||||||
title="Enter 0 - 59"
|
|
||||||
step="1"
|
|
||||||
@change="validate()"
|
|
||||||
@keyup="validate()"
|
|
||||||
@focusin="selectAll($event)"
|
|
||||||
@focusout="format('inputMins')"
|
|
||||||
@wheel="increment($event, 'inputMins')"
|
|
||||||
>
|
|
||||||
:
|
|
||||||
</div>
|
|
||||||
<div class="pr-time-controls">
|
|
||||||
<input
|
|
||||||
ref="inputSecs"
|
|
||||||
v-model="inputSecs"
|
|
||||||
type="number"
|
|
||||||
class="pr-time-controls__secs"
|
|
||||||
min="0"
|
|
||||||
max="59"
|
|
||||||
title="Enter 0 - 59"
|
|
||||||
step="1"
|
|
||||||
@change="validate()"
|
|
||||||
@keyup="validate()"
|
|
||||||
@focusin="selectAll($event)"
|
|
||||||
@focusout="format('inputSecs')"
|
|
||||||
@wheel="increment($event, 'inputSecs')"
|
|
||||||
>
|
|
||||||
<div class="pr-time__buttons">
|
|
||||||
<button
|
|
||||||
class="c-button c-button--major icon-check"
|
|
||||||
:disabled="isDisabled"
|
|
||||||
@click.prevent="submit"
|
|
||||||
></button>
|
|
||||||
<button
|
|
||||||
class="c-button icon-x"
|
|
||||||
@click.prevent="hide"
|
|
||||||
></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
offset: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
bottom: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
inputHrs: '00',
|
|
||||||
inputMins: '00',
|
|
||||||
inputSecs: '00',
|
|
||||||
isDisabled: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.setOffset();
|
|
||||||
document.addEventListener('click', this.hide);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
document.removeEventListener('click', this.hide);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
format(ref) {
|
|
||||||
const curVal = this[ref];
|
|
||||||
this[ref] = curVal.padStart(2, '0');
|
|
||||||
},
|
|
||||||
validate() {
|
|
||||||
let disabled = false;
|
|
||||||
let refs = ['inputHrs', 'inputMins', 'inputSecs'];
|
|
||||||
|
|
||||||
for (let ref of refs) {
|
|
||||||
let min = Number(this.$refs[ref].min);
|
|
||||||
let max = Number(this.$refs[ref].max);
|
|
||||||
let value = Number(this.$refs[ref].value);
|
|
||||||
|
|
||||||
if (value > max || value < min) {
|
|
||||||
disabled = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isDisabled = disabled;
|
|
||||||
},
|
|
||||||
submit() {
|
|
||||||
this.$emit('update', {
|
|
||||||
type: this.type,
|
|
||||||
hours: this.inputHrs,
|
|
||||||
minutes: this.inputMins,
|
|
||||||
seconds: this.inputSecs
|
|
||||||
});
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
this.$emit('hide');
|
|
||||||
},
|
|
||||||
increment($ev, ref) {
|
|
||||||
$ev.preventDefault();
|
|
||||||
const step = (ref === 'inputHrs') ? 1 : 5;
|
|
||||||
const maxVal = (ref === 'inputHrs') ? 23 : 59;
|
|
||||||
let cv = Math.round(parseInt(this[ref], 10) / step) * step;
|
|
||||||
cv = Math.min(maxVal, Math.max(0, ($ev.deltaY < 0) ? cv + step : cv - step));
|
|
||||||
this[ref] = cv.toString().padStart(2, '0');
|
|
||||||
this.validate();
|
|
||||||
},
|
|
||||||
setOffset() {
|
|
||||||
[this.inputHrs, this.inputMins, this.inputSecs] = this.offset.split(':');
|
|
||||||
this.numberSelect('inputHrs');
|
|
||||||
},
|
|
||||||
numberSelect(input) {
|
|
||||||
this.$refs[input].focus();
|
|
||||||
|
|
||||||
// change to text, select, then change back to number
|
|
||||||
// number inputs do not support select()
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs[input].setAttribute('type', 'text');
|
|
||||||
this.$refs[input].select();
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs[input].setAttribute('type', 'number');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
selectAll($ev) {
|
|
||||||
$ev.target.select();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
314
src/plugins/timeConductor/timePopupFixed.vue
Normal file
314
src/plugins/timeConductor/timePopupFixed.vue
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
<template>
|
||||||
|
<form
|
||||||
|
ref="fixedDeltaInput"
|
||||||
|
class="c-tc-input-popup__input-grid"
|
||||||
|
>
|
||||||
|
<div class="pr-time-label"><em>Start</em> Date</div>
|
||||||
|
<div class="pr-time-label">Time Z</div>
|
||||||
|
<div class="pr-time-label"></div>
|
||||||
|
<div class="pr-time-label"><em>End</em> Date</div>
|
||||||
|
<div class="pr-time-label">Time Z</div>
|
||||||
|
<div class="pr-time-label"></div>
|
||||||
|
|
||||||
|
<div class="pr-time-input pr-time-input--date pr-time-input--input-and-button">
|
||||||
|
<input
|
||||||
|
ref="startDate"
|
||||||
|
v-model="formattedBounds.start"
|
||||||
|
class="c-input--datetime"
|
||||||
|
type="text"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
@change="validateAllBounds('startDate'); submitForm()"
|
||||||
|
>
|
||||||
|
<date-picker
|
||||||
|
v-if="isUTCBased"
|
||||||
|
class="c-ctrl-wrapper--menus-left"
|
||||||
|
:default-date-time="formattedBounds.start"
|
||||||
|
:formatter="timeFormatter"
|
||||||
|
@date-selected="startDateSelected"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pr-time-input pr-time-input--time">
|
||||||
|
<input
|
||||||
|
ref="startTime"
|
||||||
|
v-model="formattedBounds.startTime"
|
||||||
|
class="c-input--datetime"
|
||||||
|
type="text"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
@change="validateAllBounds('startDate'); submitForm()"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pr-time-input pr-time-input__start-end-sep icon-arrows-right-left"></div>
|
||||||
|
|
||||||
|
<div class="pr-time-input pr-time-input--date pr-time-input--input-and-button">
|
||||||
|
<input
|
||||||
|
ref="endDate"
|
||||||
|
v-model="formattedBounds.end"
|
||||||
|
class="c-input--datetime"
|
||||||
|
type="text"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
@change="validateAllBounds('endDate'); submitForm()"
|
||||||
|
>
|
||||||
|
<date-picker
|
||||||
|
v-if="isUTCBased"
|
||||||
|
class="c-ctrl-wrapper--menus-left"
|
||||||
|
:default-date-time="formattedBounds.end"
|
||||||
|
:formatter="timeFormatter"
|
||||||
|
@date-selected="endDateSelected"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pr-time-input pr-time-input--time">
|
||||||
|
<input
|
||||||
|
ref="endTime"
|
||||||
|
v-model="formattedBounds.endTime"
|
||||||
|
class="c-input--datetime"
|
||||||
|
type="text"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
@change="validateAllBounds('endDate'); submitForm()"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pr-time-input pr-time-input--buttons">
|
||||||
|
<button
|
||||||
|
class="c-button c-button--major icon-check"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
@click.prevent="submit"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
class="c-button icon-x"
|
||||||
|
@click.prevent="hide"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from "lodash";
|
||||||
|
import DatePicker from "./DatePicker.vue";
|
||||||
|
|
||||||
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DatePicker
|
||||||
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
inputBounds: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
inputTimeSystem: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
let timeSystem = this.openmct.time.getTimeSystem();
|
||||||
|
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
|
let bounds = this.bounds || this.openmct.time.bounds();
|
||||||
|
|
||||||
|
return {
|
||||||
|
timeFormatter,
|
||||||
|
durationFormatter,
|
||||||
|
bounds: {
|
||||||
|
start: bounds.start,
|
||||||
|
end: bounds.end
|
||||||
|
},
|
||||||
|
formattedBounds: {
|
||||||
|
start: timeFormatter.format(bounds.start).split(' ')[0],
|
||||||
|
end: timeFormatter.format(bounds.end).split(' ')[0],
|
||||||
|
startTime: durationFormatter.format(Math.abs(bounds.start)),
|
||||||
|
endTime: durationFormatter.format(Math.abs(bounds.end))
|
||||||
|
},
|
||||||
|
isUTCBased: timeSystem.isUTCBased,
|
||||||
|
isDisabled: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
inputBounds: {
|
||||||
|
handler(newBounds) {
|
||||||
|
this.handleNewBounds(newBounds);
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
inputTimeSystem: {
|
||||||
|
handler(newTimeSystem) {
|
||||||
|
this.setTimeSystem(newTimeSystem);
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
||||||
|
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearAllValidation();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleNewBounds(bounds) {
|
||||||
|
this.setBounds(bounds);
|
||||||
|
this.setViewFromBounds(bounds);
|
||||||
|
},
|
||||||
|
clearAllValidation() {
|
||||||
|
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
|
||||||
|
},
|
||||||
|
clearValidationForInput(input) {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
input.title = '';
|
||||||
|
},
|
||||||
|
setBounds(bounds) {
|
||||||
|
this.bounds = bounds;
|
||||||
|
},
|
||||||
|
setViewFromBounds(bounds) {
|
||||||
|
this.formattedBounds.start = this.timeFormatter.format(bounds.start).split(' ')[0];
|
||||||
|
this.formattedBounds.end = this.timeFormatter.format(bounds.end).split(' ')[0];
|
||||||
|
this.formattedBounds.startTime = this.durationFormatter.format(Math.abs(bounds.start));
|
||||||
|
this.formattedBounds.endTime = this.durationFormatter.format(Math.abs(bounds.end));
|
||||||
|
},
|
||||||
|
setTimeSystem(timeSystem) {
|
||||||
|
this.timeSystem = timeSystem;
|
||||||
|
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
|
this.durationFormatter = this.getFormatter(
|
||||||
|
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
this.isUTCBased = timeSystem.isUTCBased;
|
||||||
|
},
|
||||||
|
getFormatter(key) {
|
||||||
|
return this.openmct.telemetry.getValueFormatter({
|
||||||
|
format: key
|
||||||
|
}).formatter;
|
||||||
|
},
|
||||||
|
setBoundsFromView(dismiss) {
|
||||||
|
if (this.$refs.fixedDeltaInput.checkValidity()) {
|
||||||
|
let start = this.timeFormatter.parse(`${this.formattedBounds.start} ${this.formattedBounds.startTime}`);
|
||||||
|
let end = this.timeFormatter.parse(`${this.formattedBounds.end} ${this.formattedBounds.endTime}`);
|
||||||
|
|
||||||
|
this.$emit('update', {
|
||||||
|
start: start,
|
||||||
|
end: end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dismiss) {
|
||||||
|
this.$emit('dismiss');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
this.validateAllBounds('startDate');
|
||||||
|
this.validateAllBounds('endDate');
|
||||||
|
this.submitForm(!this.isDisabled);
|
||||||
|
},
|
||||||
|
submitForm(dismiss) {
|
||||||
|
// Allow Vue model to catch up to user input.
|
||||||
|
// Submitting form will cause validation messages to display (but only if triggered by button click)
|
||||||
|
this.$nextTick(() => this.setBoundsFromView(dismiss));
|
||||||
|
},
|
||||||
|
validateAllBounds(ref) {
|
||||||
|
if (!this.areBoundsFormatsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let validationResult = {
|
||||||
|
valid: true
|
||||||
|
};
|
||||||
|
const currentInput = this.$refs[ref];
|
||||||
|
|
||||||
|
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||||
|
let boundsValues = {
|
||||||
|
start: this.timeFormatter.parse(`${this.formattedBounds.start} ${this.formattedBounds.startTime}`),
|
||||||
|
end: this.timeFormatter.parse(`${this.formattedBounds.end} ${this.formattedBounds.endTime}`)
|
||||||
|
};
|
||||||
|
//TODO: Do we need limits here? We have conductor limits disabled right now
|
||||||
|
// const limit = this.getBoundsLimit();
|
||||||
|
const limit = false;
|
||||||
|
|
||||||
|
if (this.timeSystem.isUTCBased && limit
|
||||||
|
&& boundsValues.end - boundsValues.start > limit) {
|
||||||
|
if (input === currentInput) {
|
||||||
|
validationResult = {
|
||||||
|
valid: false,
|
||||||
|
message: "Start and end difference exceeds allowable limit"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (input === currentInput) {
|
||||||
|
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.handleValidationResults(input, validationResult);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
areBoundsFormatsValid() {
|
||||||
|
let validationResult = {
|
||||||
|
valid: true
|
||||||
|
};
|
||||||
|
|
||||||
|
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||||
|
const formattedDate = input === this.$refs.startDate
|
||||||
|
? `${this.formattedBounds.start} ${this.formattedBounds.startTime}`
|
||||||
|
: `${this.formattedBounds.end} ${this.formattedBounds.endTime}`
|
||||||
|
;
|
||||||
|
|
||||||
|
if (!this.timeFormatter.validate(formattedDate)) {
|
||||||
|
validationResult = {
|
||||||
|
valid: false,
|
||||||
|
message: 'Invalid date'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.handleValidationResults(input, validationResult);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getBoundsLimit() {
|
||||||
|
const configuration = this.configuration.menuOptions
|
||||||
|
.filter(option => option.timeSystem === this.timeSystem.key)
|
||||||
|
.find(option => option.limit);
|
||||||
|
|
||||||
|
const limit = configuration ? configuration.limit : undefined;
|
||||||
|
|
||||||
|
return limit;
|
||||||
|
},
|
||||||
|
handleValidationResults(input, validationResult) {
|
||||||
|
if (validationResult.valid !== true) {
|
||||||
|
input.setCustomValidity(validationResult.message);
|
||||||
|
input.title = validationResult.message;
|
||||||
|
this.isDisabled = true;
|
||||||
|
} else {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
input.title = '';
|
||||||
|
this.isDisabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.fixedDeltaInput.reportValidity();
|
||||||
|
|
||||||
|
return validationResult.valid;
|
||||||
|
},
|
||||||
|
startDateSelected(date) {
|
||||||
|
this.formattedBounds.start = this.timeFormatter.format(date).split(' ')[0];
|
||||||
|
this.validateAllBounds('startDate');
|
||||||
|
this.submitForm();
|
||||||
|
},
|
||||||
|
endDateSelected(date) {
|
||||||
|
this.formattedBounds.end = this.timeFormatter.format(date).split(' ')[0];
|
||||||
|
this.validateAllBounds('endDate');
|
||||||
|
this.submitForm();
|
||||||
|
},
|
||||||
|
hide($event) {
|
||||||
|
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
||||||
|
this.$emit('dismiss');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
252
src/plugins/timeConductor/timePopupRealtime.vue
Normal file
252
src/plugins/timeConductor/timePopupRealtime.vue
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
<template>
|
||||||
|
<form
|
||||||
|
ref="deltaInput"
|
||||||
|
class="c-tc-input-popup__input-grid"
|
||||||
|
>
|
||||||
|
<div class="pr-time-label icon-minus">Hrs</div>
|
||||||
|
<div class="pr-time-label">Mins</div>
|
||||||
|
<div class="pr-time-label">Secs</div>
|
||||||
|
<div class="pr-time-label"></div>
|
||||||
|
<div class="pr-time-label icon-plus">Hrs</div>
|
||||||
|
<div class="pr-time-label">Mins</div>
|
||||||
|
<div class="pr-time-label">Secs</div>
|
||||||
|
<div class="pr-time-label"></div>
|
||||||
|
|
||||||
|
<div class="pr-time-input">
|
||||||
|
<input
|
||||||
|
ref="startInputHrs"
|
||||||
|
v-model="startInputHrs"
|
||||||
|
class="pr-time-input__hrs"
|
||||||
|
step="1"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="23"
|
||||||
|
title="Enter 0 - 23"
|
||||||
|
@change="validate()"
|
||||||
|
@keyup="validate()"
|
||||||
|
@focusin="selectAll($event)"
|
||||||
|
@focusout="format('startInputHrs')"
|
||||||
|
@wheel="increment($event, 'startInputHrs')"
|
||||||
|
>
|
||||||
|
<b>:</b>
|
||||||
|
</div>
|
||||||
|
<div class="pr-time-input">
|
||||||
|
<input
|
||||||
|
ref="startInputMins"
|
||||||
|
v-model="startInputMins"
|
||||||
|
type="number"
|
||||||
|
class="pr-time-input__mins"
|
||||||
|
min="0"
|
||||||
|
max="59"
|
||||||
|
title="Enter 0 - 59"
|
||||||
|
step="1"
|
||||||
|
@change="validate()"
|
||||||
|
@keyup="validate()"
|
||||||
|
@focusin="selectAll($event)"
|
||||||
|
@focusout="format('startInputMins')"
|
||||||
|
@wheel="increment($event, 'startInputMins')"
|
||||||
|
>
|
||||||
|
<b>:</b>
|
||||||
|
</div>
|
||||||
|
<div class="pr-time-input">
|
||||||
|
<input
|
||||||
|
ref="startInputSecs"
|
||||||
|
v-model="startInputSecs"
|
||||||
|
type="number"
|
||||||
|
class="pr-time-input__secs"
|
||||||
|
min="0"
|
||||||
|
max="59"
|
||||||
|
title="Enter 0 - 59"
|
||||||
|
step="1"
|
||||||
|
@change="validate()"
|
||||||
|
@keyup="validate()"
|
||||||
|
@focusin="selectAll($event)"
|
||||||
|
@focusout="format('startInputSecs')"
|
||||||
|
@wheel="increment($event, 'startInputSecs')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pr-time-input pr-time-input__start-end-sep icon-arrows-right-left"></div>
|
||||||
|
|
||||||
|
<div class="pr-time-input">
|
||||||
|
<input
|
||||||
|
ref="endInputHrs"
|
||||||
|
v-model="endInputHrs"
|
||||||
|
class="pr-time-input__hrs"
|
||||||
|
step="1"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="23"
|
||||||
|
title="Enter 0 - 23"
|
||||||
|
@change="validate()"
|
||||||
|
@keyup="validate()"
|
||||||
|
@focusin="selectAll($event)"
|
||||||
|
@focusout="format('endInputHrs')"
|
||||||
|
@wheel="increment($event, 'endInputHrs')"
|
||||||
|
>
|
||||||
|
<b>:</b>
|
||||||
|
</div>
|
||||||
|
<div class="pr-time-input">
|
||||||
|
<input
|
||||||
|
ref="endInputMins"
|
||||||
|
v-model="endInputMins"
|
||||||
|
type="number"
|
||||||
|
class="pr-time-input__mins"
|
||||||
|
min="0"
|
||||||
|
max="59"
|
||||||
|
title="Enter 0 - 59"
|
||||||
|
step="1"
|
||||||
|
@change="validate()"
|
||||||
|
@keyup="validate()"
|
||||||
|
@focusin="selectAll($event)"
|
||||||
|
@focusout="format('endInputMins')"
|
||||||
|
@wheel="increment($event, 'endInputMins')"
|
||||||
|
>
|
||||||
|
<b>:</b>
|
||||||
|
</div>
|
||||||
|
<div class="pr-time-input">
|
||||||
|
<input
|
||||||
|
ref="endInputSecs"
|
||||||
|
v-model="endInputSecs"
|
||||||
|
type="number"
|
||||||
|
class="pr-time-input__secs"
|
||||||
|
min="0"
|
||||||
|
max="59"
|
||||||
|
title="Enter 0 - 59"
|
||||||
|
step="1"
|
||||||
|
@change="validate()"
|
||||||
|
@keyup="validate()"
|
||||||
|
@focusin="selectAll($event)"
|
||||||
|
@focusout="format('endInputSecs')"
|
||||||
|
@wheel="increment($event, 'endInputSecs')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pr-time-input pr-time-input--buttons">
|
||||||
|
<button
|
||||||
|
class="c-button c-button--major icon-check"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
@click.prevent="submit"
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
class="c-button icon-x"
|
||||||
|
@click.prevent="hide"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
offsets: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
startInputHrs: '00',
|
||||||
|
startInputMins: '00',
|
||||||
|
startInputSecs: '00',
|
||||||
|
endInputHrs: '00',
|
||||||
|
endInputMins: '00',
|
||||||
|
endInputSecs: '00',
|
||||||
|
isDisabled: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
offsets: {
|
||||||
|
handler() {
|
||||||
|
this.setOffsets();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setOffsets();
|
||||||
|
document.addEventListener('click', this.hide);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
document.removeEventListener('click', this.hide);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
format(ref) {
|
||||||
|
const curVal = this[ref];
|
||||||
|
this[ref] = curVal.padStart(2, '0');
|
||||||
|
},
|
||||||
|
validate() {
|
||||||
|
let disabled = false;
|
||||||
|
let refs = ['startInputHrs', 'startInputMins', 'startInputSecs', 'endInputHrs', 'endInputMins', 'endInputSecs'];
|
||||||
|
|
||||||
|
for (let ref of refs) {
|
||||||
|
let min = Number(this.$refs[ref].min);
|
||||||
|
let max = Number(this.$refs[ref].max);
|
||||||
|
let value = Number(this.$refs[ref].value);
|
||||||
|
|
||||||
|
if (value > max || value < min) {
|
||||||
|
disabled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDisabled = disabled;
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
this.$emit('update', {
|
||||||
|
start: {
|
||||||
|
hours: this.startInputHrs,
|
||||||
|
minutes: this.startInputMins,
|
||||||
|
seconds: this.startInputSecs
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
hours: this.endInputHrs,
|
||||||
|
minutes: this.endInputMins,
|
||||||
|
seconds: this.endInputSecs
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$emit('dismiss');
|
||||||
|
},
|
||||||
|
hide($event) {
|
||||||
|
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
||||||
|
this.$emit('dismiss');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
increment($ev, ref) {
|
||||||
|
$ev.preventDefault();
|
||||||
|
const step = (ref === 'startInputHrs' || ref === 'endInputHrs') ? 1 : 5;
|
||||||
|
const maxVal = (ref === 'startInputHrs' || ref === 'endInputHrs') ? 23 : 59;
|
||||||
|
let cv = Math.round(parseInt(this[ref], 10) / step) * step;
|
||||||
|
cv = Math.min(maxVal, Math.max(0, ($ev.deltaY < 0) ? cv + step : cv - step));
|
||||||
|
this[ref] = cv.toString().padStart(2, '0');
|
||||||
|
this.validate();
|
||||||
|
},
|
||||||
|
setOffsets() {
|
||||||
|
[this.startInputHrs, this.startInputMins, this.startInputSecs] = this.offsets.start.split(':');
|
||||||
|
[this.endInputHrs, this.endInputMins, this.endInputSecs] = this.offsets.end.split(':');
|
||||||
|
this.numberSelect('startInputHrs');
|
||||||
|
},
|
||||||
|
numberSelect(input) {
|
||||||
|
this.$refs[input].focus();
|
||||||
|
|
||||||
|
// change to text, select, then change back to number
|
||||||
|
// number inputs do not support select()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs[input] === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs[input].setAttribute('type', 'text');
|
||||||
|
this.$refs[input].select();
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs[input].setAttribute('type', 'number');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectAll($ev) {
|
||||||
|
$ev.target.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -81,6 +81,7 @@ $colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
|||||||
$colorA: #ccc;
|
$colorA: #ccc;
|
||||||
$colorAHov: #fff;
|
$colorAHov: #fff;
|
||||||
$filterHov: brightness(1.3) contrast(1.5); // Tree, location items
|
$filterHov: brightness(1.3) contrast(1.5); // Tree, location items
|
||||||
|
$filterHovSubtle: brightness(1.2) contrast(1.2);
|
||||||
$colorSelectedBg: rgba($colorKey, 0.3);
|
$colorSelectedBg: rgba($colorKey, 0.3);
|
||||||
$colorSelectedFg: pullForward($colorBodyFg, 20%);
|
$colorSelectedFg: pullForward($colorBodyFg, 20%);
|
||||||
|
|
||||||
@ -139,13 +140,30 @@ $colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%);
|
|||||||
$colorKeySubtle: pushBack($colorKey, 10%);
|
$colorKeySubtle: pushBack($colorKey, 10%);
|
||||||
|
|
||||||
// Time Colors
|
// Time Colors
|
||||||
$colorTime: #618cff;
|
$colorTimeFixed: #59554C;
|
||||||
$colorTimeBg: $colorTime;
|
$colorTimeFixedBg: $colorTimeFixed;
|
||||||
$colorTimeFg: pullForward($colorTimeBg, 30%);
|
$colorTimeFixedFg: #eee;
|
||||||
$colorTimeHov: pullForward($colorTime, 10%);
|
$colorTimeFixedFgSubtle: #B2AA98;
|
||||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
$colorTimeFixedHov: pullForward($colorTimeFixed, 10%);
|
||||||
|
$colorTimeFixedSubtle: pushBack($colorTimeFixed, 20%);
|
||||||
|
$colorTimeFixedBtnBg: pullForward($colorTimeFixed, 5%);
|
||||||
|
$colorTimeFixedBtnFg: $colorTimeFixedFgSubtle;
|
||||||
|
$colorTimeFixedBtnBgMajor: #a09375;
|
||||||
|
$colorTimeFixedBtnFgMajor: #fff;
|
||||||
|
|
||||||
|
$colorTimeRealtime: #445890;
|
||||||
|
$colorTimeRealtimeBg: $colorTimeRealtime;
|
||||||
|
$colorTimeRealtimeFg: #eee;
|
||||||
|
$colorTimeRealtimeFgSubtle: #88B0FF;
|
||||||
|
$colorTimeRealtimeHov: pullForward($colorTimeRealtime, 10%);
|
||||||
|
$colorTimeRealtimeSubtle: pushBack($colorTimeRealtime, 20%);
|
||||||
|
$colorTimeRealtimeBtnBg: pullForward($colorTimeRealtime, 5%);
|
||||||
|
$colorTimeRealtimeBtnFg: $colorTimeRealtimeFgSubtle;
|
||||||
|
$colorTimeRealtimeBtnBgMajor: #588ffa;
|
||||||
|
$colorTimeRealtimeBtnFgMajor: #fff;
|
||||||
|
|
||||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
$colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov
|
||||||
$timeConductorAxisHoverFilter: brightness(1.2);
|
$timeConductorAxisHoverFilter: brightness(1.2);
|
||||||
$timeConductorActiveBg: $colorKey;
|
$timeConductorActiveBg: $colorKey;
|
||||||
$timeConductorActivePanBg: #226074;
|
$timeConductorActivePanBg: #226074;
|
||||||
@ -241,7 +259,7 @@ $controlDisabledOpacity: 0.2;
|
|||||||
$colorMenuBg: $colorBodyBg;
|
$colorMenuBg: $colorBodyBg;
|
||||||
$colorMenuFg: $colorBodyFg;
|
$colorMenuFg: $colorBodyFg;
|
||||||
$colorMenuIc: $colorKey;
|
$colorMenuIc: $colorKey;
|
||||||
$filterMenu: brightness(1.4);
|
$filterMenu: brightness(1.2);
|
||||||
$colorMenuHovBg: rgba($colorKey, 0.5);
|
$colorMenuHovBg: rgba($colorKey, 0.5);
|
||||||
$colorMenuHovFg: $colorBodyFgEm;
|
$colorMenuHovFg: $colorBodyFgEm;
|
||||||
$colorMenuHovIc: $colorMenuHovFg;
|
$colorMenuHovIc: $colorMenuHovFg;
|
||||||
|
@ -85,6 +85,7 @@ $colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
|||||||
$colorA: #ccc;
|
$colorA: #ccc;
|
||||||
$colorAHov: #fff;
|
$colorAHov: #fff;
|
||||||
$filterHov: brightness(1.3) contrast(1.5); // Tree, location items
|
$filterHov: brightness(1.3) contrast(1.5); // Tree, location items
|
||||||
|
$filterHovSubtle: brightness(1.2) contrast(1.2);
|
||||||
$colorSelectedBg: rgba($colorKey, 0.3);
|
$colorSelectedBg: rgba($colorKey, 0.3);
|
||||||
$colorSelectedFg: pullForward($colorBodyFg, 20%);
|
$colorSelectedFg: pullForward($colorBodyFg, 20%);
|
||||||
|
|
||||||
@ -143,13 +144,30 @@ $colorBodyBgSubtleHov: pushBack($colorKey, 50%);
|
|||||||
$colorKeySubtle: pushBack($colorKey, 10%);
|
$colorKeySubtle: pushBack($colorKey, 10%);
|
||||||
|
|
||||||
// Time Colors
|
// Time Colors
|
||||||
$colorTime: #618cff;
|
$colorTimeFixed: #59554C;
|
||||||
$colorTimeBg: $colorTime;
|
$colorTimeFixedBg: $colorTimeFixed;
|
||||||
$colorTimeFg: pullForward($colorTimeBg, 30%);
|
$colorTimeFixedFg: #eee;
|
||||||
$colorTimeHov: pullForward($colorTime, 10%);
|
$colorTimeFixedFgSubtle: #B2AA98;
|
||||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
$colorTimeFixedHov: pullForward($colorTimeFixed, 10%);
|
||||||
|
$colorTimeFixedSubtle: pushBack($colorTimeFixed, 20%);
|
||||||
|
$colorTimeFixedBtnBg: pullForward($colorTimeFixed, 5%);
|
||||||
|
$colorTimeFixedBtnFg: $colorTimeFixedFgSubtle;
|
||||||
|
$colorTimeFixedBtnBgMajor: #a09375;
|
||||||
|
$colorTimeFixedBtnFgMajor: #fff;
|
||||||
|
|
||||||
|
$colorTimeRealtime: #445890;
|
||||||
|
$colorTimeRealtimeBg: $colorTimeRealtime;
|
||||||
|
$colorTimeRealtimeFg: #eee;
|
||||||
|
$colorTimeRealtimeFgSubtle: #88B0FF;
|
||||||
|
$colorTimeRealtimeHov: pullForward($colorTimeRealtime, 10%);
|
||||||
|
$colorTimeRealtimeSubtle: pushBack($colorTimeRealtime, 20%);
|
||||||
|
$colorTimeRealtimeBtnBg: pullForward($colorTimeRealtime, 5%);
|
||||||
|
$colorTimeRealtimeBtnFg: $colorTimeRealtimeFgSubtle;
|
||||||
|
$colorTimeRealtimeBtnBgMajor: #588ffa;
|
||||||
|
$colorTimeRealtimeBtnFgMajor: #fff;
|
||||||
|
|
||||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
$colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov
|
||||||
$timeConductorAxisHoverFilter: brightness(1.2);
|
$timeConductorAxisHoverFilter: brightness(1.2);
|
||||||
$timeConductorActiveBg: $colorKey;
|
$timeConductorActiveBg: $colorKey;
|
||||||
$timeConductorActivePanBg: #226074;
|
$timeConductorActivePanBg: #226074;
|
||||||
|
@ -81,6 +81,7 @@ $colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
|||||||
$colorA: $colorBodyFg;
|
$colorA: $colorBodyFg;
|
||||||
$colorAHov: $colorKey;
|
$colorAHov: $colorKey;
|
||||||
$filterHov: hue-rotate(-10deg) brightness(0.8) contrast(2); // Tree, location items
|
$filterHov: hue-rotate(-10deg) brightness(0.8) contrast(2); // Tree, location items
|
||||||
|
$filterHovSubtle: hue-rotate(-8deg) brightness(0.5) contrast(1.2);
|
||||||
$colorSelectedBg: pushBack($colorKey, 40%);
|
$colorSelectedBg: pushBack($colorKey, 40%);
|
||||||
$colorSelectedFg: pullForward($colorBodyFg, 10%);
|
$colorSelectedFg: pullForward($colorBodyFg, 10%);
|
||||||
|
|
||||||
@ -139,13 +140,30 @@ $colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%);
|
|||||||
$colorKeySubtle: pushBack($colorKey, 20%);
|
$colorKeySubtle: pushBack($colorKey, 20%);
|
||||||
|
|
||||||
// Time Colors
|
// Time Colors
|
||||||
$colorTime: #618cff;
|
$colorTimeFixed: #59554C;
|
||||||
$colorTimeBg: $colorTime;
|
$colorTimeFixedBg: $colorTimeFixed;
|
||||||
$colorTimeFg: $colorBodyBg;
|
$colorTimeFixedFg: #eee;
|
||||||
$colorTimeHov: pushBack($colorTime, 5%);
|
$colorTimeFixedFgSubtle: #B2AA98;
|
||||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
$colorTimeFixedHov: pullForward($colorTimeFixed, 10%);
|
||||||
|
$colorTimeFixedSubtle: pushBack($colorTimeFixed, 20%);
|
||||||
|
$colorTimeFixedBtnBg: pullForward($colorTimeFixed, 5%);
|
||||||
|
$colorTimeFixedBtnFg: $colorTimeFixedFgSubtle;
|
||||||
|
$colorTimeFixedBtnBgMajor: #a09375;
|
||||||
|
$colorTimeFixedBtnFgMajor: #fff;
|
||||||
|
|
||||||
|
$colorTimeRealtime: #445890;
|
||||||
|
$colorTimeRealtimeBg: $colorTimeRealtime;
|
||||||
|
$colorTimeRealtimeFg: #eee;
|
||||||
|
$colorTimeRealtimeFgSubtle: #88B0FF;
|
||||||
|
$colorTimeRealtimeHov: pullForward($colorTimeRealtime, 10%);
|
||||||
|
$colorTimeRealtimeSubtle: pushBack($colorTimeRealtime, 20%);
|
||||||
|
$colorTimeRealtimeBtnBg: pullForward($colorTimeRealtime, 5%);
|
||||||
|
$colorTimeRealtimeBtnFg: $colorTimeRealtimeFgSubtle;
|
||||||
|
$colorTimeRealtimeBtnBgMajor: #588ffa;
|
||||||
|
$colorTimeRealtimeBtnFgMajor: #fff;
|
||||||
|
|
||||||
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||||
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
$colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov
|
||||||
$timeConductorAxisHoverFilter: brightness(0.8);
|
$timeConductorAxisHoverFilter: brightness(0.8);
|
||||||
$timeConductorActiveBg: $colorKey;
|
$timeConductorActiveBg: $colorKey;
|
||||||
$timeConductorActivePanBg: #A0CDE1;
|
$timeConductorActivePanBg: #A0CDE1;
|
||||||
|
@ -244,6 +244,13 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-not-button {
|
||||||
|
// Use within a holder that's clickable; use to indicate interactability
|
||||||
|
@include cButtonLayout();
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************** DISCLOSURE CONTROLS */
|
/******************************************************** DISCLOSURE CONTROLS */
|
||||||
/********* Disclosure Button */
|
/********* Disclosure Button */
|
||||||
// Provides a downward arrow icon that when clicked displays additional options and/or info.
|
// Provides a downward arrow icon that when clicked displays additional options and/or info.
|
||||||
|
@ -63,6 +63,11 @@ div {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.u-flex-spreader {
|
||||||
|
// Pushes against elements in a flex layout to spread them out
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************** BROWSER ELEMENTS */
|
/******************************************************** BROWSER ELEMENTS */
|
||||||
body.desktop {
|
body.desktop {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
@ -521,24 +521,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin cButton() {
|
@mixin cButtonLayout() {
|
||||||
@include cControl();
|
$pad: $interiorMargin;
|
||||||
@include cControlHov();
|
padding: $pad floor($pad * 1.25);
|
||||||
@include themedButton();
|
|
||||||
border-radius: $controlCr;
|
|
||||||
color: $colorBtnFg;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: $interiorMargin floor($interiorMargin * 1.25);
|
|
||||||
|
|
||||||
&:after,
|
&:after,
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-left: $interiorMarginSm;
|
margin-left: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[class*='--compact'] {
|
||||||
|
padding: floor(math.div($pad, 1.5)) $pad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin cButton() {
|
||||||
|
@include cControl();
|
||||||
|
@include cControlHov();
|
||||||
|
@include themedButton();
|
||||||
|
@include cButtonLayout();
|
||||||
|
border-radius: $controlCr;
|
||||||
|
color: $colorBtnFg;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&[class*="--major"],
|
&[class*="--major"],
|
||||||
&[class*='is-active']{
|
&[class*='is-active']{
|
||||||
background: $colorBtnMajorBg;
|
background: $colorBtnMajorBg !important;
|
||||||
color: $colorBtnMajorFg;
|
color: $colorBtnMajorFg !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[class*='--caution'] {
|
&[class*='--caution'] {
|
||||||
@ -576,7 +585,7 @@
|
|||||||
*:before {
|
*:before {
|
||||||
// *:before handles any nested containers that may contain glyph elements
|
// *:before handles any nested containers that may contain glyph elements
|
||||||
// Needed for c-togglebutton.
|
// Needed for c-togglebutton.
|
||||||
font-size: 1.25em;
|
font-size: 1.15em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,15 @@
|
|||||||
'has-complex-content': complexContent
|
'has-complex-content': complexContent
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="supportsIndependentTime"
|
||||||
|
class="c-conductor-holder--compact"
|
||||||
|
>
|
||||||
|
<independent-time-conductor
|
||||||
|
:domain-object="domainObject"
|
||||||
|
:object-path="objectPath"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<NotebookMenuSwitcher
|
<NotebookMenuSwitcher
|
||||||
v-if="notebookEnabled"
|
v-if="notebookEnabled"
|
||||||
:domain-object="domainObject"
|
:domain-object="domainObject"
|
||||||
@ -105,6 +114,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import ObjectView from './ObjectView.vue';
|
import ObjectView from './ObjectView.vue';
|
||||||
import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue';
|
import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue';
|
||||||
|
import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue';
|
||||||
|
|
||||||
const SIMPLE_CONTENT_TYPES = [
|
const SIMPLE_CONTENT_TYPES = [
|
||||||
'clock',
|
'clock',
|
||||||
@ -118,7 +128,8 @@ const CSS_WIDTH_LESS_STR = '--width-less-than-';
|
|||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ObjectView,
|
ObjectView,
|
||||||
NotebookMenuSwitcher
|
NotebookMenuSwitcher,
|
||||||
|
IndependentTimeConductor
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
@ -163,6 +174,11 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
statusClass() {
|
statusClass() {
|
||||||
return (this.status) ? `is-status--${this.status}` : '';
|
return (this.status) ? `is-status--${this.status}` : '';
|
||||||
|
},
|
||||||
|
supportsIndependentTime() {
|
||||||
|
// const viewKey = this.getViewKey();
|
||||||
|
|
||||||
|
return true; //this.domainObject && SupportedViewTypes.includes(viewKey);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -233,6 +249,14 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.widthClass = wClass.trimStart();
|
this.widthClass = wClass.trimStart();
|
||||||
|
},
|
||||||
|
getViewKey() {
|
||||||
|
let viewKey = this.this.$refs.objectView.viewKey;
|
||||||
|
if (this.objectViewKey) {
|
||||||
|
viewKey = this.objectViewKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
v-if="supportsIndependentTime"
|
v-if="supportsIndependentTime && false"
|
||||||
class="c-conductor-holder--compact l-shell__main-independent-time-conductor"
|
class="c-conductor-holder--compact l-shell__main-independent-time-conductor"
|
||||||
>
|
>
|
||||||
<independent-time-conductor
|
<independent-time-conductor
|
||||||
:domain-object="domainObject"
|
:domain-object="domainObject"
|
||||||
:object-path="path"
|
:object-path="path"
|
||||||
@stateChanged="updateIndependentTimeState"
|
|
||||||
@updated="saveTimeOptions"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -457,13 +455,6 @@ export default {
|
|||||||
if (elemToStyle !== undefined) {
|
if (elemToStyle !== undefined) {
|
||||||
elemToStyle.dataset.font = newFont;
|
elemToStyle.dataset.font = newFont;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
//Should the domainObject be updated in the Independent Time conductor component itself?
|
|
||||||
updateIndependentTimeState(useIndependentTime) {
|
|
||||||
this.openmct.objects.mutate(this.domainObject, 'configuration.useIndependentTime', useIndependentTime);
|
|
||||||
},
|
|
||||||
saveTimeOptions(options) {
|
|
||||||
this.openmct.objects.mutate(this.domainObject, 'configuration.timeOptions', options);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -91,7 +91,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateNowMarker() {
|
updateNowMarker() {
|
||||||
if (this.openmct.time.clock() === undefined) {
|
if (this.openmct.time.getClock() === undefined) {
|
||||||
let nowMarker = document.querySelector('.nowMarker');
|
let nowMarker = document.querySelector('.nowMarker');
|
||||||
if (nowMarker) {
|
if (nowMarker) {
|
||||||
nowMarker.classList.add('hidden');
|
nowMarker.classList.add('hidden');
|
||||||
@ -101,7 +101,7 @@ export default {
|
|||||||
if (nowMarker) {
|
if (nowMarker) {
|
||||||
nowMarker.classList.remove('hidden');
|
nowMarker.classList.remove('hidden');
|
||||||
nowMarker.style.height = this.contentHeight + 'px';
|
nowMarker.style.height = this.contentHeight + 'px';
|
||||||
const nowTimeStamp = this.openmct.time.clock().currentValue();
|
const nowTimeStamp = this.openmct.time.getClock().currentValue();
|
||||||
const now = this.xScale(nowTimeStamp);
|
const now = this.xScale(nowTimeStamp);
|
||||||
nowMarker.style.left = now + this.offset + 'px';
|
nowMarker.style.left = now + this.offset + 'px';
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (timeSystem === undefined) {
|
if (timeSystem === undefined) {
|
||||||
timeSystem = this.openmct.time.timeSystem();
|
timeSystem = this.openmct.time.getTimeSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeSystem.isUTCBased) {
|
if (timeSystem.isUTCBased) {
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
.c-object-label {
|
.c-object-label {
|
||||||
font-size: 1.05em;
|
font-size: 1.05em;
|
||||||
|
min-width: 20%;
|
||||||
|
|
||||||
&__type-icon {
|
&__type-icon {
|
||||||
opacity: $objectLabelTypeIconOpacity;
|
opacity: $objectLabelTypeIconOpacity;
|
||||||
}
|
}
|
||||||
@ -37,7 +39,8 @@
|
|||||||
/*************************** FRAME CONTROLS */
|
/*************************** FRAME CONTROLS */
|
||||||
&__frame-controls {
|
&__frame-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&__btns,
|
&__btns,
|
||||||
&__more {
|
&__more {
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
.c-object-label {
|
.c-object-label {
|
||||||
// <a> tag and draggable element that holds type icon and name.
|
// <a> tag and draggable element that holds type icon and name.
|
||||||
// Used mostly in trees and lists
|
// Used mostly in trees and lists
|
||||||
|
@include ellipsize();
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
> * + * { margin-left: $interiorMargin; }
|
> * + * { margin-left: $interiorMargin; }
|
||||||
|
|
||||||
|
@ -1,9 +1,25 @@
|
|||||||
@use 'sass:math';
|
@use 'sass:math';
|
||||||
|
|
||||||
.c-toggle-switch {
|
@mixin toggleSwitch($d: 12px, $m: 2px, $bg: $colorBtnBg) {
|
||||||
$d: 12px;
|
|
||||||
$m: 2px;
|
|
||||||
$br: math.div($d, 1.5);
|
$br: math.div($d, 1.5);
|
||||||
|
|
||||||
|
.c-toggle-switch__slider {
|
||||||
|
background: $bg;
|
||||||
|
border-radius: $br;
|
||||||
|
height: $d + ($m*2);
|
||||||
|
width: $d*2 + $m*2;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
// Knob
|
||||||
|
border-radius: floor($br * 0.8);
|
||||||
|
box-shadow: rgba(black, 0.4) 0 0 2px;
|
||||||
|
height: $d; width: $d;
|
||||||
|
top: $m; left: $m; right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-toggle-switch {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -20,6 +36,26 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__slider {
|
||||||
|
// Sits within __switch
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
// Knob
|
||||||
|
background: $colorBtnFg; // TODO: make discrete theme constants for these colors
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
transition: transform 100ms ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
@ -35,31 +71,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__slider {
|
@include toggleSwitch();
|
||||||
// Sits within __switch
|
}
|
||||||
background: $colorBtnBg; // TODO: make discrete theme constants for these colors
|
|
||||||
border-radius: $br;
|
.c-toggle-switch--mini {
|
||||||
display: inline-block;
|
@include toggleSwitch($d: 9px, $m: 0px);
|
||||||
height: $d + ($m*2);
|
|
||||||
position: relative;
|
|
||||||
width: $d*2 + $m*2;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
// Knob
|
|
||||||
background: $colorBtnFg; // TODO: make discrete theme constants for these colors
|
|
||||||
border-radius: floor($br * 0.8);
|
|
||||||
box-shadow: rgba(black, 0.4) 0 0 2px;
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
height: $d; width: $d;
|
|
||||||
top: $m; left: $m; right: auto;
|
|
||||||
transition: transform 100ms ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
margin-left: $interiorMarginSm;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="l-browse-bar__end">
|
<div class="l-browse-bar__end">
|
||||||
|
<div
|
||||||
|
v-if="supportsIndependentTime"
|
||||||
|
class="c-conductor-holder--compact l-shell__main-independent-time-conductor"
|
||||||
|
>
|
||||||
|
<independent-time-conductor
|
||||||
|
:domain-object="domainObject"
|
||||||
|
:object-path="openmct.router.path"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<ViewSwitcher
|
<ViewSwitcher
|
||||||
v-if="!isEditing"
|
v-if="!isEditing"
|
||||||
:current-view="currentView"
|
:current-view="currentView"
|
||||||
@ -126,11 +135,20 @@
|
|||||||
<script>
|
<script>
|
||||||
import ViewSwitcher from './ViewSwitcher.vue';
|
import ViewSwitcher from './ViewSwitcher.vue';
|
||||||
import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue';
|
import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue';
|
||||||
|
import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue';
|
||||||
|
|
||||||
|
const SupportedViewTypes = [
|
||||||
|
'plot-stacked',
|
||||||
|
'plot-overlay',
|
||||||
|
'bar-graph.view',
|
||||||
|
'time-strip.view'
|
||||||
|
];
|
||||||
|
|
||||||
const PLACEHOLDER_OBJECT = {};
|
const PLACEHOLDER_OBJECT = {};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
IndependentTimeConductor,
|
||||||
NotebookMenuSwitcher,
|
NotebookMenuSwitcher,
|
||||||
ViewSwitcher
|
ViewSwitcher
|
||||||
},
|
},
|
||||||
@ -220,6 +238,11 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return 'Unlocked for editing - click to lock.';
|
return 'Unlocked for editing - click to lock.';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
supportsIndependentTime() {
|
||||||
|
const viewKey = this.getViewKey();
|
||||||
|
|
||||||
|
return this.domainObject && SupportedViewTypes.includes(viewKey);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -291,6 +314,14 @@ export default {
|
|||||||
edit() {
|
edit() {
|
||||||
this.openmct.editor.edit();
|
this.openmct.editor.edit();
|
||||||
},
|
},
|
||||||
|
getViewKey() {
|
||||||
|
let viewKey = this.viewKey;
|
||||||
|
if (this.objectViewKey) {
|
||||||
|
viewKey = this.objectViewKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewKey;
|
||||||
|
},
|
||||||
promptUserandCancelEditing() {
|
promptUserandCancelEditing() {
|
||||||
let dialog = this.openmct.overlays.dialog({
|
let dialog = this.openmct.overlays.dialog({
|
||||||
iconClass: 'alert',
|
iconClass: 'alert',
|
||||||
|
@ -283,17 +283,6 @@
|
|||||||
flex: 1 1 auto !important;
|
flex: 1 1 auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__time-conductor {
|
|
||||||
border-top: 1px solid $colorInteriorBorder;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-top: $interiorMargin;
|
|
||||||
|
|
||||||
> * + * {
|
|
||||||
margin-top: $interiorMargin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__main {
|
&__main {
|
||||||
> .l-pane {
|
> .l-pane {
|
||||||
padding: nth($shellPanePad, 1) 0;
|
padding: nth($shellPanePad, 1) 0;
|
||||||
@ -377,10 +366,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
[class*="__"] {
|
//[class*="__"] {
|
||||||
// Removes extraneous horizontal white space
|
// // Removes extraneous horizontal white space
|
||||||
display: inline-flex;
|
// display: inline-flex;
|
||||||
}
|
//}
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-left: $interiorMarginSm;
|
margin-left: $interiorMarginSm;
|
||||||
|
8
src/utils/debounce.js
Normal file
8
src/utils/debounce.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default function debounce(func, delay) {
|
||||||
|
let debounceTimer;
|
||||||
|
|
||||||
|
return function (...args) {
|
||||||
|
clearTimeout(debounceTimer);
|
||||||
|
debounceTimer = setTimeout(() => func(...args), delay);
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user