mirror of
https://github.com/nasa/openmct.git
synced 2025-06-12 20:28:14 +00:00
[Time] Conductors and API Enhancements (#6768)
* Fixed #4975 - Compact Time Conductor styling * Fixed #5773 - Ubiquitous global clock * Mode functionality added to TimeAPI * TimeAPI modified to always have a ticking clock * Mode dropdown added to independent and regular time conductors * Overall conductor appearance modifications and enhancements * TimeAPI methods deprecated with warnings * Significant updates to markup, styling and behavior of main Time Conductor and independent version. --------- Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com> Co-authored-by: Shefali <simplyrender@gmail.com> Co-authored-by: Andrew Henry <akhenry@gmail.com> Co-authored-by: John Hill <john.c.hill@nasa.gov> Co-authored-by: Scott Bell <scott@traclabs.com>
This commit is contained in:
@ -20,76 +20,42 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<form ref="fixedDeltaInput" class="c-conductor__inputs">
|
||||
<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"
|
||||
/>
|
||||
<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__setting-wrapper">
|
||||
<div
|
||||
class="c-compact-tc__setting-value u-fade-truncate--lg --no-sep"
|
||||
:title="`Start bounds: ${formattedBounds.start}`"
|
||||
>
|
||||
{{ formattedBounds.start }}
|
||||
</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 class="c-compact-tc__bounds__start-end-sep icon-arrows-right-left"></div>
|
||||
<div
|
||||
class="c-compact-tc__setting-value u-fade-truncate--lg --no-sep"
|
||||
:title="`End bounds: ${formattedBounds.end}`"
|
||||
>
|
||||
{{ formattedBounds.end }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatePicker from './DatePicker.vue';
|
||||
import TimePopupFixed from './timePopupFixed.vue';
|
||||
import _ from 'lodash';
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatePicker
|
||||
TimePopupFixed
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
keyString: {
|
||||
type: String,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
inputBounds: {
|
||||
type: Object,
|
||||
default() {
|
||||
@ -101,20 +67,27 @@ 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 timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
let bounds = this.bounds || this.openmct.time.bounds();
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
const timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
let bounds = this.inputBounds || this.openmct.time.getBounds();
|
||||
|
||||
return {
|
||||
showTCInputStart: true,
|
||||
showTCInputEnd: true,
|
||||
durationFormatter,
|
||||
timeSystem,
|
||||
timeFormatter,
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
@ -128,8 +101,15 @@ export default {
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
keyString() {
|
||||
this.setTimeContext();
|
||||
objectPath: {
|
||||
handler(newPath, oldPath) {
|
||||
if (newPath === oldPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTimeContext();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
inputBounds: {
|
||||
handler(newBounds) {
|
||||
@ -140,41 +120,31 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
|
||||
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
|
||||
this.setTimeContext();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearAllValidation();
|
||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
||||
this.openmct.time.off(TIME_CONTEXT_EVENTS.timeSystemChanged, 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);
|
||||
this.handleNewBounds(this.timeContext.getBounds());
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.handleNewBounds);
|
||||
this.timeContext.off('clock', this.clearAllValidation);
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
|
||||
}
|
||||
},
|
||||
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;
|
||||
},
|
||||
@ -185,9 +155,6 @@ export default {
|
||||
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) {
|
||||
@ -195,112 +162,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('boundsUpdated', {
|
||||
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('dismissInputsFixed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user