Compare commits

...

3 Commits

Author SHA1 Message Date
e75cc35cb7 Moved operator status plugin to Open 2022-04-26 14:58:52 -07:00
f216bd8769 Emit event on click 2022-04-04 14:51:27 -07:00
79a430278e Added click event to simple indicator 2022-03-30 19:10:12 -07:00
6 changed files with 533 additions and 77 deletions

View File

@ -38,7 +38,7 @@ define([
};
IndicatorAPI.prototype.simpleIndicator = function () {
return new SimpleIndicator(this.openmct);
return new SimpleIndicator.default(this.openmct);
};
/**

View File

@ -20,82 +20,104 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['zepto', './res/indicator-template.html'],
function ($, indicatorTemplate) {
const DEFAULT_ICON_CLASS = 'icon-info';
import EventEmitter from 'EventEmitter';
import indicatorTemplate from './res/indicator-template.html';
function SimpleIndicator(openmct) {
this.openmct = openmct;
this.element = $(indicatorTemplate)[0];
this.priority = openmct.priority.DEFAULT;
const DEFAULT_ICON_CLASS = 'icon-info';
this.textElement = this.element.querySelector('.js-indicator-text');
class SimpleIndicator extends EventEmitter{
constructor(openmct) {
super();
//Set defaults
this.text('New Indicator');
this.description('');
this.iconClass(DEFAULT_ICON_CLASS);
this.statusClass('');
}
this.openmct = openmct;
this.element = compileTemplate(indicatorTemplate)[0];
this.priority = openmct.priority.DEFAULT;
this.textElement = this.element.querySelector('.js-indicator-text');
//Set defaults
this.text('New Indicator');
this.description('');
this.iconClass(DEFAULT_ICON_CLASS);
this.statusClass('');
this.click = this.click.bind(this);
SimpleIndicator.prototype.text = function (text) {
if (text !== undefined && text !== this.textValue) {
this.textValue = text;
this.textElement.innerText = text;
if (!text) {
this.element.classList.add('hidden');
} else {
this.element.classList.remove('hidden');
}
}
return this.textValue;
};
SimpleIndicator.prototype.description = function (description) {
if (description !== undefined && description !== this.descriptionValue) {
this.descriptionValue = description;
this.element.title = description;
}
return this.descriptionValue;
};
SimpleIndicator.prototype.iconClass = function (iconClass) {
if (iconClass !== undefined && iconClass !== this.iconClassValue) {
// element.classList is precious and throws errors if you try and add
// or remove empty strings
if (this.iconClassValue) {
this.element.classList.remove(this.iconClassValue);
}
if (iconClass) {
this.element.classList.add(iconClass);
}
this.iconClassValue = iconClass;
}
return this.iconClassValue;
};
SimpleIndicator.prototype.statusClass = function (statusClass) {
if (statusClass !== undefined && statusClass !== this.statusClassValue) {
if (this.statusClassValue) {
this.element.classList.remove(this.statusClassValue);
}
if (statusClass) {
this.element.classList.add(statusClass);
}
this.statusClassValue = statusClass;
}
return this.statusClassValue;
};
return SimpleIndicator;
this.element.addEventListener('click', this.click);
openmct.once('destroy', () => {
this.removeAllListeners();
this.element.removeEventListener('click', this.click)
})
}
);
text(text) {
if (text !== undefined && text !== this.textValue) {
this.textValue = text;
this.textElement.innerText = text;
if (!text) {
this.element.classList.add('hidden');
} else {
this.element.classList.remove('hidden');
}
}
return this.textValue;
};
description(description) {
if (description !== undefined && description !== this.descriptionValue) {
this.descriptionValue = description;
this.element.title = description;
}
return this.descriptionValue;
};
iconClass(iconClass) {
if (iconClass !== undefined && iconClass !== this.iconClassValue) {
// element.classList is precious and throws errors if you try and add
// or remove empty strings
if (this.iconClassValue) {
this.element.classList.remove(this.iconClassValue);
}
if (iconClass) {
this.element.classList.add(iconClass);
}
this.iconClassValue = iconClass;
}
return this.iconClassValue;
};
statusClass(statusClass) {
if (statusClass !== undefined && statusClass !== this.statusClassValue) {
if (this.statusClassValue) {
this.element.classList.remove(this.statusClassValue);
}
if (statusClass) {
this.element.classList.add(statusClass);
}
this.statusClassValue = statusClass;
}
return this.statusClassValue;
};
click(event) {
this.emit('click', event);
}
}
function compileTemplate(htmlTemplate) {
const templateNode = document.createElement('template');
templateNode.innerHTML = htmlTemplate;
return templateNode.content.cloneNode(true).children;
}
export default SimpleIndicator;

View File

@ -0,0 +1,207 @@
<template>
<div :style="position" class="c-menu" @click.stop="noop">
<div>My Role: {{role}}</div>
<div>Current Poll Question: {{currentPollQuestion}}</div>
<div>Current Poll Date: {{pollQuestionUpdated}}</div>
<div>My Status: {{roleStatus}}</div>
<div>Set Status:
<select v-model="selectedStatus" name="setStatus" @change="setStatus">
<option v-for="status in allStatuses" :key="status.value" :value="status.value">{{status.label}}</option>
</select>
</div>
</div>
</template>
<script>
// TODO: Make this configuration
const ROLES = ['Driver'];
const POLL_TELEMETRY_POINT_ID = {
namespace: 'taxonomy',
key: '~ViperGround~OperatorStatus~PollQuestion'
};
const POLL_QUESTION_KEY = 'value';
const ROLE_STATUS_TELEMETRY_POINT_ID = {
namespace: 'taxonomy',
key: '~ViperGround~OperatorStatus~driverStatus'
};
const ROLE_STATUS_KEY = 'value';
const SET_STATUS_URL = 'http://localhost:9000/viper-proxy/viper/yamcs/api/processors/viper/realtime/parameters/ViperGround/OperatorStatus/driverStatus';
const STATUSES = [
{
value: 0,
label: 'NO_STATUS'
},{
value: 1,
label: 'NO_GO'
},{
value: 2,
label: 'GO'
},{
value: 3,
label: 'MAYBE'
},
]
export default {
inject: ['openmct'],
props: {
positionX: {
type: Number,
required: true
},
positionY: {
type: Number,
required: true
}
},
data() {
return {
role: '--',
pollQuestionUpdated: '--',
currentPollQuestion: '--',
roleStatus: '--',
selectedStatus: 'no-go',
allStatuses: STATUSES
};
},
async mounted() {
this.unsubscribe = [];
this.fetchAllStatuses();
await this.fetchTelemetryObjects();
this.setMetadata();
this.fetchCurrentPoll();
this.fetchMyStatus();
this.subscribeToMyStatus();
this.subscribeToPollQuestion();
this.findFirstApplicableRole();
},
methods: {
fetchAllStatuses() {
//this.allStatuses = openmct.user.getAllStatuses();
},
async fetchTelemetryObjects() {
const telemetryObjects = await Promise.all([
this.openmct.objects.get(ROLE_STATUS_TELEMETRY_POINT_ID),
this.openmct.objects.get(POLL_TELEMETRY_POINT_ID)
]);
this.roleStatusTelemetryObject = telemetryObjects[0];
this.pollQuestionTelemetryObject = telemetryObjects[1];
},
setMetadata() {
const roleStatusMetadata = openmct.telemetry.getMetadata(this.roleStatusTelemetryObject);
const roleStatusMetadataValue = roleStatusMetadata.value(ROLE_STATUS_KEY);
const pollQuestionMetadata = openmct.telemetry.getMetadata(this.pollQuestionTelemetryObject);
const pollQuestionMetadataValue = pollQuestionMetadata.value(POLL_QUESTION_KEY);
const timestampMetadataValue = pollQuestionMetadata.value(this.openmct.time.timeSystem().key);
this.timestampFormatter = openmct.telemetry.getValueFormatter(timestampMetadataValue);
this.statusFormatter = openmct.telemetry.getValueFormatter(roleStatusMetadataValue);
this.pollQuestionFormatter = openmct.telemetry.getValueFormatter(pollQuestionMetadataValue);
},
async findFirstApplicableRole() {
const userRolesMap = await Promise.all(ROLES.map((role) => this.openmct.user.hasRole(role)));
const index = userRolesMap.findIndex((hasRole) => hasRole);
this.role = ROLES[index];
},
async fetchCurrentPoll() {
//const pollMessage = await openmct.user.getCurrentPollQuestion();
const pollMessages = await this.openmct.telemetry.request(this.pollQuestionTelemetryObject, {
strategy: 'latest',
// TODO: THIS IS A HACK, NEED A WAY TO ALWAYS GET THE LAST VALUE FROM ANY TIME WINDOW (maybe getParameterValue()? What happens if there is nothing in the cache though (eg after a restart))
start: 0,
end: Date.now()
});
if (pollMessages.length > 0) {
const datum = pollMessages[pollMessages.length - 1];
this.setPollQuestionFromDatum(datum);
}
},
setPollQuestionFromDatum(datum) {
this.currentPollQuestion = this.pollQuestionFormatter.format(datum);
this.pollQuestionUpdated = this.timestampFormatter.format(datum);
},
async fetchMyStatus() {
// const currentUser = this.openmct.user.getCurrentUser();
// const status = await this.openmct.user.getStatus(currentUser);
// TODO: Make role-specific
const roleStatuses = await this.openmct.telemetry.request(this.roleStatusTelemetryObject, {
strategy: 'latest',
start: 0,
end: Date.now()
});
if (roleStatuses.length > 0) {
const datum = roleStatuses[roleStatuses.length - 1];
this.setRoleStatusFromDatum(datum);
}
},
subscribeToMyStatus() {
// const currentUser = this.openmct.user.getCurrentUser();
// this.openmct.user.onUserStatusChange(currentUser, this.setRoleStatus);
this.unsubscribe.push(this.openmct.telemetry.subscribe(this.roleStatusTelemetryObject, (datum) => {
this.setRoleStatusFromDatum(datum);
}));
},
subscribeToPollQuestion() {
this.unsubscribe.push(this.openmct.telemetry.subscribe(this.roleStatusTelemetryObject, (datum) => {
this.setRoleStatusFromDatum(datum);
}));
},
setRoleStatusFromDatum(datum) {
this.roleStatus = this.statusFormatter.format(datum);
this.selectedStatus = this.findStatus(this.roleStatus);
},
findStatus(status) {
return (STATUSES.find(s => s.label === status) || STATUSES[0]).value;
},
async setStatus(status) {
// Where does 'status' come from. What does it look like?
// Could be provided as an enum from the user API.
//const result = await openmct.user.setStatus(user, status);
// const newDatum = openmct.telemetry.newDatumFromValues(this.statusDomainObject)
// const result = await openmct.telemetry.setValue(this.statusDomainObject, newDatum);
const fetchResult = await fetch(SET_STATUS_URL, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'UINT32',
uint32Value: `${this.selectedStatus}`
})
});
},
noop() {}
},
computed: {
position() {
return {
position: 'absolute',
left: `${this.positionX}px`,
top: `${this.positionY}px`
}
}
},
beforeDestroy() {
this.unsubscribe.forEach(unsubscribe => unsubscribe);
}
};
</script>

View File

@ -0,0 +1,135 @@
<template>
<div :style="position" class="c-menu" @click.stop="noop">
<div>Current Poll Question: {{currentPollQuestion}}</div>
<div>Current Poll Date: {{pollQuestionUpdated}}</div>
<div>Set Poll Question:
<input type="text" v-model="newPollQuestion" name="newPollQuestion" @change="setPollQuestion">
</div>
<div><button class="c-button" @click="clearAllResponses"> Clear All Responses</button></div>
</div>
</template>
<script>
const POLL_TELEMETRY_POINT_ID = {
namespace: 'taxonomy',
key: '~ViperGround~OperatorStatus~PollQuestion'
};
const POLL_QUESTION_KEY = 'value';
const SET_POLL_QUESTION_URL = 'http://localhost:9000/viper-proxy/viper/yamcs/api/processors/viper/realtime/parameters/ViperGround/OperatorStatus/PollQuestion';
const CLEAR_RESPONSES_URL = 'http://localhost:9000/viper-proxy/viper/yamcs/api/processors/viper/realtime/parameters:batchSet';
export default {
inject: ['openmct'],
props: {
positionX: {
type: Number,
required: true
},
positionY: {
type: Number,
required: true
}
},
data() {
return {
pollQuestionUpdated: '--',
currentPollQuestion: '--',
newPollQuestion: undefined
};
},
async mounted() {
this.unsubscribe = [];
this.pollQuestionTelemetryObject = await this.openmct.objects.get(POLL_TELEMETRY_POINT_ID);
this.setMetadata();
this.fetchCurrentPoll();
this.subscribeToPollQuestion();
},
methods: {
setMetadata() {
const pollQuestionMetadata = openmct.telemetry.getMetadata(this.pollQuestionTelemetryObject);
const pollQuestionMetadataValue = pollQuestionMetadata.value(POLL_QUESTION_KEY);
const timestampMetadataValue = pollQuestionMetadata.value(this.openmct.time.timeSystem().key);
this.timestampFormatter = openmct.telemetry.getValueFormatter(timestampMetadataValue);
this.pollQuestionFormatter = openmct.telemetry.getValueFormatter(pollQuestionMetadataValue);
},
async fetchCurrentPoll() {
const pollMessages = await this.openmct.telemetry.request(this.pollQuestionTelemetryObject, {
strategy: 'latest',
// TODO: THIS IS A HACK, NEED A WAY TO ALWAYS GET THE LAST VALUE FROM ANY TIME WINDOW (maybe getParameterValue()? What happens if there is nothing in the cache though (eg after a restart))
start: 0,
end: Date.now()
});
if (pollMessages.length > 0) {
const datum = pollMessages[pollMessages.length - 1];
this.setPollQuestionFromDatum(datum);
}
},
subscribeToPollQuestion() {
this.unsubscribe.push(this.openmct.telemetry.subscribe(this.pollQuestionTelemetryObject, (datum) => {
this.setPollQuestionFromDatum(datum);
}));
},
setPollQuestionFromDatum(datum) {
this.currentPollQuestion = this.pollQuestionFormatter.format(datum);
this.pollQuestionUpdated = this.timestampFormatter.format(datum);
},
async setPollQuestion() {
//const response = await this.openmct.user.setPollQuestion(this.newPollQuestion);
await fetch(SET_POLL_QUESTION_URL, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'STRING',
stringValue: `${this.newPollQuestion}`
})
});
this.newPollQuestion = undefined;
},
clearAllResponses() {
//this.openmct.user.clearAllStatuses();
fetch(CLEAR_RESPONSES_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
request: [{
id: {
name: "/ViperGround/OperatorStatus/driverStatus",
},
value: {
type: 'UINT32',
uint32Value: '0'
}
//TODO: Also set all the other operator status parameters
}]
})
});
},
noop() {}
},
computed: {
position() {
return {
position: 'absolute',
left: `${this.positionX}px`,
top: `${this.positionY}px`
}
}
},
beforeDestroy() {
this.unsubscribe.forEach(unsubscribe => unsubscribe);
}
};
</script>

View File

@ -0,0 +1,89 @@
/**
* Could potentially be implemented using the User API to make this "generic" (although only supported from Yamcs initially)
*/
import OperatorStatusComponent from './OperatorStatus.vue';
import PollQuestionComponent from './PollQuestion.vue';
import Vue from 'vue';
export default function operatorStatusPlugin(config) {
return function install(openmct) {
/**
Operator Status
*/
const operatorStatusElement = new Vue({
components: {
OperatorStatus: OperatorStatusComponent
},
provide: {
openmct
},
data() {
return {
positionX: 0,
positionY: 0
}
},
template: '<operator-status :positionX="positionX" :positionY="positionY" />'
}).$mount();
const operatorIndicator = openmct.indicators.simpleIndicator();
operatorIndicator.text("My Operator Status");
operatorIndicator.description("Set my operator status");
operatorIndicator.iconClass('icon-check');
operatorIndicator.on('click', (event) => {
//Don't propagate, otherwise this event will trigger the listener below and remove itself.
event.stopPropagation();
document.body.appendChild(operatorStatusElement.$el);
operatorStatusElement.positionX = event.clientX;
operatorStatusElement.positionY = event.clientY;
document.addEventListener('click', event => {
operatorStatusElement.$el.remove();
}, {once: true});
});
openmct.indicators.add(operatorIndicator);
/**
Poll Question
*/
const pollQuestionElement = new Vue({
components: {
PollQuestion: PollQuestionComponent
},
provide: {
openmct
},
data() {
return {
positionX: 0,
positionY: 0
}
},
template: '<poll-question :positionX="positionX" :positionY="positionY" />'
}).$mount();
const pollQuestionIndicator = openmct.indicators.simpleIndicator();
pollQuestionIndicator.text("Poll Question");
pollQuestionIndicator.description("Set the current poll question");
pollQuestionIndicator.iconClass('icon-draft');
pollQuestionIndicator.on('click', (event) => {
//Don't propagate, otherwise this event will trigger the listener below and remove itself.
event.stopPropagation();
document.body.appendChild(pollQuestionElement.$el);
pollQuestionElement.positionX = event.clientX;
pollQuestionElement.positionY = event.clientY;
document.addEventListener('click', event => {
pollQuestionElement.$el.remove();
}, {once: true});
});
openmct.indicators.add(pollQuestionIndicator);
};
};

View File

@ -76,7 +76,8 @@ define([
'./timer/plugin',
'./userIndicator/plugin',
'../../example/exampleUser/plugin',
'./localStorage/plugin'
'./localStorage/plugin',
'./operatorStatus/plugin'
], function (
_,
UTCTimeSystem,
@ -133,7 +134,8 @@ define([
Timer,
UserIndicator,
ExampleUser,
LocalStorage
LocalStorage,
OperatorStatus
) {
const plugins = {};
@ -210,6 +212,7 @@ define([
plugins.DeviceClassifier = DeviceClassifier.default;
plugins.UserIndicator = UserIndicator.default;
plugins.LocalStorage = LocalStorage.default;
plugins.OperatorStatus = OperatorStatus.default;
return plugins;
});