New Time Conductor time input for real-time (#3409)

* Time Conductor Real Time input popup

Co-authored-by: Jamie Vigliotta <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Charles Hacskaylo 2021-05-12 11:54:10 -07:00 committed by GitHub
parent 633bac2ed5
commit fe899cbcc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 281 additions and 22 deletions

View File

@ -73,15 +73,22 @@
>
<!-- RT start -->
<div class="c-direction-indicator icon-minus"></div>
<input
<time-popup
v-if="showTCInputStart"
class="pr-tc-input-menu--start"
:type="'start'"
:offset="offsets.start"
@focus.native="$event.target.select()"
@hide="hideAllTimePopups"
@update="timePopUpdate"
/>
<button
@click="showTimePopupStart"
class="c-button c-conductor__delta-button"
ref="startOffset"
v-model="offsets.start"
class="c-input--hrs-min-sec"
type="text"
autocorrect="off"
spellcheck="false"
@change="validateAllOffsets(); submitForm()"
>
{{ offsets.start }}
</button>
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
@ -114,15 +121,22 @@
>
<!-- RT end -->
<div class="c-direction-indicator icon-plus"></div>
<input
<time-popup
v-if="showTCInputEnd"
class="pr-tc-input-menu--end"
:type="'end'"
:offset="offsets.end"
@focus.native="$event.target.select()"
@hide="hideAllTimePopups"
@update="timePopUpdate"
/>
<button
ref="endOffset"
v-model="offsets.end"
class="c-input--hrs-min-sec"
type="text"
autocorrect="off"
spellcheck="false"
@change="validateAllOffsets(); submitForm()"
class="c-button c-conductor__delta-button"
@click="showTimePopupEnd"
>
{{offsets.end}}
</button>
</div>
<conductor-axis
@ -164,6 +178,7 @@ import DatePicker from './DatePicker.vue';
import ConductorAxis from './ConductorAxis.vue';
import ConductorModeIcon from './ConductorModeIcon.vue';
import ConductorHistory from './ConductorHistory.vue';
import TimePopup from './timePopup.vue';
const DEFAULT_DURATION_FORMATTER = 'duration';
@ -174,7 +189,8 @@ export default {
DatePicker,
ConductorAxis,
ConductorModeIcon,
ConductorHistory
ConductorHistory,
TimePopup
},
inject: ['openmct', 'configuration'],
data() {
@ -209,7 +225,9 @@ export default {
showDatePicker: false,
altPressed: false,
isPanning: false,
isZooming: false
isZooming: false,
showTCInputStart: false,
showTCInputEnd: false
};
},
computed: {
@ -458,6 +476,23 @@ export default {
this.formattedBounds.end = this.timeFormatter.format(date);
this.validateAllBounds('endDate');
this.submitForm();
},
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(':');
this.setOffsetsFromView();
this.hideAllTimePopups();
}
}
};

View File

@ -207,14 +207,10 @@
}
.is-realtime-mode {
button {
.c-conductor__controls button,
.c-conductor__delta-button {
@include themedButton($colorTimeBg);
color: $colorTimeFg;
&:hover {
background: $colorTimeHov !important;
color: $colorTimeFg !important;
}
}
.c-conductor-input {
@ -233,6 +229,59 @@
box-shadow: none;
color: $colorTime;
pointer-events: none;
&[disabled] {
opacity: 1 !important;
}
}
}
}
// Prototype
[class^='pr-tc-input-menu'] {
// Uses ^= here to target both start and end menus
background: $colorBodyBg;
border-radius: $controlCr;
display: grid;
grid-template-columns: 1fr 1fr 2fr;
grid-column-gap: 3px;
grid-row-gap: 4px;
align-items: start;
filter: brightness(1.4);
box-shadow: $shdwMenu;
padding: $interiorMargin;
position: absolute;
bottom: 24px;
z-index: 99;
&[class*='--start'] {
left: -25px;
}
&[class*='--end'] {
right: 0;
}
}
[class^='pr-time'] {
&[class*='label'] {
font-size: 0.8em;
opacity: 0.6;
text-transform: uppercase;
}
&[class*='controls'] {
display: flex;
align-items: center;
white-space: nowrap;
input {
height: 22px;
line-height: 22px;
margin-right: $interiorMarginSm;
font-size: 1.25em;
width: 42px;
}
}
}

View File

@ -0,0 +1,171 @@
<template>
<div
class="pr-tc-input-menu"
@keydown.enter.prevent
@keyup.enter.prevent="submit"
@click.stop
>
<div class="pr-time-label__hrs">Hrs</div>
<div class="pr-time-label__mins">Mins</div>
<div class="pr-time-label__secs">Secs</div>
<div class="pr-time-controls">
<input
ref="inputHrs"
v-model="inputHrs"
class="pr-time-controls__hrs"
step="1"
type="number"
min="0"
max="23"
title="Enter 0 - 23"
@change="validate()"
@keyup="validate()"
@focusin="selectAll($event)"
@focusout="format('inputHrs')"
@wheel="increment($event, 'inputHrs')"
>
:
</div>
<div class="pr-time-controls">
<input
ref="inputMins"
v-model="inputMins"
type="number"
class="pr-time-controls__mins"
min="0"
max="59"
title="Enter 0 - 59"
step="1"
@change="validate()"
@keyup="validate()"
@focusin="selectAll($event)"
@focusout="format('inputMins')"
@wheel="increment($event, 'inputMins')"
>
:
</div>
<div class="pr-time-controls">
<input
ref="inputSecs"
v-model="inputSecs"
type="number"
class="pr-time-controls__secs"
min="0"
max="59"
title="Enter 0 - 59"
step="1"
@change="validate()"
@keyup="validate()"
@focusin="selectAll($event)"
@focusout="format('inputSecs')"
@wheel="increment($event, 'inputSecs')"
>
<div class="pr-time__buttons">
<button
class="c-button c-button--major icon-check"
:disabled="isDisabled"
@click.prevent="submit"
></button>
<button class="c-button icon-x"
@click.prevent="hide"
></button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
required: true
},
offset: {
type: String,
required: true
}
},
data() {
return {
inputHrs: '00',
inputMins: '00',
inputSecs: '00',
isDisabled: false
};
},
mounted() {
this.setOffset();
document.addEventListener('click', this.hide);
},
beforeDestroy() {
document.removeEventListener('click', this.hide);
},
methods: {
format(ref) {
const curVal = this[ref];
this[ref] = curVal.padStart(2, '0');
},
validate() {
let disabled = false;
let refs = ['inputHrs', 'inputMins', 'inputSecs'];
for (let ref of refs) {
let min = Number(this.$refs[ref].min);
let max = Number(this.$refs[ref].max);
let value = Number(this.$refs[ref].value);
if (value > max || value < min) {
disabled = true;
break;
}
}
this.isDisabled = disabled;
},
submit() {
this.$emit('update', {
type: this.type,
hours: this.inputHrs,
minutes: this.inputMins,
seconds: this.inputSecs
});
},
hide() {
this.$emit('hide');
},
increment($ev, ref) {
$ev.preventDefault();
const step = (ref === 'inputHrs') ? 1 : 5;
const maxVal = (ref === 'inputHrs') ? 23 : 59;
let cv = Math.round(parseInt(this[ref], 10) / step) * step;
cv = Math.min(maxVal, Math.max(0, ($ev.deltaY < 0) ? cv + step : cv - step));
this[ref] = cv.toString().padStart(2, '0');
this.validate();
},
setOffset() {
[this.inputHrs, this.inputMins, this.inputSecs] = this.offset.split(':');
this.numberSelect('inputHrs');
},
numberSelect(input) {
this.$refs[input].focus();
// change to text, select, then change back to number
// number inputs do not support select()
this.$nextTick(() => {
this.$refs[input].setAttribute('type', 'text');
this.$refs[input].select();
this.$nextTick(() => {
this.$refs[input].setAttribute('type', 'number');
});
});
},
selectAll($ev) {
$ev.target.select();
}
}
};
</script>

View File

@ -375,6 +375,10 @@
&:focus {
box-shadow: $shdwInputFoc;
}
&::selection {
background: rgba($colorKeySelectedBg, 0.5);
}
}
@mixin inlineInput() {