mirror of
https://github.com/nasa/openmct.git
synced 2025-01-29 15:43:52 +00:00
Imagery Age to be displayed for realtime mode in Imagery View (#3308)
* fix linting errors * removing testing units * WIP: stubbe in age in template, adding getAge function * WIP: stubbed in age in template, dummy function to start * added image age for realtime mode, ready for styling * reverting unnecesarry telemetryview file changes, not needed for this issue * checking for age tracking conditions on mount * Image age styling and changes - Cleaned up code in ImageryPlugin to use const instead of var, changed image delay time into a const * Image age styling and changes - WIP! - Layout changes for Imagery control-bar; - New animation effect, WIP; * Image age styling and changes - Markup and CSS updates for Imagery view; - Final layout for age indicator; * parsing image timestamp in case it is a string * using moment for human readable durations above 8 hours * UTC based timesystem check * reset "new" css class on image age when "time" updates * WIP: debuggin weird imagery plugin issue for first selection of image in thumbnails * fixing pause overwriting clicked images selection * making isImageNew a computed value * WIP: pr updates * WIP: tabling PR edits to focus on lower hanging PR edits for testathon * WIP * overhaul of imagery plugin logic for optimization PLUS imagery age * adding next/prev functionality to refactored plugin * added arrow left and right keys to navigate next and previous * added arrow key scrolling and scrolling thumbnail into view and hold down scrolling * adding in missing class * component based key listening, PR updates * refactor to use just imageIndex to track focused image, utilized more caching, PR comment edits Co-authored-by: David Tsay <david.e.tsay@nasa.gov> Co-authored-by: charlesh88 <charlesh88@gmail.com>
This commit is contained in:
parent
a91179091f
commit
ab76451360
@ -27,7 +27,7 @@ define([
|
|||||||
) {
|
) {
|
||||||
function ImageryPlugin() {
|
function ImageryPlugin() {
|
||||||
|
|
||||||
var IMAGE_SAMPLES = [
|
const IMAGE_SAMPLES = [
|
||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg",
|
||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg",
|
||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg",
|
||||||
@ -47,13 +47,14 @@ define([
|
|||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg",
|
||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
||||||
];
|
];
|
||||||
|
const IMAGE_DELAY = 20000;
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, name) {
|
function pointForTimestamp(timestamp, name) {
|
||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
utc: Math.floor(timestamp / 5000) * 5000,
|
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
local: Math.floor(timestamp / 5000) * 5000,
|
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
url: IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ define([
|
|||||||
subscribe: function (domainObject, callback) {
|
subscribe: function (domainObject, callback) {
|
||||||
var interval = setInterval(function () {
|
var interval = setInterval(function () {
|
||||||
callback(pointForTimestamp(Date.now(), domainObject.name));
|
callback(pointForTimestamp(Date.now(), domainObject.name));
|
||||||
}, 5000);
|
}, IMAGE_DELAY);
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
@ -81,9 +82,9 @@ define([
|
|||||||
var start = options.start;
|
var start = options.start;
|
||||||
var end = Math.min(options.end, Date.now());
|
var end = Math.min(options.end, Date.now());
|
||||||
var data = [];
|
var data = [];
|
||||||
while (start <= end && data.length < 5000) {
|
while (start <= end && data.length < IMAGE_DELAY) {
|
||||||
data.push(pointForTimestamp(start, domainObject.name));
|
data.push(pointForTimestamp(start, domainObject.name));
|
||||||
start += 5000;
|
start += IMAGE_DELAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(data);
|
return Promise.resolve(data);
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-imagery">
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
class="c-imagery"
|
||||||
|
@keyup="arrowUpHandler"
|
||||||
|
@keydown="arrowDownHandler"
|
||||||
|
@mouseover="focusElement"
|
||||||
|
>
|
||||||
<div class="c-imagery__main-image-wrapper has-local-controls">
|
<div class="c-imagery__main-image-wrapper has-local-controls">
|
||||||
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
|
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
|
||||||
<span class="holder flex-elem grows c-imagery__lc__sliders">
|
<span class="holder flex-elem grows c-imagery__lc__sliders">
|
||||||
@ -22,98 +28,188 @@
|
|||||||
></a>
|
></a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-image s-image-main c-imagery__main-image has-local-controls js-imageryView-image"
|
<div class="main-image s-image-main c-imagery__main-image has-local-controls"
|
||||||
:class="{'paused unnsynced': paused(),'stale':false }"
|
:class="{'paused unnsynced': isPaused,'stale':false }"
|
||||||
:style="{'background-image': getImageUrl() ? `url(${getImageUrl()})` : 'none',
|
:style="{'background-image': imageUrl ? `url(${imageUrl})` : 'none',
|
||||||
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
|
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
|
||||||
:data-openmct-image-timestamp="getTime()"
|
:data-openmct-image-timestamp="time"
|
||||||
:data-openmct-object-keystring="keystring"
|
:data-openmct-object-keystring="keyString"
|
||||||
>
|
>
|
||||||
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
||||||
<button class="c-nav c-nav--prev"
|
<button class="c-nav c-nav--prev"
|
||||||
title="Previous image"
|
title="Previous image"
|
||||||
:disabled="isPrevDisabled()"
|
:disabled="isPrevDisabled"
|
||||||
@click="prevImage()"
|
@click="prevImage()"
|
||||||
></button>
|
></button>
|
||||||
<button class="c-nav c-nav--next"
|
<button class="c-nav c-nav--next"
|
||||||
title="Next image"
|
title="Next image"
|
||||||
:disabled="isNextDisabled()"
|
:disabled="isNextDisabled"
|
||||||
@click="nextImage()"
|
@click="nextImage()"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="c-imagery__control-bar">
|
<div class="c-imagery__control-bar">
|
||||||
<div class="c-imagery__timestamp">{{ getTime() }}</div>
|
<div class="c-imagery__time">
|
||||||
|
<div class="c-imagery__timestamp">{{ time }}</div>
|
||||||
|
<div
|
||||||
|
v-if="canTrackDuration"
|
||||||
|
:class="{'c-imagery--new': isImageNew && !refreshCSS}"
|
||||||
|
class="c-imagery__age icon-timer"
|
||||||
|
>{{ formattedDuration }}</div>
|
||||||
|
</div>
|
||||||
<div class="h-local-controls flex-elem">
|
<div class="h-local-controls flex-elem">
|
||||||
<button
|
<button
|
||||||
class="c-button icon-pause pause-play"
|
class="c-button icon-pause pause-play"
|
||||||
:class="{'is-paused': paused()}"
|
:class="{'is-paused': isPaused}"
|
||||||
@click="paused(!paused(), true)"
|
@click="paused(!isPaused, 'button')"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="thumbsWrapper"
|
<div ref="thumbsWrapper"
|
||||||
class="c-imagery__thumbs-wrapper"
|
class="c-imagery__thumbs-wrapper"
|
||||||
:class="{'is-paused': paused()}"
|
:class="{'is-paused': isPaused}"
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
>
|
>
|
||||||
<div v-for="(imageData, index) in imageHistory"
|
<div v-for="(datum, index) in imageHistory"
|
||||||
:key="index"
|
:key="datum.url"
|
||||||
class="c-imagery__thumb c-thumb"
|
class="c-imagery__thumb c-thumb"
|
||||||
:class="{selected: imageData.selected}"
|
:class="{ selected: focusedImageIndex === index && isPaused }"
|
||||||
@click="setSelectedImage(imageData)"
|
@click="setFocusedImage(index, thumbnailClick)"
|
||||||
>
|
>
|
||||||
<img class="c-thumb__image"
|
<img class="c-thumb__image"
|
||||||
:src="getImageUrl(imageData)"
|
:src="formatImageUrl(datum)"
|
||||||
>
|
>
|
||||||
<div class="c-thumb__timestamp">{{ getTime(imageData) }}</div>
|
<div class="c-thumb__timestamp">{{ formatTime(datum) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
const REFRESH_CSS_MS = 500;
|
||||||
|
const DURATION_TRACK_MS = 1000;
|
||||||
|
const ARROW_DOWN_DELAY_CHECK_MS = 400;
|
||||||
|
const ARROW_SCROLL_RATE_MS = 100;
|
||||||
|
const THUMBNAIL_CLICKED = true;
|
||||||
|
|
||||||
|
const ONE_MINUTE = 60 * 1000;
|
||||||
|
const FIVE_MINUTES = 5 * ONE_MINUTE;
|
||||||
|
const ONE_HOUR = ONE_MINUTE * 60;
|
||||||
|
const EIGHT_HOURS = 8 * ONE_HOUR;
|
||||||
|
const TWENTYFOUR_HOURS = EIGHT_HOURS * 3;
|
||||||
|
|
||||||
|
const ARROW_RIGHT = 39;
|
||||||
|
const ARROW_LEFT = 37;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
data() {
|
data() {
|
||||||
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
autoScroll: true,
|
autoScroll: true,
|
||||||
|
durationFormatter: undefined,
|
||||||
filters: {
|
filters: {
|
||||||
brightness: 100,
|
brightness: 100,
|
||||||
contrast: 100
|
contrast: 100
|
||||||
},
|
},
|
||||||
image: {
|
|
||||||
selected: ''
|
|
||||||
},
|
|
||||||
imageFormat: '',
|
|
||||||
imageHistory: [],
|
imageHistory: [],
|
||||||
imageUrl: '',
|
thumbnailClick: THUMBNAIL_CLICKED,
|
||||||
isPaused: false,
|
isPaused: false,
|
||||||
metadata: {},
|
metadata: {},
|
||||||
requestCount: 0,
|
requestCount: 0,
|
||||||
timeFormat: '',
|
timeSystem: timeSystem,
|
||||||
keystring: ''
|
timeFormatter: undefined,
|
||||||
|
refreshCSS: false,
|
||||||
|
keyString: undefined,
|
||||||
|
focusedImageIndex: undefined,
|
||||||
|
numericDuration: undefined
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
bounds() {
|
time() {
|
||||||
return this.openmct.time.bounds();
|
return this.formatTime(this.focusedImage);
|
||||||
|
},
|
||||||
|
imageUrl() {
|
||||||
|
return this.formatImageUrl(this.focusedImage);
|
||||||
|
},
|
||||||
|
isImageNew() {
|
||||||
|
let cutoff = FIVE_MINUTES;
|
||||||
|
let age = this.numericDuration;
|
||||||
|
|
||||||
|
return age < cutoff && !this.refreshCSS;
|
||||||
|
},
|
||||||
|
canTrackDuration() {
|
||||||
|
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
||||||
|
},
|
||||||
|
isNextDisabled() {
|
||||||
|
let disabled = false;
|
||||||
|
|
||||||
|
if (this.focusedImageIndex === -1 || this.focusedImageIndex === this.imageHistory.length - 1) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return disabled;
|
||||||
|
},
|
||||||
|
isPrevDisabled() {
|
||||||
|
let disabled = false;
|
||||||
|
|
||||||
|
if (this.focusedImageIndex === 0 || this.imageHistory.length < 2) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return disabled;
|
||||||
|
},
|
||||||
|
focusedImage() {
|
||||||
|
return this.imageHistory[this.focusedImageIndex];
|
||||||
|
},
|
||||||
|
parsedSelectedTime() {
|
||||||
|
return this.parseTime(this.focusedImage);
|
||||||
|
},
|
||||||
|
formattedDuration() {
|
||||||
|
let result = 'N/A';
|
||||||
|
let negativeAge = -1;
|
||||||
|
|
||||||
|
if (this.numericDuration > TWENTYFOUR_HOURS) {
|
||||||
|
negativeAge *= (this.numericDuration / TWENTYFOUR_HOURS);
|
||||||
|
result = moment.duration(negativeAge, 'days').humanize(true);
|
||||||
|
} else if (this.numericDuration > EIGHT_HOURS) {
|
||||||
|
negativeAge *= (this.numericDuration / ONE_HOUR);
|
||||||
|
result = moment.duration(negativeAge, 'hours').humanize(true);
|
||||||
|
} else if (this.durationFormatter) {
|
||||||
|
result = this.durationFormatter.format(this.numericDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
focusedImageIndex() {
|
||||||
|
this.trackDuration();
|
||||||
|
this.resetAgeCSS();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// set
|
|
||||||
this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
|
||||||
this.imageFormat = this.openmct.telemetry.getValueFormatter(this.metadata.valuesForHints(['image'])[0]);
|
|
||||||
// initialize
|
|
||||||
this.timeKey = this.openmct.time.timeSystem().key;
|
|
||||||
this.timeFormat = this.openmct.telemetry.getValueFormatter(this.metadata.value(this.timeKey));
|
|
||||||
// listen
|
// listen
|
||||||
this.openmct.time.on('bounds', this.boundsChange);
|
this.openmct.time.on('bounds', this.boundsChange);
|
||||||
this.openmct.time.on('timeSystem', this.timeSystemChange);
|
this.openmct.time.on('timeSystem', this.timeSystemChange);
|
||||||
|
this.openmct.time.on('clock', this.clockChange);
|
||||||
|
|
||||||
|
// set
|
||||||
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.metadata.valuesForHints(['image'])[0]);
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
this.timeKey = this.timeSystem.key;
|
||||||
|
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||||
|
|
||||||
// kickoff
|
// kickoff
|
||||||
this.subscribe();
|
this.subscribe();
|
||||||
this.requestHistory();
|
this.requestHistory();
|
||||||
@ -127,41 +223,55 @@ export default {
|
|||||||
delete this.unsubscribe;
|
delete this.unsubscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.stopDurationTracking();
|
||||||
this.openmct.time.off('bounds', this.boundsChange);
|
this.openmct.time.off('bounds', this.boundsChange);
|
||||||
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
||||||
|
this.openmct.time.off('clock', this.clockChange);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
focusElement() {
|
||||||
|
this.$el.focus();
|
||||||
|
},
|
||||||
datumIsNotValid(datum) {
|
datumIsNotValid(datum) {
|
||||||
if (this.imageHistory.length === 0) {
|
if (this.imageHistory.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const datumTime = this.timeFormat.format(datum);
|
const datumURL = this.formatImageUrl(datum);
|
||||||
const datumURL = this.imageFormat.format(datum);
|
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
|
||||||
const lastHistoryTime = this.timeFormat.format(this.imageHistory.slice(-1)[0]);
|
|
||||||
const lastHistoryURL = this.imageFormat.format(this.imageHistory.slice(-1)[0]);
|
|
||||||
|
|
||||||
// datum is not valid if it matches the last datum in history,
|
// datum is not valid if it matches the last datum in history,
|
||||||
// or it is before the last datum in the history
|
// or it is before the last datum in the history
|
||||||
const datumTimeCheck = this.timeFormat.parse(datum);
|
const datumTimeCheck = this.parseTime(datum);
|
||||||
const historyTimeCheck = this.timeFormat.parse(this.imageHistory.slice(-1)[0]);
|
const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
|
||||||
const matchesLast = (datumTime === lastHistoryTime) && (datumURL === lastHistoryURL);
|
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
|
||||||
const isStale = datumTimeCheck < historyTimeCheck;
|
const isStale = datumTimeCheck < historyTimeCheck;
|
||||||
|
|
||||||
return matchesLast || isStale;
|
return matchesLast || isStale;
|
||||||
},
|
},
|
||||||
getImageUrl(datum) {
|
formatImageUrl(datum) {
|
||||||
return datum
|
if (!datum) {
|
||||||
? this.imageFormat.format(datum)
|
return;
|
||||||
: this.imageUrl;
|
}
|
||||||
|
|
||||||
|
return this.imageFormatter.format(datum);
|
||||||
},
|
},
|
||||||
getTime(datum) {
|
formatTime(datum) {
|
||||||
let dateTimeStr = datum
|
if (!datum) {
|
||||||
? this.timeFormat.format(datum)
|
return;
|
||||||
: this.time;
|
}
|
||||||
|
|
||||||
|
let dateTimeStr = this.timeFormatter.format(datum);
|
||||||
|
|
||||||
// Replace ISO "T" with a space to allow wrapping
|
// Replace ISO "T" with a space to allow wrapping
|
||||||
return dateTimeStr ? dateTimeStr.replace("T", " ") : "";
|
return dateTimeStr.replace("T", " ");
|
||||||
|
},
|
||||||
|
parseTime(datum) {
|
||||||
|
if (!datum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.timeFormatter.parse(datum);
|
||||||
},
|
},
|
||||||
handleScroll() {
|
handleScroll() {
|
||||||
const thumbsWrapper = this.$refs.thumbsWrapper;
|
const thumbsWrapper = this.$refs.thumbsWrapper;
|
||||||
@ -174,26 +284,35 @@ export default {
|
|||||||
|| (scrollHeight - scrollTop) > 2 * clientHeight;
|
|| (scrollHeight - scrollTop) > 2 * clientHeight;
|
||||||
this.autoScroll = !disableScroll;
|
this.autoScroll = !disableScroll;
|
||||||
},
|
},
|
||||||
paused(state, button = false) {
|
paused(state, type) {
|
||||||
if (arguments.length > 0 && state !== this.isPaused) {
|
|
||||||
this.unselectAllImages();
|
|
||||||
this.isPaused = state;
|
|
||||||
if (state === true && button) {
|
|
||||||
// If we are pausing, select the latest image in imageHistory
|
|
||||||
this.setSelectedImage(this.imageHistory[this.imageHistory.length - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.nextDatum) {
|
this.isPaused = state;
|
||||||
this.updateValues(this.nextDatum);
|
|
||||||
delete this.nextDatum;
|
|
||||||
} else {
|
|
||||||
this.updateValues(this.imageHistory[this.imageHistory.length - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.autoScroll = true;
|
if (type === 'button') {
|
||||||
|
this.setFocusedImage(this.imageHistory.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.isPaused;
|
if (this.nextImageIndex) {
|
||||||
|
this.setFocusedImage(this.nextImageIndex);
|
||||||
|
delete this.nextImageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.autoScroll = true;
|
||||||
|
},
|
||||||
|
scrollToFocused() {
|
||||||
|
const thumbsWrapper = this.$refs.thumbsWrapper;
|
||||||
|
if (!thumbsWrapper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let domThumb = thumbsWrapper.children[this.focusedImageIndex];
|
||||||
|
|
||||||
|
if (domThumb) {
|
||||||
|
domThumb.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center'
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scrollToRight() {
|
scrollToRight() {
|
||||||
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
|
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
|
||||||
@ -207,22 +326,17 @@ export default {
|
|||||||
|
|
||||||
setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0);
|
setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0);
|
||||||
},
|
},
|
||||||
setSelectedImage(image) {
|
setFocusedImage(index, thumbnailClick = false) {
|
||||||
// If we are paused and the current image IS selected, unpause
|
if (this.isPaused && !thumbnailClick) {
|
||||||
// Otherwise, set current image and pause
|
this.nextImageIndex = index;
|
||||||
if (!image) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isPaused && image.selected) {
|
this.focusedImageIndex = index;
|
||||||
this.paused(false);
|
|
||||||
this.unselectAllImages();
|
if (thumbnailClick && !this.isPaused) {
|
||||||
} else {
|
|
||||||
this.imageUrl = this.getImageUrl(image);
|
|
||||||
this.time = this.getTime(image);
|
|
||||||
this.paused(true);
|
this.paused(true);
|
||||||
this.unselectAllImages();
|
|
||||||
image.selected = true;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
boundsChange(bounds, isTick) {
|
boundsChange(bounds, isTick) {
|
||||||
@ -230,98 +344,158 @@ export default {
|
|||||||
this.requestHistory();
|
this.requestHistory();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestHistory() {
|
async requestHistory() {
|
||||||
const requestId = ++this.requestCount;
|
let bounds = this.openmct.time.bounds();
|
||||||
|
this.requestCount++;
|
||||||
|
const requestId = this.requestCount;
|
||||||
this.imageHistory = [];
|
this.imageHistory = [];
|
||||||
this.openmct.telemetry
|
let data = await this.openmct.telemetry
|
||||||
.request(this.domainObject, this.bounds)
|
.request(this.domainObject, bounds) || [];
|
||||||
.then((values = []) => {
|
|
||||||
if (this.requestCount === requestId) {
|
if (this.requestCount === requestId) {
|
||||||
// add each image to the history
|
data.forEach((datum, index) => {
|
||||||
// update values for the very last image (set current image time and url)
|
this.updateHistory(datum, index === data.length - 1);
|
||||||
values.forEach((datum, index) => this.updateHistory(datum, index === values.length - 1));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
timeSystemChange(system) {
|
timeSystemChange(system) {
|
||||||
// reset timesystem dependent variables
|
this.timeSystem = this.openmct.time.timeSystem();
|
||||||
this.timeKey = system.key;
|
this.timeKey = this.timeSystem.key;
|
||||||
this.timeFormat = this.openmct.telemetry.getValueFormatter(this.metadata.value(this.timeKey));
|
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||||
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
this.trackDuration();
|
||||||
|
},
|
||||||
|
clockChange(clock) {
|
||||||
|
this.trackDuration();
|
||||||
},
|
},
|
||||||
subscribe() {
|
subscribe() {
|
||||||
this.unsubscribe = this.openmct.telemetry
|
this.unsubscribe = this.openmct.telemetry
|
||||||
.subscribe(this.domainObject, (datum) => {
|
.subscribe(this.domainObject, (datum) => {
|
||||||
let parsedTimestamp = this.timeFormat.parse(datum);
|
let parsedTimestamp = this.parseTime(datum);
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
|
||||||
if (parsedTimestamp >= this.bounds.start && parsedTimestamp <= this.bounds.end) {
|
if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
|
||||||
this.updateHistory(datum);
|
this.updateHistory(datum);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
unselectAllImages() {
|
updateHistory(datum, setFocused = true) {
|
||||||
this.imageHistory.forEach(image => image.selected = false);
|
|
||||||
},
|
|
||||||
updateHistory(datum, updateValues = true) {
|
|
||||||
if (this.datumIsNotValid(datum)) {
|
if (this.datumIsNotValid(datum)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.imageHistory.push(datum);
|
this.imageHistory.push(datum);
|
||||||
|
|
||||||
if (updateValues) {
|
if (setFocused) {
|
||||||
this.updateValues(datum);
|
this.setFocusedImage(this.imageHistory.length - 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateValues(datum) {
|
getFormatter(key) {
|
||||||
if (this.isPaused) {
|
let metadataValue = this.metadata.value(key) || { format: key };
|
||||||
this.nextDatum = datum;
|
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||||
|
|
||||||
|
return valueFormatter;
|
||||||
|
},
|
||||||
|
trackDuration() {
|
||||||
|
if (this.canTrackDuration) {
|
||||||
|
this.stopDurationTracking();
|
||||||
|
this.updateDuration();
|
||||||
|
this.durationTracker = window.setInterval(
|
||||||
|
this.updateDuration, DURATION_TRACK_MS
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.stopDurationTracking();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stopDurationTracking() {
|
||||||
|
window.clearInterval(this.durationTracker);
|
||||||
|
},
|
||||||
|
updateDuration() {
|
||||||
|
let currentTime = this.openmct.time.clock().currentValue();
|
||||||
|
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||||
|
},
|
||||||
|
resetAgeCSS() {
|
||||||
|
this.refreshCSS = true;
|
||||||
|
// unable to make this work with nextTick
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshCSS = false;
|
||||||
|
}, REFRESH_CSS_MS);
|
||||||
|
},
|
||||||
|
nextImage() {
|
||||||
|
if (this.isNextDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.time = this.timeFormat.format(datum);
|
let index = this.focusedImageIndex;
|
||||||
this.imageUrl = this.imageFormat.format(datum);
|
|
||||||
},
|
this.setFocusedImage(++index, THUMBNAIL_CLICKED);
|
||||||
selectedImageIndex() {
|
|
||||||
return this.imageHistory.findIndex(image => image.selected);
|
|
||||||
},
|
|
||||||
setSelectedByIndex(index) {
|
|
||||||
this.setSelectedImage(this.imageHistory[index]);
|
|
||||||
},
|
|
||||||
nextImage() {
|
|
||||||
let index = this.selectedImageIndex();
|
|
||||||
this.setSelectedByIndex(++index);
|
|
||||||
if (index === this.imageHistory.length - 1) {
|
if (index === this.imageHistory.length - 1) {
|
||||||
this.paused(false);
|
this.paused(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prevImage() {
|
prevImage() {
|
||||||
let index = this.selectedImageIndex();
|
if (this.isPrevDisabled) {
|
||||||
if (index === -1) {
|
return;
|
||||||
this.setSelectedByIndex(this.imageHistory.length - 2);
|
}
|
||||||
|
|
||||||
|
let index = this.focusedImageIndex;
|
||||||
|
|
||||||
|
if (index === this.imageHistory.length - 1) {
|
||||||
|
this.setFocusedImage(this.imageHistory.length - 2, THUMBNAIL_CLICKED);
|
||||||
} else {
|
} else {
|
||||||
this.setSelectedByIndex(--index);
|
this.setFocusedImage(--index, THUMBNAIL_CLICKED);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isNextDisabled() {
|
arrowDownHandler(event) {
|
||||||
let disabled = false;
|
let key = event.keyCode;
|
||||||
let index = this.selectedImageIndex();
|
|
||||||
|
|
||||||
if (index === -1 || index === this.imageHistory.length - 1) {
|
if (this.isLeftOrRightArrowKey(key)) {
|
||||||
disabled = true;
|
this.arrowDown = true;
|
||||||
|
window.clearTimeout(this.arrowDownDelayTimeout);
|
||||||
|
this.arrowDownDelayTimeout = window.setTimeout(() => {
|
||||||
|
this.arrowKeyScroll(this.directionByKey(key));
|
||||||
|
}, ARROW_DOWN_DELAY_CHECK_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return disabled;
|
|
||||||
},
|
},
|
||||||
isPrevDisabled() {
|
arrowUpHandler(event) {
|
||||||
let disabled = false;
|
let key = event.keyCode;
|
||||||
let index = this.selectedImageIndex();
|
|
||||||
|
|
||||||
if (index === 0 || this.imageHistory.length < 2) {
|
window.clearTimeout(this.arrowDownDelayTimeout);
|
||||||
disabled = true;
|
|
||||||
|
if (this.isLeftOrRightArrowKey(key)) {
|
||||||
|
this.arrowDown = false;
|
||||||
|
let direction = this.directionByKey(key);
|
||||||
|
this[direction + 'Image']();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
arrowKeyScroll(direction) {
|
||||||
|
if (this.arrowDown) {
|
||||||
|
this.arrowKeyScrolling = true;
|
||||||
|
this[direction + 'Image']();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.arrowKeyScroll(direction);
|
||||||
|
}, ARROW_SCROLL_RATE_MS);
|
||||||
|
} else {
|
||||||
|
window.clearTimeout(this.arrowDownDelayTimeout);
|
||||||
|
this.arrowKeyScrolling = false;
|
||||||
|
this.scrollToFocused();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directionByKey(keyCode) {
|
||||||
|
let direction;
|
||||||
|
|
||||||
|
if (keyCode === ARROW_LEFT) {
|
||||||
|
direction = 'prev';
|
||||||
}
|
}
|
||||||
|
|
||||||
return disabled;
|
if (keyCode === ARROW_RIGHT) {
|
||||||
|
direction = 'next';
|
||||||
|
}
|
||||||
|
|
||||||
|
return direction;
|
||||||
|
},
|
||||||
|
isLeftOrRightArrowKey(keyCode) {
|
||||||
|
return [ARROW_RIGHT, ARROW_LEFT].includes(keyCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,10 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-top: $interiorMargin;
|
margin-top: $interiorMargin;
|
||||||
}
|
}
|
||||||
@ -25,14 +29,57 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__control-bar {
|
&__control-bar,
|
||||||
padding: 5px 0 0 0;
|
&__time {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: baseline;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__control-bar {
|
||||||
|
margin-top: 2px;
|
||||||
|
padding: $interiorMarginSm 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__time {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__timestamp,
|
||||||
|
&__age {
|
||||||
|
@include ellipsize();
|
||||||
|
flex: 0 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__timestamp {
|
&__timestamp {
|
||||||
flex: 1 1 auto;
|
flex-shrink: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__age {
|
||||||
|
border-radius: $controlCr;
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: baseline;
|
||||||
|
padding: 1px $interiorMarginSm;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-right: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--new {
|
||||||
|
// New imagery
|
||||||
|
$bgColor: $colorOk;
|
||||||
|
background: rgba($bgColor, 0.5);
|
||||||
|
@include flash($animName: flashImageAge, $dur: 250ms, $valStart: rgba($colorOk, 0.7), $valEnd: rgba($colorOk, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
&__thumbs-wrapper {
|
&__thumbs-wrapper {
|
||||||
@ -151,6 +198,8 @@
|
|||||||
/*************************************** BUTTONS */
|
/*************************************** BUTTONS */
|
||||||
.c-button.pause-play {
|
.c-button.pause-play {
|
||||||
// Pause icon set by default in markup
|
// Pause icon set by default in markup
|
||||||
|
justify-self: end;
|
||||||
|
|
||||||
&.is-paused {
|
&.is-paused {
|
||||||
background: $colorPausedBg !important;
|
background: $colorPausedBg !important;
|
||||||
color: $colorPausedFg;
|
color: $colorPausedFg;
|
||||||
|
@ -50,6 +50,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/************************** EFFECTS */
|
/************************** EFFECTS */
|
||||||
|
@mixin flash($animName: flash, $dur: 500ms, $dir: alternate, $iter: 20, $prop: background, $valStart: rgba($colorOk, 1), $valEnd: rgba($colorOk, 0)) {
|
||||||
|
@keyframes #{$animName} {
|
||||||
|
0% { #{$prop}: $valStart; }
|
||||||
|
100% { #{$prop}: $valEnd; }
|
||||||
|
}
|
||||||
|
animation-name: $animName;
|
||||||
|
animation-duration: $dur;
|
||||||
|
animation-direction: $dir;
|
||||||
|
animation-iteration-count: $iter;
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
@mixin mixedBg() {
|
@mixin mixedBg() {
|
||||||
$c1: nth($mixedSettingBg, 1);
|
$c1: nth($mixedSettingBg, 1);
|
||||||
$c2: nth($mixedSettingBg, 2);
|
$c2: nth($mixedSettingBg, 2);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user