Compare commits

...

65 Commits

Author SHA1 Message Date
David Tsay
a7fb75aec6 display validation errors for specific input modified 2020-05-11 19:51:30 -07:00
David Tsay
bbac4df4b8 provide reasonable defaults for conductor configuration 2020-05-11 16:49:13 -07:00
David Tsay
ed2d728f1d section-hint without section-separator styling 2020-05-11 16:45:01 -07:00
David Tsay
a4ae654de9 remove comments 2020-05-08 11:58:20 -07:00
David Tsay
378769fca7 fix indentation
remove logging
2020-05-08 11:52:25 -07:00
David Tsay
2aac221e51 Merge branch 'master' into time-conductor-merge 2020-05-07 10:20:50 -07:00
David Tsay
1055733ff5 cleanup 2020-04-24 22:34:22 -07:00
David Tsay
dc642f826e do not install local time system 2020-04-24 16:07:45 -07:00
David Tsay
920b7a0c72 Merge branch 'time-conductor-merge' into dave/time-conductor 2020-04-24 16:06:39 -07:00
David Tsay
ba7e7321df add default configurables for examples 2020-04-24 15:54:11 -07:00
David Tsay
30ed5d8fdb remove lodash 2020-04-24 15:07:01 -07:00
David Tsay
5dc8f2b0d2 fixes for history 2020-04-24 15:02:07 -07:00
David Tsay
8161ed7ea6 add presets and records 2020-04-23 22:12:30 -07:00
David Tsay
9644208e6d add configurable bounds limit to time conductor 2020-04-22 16:51:01 -07:00
charlesh88
42b6eb158a Styling for Time Conductor zoom and pan
- Minor fix to hover cursor for alt-pressed panning;
2020-04-22 14:10:41 -07:00
David Tsay
bac9991855 rename inMode vars for clarity 2020-04-22 11:25:05 -07:00
David Tsay
1f545cb969 handle no drag on pan 2020-04-22 11:04:18 -07:00
David Tsay
9271259a4c move altPressed up to parent 2020-04-22 10:43:50 -07:00
David Tsay
5f51a7cc90 fix zoom 2020-04-21 18:27:19 -07:00
David Tsay
445b9f3788 WIP almost there 2020-04-21 16:47:40 -07:00
David Tsay
acc0abc903 move zoom/pan styling up to conductor 2020-04-20 10:16:37 -07:00
David Tsay
27dfda904e fix merge conflict 2020-04-17 17:45:25 -07:00
David Tsay
7aad1101b4 Merge branch 'time-conductor-merge' into dave/time-conductor 2020-04-17 17:44:14 -07:00
charlesh88
46d8d95583 Styling Time Conductor axis and inputs
- Moved panning and zooming styles up into `conductor.scss`;
- Stubbed in :class names in Conductor.vue;
- New theme constants;
2020-04-17 17:24:18 -07:00
David Tsay
f6bd76be0e resolve merge conflicts 2020-04-17 17:06:12 -07:00
charlesh88
b527bf3810 Styling Time Conductor axis area
- Styles for `is-zooming` state and brush;
- Styles for `is-alt-key-down` for panning;
- Styles for hover modified;
2020-04-17 16:03:25 -07:00
David Tsay
b729c5132b Merge branch 'time-conductor-merge' into dave/time-conductor 2020-04-17 12:41:00 -07:00
David Tsay
4793fae5d1 refactor to use browser mouse events instead of d3brush 2020-04-17 12:33:45 -07:00
charlesh88
26ba2f889e Tweaks to Time Conductor History menu
- Enhanced styles for `.c-menu`;
- Added hint messaging and separator;
- Reversed displayed history array so that latest entry is always first;
2020-04-16 16:50:34 -07:00
David Tsay
a4956edf7b add max duration validation 2020-04-16 10:54:03 -07:00
David Tsay
987e0c698c change realtime end bound to 30 secondes 2020-04-15 12:36:47 -07:00
David Tsay
a4200e81d9 linting 2020-04-14 11:39:17 -07:00
David Tsay
2b5706f757 more linting 2020-04-14 11:36:48 -07:00
David Tsay
2c2d674b99 linting 2020-04-14 11:35:15 -07:00
David Tsay
aecbbf24b0 linting 2020-04-14 11:20:23 -07:00
David Tsay
0ad6a595b0 Merge branch 'master' into time-conductor-merge 2020-04-14 10:45:52 -07:00
David Tsay
a6ac54383d fix bounds to use for timespans on pan axis 2019-12-12 12:02:31 -08:00
David Tsay
490f25add8 change tick to timespan to avoid confusion 2019-12-12 11:58:12 -08:00
David Tsay
694255db6b remove unused function calls 2019-12-04 11:29:10 -08:00
David Tsay
6910ae0a2b axis visual tuning 2019-11-26 14:59:29 -08:00
David Tsay
b3bf7f2db1 add presets
code cleanup
2019-11-26 14:49:11 -08:00
David Tsay
348ba9085b Merge branch 'topic-core-refactor' into feature/time-conductor-axis/zoom-slider 2019-11-26 11:46:34 -08:00
David Tsay
9d6a5e2e17 clean up refactoring 2019-11-26 11:46:02 -08:00
David Tsay
5cb0e2e885 conductor history functionality complete 2019-11-26 11:30:39 -08:00
David Tsay
ee277f3547 reset axis height after prototyping 2019-11-26 11:30:24 -08:00
David Tsay
bf77d240c7 WIP persistence works 2019-11-25 16:37:47 -08:00
David Tsay
684a3d2807 WIP save history to and pull history from local storage 2019-11-25 10:28:50 -08:00
David Tsay
27e01ef13f WIP conductor history 2019-11-21 16:35:15 -08:00
David Tsay
4a2b1640e9 set global bounds before emitting change event 2019-11-21 10:31:56 -08:00
David Tsay
38cb92b203 WIP prototyping conductor history 2019-11-18 15:32:16 -08:00
David Tsay
0792ae0ae4 just use d3-brush instead of entire d3 package 2019-11-15 15:47:06 -08:00
David Tsay
d462f5d763 Merge branch 'topic-core-refactor' into feature/time-conductor-axis/zoom-slider 2019-11-15 15:09:48 -08:00
David Tsay
cb8e97dc1c resize brush on window resize 2019-11-13 15:07:07 -08:00
David Tsay
aed3c19edd linting 2019-11-13 14:00:17 -08:00
David Tsay
a19c5cfd52 disable cursor for local time and enable for fixed time 2019-11-13 13:59:13 -08:00
David Tsay
03e5241041 pass isFixed as props so we can watch for change from parent 2019-11-13 13:40:37 -08:00
David Tsay
29ed251685 zoom axis sets start and end times 2019-11-13 11:59:50 -08:00
David Tsay
60433a12b8 linting 2019-11-12 17:03:30 -08:00
David Tsay
69b8dd6c37 make isTimeFixed check reusable 2019-11-12 15:48:09 -08:00
David Tsay
e6c78c1826 add LocalTimeSystem 2019-11-12 13:15:10 -08:00
David Tsay
a1657817dc make LocalTimeSystem UTCBased (Earth based) 2019-11-12 12:05:45 -08:00
David Tsay
29538e6e78 revert selection to times 2019-11-08 16:15:11 -08:00
David Tsay
68e9152d6a pan and zoom now co-exist 2019-11-08 15:58:16 -08:00
David Tsay
02b2b47411 require alt pressed for grab handle. display only 2019-11-07 16:46:56 -08:00
David Tsay
70624c2c5c basic brush prototype visible 2019-11-07 13:22:52 -08:00
12 changed files with 593 additions and 102 deletions

View File

@@ -34,8 +34,8 @@
<body>
</body>
<script>
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_MINUTES = 30 * 60 * 1000;
const THIRTY_SECONDS = 30 * 1000;
const THIRTY_MINUTES = THIRTY_SECONDS * 60;
[
'example/eventGenerator'
@@ -63,7 +63,34 @@
bounds: {
start: Date.now() - THIRTY_MINUTES,
end: Date.now()
}
},
presets: [
{
label: 'Last Day',
bounds: {
start: Date.now() - 1000 * 60 * 60 * 24,
end: Date.now()
}
},
{
label: 'Last 2 hours',
bounds: {
start: Date.now() - 1000 * 60 * 60 * 2,
end: Date.now()
}
},
{
label: 'Last hour',
bounds: {
start: Date.now() - 1000 * 60 * 60,
end: Date.now()
}
}
],
// maximum entries to retain in conductor history
records: 10,
// maximum duration between start and end bounds
limit: 1000 * 60 * 60 * 24
},
{
name: "Realtime",
@@ -71,7 +98,7 @@
clock: 'local',
clockOffsets: {
start: - THIRTY_MINUTES,
end: FIVE_MINUTES
end: THIRTY_SECONDS
}
}
]

View File

@@ -41,7 +41,7 @@ define([], function () {
this.timeFormat = 'local-format';
this.durationFormat = 'duration';
this.isUTCBased = false;
this.isUTCBased = true;
}
return LocalTimeSystem;

View File

@@ -22,7 +22,12 @@
<template>
<div
class="c-conductor"
:class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode']"
:class="[
{ 'is-zooming': isZooming },
{ 'is-panning': isPanning },
{ 'alt-pressed': altPressed },
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
]"
>
<form
ref="conductorForm"
@@ -52,7 +57,7 @@
type="text"
autocorrect="off"
spellcheck="false"
@change="validateAllBounds(); submitForm()"
@change="validateAllBounds('startDate'); submitForm()"
>
<date-picker
v-if="isFixed && isUTCBased"
@@ -92,7 +97,7 @@
autocorrect="off"
spellcheck="false"
:disabled="!isFixed"
@change="validateAllBounds(); submitForm()"
@change="validateAllBounds('endDate'); submitForm()"
>
<date-picker
v-if="isFixed && isUTCBased"
@@ -122,14 +127,25 @@
<conductor-axis
class="c-conductor__ticks"
:bounds="rawBounds"
@panAxis="setViewFromBounds"
:view-bounds="viewBounds"
:is-fixed="isFixed"
:alt-pressed="altPressed"
@endPan="endPan"
@endZoom="endZoom"
@panAxis="pan"
@zoomAxis="zoom"
/>
</div>
<div class="c-conductor__controls">
<!-- Mode, time system menu buttons and duration slider -->
<ConductorMode class="c-conductor__mode-select" />
<ConductorTimeSystem class="c-conductor__time-system-select" />
<ConductorHistory
v-if="isFixed"
class="c-conductor__history-select"
:bounds="openmct.time.bounds()"
:time-system="timeSystem"
/>
</div>
<input
type="submit"
@@ -145,6 +161,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';
@@ -155,7 +172,8 @@ export default {
ConductorTimeSystem,
DatePicker,
ConductorAxis,
ConductorModeIcon
ConductorModeIcon,
ConductorHistory
},
data() {
let bounds = this.openmct.time.bounds();
@@ -165,6 +183,7 @@ export default {
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
return {
timeSystem: timeSystem,
timeFormatter: timeFormatter,
durationFormatter: durationFormatter,
offsets: {
@@ -175,29 +194,64 @@ export default {
start: timeFormatter.format(bounds.start),
end: timeFormatter.format(bounds.end)
},
rawBounds: {
viewBounds: {
start: bounds.start,
end: bounds.end
},
isFixed: this.openmct.time.clock() === undefined,
isUTCBased: timeSystem.isUTCBased,
showDatePicker: false
showDatePicker: false,
altPressed: false,
isPanning: false,
isZooming: false
}
},
created() {
document.addEventListener('keydown', (e) => {
if (e.key === 'Alt') {
this.altPressed = true;
}
});
document.addEventListener('keyup', (e) => {
if (e.key === 'Alt') {
this.altPressed = false;
}
});
},
mounted() {
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
this.openmct.time.on('bounds', this.setViewFromBounds);
this.openmct.time.on('timeSystem', this.setTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
},
methods: {
pan(bounds) {
this.isPanning = true;
this.setViewFromBounds(bounds);
},
endPan(bounds) {
this.isPanning = false;
if (bounds) {
this.openmct.time.bounds(bounds);
}
},
zoom(bounds) {
this.isZooming = true;
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
},
endZoom(bounds) {
const _bounds = bounds ? bounds : this.openmct.time.bounds();
this.isZooming = false;
this.openmct.time.bounds(_bounds);
},
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) {
@@ -237,8 +291,8 @@ export default {
setViewFromBounds(bounds) {
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
this.rawBounds.start = bounds.start;
this.rawBounds.end = bounds.end;
this.viewBounds.start = bounds.start;
this.viewBounds.end = bounds.end;
},
setViewFromOffsets(offsets) {
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
@@ -251,6 +305,15 @@ export default {
this.setOffsetsFromView();
}
},
getBoundsLimit() {
const configuration = this.configuration.menuOptions
.filter(option => option.timeSystem === this.timeSystem.key)
.find(option => option.limit);
const limit = configuration ? configuration.limit : undefined;
return limit;
},
clearAllValidation() {
if (this.isFixed) {
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
@@ -262,37 +325,37 @@ export default {
input.setCustomValidity('');
input.title = '';
},
validateAllBounds() {
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
let validationResult = true;
let formattedDate;
validateAllBounds(ref) {
const input = this.$refs[ref];
let validationResult = true;
if (input === this.$refs.startDate) {
formattedDate = this.formattedBounds.start;
} else {
formattedDate = this.formattedBounds.end;
}
const formattedDate = input === this.$refs.startDate
? this.formattedBounds.start
: this.formattedBounds.end
;
if (!this.timeFormatter.validate(formattedDate)) {
validationResult = 'Invalid date';
if (!this.timeFormatter.validate(formattedDate)) {
validationResult = 'Invalid date';
} else {
let boundsValues = {
start: this.timeFormatter.parse(this.formattedBounds.start),
end: this.timeFormatter.parse(this.formattedBounds.end)
};
const limit = this.getBoundsLimit();
if (
this.timeSystem.isUTCBased
&& limit
&& boundsValues.end - boundsValues.start > limit
) {
validationResult = "Start and end difference exceeds allowable limit";
} else {
let boundsValues = {
start: this.timeFormatter.parse(this.formattedBounds.start),
end: this.timeFormatter.parse(this.formattedBounds.end)
};
validationResult = this.openmct.time.validateBounds(boundsValues);
}
if (validationResult !== true) {
input.setCustomValidity(validationResult);
input.title = validationResult;
return false;
} else {
input.setCustomValidity('');
input.title = '';
return true;
}
});
}
this.handleValidationResult(input, validationResult);
},
validateAllOffsets(event) {
return [this.$refs.startOffset, this.$refs.endOffset].every((input) => {
@@ -315,17 +378,20 @@ export default {
validationResult = this.openmct.time.validateOffsets(offsetValues);
}
if (validationResult !== true) {
input.setCustomValidity(validationResult);
input.title = validationResult;
return false;
} else {
input.setCustomValidity('');
input.title = '';
return true;
}
this.handleValidationResult(input, validationResult);
});
},
handleValidationResult(input, validationResult) {
if (validationResult !== true) {
input.setCustomValidity(validationResult);
input.title = validationResult;
return false;
} else {
input.setCustomValidity('');
input.title = '';
return true;
}
},
submitForm() {
// Allow Vue model to catch up to user input.
// Submitting form will cause validation messages to display (but only if triggered by button click)
@@ -338,12 +404,12 @@ export default {
},
startDateSelected(date) {
this.formattedBounds.start = this.timeFormatter.format(date);
this.validateAllBounds();
this.validateAllBounds('startDate');
this.submitForm();
},
endDateSelected(date) {
this.formattedBounds.end = this.timeFormatter.format(date);
this.validateAllBounds();
this.validateAllBounds('endDate');
this.submitForm();
}
}

View File

@@ -24,7 +24,12 @@
ref="axisHolder"
class="c-conductor-axis"
@mousedown="dragStart($event)"
></div>
>
<div
class="c-conductor-axis__zoom-indicator"
:style="zoomStyle"
></div>
</div>
</template>
<script>
@@ -43,14 +48,35 @@ const PIXELS_PER_TICK_WIDE = 200;
export default {
inject: ['openmct'],
props: {
bounds: {
viewBounds: {
type: Object,
required: true
},
isFixed: {
type: Boolean,
required: true
},
altPressed: {
type: Boolean,
required: true
}
},
data() {
return {
inPanMode: false,
dragStartX: undefined,
dragX: undefined,
zoomStyle: {}
}
},
computed: {
inZoomMode() {
return !this.inPanMode;
}
},
watch: {
bounds: {
handler(bounds) {
viewBounds: {
handler() {
this.setScale();
},
deep: true
@@ -58,18 +84,23 @@ export default {
},
mounted() {
let axisHolder = this.$refs.axisHolder;
let height = axisHolder.offsetHeight;
this.height = axisHolder.offsetHeight;
this.width = axisHolder.clientWidth;
const rect = axisHolder.getBoundingClientRect();
this.left = Math.round(rect.left);
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();
@@ -83,12 +114,15 @@ export default {
methods: {
setScale() {
let timeSystem = this.openmct.time.timeSystem();
let bounds = this.bounds;
if (timeSystem.isUTCBased) {
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
this.xScale.domain(
[new Date(this.viewBounds.start), new Date(this.viewBounds.end)]
);
} else {
this.xScale.domain([bounds.start, bounds.end]);
this.xScale.domain(
[this.viewBounds.start, this.viewBounds.end]
);
}
this.xAxis.scale(this.xScale);
@@ -102,7 +136,7 @@ export default {
this.xAxis.ticks(this.width / PIXELS_PER_TICK);
}
this.msPerPixel = (bounds.end - bounds.start) / this.width;
this.msPerPixel = (this.viewBounds.end - this.viewBounds.start) / this.width;
},
setViewFromTimeSystem(timeSystem) {
//The D3 scale used depends on the type of time system as d3
@@ -120,9 +154,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);
@@ -134,42 +167,128 @@ export default {
}).formatter;
},
dragStart($event) {
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed) {
if (this.isFixed) {
this.dragStartX = $event.clientX;
if (this.altPressed) {
this.inPanMode = true;
}
document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.dragEnd, {
once: true
});
if (this.inZoomMode) {
this.startZoom();
}
}
},
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
});
requestAnimationFrame(() => {
this.dragX = $event.clientX;
this.inPanMode ? this.pan() : this.zoom();
this.dragging = false;
})
} else {
console.log('Rejected drag due to RAF cap');
});
}
},
dragEnd() {
this.inPanMode ? this.endPan() : this.endZoom();
document.removeEventListener('mousemove', this.drag);
this.openmct.time.bounds({
start: this.bounds.start,
end: this.bounds.end
this.dragStartX = undefined;
this.dragX = undefined;
},
pan() {
const panBounds = this.getPanBounds();
this.$emit('panAxis', panBounds);
},
endPan() {
const panBounds = this.dragStartX && this.dragX && this.dragStartX !== this.dragX
? this.getPanBounds()
: undefined;
this.$emit('endPan', panBounds);
this.inPanMode = false;
},
getPanBounds() {
const bounds = this.openmct.time.bounds();
const deltaTime = bounds.end - bounds.start;
const deltaX = this.dragX - this.dragStartX;
const percX = deltaX / this.width;
const panStart = bounds.start - percX * deltaTime;
return {
start: panStart,
end: panStart + deltaTime
};
},
startZoom() {
const x = this.scaleToBounds(this.dragStartX);
this.zoomStyle = {
left: `${this.dragStartX - this.left}px`
};
this.$emit('zoomAxis', {
start: x,
end: x
});
},
zoom() {
const zoomRange = this.getZoomRange();
this.zoomStyle = {
left: `${zoomRange.start - this.left}px`,
width: `${zoomRange.end - zoomRange.start}px`
};
this.$emit('zoomAxis', {
start: this.scaleToBounds(zoomRange.start),
end: this.scaleToBounds(zoomRange.end)
});
},
endZoom() {
const zoomRange = this.dragStartX && this.dragX && this.dragStartX !== this.dragX
? this.getZoomRange()
: undefined;
const zoomBounds = zoomRange
? {
start: this.scaleToBounds(zoomRange.start),
end: this.scaleToBounds(zoomRange.end)
}
: this.openmct.time.bounds();
this.zoomStyle = {};
this.$emit('endZoom', zoomBounds);
},
getZoomRange() {
const leftBound = this.left;
const rightBound = this.left + this.width;
const zoomStart = this.dragX < leftBound
? leftBound
: Math.min(this.dragX, this.dragStartX);
const zoomEnd = this.dragX > rightBound
? rightBound
: Math.max(this.dragX, this.dragStartX);
return {
start: zoomStart,
end: zoomEnd
};
},
scaleToBounds(value) {
const bounds = this.openmct.time.bounds();
const timeDelta = bounds.end - bounds.start;
const valueDelta = value - this.left;
const offset = valueDelta / this.width * timeDelta;
return bounds.start + offset;
},
resize() {
if (this.$refs.axisHolder.clientWidth !== this.width) {
this.width = this.$refs.axisHolder.clientWidth;

View File

@@ -0,0 +1,198 @@
/*****************************************************************************
* 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 v-if="open"
class="c-menu c-conductor__history-menu"
>
<ul v-if="hasHistoryPresets">
<li
v-for="preset in presets"
:key="preset.label"
class="icon-clock"
@click="selectTimespan(preset.bounds)"
>
{{ preset.label }}
</li>
</ul>
<div
v-if="hasHistoryPresets"
class="c-menu__section-separator"
></div>
<div class="c-menu__section-hint">
Past timeframes, ordered by latest first
</div>
<ul>
<li
v-for="(timespan, index) in historyForCurrentTimeSystem"
:key="index"
class="icon-history"
@click="selectTimespan(timespan)"
>
{{ formatTime(timespan.start) }} - {{ formatTime(timespan.end) }}
</li>
</ul>
</div>
</div>
</template>
<script>
import toggleMixin from '../../ui/mixins/toggle-mixin';
const LOCAL_STORAGE_HISTORY_KEY = 'tcHistory';
const DEFAULT_RECORDS = 10;
export default {
inject: ['openmct', 'configuration'],
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
presets: []
}
},
computed: {
hasHistoryPresets() {
return this.timeSystem.isUTCBased && this.presets.length;
},
historyForCurrentTimeSystem() {
const history = this.history[this.timeSystem.key];
return history;
}
},
watch: {
bounds: {
handler() {
this.addTimespan();
},
deep: true
},
timeSystem: {
handler() {
this.loadConfiguration();
this.addTimespan();
},
deep: true
},
history: {
handler() {
this.persistHistoryToLocalStorage();
},
deep: true
}
},
mounted() {
this.getHistoryFromLocalStorage();
},
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
};
const isNotEqual = function (entry) {
const start = entry.start !== this.start;
const end = entry.end !== this.end;
return start || end;
};
currentHistory = currentHistory.filter(isNotEqual, timespan);
while (currentHistory.length >= this.records) {
currentHistory.pop();
}
currentHistory.unshift(timespan);
this.history[key] = currentHistory;
},
selectTimespan(timespan) {
this.openmct.time.bounds(timespan);
},
selectHours(hours) {
const now = Date.now();
this.selectTimespan({
start: now - hours * 60 * 60 * 1000,
end: now
});
},
loadConfiguration() {
const configurations = this.configuration.menuOptions
.filter(option => option.timeSystem === this.timeSystem.key);
this.presets = this.loadPresets(configurations);
this.records = this.loadRecords(configurations);
},
loadPresets(configurations) {
const configuration = configurations.find(option => option.presets);
const presets = configuration ? configuration.presets : [];
return presets;
},
loadRecords(configurations) {
const configuration = configurations.find(option => option.records);
const records = configuration ? configuration.records : DEFAULT_RECORDS;
return records;
},
formatTime(time) {
const formatter = this.openmct.telemetry.getValueFormatter({
format: this.timeSystem.timeFormat
}).formatter;
return formatter.format(time);
}
}
}
</script>

View File

@@ -110,7 +110,7 @@ export default {
if (clock === undefined) {
return {
key: 'fixed',
name: 'Fixed Timespan Mode',
name: 'Fixed Timespan',
description: 'Query and explore data that falls between two fixed datetimes.',
cssClass: 'icon-tabular'
}

View File

@@ -13,7 +13,7 @@
text-rendering: geometricPrecision;
width: 100%;
height: 100%;
> g {
> g.axis {
// Overall Tick holder
transform: translateY($tickYPos);
path {
@@ -44,7 +44,6 @@
}
body.desktop .is-fixed-mode & {
@include cursorGrab();
background-size: 3px 30%;
background-color: $colorBodyBgSubtle;
box-shadow: inset rgba(black, 0.4) 0 1px 1px;
@@ -55,17 +54,6 @@
stroke: $colorBodyBgSubtle;
transition: $transOut;
}
&:hover,
&:active {
$c: $colorKeySubtle;
background-color: $c;
transition: $transIn;
svg text {
stroke: $c;
transition: $transIn;
}
}
}
.is-realtime-mode & {

View File

@@ -57,6 +57,65 @@
}
}
&.is-fixed-mode {
.c-conductor-axis {
&__zoom-indicator {
border: 1px solid transparent;
display: none; // Hidden by default
}
}
&:not(.is-panning),
&:not(.is-zooming) {
.c-conductor-axis {
&:hover,
&:active {
cursor: col-resize;
filter: $timeConductorAxisHoverFilter;
}
}
}
&.is-panning,
&.is-zooming {
.c-conductor-input input {
// Styles for inputs while zooming or panning
background: rgba($timeConductorActiveBg, 0.4);
}
}
&.alt-pressed {
.c-conductor-axis:hover {
// When alt is being pressed and user is hovering over the axis, set the cursor
@include cursorGrab();
}
}
&.is-panning {
.c-conductor-axis {
@include cursorGrab();
background-color: $timeConductorActivePanBg;
transition: $transIn;
svg text {
stroke: $timeConductorActivePanBg;
transition: $transIn;
}
}
}
&.is-zooming {
.c-conductor-axis__zoom-indicator {
display: block;
position: absolute;
background: rgba($timeConductorActiveBg, 0.4);
border-left-color: $timeConductorActiveBg;
border-right-color: $timeConductorActiveBg;
top: 0; bottom: 0;
}
}
}
&.is-realtime-mode {
.c-conductor__time-bounds {
grid-template-columns: 20px auto 1fr auto auto;

View File

@@ -142,6 +142,9 @@ $colorTimeHov: pullForward($colorTime, 10%);
$colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(1.2);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #226074;
/************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%);

View File

@@ -146,6 +146,9 @@ $colorTimeHov: pullForward($colorTime, 10%);
$colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(1.2);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #226074;
/************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%);

View File

@@ -132,7 +132,7 @@ $colorPausedFg: #fff;
// Base variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
$colorKeySubtle: pushBack($colorKey, 10%);
$colorKeySubtle: pushBack($colorKey, 20%);
// Time Colors
$colorTime: #618cff;
@@ -142,6 +142,9 @@ $colorTimeHov: pushBack($colorTime, 5%);
$colorTimeSubtle: pushBack($colorTime, 20%);
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
$timeConductorAxisHoverFilter: brightness(0.8);
$timeConductorActiveBg: $colorKey;
$timeConductorActivePanBg: #A0CDE1;
/************************************************** BROWSING */
$browseFrameColor: pullForward($colorBodyBg, 10%);

View File

@@ -462,9 +462,17 @@ select {
text-shadow: $shdwMenuText;
padding: $interiorMarginSm;
box-shadow: $shdwMenu;
display: block;
display: flex;
flex-direction: column;
position: absolute;
z-index: 100;
> * {
flex: 0 0 auto;
//+ * {
// margin-top: $interiorMarginSm;
//}
}
}
@mixin menuInner() {
@@ -502,6 +510,23 @@ select {
.c-menu {
@include menuOuter();
@include menuInner();
&__section-hint {
$m: $interiorMargin;
margin: $m 0;
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
opacity: 0.6;
font-size: 0.9em;
font-style: italic;
}
&__section-separator {
$m: $interiorMargin;
border-top: 1px solid $colorInteriorBorder;
margin: $m 0;
padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2);
}
}
.c-super-menu {