Compare commits
	
		
			1 Commits
		
	
	
		
			imagery-te
			...
			v1.4.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					14b5c8c1be | 
@@ -182,7 +182,7 @@ The following guidelines are provided for anyone contributing source code to the
 | 
			
		||||
1. Avoid the use of "magic" values.
 | 
			
		||||
   eg.
 | 
			
		||||
   ```JavaScript
 | 
			
		||||
   const UNAUTHORIZED = 401;
 | 
			
		||||
   Const UNAUTHORIZED = 401
 | 
			
		||||
   if (responseCode === UNAUTHORIZED)
 | 
			
		||||
   ```
 | 
			
		||||
   is preferable to
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "openmct",
 | 
			
		||||
  "version": "1.5.0-SNAPSHOT",
 | 
			
		||||
  "version": "1.4.1",
 | 
			
		||||
  "description": "The Open MCT core platform",
 | 
			
		||||
  "dependencies": {},
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
@@ -101,5 +101,5 @@
 | 
			
		||||
  },
 | 
			
		||||
  "author": "",
 | 
			
		||||
  "license": "Apache-2.0",
 | 
			
		||||
  "private": true
 | 
			
		||||
  "private": false
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
        <div class="c-imagery__main-image__bg"
 | 
			
		||||
             :class="{'paused unnsynced': isPaused,'stale':false }"
 | 
			
		||||
        >
 | 
			
		||||
            <div class="c-imagery__main-image__image js-imageryView-image"
 | 
			
		||||
            <div class="c-imagery__main-image__image"
 | 
			
		||||
                 :style="{
 | 
			
		||||
                     'background-image': imageUrl ? `url(${imageUrl})` : 'none',
 | 
			
		||||
                     'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
 | 
			
		||||
 
 | 
			
		||||
@@ -1,258 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -143,9 +143,7 @@ export default {
 | 
			
		||||
                this.openmct.notifications.alert(message);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const relativeHash = hash.slice(hash.indexOf('#'));
 | 
			
		||||
            const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
 | 
			
		||||
            window.location.hash = url.hash;
 | 
			
		||||
            window.location.hash = hash;
 | 
			
		||||
        },
 | 
			
		||||
        formatTime(unixTime, timeFormat) {
 | 
			
		||||
            return Moment.utc(unixTime).format(timeFormat);
 | 
			
		||||
 
 | 
			
		||||
@@ -73,15 +73,6 @@
 | 
			
		||||
            >
 | 
			
		||||
                <!-- RT start -->
 | 
			
		||||
                <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
 | 
			
		||||
                    ref="startOffset"
 | 
			
		||||
                    v-model="offsets.start"
 | 
			
		||||
@@ -90,7 +81,6 @@
 | 
			
		||||
                    autocorrect="off"
 | 
			
		||||
                    spellcheck="false"
 | 
			
		||||
                    @change="validateAllOffsets(); submitForm()"
 | 
			
		||||
                    @click="showTimePopupStart"
 | 
			
		||||
                >
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
@@ -107,7 +97,7 @@
 | 
			
		||||
                    autocorrect="off"
 | 
			
		||||
                    spellcheck="false"
 | 
			
		||||
                    :disabled="!isFixed"
 | 
			
		||||
                    @change="validateAllBounds('endDate'); submitForm ()"
 | 
			
		||||
                    @change="validateAllBounds('endDate'); submitForm()"
 | 
			
		||||
                >
 | 
			
		||||
                <date-picker
 | 
			
		||||
                    v-if="isFixed && isUTCBased"
 | 
			
		||||
@@ -124,15 +114,6 @@
 | 
			
		||||
            >
 | 
			
		||||
                <!-- RT end -->
 | 
			
		||||
                <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
 | 
			
		||||
                    ref="endOffset"
 | 
			
		||||
                    v-model="offsets.end"
 | 
			
		||||
@@ -141,7 +122,6 @@
 | 
			
		||||
                    autocorrect="off"
 | 
			
		||||
                    spellcheck="false"
 | 
			
		||||
                    @change="validateAllOffsets(); submitForm()"
 | 
			
		||||
                    @click="showTimePopupEnd"
 | 
			
		||||
                >
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
@@ -183,7 +163,6 @@ 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';
 | 
			
		||||
 | 
			
		||||
@@ -195,8 +174,7 @@ export default {
 | 
			
		||||
        DatePicker,
 | 
			
		||||
        ConductorAxis,
 | 
			
		||||
        ConductorModeIcon,
 | 
			
		||||
        ConductorHistory,
 | 
			
		||||
        TimePopup
 | 
			
		||||
        ConductorHistory
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        let bounds = this.openmct.time.bounds();
 | 
			
		||||
@@ -230,9 +208,7 @@ export default {
 | 
			
		||||
            showDatePicker: false,
 | 
			
		||||
            altPressed: false,
 | 
			
		||||
            isPanning: false,
 | 
			
		||||
            isZooming: false,
 | 
			
		||||
            showTCInputStart: false,
 | 
			
		||||
            showTCInputEnd: false
 | 
			
		||||
            isZooming: false
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -481,25 +457,6 @@ 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(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 {
 | 
			
		||||
    .c-conductor__controls button {
 | 
			
		||||
    button {
 | 
			
		||||
        @include themedButton($colorTimeBg);
 | 
			
		||||
        color: $colorTimeFg;
 | 
			
		||||
 | 
			
		||||
@@ -236,77 +236,3 @@
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,127 +0,0 @@
 | 
			
		||||
<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