mirror of
https://github.com/nasa/openmct.git
synced 2025-06-30 12:43:04 +00:00
Compare commits
13 Commits
vue-hack
...
imagery-te
Author | SHA1 | Date | |
---|---|---|---|
b2dadaeb45 | |||
a98cf17b58 | |||
a874e906a0 | |||
d1f1707893 | |||
b7ec3605a7 | |||
8d7d7d8211 | |||
9697c20dc1 | |||
26266ab831 | |||
949f45b31c | |||
690c0e7466 | |||
cce6fe0f31 | |||
4c5a3362a0 | |||
fc87b3afec |
258
src/plugins/imagery/pluginSpec.js
Normal file
258
src/plugins/imagery/pluginSpec.js
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, 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 ImageryPlugin from './plugin.js';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import {
|
||||||
|
createOpenMct,
|
||||||
|
resetApplicationState
|
||||||
|
} from 'utils/testing';
|
||||||
|
|
||||||
|
const ONE_MINUTE = 1000 * 60;
|
||||||
|
const TEN_MINUTES = ONE_MINUTE * 10;
|
||||||
|
const MAIN_IMAGE_CLASS = '.js-imageryView-image';
|
||||||
|
const NEW_IMAGE_CLASS = '.c-imagery__age.c-imagery--new';
|
||||||
|
const REFRESH_CSS_MS = 500;
|
||||||
|
|
||||||
|
function getImageInfo(doc) {
|
||||||
|
let imageElement = doc.querySelectorAll(MAIN_IMAGE_CLASS)[0];
|
||||||
|
let timestamp = imageElement.dataset.openmctImageTimestamp;
|
||||||
|
let identifier = imageElement.dataset.openmctObjectKeystring;
|
||||||
|
let url = imageElement.style.backgroundImage;
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp,
|
||||||
|
identifier,
|
||||||
|
url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNew(doc) {
|
||||||
|
let newIcon = doc.querySelectorAll(NEW_IMAGE_CLASS);
|
||||||
|
|
||||||
|
return newIcon.length !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTelemetry(start, count) {
|
||||||
|
let telemetry = [];
|
||||||
|
|
||||||
|
for (let i = 1, l = count + 1; i < l; i++) {
|
||||||
|
let stringRep = i + 'minute';
|
||||||
|
let logo = 'images/logo-openmct.svg';
|
||||||
|
|
||||||
|
telemetry.push({
|
||||||
|
"name": stringRep + " Imagery",
|
||||||
|
"utc": start + (i * ONE_MINUTE),
|
||||||
|
"url": location.host + '/' + logo + '?time=' + stringRep,
|
||||||
|
"timeId": stringRep
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return telemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("The Imagery View Layout", () => {
|
||||||
|
const imageryKey = 'example.imagery';
|
||||||
|
const START = Date.now();
|
||||||
|
const COUNT = 10;
|
||||||
|
|
||||||
|
let openmct;
|
||||||
|
let imageryPlugin;
|
||||||
|
let parent;
|
||||||
|
let child;
|
||||||
|
let timeFormat = 'utc';
|
||||||
|
let bounds = {
|
||||||
|
start: START - TEN_MINUTES,
|
||||||
|
end: START
|
||||||
|
};
|
||||||
|
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
||||||
|
let imageryObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "imageryId"
|
||||||
|
},
|
||||||
|
name: "Example Imagery",
|
||||||
|
type: "example.imagery",
|
||||||
|
location: "parentId",
|
||||||
|
modified: 0,
|
||||||
|
persisted: 0,
|
||||||
|
telemetry: {
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
"name": "Image",
|
||||||
|
"key": "url",
|
||||||
|
"format": "image",
|
||||||
|
"hints": {
|
||||||
|
"image": 1,
|
||||||
|
"priority": 3
|
||||||
|
},
|
||||||
|
"source": "url"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Name",
|
||||||
|
"key": "name",
|
||||||
|
"source": "name",
|
||||||
|
"hints": {
|
||||||
|
"priority": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Time",
|
||||||
|
"key": "utc",
|
||||||
|
"format": "utc",
|
||||||
|
"hints": {
|
||||||
|
"domain": 2,
|
||||||
|
"priority": 1
|
||||||
|
},
|
||||||
|
"source": "utc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Local Time",
|
||||||
|
"key": "local",
|
||||||
|
"format": "local-format",
|
||||||
|
"hints": {
|
||||||
|
"domain": 1,
|
||||||
|
"priority": 2
|
||||||
|
},
|
||||||
|
"source": "local"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// this setups up the app
|
||||||
|
beforeEach((done) => {
|
||||||
|
const appHolder = document.createElement('div');
|
||||||
|
appHolder.style.width = '640px';
|
||||||
|
appHolder.style.height = '480px';
|
||||||
|
|
||||||
|
openmct = createOpenMct();
|
||||||
|
|
||||||
|
parent = document.createElement('div');
|
||||||
|
child = document.createElement('div');
|
||||||
|
parent.appendChild(child);
|
||||||
|
|
||||||
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||||
|
|
||||||
|
imageryPlugin = new ImageryPlugin();
|
||||||
|
openmct.install(imageryPlugin);
|
||||||
|
|
||||||
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
|
openmct.time.timeSystem(timeFormat, {
|
||||||
|
start: 0,
|
||||||
|
end: 4
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.on('start', done);
|
||||||
|
openmct.startHeadless(appHolder);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should provide an imagery view only for imagery producing objects", () => {
|
||||||
|
let applicableViews = openmct.objectViews.get(imageryObject);
|
||||||
|
let imageryView = applicableViews.find(
|
||||||
|
viewProvider => viewProvider.key === imageryKey
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imageryView).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("imagery view", () => {
|
||||||
|
let applicableViews;
|
||||||
|
let imageryViewProvider;
|
||||||
|
let imageryView;
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
let telemetryRequestResolve;
|
||||||
|
let telemetryRequestPromise = new Promise((resolve) => {
|
||||||
|
telemetryRequestResolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.telemetry.request.and.callFake(() => {
|
||||||
|
telemetryRequestResolve(imageTelemetry);
|
||||||
|
|
||||||
|
return telemetryRequestPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.time.clock('local', {
|
||||||
|
start: bounds.start,
|
||||||
|
end: bounds.end + 100
|
||||||
|
});
|
||||||
|
|
||||||
|
applicableViews = openmct.objectViews.get(imageryObject);
|
||||||
|
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
||||||
|
imageryView = imageryViewProvider.view(imageryObject);
|
||||||
|
imageryView.show(child);
|
||||||
|
|
||||||
|
await telemetryRequestPromise;
|
||||||
|
await Vue.nextTick();
|
||||||
|
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("on mount should show the the most recent image", () => {
|
||||||
|
const imageInfo = getImageInfo(parent);
|
||||||
|
|
||||||
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the clicked thumbnail as the main image", async () => {
|
||||||
|
const target = imageTelemetry[5].url;
|
||||||
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
await Vue.nextTick();
|
||||||
|
const imageInfo = getImageInfo(parent);
|
||||||
|
|
||||||
|
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show that an image is new", async (done) => {
|
||||||
|
await Vue.nextTick();
|
||||||
|
|
||||||
|
// used in code, need to wait to the 500ms here too
|
||||||
|
setTimeout(() => {
|
||||||
|
const imageIsNew = isNew(parent);
|
||||||
|
|
||||||
|
expect(imageIsNew).toBeTrue();
|
||||||
|
done();
|
||||||
|
}, REFRESH_CSS_MS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show that an image is not new", async (done) => {
|
||||||
|
const target = imageTelemetry[2].url;
|
||||||
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
|
||||||
|
await Vue.nextTick();
|
||||||
|
|
||||||
|
// used in code, need to wait to the 500ms here too
|
||||||
|
setTimeout(() => {
|
||||||
|
const imageIsNew = isNew(parent);
|
||||||
|
|
||||||
|
expect(imageIsNew).toBeFalse();
|
||||||
|
done();
|
||||||
|
}, REFRESH_CSS_MS);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -73,6 +73,15 @@
|
|||||||
>
|
>
|
||||||
<!-- RT start -->
|
<!-- RT start -->
|
||||||
<div class="c-direction-indicator icon-minus"></div>
|
<div class="c-direction-indicator icon-minus"></div>
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
ref="startOffset"
|
ref="startOffset"
|
||||||
v-model="offsets.start"
|
v-model="offsets.start"
|
||||||
@ -81,6 +90,7 @@
|
|||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
@change="validateAllOffsets(); submitForm()"
|
@change="validateAllOffsets(); submitForm()"
|
||||||
|
@click="showTimePopupStart"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -97,7 +107,7 @@
|
|||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
:disabled="!isFixed"
|
:disabled="!isFixed"
|
||||||
@change="validateAllBounds('endDate'); submitForm()"
|
@change="validateAllBounds('endDate'); submitForm ()"
|
||||||
>
|
>
|
||||||
<date-picker
|
<date-picker
|
||||||
v-if="isFixed && isUTCBased"
|
v-if="isFixed && isUTCBased"
|
||||||
@ -114,6 +124,15 @@
|
|||||||
>
|
>
|
||||||
<!-- RT end -->
|
<!-- RT end -->
|
||||||
<div class="c-direction-indicator icon-plus"></div>
|
<div class="c-direction-indicator icon-plus"></div>
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
ref="endOffset"
|
ref="endOffset"
|
||||||
v-model="offsets.end"
|
v-model="offsets.end"
|
||||||
@ -122,6 +141,7 @@
|
|||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
@change="validateAllOffsets(); submitForm()"
|
@change="validateAllOffsets(); submitForm()"
|
||||||
|
@click="showTimePopupEnd"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -163,6 +183,7 @@ import DatePicker from './DatePicker.vue';
|
|||||||
import ConductorAxis from './ConductorAxis.vue';
|
import ConductorAxis from './ConductorAxis.vue';
|
||||||
import ConductorModeIcon from './ConductorModeIcon.vue';
|
import ConductorModeIcon from './ConductorModeIcon.vue';
|
||||||
import ConductorHistory from './ConductorHistory.vue';
|
import ConductorHistory from './ConductorHistory.vue';
|
||||||
|
import TimePopup from './timePopup.vue';
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
|
||||||
@ -174,7 +195,8 @@ export default {
|
|||||||
DatePicker,
|
DatePicker,
|
||||||
ConductorAxis,
|
ConductorAxis,
|
||||||
ConductorModeIcon,
|
ConductorModeIcon,
|
||||||
ConductorHistory
|
ConductorHistory,
|
||||||
|
TimePopup
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
let bounds = this.openmct.time.bounds();
|
let bounds = this.openmct.time.bounds();
|
||||||
@ -208,7 +230,9 @@ export default {
|
|||||||
showDatePicker: false,
|
showDatePicker: false,
|
||||||
altPressed: false,
|
altPressed: false,
|
||||||
isPanning: false,
|
isPanning: false,
|
||||||
isZooming: false
|
isZooming: false,
|
||||||
|
showTCInputStart: false,
|
||||||
|
showTCInputEnd: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -457,6 +481,25 @@ export default {
|
|||||||
this.formattedBounds.end = this.timeFormatter.format(date);
|
this.formattedBounds.end = this.timeFormatter.format(date);
|
||||||
this.validateAllBounds('endDate');
|
this.validateAllBounds('endDate');
|
||||||
this.submitForm();
|
this.submitForm();
|
||||||
|
},
|
||||||
|
hideAllTimePopups() {
|
||||||
|
this.showTCInputStart = false;
|
||||||
|
this.showTCInputEnd = false;
|
||||||
|
},
|
||||||
|
showTimePopupStart() {
|
||||||
|
this.hideAllTimePopups();
|
||||||
|
this.showTCInputStart = !this.showTCInputStart;
|
||||||
|
},
|
||||||
|
showTimePopupEnd() {
|
||||||
|
this.hideAllTimePopups();
|
||||||
|
this.showTCInputEnd = !this.showTCInputEnd;
|
||||||
|
},
|
||||||
|
timePopUpdate(opts) {
|
||||||
|
let { type, hours, minutes, seconds } = opts;
|
||||||
|
|
||||||
|
this.offsets[type] = [hours, minutes, seconds].join(':');
|
||||||
|
this.setOffsetsFromView();
|
||||||
|
this.hideAllTimePopups();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.is-realtime-mode {
|
.is-realtime-mode {
|
||||||
button {
|
.c-conductor__controls button {
|
||||||
@include themedButton($colorTimeBg);
|
@include themedButton($colorTimeBg);
|
||||||
color: $colorTimeFg;
|
color: $colorTimeFg;
|
||||||
|
|
||||||
@ -236,3 +236,77 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prototype
|
||||||
|
[class^='pr-tc-input-menu'] {
|
||||||
|
background: $colorBodyBg;
|
||||||
|
border-radius: $controlCr;
|
||||||
|
filter: brightness(1.4);
|
||||||
|
box-shadow: $shdwMenu;
|
||||||
|
padding: $interiorMargin;
|
||||||
|
position: absolute;
|
||||||
|
width: 170px;
|
||||||
|
height: 90px;
|
||||||
|
bottom: 20px;
|
||||||
|
z-index: 99;
|
||||||
|
|
||||||
|
&[class*='--start'] {
|
||||||
|
left: -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='--end'] {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-top: $interiorMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[class^='pr-tim'] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
//margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
&[class*='labels'] {
|
||||||
|
font-size: 0.8em;
|
||||||
|
|
||||||
|
[class*='__'] {
|
||||||
|
opacity: 0.6;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='_hrs'] {
|
||||||
|
width: 67px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='inputs'] {
|
||||||
|
//background: deeppink;
|
||||||
|
border: 1px solid rgba($colorBodyFg, 0.2);
|
||||||
|
border-radius: $controlCr;
|
||||||
|
padding: 2px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: 1.25em;
|
||||||
|
width: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='_hrs'] {
|
||||||
|
width: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="colon"] {
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='__buttons'] {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
127
src/plugins/timeConductor/timePopup.vue
Normal file
127
src/plugins/timeConductor/timePopup.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="pr-tc-input-menu"
|
||||||
|
@keydown.enter.prevent
|
||||||
|
@keyup.enter.prevent="submit"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<div class="pr-tim-labels">
|
||||||
|
<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>
|
||||||
|
<div class="pr-tim-inputs">
|
||||||
|
<input
|
||||||
|
ref="inputHrs"
|
||||||
|
v-model="inputHrs"
|
||||||
|
class="pr-time-input__hrs"
|
||||||
|
step="1"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="999"
|
||||||
|
@focusin="selectAll($event)"
|
||||||
|
@focusout="format('inputHrs')"
|
||||||
|
@wheel="increment($event, 'inputHrs')"
|
||||||
|
>
|
||||||
|
<span class="pr-tim-colon">:</span>
|
||||||
|
<input
|
||||||
|
ref="inputMins"
|
||||||
|
v-model="inputMins"
|
||||||
|
type="number"
|
||||||
|
class="pr-time-input__mins"
|
||||||
|
min="0"
|
||||||
|
max="59"
|
||||||
|
step="1"
|
||||||
|
@focusin="selectAll($event)"
|
||||||
|
@focusout="format('inputMins')"
|
||||||
|
@wheel="increment($event, 'inputMins')"
|
||||||
|
>
|
||||||
|
<span class="pr-tim-colon">:</span>
|
||||||
|
<input
|
||||||
|
ref="inputSecs"
|
||||||
|
v-model="inputSecs"
|
||||||
|
type="number"
|
||||||
|
class="pr-time-input__secs"
|
||||||
|
min="0"
|
||||||
|
max="59"
|
||||||
|
step="1"
|
||||||
|
@focusin="selectAll($event)"
|
||||||
|
@focusout="format('inputSecs')"
|
||||||
|
@wheel="increment($event, 'inputSecs')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="pr-tim__buttons c-button-set c-button-set--strip-h">
|
||||||
|
<button class="c-button icon-check"
|
||||||
|
@click.prevent="submit"
|
||||||
|
></button>
|
||||||
|
<button class="c-button icon-x"
|
||||||
|
@click.prevent="hide"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
offset: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inputHrs: '000',
|
||||||
|
inputMins: '00',
|
||||||
|
inputSecs: '00'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setOffset();
|
||||||
|
document.addEventListener('click', this.hide);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
document.removeEventListener('click', this.hide);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
format(ref) {
|
||||||
|
const curVal = this[ref];
|
||||||
|
const padAmt = (ref === 'inputHrs') ? 3 : 2;
|
||||||
|
this[ref] = curVal.padStart(padAmt, '0');
|
||||||
|
},
|
||||||
|
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 padAmt = (ref === 'inputHrs') ? 3 : 2;
|
||||||
|
const step = (ref === 'inputHrs') ? 1 : 5;
|
||||||
|
const maxVal = (ref === 'inputHrs') ? 999 : 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(padAmt, '0');
|
||||||
|
},
|
||||||
|
setOffset() {
|
||||||
|
[this.inputHrs, this.inputMins, this.inputSecs] = this.offset.split(':');
|
||||||
|
this.inputHrs = this.inputHrs.padStart(3, '0');
|
||||||
|
this.$refs.inputHrs.focus();
|
||||||
|
},
|
||||||
|
selectAll($ev) {
|
||||||
|
$ev.target.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
Reference in New Issue
Block a user