Compare commits
29 Commits
fix-mode
...
feature/ti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6ac54383d | ||
|
|
490f25add8 | ||
|
|
694255db6b | ||
|
|
6910ae0a2b | ||
|
|
b3bf7f2db1 | ||
|
|
348ba9085b | ||
|
|
9d6a5e2e17 | ||
|
|
5cb0e2e885 | ||
|
|
ee277f3547 | ||
|
|
bf77d240c7 | ||
|
|
684a3d2807 | ||
|
|
27e01ef13f | ||
|
|
4a2b1640e9 | ||
|
|
38cb92b203 | ||
|
|
0792ae0ae4 | ||
|
|
d462f5d763 | ||
|
|
cb8e97dc1c | ||
|
|
aed3c19edd | ||
|
|
a19c5cfd52 | ||
|
|
03e5241041 | ||
|
|
29ed251685 | ||
|
|
60433a12b8 | ||
|
|
69b8dd6c37 | ||
|
|
e6c78c1826 | ||
|
|
a1657817dc | ||
|
|
29538e6e78 | ||
|
|
68e9152d6a | ||
|
|
02b2b47411 | ||
|
|
70624c2c5c |
20
index.html
20
index.html
@@ -48,6 +48,7 @@
|
||||
openmct.install(openmct.plugins.Generator());
|
||||
openmct.install(openmct.plugins.ExampleImagery());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||
openmct.install(openmct.plugins.AutoflowView({
|
||||
type: "telemetry.panel"
|
||||
}));
|
||||
@@ -72,7 +73,24 @@
|
||||
start: - THIRTY_MINUTES,
|
||||
end: FIVE_MINUTES
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Fixed",
|
||||
timeSystem: 'local',
|
||||
bounds: {
|
||||
start: Date.now() - THIRTY_MINUTES,
|
||||
end: Date.now()
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Realtime",
|
||||
timeSystem: 'local',
|
||||
clock: 'local',
|
||||
clockOffsets: {
|
||||
start: - THIRTY_MINUTES,
|
||||
end: FIVE_MINUTES
|
||||
}
|
||||
},
|
||||
]
|
||||
}));
|
||||
openmct.install(openmct.plugins.SummaryWidget());
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"css-loader": "^1.0.0",
|
||||
"d3-array": "1.2.x",
|
||||
"d3-axis": "1.0.x",
|
||||
"d3-brush": "^1.1.3",
|
||||
"d3-collection": "1.0.x",
|
||||
"d3-color": "1.0.x",
|
||||
"d3-format": "1.2.x",
|
||||
|
||||
@@ -41,7 +41,7 @@ define([], function () {
|
||||
this.timeFormat = 'local-format';
|
||||
this.durationFormat = 'duration';
|
||||
|
||||
this.isUTCBased = false;
|
||||
this.isUTCBased = true;
|
||||
}
|
||||
|
||||
return LocalTimeSystem;
|
||||
|
||||
@@ -88,15 +88,24 @@
|
||||
</div>
|
||||
|
||||
<conductor-axis
|
||||
class="c-conductor__ticks"
|
||||
:bounds="rawBounds"
|
||||
@panAxis="setViewFromBounds"></conductor-axis>
|
||||
class="c-conductor__ticks"
|
||||
:bounds="rawBounds"
|
||||
:isFixed="isFixed"
|
||||
@panAxis="setViewFromBounds"
|
||||
@zoomAxis="setViewFromBounds"
|
||||
></conductor-axis>
|
||||
|
||||
</div>
|
||||
<div class="c-conductor__controls">
|
||||
<!-- Mode, time system menu buttons and duration slider -->
|
||||
<ConductorMode class="c-conductor__mode-select"></ConductorMode>
|
||||
<ConductorTimeSystem class="c-conductor__time-system-select"></ConductorTimeSystem>
|
||||
<ConductorHistory
|
||||
v-if="isFixed"
|
||||
class="c-conductor__history-select"
|
||||
:bounds="bounds"
|
||||
:time-system="timeSystem"
|
||||
@select-timespan="setViewFromBounds"
|
||||
></ConductorHistory>
|
||||
</div>
|
||||
<input type="submit" class="invisible">
|
||||
</form>
|
||||
@@ -294,6 +303,7 @@ import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
||||
import DatePicker from './DatePicker.vue';
|
||||
import ConductorAxis from './ConductorAxis.vue';
|
||||
import ConductorModeIcon from './ConductorModeIcon.vue';
|
||||
import ConductorHistory from './ConductorHistory.vue'
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
const SECONDS = 1000;
|
||||
@@ -309,7 +319,8 @@ export default {
|
||||
ConductorTimeSystem,
|
||||
DatePicker,
|
||||
ConductorAxis,
|
||||
ConductorModeIcon
|
||||
ConductorModeIcon,
|
||||
ConductorHistory
|
||||
},
|
||||
data() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
@@ -319,6 +330,7 @@ export default {
|
||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
|
||||
return {
|
||||
timeSystem: timeSystem,
|
||||
timeFormatter: timeFormatter,
|
||||
durationFormatter: durationFormatter,
|
||||
offsets: {
|
||||
@@ -329,6 +341,10 @@ export default {
|
||||
start: timeFormatter.format(bounds.start),
|
||||
end: timeFormatter.format(bounds.end)
|
||||
},
|
||||
bounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
},
|
||||
rawBounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
@@ -340,10 +356,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
setTimeSystem(timeSystem) {
|
||||
this.timeSystem = timeSystem
|
||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
setOffsetsFromView($event) {
|
||||
@@ -492,11 +508,14 @@ export default {
|
||||
this.validateAllBounds();
|
||||
this.submitForm();
|
||||
},
|
||||
setNewBounds(bounds) {
|
||||
this.bounds.start = bounds.start;
|
||||
this.bounds.end = bounds.end;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
||||
|
||||
this.openmct.time.on('bounds', this.setViewFromBounds);
|
||||
this.openmct.time.on('bounds', this.setNewBounds);
|
||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
||||
this.openmct.time.on('clock', this.setViewFromClock);
|
||||
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
<template>
|
||||
<div class="c-conductor-axis"
|
||||
ref="axisHolder"
|
||||
@mousedown="dragStart($event)">
|
||||
@mousedown="dragStart($event)"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -44,7 +45,7 @@
|
||||
text-rendering: geometricPrecision;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
> g {
|
||||
> g.axis {
|
||||
// Overall Tick holder
|
||||
transform: translateY($tickYPos);
|
||||
path {
|
||||
@@ -97,6 +98,7 @@
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.is-realtime-mode & {
|
||||
@@ -115,6 +117,7 @@
|
||||
import * as d3Selection from 'd3-selection';
|
||||
import * as d3Axis from 'd3-axis';
|
||||
import * as d3Scale from 'd3-scale';
|
||||
import * as d3Brush from 'd3-brush';
|
||||
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
||||
|
||||
const PADDING = 1;
|
||||
@@ -126,7 +129,13 @@ const PIXELS_PER_TICK_WIDE = 200;
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
bounds: Object
|
||||
bounds: Object,
|
||||
isFixed: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
altPressed: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setScale() {
|
||||
@@ -171,9 +180,8 @@ export default {
|
||||
},
|
||||
getActiveFormatter() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let isFixed = this.openmct.time.clock() === undefined;
|
||||
|
||||
if (isFixed) {
|
||||
if (this.isFixed) {
|
||||
return this.getFormatter(timeSystem.timeFormat);
|
||||
} else {
|
||||
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
@@ -185,8 +193,7 @@ export default {
|
||||
}).formatter;
|
||||
},
|
||||
dragStart($event){
|
||||
let isFixed = this.openmct.time.clock() === undefined;
|
||||
if (isFixed){
|
||||
if (this.isFixed){
|
||||
this.dragStartX = $event.clientX;
|
||||
|
||||
document.addEventListener('mousemove', this.drag);
|
||||
@@ -198,18 +205,20 @@ export default {
|
||||
drag($event) {
|
||||
if (!this.dragging){
|
||||
this.dragging = true;
|
||||
requestAnimationFrame(()=>{
|
||||
let deltaX = $event.clientX - this.dragStartX;
|
||||
let percX = deltaX / this.width;
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let deltaTime = bounds.end - bounds.start;
|
||||
let newStart = bounds.start - percX * deltaTime;
|
||||
this.$emit('panAxis',{
|
||||
start: newStart,
|
||||
end: newStart + deltaTime
|
||||
});
|
||||
this.dragging = false;
|
||||
})
|
||||
if (this.altPressed) {
|
||||
requestAnimationFrame(()=>{
|
||||
let deltaX = $event.clientX - this.dragStartX;
|
||||
let percX = deltaX / this.width;
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let deltaTime = bounds.end - bounds.start;
|
||||
let newStart = bounds.start - percX * deltaTime;
|
||||
this.$emit('panAxis',{
|
||||
start: newStart,
|
||||
end: newStart + deltaTime
|
||||
});
|
||||
})
|
||||
}
|
||||
this.dragging = false;
|
||||
} else {
|
||||
console.log('Rejected drag due to RAF cap');
|
||||
}
|
||||
@@ -225,6 +234,55 @@ export default {
|
||||
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
||||
this.width = this.$refs.axisHolder.clientWidth;
|
||||
this.setScale();
|
||||
this.destroyBrush();
|
||||
this.initBrush();
|
||||
this.createBrush();
|
||||
}
|
||||
},
|
||||
initBrush() {
|
||||
// brush extent y coordinate starts at -1 to prevent underlying layer cursor to show
|
||||
this.brush = d3Brush.brushX()
|
||||
.extent([[0, -1], [this.width, this.height]])
|
||||
.on("end", this.brushEnd);
|
||||
},
|
||||
createBrush() {
|
||||
if (!this.isFixed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.svg = this.svg || d3Selection.select('svg');
|
||||
if (!this.svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.svg.append("g")
|
||||
.attr("class", "brush")
|
||||
.call(this.brush);
|
||||
},
|
||||
brushEnd () {
|
||||
const selection = d3Selection.event.selection;
|
||||
if (!d3Selection.event.sourceEvent || !selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// SMELL
|
||||
const [x0, x1] = selection.map(d => this.xScale.invert(d).getTime());
|
||||
this.openmct.time.bounds({
|
||||
start: x0,
|
||||
end: x1
|
||||
});
|
||||
this.$emit('zoomAxis', {
|
||||
start: x0,
|
||||
end: x1
|
||||
});
|
||||
|
||||
// clear brush
|
||||
d3Selection.select('g.brush').call(this.brush.move, null);
|
||||
},
|
||||
destroyBrush() {
|
||||
const brush = d3Selection.select('g.brush')
|
||||
if (brush){
|
||||
brush.remove();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -234,22 +292,40 @@ export default {
|
||||
this.setScale();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
isFixed: function(value) {
|
||||
value ? this.createBrush() : this.destroyBrush();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Alt') {
|
||||
this.altPressed = true;
|
||||
this.destroyBrush();
|
||||
}
|
||||
});
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Alt') {
|
||||
this.altPressed = false;
|
||||
this.createBrush();
|
||||
}
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
let axisHolder = this.$refs.axisHolder;
|
||||
let height = axisHolder.offsetHeight;
|
||||
this.height = axisHolder.offsetHeight;
|
||||
let vis = d3Selection.select(axisHolder)
|
||||
.append("svg:svg")
|
||||
.attr("width", "100%")
|
||||
.attr("height", height);
|
||||
.attr("height", this.height);
|
||||
|
||||
this.width = this.$refs.axisHolder.clientWidth;
|
||||
this.xAxis = d3Axis.axisTop();
|
||||
this.dragging = false;
|
||||
|
||||
// draw x axis with labels. CSS is used to position them.
|
||||
this.axisElement = vis.append("g");
|
||||
this.axisElement = vis.append("g")
|
||||
.attr("class", "axis");
|
||||
|
||||
this.setViewFromTimeSystem(this.openmct.time.timeSystem());
|
||||
this.setScale();
|
||||
@@ -257,6 +333,9 @@ export default {
|
||||
//Respond to changes in conductor
|
||||
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
|
||||
setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||
|
||||
this.initBrush();
|
||||
this.createBrush();
|
||||
},
|
||||
destroyed() {
|
||||
}
|
||||
|
||||
161
src/plugins/timeConductor/ConductorHistory.vue
Normal file
161
src/plugins/timeConductor/ConductorHistory.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
|
||||
<button class="c-button--menu c-history-button icon-history"
|
||||
@click.prevent="toggle">
|
||||
<span class="c-button__label">History</span>
|
||||
</button>
|
||||
<div class="c-menu" v-if="open">
|
||||
<ul
|
||||
v-if="isUTCBased"
|
||||
>
|
||||
<li @click="selectHours(24)">Last 24 hours</li>
|
||||
<li @click="selectHours(2)">Last 2 hours</li>
|
||||
<li @click="selectHours(1)">Last hour</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li
|
||||
v-for="(timespan, index) in historyForCurrentTimeSystem"
|
||||
:key="index"
|
||||
@click="selectTimespan(timespan)"
|
||||
>
|
||||
{{ formatTime(timespan.start) }} - {{ formatTime(timespan.end) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import toggleMixin from '../../ui/mixins/toggle-mixin';
|
||||
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
||||
import _ from 'lodash'
|
||||
|
||||
const LOCAL_STORAGE_HISTORY_MAX_RECORDS = 20;
|
||||
const LOCAL_STORAGE_HISTORY_KEY = 'tcHistory';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
mixins: [toggleMixin],
|
||||
props: {
|
||||
bounds: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
timeSystem: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
history: {} // contains arrays of timespans {start, end}, array key is time system key
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isUTCBased() {
|
||||
return this.timeSystem.isUTCBased;
|
||||
},
|
||||
historyForCurrentTimeSystem() {
|
||||
return this.history[this.timeSystem.key]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getHistoryFromLocalStorage() {
|
||||
if (localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY)) {
|
||||
this.history = JSON.parse(localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY))
|
||||
} else {
|
||||
this.history = {};
|
||||
this.persistHistoryToLocalStorage();
|
||||
}
|
||||
},
|
||||
persistHistoryToLocalStorage() {
|
||||
localStorage.setItem(LOCAL_STORAGE_HISTORY_KEY, JSON.stringify(this.history));
|
||||
},
|
||||
addTimespan() {
|
||||
const key = this.timeSystem.key;
|
||||
let [...currentHistory] = this.history[key] || [];
|
||||
const timespan = {
|
||||
start: this.bounds.start,
|
||||
end: this.bounds.end,
|
||||
};
|
||||
|
||||
// when choosing an existing entry, remove it and add it back as latest entry
|
||||
currentHistory = currentHistory.filter((entry) => {
|
||||
return !_.isEqual(timespan, entry);
|
||||
});
|
||||
|
||||
if (currentHistory.length >= LOCAL_STORAGE_HISTORY_MAX_RECORDS) {
|
||||
currentHistory.shift();
|
||||
}
|
||||
|
||||
currentHistory.push(timespan);
|
||||
this.history[key] = currentHistory;
|
||||
},
|
||||
selectTimespan(timespan) {
|
||||
this.$emit('select-timespan', timespan);
|
||||
},
|
||||
selectHours(hours) {
|
||||
const now = Date.now();
|
||||
this.selectTimespan({
|
||||
start: now - hours * 60 * 60 * 1000,
|
||||
end: now
|
||||
});
|
||||
},
|
||||
formatTime(time) {
|
||||
const formatter = this.openmct.telemetry.getValueFormatter({
|
||||
format: this.timeSystem.timeFormat
|
||||
}).formatter;
|
||||
|
||||
return formatter.format(time);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
bounds: {
|
||||
handler() {
|
||||
this.addTimespan();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
timeSystem: {
|
||||
handler() {
|
||||
this.addTimespan();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
history: {
|
||||
handler() {
|
||||
this.persistHistoryToLocalStorage();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getHistoryFromLocalStorage();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -432,6 +432,11 @@ select {
|
||||
content: ''; // Add this element so that menu items without an icon still indent properly
|
||||
}
|
||||
}
|
||||
ul {
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid $colorInteriorBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-menu {
|
||||
|
||||
Reference in New Issue
Block a user