Compare commits
	
		
			28 Commits
		
	
	
		
			4.0.0-rc1
			...
			time-api-e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 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 | 
| @@ -134,6 +134,11 @@ class TimeAPI extends GlobalTimeContext { | ||||
|      */ | ||||
|     addIndependentContext(key, value, clockKey) { | ||||
|         let timeContext = this.getIndependentContext(key); | ||||
|         let upstreamClock; | ||||
|         if (timeContext.upstreamTimeContext) { | ||||
|             upstreamClock = timeContext.upstreamTimeContext.clock(); | ||||
|         } | ||||
|  | ||||
|         //stop following upstream time context since the view has it's own | ||||
|         timeContext.resetContext(); | ||||
|  | ||||
| @@ -141,6 +146,11 @@ class TimeAPI extends GlobalTimeContext { | ||||
|             timeContext.clock(clockKey, value); | ||||
|         } else { | ||||
|             timeContext.stopClock(); | ||||
|             //upstream clock was active, but now we don't have one | ||||
|             if (upstreamClock) { | ||||
|                 timeContext.emit('clock', timeContext.activeClock); | ||||
|             } | ||||
|  | ||||
|             timeContext.bounds(value); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -22,12 +22,25 @@ | ||||
|  | ||||
| import EventEmitter from 'EventEmitter'; | ||||
|  | ||||
| export const TIME_CONTEXT_EVENTS = [ | ||||
|     'bounds', | ||||
|     'clock', | ||||
|     'timeSystem', | ||||
|     'clockOffsets' | ||||
| ]; | ||||
| 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 MODES = { | ||||
|     fixed: 'fixed', | ||||
|     realtime: 'realtime' | ||||
| }; | ||||
|  | ||||
| class TimeContext extends EventEmitter { | ||||
|     constructor() { | ||||
| @@ -47,6 +60,7 @@ class TimeContext extends EventEmitter { | ||||
|  | ||||
|         this.activeClock = undefined; | ||||
|         this.offsets = undefined; | ||||
|         this.mode = undefined; | ||||
|  | ||||
|         this.tick = this.tick.bind(this); | ||||
|     } | ||||
| @@ -278,7 +292,7 @@ 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 | ||||
|      * bounds. | ||||
|      */ | ||||
| @@ -361,6 +375,7 @@ class TimeContext extends EventEmitter { | ||||
|  | ||||
|         this.boundsVal = newBounds; | ||||
|         this.emit('bounds', this.boundsVal, true); | ||||
|         this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, true); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -374,6 +389,278 @@ class TimeContext extends EventEmitter { | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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 " + timeSystem.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 = 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.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 JSON.parse(JSON.stringify(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 = JSON.parse(JSON.stringify(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 JSON.parse(JSON.stringify(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'?"; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const isRealtimeMode = this.getMode() === MODES.realtime; | ||||
|         const previousClock = this.activeClock; | ||||
|         if (previousClock !== undefined && isRealtimeMode) { | ||||
|             previousClock.off("tick", this.tick); | ||||
|         } | ||||
|  | ||||
|         this.activeClock = clock; | ||||
|  | ||||
|         /** | ||||
|          * 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 (isRealtimeMode) { | ||||
|             if (this.activeClock !== undefined) { | ||||
|                 this.activeClock.on("tick", this.tick); | ||||
|             } | ||||
|  | ||||
|             if (offsets !== undefined) { | ||||
|                 this.clockOffsets(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 | ||||
|      * @param {ClockOffsets | Bounds} offsets on each tick these will be used to calculate | ||||
|      * the start and end bounds. In realtime mode, this maintains a sliding time window of a fixed | ||||
|      * width that automatically updates. | ||||
|      * @fires module:openmct.TimeAPI~clock | ||||
|      * @return {Mode} the currently active mode; | ||||
|      */ | ||||
|     setMode(mode, offsets) { | ||||
|         if (offsets === undefined) { | ||||
|             throw "When setting the mode, offsets must also be provided"; | ||||
|         } | ||||
|  | ||||
|         const previousMode = this.mode; | ||||
|  | ||||
|         if (previousMode === MODES.realtime) { | ||||
|             this.activeClock.off('tick', this.tick); | ||||
|         } | ||||
|  | ||||
|         this.mode = mode; | ||||
|  | ||||
|         if (mode === MODES.realtime) { | ||||
|             this.activeClock.on("tick", this.tick); | ||||
|             this.setClockOffsets(offsets); | ||||
|         } else if (mode === MODES.fixed) { | ||||
|             this.activeClock.off("tick", this.tick); | ||||
|             this.setBounds(offsets); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * 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.mode); | ||||
|  | ||||
|         return this.mode; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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); | ||||
|         } | ||||
|  | ||||
|         this.offsets = 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, offsets); | ||||
|  | ||||
|         return this.offsets; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default TimeContext; | ||||
|   | ||||
| @@ -21,7 +21,8 @@ | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
| <div | ||||
|     class="c-conductor" | ||||
|     ref="timeConductorOptionsHolder" | ||||
|     class="c-compact-tc is-expanded" | ||||
|     :class="[ | ||||
|         { 'is-zooming': isZooming }, | ||||
|         { 'is-panning': isPanning }, | ||||
| @@ -29,52 +30,44 @@ | ||||
|         isFixed ? 'is-fixed-mode' : 'is-realtime-mode' | ||||
|     ]" | ||||
| > | ||||
|     <div class="c-conductor__time-bounds"> | ||||
|         <conductor-inputs-fixed | ||||
|             v-if="isFixed" | ||||
|             :input-bounds="viewBounds" | ||||
|             @updated="saveFixedOffsets" | ||||
|         /> | ||||
|         <conductor-inputs-realtime | ||||
|             v-else | ||||
|             :input-bounds="viewBounds" | ||||
|             @updated="saveClockOffsets" | ||||
|         /> | ||||
|         <ConductorModeIcon class="c-conductor__mode-icon" /> | ||||
|         <conductor-axis | ||||
|             class="c-conductor__ticks" | ||||
|             :view-bounds="viewBounds" | ||||
|             :is-fixed="isFixed" | ||||
|             :alt-pressed="altPressed" | ||||
|             @endPan="endPan" | ||||
|             @endZoom="endZoom" | ||||
|             @panAxis="pan" | ||||
|             @zoomAxis="zoom" | ||||
|         /> | ||||
|     </div> | ||||
|     <div class="c-conductor__controls"> | ||||
|         <ConductorMode class="c-conductor__mode-select" /> | ||||
|         <ConductorTimeSystem class="c-conductor__time-system-select" /> | ||||
|         <ConductorHistory | ||||
|             class="c-conductor__history-select" | ||||
|             :offsets="openmct.time.clockOffsets()" | ||||
|             :bounds="bounds" | ||||
|             :time-system="timeSystem" | ||||
|             :mode="timeMode" | ||||
|         /> | ||||
|     </div> | ||||
|     <ConductorModeIcon class="c-conductor__mode-icon" /> | ||||
|     <!-- TODO - NEED TO ADD MODE, CLOCK AND TIMESYSTEM VIEW ONLY INFORMATION HERE --> | ||||
|     <conductor-inputs-fixed | ||||
|         v-if="isFixed" | ||||
|         :input-bounds="viewBounds" | ||||
|         :read-only="true" | ||||
|     /> | ||||
|     <conductor-inputs-realtime | ||||
|         v-else | ||||
|         :input-bounds="viewBounds" | ||||
|         :read-only="true" | ||||
|     /> | ||||
|     <conductor-axis | ||||
|         v-if="isFixed" | ||||
|         class="c-conductor__ticks" | ||||
|         :view-bounds="viewBounds" | ||||
|         :is-fixed="isFixed" | ||||
|         :alt-pressed="altPressed" | ||||
|         @endPan="endPan" | ||||
|         @endZoom="endZoom" | ||||
|         @panAxis="pan" | ||||
|         @zoomAxis="zoom" | ||||
|     /> | ||||
|     <div | ||||
|         v-else | ||||
|         class="u-flex-spreader" | ||||
|     ></div> | ||||
|     <div class="c-not-button c-not-button--compact c-compact-tc__gear icon-gear"></div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import _ from 'lodash'; | ||||
| import ConductorMode from './ConductorMode.vue'; | ||||
| import ConductorTimeSystem from './ConductorTimeSystem.vue'; | ||||
| import ConductorAxis from './ConductorAxis.vue'; | ||||
| import ConductorModeIcon from './ConductorModeIcon.vue'; | ||||
| import ConductorHistory from './ConductorHistory.vue'; | ||||
| import ConductorInputsFixed from "./ConductorInputsFixed.vue"; | ||||
| import ConductorInputsRealtime from "./ConductorInputsRealtime.vue"; | ||||
| import conductorPopUpManager from "./conductorPopUpManager"; | ||||
|  | ||||
| const DEFAULT_DURATION_FORMATTER = 'duration'; | ||||
|  | ||||
| @@ -82,12 +75,10 @@ export default { | ||||
|     components: { | ||||
|         ConductorInputsRealtime, | ||||
|         ConductorInputsFixed, | ||||
|         ConductorMode, | ||||
|         ConductorTimeSystem, | ||||
|         ConductorAxis, | ||||
|         ConductorModeIcon, | ||||
|         ConductorHistory | ||||
|         ConductorModeIcon | ||||
|     }, | ||||
|     mixins: [conductorPopUpManager], | ||||
|     inject: ['openmct', 'configuration'], | ||||
|     data() { | ||||
|         let bounds = this.openmct.time.bounds(); | ||||
| @@ -121,16 +112,9 @@ export default { | ||||
|             showDatePicker: false, | ||||
|             altPressed: false, | ||||
|             isPanning: false, | ||||
|             isZooming: false, | ||||
|             showTCInputStart: false, | ||||
|             showTCInputEnd: false | ||||
|             isZooming: false | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
|         timeMode() { | ||||
|             return this.isFixed ? 'fixed' : 'realtime'; | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         document.addEventListener('keydown', this.handleKeyDown); | ||||
|         document.addEventListener('keyup', this.handleKeyUp); | ||||
| @@ -196,7 +180,6 @@ export default { | ||||
|             this.isUTCBased = timeSystem.isUTCBased; | ||||
|         }, | ||||
|         setViewFromClock(clock) { | ||||
|             // this.clearAllValidation(); | ||||
|             this.isFixed = clock === undefined; | ||||
|         }, | ||||
|         setViewFromBounds(bounds) { | ||||
| @@ -210,11 +193,22 @@ export default { | ||||
|                 format: key | ||||
|             }).formatter; | ||||
|         }, | ||||
|         saveFixedBounds(bounds) { | ||||
|             this.openmct.time.bounds(bounds); | ||||
|         }, | ||||
|         saveClockOffsets(offsets) { | ||||
|             this.openmct.time.clockOffsets(offsets); | ||||
|         }, | ||||
|         saveFixedOffsets(bounds) { | ||||
|             this.openmct.time.bounds(bounds); | ||||
|         saveMode(option) { | ||||
|             if (option.timeSystem) { | ||||
|                 this.openmct.time.timeSystem(option.timeSystem, option.bounds); | ||||
|             } | ||||
|  | ||||
|             if (option.clockKey === undefined) { | ||||
|                 this.openmct.time.stopClock(); | ||||
|             } else { | ||||
|                 this.openmct.time.clock(option.clockKey, option.offsets); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -98,7 +98,10 @@ export default { | ||||
|  | ||||
|         //Respond to changes in conductor | ||||
|         this.openmct.time.on("timeSystem", this.setViewFromTimeSystem); | ||||
|         setInterval(this.resize, RESIZE_POLL_INTERVAL); | ||||
|         this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         clearInterval(this.resizeTimer); | ||||
|     }, | ||||
|     methods: { | ||||
|         setAxisDimensions() { | ||||
|   | ||||
| @@ -26,8 +26,9 @@ | ||||
| > | ||||
|     <div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left"> | ||||
|         <button | ||||
|             class="c-button--menu c-button--compact c-history-button icon-history" | ||||
|             :class="buttonCssClass" | ||||
|             aria-label="Time Conductor History" | ||||
|             class="c-button--menu c-history-button icon-history" | ||||
|             @click.prevent.stop="showHistoryMenu" | ||||
|         > | ||||
|             <span class="c-button__label">History</span> | ||||
| @@ -64,6 +65,13 @@ export default { | ||||
|         mode: { | ||||
|             type: String, | ||||
|             required: true | ||||
|         }, | ||||
|         buttonCssClass: { | ||||
|             type: String, | ||||
|             required: false, | ||||
|             default() { | ||||
|                 return ''; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
| @@ -106,8 +114,8 @@ export default { | ||||
|         bounds: { | ||||
|             handler() { | ||||
|                 // only for fixed time since we track offsets for realtime | ||||
|                 this.updateMode(); | ||||
|                 if (this.isFixed) { | ||||
|                     this.updateMode(); | ||||
|                     this.addTimespan(); | ||||
|                 } | ||||
|             }, | ||||
| @@ -116,7 +124,9 @@ export default { | ||||
|         offsets: { | ||||
|             handler() { | ||||
|                 this.updateMode(); | ||||
|                 this.addTimespan(); | ||||
|                 if (!this.isFixed) { | ||||
|                     this.addTimespan(); | ||||
|                 } | ||||
|             }, | ||||
|             deep: true | ||||
|         }, | ||||
|   | ||||
| @@ -1,78 +1,34 @@ | ||||
| <template> | ||||
| <form | ||||
|     ref="fixedDeltaInput" | ||||
|     class="c-conductor__inputs" | ||||
| <time-popup-fixed | ||||
|     v-if="readOnly === false" | ||||
|     :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 | ||||
|         class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed" | ||||
|     > | ||||
|         <!-- Fixed start --> | ||||
|         <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> | ||||
|     <div class="c-compact-tc__bounds__value">{{ formattedBounds.start }}</div> | ||||
|     <div class="c-compact-tc__bounds__start-end-sep icon-arrows-right-left"></div> | ||||
|     <div class="c-compact-tc__bounds__value">{{ formattedBounds.end }}</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| import DatePicker from "./DatePicker.vue"; | ||||
| import TimePopupFixed from "./timePopupFixed.vue"; | ||||
| import _ from "lodash"; | ||||
|  | ||||
| const DEFAULT_DURATION_FORMATTER = 'duration'; | ||||
| // const DEFAULT_DURATION_FORMATTER = 'duration'; | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
|         DatePicker | ||||
|         TimePopupFixed | ||||
|     }, | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         keyString: { | ||||
|             type: String, | ||||
|             default() { | ||||
|                 return undefined; | ||||
|             } | ||||
|         }, | ||||
|         inputBounds: { | ||||
|             type: Object, | ||||
|             default() { | ||||
| @@ -84,18 +40,29 @@ export default { | ||||
|             default() { | ||||
|                 return []; | ||||
|             } | ||||
|         }, | ||||
|         readOnly: { | ||||
|             type: Boolean, | ||||
|             default() { | ||||
|                 return false; | ||||
|             } | ||||
|         }, | ||||
|         compact: { | ||||
|             type: Boolean, | ||||
|             default() { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         let timeSystem = this.openmct.time.timeSystem(); | ||||
|         let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|         // let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|         let timeFormatter = this.getFormatter(timeSystem.timeFormat); | ||||
|         let bounds = this.bounds || this.openmct.time.bounds(); | ||||
|  | ||||
|         return { | ||||
|             showTCInputStart: true, | ||||
|             showTCInputEnd: true, | ||||
|             durationFormatter, | ||||
|             timeSystem: timeSystem, | ||||
|             // durationFormatter, | ||||
|             timeFormatter, | ||||
|             bounds: { | ||||
|                 start: bounds.start, | ||||
| @@ -109,8 +76,15 @@ export default { | ||||
|         }; | ||||
|     }, | ||||
|     watch: { | ||||
|         keyString() { | ||||
|             this.setTimeContext(); | ||||
|         objectPath: { | ||||
|             handler(newPath, oldPath) { | ||||
|                 if (newPath === oldPath) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.setTimeContext(); | ||||
|             }, | ||||
|             deep: true | ||||
|         }, | ||||
|         inputBounds: { | ||||
|             handler(newBounds) { | ||||
| @@ -126,36 +100,26 @@ export default { | ||||
|         this.setTimeContext(); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.clearAllValidation(); | ||||
|         this.openmct.time.off('timeSystem', this.setTimeSystem); | ||||
|         this.stopFollowingTimeContext(); | ||||
|     }, | ||||
|     methods: { | ||||
|         setTimeContext() { | ||||
|             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.timeContext.on('bounds', this.handleNewBounds); | ||||
|             this.timeContext.on('clock', this.clearAllValidation); | ||||
|         }, | ||||
|         stopFollowingTimeContext() { | ||||
|             if (this.timeContext) { | ||||
|                 this.timeContext.off('bounds', this.handleNewBounds); | ||||
|                 this.timeContext.off('clock', this.clearAllValidation); | ||||
|             } | ||||
|         }, | ||||
|         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; | ||||
|         }, | ||||
| @@ -166,8 +130,8 @@ export default { | ||||
|         setTimeSystem(timeSystem) { | ||||
|             this.timeSystem = timeSystem; | ||||
|             this.timeFormatter = this.getFormatter(timeSystem.timeFormat); | ||||
|             this.durationFormatter = this.getFormatter( | ||||
|                 timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|             // this.durationFormatter = this.getFormatter( | ||||
|             //     timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|             this.isUTCBased = timeSystem.isUTCBased; | ||||
|         }, | ||||
|         getFormatter(key) { | ||||
| @@ -175,116 +139,14 @@ export default { | ||||
|                 format: key | ||||
|             }).formatter; | ||||
|         }, | ||||
|         setBoundsFromView($event) { | ||||
|             if (this.$refs.fixedDeltaInput.checkValidity()) { | ||||
|                 let start = this.timeFormatter.parse(this.formattedBounds.start); | ||||
|                 let end = this.timeFormatter.parse(this.formattedBounds.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); | ||||
|         setBoundsFromView(bounds) { | ||||
|             this.$emit('updated', { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end | ||||
|             }); | ||||
|         }, | ||||
|         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.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(); | ||||
|         dismiss() { | ||||
|             this.$emit('dismiss'); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -1,95 +1,43 @@ | ||||
| <template> | ||||
| <form | ||||
|     ref="deltaInput" | ||||
|     class="c-conductor__inputs" | ||||
| <time-popup-realtime | ||||
|     v-if="readOnly === false" | ||||
|     :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 | ||||
|         class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta" | ||||
|     > | ||||
|         <!-- RT start --> | ||||
|         <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> | ||||
|         v-if="compact" | ||||
|         class="c-compact-tc__bounds__start-end-sep icon-arrows-right-left" | ||||
|     ></div> | ||||
|     <div | ||||
|         class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta" | ||||
|         v-else | ||||
|         class="c-compact-tc__current-update" | ||||
|     > | ||||
|         <!-- RT 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> | ||||
|         LAST UPDATE {{ formattedBounds.end }} | ||||
|     </div> | ||||
| </form> | ||||
|     <div class="c-compact-tc__bounds__value icon-plus">{{ offsets.end }}</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| import timePopup from "./timePopup.vue"; | ||||
| import TimePopupRealtime from "./timePopupRealtime.vue"; | ||||
| import _ from "lodash"; | ||||
|  | ||||
| const DEFAULT_DURATION_FORMATTER = 'duration'; | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
|         timePopup | ||||
|         TimePopupRealtime | ||||
|     }, | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         keyString: { | ||||
|             type: String, | ||||
|             default() { | ||||
|                 return undefined; | ||||
|             } | ||||
|         }, | ||||
|         objectPath: { | ||||
|             type: Array, | ||||
|             default() { | ||||
| @@ -101,6 +49,18 @@ export default { | ||||
|             default() { | ||||
|                 return undefined; | ||||
|             } | ||||
|         }, | ||||
|         readOnly: { | ||||
|             type: Boolean, | ||||
|             default() { | ||||
|                 return false; | ||||
|             } | ||||
|         }, | ||||
|         compact: { | ||||
|             type: Boolean, | ||||
|             default() { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
| @@ -134,8 +94,15 @@ export default { | ||||
|         }; | ||||
|     }, | ||||
|     watch: { | ||||
|         keyString() { | ||||
|             this.setTimeContext(); | ||||
|         objectPath: { | ||||
|             handler(newPath, oldPath) { | ||||
|                 if (newPath === oldPath) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.setTimeContext(); | ||||
|             }, | ||||
|             deep: true | ||||
|         }, | ||||
|         inputBounds: { | ||||
|             handler(newBounds) { | ||||
| @@ -159,19 +126,17 @@ export default { | ||||
|             this.handleNewBounds(this.timeContext.bounds()); | ||||
|             this.setViewFromOffsets(this.timeContext.clockOffsets()); | ||||
|             this.timeContext.on('bounds', this.handleNewBounds); | ||||
|             this.timeContext.on('clock', this.clearAllValidation); | ||||
|             this.timeContext.on('clockOffsets', this.setViewFromOffsets); | ||||
|         }, | ||||
|         stopFollowingTime() { | ||||
|             if (this.timeContext) { | ||||
|                 this.timeContext.off('bounds', this.handleNewBounds); | ||||
|                 this.timeContext.off('clock', this.clearAllValidation); | ||||
|                 this.timeContext.off('clockOffsets', this.setViewFromOffsets); | ||||
|             } | ||||
|         }, | ||||
|         setTimeContext() { | ||||
|             this.stopFollowingTime(); | ||||
|             this.timeContext = this.openmct.time.getContextForView(this.keyString ? this.objectPath : []); | ||||
|             this.timeContext = this.openmct.time.getContextForView(this.objectPath); | ||||
|             this.followTime(); | ||||
|         }, | ||||
|         handleNewBounds(bounds) { | ||||
| @@ -179,13 +144,6 @@ export default { | ||||
|             this.setViewFromBounds(bounds); | ||||
|             this.updateCurrentValue(); | ||||
|         }, | ||||
|         clearAllValidation() { | ||||
|             [this.$refs.startOffset, this.$refs.endOffset].forEach(this.clearValidationForInput); | ||||
|         }, | ||||
|         clearValidationForInput(input) { | ||||
|             input.setCustomValidity(''); | ||||
|             input.title = ''; | ||||
|         }, | ||||
|         setViewFromOffsets(offsets) { | ||||
|             if (offsets) { | ||||
|                 this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start)); | ||||
| @@ -222,86 +180,22 @@ export default { | ||||
|                 format: key | ||||
|             }).formatter; | ||||
|         }, | ||||
|         hideAllTimePopups() { | ||||
|             this.showTCInputStart = false; | ||||
|             this.showTCInputEnd = false; | ||||
|         }, | ||||
|         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(':'); | ||||
|         timePopUpdate({ start, end }) { | ||||
|             this.offsets.start = [start.hours, start.minutes, start.seconds].join(':'); | ||||
|             this.offsets.end = [end.hours, end.minutes, end.seconds].join(':'); | ||||
|             this.setOffsetsFromView(); | ||||
|             this.hideAllTimePopups(); | ||||
|         }, | ||||
|         setOffsetsFromView($event) { | ||||
|             if (this.$refs.deltaInput.checkValidity()) { | ||||
|                 let startOffset = 0 - this.durationFormatter.parse(this.offsets.start); | ||||
|                 let endOffset = this.durationFormatter.parse(this.offsets.end); | ||||
|         setOffsetsFromView() { | ||||
|             let startOffset = 0 - this.durationFormatter.parse(this.offsets.start); | ||||
|             let endOffset = this.durationFormatter.parse(this.offsets.end); | ||||
|  | ||||
|                 this.$emit('updated', { | ||||
|                     start: startOffset, | ||||
|                     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); | ||||
|             this.$emit('updated', { | ||||
|                 start: startOffset, | ||||
|                 end: endOffset | ||||
|             }); | ||||
|         }, | ||||
|         handleValidationResults(input, validationResult) { | ||||
|             if (validationResult.valid !== true) { | ||||
|                 input.setCustomValidity(validationResult.message); | ||||
|                 input.title = validationResult.message; | ||||
|             } else { | ||||
|                 input.setCustomValidity(''); | ||||
|                 input.title = ''; | ||||
|             } | ||||
|  | ||||
|             return validationResult.valid; | ||||
|         dismiss() { | ||||
|             this.$emit('dismiss'); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -22,11 +22,15 @@ | ||||
| <template> | ||||
| <div | ||||
|     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"> | ||||
|         <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" | ||||
|         > | ||||
|             <span class="c-button__label">{{ selectedMode.name }}</span> | ||||
| @@ -41,6 +45,15 @@ import toggleMixin from '../../ui/mixins/toggle-mixin'; | ||||
| export default { | ||||
|     mixins: [toggleMixin], | ||||
|     inject: ['openmct', 'configuration'], | ||||
|     props: { | ||||
|         buttonCssClass: { | ||||
|             type: String, | ||||
|             required: false, | ||||
|             default() { | ||||
|                 return ''; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     data: function () { | ||||
|         let activeClock = this.openmct.time.clock(); | ||||
|         if (activeClock !== undefined) { | ||||
| @@ -134,6 +147,10 @@ export default { | ||||
|                 clockKey = undefined; | ||||
|             } | ||||
|  | ||||
|             let option = { | ||||
|                 clockKey | ||||
|             }; | ||||
|  | ||||
|             let configuration = this.getMatchingConfig({ | ||||
|                 clock: clockKey, | ||||
|                 timeSystem: this.openmct.time.timeSystem().key | ||||
| @@ -143,16 +160,19 @@ export default { | ||||
|                 configuration = this.getMatchingConfig({ | ||||
|                     clock: clockKey | ||||
|                 }); | ||||
|  | ||||
|                 this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds); | ||||
|                 option.timeSystem = configuration.timeSystem; | ||||
|                 option.bounds = configuration.bounds; | ||||
|             } | ||||
|  | ||||
|             if (clockKey === undefined) { | ||||
|                 this.openmct.time.stopClock(); | ||||
|                 // this.openmct.time.stopClock(); | ||||
|             } else { | ||||
|                 const offsets = this.openmct.time.clockOffsets() || configuration.clockOffsets; | ||||
|                 this.openmct.time.clock(clockKey, offsets); | ||||
|                 option.offsets = offsets; | ||||
|                 // this.openmct.time.clock(clockKey, offsets); | ||||
|             } | ||||
|  | ||||
|             this.$emit('updated', option); | ||||
|         }, | ||||
|  | ||||
|         getMatchingConfig(options) { | ||||
|   | ||||
| @@ -21,6 +21,17 @@ | ||||
| *****************************************************************************/ | ||||
| <template> | ||||
| <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-big"></div> | ||||
| </div> | ||||
|   | ||||
							
								
								
									
										210
									
								
								src/plugins/timeConductor/ConductorPopUp.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/plugins/timeConductor/ConductorPopUp.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | ||||
| <template> | ||||
| <div | ||||
|     :style="position" | ||||
|     class="c-tc-input-popup" | ||||
|     :class="modeClass" | ||||
|     @click.stop | ||||
|     @keydown.enter.prevent | ||||
|     @keyup.enter.prevent="submit" | ||||
|     @keydown.esc.prevent | ||||
|     @keyup.esc.prevent="hide" | ||||
| > | ||||
|     <div | ||||
|         class="c-tc-input-popup__options" | ||||
|     > | ||||
|         <Mode | ||||
|             v-if="isIndependent" | ||||
|             class="c-button--compact c-conductor__mode-select" | ||||
|             :mode="timeOptionMode" | ||||
|             :button-css-class="'c-button--compact'" | ||||
|             @modeChanged="saveIndependentMode" | ||||
|         /> | ||||
|         <ConductorMode | ||||
|             v-else | ||||
|             class="c-conductor__mode-select" | ||||
|             :button-css-class="'c-icon-button'" | ||||
|             @updated="saveMode" | ||||
|         /> | ||||
|         <!-- 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" | ||||
|         @updated="saveFixedBounds" | ||||
|         @dismiss="dismiss" | ||||
|     /> | ||||
|     <conductor-inputs-realtime | ||||
|         v-else | ||||
|         :input-bounds="bounds" | ||||
|         :object-path="objectPath" | ||||
|         @updated="saveClockOffsets" | ||||
|         @dismiss="dismiss" | ||||
|     /> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import ConductorMode from './ConductorMode.vue'; | ||||
| import Mode from './independent/Mode.vue'; | ||||
| import ConductorTimeSystem from "./ConductorTimeSystem.vue"; | ||||
| import ConductorHistory from "./ConductorHistory.vue"; | ||||
| import ConductorInputsFixed from "./ConductorInputsFixed.vue"; | ||||
| import ConductorInputsRealtime from "./ConductorInputsRealtime.vue"; | ||||
|  | ||||
| export default { | ||||
|  | ||||
|     components: { | ||||
|         ConductorMode, | ||||
|         Mode, | ||||
|         ConductorTimeSystem, | ||||
|         ConductorHistory, | ||||
|         ConductorInputsFixed, | ||||
|         ConductorInputsRealtime | ||||
|     }, | ||||
|     inject: ['openmct', 'configuration'], | ||||
|     props: { | ||||
|         positionX: { | ||||
|             type: Number, | ||||
|             required: true | ||||
|         }, | ||||
|         // positionY: { | ||||
|         //     type: Number, | ||||
|         //     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() { | ||||
|         let bounds = this.openmct.time.bounds(); | ||||
|         let timeSystem = this.openmct.time.timeSystem(); | ||||
|  | ||||
|         return { | ||||
|             timeSystem: timeSystem, | ||||
|             bounds: { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end | ||||
|             }, | ||||
|             isFixed: this.openmct.time.clock() === undefined | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
|         position() { | ||||
|             return { | ||||
|                 left: `${this.positionX}px` | ||||
|                 // top: `${this.positionY}px` | ||||
|             }; | ||||
|         }, | ||||
|         timeOffsets() { | ||||
|             return this.isFixed || !this.timeContext ? undefined : this.timeContext.clockOffsets(); | ||||
|         }, | ||||
|         timeMode() { | ||||
|             return this.isFixed ? 'fixed' : 'realtime'; | ||||
|         }, | ||||
|         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; | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         objectPath: { | ||||
|             handler(newPath, oldPath) { | ||||
|                 //domain object or view has probably changed | ||||
|                 if (newPath === oldPath) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.setTimeContext(); | ||||
|             }, | ||||
|             deep: true | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.setTimeContext(); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.stopFollowingTimeContext(); | ||||
|     }, | ||||
|     methods: { | ||||
|         setTimeContext() { | ||||
|             this.stopFollowingTimeContext(); | ||||
|             this.timeContext = this.openmct.time.getContextForView(this.objectPath); | ||||
|             this.timeContext.on('clock', this.setViewFromClock); | ||||
|             this.timeContext.on('bounds', this.setBounds); | ||||
|             this.setViewFromClock(this.timeContext.clock()); | ||||
|             this.setBounds(this.timeContext.bounds()); | ||||
|         }, | ||||
|         stopFollowingTimeContext() { | ||||
|             if (this.timeContext) { | ||||
|                 this.timeContext.off('clock', this.setViewFromClock); | ||||
|                 this.timeContext.off('bounds', this.setBounds); | ||||
|             } | ||||
|         }, | ||||
|         setViewFromClock(clock) { | ||||
|             this.isFixed = clock === undefined; | ||||
|             this.bounds = this.timeContext.bounds(); | ||||
|         }, | ||||
|         setBounds() { | ||||
|             this.bounds = this.timeContext.bounds(); | ||||
|         }, | ||||
|         saveFixedBounds(bounds) { | ||||
|             this.$emit('fixedBoundsUpdated', bounds); | ||||
|         }, | ||||
|         saveClockOffsets(offsets) { | ||||
|             this.$emit('clockOffsetsUpdated', offsets); | ||||
|         }, | ||||
|         saveMode(option) { | ||||
|             this.$emit('modeUpdated', option); | ||||
|         }, | ||||
|         saveIndependentMode(mode) { | ||||
|             this.$emit('independentModeUpdated', mode); | ||||
|         }, | ||||
|         dismiss() { | ||||
|             this.$emit('dismiss'); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| }; | ||||
|  | ||||
| </script> | ||||
| @@ -26,11 +26,13 @@ | ||||
|     class="c-ctrl-wrapper c-ctrl-wrapper--menus-up" | ||||
| > | ||||
|     <button | ||||
|         class="c-button--menu c-time-system-button" | ||||
|         :class="selectedTimeSystem.cssClass" | ||||
|         class="c-button--menu c-button--compact c-time-system-button" | ||||
|         :class="[ | ||||
|             buttonCssClass | ||||
|         ]" | ||||
|         @click.prevent.stop="showTimeSystemMenu" | ||||
|     > | ||||
|         <span class="c-button__label">{{ selectedTimeSystem.name }}</span> | ||||
|         {{ selectedTimeSystem.name }} | ||||
|     </button> | ||||
| </div> | ||||
| </template> | ||||
| @@ -38,6 +40,15 @@ | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['openmct', 'configuration'], | ||||
|     props: { | ||||
|         buttonCssClass: { | ||||
|             type: String, | ||||
|             required: false, | ||||
|             default() { | ||||
|                 return ''; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     data: function () { | ||||
|         let activeClock = this.openmct.time.clock(); | ||||
|  | ||||
|   | ||||
| @@ -57,11 +57,11 @@ | ||||
|     } | ||||
|  | ||||
|     .is-realtime-mode & { | ||||
|         $c: 1px solid rgba($colorTime, 0.7); | ||||
|         $c: 1px solid rgba($colorTimeRealtime, 0.7); | ||||
|         border-left: $c; | ||||
|         border-right: $c; | ||||
|         svg text { | ||||
|             fill: $colorTime; | ||||
|             fill: $colorTimeRealtime; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -33,22 +33,17 @@ | ||||
|  | ||||
|  | ||||
| .c-clock-symbol { | ||||
|     $c: $colorBtnBg; //$colorObjHdrIc; | ||||
|     $d: 18px; | ||||
|     $c: rgba($colorBodyFg, 0.5); | ||||
|     $d: 16px; | ||||
|     height: $d; | ||||
|     width: $d; | ||||
|     position: relative; | ||||
|  | ||||
|     &:before { | ||||
|         font-family: symbolsfont; | ||||
|         color: $c; | ||||
|         content: $glyph-icon-brackets; | ||||
|         font-size: $d; | ||||
|         line-height: normal; | ||||
|         display: block; | ||||
|     &__outer { | ||||
|         // SVG brackets shape | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         z-index: 1; | ||||
|         fill: $c; | ||||
|     } | ||||
|  | ||||
|     // Clock hands | ||||
| @@ -93,14 +88,15 @@ | ||||
|     // Modes | ||||
|     .is-realtime-mode &, | ||||
|     .is-lad-mode & { | ||||
|         &:before { | ||||
|         $c: $colorTimeRealtimeFgSubtle; | ||||
|         .c-clock-symbol__outer { | ||||
|             // Brackets icon | ||||
|             color: $colorTime; | ||||
|             fill: $c; | ||||
|         } | ||||
|         div[class*="hand"] { | ||||
|             animation-name: clock-hands; | ||||
|             &:before { | ||||
|                 background: $colorTime; | ||||
|                 background: $c; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| .c-conductor__mode-menu { | ||||
|     max-height: 80vh; | ||||
|     max-width: 500px; | ||||
|     min-height: 250px; | ||||
|     z-index: 70; | ||||
|     min-height: 50px; | ||||
|     //We don't need the z-index now that we're using the popup | ||||
|     //z-index: 70; | ||||
|  | ||||
|     [class*="__icon"] { | ||||
|         filter: $colorKeyFilter; | ||||
|   | ||||
| @@ -9,10 +9,15 @@ | ||||
| /*********************************************** CONDUCTOR LAYOUT */ | ||||
| .c-conductor { | ||||
|     &__inputs { | ||||
|         display: contents; | ||||
|         display: flex; | ||||
|         flex: 0 0 auto; | ||||
|  | ||||
|         > * + * { | ||||
|             margin-left: $interiorMargin; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__time-bounds { | ||||
| /*    &__time-bounds { | ||||
|         display: grid; | ||||
|         grid-column-gap: $interiorMargin; | ||||
|         grid-row-gap: $interiorMargin; | ||||
| @@ -39,16 +44,17 @@ | ||||
|         grid-area: tc-end; | ||||
|         display: flex; | ||||
|         justify-content: flex-end; | ||||
|     } | ||||
|     }*/ | ||||
|  | ||||
|     &__ticks { | ||||
|         grid-area: tc-ticks; | ||||
|         flex: 1 1 auto; | ||||
|     } | ||||
|  | ||||
|     &__controls { | ||||
|         grid-area: tc-controls; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|  | ||||
|         > * + * { | ||||
|             margin-left: $interiorMargin; | ||||
|         } | ||||
| @@ -107,7 +113,8 @@ | ||||
|                 background: rgba($timeConductorActiveBg, 0.4); | ||||
|                 border-left-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 { | ||||
|             grid-row-gap: $interiorMargin; | ||||
|             grid-template-rows: auto auto; | ||||
| @@ -160,8 +167,8 @@ | ||||
|                 grid-template-areas: | ||||
|                     "tc-mode-icon tc-start tc-start" | ||||
|                     "tc-mode-icon tc-end tc-end" | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.is-realtime-mode { | ||||
|             .c-conductor__time-bounds { | ||||
| @@ -174,21 +181,20 @@ | ||||
|                 justify-content: flex-end; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     }*/ | ||||
| } | ||||
|  | ||||
| .c-conductor-holder--compact { | ||||
|     min-height: 22px; | ||||
|     //min-height: 22px; | ||||
|     flex: 0 1 auto; | ||||
|     overflow: hidden; | ||||
|  | ||||
|     .c-conductor { | ||||
|         &__inputs, | ||||
|         &__time-bounds { | ||||
|             display: flex; | ||||
|  | ||||
|             .c-toggle-switch { | ||||
|                 // Used in independent Time Conductor | ||||
|                 flex: 0 0 auto; | ||||
|             } | ||||
|             flex: 0 1 auto; | ||||
|             overflow: hidden; | ||||
|         } | ||||
|  | ||||
|         &__inputs { | ||||
| @@ -218,38 +224,32 @@ | ||||
|         margin-right: $interiorMarginSm; | ||||
|     } | ||||
|  | ||||
|     .c-direction-indicator { | ||||
|         // Holds realtime-mode + and - symbols | ||||
|         font-size: 0.7em; | ||||
|     } | ||||
|  | ||||
|     input:invalid { | ||||
|         background: rgba($colorFormInvalid, 0.5); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .is-realtime-mode { | ||||
|     .c-conductor__controls button, | ||||
|     .c-conductor__delta-button { | ||||
|         @include themedButton($colorTimeBg); | ||||
|         color: $colorTimeFg; | ||||
|         //@include themedButton($colorTimeRealtimeBg); | ||||
|         color: $colorTimeRealtimeFg; | ||||
|     } | ||||
|  | ||||
|     .c-conductor-input { | ||||
|         &:before { | ||||
|             color: $colorTime; | ||||
|             color: $colorTimeRealtimeFgSubtle; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-conductor__end-fixed { | ||||
|         // Displays last RT udpate | ||||
|         color: $colorTime; | ||||
|         // Displays last RT update | ||||
|         color: $colorTimeRealtimeFgSubtle; | ||||
|  | ||||
|         input { | ||||
|             // Remove input look | ||||
|             background: none; | ||||
|             box-shadow: none; | ||||
|             color: $colorTime; | ||||
|             color: $colorTimeRealtimeFgSubtle; | ||||
|             pointer-events: none; | ||||
|  | ||||
|             &[disabled] { | ||||
| @@ -259,6 +259,7 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| //TODO: Do we need this? | ||||
| [class^='pr-tc-input-menu'] { | ||||
|     // Uses ^= here to target both start and end menus | ||||
|     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; | ||||
|     right: 0; | ||||
| } | ||||
|  | ||||
| .pr-time-label { | ||||
|     font-size: 0.9em; | ||||
|     text-transform: uppercase; | ||||
|  | ||||
| [class^='pr-time'] { | ||||
|     &[class*='label'] { | ||||
|     &:before { | ||||
|         font-size: 0.8em; | ||||
|         opacity: 0.6; | ||||
|         text-transform: uppercase; | ||||
|         margin-right: $interiorMarginSm; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .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; | ||||
|         align-items: center; | ||||
|         white-space: nowrap; | ||||
|  | ||||
|         input { | ||||
|             height: 22px; | ||||
|             line-height: 22px; | ||||
|         > * + * { | ||||
|             margin-left: $interiorMargin; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__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; | ||||
|             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; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										136
									
								
								src/plugins/timeConductor/conductorPopUpManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/plugins/timeConductor/conductorPopUpManager.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 Vue from "vue"; | ||||
| import ConductorPopUp from "./ConductorPopUp.vue"; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'configuration'], | ||||
|     mounted() { | ||||
|         this.showPopup = this.showPopup.bind(this); | ||||
|         this.clearPopup = this.clearPopup.bind(this); | ||||
|         this.positionBox = this.positionBox.bind(this); | ||||
|         this.positionBox = raf(this.positionBox); | ||||
|         this.timeConductorOptionsHolder = this.$refs.timeConductorOptionsHolder; | ||||
|         this.registerPopUp(); | ||||
|         this.popupComponent = this.createPopupComponent(); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.removePopup(); | ||||
|     }, | ||||
|     methods: { | ||||
|         showPopup() { | ||||
|             const popupElement = this.popupComponent; | ||||
|  | ||||
|             document.body.appendChild(popupElement.$el); | ||||
|             //Use capture, so we don't trigger immediately on the same iteration of the event loop | ||||
|             document.addEventListener('click', this.clearPopup, { | ||||
|                 capture: true | ||||
|             }); | ||||
|  | ||||
|             this.positionBox(); | ||||
|  | ||||
|             window.addEventListener('resize', this.positionBox); | ||||
|         }, | ||||
|  | ||||
|         positionBox() { | ||||
|             const popupElement = this.popupComponent; | ||||
|             const timeConductorOptions = this.timeConductorOptionsHolder; | ||||
|  | ||||
|             let timeConductorOptionsBox = timeConductorOptions.getBoundingClientRect(); | ||||
|             popupElement.positionX = timeConductorOptionsBox.left; | ||||
|             //TODO: PositionY should be calculated to be top or bottom based on the location of the conductor options | ||||
|             popupElement.positionY = timeConductorOptionsBox.top; | ||||
|             const offsetTop = popupElement.$el.getBoundingClientRect().height; | ||||
|  | ||||
|             const popupRight = popupElement.positionX + popupElement.$el.clientWidth; | ||||
|             const offsetLeft = Math.min(window.innerWidth - popupRight, 0); | ||||
|  | ||||
|             popupElement.positionX = popupElement.positionX + offsetLeft; | ||||
|             popupElement.positionY = popupElement.positionY - offsetTop; | ||||
|         }, | ||||
|  | ||||
|         clearPopup(clickAwayEvent) { | ||||
|             if (this.canClose(clickAwayEvent)) { | ||||
|                 clickAwayEvent.stopPropagation(); | ||||
|                 this.removePopup(); | ||||
|             } | ||||
|         }, | ||||
|         canClose(clickAwayEvent) { | ||||
|             const popupElement = this.popupComponent; | ||||
|  | ||||
|             const isChildMenu = clickAwayEvent.target.closest('.c-menu') !== null; | ||||
|             const isPopupElementItem = popupElement.$el.contains(clickAwayEvent.target); | ||||
|  | ||||
|             return !isChildMenu && !isPopupElementItem; | ||||
|         }, | ||||
|         removePopup() { | ||||
|             const popupElement = this.popupComponent; | ||||
|             popupElement.$el.remove(); | ||||
|             document.removeEventListener('click', this.clearPopup, { | ||||
|                 capture: true | ||||
|             }); | ||||
|             window.removeEventListener('resize', this.positionBox); | ||||
|         }, | ||||
|  | ||||
|         createPopupComponent() { | ||||
|             const saveFixedBounds = this.saveFixedBounds; | ||||
|             const saveClockOffsets = this.saveClockOffsets; | ||||
|             const saveMode = this.saveMode; | ||||
|             const removePopup = this.removePopup; | ||||
|  | ||||
|             const popupElement = new Vue({ | ||||
|                 components: { | ||||
|                     ConductorPopUp | ||||
|                 }, | ||||
|                 provide: { | ||||
|                     openmct: this.openmct, | ||||
|                     configuration: this.configuration | ||||
|                 }, | ||||
|                 data() { | ||||
|                     return { | ||||
|                         positionX: 0, | ||||
|                         positionY: 0, | ||||
|                         saveClockOffsets, | ||||
|                         saveFixedBounds, | ||||
|                         saveMode, | ||||
|                         removePopup | ||||
|                     }; | ||||
|                 }, | ||||
|                 template: `<conductor-pop-up  | ||||
|                     @dismiss="removePopup()"  | ||||
|                     @modeUpdated="saveMode"  | ||||
|                     @fixedBoundsUpdated="saveFixedBounds"  | ||||
|                     @clockOffsetsUpdated="saveClockOffsets"  | ||||
|                     :bottom="false"  | ||||
|                     :positionX="positionX"  | ||||
|                     :positionY="positionY" />` | ||||
|             }).$mount(); | ||||
|  | ||||
|             return popupElement; | ||||
|         }, | ||||
|  | ||||
|         registerPopUp() { | ||||
|             this.timeConductorOptionsHolder.addEventListener('click', this.showPopup); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| @@ -21,49 +21,39 @@ | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
| <div | ||||
|     class="c-conductor" | ||||
|     ref="timeConductorOptionsHolder" | ||||
|     class="c-compact-tc" | ||||
|     :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 | ||||
|             id="independentTCToggle" | ||||
|             :checked="independentTCEnabled" | ||||
|             :title="`${independentTCEnabled ? 'Disable' : 'Enable'} independent Time Conductor`" | ||||
|             @change="toggleIndependentTC" | ||||
|         /> | ||||
|     <toggle-switch | ||||
|         id="independentTCToggle" | ||||
|         class="c-toggle-switch--mini" | ||||
|         :checked="independentTCEnabled" | ||||
|         :title="`${independentTCEnabled ? 'Disable' : 'Enable'} independent Time Conductor`" | ||||
|         @change="toggleIndependentTC" | ||||
|     /> | ||||
|  | ||||
|         <ConductorModeIcon /> | ||||
|     <ConductorModeIcon v-if="independentTCEnabled" /> | ||||
|  | ||||
|         <div | ||||
|             v-if="timeOptions && independentTCEnabled" | ||||
|             class="c-conductor__controls" | ||||
|         > | ||||
|             <Mode | ||||
|                 v-if="mode" | ||||
|                 class="c-conductor__mode-select" | ||||
|                 :key-string="domainObject.identifier.key" | ||||
|                 :mode="timeOptions.mode" | ||||
|                 :enabled="independentTCEnabled" | ||||
|                 @modeChanged="saveMode" | ||||
|             /> | ||||
|     <conductor-inputs-fixed | ||||
|         v-if="isFixed && independentTCEnabled" | ||||
|         class="c-compact-tc__bounds--fixed" | ||||
|         :object-path="objectPath" | ||||
|         :read-only="true" | ||||
|         :compact="true" | ||||
|     /> | ||||
|  | ||||
|             <conductor-inputs-fixed | ||||
|                 v-if="isFixed" | ||||
|                 :key-string="domainObject.identifier.key" | ||||
|                 :object-path="objectPath" | ||||
|                 @updated="saveFixedOffsets" | ||||
|             /> | ||||
|  | ||||
|             <conductor-inputs-realtime | ||||
|                 v-else | ||||
|                 :key-string="domainObject.identifier.key" | ||||
|                 :object-path="objectPath" | ||||
|                 @updated="saveClockOffsets" | ||||
|             /> | ||||
|         </div> | ||||
|     </div> | ||||
|     <conductor-inputs-realtime | ||||
|         v-if="!isFixed && independentTCEnabled" | ||||
|         class="c-compact-tc__bounds--real-time" | ||||
|         :object-path="objectPath" | ||||
|         :read-only="true" | ||||
|         :compact="true" | ||||
|     /> | ||||
|     <div class="c-not-button c-not-button--compact c-compact-tc__gear icon-gear"></div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -72,16 +62,16 @@ import ConductorInputsFixed from "../ConductorInputsFixed.vue"; | ||||
| import ConductorInputsRealtime from "../ConductorInputsRealtime.vue"; | ||||
| import ConductorModeIcon from "@/plugins/timeConductor/ConductorModeIcon.vue"; | ||||
| import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue'; | ||||
| import Mode from "./Mode.vue"; | ||||
| import independentTimeConductorPopUpManager from "./independentTimeConductorPopUpManager"; | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
|         Mode, | ||||
|         ConductorModeIcon, | ||||
|         ConductorInputsRealtime, | ||||
|         ConductorInputsFixed, | ||||
|         ToggleSwitch | ||||
|     }, | ||||
|     mixins: [independentTimeConductorPopUpManager], | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         domainObject: { | ||||
| @@ -94,13 +84,19 @@ export default { | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         const bounds = this.openmct.time.bounds(); | ||||
|  | ||||
|         return { | ||||
|             timeOptions: this.domainObject.configuration.timeOptions || { | ||||
|                 clockOffsets: this.openmct.time.clockOffsets(), | ||||
|                 fixedOffsets: this.openmct.time.bounds() | ||||
|             }, | ||||
|             mode: undefined, | ||||
|             independentTCEnabled: this.domainObject.configuration.useIndependentTime === true | ||||
|             independentTCEnabled: this.domainObject.configuration.useIndependentTime === true, | ||||
|             viewBounds: { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end | ||||
|             } | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -130,6 +126,13 @@ export default { | ||||
|                 } | ||||
|             }, | ||||
|             deep: true | ||||
|         }, | ||||
|         objectPath: { | ||||
|             handler() { | ||||
|                 //domain object or view has probably changed | ||||
|                 this.setTimeContext(); | ||||
|             }, | ||||
|             deep: true | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
| @@ -163,6 +166,7 @@ export default { | ||||
|             if (this.independentTCEnabled) { | ||||
|                 this.registerIndependentTimeOffsets(); | ||||
|             } else { | ||||
|                 this.removePopup(); | ||||
|                 this.destroyIndependentTime(); | ||||
|             } | ||||
|  | ||||
| @@ -187,11 +191,10 @@ export default { | ||||
|                 this.registerIndependentTimeOffsets(); | ||||
|             } | ||||
|         }, | ||||
|         saveFixedOffsets(offsets) { | ||||
|         saveFixedBounds(bounds) { | ||||
|             const newOptions = Object.assign({}, this.timeOptions, { | ||||
|                 fixedOffsets: offsets | ||||
|                 fixedOffsets: bounds | ||||
|             }); | ||||
|  | ||||
|             this.updateTimeOptions(newOptions); | ||||
|         }, | ||||
|         saveClockOffsets(offsets) { | ||||
|   | ||||
| @@ -28,7 +28,11 @@ | ||||
|     <div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left"> | ||||
|         <button | ||||
|             v-if="selectedMode" | ||||
|             class="c-button--menu c-mode-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> | ||||
| @@ -55,6 +59,13 @@ export default { | ||||
|             default() { | ||||
|                 return false; | ||||
|             } | ||||
|         }, | ||||
|         buttonCssClass: { | ||||
|             type: String, | ||||
|             required: false, | ||||
|             default() { | ||||
|                 return ''; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     data: function () { | ||||
|   | ||||
| @@ -0,0 +1,147 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 Vue from "vue"; | ||||
| import ConductorPopUp from "../ConductorPopUp.vue"; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     mounted() { | ||||
|         this.showPopup = this.showPopup.bind(this); | ||||
|         this.clearPopup = this.clearPopup.bind(this); | ||||
|         this.positionBox = this.positionBox.bind(this); | ||||
|         this.positionBox = raf(this.positionBox); | ||||
|         this.timeConductorOptionsHolder = this.$refs.timeConductorOptionsHolder; | ||||
|         this.registerPopUp(); | ||||
|         this.popupComponent = this.createPopupComponent(); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.removePopup(); | ||||
|     }, | ||||
|     methods: { | ||||
|         showPopup() { | ||||
|             if (!this.independentTCEnabled) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             const popupElement = this.popupComponent; | ||||
|  | ||||
|             document.body.appendChild(popupElement.$el); | ||||
|             //Use capture, so we don't trigger immediately on the same iteration of the event loop | ||||
|             document.addEventListener('click', this.clearPopup, { | ||||
|                 capture: true | ||||
|             }); | ||||
|  | ||||
|             this.positionBox(); | ||||
|  | ||||
|             window.addEventListener('resize', this.positionBox); | ||||
|         }, | ||||
|  | ||||
|         positionBox() { | ||||
|             const popupElement = this.popupComponent; | ||||
|             const timeConductorOptions = this.timeConductorOptionsHolder; | ||||
|  | ||||
|             let timeConductorOptionsBox = timeConductorOptions.getBoundingClientRect(); | ||||
|             popupElement.positionX = timeConductorOptionsBox.left; | ||||
|             //TODO: PositionY should be calculated to be top or bottom based on the location of the conductor options | ||||
|             popupElement.positionY = timeConductorOptionsBox.top; | ||||
|             const offsetTop = popupElement.$el.getBoundingClientRect().height; | ||||
|  | ||||
|             const popupRight = popupElement.positionX + popupElement.$el.clientWidth; | ||||
|             const offsetLeft = Math.min(window.innerWidth - popupRight, 0); | ||||
|  | ||||
|             popupElement.positionX = popupElement.positionX + offsetLeft; | ||||
|             popupElement.positionY = popupElement.positionY - offsetTop; | ||||
|         }, | ||||
|  | ||||
|         clearPopup(clickAwayEvent) { | ||||
|             if (this.canClose(clickAwayEvent)) { | ||||
|                 clickAwayEvent.stopPropagation(); | ||||
|                 this.removePopup(); | ||||
|             } | ||||
|         }, | ||||
|         canClose(clickAwayEvent) { | ||||
|             const popupElement = this.popupComponent; | ||||
|  | ||||
|             const isChildMenu = clickAwayEvent.target.closest('.c-menu') !== null; | ||||
|             const isPopupElementItem = popupElement.$el.contains(clickAwayEvent.target); | ||||
|  | ||||
|             return !isChildMenu && !isPopupElementItem; | ||||
|         }, | ||||
|         removePopup() { | ||||
|             const popupElement = this.popupComponent; | ||||
|             document.removeEventListener('click', this.clearPopup, { | ||||
|                 capture: true | ||||
|             }); | ||||
|             window.removeEventListener('resize', this.positionBox); | ||||
|             popupElement.$el.remove(); | ||||
|         }, | ||||
|  | ||||
|         createPopupComponent() { | ||||
|             const saveFixedBounds = this.saveFixedBounds; | ||||
|             const saveClockOffsets = this.saveClockOffsets; | ||||
|             const saveMode = this.saveMode; | ||||
|             const removePopup = this.removePopup; | ||||
|             const objectPath = this.objectPath; | ||||
|             const timeOptions = this.timeOptions; | ||||
|  | ||||
|             const popupElement = new Vue({ | ||||
|                 components: { | ||||
|                     ConductorPopUp | ||||
|                 }, | ||||
|                 provide: { | ||||
|                     openmct: this.openmct, | ||||
|                     configuration: undefined | ||||
|                 }, | ||||
|                 data() { | ||||
|                     return { | ||||
|                         positionX: 0, | ||||
|                         positionY: 0, | ||||
|                         saveClockOffsets, | ||||
|                         saveFixedBounds, | ||||
|                         saveMode, | ||||
|                         removePopup, | ||||
|                         timeOptions, | ||||
|                         objectPath | ||||
|                     }; | ||||
|                 }, | ||||
|                 template: `<conductor-pop-up  | ||||
|                     @dismiss="removePopup()"  | ||||
|                     @independentModeUpdated="saveMode"  | ||||
|                     @fixedBoundsUpdated="saveFixedBounds" | ||||
|                     @clockOffsetsUpdated="saveClockOffsets"  | ||||
|                     :object-path="objectPath" | ||||
|                     :is-independent="true" | ||||
|                     :time-options="timeOptions" | ||||
|                     :bottom="true"  | ||||
|                     :positionX="positionX"  | ||||
|                     :positionY="positionY" />` | ||||
|             }).$mount(); | ||||
|  | ||||
|             return popupElement; | ||||
|         }, | ||||
|  | ||||
|         registerPopUp() { | ||||
|             this.timeConductorOptionsHolder.addEventListener('click', this.showPopup); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| @@ -106,7 +106,7 @@ describe('time conductor', () => { | ||||
|  | ||||
|     describe('in realtime mode', () => { | ||||
|         beforeEach((done) => { | ||||
|             const switcher = appHolder.querySelector('.c-mode-button'); | ||||
|             const switcher = appHolder.querySelector('.js-mode-button'); | ||||
|             const clickEvent = createMouseEvent("click"); | ||||
|  | ||||
|             switcher.dispatchEvent(clickEvent); | ||||
|   | ||||
| @@ -1,96 +1,160 @@ | ||||
| <template> | ||||
| <div | ||||
|     class="pr-tc-input-menu" | ||||
|     :class="{'pr-tc-input-menu--bottom' : bottom === true}" | ||||
|     class="c-tc-input-popup" | ||||
|     :class="{'c-tc-input-popup--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> | ||||
|     <slot></slot> | ||||
|     <div | ||||
|         v-if="false" | ||||
|     > | ||||
|         <div class="c-tc-input-popup__options c-tc-input-popup__options--real-time"> | ||||
|             Buttons here | ||||
|         </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 class="c-tc-input-popup__input-grid"> | ||||
|             <div class="pr-time-label icon-line-horz">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="inputHrs" | ||||
|                     v-model="inputHrs" | ||||
|                     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('inputHrs')" | ||||
|                     @wheel="increment($event, 'inputHrs')" | ||||
|                 > | ||||
|                 : | ||||
|             </div> | ||||
|             <div class="pr-time-input"> | ||||
|                 <input | ||||
|                     ref="inputMins" | ||||
|                     v-model="inputMins" | ||||
|                     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('inputMins')" | ||||
|                     @wheel="increment($event, 'inputMins')" | ||||
|                 > | ||||
|                 : | ||||
|             </div> | ||||
|             <div class="pr-time-input"> | ||||
|                 <input | ||||
|                     ref="inputSecs" | ||||
|                     v-model="inputSecs" | ||||
|                     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('inputSecs')" | ||||
|                     @wheel="increment($event, 'inputSecs')" | ||||
|                 > | ||||
|             </div> | ||||
|  | ||||
|             <div class="pr-time-input pr-time-input__start-end-sep icon-arrows-right-left"></div> | ||||
|  | ||||
|             <div class="pr-time-input"> | ||||
|                 <input | ||||
|                     ref="inputHrs" | ||||
|                     v-model="inputHrs" | ||||
|                     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('inputHrs')" | ||||
|                     @wheel="increment($event, 'inputHrs')" | ||||
|                 > | ||||
|                 : | ||||
|             </div> | ||||
|             <div class="pr-time-input"> | ||||
|                 <input | ||||
|                     ref="inputMins" | ||||
|                     v-model="inputMins" | ||||
|                     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('inputMins')" | ||||
|                     @wheel="increment($event, 'inputMins')" | ||||
|                 > | ||||
|                 : | ||||
|             </div> | ||||
|             <div class="pr-time-input pr-time-input--buttons"> | ||||
|                 <input | ||||
|                     ref="inputSecs" | ||||
|                     v-model="inputSecs" | ||||
|                     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('inputSecs')" | ||||
|                     @wheel="increment($event, 'inputSecs')" | ||||
|                 > | ||||
|             </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> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| export default { | ||||
|     props: { | ||||
|         type: { | ||||
|             type: String, | ||||
|             required: true | ||||
|         }, | ||||
|         offset: { | ||||
|             type: String, | ||||
|             required: true | ||||
|         }, | ||||
|         bottom: { | ||||
|             type: Boolean, | ||||
|             default() { | ||||
| @@ -106,6 +170,11 @@ export default { | ||||
|             isDisabled: false | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
|         isRealtime() { | ||||
|             return this.mode.indexOf('realtime') !== -1; | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.setOffset(); | ||||
|         document.addEventListener('click', this.hide); | ||||
|   | ||||
							
								
								
									
										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.timeSystem(); | ||||
|         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.timeSystem()))); | ||||
|     }, | ||||
|     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; | ||||
| $colorAHov: #fff; | ||||
| $filterHov: brightness(1.3) contrast(1.5); // Tree, location items | ||||
| $filterHovSubtle: brightness(1.2) contrast(1.2); | ||||
| $colorSelectedBg: rgba($colorKey, 0.3); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 20%); | ||||
|  | ||||
| @@ -139,13 +140,30 @@ $colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%); | ||||
| $colorKeySubtle: pushBack($colorKey, 10%); | ||||
|  | ||||
| // Time Colors | ||||
| $colorTime: #618cff; | ||||
| $colorTimeBg: $colorTime; | ||||
| $colorTimeFg: pullForward($colorTimeBg, 30%); | ||||
| $colorTimeHov: pullForward($colorTime, 10%); | ||||
| $colorTimeSubtle: pushBack($colorTime, 20%); | ||||
| $colorTimeFixed: #59554C; | ||||
| $colorTimeFixedBg: $colorTimeFixed; | ||||
| $colorTimeFixedFg: #eee; | ||||
| $colorTimeFixedFgSubtle: #B2AA98; | ||||
| $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 | ||||
| $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov | ||||
| $colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov | ||||
| $timeConductorAxisHoverFilter: brightness(1.2); | ||||
| $timeConductorActiveBg: $colorKey; | ||||
| $timeConductorActivePanBg: #226074; | ||||
| @@ -241,7 +259,7 @@ $controlDisabledOpacity: 0.2; | ||||
| $colorMenuBg: $colorBodyBg; | ||||
| $colorMenuFg: $colorBodyFg; | ||||
| $colorMenuIc: $colorKey; | ||||
| $filterMenu:  brightness(1.4); | ||||
| $filterMenu:  brightness(1.2); | ||||
| $colorMenuHovBg: rgba($colorKey, 0.5); | ||||
| $colorMenuHovFg: $colorBodyFgEm; | ||||
| $colorMenuHovIc: $colorMenuHovFg; | ||||
|   | ||||
| @@ -85,6 +85,7 @@ $colorInteriorBorder: rgba($colorBodyFg, 0.2); | ||||
| $colorA: #ccc; | ||||
| $colorAHov: #fff; | ||||
| $filterHov: brightness(1.3) contrast(1.5); // Tree, location items | ||||
| $filterHovSubtle: brightness(1.2) contrast(1.2); | ||||
| $colorSelectedBg: rgba($colorKey, 0.3); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 20%); | ||||
|  | ||||
| @@ -143,13 +144,30 @@ $colorBodyBgSubtleHov: pushBack($colorKey, 50%); | ||||
| $colorKeySubtle: pushBack($colorKey, 10%); | ||||
|  | ||||
| // Time Colors | ||||
| $colorTime: #618cff; | ||||
| $colorTimeBg: $colorTime; | ||||
| $colorTimeFg: pullForward($colorTimeBg, 30%); | ||||
| $colorTimeHov: pullForward($colorTime, 10%); | ||||
| $colorTimeSubtle: pushBack($colorTime, 20%); | ||||
| $colorTimeFixed: #59554C; | ||||
| $colorTimeFixedBg: $colorTimeFixed; | ||||
| $colorTimeFixedFg: #eee; | ||||
| $colorTimeFixedFgSubtle: #B2AA98; | ||||
| $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 | ||||
| $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov | ||||
| $colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov | ||||
| $timeConductorAxisHoverFilter: brightness(1.2); | ||||
| $timeConductorActiveBg: $colorKey; | ||||
| $timeConductorActivePanBg: #226074; | ||||
|   | ||||
| @@ -81,6 +81,7 @@ $colorInteriorBorder: rgba($colorBodyFg, 0.2); | ||||
| $colorA: $colorBodyFg; | ||||
| $colorAHov: $colorKey; | ||||
| $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%); | ||||
| $colorSelectedFg: pullForward($colorBodyFg, 10%); | ||||
|  | ||||
| @@ -139,13 +140,30 @@ $colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%); | ||||
| $colorKeySubtle: pushBack($colorKey, 20%); | ||||
|  | ||||
| // Time Colors | ||||
| $colorTime: #618cff; | ||||
| $colorTimeBg: $colorTime; | ||||
| $colorTimeFg: $colorBodyBg; | ||||
| $colorTimeHov: pushBack($colorTime, 5%); | ||||
| $colorTimeSubtle: pushBack($colorTime, 20%); | ||||
| $colorTimeFixed: #59554C; | ||||
| $colorTimeFixedBg: $colorTimeFixed; | ||||
| $colorTimeFixedFg: #eee; | ||||
| $colorTimeFixedFgSubtle: #B2AA98; | ||||
| $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 | ||||
| $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov | ||||
| $colorTOIHov: $colorTimeRealtime; // was $timeControllerToiLineColorHov | ||||
| $timeConductorAxisHoverFilter: brightness(0.8); | ||||
| $timeConductorActiveBg: $colorKey; | ||||
| $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 Button */ | ||||
| // 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 */ | ||||
| body.desktop { | ||||
|     ::-webkit-scrollbar { | ||||
|   | ||||
| @@ -521,24 +521,33 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin cButton() { | ||||
|     @include cControl(); | ||||
|     @include cControlHov(); | ||||
|     @include themedButton(); | ||||
|     border-radius: $controlCr; | ||||
|     color: $colorBtnFg; | ||||
|     cursor: pointer; | ||||
|     padding: $interiorMargin floor($interiorMargin * 1.25); | ||||
| @mixin cButtonLayout() { | ||||
|     $pad: $interiorMargin; | ||||
|     padding: $pad floor($pad * 1.25); | ||||
|  | ||||
|     &:after, | ||||
|     > * + * { | ||||
|         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*='is-active']{ | ||||
|         background: $colorBtnMajorBg; | ||||
|         color: $colorBtnMajorFg; | ||||
|         background: $colorBtnMajorBg !important; | ||||
|         color: $colorBtnMajorFg !important; | ||||
|     } | ||||
|  | ||||
|     &[class*='--caution'] { | ||||
| @@ -576,7 +585,7 @@ | ||||
|     *:before { | ||||
|         // *:before handles any nested containers that may contain glyph elements | ||||
|         // Needed for c-togglebutton. | ||||
|         font-size: 1.25em; | ||||
|         font-size: 1.15em; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -61,6 +61,17 @@ | ||||
|                 'has-complex-content': complexContent | ||||
|             }" | ||||
|         > | ||||
|             <div | ||||
|                 v-if="supportsIndependentTime" | ||||
|                 class="c-conductor-holder--compact" | ||||
|             > | ||||
|                 <independent-time-conductor | ||||
|                     :domain-object="domainObject" | ||||
|                     :object-path="[domainObject]" | ||||
|                     @stateChanged="updateIndependentTimeState" | ||||
|                     @updated="saveTimeOptions" | ||||
|                 /> | ||||
|             </div> | ||||
|             <NotebookMenuSwitcher | ||||
|                 v-if="notebookEnabled" | ||||
|                 :domain-object="domainObject" | ||||
| @@ -105,6 +116,7 @@ | ||||
| <script> | ||||
| import ObjectView from './ObjectView.vue'; | ||||
| import NotebookMenuSwitcher from '@/plugins/notebook/components/NotebookMenuSwitcher.vue'; | ||||
| import IndependentTimeConductor from '@/plugins/timeConductor/independent/IndependentTimeConductor.vue'; | ||||
|  | ||||
| const SIMPLE_CONTENT_TYPES = [ | ||||
|     'clock', | ||||
| @@ -115,10 +127,18 @@ const SIMPLE_CONTENT_TYPES = [ | ||||
| ]; | ||||
| const CSS_WIDTH_LESS_STR = '--width-less-than-'; | ||||
|  | ||||
| const SupportedViewTypes = [ | ||||
|     'plot-stacked', | ||||
|     'plot-overlay', | ||||
|     'bar-graph.view', | ||||
|     'time-strip.view' | ||||
| ]; | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
|         ObjectView, | ||||
|         NotebookMenuSwitcher | ||||
|         NotebookMenuSwitcher, | ||||
|         IndependentTimeConductor | ||||
|     }, | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
| @@ -163,6 +183,11 @@ export default { | ||||
|     computed: { | ||||
|         statusClass() { | ||||
|             return (this.status) ? `is-status--${this.status}` : ''; | ||||
|         }, | ||||
|         supportsIndependentTime() { | ||||
|             // const viewKey = this.getViewKey(); | ||||
|  | ||||
|             return true; //this.domainObject && SupportedViewTypes.includes(viewKey); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
| @@ -233,6 +258,21 @@ export default { | ||||
|             } | ||||
|  | ||||
|             this.widthClass = wClass.trimStart(); | ||||
|         }, | ||||
|         getViewKey() { | ||||
|             let viewKey = this.this.$refs.objectView.viewKey; | ||||
|             if (this.objectViewKey) { | ||||
|                 viewKey = this.objectViewKey; | ||||
|             } | ||||
|  | ||||
|             return viewKey; | ||||
|         }, | ||||
|         //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); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div> | ||||
|     <div | ||||
|         v-if="supportsIndependentTime" | ||||
|         v-if="supportsIndependentTime && false" | ||||
|         class="c-conductor-holder--compact l-shell__main-independent-time-conductor" | ||||
|     > | ||||
|         <independent-time-conductor | ||||
|   | ||||
| @@ -15,6 +15,8 @@ | ||||
|  | ||||
|         .c-object-label { | ||||
|             font-size: 1.05em; | ||||
|             min-width: 20%; | ||||
|  | ||||
|             &__type-icon { | ||||
|                 opacity: $objectLabelTypeIconOpacity; | ||||
|             } | ||||
| @@ -37,7 +39,8 @@ | ||||
|     /*************************** FRAME CONTROLS */ | ||||
|     &__frame-controls { | ||||
|         display: flex; | ||||
|         flex: 0 0 auto; | ||||
|         flex: 0 1 auto; | ||||
|         overflow: hidden; | ||||
|  | ||||
|         &__btns, | ||||
|         &__more { | ||||
|   | ||||
| @@ -1,11 +1,10 @@ | ||||
| .c-object-label { | ||||
|     // <a> tag and draggable element that holds type icon and name. | ||||
|     // Used mostly in trees and lists | ||||
|     @include ellipsize(); | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     flex: 0 1 auto; | ||||
|     overflow: hidden; | ||||
|     white-space: nowrap; | ||||
|  | ||||
|     > * + * { margin-left: $interiorMargin; } | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,25 @@ | ||||
| @use 'sass:math'; | ||||
|  | ||||
| .c-toggle-switch { | ||||
|     $d: 12px; | ||||
|     $m: 2px; | ||||
| @mixin toggleSwitch($d: 12px, $m: 2px, $bg: $colorBtnBg) { | ||||
|     $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; | ||||
|     display: inline-flex; | ||||
|     align-items: center; | ||||
| @@ -20,6 +36,26 @@ | ||||
|         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 { | ||||
|         opacity: 0; | ||||
|         width: 0; | ||||
| @@ -35,31 +71,9 @@ | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__slider { | ||||
|         // Sits within __switch | ||||
|         background: $colorBtnBg; // TODO: make discrete theme constants for these colors | ||||
|         border-radius: $br; | ||||
|         display: inline-block; | ||||
|         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; | ||||
|     } | ||||
|     @include toggleSwitch(); | ||||
| } | ||||
|  | ||||
| .c-toggle-switch--mini { | ||||
|     @include toggleSwitch($d: 9px, $m: 0px); | ||||
| } | ||||
|   | ||||
| @@ -34,6 +34,17 @@ | ||||
|     </div> | ||||
|  | ||||
|     <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" | ||||
|                 @stateChanged="updateIndependentTimeState" | ||||
|                 @updated="saveTimeOptions" | ||||
|             /> | ||||
|         </div> | ||||
|         <ViewSwitcher | ||||
|             v-if="!isEditing" | ||||
|             :current-view="currentView" | ||||
| @@ -126,11 +137,20 @@ | ||||
| <script> | ||||
| import ViewSwitcher from './ViewSwitcher.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 = {}; | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
|         IndependentTimeConductor, | ||||
|         NotebookMenuSwitcher, | ||||
|         ViewSwitcher | ||||
|     }, | ||||
| @@ -220,6 +240,11 @@ export default { | ||||
|             } else { | ||||
|                 return 'Unlocked for editing - click to lock.'; | ||||
|             } | ||||
|         }, | ||||
|         supportsIndependentTime() { | ||||
|             const viewKey = this.getViewKey(); | ||||
|  | ||||
|             return this.domainObject && SupportedViewTypes.includes(viewKey); | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
| @@ -291,6 +316,14 @@ export default { | ||||
|         edit() { | ||||
|             this.openmct.editor.edit(); | ||||
|         }, | ||||
|         getViewKey() { | ||||
|             let viewKey = this.viewKey; | ||||
|             if (this.objectViewKey) { | ||||
|                 viewKey = this.objectViewKey; | ||||
|             } | ||||
|  | ||||
|             return viewKey; | ||||
|         }, | ||||
|         promptUserandCancelEditing() { | ||||
|             let dialog = this.openmct.overlays.dialog({ | ||||
|                 iconClass: 'alert', | ||||
| @@ -367,6 +400,13 @@ export default { | ||||
|         }, | ||||
|         setStatus(status) { | ||||
|             this.status = status; | ||||
|         }, | ||||
|         //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); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -283,17 +283,6 @@ | ||||
|         flex: 1 1 auto !important; | ||||
|     } | ||||
|  | ||||
|     &__time-conductor { | ||||
|         border-top: 1px solid $colorInteriorBorder; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         padding-top: $interiorMargin; | ||||
|  | ||||
|         > * + * { | ||||
|             margin-top: $interiorMargin; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__main { | ||||
|         > .l-pane { | ||||
|             padding: nth($shellPanePad, 1) 0; | ||||
| @@ -377,10 +366,10 @@ | ||||
|     align-items: center; | ||||
|     justify-content: space-between; | ||||
|  | ||||
|     [class*="__"] { | ||||
|         // Removes extraneous horizontal white space | ||||
|         display: inline-flex; | ||||
|     } | ||||
|     //[class*="__"] { | ||||
|     //    // Removes extraneous horizontal white space | ||||
|     //    display: inline-flex; | ||||
|     //} | ||||
|  | ||||
|     > * + * { | ||||
|         margin-left: $interiorMarginSm; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user