mirror of
https://github.com/nasa/openmct.git
synced 2024-12-23 23:12:23 +00:00
Sync time conductor with plots time range (#3843)
* Adds play and pause functionality for plots (not for legacy plots) * Add time conductor sync gesture to actions. Also fix status css. Co-authored-by: charlesh88 <charles.f.hacskaylo@nasa.gov> Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
parent
dacec48aec
commit
633bac2ed5
@ -85,8 +85,8 @@
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div class="c-button-set c-button-set--strip-h"
|
||||
:disabled="!plotHistory.length"
|
||||
<div v-if="plotHistory.length"
|
||||
class="c-button-set c-button-set--strip-h"
|
||||
>
|
||||
<button class="c-button icon-arrow-left"
|
||||
title="Restore previous pan/zoom"
|
||||
@ -99,6 +99,31 @@
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isRealTime"
|
||||
class="c-button-set c-button-set--strip-h"
|
||||
>
|
||||
<button v-if="!isFrozen"
|
||||
class="c-button icon-pause"
|
||||
title="Pause incoming real-time data"
|
||||
@click="pause()"
|
||||
>
|
||||
</button>
|
||||
<button v-if="isFrozen"
|
||||
class="c-button icon-arrow-right pause-play is-paused"
|
||||
title="Resume displaying real-time data"
|
||||
@click="play()"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isTimeOutOfSync || isFrozen"
|
||||
class="c-button-set c-button-set--strip-h"
|
||||
>
|
||||
<button class="c-button icon-clock"
|
||||
title="Synchronize Time Conductor"
|
||||
@click="showSynchronizeDialog()"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Cursor guides-->
|
||||
@ -186,10 +211,15 @@ export default {
|
||||
xKeyOptions: [],
|
||||
config: {},
|
||||
pending: 0,
|
||||
loaded: false
|
||||
isRealTime: this.openmct.time.clock() !== undefined,
|
||||
loaded: false,
|
||||
isTimeOutOfSync: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isFrozen() {
|
||||
return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
|
||||
},
|
||||
plotLegendPositionClass() {
|
||||
return `plot-legend-${this.config.legend.get('position')}`;
|
||||
},
|
||||
@ -227,6 +257,7 @@ export default {
|
||||
'configuration.filters',
|
||||
this.updateFiltersAndResubscribe
|
||||
);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.updateStatus);
|
||||
|
||||
this.openmct.objectViews.on('clearData', this.clearData);
|
||||
this.followTimeConductor();
|
||||
@ -243,6 +274,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
followTimeConductor() {
|
||||
this.openmct.time.on('clock', this.updateRealTime);
|
||||
this.openmct.time.on('bounds', this.updateDisplayBounds);
|
||||
this.synchronized(true);
|
||||
},
|
||||
@ -371,6 +403,9 @@ export default {
|
||||
const displayRange = series.getDisplayRange(xKey);
|
||||
this.config.xAxis.set('range', displayRange);
|
||||
},
|
||||
updateRealTime(clock) {
|
||||
this.isRealTime = clock !== undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Track latest display bounds. Forces update when not receiving ticks.
|
||||
@ -424,19 +459,30 @@ export default {
|
||||
* displays can update accordingly.
|
||||
*/
|
||||
synchronized(value) {
|
||||
const isLocalClock = this.openmct.time.clock();
|
||||
|
||||
if (typeof value !== 'undefined') {
|
||||
this._synchronized = value;
|
||||
const isUnsynced = !value && this.openmct.time.clock();
|
||||
const domainObject = this.openmct.legacyObject(this.domainObject);
|
||||
if (domainObject.getCapability('status')) {
|
||||
domainObject.getCapability('status')
|
||||
.set('timeconductor-unsynced', isUnsynced);
|
||||
}
|
||||
this.isTimeOutOfSync = value !== true;
|
||||
|
||||
const isUnsynced = isLocalClock && !value;
|
||||
this.setStatus(isUnsynced);
|
||||
}
|
||||
|
||||
return this._synchronized;
|
||||
},
|
||||
|
||||
setStatus(isNotInSync) {
|
||||
const outOfSync = isNotInSync === true
|
||||
|| this.isTimeOutOfSync === true
|
||||
|| this.isFrozen === true;
|
||||
if (outOfSync === true) {
|
||||
this.openmct.status.set(this.domainObject.identifier, 'timeconductor-unsynced');
|
||||
} else {
|
||||
this.openmct.status.set(this.domainObject.identifier, '');
|
||||
}
|
||||
},
|
||||
|
||||
initCanvas() {
|
||||
if (this.canvas) {
|
||||
this.stopListening(this.canvas);
|
||||
@ -729,7 +775,8 @@ export default {
|
||||
const ZOOM_AMT = 0.1;
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.positionOverPlot) {
|
||||
if (event.wheelDelta === undefined
|
||||
|| !this.positionOverPlot) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -847,11 +894,13 @@ export default {
|
||||
freeze() {
|
||||
this.config.yAxis.set('frozen', true);
|
||||
this.config.xAxis.set('frozen', true);
|
||||
this.setStatus();
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.config.yAxis.set('frozen', false);
|
||||
this.config.xAxis.set('frozen', false);
|
||||
this.setStatus();
|
||||
this.plotHistory = [];
|
||||
this.userViewportChangeEnd();
|
||||
},
|
||||
@ -881,6 +930,59 @@ export default {
|
||||
this.config.series.models[0].emit('change:yKey', yKey);
|
||||
},
|
||||
|
||||
pause() {
|
||||
this.freeze();
|
||||
},
|
||||
|
||||
play() {
|
||||
this.clear();
|
||||
},
|
||||
|
||||
showSynchronizeDialog() {
|
||||
const isLocalClock = this.openmct.time.clock();
|
||||
if (isLocalClock !== undefined) {
|
||||
const message = `
|
||||
This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds.
|
||||
Do you want to continue?
|
||||
`;
|
||||
|
||||
let dialog = this.openmct.overlays.dialog({
|
||||
title: 'Synchronize Time Conductor',
|
||||
iconClass: 'alert',
|
||||
size: 'fit',
|
||||
message: message,
|
||||
buttons: [
|
||||
{
|
||||
label: 'OK',
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
this.synchronizeTimeConductor();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
this.openmct.notifications.alert('Time conductor bounds have changed.');
|
||||
this.synchronizeTimeConductor();
|
||||
}
|
||||
},
|
||||
|
||||
synchronizeTimeConductor() {
|
||||
this.openmct.time.stopClock();
|
||||
const range = this.config.xAxis.get('displayRange');
|
||||
this.openmct.time.bounds({
|
||||
start: range.min,
|
||||
end: range.max
|
||||
});
|
||||
this.isTimeOutOfSync = false;
|
||||
},
|
||||
|
||||
destroy() {
|
||||
configStore.deleteStore(this.config.id);
|
||||
|
||||
@ -894,8 +996,16 @@ export default {
|
||||
this.filterObserver();
|
||||
}
|
||||
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
|
||||
this.openmct.time.off('clock', this.updateRealTime);
|
||||
this.openmct.time.off('bounds', this.updateDisplayBounds);
|
||||
this.openmct.objectViews.off('clearData', this.clearData);
|
||||
},
|
||||
updateStatus(status) {
|
||||
this.$emit('statusUpdated', status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -56,6 +56,7 @@
|
||||
|
||||
<div ref="plotContainer"
|
||||
class="l-view-section u-style-receiver js-style-receiver"
|
||||
:class="{'s-status-timeconductor-unsynced': status && status === 'timeconductor-unsynced'}"
|
||||
>
|
||||
<div v-show="!!loading"
|
||||
class="c-loading--overlay loading"
|
||||
@ -64,6 +65,7 @@
|
||||
:cursor-guide="cursorGuide"
|
||||
:options="options"
|
||||
@loadingUpdated="loadingUpdated"
|
||||
@statusUpdated="setStatus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,7 +96,8 @@ export default {
|
||||
// hideExportButtons: false
|
||||
cursorGuide: false,
|
||||
gridLines: !this.options.compact,
|
||||
loading: false
|
||||
loading: false,
|
||||
status: ''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -131,6 +134,9 @@ export default {
|
||||
|
||||
toggleGridLines() {
|
||||
this.gridLines = !this.gridLines;
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -313,6 +313,46 @@ describe("the plugin", function () {
|
||||
expect(options[0].value).toBe("Some attribute");
|
||||
expect(options[1].value).toBe("Another attribute");
|
||||
});
|
||||
|
||||
it('hides the pause and play controls', () => {
|
||||
let pauseEl = element.querySelectorAll(".c-button-set .icon-pause");
|
||||
let playEl = element.querySelectorAll(".c-button-set .icon-arrow-right");
|
||||
expect(pauseEl.length).toBe(0);
|
||||
expect(playEl.length).toBe(0);
|
||||
});
|
||||
|
||||
describe('pause and play controls', () => {
|
||||
beforeEach(() => {
|
||||
openmct.time.clock('local', {
|
||||
start: -1000,
|
||||
end: 100
|
||||
});
|
||||
|
||||
return Vue.nextTick();
|
||||
});
|
||||
|
||||
it('shows the pause controls', (done) => {
|
||||
Vue.nextTick(() => {
|
||||
let pauseEl = element.querySelectorAll(".c-button-set .icon-pause");
|
||||
expect(pauseEl.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('shows the play control if plot is paused', (done) => {
|
||||
let pauseEl = element.querySelector(".c-button-set .icon-pause");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
|
||||
pauseEl.dispatchEvent(clickEvent);
|
||||
Vue.nextTick(() => {
|
||||
let playEl = element.querySelectorAll(".c-button-set .is-paused");
|
||||
expect(playEl.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("The stacked plot view", () => {
|
||||
|
@ -90,6 +90,7 @@ export default {
|
||||
|
||||
const onTickWidthChange = this.onTickWidthChange;
|
||||
const loadingUpdated = this.loadingUpdated;
|
||||
const setStatus = this.setStatus;
|
||||
|
||||
const openmct = this.openmct;
|
||||
const object = this.object;
|
||||
@ -111,17 +112,23 @@ export default {
|
||||
return {
|
||||
...getProps(),
|
||||
onTickWidthChange,
|
||||
loadingUpdated
|
||||
loadingUpdated,
|
||||
setStatus
|
||||
};
|
||||
},
|
||||
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver"><div v-show="!!loading" class="c-loading--overlay loading"></div><mct-plot :grid-lines="gridLines" :cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :options="options" @plotTickWidth="onTickWidthChange" @loadingUpdated="loadingUpdated"/></div>'
|
||||
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :data-status="status" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><div v-show="!!loading" class="c-loading--overlay loading"></div><mct-plot :grid-lines="gridLines" :cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :options="options" @plotTickWidth="onTickWidthChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
|
||||
});
|
||||
},
|
||||
onTickWidthChange() {
|
||||
this.$emit('plotTickWidth', ...arguments);
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
this.updateComponentProp('status', status);
|
||||
},
|
||||
loadingUpdated(loaded) {
|
||||
this.loading = loaded;
|
||||
this.updateComponentProp('loading', loaded);
|
||||
},
|
||||
getProps() {
|
||||
return {
|
||||
@ -129,7 +136,8 @@ export default {
|
||||
cursorGuide: this.cursorGuide,
|
||||
plotTickWidth: this.plotTickWidth,
|
||||
loading: this.loading,
|
||||
options: this.options
|
||||
options: this.options,
|
||||
status: this.status
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -638,6 +638,16 @@
|
||||
box-shadow: $shdw;
|
||||
}
|
||||
|
||||
@mixin smallerControlButtons() {
|
||||
.c-click-icon,
|
||||
.c-button,
|
||||
.c-icon-button {
|
||||
// Shrink buttons a bit when they appear in containers
|
||||
font-size: 0.9em;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin wrappedInput() {
|
||||
// An input that is wrapped. Optionally includes a __label or icon element.
|
||||
// Based on .c-search.
|
||||
|
@ -129,14 +129,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.c-click-icon,
|
||||
.c-button,
|
||||
.c-icon-button {
|
||||
// Shrink buttons a bit when they appear in a frame
|
||||
border-radius: $smallCr !important;
|
||||
font-size: 0.9em;
|
||||
padding: 5px;
|
||||
}
|
||||
@include smallerControlButtons;
|
||||
|
||||
&.has-complex-content {
|
||||
> .c-so-view__view-large { display: block; }
|
||||
|
@ -22,5 +22,7 @@
|
||||
.c-plan {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
@include smallerControlButtons;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user