mirror of
https://github.com/nasa/openmct.git
synced 2025-01-18 02:39:56 +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() {
|
||||
|
||||
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-18732.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-18748.jpg"
|
||||
];
|
||||
const IMAGE_DELAY = 20000;
|
||||
|
||||
function pointForTimestamp(timestamp, name) {
|
||||
return {
|
||||
name: name,
|
||||
utc: Math.floor(timestamp / 5000) * 5000,
|
||||
local: Math.floor(timestamp / 5000) * 5000,
|
||||
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
||||
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||
url: IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length]
|
||||
};
|
||||
}
|
||||
|
||||
@ -64,7 +65,7 @@ define([
|
||||
subscribe: function (domainObject, callback) {
|
||||
var interval = setInterval(function () {
|
||||
callback(pointForTimestamp(Date.now(), domainObject.name));
|
||||
}, 5000);
|
||||
}, IMAGE_DELAY);
|
||||
|
||||
return function () {
|
||||
clearInterval(interval);
|
||||
@ -81,9 +82,9 @@ define([
|
||||
var start = options.start;
|
||||
var end = Math.min(options.end, Date.now());
|
||||
var data = [];
|
||||
while (start <= end && data.length < 5000) {
|
||||
while (start <= end && data.length < IMAGE_DELAY) {
|
||||
data.push(pointForTimestamp(start, domainObject.name));
|
||||
start += 5000;
|
||||
start += IMAGE_DELAY;
|
||||
}
|
||||
|
||||
return Promise.resolve(data);
|
||||
|
@ -1,5 +1,11 @@
|
||||
<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="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">
|
||||
@ -22,98 +28,188 @@
|
||||
></a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="main-image s-image-main c-imagery__main-image has-local-controls js-imageryView-image"
|
||||
:class="{'paused unnsynced': paused(),'stale':false }"
|
||||
:style="{'background-image': getImageUrl() ? `url(${getImageUrl()})` : 'none',
|
||||
<div class="main-image s-image-main c-imagery__main-image has-local-controls"
|
||||
:class="{'paused unnsynced': isPaused,'stale':false }"
|
||||
:style="{'background-image': imageUrl ? `url(${imageUrl})` : 'none',
|
||||
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
|
||||
:data-openmct-image-timestamp="getTime()"
|
||||
:data-openmct-object-keystring="keystring"
|
||||
:data-openmct-image-timestamp="time"
|
||||
:data-openmct-object-keystring="keyString"
|
||||
>
|
||||
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
||||
<button class="c-nav c-nav--prev"
|
||||
title="Previous image"
|
||||
:disabled="isPrevDisabled()"
|
||||
:disabled="isPrevDisabled"
|
||||
@click="prevImage()"
|
||||
></button>
|
||||
<button class="c-nav c-nav--next"
|
||||
title="Next image"
|
||||
:disabled="isNextDisabled()"
|
||||
:disabled="isNextDisabled"
|
||||
@click="nextImage()"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<button
|
||||
class="c-button icon-pause pause-play"
|
||||
:class="{'is-paused': paused()}"
|
||||
@click="paused(!paused(), true)"
|
||||
:class="{'is-paused': isPaused}"
|
||||
@click="paused(!isPaused, 'button')"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="thumbsWrapper"
|
||||
class="c-imagery__thumbs-wrapper"
|
||||
:class="{'is-paused': paused()}"
|
||||
:class="{'is-paused': isPaused}"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div v-for="(imageData, index) in imageHistory"
|
||||
:key="index"
|
||||
<div v-for="(datum, index) in imageHistory"
|
||||
:key="datum.url"
|
||||
class="c-imagery__thumb c-thumb"
|
||||
:class="{selected: imageData.selected}"
|
||||
@click="setSelectedImage(imageData)"
|
||||
:class="{ selected: focusedImageIndex === index && isPaused }"
|
||||
@click="setFocusedImage(index, thumbnailClick)"
|
||||
>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
|
||||
return {
|
||||
autoScroll: true,
|
||||
durationFormatter: undefined,
|
||||
filters: {
|
||||
brightness: 100,
|
||||
contrast: 100
|
||||
},
|
||||
image: {
|
||||
selected: ''
|
||||
},
|
||||
imageFormat: '',
|
||||
imageHistory: [],
|
||||
imageUrl: '',
|
||||
thumbnailClick: THUMBNAIL_CLICKED,
|
||||
isPaused: false,
|
||||
metadata: {},
|
||||
requestCount: 0,
|
||||
timeFormat: '',
|
||||
keystring: ''
|
||||
timeSystem: timeSystem,
|
||||
timeFormatter: undefined,
|
||||
refreshCSS: false,
|
||||
keyString: undefined,
|
||||
focusedImageIndex: undefined,
|
||||
numericDuration: undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
bounds() {
|
||||
return this.openmct.time.bounds();
|
||||
time() {
|
||||
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() {
|
||||
// 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
|
||||
this.openmct.time.on('bounds', this.boundsChange);
|
||||
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
|
||||
this.subscribe();
|
||||
this.requestHistory();
|
||||
@ -127,41 +223,55 @@ export default {
|
||||
delete this.unsubscribe;
|
||||
}
|
||||
|
||||
this.stopDurationTracking();
|
||||
this.openmct.time.off('bounds', this.boundsChange);
|
||||
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
||||
this.openmct.time.off('clock', this.clockChange);
|
||||
},
|
||||
methods: {
|
||||
focusElement() {
|
||||
this.$el.focus();
|
||||
},
|
||||
datumIsNotValid(datum) {
|
||||
if (this.imageHistory.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const datumTime = this.timeFormat.format(datum);
|
||||
const datumURL = this.imageFormat.format(datum);
|
||||
const lastHistoryTime = this.timeFormat.format(this.imageHistory.slice(-1)[0]);
|
||||
const lastHistoryURL = this.imageFormat.format(this.imageHistory.slice(-1)[0]);
|
||||
const datumURL = this.formatImageUrl(datum);
|
||||
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
|
||||
|
||||
// datum is not valid if it matches the last datum in history,
|
||||
// or it is before the last datum in the history
|
||||
const datumTimeCheck = this.timeFormat.parse(datum);
|
||||
const historyTimeCheck = this.timeFormat.parse(this.imageHistory.slice(-1)[0]);
|
||||
const matchesLast = (datumTime === lastHistoryTime) && (datumURL === lastHistoryURL);
|
||||
const datumTimeCheck = this.parseTime(datum);
|
||||
const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
|
||||
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
|
||||
const isStale = datumTimeCheck < historyTimeCheck;
|
||||
|
||||
return matchesLast || isStale;
|
||||
},
|
||||
getImageUrl(datum) {
|
||||
return datum
|
||||
? this.imageFormat.format(datum)
|
||||
: this.imageUrl;
|
||||
formatImageUrl(datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.imageFormatter.format(datum);
|
||||
},
|
||||
getTime(datum) {
|
||||
let dateTimeStr = datum
|
||||
? this.timeFormat.format(datum)
|
||||
: this.time;
|
||||
formatTime(datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dateTimeStr = this.timeFormatter.format(datum);
|
||||
|
||||
// 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() {
|
||||
const thumbsWrapper = this.$refs.thumbsWrapper;
|
||||
@ -174,26 +284,35 @@ export default {
|
||||
|| (scrollHeight - scrollTop) > 2 * clientHeight;
|
||||
this.autoScroll = !disableScroll;
|
||||
},
|
||||
paused(state, button = false) {
|
||||
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]);
|
||||
}
|
||||
paused(state, type) {
|
||||
|
||||
if (this.nextDatum) {
|
||||
this.updateValues(this.nextDatum);
|
||||
delete this.nextDatum;
|
||||
} else {
|
||||
this.updateValues(this.imageHistory[this.imageHistory.length - 1]);
|
||||
}
|
||||
this.isPaused = state;
|
||||
|
||||
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() {
|
||||
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
|
||||
@ -207,22 +326,17 @@ export default {
|
||||
|
||||
setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0);
|
||||
},
|
||||
setSelectedImage(image) {
|
||||
// If we are paused and the current image IS selected, unpause
|
||||
// Otherwise, set current image and pause
|
||||
if (!image) {
|
||||
setFocusedImage(index, thumbnailClick = false) {
|
||||
if (this.isPaused && !thumbnailClick) {
|
||||
this.nextImageIndex = index;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isPaused && image.selected) {
|
||||
this.paused(false);
|
||||
this.unselectAllImages();
|
||||
} else {
|
||||
this.imageUrl = this.getImageUrl(image);
|
||||
this.time = this.getTime(image);
|
||||
this.focusedImageIndex = index;
|
||||
|
||||
if (thumbnailClick && !this.isPaused) {
|
||||
this.paused(true);
|
||||
this.unselectAllImages();
|
||||
image.selected = true;
|
||||
}
|
||||
},
|
||||
boundsChange(bounds, isTick) {
|
||||
@ -230,98 +344,158 @@ export default {
|
||||
this.requestHistory();
|
||||
}
|
||||
},
|
||||
requestHistory() {
|
||||
const requestId = ++this.requestCount;
|
||||
async requestHistory() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
this.requestCount++;
|
||||
const requestId = this.requestCount;
|
||||
this.imageHistory = [];
|
||||
this.openmct.telemetry
|
||||
.request(this.domainObject, this.bounds)
|
||||
.then((values = []) => {
|
||||
if (this.requestCount === requestId) {
|
||||
// add each image to the history
|
||||
// update values for the very last image (set current image time and url)
|
||||
values.forEach((datum, index) => this.updateHistory(datum, index === values.length - 1));
|
||||
}
|
||||
let data = await this.openmct.telemetry
|
||||
.request(this.domainObject, bounds) || [];
|
||||
|
||||
if (this.requestCount === requestId) {
|
||||
data.forEach((datum, index) => {
|
||||
this.updateHistory(datum, index === data.length - 1);
|
||||
});
|
||||
}
|
||||
},
|
||||
timeSystemChange(system) {
|
||||
// reset timesystem dependent variables
|
||||
this.timeKey = system.key;
|
||||
this.timeFormat = this.openmct.telemetry.getValueFormatter(this.metadata.value(this.timeKey));
|
||||
this.timeSystem = this.openmct.time.timeSystem();
|
||||
this.timeKey = this.timeSystem.key;
|
||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
this.trackDuration();
|
||||
},
|
||||
clockChange(clock) {
|
||||
this.trackDuration();
|
||||
},
|
||||
subscribe() {
|
||||
this.unsubscribe = this.openmct.telemetry
|
||||
.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);
|
||||
}
|
||||
});
|
||||
},
|
||||
unselectAllImages() {
|
||||
this.imageHistory.forEach(image => image.selected = false);
|
||||
},
|
||||
updateHistory(datum, updateValues = true) {
|
||||
updateHistory(datum, setFocused = true) {
|
||||
if (this.datumIsNotValid(datum)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.imageHistory.push(datum);
|
||||
|
||||
if (updateValues) {
|
||||
this.updateValues(datum);
|
||||
if (setFocused) {
|
||||
this.setFocusedImage(this.imageHistory.length - 1);
|
||||
}
|
||||
},
|
||||
updateValues(datum) {
|
||||
if (this.isPaused) {
|
||||
this.nextDatum = datum;
|
||||
getFormatter(key) {
|
||||
let metadataValue = this.metadata.value(key) || { format: key };
|
||||
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;
|
||||
}
|
||||
|
||||
this.time = this.timeFormat.format(datum);
|
||||
this.imageUrl = this.imageFormat.format(datum);
|
||||
},
|
||||
selectedImageIndex() {
|
||||
return this.imageHistory.findIndex(image => image.selected);
|
||||
},
|
||||
setSelectedByIndex(index) {
|
||||
this.setSelectedImage(this.imageHistory[index]);
|
||||
},
|
||||
nextImage() {
|
||||
let index = this.selectedImageIndex();
|
||||
this.setSelectedByIndex(++index);
|
||||
let index = this.focusedImageIndex;
|
||||
|
||||
this.setFocusedImage(++index, THUMBNAIL_CLICKED);
|
||||
if (index === this.imageHistory.length - 1) {
|
||||
this.paused(false);
|
||||
}
|
||||
},
|
||||
prevImage() {
|
||||
let index = this.selectedImageIndex();
|
||||
if (index === -1) {
|
||||
this.setSelectedByIndex(this.imageHistory.length - 2);
|
||||
if (this.isPrevDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let index = this.focusedImageIndex;
|
||||
|
||||
if (index === this.imageHistory.length - 1) {
|
||||
this.setFocusedImage(this.imageHistory.length - 2, THUMBNAIL_CLICKED);
|
||||
} else {
|
||||
this.setSelectedByIndex(--index);
|
||||
this.setFocusedImage(--index, THUMBNAIL_CLICKED);
|
||||
}
|
||||
},
|
||||
isNextDisabled() {
|
||||
let disabled = false;
|
||||
let index = this.selectedImageIndex();
|
||||
arrowDownHandler(event) {
|
||||
let key = event.keyCode;
|
||||
|
||||
if (index === -1 || index === this.imageHistory.length - 1) {
|
||||
disabled = true;
|
||||
if (this.isLeftOrRightArrowKey(key)) {
|
||||
this.arrowDown = true;
|
||||
window.clearTimeout(this.arrowDownDelayTimeout);
|
||||
this.arrowDownDelayTimeout = window.setTimeout(() => {
|
||||
this.arrowKeyScroll(this.directionByKey(key));
|
||||
}, ARROW_DOWN_DELAY_CHECK_MS);
|
||||
}
|
||||
|
||||
return disabled;
|
||||
},
|
||||
isPrevDisabled() {
|
||||
let disabled = false;
|
||||
let index = this.selectedImageIndex();
|
||||
arrowUpHandler(event) {
|
||||
let key = event.keyCode;
|
||||
|
||||
if (index === 0 || this.imageHistory.length < 2) {
|
||||
disabled = true;
|
||||
window.clearTimeout(this.arrowDownDelayTimeout);
|
||||
|
||||
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;
|
||||
height: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
@ -25,14 +29,57 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__control-bar {
|
||||
padding: 5px 0 0 0;
|
||||
&__control-bar,
|
||||
&__time {
|
||||
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 {
|
||||
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 {
|
||||
@ -151,6 +198,8 @@
|
||||
/*************************************** BUTTONS */
|
||||
.c-button.pause-play {
|
||||
// Pause icon set by default in markup
|
||||
justify-self: end;
|
||||
|
||||
&.is-paused {
|
||||
background: $colorPausedBg !important;
|
||||
color: $colorPausedFg;
|
||||
|
@ -50,6 +50,18 @@
|
||||
}
|
||||
|
||||
/************************** 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() {
|
||||
$c1: nth($mixedSettingBg, 1);
|
||||
$c2: nth($mixedSettingBg, 2);
|
||||
|
Loading…
Reference in New Issue
Block a user