mirror of
https://github.com/nasa/openmct.git
synced 2025-05-30 06:04:20 +00:00
Add gauge 4896 (#4919)
* Add new Gauge component Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com> Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov> Co-authored-by: John Hill <john.c.hill@nasa.gov> Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
parent
402cd15726
commit
a53a3a0297
31
e2e/tests/plugins/gauge.e2eSpec.js
Normal file
31
e2e/tests/plugins/gauge.e2eSpec.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 1. create an gauge plugin
|
||||||
|
// 2. create with custom settings
|
||||||
|
// 3. verify required fields are required
|
||||||
|
// 4. should not be able to create gaugue inside a gauge.
|
||||||
|
// 5. should not be able to move/duplicate gaugue inside a gauge.
|
||||||
|
// 6. delete gauge
|
||||||
|
// 7. snapshot gauge
|
||||||
|
// 8. snapshot gauge inside Notebook entry
|
||||||
|
// 9. gauge inside Notebook entry
|
||||||
|
// 10. can drop inside other objects:
|
||||||
|
// can drop to display layout : yes
|
||||||
|
// can drop to Flexible layout : yes
|
||||||
|
// can drop to Folder : yes
|
||||||
|
// can drop to Bar graph: No
|
||||||
|
// can drop to clock: No
|
||||||
|
// . can drop to condition set: No
|
||||||
|
// . can drop to condition widegt: No
|
||||||
|
// // .............all other objects
|
||||||
|
// . can drop to webpage: No
|
||||||
|
// 11. drop telemetry inside a gauge and reflect on data in both fixed and realtime
|
||||||
|
// 12. can have only one telemetry per gague. (show confirmation dialog if want to replace)
|
||||||
|
// 12. all form props (except hidefromInspector) shows inside inspector
|
||||||
|
// 13. should be able to export and import
|
||||||
|
// 14. able to search in tree
|
||||||
|
// 15. supports fix and realtime mode
|
||||||
|
// 17. refresh after creating
|
||||||
|
// 18. open in new tab
|
||||||
|
// 19. two separate gauges inside display layout should not show same numbers unless have same telemetry (should show respctive telemetry data).
|
||||||
|
// 20. inside display layout compare gague values vs its telemetry value (should be same in fixed time and should tick with same rate/value in realtime)
|
||||||
|
// 21. same for two diff gauges- > inside display layout compare gague values vs its telemetry value (should be same in fixed time and should tick with same rate/value in realtime)
|
||||||
|
// 22. export jpeg/png of plugin
|
@ -242,6 +242,7 @@ define([
|
|||||||
|
|
||||||
// Plugins that are installed by default
|
// Plugins that are installed by default
|
||||||
|
|
||||||
|
this.install(this.plugins.Gauge());
|
||||||
this.install(this.plugins.Plot());
|
this.install(this.plugins.Plot());
|
||||||
this.install(this.plugins.Chart());
|
this.install(this.plugins.Chart());
|
||||||
this.install(this.plugins.TelemetryTable.default());
|
this.install(this.plugins.TelemetryTable.default());
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import AutoCompleteField from './components/controls/AutoCompleteField.vue';
|
import AutoCompleteField from './components/controls/AutoCompleteField.vue';
|
||||||
import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue';
|
import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue';
|
||||||
|
import CheckBoxField from './components/controls/CheckBoxField.vue';
|
||||||
import Datetime from './components/controls/Datetime.vue';
|
import Datetime from './components/controls/Datetime.vue';
|
||||||
import FileInput from './components/controls/FileInput.vue';
|
import FileInput from './components/controls/FileInput.vue';
|
||||||
import Locator from './components/controls/Locator.vue';
|
import Locator from './components/controls/Locator.vue';
|
||||||
@ -7,11 +8,13 @@ import NumberField from './components/controls/NumberField.vue';
|
|||||||
import SelectField from './components/controls/SelectField.vue';
|
import SelectField from './components/controls/SelectField.vue';
|
||||||
import TextAreaField from './components/controls/TextAreaField.vue';
|
import TextAreaField from './components/controls/TextAreaField.vue';
|
||||||
import TextField from './components/controls/TextField.vue';
|
import TextField from './components/controls/TextField.vue';
|
||||||
|
import ToggleSwitchField from './components/controls/ToggleSwitchField.vue';
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
export const DEFAULT_CONTROLS_MAP = {
|
export const DEFAULT_CONTROLS_MAP = {
|
||||||
'autocomplete': AutoCompleteField,
|
'autocomplete': AutoCompleteField,
|
||||||
|
'checkbox': CheckBoxField,
|
||||||
'composite': ClockDisplayFormatField,
|
'composite': ClockDisplayFormatField,
|
||||||
'datetime': Datetime,
|
'datetime': Datetime,
|
||||||
'file-input': FileInput,
|
'file-input': FileInput,
|
||||||
@ -19,7 +22,8 @@ export const DEFAULT_CONTROLS_MAP = {
|
|||||||
'numberfield': NumberField,
|
'numberfield': NumberField,
|
||||||
'select': SelectField,
|
'select': SelectField,
|
||||||
'textarea': TextAreaField,
|
'textarea': TextAreaField,
|
||||||
'textfield': TextField
|
'textfield': TextField,
|
||||||
|
'toggleSwitch': ToggleSwitchField
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class FormControl {
|
export default class FormControl {
|
||||||
@ -94,4 +98,3 @@ export default class FormControl {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,10 +79,12 @@ export default {
|
|||||||
rowClass() {
|
rowClass() {
|
||||||
let cssClass = this.cssClass;
|
let cssClass = this.cssClass;
|
||||||
|
|
||||||
if (this.row.required) {
|
if (!this.row.required) {
|
||||||
cssClass = `${cssClass} req`;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cssClass = `${cssClass} req`;
|
||||||
|
|
||||||
if (this.visited && this.valid !== undefined) {
|
if (this.visited && this.valid !== undefined) {
|
||||||
if (this.valid === true) {
|
if (this.valid === true) {
|
||||||
cssClass = `${cssClass} valid`;
|
cssClass = `${cssClass} valid`;
|
||||||
|
55
src/api/forms/components/controls/CheckBoxField.vue
Normal file
55
src/api/forms/components/controls/CheckBoxField.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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>
|
||||||
|
<span class="form-control shell">
|
||||||
|
<span
|
||||||
|
class="field control"
|
||||||
|
:class="model.cssClass"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="isChecked"
|
||||||
|
@input="toggleCheckBox"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import toggleMixin from '../../toggle-check-box-mixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [toggleMixin],
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isChecked: this.model.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -58,7 +58,6 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateText() {
|
updateText() {
|
||||||
console.log('updateText', this.field);
|
|
||||||
const data = {
|
const data = {
|
||||||
model: this.model,
|
model: this.model,
|
||||||
value: this.field
|
value: this.field
|
||||||
|
62
src/api/forms/components/controls/ToggleSwitchField.vue
Normal file
62
src/api/forms/components/controls/ToggleSwitchField.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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>
|
||||||
|
<span class="form-control shell">
|
||||||
|
<span
|
||||||
|
class="field control"
|
||||||
|
:class="model.cssClass"
|
||||||
|
>
|
||||||
|
<ToggleSwitch
|
||||||
|
id="switchId"
|
||||||
|
:checked="isChecked"
|
||||||
|
@change="toggleCheckBox"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import toggleMixin from '../../toggle-check-box-mixin';
|
||||||
|
import ToggleSwitch from '@/ui/components/ToggleSwitch.vue';
|
||||||
|
|
||||||
|
import uuid from 'uuid';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ToggleSwitch
|
||||||
|
},
|
||||||
|
mixins: [toggleMixin],
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
switchId: `toggleSwitch-${uuid}`,
|
||||||
|
isChecked: this.model.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
19
src/api/forms/toggle-check-box-mixin.js
Normal file
19
src/api/forms/toggle-check-box-mixin.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isChecked: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleCheckBox(event) {
|
||||||
|
this.isChecked = !this.isChecked;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
model: this.model,
|
||||||
|
value: this.isChecked
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$emit('onChange', data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -90,6 +90,9 @@ export default class CreateWizard {
|
|||||||
rows: this.properties.map(property => {
|
rows: this.properties.map(property => {
|
||||||
const row = JSON.parse(JSON.stringify(property));
|
const row = JSON.parse(JSON.stringify(property));
|
||||||
row.value = this.getValue(row);
|
row.value = this.getValue(row);
|
||||||
|
if (property.validate) {
|
||||||
|
row.validate = property.validate;
|
||||||
|
}
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
}).filter(row => row && row.control)
|
}).filter(row => row && row.control)
|
||||||
|
199
src/plugins/gauge/GaugePlugin.js
Normal file
199
src/plugins/gauge/GaugePlugin.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import GaugeViewProvider from './GaugeViewProvider';
|
||||||
|
import GaugeFormController from './components/GaugeFormController.vue';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export const GAUGE_TYPES = [
|
||||||
|
['Filled Dial', 'dial-filled'],
|
||||||
|
['Needle Dial', 'dial-needle'],
|
||||||
|
['Vertical Meter', 'meter-vertical'],
|
||||||
|
['Vertical Meter Inverted', 'meter-vertical-inverted'],
|
||||||
|
['Horizontal Meter', 'meter-horizontal']
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return function install(openmct) {
|
||||||
|
openmct.objectViews.addProvider(new GaugeViewProvider(openmct));
|
||||||
|
|
||||||
|
openmct.forms.addNewFormControl('gauge-controller', getGaugeFormController(openmct));
|
||||||
|
openmct.types.addType('gauge', {
|
||||||
|
name: "Gauge",
|
||||||
|
creatable: true,
|
||||||
|
description: "Graphically visualize a telemetry element's current value between a minimum and maximum.",
|
||||||
|
cssClass: 'icon-gauge',
|
||||||
|
initialize(domainObject) {
|
||||||
|
domainObject.composition = [];
|
||||||
|
domainObject.configuration = {
|
||||||
|
gaugeController: {
|
||||||
|
gaugeType: GAUGE_TYPES[0][1],
|
||||||
|
isDisplayMinMax: true,
|
||||||
|
isDisplayCurVal: true,
|
||||||
|
isUseTelemetryLimits: true,
|
||||||
|
limitLow: 10,
|
||||||
|
limitHigh: 90,
|
||||||
|
max: 100,
|
||||||
|
min: 0,
|
||||||
|
precision: 2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
name: "Display current value",
|
||||||
|
control: "toggleSwitch",
|
||||||
|
cssClass: "l-input",
|
||||||
|
key: "isDisplayCurVal",
|
||||||
|
property: [
|
||||||
|
"configuration",
|
||||||
|
"gaugeController",
|
||||||
|
"isDisplayCurVal"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Display range values",
|
||||||
|
control: "toggleSwitch",
|
||||||
|
cssClass: "l-input",
|
||||||
|
key: "isDisplayMinMax",
|
||||||
|
property: [
|
||||||
|
"configuration",
|
||||||
|
"gaugeController",
|
||||||
|
"isDisplayMinMax"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Float precision",
|
||||||
|
control: "numberfield",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
key: "precision",
|
||||||
|
property: [
|
||||||
|
"configuration",
|
||||||
|
"gaugeController",
|
||||||
|
"precision"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gauge type",
|
||||||
|
options: GAUGE_TYPES.map(type => {
|
||||||
|
return {
|
||||||
|
name: type[0],
|
||||||
|
value: type[1]
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
control: "select",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
key: "gaugeController",
|
||||||
|
property: [
|
||||||
|
"configuration",
|
||||||
|
"gaugeController",
|
||||||
|
"gaugeType"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Value ranges and limits",
|
||||||
|
control: "gauge-controller",
|
||||||
|
cssClass: "l-input",
|
||||||
|
key: "gaugeController",
|
||||||
|
required: false,
|
||||||
|
hideFromInspector: true,
|
||||||
|
property: [
|
||||||
|
"configuration",
|
||||||
|
"gaugeController"
|
||||||
|
],
|
||||||
|
validate: ({ value }, callback) => {
|
||||||
|
if (value.isUseTelemetryLimits) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { min, max, limitLow, limitHigh } = value;
|
||||||
|
const valid = {
|
||||||
|
min: true,
|
||||||
|
max: true,
|
||||||
|
limitLow: true,
|
||||||
|
limitHigh: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (min === '') {
|
||||||
|
valid.min = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max === '') {
|
||||||
|
valid.max = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max < min) {
|
||||||
|
valid.min = false;
|
||||||
|
valid.max = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitLow !== '') {
|
||||||
|
valid.limitLow = min <= limitLow && limitLow < max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitHigh !== '') {
|
||||||
|
valid.limitHigh = min < limitHigh && limitHigh <= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid.limitLow && valid.limitHigh
|
||||||
|
&& limitLow !== '' && limitHigh !== ''
|
||||||
|
&& limitLow > limitHigh) {
|
||||||
|
valid.limitLow = false;
|
||||||
|
valid.limitHigh = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback(valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid.min && valid.max && valid.limitLow && valid.limitHigh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function getGaugeFormController(openmct) {
|
||||||
|
return {
|
||||||
|
show(element, model, onChange) {
|
||||||
|
const rowComponent = new Vue({
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
GaugeFormController
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
model,
|
||||||
|
onChange
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: `<GaugeFormController :model="model" @onChange="onChange"></GaugeFormController>`
|
||||||
|
});
|
||||||
|
|
||||||
|
return rowComponent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
805
src/plugins/gauge/GaugePluginSpec.js
Normal file
805
src/plugins/gauge/GaugePluginSpec.js
Normal file
@ -0,0 +1,805 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
import {
|
||||||
|
createOpenMct,
|
||||||
|
resetApplicationState
|
||||||
|
} from 'utils/testing';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
let gaugeDomainObject = {
|
||||||
|
identifier: {
|
||||||
|
key: 'gauge',
|
||||||
|
namespace: 'test-namespace'
|
||||||
|
},
|
||||||
|
type: 'gauge',
|
||||||
|
composition: []
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Gauge plugin', () => {
|
||||||
|
let openmct;
|
||||||
|
let child;
|
||||||
|
let gaugeHolder;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
gaugeHolder = document.createElement('div');
|
||||||
|
gaugeHolder.style.display = 'block';
|
||||||
|
gaugeHolder.style.width = '1920px';
|
||||||
|
gaugeHolder.style.height = '1080px';
|
||||||
|
|
||||||
|
child = document.createElement('div');
|
||||||
|
gaugeHolder.appendChild(child);
|
||||||
|
|
||||||
|
openmct = createOpenMct();
|
||||||
|
openmct.on('start', done);
|
||||||
|
|
||||||
|
openmct.startHeadless();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Plugin installed by default', () => {
|
||||||
|
const gaugueType = openmct.types.get('gauge');
|
||||||
|
|
||||||
|
expect(gaugueType).not.toBeNull();
|
||||||
|
expect(gaugueType.definition.name).toEqual('Gauge');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Gaugue plugin is creatable', () => {
|
||||||
|
const gaugueType = openmct.types.get('gauge');
|
||||||
|
|
||||||
|
expect(gaugueType.definition.creatable).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Gaugue plugin is creatable', () => {
|
||||||
|
const gaugueType = openmct.types.get('gauge');
|
||||||
|
|
||||||
|
expect(gaugueType.definition.creatable).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Gaugue form controller', () => {
|
||||||
|
const gaugeController = openmct.forms.getFormControl('gauge-controller');
|
||||||
|
expect(gaugeController).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Gaugue with Filled Dial', () => {
|
||||||
|
let gaugeViewProvider;
|
||||||
|
let gaugeView;
|
||||||
|
let gaugeViewObject;
|
||||||
|
let mutablegaugeObject;
|
||||||
|
let randomValue;
|
||||||
|
|
||||||
|
const minValue = -1;
|
||||||
|
const maxValue = 1;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
randomValue = Math.random();
|
||||||
|
gaugeViewObject = {
|
||||||
|
...gaugeDomainObject,
|
||||||
|
configuration: {
|
||||||
|
gaugeController: {
|
||||||
|
gaugeType: 'dial-filled',
|
||||||
|
isDisplayMinMax: true,
|
||||||
|
isDisplayCurVal: true,
|
||||||
|
isUseTelemetryLimits: false,
|
||||||
|
limitLow: -0.9,
|
||||||
|
limitHigh: 0.9,
|
||||||
|
max: maxValue,
|
||||||
|
min: minValue,
|
||||||
|
precision: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
composition: [
|
||||||
|
{
|
||||||
|
namespace: 'test-namespace',
|
||||||
|
key: 'test-object'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
id: 'test-object',
|
||||||
|
name: 'gauge'
|
||||||
|
};
|
||||||
|
|
||||||
|
const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
|
||||||
|
'get',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'observe'
|
||||||
|
]);
|
||||||
|
|
||||||
|
openmct.editor = {};
|
||||||
|
openmct.editor.isEditing = () => false;
|
||||||
|
|
||||||
|
const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
|
||||||
|
gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
|
||||||
|
|
||||||
|
testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
openmct.objects.addProvider('test-namespace', testObjectProvider);
|
||||||
|
testObjectProvider.observe.and.returnValue(() => {});
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(true));
|
||||||
|
testObjectProvider.update.and.returnValue(Promise.resolve(true));
|
||||||
|
|
||||||
|
spyOn(openmct.telemetry, 'getMetadata').and.returnValue({
|
||||||
|
valuesForHints: () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: 'sin'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
value: () => 1
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getValueFormatter').and.returnValue({
|
||||||
|
parse: () => {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getFormatMap').and.returnValue({
|
||||||
|
sin: {
|
||||||
|
format: (datum) => {
|
||||||
|
return randomValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
|
||||||
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
|
||||||
|
spyOn(openmct.time, 'bounds').and.returnValue({
|
||||||
|
start: 1000,
|
||||||
|
end: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||||
|
mutablegaugeObject = mutableObject;
|
||||||
|
gaugeView = gaugeViewProvider.view(mutablegaugeObject);
|
||||||
|
gaugeView.show(child);
|
||||||
|
|
||||||
|
return Vue.nextTick();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
gaugeView.destroy();
|
||||||
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides gauge view', () => {
|
||||||
|
expect(gaugeViewProvider).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders gauge element', () => {
|
||||||
|
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||||
|
expect(gaugeElement.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders major elements', () => {
|
||||||
|
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||||
|
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||||
|
const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
|
||||||
|
const dialElement = gaugeHolder.querySelector('.c-dial');
|
||||||
|
|
||||||
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||||
|
|
||||||
|
expect(hasMajorElements).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correct min max values', () => {
|
||||||
|
expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${minValue} ${maxValue}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correct current value', (done) => {
|
||||||
|
function WatchUpdateValue() {
|
||||||
|
const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
|
||||||
|
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedWatchUpdate = debounce(WatchUpdateValue, 200);
|
||||||
|
Vue.nextTick(debouncedWatchUpdate);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Gaugue with Needle Dial', () => {
|
||||||
|
let gaugeViewProvider;
|
||||||
|
let gaugeView;
|
||||||
|
let gaugeViewObject;
|
||||||
|
let mutablegaugeObject;
|
||||||
|
let randomValue;
|
||||||
|
|
||||||
|
const minValue = -1;
|
||||||
|
const maxValue = 1;
|
||||||
|
beforeEach(() => {
|
||||||
|
randomValue = Math.random();
|
||||||
|
gaugeViewObject = {
|
||||||
|
...gaugeDomainObject,
|
||||||
|
configuration: {
|
||||||
|
gaugeController: {
|
||||||
|
gaugeType: 'dial-needle',
|
||||||
|
isDisplayMinMax: true,
|
||||||
|
isDisplayCurVal: true,
|
||||||
|
isUseTelemetryLimits: false,
|
||||||
|
limitLow: -0.9,
|
||||||
|
limitHigh: 0.9,
|
||||||
|
max: maxValue,
|
||||||
|
min: minValue,
|
||||||
|
precision: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
composition: [
|
||||||
|
{
|
||||||
|
namespace: 'test-namespace',
|
||||||
|
key: 'test-object'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
id: 'test-object',
|
||||||
|
name: 'gauge'
|
||||||
|
};
|
||||||
|
|
||||||
|
const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
|
||||||
|
'get',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'observe'
|
||||||
|
]);
|
||||||
|
|
||||||
|
openmct.editor = {};
|
||||||
|
openmct.editor.isEditing = () => false;
|
||||||
|
|
||||||
|
const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
|
||||||
|
gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
|
||||||
|
|
||||||
|
testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
openmct.objects.addProvider('test-namespace', testObjectProvider);
|
||||||
|
testObjectProvider.observe.and.returnValue(() => {});
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(true));
|
||||||
|
testObjectProvider.update.and.returnValue(Promise.resolve(true));
|
||||||
|
|
||||||
|
spyOn(openmct.telemetry, 'getMetadata').and.returnValue({
|
||||||
|
valuesForHints: () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: 'sin'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
value: () => 1
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getValueFormatter').and.returnValue({
|
||||||
|
parse: () => {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getFormatMap').and.returnValue({
|
||||||
|
sin: {
|
||||||
|
format: (datum) => {
|
||||||
|
return randomValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
|
||||||
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
|
||||||
|
spyOn(openmct.time, 'bounds').and.returnValue({
|
||||||
|
start: 1000,
|
||||||
|
end: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||||
|
mutablegaugeObject = mutableObject;
|
||||||
|
gaugeView = gaugeViewProvider.view(mutablegaugeObject);
|
||||||
|
gaugeView.show(child);
|
||||||
|
|
||||||
|
return Vue.nextTick();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
gaugeView.destroy();
|
||||||
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides gauge view', () => {
|
||||||
|
expect(gaugeViewProvider).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders gauge element', () => {
|
||||||
|
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||||
|
expect(gaugeElement.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders major elements', () => {
|
||||||
|
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||||
|
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||||
|
const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
|
||||||
|
const dialElement = gaugeHolder.querySelector('.c-dial');
|
||||||
|
|
||||||
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||||
|
|
||||||
|
expect(hasMajorElements).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correct min max values', () => {
|
||||||
|
expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${minValue} ${maxValue}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correct current value', (done) => {
|
||||||
|
function WatchUpdateValue() {
|
||||||
|
const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
|
||||||
|
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedWatchUpdate = debounce(WatchUpdateValue, 200);
|
||||||
|
Vue.nextTick(debouncedWatchUpdate);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Gaugue with Vertical Meter', () => {
|
||||||
|
let gaugeViewProvider;
|
||||||
|
let gaugeView;
|
||||||
|
let gaugeViewObject;
|
||||||
|
let mutablegaugeObject;
|
||||||
|
let randomValue;
|
||||||
|
|
||||||
|
const minValue = -1;
|
||||||
|
const maxValue = 1;
|
||||||
|
beforeEach(() => {
|
||||||
|
randomValue = Math.random();
|
||||||
|
gaugeViewObject = {
|
||||||
|
...gaugeDomainObject,
|
||||||
|
configuration: {
|
||||||
|
gaugeController: {
|
||||||
|
gaugeType: 'meter-vertical',
|
||||||
|
isDisplayMinMax: true,
|
||||||
|
isDisplayCurVal: true,
|
||||||
|
isUseTelemetryLimits: false,
|
||||||
|
limitLow: -0.9,
|
||||||
|
limitHigh: 0.9,
|
||||||
|
max: maxValue,
|
||||||
|
min: minValue,
|
||||||
|
precision: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
composition: [
|
||||||
|
{
|
||||||
|
namespace: 'test-namespace',
|
||||||
|
key: 'test-object'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
id: 'test-object',
|
||||||
|
name: 'gauge'
|
||||||
|
};
|
||||||
|
|
||||||
|
const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
|
||||||
|
'get',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'observe'
|
||||||
|
]);
|
||||||
|
|
||||||
|
openmct.editor = {};
|
||||||
|
openmct.editor.isEditing = () => false;
|
||||||
|
|
||||||
|
const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
|
||||||
|
gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
|
||||||
|
|
||||||
|
testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
openmct.objects.addProvider('test-namespace', testObjectProvider);
|
||||||
|
testObjectProvider.observe.and.returnValue(() => {});
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(true));
|
||||||
|
testObjectProvider.update.and.returnValue(Promise.resolve(true));
|
||||||
|
|
||||||
|
spyOn(openmct.telemetry, 'getMetadata').and.returnValue({
|
||||||
|
valuesForHints: () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: 'sin'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
value: () => 1
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getValueFormatter').and.returnValue({
|
||||||
|
parse: () => {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getFormatMap').and.returnValue({
|
||||||
|
sin: {
|
||||||
|
format: (datum) => {
|
||||||
|
return randomValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
|
||||||
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
|
||||||
|
spyOn(openmct.time, 'bounds').and.returnValue({
|
||||||
|
start: 1000,
|
||||||
|
end: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||||
|
mutablegaugeObject = mutableObject;
|
||||||
|
gaugeView = gaugeViewProvider.view(mutablegaugeObject);
|
||||||
|
gaugeView.show(child);
|
||||||
|
|
||||||
|
return Vue.nextTick();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
gaugeView.destroy();
|
||||||
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides gauge view', () => {
|
||||||
|
expect(gaugeViewProvider).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders gauge element', () => {
|
||||||
|
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||||
|
expect(gaugeElement.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders major elements', () => {
|
||||||
|
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||||
|
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||||
|
const curveElement = gaugeHolder.querySelector('.c-meter');
|
||||||
|
const dialElement = gaugeHolder.querySelector('.c-meter__bg');
|
||||||
|
|
||||||
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||||
|
|
||||||
|
expect(hasMajorElements).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correct min max values', () => {
|
||||||
|
expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${maxValue} ${minValue}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correct current value', (done) => {
|
||||||
|
function WatchUpdateValue() {
|
||||||
|
const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
|
||||||
|
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedWatchUpdate = debounce(WatchUpdateValue, 200);
|
||||||
|
Vue.nextTick(debouncedWatchUpdate);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Gaugue with Vertical Meter Inverted', () => {
|
||||||
|
let gaugeViewProvider;
|
||||||
|
let gaugeView;
|
||||||
|
let gaugeViewObject;
|
||||||
|
let mutablegaugeObject;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
gaugeViewObject = {
|
||||||
|
...gaugeDomainObject,
|
||||||
|
configuration: {
|
||||||
|
gaugeController: {
|
||||||
|
gaugeType: 'meter-vertical',
|
||||||
|
isDisplayMinMax: true,
|
||||||
|
isDisplayCurVal: true,
|
||||||
|
isUseTelemetryLimits: false,
|
||||||
|
limitLow: -0.9,
|
||||||
|
limitHigh: 0.9,
|
||||||
|
max: 1,
|
||||||
|
min: -1,
|
||||||
|
precision: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: 'test-object',
|
||||||
|
name: 'gauge'
|
||||||
|
};
|
||||||
|
|
||||||
|
const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
|
||||||
|
'get',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'observe'
|
||||||
|
]);
|
||||||
|
|
||||||
|
openmct.editor = {};
|
||||||
|
openmct.editor.isEditing = () => false;
|
||||||
|
|
||||||
|
const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
|
||||||
|
gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
|
||||||
|
|
||||||
|
testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
openmct.objects.addProvider('test-namespace', testObjectProvider);
|
||||||
|
testObjectProvider.observe.and.returnValue(() => {});
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(true));
|
||||||
|
testObjectProvider.update.and.returnValue(Promise.resolve(true));
|
||||||
|
|
||||||
|
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||||
|
mutablegaugeObject = mutableObject;
|
||||||
|
|
||||||
|
gaugeView = gaugeViewProvider.view(mutablegaugeObject);
|
||||||
|
gaugeView.show(child);
|
||||||
|
|
||||||
|
return Vue.nextTick();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
gaugeView.destroy();
|
||||||
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides gauge view', () => {
|
||||||
|
expect(gaugeViewProvider).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders gauge element', () => {
|
||||||
|
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||||
|
expect(gaugeElement.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders major elements', () => {
|
||||||
|
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||||
|
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||||
|
const curveElement = gaugeHolder.querySelector('.c-meter');
|
||||||
|
const dialElement = gaugeHolder.querySelector('.c-meter__bg');
|
||||||
|
|
||||||
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||||
|
|
||||||
|
expect(hasMajorElements).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Gaugue with Horizontal Meter', () => {
|
||||||
|
let gaugeViewProvider;
|
||||||
|
let gaugeView;
|
||||||
|
let gaugeViewObject;
|
||||||
|
let mutablegaugeObject;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
gaugeViewObject = {
|
||||||
|
...gaugeDomainObject,
|
||||||
|
configuration: {
|
||||||
|
gaugeController: {
|
||||||
|
gaugeType: 'meter-vertical',
|
||||||
|
isDisplayMinMax: true,
|
||||||
|
isDisplayCurVal: true,
|
||||||
|
isUseTelemetryLimits: false,
|
||||||
|
limitLow: -0.9,
|
||||||
|
limitHigh: 0.9,
|
||||||
|
max: 1,
|
||||||
|
min: -1,
|
||||||
|
precision: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: 'test-object',
|
||||||
|
name: 'gauge'
|
||||||
|
};
|
||||||
|
|
||||||
|
const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
|
||||||
|
'get',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'observe'
|
||||||
|
]);
|
||||||
|
|
||||||
|
openmct.editor = {};
|
||||||
|
openmct.editor.isEditing = () => false;
|
||||||
|
|
||||||
|
const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
|
||||||
|
gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
|
||||||
|
|
||||||
|
testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
openmct.objects.addProvider('test-namespace', testObjectProvider);
|
||||||
|
testObjectProvider.observe.and.returnValue(() => {});
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(true));
|
||||||
|
testObjectProvider.update.and.returnValue(Promise.resolve(true));
|
||||||
|
|
||||||
|
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||||
|
mutablegaugeObject = mutableObject;
|
||||||
|
|
||||||
|
gaugeView = gaugeViewProvider.view(mutablegaugeObject);
|
||||||
|
gaugeView.show(child);
|
||||||
|
|
||||||
|
return Vue.nextTick();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
gaugeView.destroy();
|
||||||
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides gauge view', () => {
|
||||||
|
expect(gaugeViewProvider).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders gauge element', () => {
|
||||||
|
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||||
|
expect(gaugeElement.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders major elements', () => {
|
||||||
|
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||||
|
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||||
|
const curveElement = gaugeHolder.querySelector('.c-meter');
|
||||||
|
const dialElement = gaugeHolder.querySelector('.c-meter__bg');
|
||||||
|
|
||||||
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||||
|
|
||||||
|
expect(hasMajorElements).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Gaugue with Filled Dial with Use Telemetry Limits', () => {
|
||||||
|
let gaugeViewProvider;
|
||||||
|
let gaugeView;
|
||||||
|
let gaugeViewObject;
|
||||||
|
let mutablegaugeObject;
|
||||||
|
let randomValue;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
randomValue = Math.random();
|
||||||
|
|
||||||
|
gaugeViewObject = {
|
||||||
|
...gaugeDomainObject,
|
||||||
|
configuration: {
|
||||||
|
gaugeController: {
|
||||||
|
gaugeType: 'dial-filled',
|
||||||
|
isDisplayMinMax: true,
|
||||||
|
isDisplayCurVal: true,
|
||||||
|
isUseTelemetryLimits: true,
|
||||||
|
limitLow: 10,
|
||||||
|
limitHigh: 90,
|
||||||
|
max: 100,
|
||||||
|
min: 0,
|
||||||
|
precision: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
composition: [
|
||||||
|
{
|
||||||
|
namespace: 'test-namespace',
|
||||||
|
key: 'test-object'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
id: 'test-object',
|
||||||
|
name: 'gauge'
|
||||||
|
};
|
||||||
|
|
||||||
|
const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
|
||||||
|
'get',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'observe'
|
||||||
|
]);
|
||||||
|
|
||||||
|
openmct.editor = {};
|
||||||
|
openmct.editor.isEditing = () => false;
|
||||||
|
|
||||||
|
const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
|
||||||
|
gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
|
||||||
|
|
||||||
|
testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
|
||||||
|
openmct.objects.addProvider('test-namespace', testObjectProvider);
|
||||||
|
testObjectProvider.observe.and.returnValue(() => {});
|
||||||
|
testObjectProvider.create.and.returnValue(Promise.resolve(true));
|
||||||
|
testObjectProvider.update.and.returnValue(Promise.resolve(true));
|
||||||
|
|
||||||
|
spyOn(openmct.telemetry, 'getMetadata').and.returnValue({
|
||||||
|
valuesForHints: () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: 'sin'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
value: () => 1
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getValueFormatter').and.returnValue({
|
||||||
|
parse: () => {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getFormatMap').and.returnValue({
|
||||||
|
sin: {
|
||||||
|
format: (datum) => {
|
||||||
|
return randomValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spyOn(openmct.telemetry, 'getLimits').and.returnValue(
|
||||||
|
{
|
||||||
|
limits: () => Promise.resolve({
|
||||||
|
CRITICAL: {
|
||||||
|
high: 0.99,
|
||||||
|
low: -0.99
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
|
||||||
|
spyOn(openmct.time, 'bounds').and.returnValue({
|
||||||
|
start: 1000,
|
||||||
|
end: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
|
||||||
|
mutablegaugeObject = mutableObject;
|
||||||
|
gaugeView = gaugeViewProvider.view(mutablegaugeObject);
|
||||||
|
gaugeView.show(child);
|
||||||
|
|
||||||
|
return Vue.nextTick();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
gaugeView.destroy();
|
||||||
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides gauge view', () => {
|
||||||
|
expect(gaugeViewProvider).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders gauge element', () => {
|
||||||
|
const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
|
||||||
|
expect(gaugeElement.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders major elements', () => {
|
||||||
|
const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
|
||||||
|
const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
|
||||||
|
const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
|
||||||
|
const dialElement = gaugeHolder.querySelector('.c-dial');
|
||||||
|
|
||||||
|
const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
|
||||||
|
|
||||||
|
expect(hasMajorElements).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correct min max values', () => {
|
||||||
|
expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${gaugeViewObject.configuration.gaugeController.min} ${gaugeViewObject.configuration.gaugeController.max}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correct current value', (done) => {
|
||||||
|
function WatchUpdateValue() {
|
||||||
|
const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
|
||||||
|
expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedWatchUpdate = debounce(WatchUpdateValue, 200);
|
||||||
|
Vue.nextTick(debouncedWatchUpdate);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
65
src/plugins/gauge/GaugeViewProvider.js
Normal file
65
src/plugins/gauge/GaugeViewProvider.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import GaugeComponent from './components/Gauge.vue';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default function GaugeViewProvider(openmct) {
|
||||||
|
return {
|
||||||
|
key: 'gauge',
|
||||||
|
name: 'Gauge',
|
||||||
|
cssClass: 'icon-gauge',
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return domainObject.type === 'gauge';
|
||||||
|
},
|
||||||
|
canEdit: function (domainObject) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = new Vue({
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
GaugeComponent
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject,
|
||||||
|
composition: openmct.composition.get(domainObject)
|
||||||
|
},
|
||||||
|
template: '<gauge-component></gauge-component>'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: function (element) {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
467
src/plugins/gauge/components/Gauge.vue
Normal file
467
src/plugins/gauge/components/Gauge.vue
Normal file
@ -0,0 +1,467 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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-gauge"
|
||||||
|
:class="`c-gauge--${gaugeType}`"
|
||||||
|
>
|
||||||
|
<div class="c-gauge__wrapper">
|
||||||
|
<template v-if="typeDial">
|
||||||
|
<svg
|
||||||
|
class="c-gauge__range"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
v-if="displayMinMax"
|
||||||
|
font-size="35"
|
||||||
|
transform="translate(105 455) rotate(-45)"
|
||||||
|
>{{ rangeLow }}</text>
|
||||||
|
<text
|
||||||
|
v-if="displayMinMax"
|
||||||
|
font-size="35"
|
||||||
|
transform="translate(407 455) rotate(45)"
|
||||||
|
text-anchor="end"
|
||||||
|
>{{ rangeHigh }}</text>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
v-if="displayCurVal"
|
||||||
|
class="c-gauge__curval"
|
||||||
|
:viewBox="curValViewBox"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
class="c-gauge__curval-text"
|
||||||
|
lengthAdjust="spacing"
|
||||||
|
text-anchor="middle"
|
||||||
|
>{{ curVal }}</text>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div class="c-dial">
|
||||||
|
<svg
|
||||||
|
class="c-dial__bg"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
>
|
||||||
|
<path d="M256,0C114.6,0,0,114.6,0,256S114.6,512,256,512,512,397.4,512,256,397.4,0,256,0Zm0,412A156,156,0,1,1,412,256,155.9,155.9,0,0,1,256,412Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
v-if="limitHigh && dialHighLimitDeg < 270"
|
||||||
|
class="c-dial__limit-high"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
:class="{
|
||||||
|
'c-high-limit-clip--90': dialHighLimitDeg > 90,
|
||||||
|
'c-high-limit-clip--180': dialHighLimitDeg >= 180
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M100,256A156,156,0,1,1,366.3,366.3L437,437a255.2,255.2,0,0,0,75-181C512,114.6,397.4,0,256,0S0,114.6,0,256A255.2,255.2,0,0,0,75,437l70.7-70.7A155.5,155.5,0,0,1,100,256Z"
|
||||||
|
:style="`transform: rotate(${dialHighLimitDeg}deg)`"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
v-if="limitLow && dialLowLimitDeg < 270"
|
||||||
|
class="c-dial__limit-low"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
:class="{
|
||||||
|
'c-dial-clip--90': dialLowLimitDeg < 90,
|
||||||
|
'c-dial-clip--180': dialLowLimitDeg >= 90 && dialLowLimitDeg < 180
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M256,100c86.2,0,156,69.8,156,156s-69.8,156-156,156c-43.1,0-82.1-17.5-110.3-45.7L75,437 c46.3,46.3,110.3,75,181,75c141.4,0,256-114.6,256-256S397.4,0,256,0C185.3,0,121.3,28.7,75,75l70.7,70.7 C173.9,117.5,212.9,100,256,100z"
|
||||||
|
:style="`transform: rotate(${dialLowLimitDeg}deg)`"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class="c-dial__value"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
:class="{
|
||||||
|
'c-dial-clip--90': degValue < 90 && typeFilledDial,
|
||||||
|
'c-dial-clip--180': degValue >= 90 && degValue < 180 && typeFilledDial
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
v-if="typeFilledDial && degValue > 0"
|
||||||
|
d="M256,31A224.3,224.3,0,0,0,98.3,95.5l48.4,49.2a156,156,0,1,1-1,221.6L96.9,415.1A224.4,224.4,0,0,0,256,481c124.3,0,225-100.7,225-225S380.3,31,256,31Z"
|
||||||
|
:style="`transform: rotate(${degValue}deg)`"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
v-if="typeNeedleDial && valueInBounds"
|
||||||
|
d="M256,86c-93.9,0-170,76.1-170,170c0,43.9,16.6,83.9,43.9,114.1l-38.7,38.7c-3.3,3.3-3.3,8.7,0,12s8.7,3.3,12,0 l38.7-38.7C172.1,409.4,212.1,426,256,426c93.9,0,170-76.1,170-170S349.9,86,256,86z M256,411.7c-86,0-155.7-69.7-155.7-155.7 S170,100.3,256,100.3S411.7,170,411.7,256S342,411.7,256,411.7z"
|
||||||
|
:style="`transform: rotate(${degValue}deg)`"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="typeMeter">
|
||||||
|
<div class="c-meter">
|
||||||
|
<div
|
||||||
|
v-if="displayMinMax"
|
||||||
|
class="c-gauge__range c-meter__range"
|
||||||
|
>
|
||||||
|
<div class="c-meter__range__high">{{ rangeHigh }}</div>
|
||||||
|
<div class="c-meter__range__low">{{ rangeLow }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-meter__bg">
|
||||||
|
<template v-if="typeMeterVertical">
|
||||||
|
<div
|
||||||
|
class="c-meter__value"
|
||||||
|
:style="`transform: translateY(${meterValueToPerc}%)`"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="limitHigh && meterHighLimitPerc > 0"
|
||||||
|
class="c-meter__limit-high"
|
||||||
|
:style="`height: ${meterHighLimitPerc}%`"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="limitLow && meterLowLimitPerc > 0"
|
||||||
|
class="c-meter__limit-low"
|
||||||
|
:style="`height: ${meterLowLimitPerc}%`"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="typeMeterHorizontal">
|
||||||
|
<div
|
||||||
|
class="c-meter__value"
|
||||||
|
:style="`transform: translateX(${meterValueToPerc * -1}%)`"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="limitHigh && meterHighLimitPerc > 0"
|
||||||
|
class="c-meter__limit-high"
|
||||||
|
:style="`width: ${meterHighLimitPerc}%`"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="limitLow && meterLowLimitPerc > 0"
|
||||||
|
class="c-meter__limit-low"
|
||||||
|
:style="`width: ${meterLowLimitPerc}%`"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
v-if="displayCurVal"
|
||||||
|
class="c-gauge__curval"
|
||||||
|
:viewBox="curValViewBox"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
class="c-gauge__curval-text"
|
||||||
|
text-anchor="middle"
|
||||||
|
lengthAdjust="spacing"
|
||||||
|
>{{ curVal }}</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const LIMIT_PADDING_IN_PERCENT = 10;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Gauge',
|
||||||
|
inject: ['openmct', 'domainObject', 'composition'],
|
||||||
|
data() {
|
||||||
|
let gaugeController = this.domainObject.configuration.gaugeController;
|
||||||
|
|
||||||
|
return {
|
||||||
|
curVal: 0,
|
||||||
|
digits: 3,
|
||||||
|
precision: gaugeController.precision,
|
||||||
|
displayMinMax: gaugeController.isDisplayMinMax,
|
||||||
|
displayCurVal: gaugeController.isDisplayCurVal,
|
||||||
|
limitHigh: gaugeController.limitHigh,
|
||||||
|
limitLow: gaugeController.limitLow,
|
||||||
|
rangeHigh: gaugeController.max,
|
||||||
|
rangeLow: gaugeController.min,
|
||||||
|
gaugeType: gaugeController.gaugeType,
|
||||||
|
activeTimeSystem: this.openmct.time.timeSystem()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
degValue() {
|
||||||
|
return this.percentToDegrees(this.valToPercent(this.curVal));
|
||||||
|
},
|
||||||
|
dialHighLimitDeg() {
|
||||||
|
return this.percentToDegrees(this.valToPercent(this.limitHigh));
|
||||||
|
},
|
||||||
|
dialLowLimitDeg() {
|
||||||
|
return this.percentToDegrees(this.valToPercent(this.limitLow));
|
||||||
|
},
|
||||||
|
curValViewBox() {
|
||||||
|
const DIGITS_RATIO = 10;
|
||||||
|
const VIEWBOX_STR = '0 0 X 15';
|
||||||
|
|
||||||
|
return VIEWBOX_STR.replace('X', this.digits * DIGITS_RATIO);
|
||||||
|
},
|
||||||
|
typeDial() {
|
||||||
|
return this.matchGaugeType('dial');
|
||||||
|
},
|
||||||
|
typeFilledDial() {
|
||||||
|
return this.matchGaugeType('dial-filled');
|
||||||
|
},
|
||||||
|
typeNeedleDial() {
|
||||||
|
return this.matchGaugeType('dial-needle');
|
||||||
|
},
|
||||||
|
typeMeter() {
|
||||||
|
return this.matchGaugeType('meter');
|
||||||
|
},
|
||||||
|
typeMeterHorizontal() {
|
||||||
|
return this.matchGaugeType('horizontal');
|
||||||
|
},
|
||||||
|
typeMeterVertical() {
|
||||||
|
return this.matchGaugeType('vertical');
|
||||||
|
},
|
||||||
|
typeMeterInverted() {
|
||||||
|
return this.matchGaugeType('inverted');
|
||||||
|
},
|
||||||
|
meterValueToPerc() {
|
||||||
|
const meterDirection = (this.typeMeterInverted) ? -1 : 1;
|
||||||
|
|
||||||
|
if (this.curVal <= this.rangeLow) {
|
||||||
|
return meterDirection * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.curVal >= this.rangeHigh) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.valToPercentMeter(this.curVal) * meterDirection;
|
||||||
|
},
|
||||||
|
meterHighLimitPerc() {
|
||||||
|
return this.valToPercentMeter(this.limitHigh);
|
||||||
|
},
|
||||||
|
meterLowLimitPerc() {
|
||||||
|
return 100 - this.valToPercentMeter(this.limitLow);
|
||||||
|
},
|
||||||
|
valueInBounds() {
|
||||||
|
return (this.curVal >= this.rangeLow && this.curVal <= this.rangeHigh);
|
||||||
|
},
|
||||||
|
timeFormatter() {
|
||||||
|
const timeSystem = this.activeTimeSystem;
|
||||||
|
const metadataValue = this.metadata.value(timeSystem.key) || { format: timeSystem.key };
|
||||||
|
|
||||||
|
return this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
curVal(newCurValue) {
|
||||||
|
if (this.digits < newCurValue.toString().length) {
|
||||||
|
this.digits = newCurValue.toString().length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.composition.on('add', this.addedToComposition);
|
||||||
|
this.composition.on('remove', this.removeTelemetryObject);
|
||||||
|
|
||||||
|
this.composition.load();
|
||||||
|
|
||||||
|
this.openmct.time.on('bounds', this.refreshData);
|
||||||
|
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.composition.off('add', this.addedToComposition);
|
||||||
|
this.composition.off('remove', this.removeTelemetryObject);
|
||||||
|
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.time.off('bounds', this.refreshData);
|
||||||
|
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addTelemetryObjectAndSubscribe(domainObject) {
|
||||||
|
this.telemetryObject = domainObject;
|
||||||
|
this.request();
|
||||||
|
this.subscribe();
|
||||||
|
},
|
||||||
|
addedToComposition(domainObject) {
|
||||||
|
if (this.telemetryObject) {
|
||||||
|
this.confirmRemoval(domainObject);
|
||||||
|
} else {
|
||||||
|
this.addTelemetryObjectAndSubscribe(domainObject);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmRemoval(domainObject) {
|
||||||
|
const dialog = this.openmct.overlays.dialog({
|
||||||
|
iconClass: 'alert',
|
||||||
|
message: 'This action will replace the current telemetry source. Do you want to continue?',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: 'Ok',
|
||||||
|
emphasis: true,
|
||||||
|
callback: () => {
|
||||||
|
this.removeFromComposition();
|
||||||
|
this.removeTelemetryObject();
|
||||||
|
this.addTelemetryObjectAndSubscribe(domainObject);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cancel',
|
||||||
|
callback: () => {
|
||||||
|
this.removeFromComposition(domainObject);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
},
|
||||||
|
matchGaugeType(str) {
|
||||||
|
return this.gaugeType.indexOf(str) !== -1;
|
||||||
|
},
|
||||||
|
percentToDegrees(vPercent) {
|
||||||
|
return this.round((vPercent / 100) * 270, 2);
|
||||||
|
},
|
||||||
|
removeFromComposition(telemetryObject = this.telemetryObject) {
|
||||||
|
let composition = this.domainObject.composition.filter(id =>
|
||||||
|
!this.openmct.objects.areIdsEqual(id, telemetryObject.identifier)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.openmct.objects.mutate(this.domainObject, 'composition', composition);
|
||||||
|
},
|
||||||
|
refreshData(bounds, isTick) {
|
||||||
|
if (!isTick) {
|
||||||
|
this.request();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeTelemetryObject() {
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
this.unsubscribe = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.metadata = null;
|
||||||
|
this.formats = null;
|
||||||
|
this.valueKey = null;
|
||||||
|
this.limitHigh = null;
|
||||||
|
this.limitLow = null;
|
||||||
|
this.rangeHigh = null;
|
||||||
|
this.rangeLow = null;
|
||||||
|
},
|
||||||
|
request(domainObject = this.telemetryObject) {
|
||||||
|
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||||
|
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||||
|
const LimitEvaluator = this.openmct.telemetry.getLimits(domainObject);
|
||||||
|
LimitEvaluator.limits().then(this.updateLimits);
|
||||||
|
|
||||||
|
this.valueKey = this
|
||||||
|
.metadata
|
||||||
|
.valuesForHints(['range'])[0].source;
|
||||||
|
|
||||||
|
this.openmct
|
||||||
|
.telemetry
|
||||||
|
.request(domainObject, { strategy: 'latest' })
|
||||||
|
.then(values => {
|
||||||
|
const length = values.length;
|
||||||
|
this.updateValue(values[length - 1]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
round(val, decimals = this.precision) {
|
||||||
|
let precision = Math.pow(10, decimals);
|
||||||
|
|
||||||
|
return Math.round(val * precision) / precision;
|
||||||
|
},
|
||||||
|
setTimeSystem(timeSystem) {
|
||||||
|
this.activeTimeSystem = timeSystem;
|
||||||
|
},
|
||||||
|
subscribe(domainObject = this.telemetryObject) {
|
||||||
|
this.unsubscribe = this.openmct
|
||||||
|
.telemetry
|
||||||
|
.subscribe(domainObject, this.updateValue.bind(this));
|
||||||
|
},
|
||||||
|
updateLimits(telemetryLimit) {
|
||||||
|
if (!telemetryLimit || !this.domainObject.configuration.gaugeController.isUseTelemetryLimits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let limits = {
|
||||||
|
high: 0,
|
||||||
|
low: 0
|
||||||
|
};
|
||||||
|
if (telemetryLimit.CRITICAL) {
|
||||||
|
limits = telemetryLimit.CRITICAL;
|
||||||
|
} else if (telemetryLimit.DISTRESS) {
|
||||||
|
limits = telemetryLimit.DISTRESS;
|
||||||
|
} else if (telemetryLimit.SEVERE) {
|
||||||
|
limits = telemetryLimit.SEVERE;
|
||||||
|
} else if (telemetryLimit.WARNING) {
|
||||||
|
limits = telemetryLimit.WARNING;
|
||||||
|
} else if (telemetryLimit.WATCH) {
|
||||||
|
limits = telemetryLimit.WATCH;
|
||||||
|
} else {
|
||||||
|
this.openmct.notifications.error('No limits definition for given telemetry');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.limitHigh = this.round(limits.high[this.valueKey]);
|
||||||
|
this.limitLow = this.round(limits.low[this.valueKey]);
|
||||||
|
this.rangeHigh = this.round(this.limitHigh + this.limitHigh * LIMIT_PADDING_IN_PERCENT / 100);
|
||||||
|
this.rangeLow = this.round(this.limitLow - Math.abs(this.limitLow * LIMIT_PADDING_IN_PERCENT / 100));
|
||||||
|
},
|
||||||
|
updateValue(datum) {
|
||||||
|
this.datum = datum;
|
||||||
|
|
||||||
|
if (this.isRendering) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { start, end } = this.openmct.time.bounds();
|
||||||
|
const parsedValue = this.timeFormatter.parse(this.datum);
|
||||||
|
|
||||||
|
const beforeStartOfBounds = parsedValue < start;
|
||||||
|
const afterEndOfBounds = parsedValue > end;
|
||||||
|
if (afterEndOfBounds || beforeStartOfBounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isRendering = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.isRendering = false;
|
||||||
|
|
||||||
|
this.curVal = this.round(this.formats[this.valueKey].format(this.datum), this.precision);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
valToPercent(vValue) {
|
||||||
|
// Used by dial
|
||||||
|
if (vValue >= this.rangeHigh && this.typeFilledDial) {
|
||||||
|
// Don't peg at 100% if the gaugeType isn't a filled shape
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((vValue - this.rangeLow) / (this.rangeHigh - this.rangeLow)) * 100;
|
||||||
|
},
|
||||||
|
valToPercentMeter(vValue) {
|
||||||
|
return this.round((this.rangeHigh - vValue) / (this.rangeHigh - this.rangeLow) * 100, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
170
src/plugins/gauge/components/GaugeFormController.vue
Normal file
170
src/plugins/gauge/components/GaugeFormController.vue
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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>
|
||||||
|
<span class="form-control">
|
||||||
|
<span
|
||||||
|
class="field control"
|
||||||
|
:class="model.cssClass"
|
||||||
|
>
|
||||||
|
<ToggleSwitch
|
||||||
|
:checked="isUseTelemetryLimits"
|
||||||
|
label="Use telemetry limits for minimum and maximum ranges"
|
||||||
|
@change="toggleUseTelemetryLimits"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="!isUseTelemetryLimits"
|
||||||
|
class="c-form--sub-grid"
|
||||||
|
>
|
||||||
|
<div class="c-form__row">
|
||||||
|
<span class="req-indicator req">
|
||||||
|
</span>
|
||||||
|
<label>Range minimum value</label>
|
||||||
|
<input
|
||||||
|
ref="min"
|
||||||
|
v-model.number="min"
|
||||||
|
data-field-name="min"
|
||||||
|
type="number"
|
||||||
|
@input="onChange"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="c-form__row">
|
||||||
|
<span class="req-indicator">
|
||||||
|
</span>
|
||||||
|
<label>Range low limit</label>
|
||||||
|
<input
|
||||||
|
ref="limitLow"
|
||||||
|
v-model.number="limitLow"
|
||||||
|
data-field-name="limitLow"
|
||||||
|
type="number"
|
||||||
|
@input="onChange"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="c-form__row">
|
||||||
|
<span class="req-indicator req">
|
||||||
|
</span>
|
||||||
|
<label>Range maximum value</label>
|
||||||
|
<input
|
||||||
|
ref="max"
|
||||||
|
v-model.number="max"
|
||||||
|
data-field-name="max"
|
||||||
|
type="number"
|
||||||
|
@input="onChange"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="c-form__row">
|
||||||
|
<span class="req-indicator">
|
||||||
|
</span>
|
||||||
|
<label>Range high limit</label>
|
||||||
|
<input
|
||||||
|
ref="limitHigh"
|
||||||
|
v-model.number="limitHigh"
|
||||||
|
data-field-name="limitHigh"
|
||||||
|
type="number"
|
||||||
|
@input="onChange"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ToggleSwitch from '@/ui/components/ToggleSwitch.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ToggleSwitch
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isUseTelemetryLimits: this.model.value.isUseTelemetryLimits,
|
||||||
|
isDisplayMinMax: this.model.value.isDisplayMinMax,
|
||||||
|
isDisplayCurVal: this.model.value.isDisplayCurVal,
|
||||||
|
limitHigh: this.model.value.limitHigh,
|
||||||
|
limitLow: this.model.value.limitLow,
|
||||||
|
max: this.model.value.max,
|
||||||
|
min: this.model.value.min
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onChange(event) {
|
||||||
|
const data = {
|
||||||
|
model: this.model,
|
||||||
|
value: {
|
||||||
|
gaugeType: this.model.value.gaugeType,
|
||||||
|
isDisplayMinMax: this.isDisplayMinMax,
|
||||||
|
isDisplayCurVal: this.isDisplayCurVal,
|
||||||
|
isUseTelemetryLimits: this.isUseTelemetryLimits,
|
||||||
|
limitLow: this.limitLow,
|
||||||
|
limitHigh: this.limitHigh,
|
||||||
|
max: this.max,
|
||||||
|
min: this.min,
|
||||||
|
precision: this.model.value.precision
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
const target = event.target;
|
||||||
|
const targetIndicator = target.parentElement.querySelector('.req-indicator');
|
||||||
|
if (targetIndicator.classList.contains('req')) {
|
||||||
|
targetIndicator.classList.add('visited');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.model.validate(data, (valid) => {
|
||||||
|
Object.entries(valid).forEach(([key, isValid]) => {
|
||||||
|
const element = this.$refs[key];
|
||||||
|
const reqIndicatorElement = element.parentElement.querySelector('.req-indicator');
|
||||||
|
reqIndicatorElement.classList.toggle('invalid', !isValid);
|
||||||
|
|
||||||
|
if (reqIndicatorElement.classList.contains('req') && (!isValid || reqIndicatorElement.classList.contains('visited'))) {
|
||||||
|
reqIndicatorElement.classList.toggle('valid', isValid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('onChange', data);
|
||||||
|
},
|
||||||
|
toggleUseTelemetryLimits() {
|
||||||
|
this.isUseTelemetryLimits = !this.isUseTelemetryLimits;
|
||||||
|
|
||||||
|
this.onChange();
|
||||||
|
},
|
||||||
|
toggleMinMax() {
|
||||||
|
this.isDisplayMinMax = !this.isDisplayMinMax;
|
||||||
|
|
||||||
|
this.onChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
269
src/plugins/gauge/gauge.scss
Normal file
269
src/plugins/gauge/gauge.scss
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
$dialClip: polygon(0 0, 100% 0, 100% 100%, 50% 50%, 0 100%);
|
||||||
|
$dialClip90: polygon(0 0, 50% 50%, 0 100%);
|
||||||
|
$dialClip180: polygon(0 0, 100% 0, 0 100%);
|
||||||
|
$limitHighClip90: polygon(0 0, 100% 0, 100% 100%);
|
||||||
|
$limitHighClip180: polygon(100% 0, 100% 100%, 0 100%);
|
||||||
|
|
||||||
|
.is-object-type-gauge {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.req-indicator {
|
||||||
|
width: 20px;
|
||||||
|
|
||||||
|
&.invalid,
|
||||||
|
&.invalid.req { @include validationState($glyph-icon-x, $colorFormInvalid); }
|
||||||
|
|
||||||
|
&.valid,
|
||||||
|
&.valid.req { @include validationState($glyph-icon-check, $colorFormValid); }
|
||||||
|
|
||||||
|
&.req { @include validationState($glyph-icon-asterisk, $colorFormRequired); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-gauge {
|
||||||
|
// Both dial and meter types
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__range {
|
||||||
|
$c: $colorGaugeRange;
|
||||||
|
color: $c;
|
||||||
|
|
||||||
|
text {
|
||||||
|
fill: $c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
@include abs();
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
path {
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.c-gauge__curval {
|
||||||
|
@include abs();
|
||||||
|
fill: $colorGaugeTextValue;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.c-gauge__curval-text {
|
||||||
|
font-family: $heroFont;
|
||||||
|
transform: translate(50%, 75%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='dial'] {
|
||||||
|
// Square aspect ratio
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='meter'] {
|
||||||
|
@include abs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************************** DIAL GAUGE */
|
||||||
|
.c-dial {
|
||||||
|
// Dial elements
|
||||||
|
@include abs();
|
||||||
|
clip-path: $dialClip;
|
||||||
|
|
||||||
|
svg,
|
||||||
|
&__ticks,
|
||||||
|
&__bg,
|
||||||
|
&[class*='__limit'],
|
||||||
|
&__value {
|
||||||
|
@include abs();
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-high-limit-clip--90 {
|
||||||
|
clip-path: $limitHighClip90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-high-limit-clip--180 {
|
||||||
|
clip-path: $limitHighClip180;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__limit-high path { fill: $colorGaugeLimitHigh; }
|
||||||
|
&__limit-low path { fill: $colorGaugeLimitLow; }
|
||||||
|
|
||||||
|
&__value,
|
||||||
|
&__limit-low {
|
||||||
|
&.c-dial-clip--90 {
|
||||||
|
clip-path: $dialClip90;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.c-dial-clip--180 {
|
||||||
|
clip-path: $dialClip180;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
path,
|
||||||
|
polygon {
|
||||||
|
fill: $colorGaugeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bg {
|
||||||
|
path {
|
||||||
|
fill: $colorGaugeBg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-gauge--dial-needle .c-dial__value {
|
||||||
|
path {
|
||||||
|
transition: transform $transitionTimeGauge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************************** METER GAUGE */
|
||||||
|
.c-meter {
|
||||||
|
// Common styles for c-meter
|
||||||
|
@include abs();
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&__range {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bg {
|
||||||
|
background: $colorGaugeBg;
|
||||||
|
border-radius: $basicCr;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
// Filled area
|
||||||
|
position: absolute;
|
||||||
|
background: $colorGaugeValue;
|
||||||
|
transition: transform $transitionTimeGauge;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-gauge__curval {
|
||||||
|
fill: $colorGaugeMeterTextValue !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='limit'] {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__limit-high {
|
||||||
|
background: $colorGaugeLimitHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__limit-low {
|
||||||
|
background: $colorGaugeLimitLow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-meter {
|
||||||
|
.c-gauge--meter-vertical &,
|
||||||
|
.c-gauge--meter-vertical-inverted & {
|
||||||
|
&__range {
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: min-content;
|
||||||
|
margin-right: $interiorMarginSm;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
// Filled area
|
||||||
|
$lrM: $marginGaugeMeterValue;
|
||||||
|
left: $lrM;
|
||||||
|
right: $lrM;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='limit'] {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-gauge--meter-vertical & {
|
||||||
|
&__limit-low {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__limit-high {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-gauge--meter-vertical-inverted & {
|
||||||
|
&__limit-low {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__limit-high {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__range__low {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__range__high {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-gauge--meter-horizontal & {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&__range {
|
||||||
|
flex-direction: row;
|
||||||
|
min-height: min-content;
|
||||||
|
margin-top: $interiorMarginSm;
|
||||||
|
order: 2;
|
||||||
|
|
||||||
|
&__high {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__low {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bg {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
// Filled area
|
||||||
|
$m: $marginGaugeMeterValue;
|
||||||
|
top: $m;
|
||||||
|
bottom: $m;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='limit'] {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__limit-low {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__limit-high {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ $elemBg: rgba(black, 0.7);
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1;
|
z-index: 2;
|
||||||
@include userSelectNone;
|
@include userSelectNone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@
|
|||||||
.h-local-controls--overlay-content {
|
.h-local-controls--overlay-content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: $interiorMargin; top: $interiorMargin;
|
left: $interiorMargin; top: $interiorMargin;
|
||||||
z-index: 2;
|
z-index: 70;
|
||||||
background: $colorLocalControlOvrBg;
|
background: $colorLocalControlOvrBg;
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
|
@ -77,6 +77,7 @@ define([
|
|||||||
'./userIndicator/plugin',
|
'./userIndicator/plugin',
|
||||||
'../../example/exampleUser/plugin',
|
'../../example/exampleUser/plugin',
|
||||||
'./localStorage/plugin',
|
'./localStorage/plugin',
|
||||||
|
'./gauge/GaugePlugin',
|
||||||
'./timelist/plugin'
|
'./timelist/plugin'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
@ -135,6 +136,7 @@ define([
|
|||||||
UserIndicator,
|
UserIndicator,
|
||||||
ExampleUser,
|
ExampleUser,
|
||||||
LocalStorage,
|
LocalStorage,
|
||||||
|
GaugePlugin,
|
||||||
TimeList
|
TimeList
|
||||||
) {
|
) {
|
||||||
const plugins = {};
|
const plugins = {};
|
||||||
@ -212,6 +214,7 @@ define([
|
|||||||
plugins.DeviceClassifier = DeviceClassifier.default;
|
plugins.DeviceClassifier = DeviceClassifier.default;
|
||||||
plugins.UserIndicator = UserIndicator.default;
|
plugins.UserIndicator = UserIndicator.default;
|
||||||
plugins.LocalStorage = LocalStorage.default;
|
plugins.LocalStorage = LocalStorage.default;
|
||||||
|
plugins.Gauge = GaugePlugin.default;
|
||||||
plugins.Timelist = TimeList.default;
|
plugins.Timelist = TimeList.default;
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
|
@ -366,6 +366,16 @@ $legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
|||||||
$legendTableHeadBg: $colorTabHeaderBg;
|
$legendTableHeadBg: $colorTabHeaderBg;
|
||||||
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.2);
|
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.2);
|
||||||
|
|
||||||
|
// Gauges
|
||||||
|
$colorGaugeBg: pullForward($colorBodyBg, 5%); // Gauge radial area background, meter background
|
||||||
|
$colorGaugeValue: rgba(#fff, 0.3); // Gauge value graphic (radial sweep, bar) color
|
||||||
|
$colorGaugeTextValue: #fff; // Radial gauge text value
|
||||||
|
$colorGaugeMeterTextValue: $colorGaugeTextValue; // Meter text value, overlaid on value bar
|
||||||
|
$colorGaugeRange: $colorBodyFg; // Range text color
|
||||||
|
$colorGaugeLimitHigh: rgba($colorLimitRedBg, 0.4);
|
||||||
|
$colorGaugeLimitLow: $colorGaugeLimitHigh;
|
||||||
|
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
|
||||||
|
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
|
||||||
// Time Strip and Lists
|
// Time Strip and Lists
|
||||||
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
|
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
|
||||||
$colorCurrentFg: pullForward($colorBodyFg, 20%);
|
$colorCurrentFg: pullForward($colorBodyFg, 20%);
|
||||||
|
@ -370,6 +370,16 @@ $legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
|||||||
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
|
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
|
||||||
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.2);
|
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.2);
|
||||||
|
|
||||||
|
// Gauges
|
||||||
|
$colorGaugeBg: pullForward($colorBodyBg, 5%); // Gauge radial area background, meter background
|
||||||
|
$colorGaugeValue: rgba(#fff, 0.3); // Gauge value graphic (radial sweep, bar) color
|
||||||
|
$colorGaugeTextValue: #fff; // Radial gauge text value
|
||||||
|
$colorGaugeMeterTextValue: $colorGaugeTextValue; // Meter text value, overlaid on value bar
|
||||||
|
$colorGaugeRange: $colorBodyFg; // Range text color
|
||||||
|
$colorGaugeLimitHigh: rgba($colorLimitRedBg, 0.4);
|
||||||
|
$colorGaugeLimitLow: $colorGaugeLimitHigh;
|
||||||
|
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
|
||||||
|
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
|
||||||
// Time Strip and Lists
|
// Time Strip and Lists
|
||||||
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
|
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
|
||||||
$colorCurrentFg: pullForward($colorBodyFg, 20%);
|
$colorCurrentFg: pullForward($colorBodyFg, 20%);
|
||||||
|
@ -366,6 +366,16 @@ $legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
|||||||
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
|
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
|
||||||
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.4);
|
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.4);
|
||||||
|
|
||||||
|
// Gauges
|
||||||
|
$colorGaugeBg: pullForward($colorBodyBg, 20%); // Gauge radial area background, meter background
|
||||||
|
$colorGaugeValue: rgba(#000, 0.3); // Gauge value graphic (radial sweep, bar) color
|
||||||
|
$colorGaugeTextValue: pullForward($colorBodyFg, 20%); // Radial gauge text value
|
||||||
|
$colorGaugeMeterTextValue: $colorGaugeTextValue; // Meter text value, overlaid on value bar
|
||||||
|
$colorGaugeRange: $colorBodyFg; // Range text color
|
||||||
|
$colorGaugeLimitHigh: rgba($colorLimitRedBg, 0.2);
|
||||||
|
$colorGaugeLimitLow: $colorGaugeLimitHigh;
|
||||||
|
$transitionTimeGauge: 150ms; // CSS transition time used to smooth needle gauge and meter value transitions
|
||||||
|
$marginGaugeMeterValue: 10%; // Margin between meter value bar and bounds edges
|
||||||
// Time Strip and Lists
|
// Time Strip and Lists
|
||||||
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
|
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
|
||||||
$colorCurrentFg: pullForward($colorBodyFg, 20%);
|
$colorCurrentFg: pullForward($colorBodyFg, 20%);
|
||||||
|
@ -70,8 +70,24 @@
|
|||||||
padding: $formTBPad $formLRPad;
|
padding: $formTBPad $formLRPad;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--sub-grid {
|
||||||
|
// 3 columns: <req> <label> <input/control>
|
||||||
|
display: grid;
|
||||||
|
grid-column-gap: $interiorMargin;
|
||||||
|
grid-template-columns: 20px max-content 1fr;
|
||||||
|
grid-row-gap: $interiorMargin;
|
||||||
|
margin-top: $interiorMarginLg;
|
||||||
|
width: max-content;
|
||||||
|
|
||||||
|
.c-form__row {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.c-form-row {
|
.c-form-row {
|
||||||
align-items: start;
|
align-items: start;
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
@import "../ui/toolbar/components/toolbar-checkbox.scss";
|
@import "../ui/toolbar/components/toolbar-checkbox.scss";
|
||||||
@import "./notebook.scss";
|
@import "./notebook.scss";
|
||||||
@import "../plugins/notebook/components/sidebar.scss";
|
@import "../plugins/notebook/components/sidebar.scss";
|
||||||
|
@import "../plugins/gauge/gauge.scss";
|
||||||
|
|
||||||
#splash-screen {
|
#splash-screen {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -166,7 +166,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return definition.form
|
return definition.form
|
||||||
.map((field) => {
|
.filter(field => !field.hideFromInspector)
|
||||||
|
.map(field => {
|
||||||
let path = field.property;
|
let path = field.property;
|
||||||
if (typeof path === 'string') {
|
if (typeof path === 'string') {
|
||||||
path = [path];
|
path = [path];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user