Compare commits

..

72 Commits

Author SHA1 Message Date
Charles Hacskaylo
960db3c4fe Refactor Gauge for bar and needle display styles
- Separate gauge type (dial, meter, etc.) from graphic value display style (bar, needle);
- Gauge types reduced to 4;
- Add gaugeDisplayStyle property for bar and needle;
- SCSS cleanups for clarity and simplicity;
2022-04-18 13:56:55 -07:00
Charles Hacskaylo
a305017bee Refactor Gauge for bar and needle display styles
- Separate gauge type (dial, meter, etc.) from graphic value display style (bar, needle);
- Gauge types reduced to 4;
- Add gaugeDisplayStyle property for bar and needle;
- SCSS cleanups for clarity and simplicity;
2022-04-18 13:18:58 -07:00
Nikhil
c33feaa8ba Merge branch 'master' into add-gauge-4896 2022-04-11 10:24:28 -07:00
Nikhil Mandlik
3aac86541a add confirmation dialog on another telemetry added to gauge 2022-04-07 13:37:49 -07:00
Jamie V
21c3fcdc8a Merge branch 'master' into add-gauge-4896 2022-04-06 10:14:54 -07:00
Nikhil Mandlik
7dffefc17d Merge branch 'master' into add-gauge-4896 2022-04-05 20:19:02 -07:00
Nikhil Mandlik
56d8838d58 added toggle-check-box-mixin 2022-04-05 20:14:07 -07:00
Nikhil Mandlik
6800d8c4ee Merge branch 'add-gauge-4896' of github.com:nasa/openmct into add-gauge-4896 2022-04-05 19:39:40 -07:00
Nikhil Mandlik
a630880fe6 addressed review comments. 2022-04-05 19:39:25 -07:00
Jamie V
187be2e970 Merge branch 'master' into add-gauge-4896 2022-04-04 13:09:39 -07:00
Nikhil Mandlik
0db937febb added e2e doc 2022-04-01 12:25:02 -07:00
Jamie V
f428d5915a Merge branch 'master' into add-gauge-4896 2022-03-31 13:39:19 -07:00
Nikhil
94d5c23592 Merge branch 'master' into add-gauge-4896 2022-03-28 13:39:39 -07:00
Charles Hacskaylo
510952551b Merge branch 'imagery-layers' of github.com:nasa/openmct into imagery-layers 2022-03-24 16:16:44 -07:00
Nikhil Mandlik
d84a2b5c92 use source instead of key from range hints. 2022-03-23 22:13:03 -07:00
Nikhil Mandlik
fc8a270e03 changed return canedit to false 2022-03-23 22:12:33 -07:00
Nikhil Mandlik
ed0ed5ae65 fixed validation logic. 2022-03-23 22:02:50 -07:00
Nikhil Mandlik
f4e78cb4d4 Must disallow more than one telemetry point from being added to Gauge, use latest added telemetry point. 2022-03-23 21:00:28 -07:00
Nikhil Mandlik
a02cf9864a added hideFromInspector property to filter field from showing into inspector. 2022-03-23 19:55:06 -07:00
Nikhil
0dcb87cd5d Merge branch 'master' into add-gauge-4896 2022-03-23 14:11:47 -07:00
Jamie V
e30b58d939 Merge branch 'master' into add-gauge-4896 2022-03-22 20:06:26 -07:00
Charles Hacskaylo
17bd9eb923 Merge branch 'add-gauge-4896' of github.com:nasa/openmct into add-gauge-4896 2022-03-22 18:30:03 -07:00
Charles Hacskaylo
22a501b507 Refine Gauge
- Added `matchType` method and computed properties for gauge types;
2022-03-22 18:22:16 -07:00
John Hill
600f8559ce Merge branch 'master' into add-gauge-4896 2022-03-22 17:48:29 -07:00
Charles Hacskaylo
f198cbaae4 Merge branch 'add-gauge-4896' of github.com:nasa/openmct into add-gauge-4896 2022-03-22 16:32:01 -07:00
Charles Hacskaylo
205a80a3b0 Refine Gauge
- Moved embedded color settings into thematic constants, smoke tested in Espresso and Snow;
- Renamed 'gauge-horz' to 'gauge-horizontal';
2022-03-22 16:15:25 -07:00
Nikhil Mandlik
3304be0a70 addressed review comments. 2022-03-22 15:18:05 -07:00
Nikhil Mandlik
70b9d797c0 Fixed lint issues 2022-03-22 14:22:28 -07:00
Nikhil
5d6ae0ddbc Merge branch 'master' into add-gauge-4896 2022-03-22 13:46:46 -07:00
Jamie V
c467a3ef9e Merge branch 'master' into add-gauge-4896 2022-03-21 15:38:26 -07:00
Nikhil
2717af20e4 Merge branch 'master' into add-gauge-4896 2022-03-21 13:44:31 -07:00
Nikhil
e9b8dbcfe8 Merge branch 'master' into add-gauge-4896 2022-03-18 10:56:49 -07:00
Charles Hacskaylo
6919067c5e Merge branch 'add-gauge-4896' of github.com:nasa/openmct into add-gauge-4896 2022-03-18 09:31:02 -07:00
Charles Hacskaylo
5f68ef5632 Add new Gauge component
- Added toggle for displaying the current value;
2022-03-16 16:41:45 -07:00
Nikhil Mandlik
0c529d0697 fixed gauge issue with value update on bounds change . 2022-03-16 16:00:52 -07:00
Nikhil
c05812d7a7 Merge branch 'master' into add-gauge-4896 2022-03-16 15:06:54 -07:00
Charles Hacskaylo
e671df8064 Add new Gauge component
- CSS tweaks;
- Improved needle shape;
- SVG cleanup;
2022-03-16 14:58:32 -07:00
Nikhil Mandlik
9e071d14c4 added validation on limitLow and limitHigh. 2022-03-16 12:17:47 -07:00
Charles Hacskaylo
0424f010a4 Merge branch 'add-gauge-4896' of github.com:nasa/openmct into add-gauge-4896 2022-03-16 11:14:07 -07:00
Charles Hacskaylo
a5f906d569 Add new Gauge component
- Properties form sanding and polishing;
- Added new `c-form--sub-grid` CSS layout;
- Removed limitLow and limitHigh from validation eval in GaugePlugin.js;
2022-03-16 11:13:43 -07:00
Nikhil
e7f776f473 Merge branch 'master' into add-gauge-4896 2022-03-16 10:19:14 -07:00
Nikhil Mandlik
b99c606b3d Merge branch 'add-gauge-4896' of github.com:nasa/openmct into add-gauge-4896 2022-03-16 01:17:13 -07:00
Nikhil Mandlik
85478721b5 added validation to gauge controller 2022-03-16 01:16:00 -07:00
Nikhil Mandlik
44b517fa66 fixed form issue with validate function 2022-03-16 01:15:18 -07:00
Charles Hacskaylo
8f762d08c3 Add new Gauge component
- Added gauge type `meter-vertical-inverted`;
- CSS tightened up;
2022-03-15 23:23:17 -07:00
Nikhil Mandlik
5989cd622a added default values 2022-03-15 22:44:59 -07:00
Nikhil Mandlik
7ab4a80d21 current value should stop updating current time is out of bounds of time conductors start end values. 2022-03-15 22:44:41 -07:00
Charles Hacskaylo
2b7d626053 Add new Gauge component
- Properties form CSS cleanups and text tweaks;
2022-03-15 17:55:23 -07:00
Charles Hacskaylo
c1a4bda230 Add new Gauge component
- Color refinement;
- Code cleanup;
2022-03-15 16:49:07 -07:00
Charles Hacskaylo
445350e026 Add new Gauge component
- Added check to suppress display of dial needle;
- Moved filled dial display check in code;
- Code cleanup;
2022-03-15 16:05:18 -07:00
Nikhil Mandlik
8260d8c284 clean up 2022-03-14 11:51:55 -07:00
Charles Hacskaylo
ad47d0f62c Add new Gauge component
- Merging Nikhil's latest, resolve conflicts;
- TODO: clip dial display when values are outside set min/max;
2022-03-11 18:45:52 -08:00
Charles Hacskaylo
fb9c043fbf Add new Gauge component
- Current value implemented as size-relative SVG text in dial and meter;
- Code cleanups;
- TODO: cleanup and migrate colors to themes;
2022-03-11 18:34:57 -08:00
Nikhil Mandlik
063358226a implemented use telemetry limits. 2022-03-11 16:35:35 -08:00
Nikhil Mandlik
c5bc993870 renamed GaugePlugin and restructure GaugePlugin form. 2022-03-11 13:43:07 -08:00
Nikhil Mandlik
845f9e86df add toggleSwitch form control 2022-03-11 13:41:14 -08:00
Nikhil Mandlik
beb0ac8ddc add digits counter for main value size adjustment. 2022-03-11 12:43:46 -08:00
Charles Hacskaylo
80620d7705 Add new Gauge component
- Handle case when high or low limit isn't defined;
2022-03-11 11:39:03 -08:00
Charles Hacskaylo
6804ed5c8f Add new Gauge component
- WIP!
- Significant work: added lower limit to dial and meter;
- Added limitLow, refactored limit to limitHigh;
- Improved valToPercentMeter method;
2022-03-10 19:01:54 -08:00
Charles Hacskaylo
36eaee4a24 Add new Gauge component
- Remove rotation angle clipping from needle dial gauge;
- Reinstate transition for needle dial only;
- Fix typo in props form;
2022-03-09 22:56:15 -08:00
Charles Hacskaylo
84f527c1d3 Add new Gauge component
- WIP: turn off transition for dial to avoid delay "flash" problem;
2022-03-09 15:37:37 -08:00
Charles Hacskaylo
ec531cb08e Add new Gauge component
- Tweaks to properties form text;
- Enhanced GAUGE_TYPES const for better human-readable names;
2022-03-08 23:39:34 -08:00
Charles Hacskaylo
b3fabfefce Merge branch 'add-gauge-4896' of github.com:nasa/openmct into add-gauge-4896 2022-03-08 23:05:10 -08:00
Charles Hacskaylo
fd400650c7 Add new Gauge component
- Better approach to clipping high and low values/ranges in meter;
2022-03-08 23:04:59 -08:00
Nikhil Mandlik
92550af8c1 added gaugeType option 2022-03-08 22:41:55 -08:00
Charles Hacskaylo
e87eb3d6d7 Add new Gauge component
- WIP!
- Major additions of vertical and horizontal meter gauge types;
- Min and max range values now display based on properties settings;
- Better color definitions, moving towards theme-based color vars;
- Animation transition now applied to gauge value elements transforms;
2022-03-08 21:56:24 -08:00
Charles Hacskaylo
ce7df4c51a Add new Gauge component
- WIP!
- Added vertical and horizontal meter gauge types;
2022-03-08 17:20:46 -08:00
Charles Hacskaylo
db288d8c66 Add new Gauge component
- Added needle display style;
- Tweaked form wording;
2022-03-07 23:17:33 -08:00
Nikhil Mandlik
791ff2e21b rename filenames 2022-03-05 10:26:18 -08:00
Nikhil Mandlik
2e2bf9fd2b adjust radial font 2022-03-04 22:50:15 -08:00
Nikhil Mandlik
e0326cf3ea convert to ES6, added checkbox and GaugeFormController control types 2022-03-04 22:41:42 -08:00
Charles Hacskaylo
b6998b3efa Add new Gauge component
- Copied WIP from maelstrom2-core branch;
- Initial integration with current Open MCT codebase;
2022-03-04 14:18:36 -08:00
43 changed files with 1532 additions and 326 deletions

View File

@@ -149,15 +149,11 @@ workflows:
node-version: lts/fermium
browser: ChromeHeadless
post-steps:
- upload_code_covio
- upload_code_covio
- unit-test:
name: node16-chrome
node-version: lts/gallium
browser: ChromeHeadless
- unit-test:
name: node18-chrome
node-version: "18"
browser: ChromeHeadless
browser: ChromeHeadless
- e2e-test:
name: e2e-ci
node-version: lts/gallium
@@ -180,10 +176,6 @@ workflows:
name: node16-chrome-nightly
node-version: lts/gallium
browser: ChromeHeadless
- unit-test:
name: node18-chrome
node-version: "18"
browser: ChromeHeadless
- npm-audit:
node-version: lts/gallium
- e2e-test:

View File

@@ -34,7 +34,7 @@ jobs:
- run: npm install
- run: npm run test:e2e:full
- name: Archive test results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
with:
path: test-results
- name: Test success

View File

@@ -18,7 +18,6 @@ jobs:
node_version:
- 14
- 16
- 18
architecture:
- x64
name: Node ${{ matrix.node_version }} - ${{ matrix.architecture }} on ${{ matrix.os }}

View 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

View File

@@ -1,190 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*
Test for plot autoscale.
*/
const { test: _test, expect } = require('@playwright/test');
// create a new `test` API that will not append platform details to snapshot
// file names, only for the tests in this file, so that the same snapshots will
// be used for all platforms.
const test = _test.extend({
_autoSnapshotSuffix: [
async ({}, use, testInfo) => {
testInfo.snapshotSuffix = '';
await use();
},
{ auto: true }
]
});
test.use({
viewport: {
width: 1280,
height: 720
}
});
test.describe('ExportAsJSON', () => {
test.only('autoscale off causes no error from undefined user range', async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle' });
await setTimeRange(page);
await createSinewaveOverlayPlot(page);
await testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']);
await turnOffAutoscale(page);
const canvas = page.locator('canvas').nth(1);
// Make sure that after turning off autoscale, the user selected range values start at the same values the plot had prior.
await Promise.all([
testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']),
new Promise(r => setTimeout(r, 100))
.then(() => canvas.screenshot())
.then(shot => expect(shot).toMatchSnapshot('autoscale-canvas-prepan.png', { maxDiffPixels: 40 }))
]);
let errorCount = 0;
function onError() {
errorCount++;
}
page.on('pageerror', onError);
await page.keyboard.down('Alt');
await canvas.dragTo(canvas, {
sourcePosition: {
x: 200,
y: 200
},
targetPosition: {
x: 400,
y: 400
}
});
await page.keyboard.up('Alt');
page.off('pageerror', onError);
// There would have been an error at this point. So if there isn't, then
// we fixed it.
expect(errorCount).toBe(0);
// Ensure the drag worked.
await Promise.all([
testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00']),
new Promise(r => setTimeout(r, 100))
.then(() => canvas.screenshot())
.then(shot => expect(shot).toMatchSnapshot('autoscale-canvas-panned.png', { maxDiffPixels: 20 }))
]);
});
});
/**
* @param {import('@playwright/test').Page} page
* @param {string} start
* @param {string} end
*/
async function setTimeRange(page, start = '2022-03-29 22:00:00.000Z', end = '2022-03-29 22:00:30.000Z') {
// Set a specific time range for consistency, otherwise it will change
// on every test to a range based on the current time.
const timeInputs = page.locator('input.c-input--datetime');
await timeInputs.first().click();
await timeInputs.first().fill(start);
await timeInputs.nth(1).click();
await timeInputs.nth(1).fill(end);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function createSinewaveOverlayPlot(page) {
// click create button
await page.locator('button:has-text("Create")').click();
// add overlay plot with defaults
await page.locator('li:has-text("Overlay Plot")').click();
await Promise.all([
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-overlay' }*/),
page.locator('text=OK').click()
]);
// save (exit edit mode)
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
await page.locator('text=Save and Finish Editing').click();
// click create button
await page.locator('button:has-text("Create")').click();
// add sine wave generator with defaults
await page.locator('li:has-text("Sine Wave Generator")').click();
await Promise.all([
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396/5cfa5c69-17bc-4a99-9545-4da8125380c5?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-single' }*/),
page.locator('text=OK').click()
]);
// focus the overlay plot
await page.locator('text=Open MCT My Items >> span').nth(3).click();
await Promise.all([
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-overlay' }*/),
page.locator('text=Unnamed Overlay Plot').first().click()
]);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function turnOffAutoscale(page) {
// enter edit mode
await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click();
// uncheck autoscale
await page.locator('text=Y Axis Scaling Auto scale Padding >> input[type="checkbox"]').uncheck();
// save
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
await page.locator('text=Save and Finish Editing').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
async function testYTicks(page, values) {
const yTicks = page.locator('.gl-plot-y-tick-label');
let promises = [yTicks.count().then(c => expect(c).toBe(values.length))];
for (let i = 0, l = values.length; i < l; i += 1) {
promises.push(expect(yTicks.nth(i)).toHaveText(values[i])); // eslint-disable-line
}
await Promise.all(promises);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -77,7 +77,7 @@
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.example.Generator());
openmct.install(openmct.plugins.example.EventGeneratorPlugin());
openmct.install(openmct.plugins.example.ExampleImagery());

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "2.0.3-SNAPSHOT",
"version": "2.0.2",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.16.3",
@@ -23,7 +23,7 @@
"d3-axis": "1.0.x",
"d3-scale": "1.0.x",
"d3-selection": "1.3.x",
"eslint": "8.13.0",
"eslint": "8.11.0",
"eslint-plugin-compat": "4.0.2",
"eslint-plugin-playwright": "0.8.0",
"eslint-plugin-vue": "8.5.0",
@@ -37,7 +37,7 @@
"imports-loader": "0.8.0",
"jasmine-core": "4.0.1",
"jsdoc": "3.5.5",
"karma": "6.3.18",
"karma": "6.3.15",
"karma-chrome-launcher": "3.1.1",
"karma-cli": "2.0.0",
"karma-coverage": "2.1.1",
@@ -46,14 +46,14 @@
"karma-jasmine": "4.0.1",
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.34",
"karma-spec-reporter": "0.0.33",
"karma-webpack": "5.0.0",
"lighthouse": "9.5.0",
"location-bar": "3.0.1",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.6.0",
"moment": "2.29.1",
"moment-duration-format": "2.3.2",
"moment-duration-format": "2.2.2",
"moment-timezone": "0.5.34",
"node-bourbon": "4.2.3",
"painterro": "1.2.56",
@@ -61,14 +61,14 @@
"plotly.js-gl2d-dist": "2.5.0",
"printj": "1.3.1",
"request": "2.88.2",
"resolve-url-loader": "5.0.0",
"resolve-url-loader": "4.0.0",
"sass": "1.49.9",
"sass-loader": "12.6.0",
"sinon": "13.0.1",
"style-loader": "^1.0.1",
"uuid": "3.3.3",
"vue": "2.6.14",
"vue-eslint-parser": "8.3.0",
"vue-eslint-parser": "8.2.0",
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.68.0",

View File

@@ -242,6 +242,7 @@ define([
// Plugins that are installed by default
this.install(this.plugins.Gauge());
this.install(this.plugins.Plot());
this.install(this.plugins.Chart());
this.install(this.plugins.TelemetryTable.default());

View File

@@ -1,5 +1,6 @@
import AutoCompleteField from './components/controls/AutoCompleteField.vue';
import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue';
import CheckBoxField from './components/controls/CheckBoxField.vue';
import Datetime from './components/controls/Datetime.vue';
import FileInput from './components/controls/FileInput.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 TextAreaField from './components/controls/TextAreaField.vue';
import TextField from './components/controls/TextField.vue';
import ToggleSwitchField from './components/controls/ToggleSwitchField.vue';
import Vue from 'vue';
export const DEFAULT_CONTROLS_MAP = {
'autocomplete': AutoCompleteField,
'checkbox': CheckBoxField,
'composite': ClockDisplayFormatField,
'datetime': Datetime,
'file-input': FileInput,
@@ -19,7 +22,8 @@ export const DEFAULT_CONTROLS_MAP = {
'numberfield': NumberField,
'select': SelectField,
'textarea': TextAreaField,
'textfield': TextField
'textfield': TextField,
'toggleSwitch': ToggleSwitchField
};
export default class FormControl {
@@ -94,4 +98,3 @@ export default class FormControl {
};
}
}

View File

@@ -79,10 +79,12 @@ export default {
rowClass() {
let cssClass = this.cssClass;
if (this.row.required) {
cssClass = `${cssClass} req`;
if (!this.row.required) {
return;
}
cssClass = `${cssClass} req`;
if (this.visited && this.valid !== undefined) {
if (this.valid === true) {
cssClass = `${cssClass} valid`;

View 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>

View File

@@ -58,7 +58,6 @@ export default {
},
methods: {
updateText() {
console.log('updateText', this.field);
const data = {
model: this.model,
value: this.field

View 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>

View 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);
}
}
};

View File

@@ -90,6 +90,9 @@ export default class CreateWizard {
rows: this.properties.map(property => {
const row = JSON.parse(JSON.stringify(property));
row.value = this.getValue(row);
if (property.validate) {
row.validate = property.validate;
}
return row;
}).filter(row => row && row.control)

View File

@@ -0,0 +1,221 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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 = [
['Dial', 'dial'],
['Vertical Meter', 'meter-vertical'],
['Vertical Meter Inverted', 'meter-vertical-inverted'],
['Horizontal Meter', 'meter-horizontal']
];
export const GAUGE_DISPLAY_STYLES = [
['Bar', 'bar'],
['Needle', 'needle']
];
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],
gaugeDisplayStyle: GAUGE_DISPLAY_STYLES[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: "Display style",
options: GAUGE_DISPLAY_STYLES.map(type => {
return {
name: type[0],
value: type[1]
};
}),
control: "select",
cssClass: "l-input-sm",
key: "gaugeController",
property: [
"configuration",
"gaugeController",
"gaugeDisplayStyle"
]
},
{
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;
}
};
}
}

View File

@@ -0,0 +1,65 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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;
}
};
}

View File

@@ -0,0 +1,443 @@
<template>
<div
class="c-gauge"
:class="`c-gauge--${gaugeType} c-gauge--style-${gaugeDisplayStyle}`"
>
<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 && styleBar,
'c-dial-clip--180': degValue >= 90 && degValue < 180 && styleBar
}"
>
<path
v-if="styleBar && 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="styleNeedle && 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,
gaugeDisplayStyle: gaugeController.gaugeDisplayStyle
};
},
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');
},
typeMeter() {
return this.matchGaugeType('meter');
},
typeMeterHorizontal() {
return this.matchGaugeType('horizontal');
},
typeMeterVertical() {
return this.matchGaugeType('vertical');
},
typeMeterInverted() {
return this.matchGaugeType('inverted');
},
styleBar() {
return this.matchGaugeDisplayStyle('bar');
},
styleNeedle() {
return this.matchGaugeDisplayStyle('needle');
},
meterValueToPerc() {
const meterDirection = (this.typeMeterInverted) ? -1 : 1;
// For bar meter, don't move the bar if it's not visible
if (this.styleBar && this.curVal <= this.rangeLow) {
return meterDirection * 100;
}
// For bar meter, don't move the bar if it's beyond 100% of the range
if (this.styleBar && 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);
}
},
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);
},
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);
},
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();
}
}
]
});
},
getDateValueFormatter() {
const timeSystem = this.openmct.time.timeSystem();
const metadataValue = this.metadata.value(timeSystem.key) || { format: timeSystem.key };
return this.openmct.telemetry.getValueFormatter(metadataValue);
},
matchGaugeType(str) {
return this.gaugeType.indexOf(str) !== -1;
},
matchGaugeDisplayStyle(str) {
return this.gaugeDisplayStyle.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;
},
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) {
const dateValueFormatter = this.getDateValueFormatter();
const parsedValue = dateValueFormatter.parse(datum);
const { start, end } = this.openmct.time.bounds();
const beforeStartOfBounds = parsedValue < start;
const afterEndOfBounds = parsedValue > end;
if (afterEndOfBounds || beforeStartOfBounds) {
return;
}
if (this.isRendering) {
return;
}
this.isRendering = true;
requestAnimationFrame(() => {
this.isRendering = false;
this.curVal = this.round(this.formats[this.valueKey].format(datum), this.precision);
});
},
valToPercent(vValue) {
// Used by dial
if (vValue >= this.rangeHigh && this.styleBar) {
// 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>

View File

@@ -0,0 +1,172 @@
/*****************************************************************************
* 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
id="isUseTelemetryLimits"
: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,
gaugeDisplayStyle: this.model.value.gaugeDisplayStyle,
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>

View File

@@ -0,0 +1,299 @@
$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;
}
}
/********************************************** HORIZONTAL AND VERTICAL METERS */
.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;
}
}
/*********************************************** VERTICAL METER */
[class*='c-gauge--meter-vertical'] {
.c-meter {
&__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;
}
&__limit-low {
bottom: 0;
}
&__limit-high {
top: 0;
}
}
&[class*='--style-needle'] {
.c-meter__value {
border-top: 3px solid $colorGaugeValue;
background: none;
}
}
&[class*='inverted'] {
.c-meter {
&__limit-low {
bottom: auto;
top: 0;
}
&__limit-high {
bottom: 0;
top: auto;
}
&__range__low {
order: 1;
}
&__range__high {
order: 2;
}
}
&[class*='--style-needle'] {
.c-meter__value {
border-top: none;
border-bottom: 3px solid $colorGaugeValue;
right: 0;
}
}
}
}
/*********************************************** HORIZONTAL METER */
[class*='c-gauge--meter-horizontal'] {
.c-meter {
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;
}
}
&[class*='--style-needle'] {
.c-meter {
&__value {
border-right: 3px solid $colorGaugeValue;
bottom: 0;
background: none;
}
}
}
}

View File

@@ -14,7 +14,7 @@ $elemBg: rgba(black, 0.7);
position: absolute;
left: 0;
top: 0;
z-index: 1;
z-index: 2;
@include userSelectNone;
}

View File

@@ -536,7 +536,7 @@ export default {
this.updateRelatedTelemetryForFocusedImage = _.debounce(this.updateRelatedTelemetryForFocusedImage, 400);
// for resizing the object view
this.resizeImageContainer = _.debounce(this.resizeImageContainer, 400, { leading: true });
this.resizeImageContainer = _.debounce(this.resizeImageContainer, 400);
if (this.$refs.imageBG) {
this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer);

View File

@@ -222,7 +222,7 @@
.h-local-controls--overlay-content {
position: absolute;
left: $interiorMargin; top: $interiorMargin;
z-index: 2;
z-index: 70;
background: $colorLocalControlOvrBg;
border-radius: $basicCr;
max-width: 250px;

View File

@@ -37,7 +37,6 @@
v-if="seriesModels.length > 0"
:tick-width="tickWidth"
:single-series="seriesModels.length === 1"
:has-same-range-value="hasSameRangeValue"
:series-model="seriesModels[0]"
:style="{
left: (plotWidth - tickWidth) + 'px'
@@ -251,8 +250,7 @@ export default {
loaded: false,
isTimeOutOfSync: false,
showLimitLineLabels: undefined,
isFrozenOnMouseDown: false,
hasSameRangeValue: true
isFrozenOnMouseDown: false
};
},
computed: {
@@ -364,7 +362,6 @@ export default {
this.setDisplayRange(series, xKey);
}, this);
this.listenTo(series, 'change:yKey', () => {
this.checkSameRangeValue();
this.loadSeriesData(series);
}, this);
@@ -372,18 +369,10 @@ export default {
this.loadSeriesData(series);
}, this);
this.checkSameRangeValue();
this.loadSeriesData(series);
},
checkSameRangeValue() {
this.hasSameRangeValue = this.seriesModels.every((model) => {
return model.get('yKey') === this.seriesModels[0].get('yKey');
});
},
removeSeries(plotSeries) {
this.checkSameRangeValue();
this.stopListening(plotSeries);
},
@@ -499,7 +488,7 @@ export default {
},
setDisplayRange(series, xKey) {
if (this.config.series.models.length !== 1) {
if (this.config.series.length !== 1) {
return;
}

View File

@@ -29,9 +29,9 @@
>
<div
v-if="canShowYAxisLabel"
v-if="singleSeries"
class="gl-plot-label gl-plot-y-label"
:class="{'icon-gear': (yKeyOptions.length > 1 && singleSeries)}"
:class="{'icon-gear': (yKeyOptions.length > 1)}"
>{{ yAxisLabel }}
</div>
@@ -76,12 +76,6 @@ export default {
return true;
}
},
hasSameRangeValue: {
type: Boolean,
default() {
return true;
}
},
seriesModel: {
type: Object,
default() {
@@ -101,11 +95,6 @@ export default {
loaded: false
};
},
computed: {
canShowYAxisLabel() {
return this.singleSeries === true || this.hasSameRangeValue === true;
}
},
mounted() {
this.yAxis = this.getYAxisFromConfig();
this.loaded = true;

View File

@@ -157,8 +157,7 @@ export default class PlotConfigurationModel extends Model {
@typedef {{
configuration: {
series: import('./PlotSeries').PlotSeriesModelType[]
yAxis: import('./YAxisModel').YAxisModelType
},
}
}} SomeDomainObject_NeedsName
*/

View File

@@ -250,7 +250,6 @@ export default class PlotSeries extends Model {
this.evaluate = function (datum) {
return this.limitEvaluator.evaluate(datum, valueMetadata);
}.bind(this);
this.set('unit', valueMetadata.unit);
const format = this.formats[newKey];
this.getYVal = format.parse.bind(format);
}

View File

@@ -19,7 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Model from './Model';
import Model from "./Model";
/**
* @extends {Model<XAxisModelType, XAxisModelOptions>}
@@ -49,11 +49,11 @@ export default class XAxisModel extends Model {
}
});
this.on('change:frozen', (frozen) => {
this.on('change:frozen', ((frozen) => {
if (!frozen) {
this.set('range', this.get('range'));
}
});
}));
if (this.get('range')) {
this.set('range', this.get('range'));
@@ -126,7 +126,7 @@ export default class XAxisModel extends Model {
/**
@typedef {import("./Model").ModelType<{
range?: NumberRange
range: NumberRange
displayRange: NumberRange
frozen: boolean
label: string

View File

@@ -19,6 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import _ from 'lodash';
import Model from './Model';
/**
@@ -62,14 +63,14 @@ export default class YAxisModel extends Model {
*/
listenToSeriesCollection(seriesCollection) {
this.seriesCollection = seriesCollection;
this.listenTo(this.seriesCollection, 'add', series => {
this.listenTo(this.seriesCollection, 'add', (series => {
this.trackSeries(series);
this.updateFromSeries(this.seriesCollection);
}, this);
this.listenTo(this.seriesCollection, 'remove', series => {
}), this);
this.listenTo(this.seriesCollection, 'remove', (series => {
this.untrackSeries(series);
this.updateFromSeries(this.seriesCollection);
}, this);
}), this);
this.seriesCollection.forEach(this.trackSeries, this);
this.updateFromSeries(this.seriesCollection);
}
@@ -139,11 +140,11 @@ export default class YAxisModel extends Model {
}
resetStats() {
this.unset('stats');
this.seriesCollection.forEach(series => {
this.seriesCollection.forEach(function (series) {
if (series.has('stats')) {
this.updateStats(series.get('stats'));
}
});
}, this);
}
/**
* @param {import('./PlotSeries').default} series
@@ -169,18 +170,7 @@ export default class YAxisModel extends Model {
if (autoscale && this.has('stats')) {
this.set('displayRange', this.applyPadding(this.get('stats')));
} else {
const range = this.get('range');
if (range) {
// If we already have a user-defined range, make sure it maps to the
// range we'll actually use for the ticks.
this.set('displayRange', range);
} else {
// Otherwise use the last known displayRange as the initial
// values for the user-defined range, so that we don't end up
// with any error from an undefined user range.
this.set('range', this.get('displayRange'));
}
this.set('displayRange', this.get('range'));
}
}
/**
@@ -189,7 +179,7 @@ export default class YAxisModel extends Model {
*/
updateFromSeries(seriesCollection) {
const plotModel = this.plot.get('domainObject');
const label = plotModel?.configuration?.yAxis?.label;
const label = _.get(plotModel, 'configuration.yAxis.label');
const sampleSeries = seriesCollection.first();
if (!sampleSeries) {
if (!label) {
@@ -205,19 +195,19 @@ export default class YAxisModel extends Model {
this.set('format', yFormat.format.bind(yFormat));
this.set('values', yMetadata.values);
if (!label) {
const labelName = seriesCollection
.map(s => (s.metadata ? s.metadata.value(s.get('yKey')).name : ''))
.reduce((a, b) => {
if (a === undefined) {
return b;
}
const labelName = seriesCollection.map(function (s) {
return s.metadata ? s.metadata.value(s.get('yKey')).name : '';
}).reduce(function (a, b) {
if (a === undefined) {
return b;
}
if (a === b) {
return a;
}
if (a === b) {
return a;
}
return '';
}, undefined);
return '';
}, undefined);
if (labelName) {
this.set('label', labelName);
@@ -225,19 +215,19 @@ export default class YAxisModel extends Model {
return;
}
const labelUnits = seriesCollection
.map(s => (s.metadata ? s.metadata.value(s.get('yKey')).units : ''))
.reduce((a, b) => {
if (a === undefined) {
return b;
}
const labelUnits = seriesCollection.map(function (s) {
return s.metadata ? s.metadata.value(s.get('yKey')).units : '';
}).reduce(function (a, b) {
if (a === undefined) {
return b;
}
if (a === b) {
return a;
}
if (a === b) {
return a;
}
return '';
}, undefined);
return '';
}, undefined);
if (labelUnits) {
this.set('label', labelUnits);
@@ -249,17 +239,14 @@ export default class YAxisModel extends Model {
/**
* @override
* @param {import('./Model').ModelOptions<YAxisModelType, YAxisModelOptions>} options
* @returns {Partial<YAxisModelType>}
* @returns {YAxisModelType}
*/
defaultModel(options) {
// @ts-ignore incomplete YAxisModelType object used for default value.
return {
frozen: false,
autoscale: true,
autoscalePadding: 0.1
// 'range' is not specified here, it is undefined at first. When the
// user turns off autoscale, the current 'displayRange' is used for
// the initial value of 'range'.
};
}
}
@@ -270,7 +257,7 @@ export default class YAxisModel extends Model {
@typedef {import('./XAxisModel').AxisModelType & {
autoscale: boolean
autoscalePadding: number
stats?: import('./XAxisModel').NumberRange
stats: import('./XAxisModel').NumberRange
values: Array<TODO>
}} YAxisModelType
*/

View File

@@ -52,10 +52,10 @@
class="l-inspector-part"
>
<div
v-show="!autoscale && validationErrors.range"
v-show="!autoscale && validation.range"
class="grid-span-all form-error"
>
{{ validationErrors.range }}
{{ validation.range }}
</div>
<li class="grid-row force-border">
<div
@@ -88,7 +88,7 @@
</template>
<script>
import { objectPath } from "./formUtil";
import { objectPath, validate, coerce } from "./formUtil";
import _ from "lodash";
export default {
@@ -108,7 +108,7 @@ export default {
autoscalePadding: '',
rangeMin: '',
rangeMax: '',
validationErrors: {}
validation: {}
};
},
mounted() {
@@ -178,6 +178,12 @@ export default {
if (Number(range.min) > Number(range.max)) {
return 'Minimum must be less than Maximum.';
}
if (model.get('autoscale')) {
return false;
}
return true;
}
}
];
@@ -186,9 +192,14 @@ export default {
this.label = this.yAxis.get('label');
this.autoscale = this.yAxis.get('autoscale');
this.autoscalePadding = this.yAxis.get('autoscalePadding');
const range = this.yAxis.get('range') ?? this.yAxis.get('displayRange');
this.rangeMin = range?.min;
this.rangeMax = range?.max;
const range = this.yAxis.get('range');
if (!range) {
this.rangeMin = undefined;
this.rangeMax = undefined;
} else {
this.rangeMin = range.min;
this.rangeMax = range.max;
}
},
updateForm(formKey) {
let newVal;
@@ -201,27 +212,26 @@ export default {
newVal = this[formKey];
}
let oldVal = this.yAxis.get(formKey);
const oldVal = this.yAxis.get(formKey);
const formField = this.fields.find((field) => field.modelProp === formKey);
const validationError = formField.validate?.(newVal, this.yAxis);
this.validationErrors[formKey] = validationError;
if (validationError) {
const path = objectPath(formField.objectPath);
const validationResult = validate(newVal, this.yAxis, formField.validate);
if (validationResult === true) {
delete this.validation[formKey];
} else {
this.validation[formKey] = validationResult;
return;
}
newVal = formField.coerce?.(newVal) ?? newVal;
oldVal = formField.coerce?.(oldVal) ?? oldVal;
const path = objectPath(formField.objectPath);
if (!_.isEqual(newVal, oldVal)) {
// TODO: Why do we mutate yAxis twice, once directly, once via objects.mutate? Or are they different objects?
this.yAxis.set(formKey, newVal);
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
this.yAxis.set(formKey, coerce(newVal, formField.coerce));
if (path) {
this.openmct.objects.mutate(
this.domainObject,
path(this.domainObject, this.yAxis),
newVal
coerce(newVal, formField.coerce)
);
}
}

View File

@@ -15,5 +15,15 @@ export function validate(value, model, validateFunc) {
}
export function objectPath(path) {
return path && typeof path !== 'function' ? () => path : path;
if (path) {
if (typeof path !== "function") {
const staticObjectPath = path;
return function (object, model) {
return staticObjectPath;
};
}
return path;
}
}

View File

@@ -77,6 +77,7 @@ define([
'./userIndicator/plugin',
'../../example/exampleUser/plugin',
'./localStorage/plugin',
'./gauge/GaugePlugin',
'./timelist/plugin'
], function (
_,
@@ -135,6 +136,7 @@ define([
UserIndicator,
ExampleUser,
LocalStorage,
GaugePlugin,
TimeList
) {
const plugins = {};
@@ -212,6 +214,7 @@ define([
plugins.DeviceClassifier = DeviceClassifier.default;
plugins.UserIndicator = UserIndicator.default;
plugins.LocalStorage = LocalStorage.default;
plugins.Gauge = GaugePlugin.default;
plugins.Timelist = TimeList.default;
return plugins;

View File

@@ -9,11 +9,7 @@ export default class TelemetryTableView {
this.objectPath = objectPath;
this.component = undefined;
Object.defineProperty(this, 'table', {
value: new TelemetryTable(domainObject, openmct),
enumerable: false,
configurable: false
});
this.table = new TelemetryTable(domainObject, openmct);
}
getViewContext() {

View File

@@ -229,7 +229,7 @@ export default {
} else if (durationIndex === 1) {
return duration * 60 * 1000;
} else if (durationIndex === 2) {
return duration * 60 * 60 * 1000;
return duration * 60 * 24 * 1000;
}
}
},

View File

@@ -366,6 +366,16 @@ $legendHoverValueBg: rgba($colorBodyFg, 0.2);
$legendTableHeadBg: $colorTabHeaderBg;
$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
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
$colorCurrentFg: pullForward($colorBodyFg, 20%);

View File

@@ -370,6 +370,16 @@ $legendHoverValueBg: rgba($colorBodyFg, 0.2);
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
$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
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
$colorCurrentFg: pullForward($colorBodyFg, 20%);

View File

@@ -366,6 +366,16 @@ $legendHoverValueBg: rgba($colorBodyFg, 0.2);
$legendTableHeadBg: rgba($colorBodyFg, 0.15);
$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
$colorCurrentBg: rgba($colorStatusAlert, 0.3);
$colorCurrentFg: pullForward($colorBodyFg, 20%);

View File

@@ -70,8 +70,24 @@
padding: $formTBPad $formLRPad;
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 {
align-items: start;

View File

@@ -53,6 +53,7 @@
@import "../ui/toolbar/components/toolbar-checkbox.scss";
@import "./notebook.scss";
@import "../plugins/notebook/components/sidebar.scss";
@import "../plugins/gauge/gauge.scss";
#splash-screen {
display: none;

View File

@@ -166,7 +166,8 @@ export default {
}
return definition.form
.map((field) => {
.filter(field => !field.hideFromInspector)
.map(field => {
let path = field.property;
if (typeof path === 'string') {
path = [path];

View File

@@ -128,7 +128,7 @@ export default {
return;
}
const isExistingView = viewProvider.key === this.existingView?.key;
const isExistingView = viewProvider.key === this.existingView.key;
this.clear();