Compare commits

...

25 Commits

Author SHA1 Message Date
Joshi
6ac9eebab3 Fix processing data for requests 2021-09-13 11:34:28 -07:00
Joshi
04ece72679 Initial working prototype of the spectral aggregate plot 2021-09-10 15:42:46 -07:00
Joshi
80b4ccd562 Temporarily remove the spectralAttribute hint requirement - this will come back later.
Also move 1 child only requirement to the composition policy.
2021-09-10 15:39:41 -07:00
Joshi
880c3a8850 Updates to Spectral Generator 2021-09-10 15:25:38 -07:00
Joshi
72b609f527 Adds Spectral Aggregate Generator 2021-09-10 15:25:20 -07:00
Scott Bell
7c883aea96 fixed tests 2021-09-09 18:29:02 +02:00
Scott Bell
a7ed38b7ee resolve conflicts 2021-09-09 17:57:46 +02:00
Joshi
1049ac38ff Fix spectral aggregate view provider to use correct domain object type and view 2021-09-09 08:13:52 -07:00
Joshi
2f435facd4 Fix spectral generator to emit wavelengths 2021-09-09 08:12:34 -07:00
Scott Bell
121eeac27c restriction seems to work. added dialog too 2021-09-09 14:37:10 +02:00
Scott Bell
b67b13912e color is not a variable, but allColors is 2021-09-09 14:11:39 +02:00
Scott Bell
0cb884563a add listener 2021-09-09 11:29:38 +02:00
Scott Bell
4942e27a89 Mct4178 (#4190)
* composition should be ignored on non spectral aggregate parents
* composition should have spectralAttribute set to true
* added tests

Co-authored-by: Joshi <simplyrender@gmail.com>
2021-09-08 07:29:25 -07:00
Scott Bell
e84a3bfe97 Merge remote-tracking branch 'origin/master' into spectral-plots 2021-09-08 15:39:13 +02:00
Scott Bell
fdc1499339 first very rough draft of aggregate plot 2021-09-07 10:33:30 +02:00
Scott Bell
ae224ae567 Merge branch 'spectral-plots' into mct4102 2021-09-07 09:24:50 +02:00
Andrew Henry
48c22369a1 Fixed memory leak in import-export plugin (#4061) 2021-09-02 15:45:11 -07:00
David Tsay
6506077f4d refactor panes (#4125)
* remove router listeners
* remove some hard-coding
2021-09-02 14:11:05 -07:00
Shefali Joshi
48322d46fd Adds view providers for spectral plots (#4169)
* added spectral aggregate view providers
* added basic tests for view providers

Co-authored-by: Scott Bell <scott@traclabs.com>
2021-09-02 11:25:06 -07:00
Shefali Joshi
7616610f2c Adds new types for spectral plots and spectral aggregate plots (#4168)
* Adds new types for spectral plots and spectral aggregate plots

Co-authored-by: Scott Bell <scott@traclabs.com>
2021-09-02 10:04:49 -07:00
John Hill
b1b4266ff3 Add automated security scanning to our repo (#4166) 2021-08-30 15:44:25 -07:00
Charles Hacskaylo
42b0148f93 Add new icon for aggregate telemetry (#4163) 2021-08-30 11:19:47 -07:00
Charles Hacskaylo
9461ad8edd Add new Ellipse drawing object in Display Layouts (#4153)
* Add new ellipse drawing object to Display Layouts
- Fix test and linting issues;

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2021-08-30 13:29:05 -04:00
Shefali Joshi
40055ba955 Prepare for sprint 1.7.8 (#4156) 2021-08-27 04:55:30 -07:00
Shefali Joshi
9cb85ad176 1.7.7 merge into master (#4155)
* Merge 1.7.7 sprint branch into master

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
2021-08-27 04:44:23 -07:00
44 changed files with 2478 additions and 289 deletions

33
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: "CodeQL"
on:
push:
branches: [ master ]
schedule:
- cron: '28 21 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: javascript
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -28,6 +28,15 @@ define([
domain: 2
}
},
{
key: "cos",
name: "Cosine",
unit: "deg",
formatString: '%0.2f',
hints: {
domain: 3
}
},
// Need to enable "LocalTimeSystem" plugin to make use of this
// {
// key: "local",
@@ -109,6 +118,100 @@ define([
}
}
]
},
'example.spectral-generator': {
values: [
{
key: "name",
name: "Name",
format: "string"
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},
{
key: "wavelength",
name: "Wavelength",
unit: "Hz",
formatString: '%0.2f',
hints: {
domain: 2,
spectralAttribute: true
}
},
{
key: "cos",
name: "Cosine",
unit: "deg",
formatString: '%0.2f',
hints: {
range: 2,
spectralAttribute: true
}
}
]
},
'example.spectral-aggregate-generator': {
values: [
{
key: "name",
name: "Name",
format: "string"
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},
{
key: "ch1",
name: "Channel 1",
format: "string",
hints: {
range: 1
}
},
{
key: "ch2",
name: "Channel 2",
format: "string",
hints: {
range: 2
}
},
{
key: "ch3",
name: "Channel 3",
format: "string",
hints: {
range: 3
}
},
{
key: "ch4",
name: "Channel 4",
format: "string",
hints: {
range: 4
}
},
{
key: "ch5",
name: "Channel 5",
format: "string",
hints: {
range: 5
}
}
]
}
};

View File

@@ -0,0 +1,86 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
define([
], function (
) {
function SpectralAggregateGeneratorProvider() {
}
function pointForTimestamp(timestamp, count, name) {
return {
name: name,
utc: String(Math.floor(timestamp / count) * count),
ch1: String(Math.floor(timestamp / count) % 1),
ch2: String(Math.floor(timestamp / count) % 2),
ch3: String(Math.floor(timestamp / count) % 3),
ch4: String(Math.floor(timestamp / count) % 4),
ch5: String(Math.floor(timestamp / count) % 5)
};
}
SpectralAggregateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'example.spectral-aggregate-generator';
};
SpectralAggregateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var count = 5000;
var interval = setInterval(function () {
var now = Date.now();
var datum = pointForTimestamp(now, count, domainObject.name);
callback(datum);
}, count);
return function () {
clearInterval(interval);
};
};
SpectralAggregateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'example.spectral-aggregate-generator';
};
SpectralAggregateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = Math.min(Date.now(), options.end); // no future values
var count = 5000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;
}
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, count, domainObject.name));
start += count;
}
return Promise.resolve(data);
};
return SpectralAggregateGeneratorProvider;
});

View File

@@ -0,0 +1,104 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
define([
'./WorkerInterface'
], function (
WorkerInterface
) {
var REQUEST_DEFAULTS = {
amplitude: 1,
wavelength: 1,
period: 10,
offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0
};
function SpectralGeneratorProvider() {
this.workerInterface = new WorkerInterface();
}
SpectralGeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
return domainObject.type === 'example.spectral-generator';
};
SpectralGeneratorProvider.prototype.supportsRequest =
SpectralGeneratorProvider.prototype.supportsSubscribe =
SpectralGeneratorProvider.prototype.canProvideTelemetry;
SpectralGeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) {
var props = [
'amplitude',
'wavelength',
'period',
'offset',
'dataRateInHz',
'phase',
'randomness'
];
request = request || {};
var workerRequest = {};
props.forEach(function (prop) {
if (domainObject.telemetry && Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)) {
workerRequest[prop] = domainObject.telemetry[prop];
}
if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
workerRequest[prop] = request[prop];
}
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
workerRequest[prop] = REQUEST_DEFAULTS[prop];
}
workerRequest[prop] = Number(workerRequest[prop]);
});
workerRequest.name = domainObject.name;
return workerRequest;
};
SpectralGeneratorProvider.prototype.request = function (domainObject, request) {
var workerRequest = this.makeWorkerRequest(domainObject, request);
workerRequest.start = request.start;
workerRequest.end = request.end;
workerRequest.spectra = true;
return this.workerInterface.request(workerRequest);
};
SpectralGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var workerRequest = this.makeWorkerRequest(domainObject, {});
workerRequest.spectra = true;
return this.workerInterface.subscribe(workerRequest, callback);
};
return SpectralGeneratorProvider;
});

View File

@@ -54,23 +54,39 @@
var start = Date.now();
var step = 1000 / data.dataRateInHz;
var nextStep = start - (start % step) + step;
let work;
console.log('dataRate', data);
if (data.spectra) {
work = function (now) {
while (nextStep < now) {
const messageCopy = Object.create(message);
message.data.start = nextStep - (60 * 1000);
message.data.end = nextStep;
onRequest(messageCopy);
nextStep += step;
}
function work(now) {
while (nextStep < now) {
self.postMessage({
id: message.id,
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
}
});
nextStep += step;
}
return nextStep;
};
} else {
work = function (now) {
while (nextStep < now) {
self.postMessage({
id: message.id,
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
wavelength: wavelength(start, nextStep),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
}
});
nextStep += step;
}
return nextStep;
return nextStep;
};
}
subscriptions[message.id] = work;
@@ -111,13 +127,21 @@
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
wavelength: wavelength(start, nextStep),
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
});
}
self.postMessage({
id: message.id,
data: data
data: request.spectra ? {
wavelength: data.map((item) => {
return item.wavelength;
}),
cos: data.map((item) => {
return item.cos;
})
} : data
});
}
@@ -131,6 +155,10 @@
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
}
function wavelength(start, nextStep) {
return (nextStep - start) / 10;
}
function sendError(error, message) {
self.postMessage({
error: error.name + ': ' + error.message,

View File

@@ -24,11 +24,15 @@ define([
"./GeneratorProvider",
"./SinewaveLimitProvider",
"./StateGeneratorProvider",
"./SpectralGeneratorProvider",
"./SpectralAggregateGeneratorProvider",
"./GeneratorMetadataProvider"
], function (
GeneratorProvider,
SinewaveLimitProvider,
StateGeneratorProvider,
SpectralGeneratorProvider,
SpectralAggregateGeneratorProvider,
GeneratorMetadataProvider
) {
@@ -61,6 +65,37 @@ define([
openmct.telemetry.addProvider(new StateGeneratorProvider());
openmct.types.addType("example.spectral-generator", {
name: "Spectral Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
cssClass: "icon-generator-telemetry",
creatable: true,
initialize: function (object) {
object.telemetry = {
period: 10,
amplitude: 1,
wavelength: 1,
frequency: 1,
offset: 0,
dataRateInHz: 1,
phase: 0,
randomness: 0
};
}
});
openmct.telemetry.addProvider(new SpectralGeneratorProvider());
openmct.types.addType("example.spectral-aggregate-generator", {
name: "Spectral Aggregate Generator",
description: "For development use. Generates example streaming telemetry data using a simple state algorithm.",
cssClass: "icon-generator-telemetry",
creatable: true,
initialize: function (object) {
object.telemetry = {};
}
});
openmct.telemetry.addProvider(new SpectralAggregateGeneratorProvider());
openmct.types.addType("generator", {
name: "Sine Wave Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",

View File

@@ -1,8 +1,7 @@
{
"name": "openmct",
"version": "1.7.7-SNAPSHOT",
"version": "1.7.8-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {},
"devDependencies": {
"angular": ">=1.8.0",
"angular-route": "1.4.14",
@@ -41,13 +40,13 @@
"jsdoc": "^3.3.2",
"karma": "6.3.4",
"karma-chrome-launcher": "3.1.0",
"karma-firefox-launcher": "2.1.1",
"karma-cli": "2.0.0",
"karma-coverage": "2.0.3",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-junit-reporter": "2.0.1",
"karma-firefox-launcher": "2.1.1",
"karma-html-reporter": "0.2.7",
"karma-jasmine": "4.0.1",
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-webpack": "4.0.2",
"location-bar": "^3.0.1",
@@ -62,6 +61,7 @@
"node-bourbon": "^4.2.3",
"node-sass": "^4.14.1",
"painterro": "^1.2.56",
"plotly.js": "^2.5.0",
"printj": "^1.2.1",
"raw-loader": "^0.5.1",
"request": "^2.69.0",

View File

@@ -30,8 +30,8 @@ define([
return function ImportExportPlugin() {
return function (openmct) {
ExportAsJSONAction.appliesTo = function (context) {
return openmct.$injector.get('policyService')
ExportAsJSONAction.prototype.appliesTo = function (context) {
return this.openmct.$injector.get('policyService')
.allow("creation", context.domainObject.getCapability("type")
);
};

View File

@@ -29,7 +29,7 @@ define(
],
function (ExportAsJSONAction, domainObjectFactory, MCT, AdapterCapability) {
xdescribe("The export JSON action", function () {
describe("The export JSON action", function () {
var context,
action,
@@ -102,7 +102,7 @@ define(
expect(action).toBeDefined();
});
it("doesn't export non-creatable objects in tree", function () {
xit("doesn't export non-creatable objects in tree", function () {
var nonCreatableType = {
hasFeature:
function (feature) {
@@ -149,7 +149,7 @@ define(
});
});
it("can export self-containing objects", function () {
xit("can export self-containing objects", function () {
var parent = domainObjectFactory({
name: 'parent',
model: {
@@ -191,7 +191,7 @@ define(
});
});
it("exports links to external objects as new objects", function () {
xit("exports links to external objects as new objects", function () {
var parent = domainObjectFactory({
name: 'parent',
model: {

View File

@@ -27,7 +27,7 @@ define(
],
function (ImportAsJSONAction, domainObjectFactory) {
xdescribe("The import JSON action", function () {
describe("The import JSON action", function () {
var context = {};
var action,
@@ -146,7 +146,7 @@ define(
});
});
it("can import self-containing objects", function () {
xit("can import self-containing objects", function () {
var compDomainObject = domainObjectFactory({
name: 'compObject',
model: { name: 'compObject'},
@@ -198,7 +198,7 @@ define(
});
});
it("assigns new ids to each imported object", function () {
xit("assigns new ids to each imported object", function () {
dialogService.getUserInput.and.returnValue(Promise.resolve(
{
selectFile: {

View File

@@ -141,6 +141,7 @@ const NON_STYLEABLE_CONTAINER_TYPES = [
const NON_STYLEABLE_LAYOUT_ITEM_TYPES = [
'line-view',
'box-view',
'ellipse-view',
'image-view'
];
@@ -321,7 +322,7 @@ export default {
if (item) {
const type = this.openmct.types.get(item.type);
if (type && type.definition) {
creatable = (type.definition.creatable === true);
creatable = (type.definition.creatable !== undefined && (type.definition.creatable === 'true' || type.definition.creatable === true));
}
}

View File

@@ -230,7 +230,7 @@ describe('the plugin', function () {
};
const staticStyle = {
"style": {
"backgroundColor": "#717171",
"backgroundColor": "#666666",
"border": "1px solid #00ffff"
}
};
@@ -238,7 +238,7 @@ describe('the plugin', function () {
"conditionId": "39584410-cbf9-499e-96dc-76f27e69885d",
"style": {
"isStyleInvisible": "",
"backgroundColor": "#717171",
"backgroundColor": "#666666",
"border": "1px solid #ffff00"
}
};
@@ -250,7 +250,7 @@ describe('the plugin', function () {
"configuration": {
"items": [
{
"fill": "#717171",
"fill": "#666666",
"stroke": "",
"x": 1,
"y": 1,
@@ -259,12 +259,22 @@ describe('the plugin', function () {
"type": "box-view",
"id": "89b88746-d325-487b-aec4-11b79afff9e8"
},
{
"fill": "#666666",
"stroke": "",
"x": 1,
"y": 1,
"width": 10,
"height": 5,
"type": "ellipse-view",
"id": "19b88746-d325-487b-aec4-11b79afff9z8"
},
{
"x": 18,
"y": 9,
"x2": 23,
"y2": 4,
"stroke": "#717171",
"stroke": "#666666",
"type": "line-view",
"id": "57d49a28-7863-43bd-9593-6570758916f0"
},
@@ -299,12 +309,12 @@ describe('the plugin', function () {
"y": 9,
"x2": 23,
"y2": 4,
"stroke": "#717171",
"stroke": "#666666",
"type": "line-view",
"id": "57d49a28-7863-43bd-9593-6570758916f0"
};
boxLayoutItem = {
"fill": "#717171",
"fill": "#666666",
"stroke": "",
"x": 1,
"y": 1,

View File

@@ -29,9 +29,10 @@ const styleProps = {
noneValue: NONE_VALUE,
applicableForType: type => {
return !type ? true : (type === 'text-view'
|| type === 'telemetry-view'
|| type === 'box-view'
|| type === 'subobject-view');
|| type === 'telemetry-view'
|| type === 'box-view'
|| type === 'ellipse-view'
|| type === 'subobject-view');
}
},
border: {
@@ -41,6 +42,7 @@ const styleProps = {
return !type ? true : (type === 'text-view'
|| type === 'telemetry-view'
|| type === 'box-view'
|| type === 'ellipse-view'
|| type === 'image-view'
|| type === 'line-view'
|| type === 'subobject-view');

View File

@@ -149,6 +149,7 @@ define(['lodash'], function (_) {
return type === 'text-view'
|| type === 'telemetry-view'
|| type === 'box-view'
|| type === 'ellipse-view'
|| type === 'image-view'
|| type === 'line-view'
|| type === 'subobject-view';
@@ -180,6 +181,10 @@ define(['lodash'], function (_) {
"name": "Box",
"class": "icon-box-round-corners"
},
{
"name": "Ellipse",
"class": "icon-circle"
},
{
"name": "Line",
"class": "icon-line-horz"
@@ -745,7 +750,7 @@ define(['lodash'], function (_) {
if (toolbar.remove.length === 0) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'box-view') {
} else if (layoutItem.type === 'box-view' || layoutItem.type === 'ellipse-view') {
if (toolbar.position.length === 0) {
toolbar.position = [
getStackOrder(selectedParent, selectionPath),

View File

@@ -43,7 +43,7 @@ import conditionalStylesMixin from '../mixins/objectStyles-mixin';
export default {
makeDefinition() {
return {
fill: '#717171',
fill: '#666666',
stroke: '',
x: 1,
y: 1,

View File

@@ -76,6 +76,7 @@ import uuid from 'uuid';
import SubobjectView from './SubobjectView.vue';
import TelemetryView from './TelemetryView.vue';
import BoxView from './BoxView.vue';
import EllipseView from './EllipseView.vue';
import TextView from './TextView.vue';
import LineView from './LineView.vue';
import ImageView from './ImageView.vue';
@@ -112,6 +113,7 @@ const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView,
'telemetry-view': TelemetryView,
'box-view': BoxView,
'ellipse-view': EllipseView,
'line-view': LineView,
'text-view': TextView,
'image-view': ImageView

View File

@@ -28,19 +28,19 @@
>
<div
class="c-frame-edit__handle c-frame-edit__handle--nw"
@mousedown="startResize([1,1], [-1,-1], $event)"
@mousedown.left="startResize([1,1], [-1,-1], $event)"
></div>
<div
class="c-frame-edit__handle c-frame-edit__handle--ne"
@mousedown="startResize([0,1], [1,-1], $event)"
@mousedown.left="startResize([0,1], [1,-1], $event)"
></div>
<div
class="c-frame-edit__handle c-frame-edit__handle--sw"
@mousedown="startResize([1,0], [-1,1], $event)"
@mousedown.left="startResize([1,0], [-1,1], $event)"
></div>
<div
class="c-frame-edit__handle c-frame-edit__handle--se"
@mousedown="startResize([0,0], [1,1], $event)"
@mousedown.left="startResize([0,0], [1,1], $event)"
></div>
</div>
</template>

View File

@@ -0,0 +1,122 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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>
<layout-frame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
<div
class="c-ellipse-view u-style-receiver js-style-receiver"
:class="[styleClass]"
:style="style"
></div>
</layout-frame>
</template>
<script>
import LayoutFrame from './LayoutFrame.vue';
import conditionalStylesMixin from '../mixins/objectStyles-mixin';
export default {
makeDefinition() {
return {
fill: '#666666',
stroke: '',
x: 1,
y: 1,
width: 10,
height: 10
};
},
components: {
LayoutFrame
},
mixins: [conditionalStylesMixin],
inject: ['openmct'],
props: {
item: {
type: Object,
required: true
},
gridSize: {
type: Array,
required: true,
validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number')
},
index: {
type: Number,
required: true
},
initSelect: Boolean,
isEditing: {
type: Boolean,
required: true
}
},
computed: {
style() {
if (this.itemStyle) {
return this.itemStyle;
} else {
return {
backgroundColor: this.item.fill,
border: this.item.stroke ? '1px solid ' + this.item.stroke : ''
};
}
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {
this.context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
}
};
</script>

View File

@@ -96,7 +96,7 @@ export default {
y: 10,
x2: 10,
y2: 5,
stroke: '#717171'
stroke: '#666666'
};
},
mixins: [conditionalStylesMixin],

View File

@@ -1,4 +1,5 @@
.c-box-view {
.c-box-view,
.c-ellipse-view {
border-width: $drawingObjBorderW !important;
display: flex;
align-items: stretch;
@@ -8,6 +9,10 @@
}
}
.c-ellipse-view {
border-radius: 50%;
}
.c-line-view {
&.c-frame {
box-shadow: none !important;

View File

@@ -186,7 +186,7 @@ describe('the plugin', function () {
'configuration': {
'items': [
{
'fill': '#717171',
'fill': '#666666',
'stroke': '',
'x': 1,
'y': 1,
@@ -195,12 +195,22 @@ describe('the plugin', function () {
'type': 'box-view',
'id': '89b88746-d325-487b-aec4-11b79afff9e8'
},
{
'fill': '#666666',
'stroke': '',
'x': 1,
'y': 1,
'width': 10,
'height': 10,
'type': 'ellipse-view',
'id': '19b88746-d325-487b-aec4-11b79afff9z8'
},
{
'x': 18,
'y': 9,
'x2': 23,
'y2': 4,
'stroke': '#717171',
'stroke': '#666666',
'type': 'line-view',
'id': '57d49a28-7863-43bd-9593-6570758916f0'
},
@@ -341,7 +351,7 @@ describe('the plugin', function () {
it('provides controls including separators', () => {
const displayLayoutToolbar = openmct.toolbars.get(selection);
expect(displayLayoutToolbar.length).toBe(9);
expect(displayLayoutToolbar.length).toBe(7);
});
});
});

View File

@@ -103,10 +103,13 @@ export function validateNotebookStorageObject() {
let valid = false;
if (notebookStorage) {
Object.entries(notebookStorage).forEach(([key, value]) => {
const oldInvalidKeys = ['notebookMeta', 'page', 'section'];
valid = Object.entries(notebookStorage).every(([key, value]) => {
const validKey = key !== undefined && key !== null;
const validValue = value !== undefined && value !== null;
valid = validKey && validValue;
const hasOldInvalidKeys = oldInvalidKeys.includes(key);
return validKey && validValue && !hasOldInvalidKeys;
});
}

View File

@@ -89,13 +89,4 @@ ColorPalette.prototype.getNextColor = function () {
return this.availableColors.shift();
};
/**
* @param {number} index the index of the color to return. An index
* value larger than the size of the index will wrap around.
* @returns {Color}
*/
ColorPalette.prototype.getColor = function (index) {
return this.colors[index % this.colors.length];
};
export default ColorPalette;

View File

@@ -19,13 +19,17 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { SPECTRAL_AGGREGATE_KEY } from './spectralAggregatePlot/SpectralAggregateConstants';
import PlotViewProvider from './PlotViewProvider';
import SpectralPlotViewProvider from './spectralPlot/SpectralPlotViewProvider';
import SpectralAggregatePlotViewProvider from './spectralAggregatePlot/SpectralAggregatePlotViewProvider';
import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider';
import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider';
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
import SpectralPlotCompositionPolicy from './spectralPlot/SpectralPlotCompositionPolicy';
import SpectralAggregatePlotCompositionPolicy from './spectralAggregatePlot/SpectralAggregatePlotCompositionPolicy';
export default function () {
return function install(openmct) {
@@ -59,13 +63,46 @@ export default function () {
},
priority: 890
});
openmct.types.addType('telemetry.plot.spectral', {
key: "telemetry.plot.spectral",
name: "Spectral Plot",
cssClass: "icon-plot-stacked",
description: "View Spectra on Y Axes with non-time domain on the X axis. Can be added to Display Layouts.",
creatable: true,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {};
},
priority: 890
});
openmct.types.addType(SPECTRAL_AGGREGATE_KEY, {
key: SPECTRAL_AGGREGATE_KEY,
name: "Spectral Aggregate Plot",
cssClass: "icon-plot-stacked",
description: "View Spectra on Y Axes with non-time domain on the X axis. Can be added to Display Layouts.",
creatable: true,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
plotType: 'bar'
};
},
priority: 891
});
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
openmct.objectViews.addProvider(new SpectralPlotViewProvider(openmct));
openmct.objectViews.addProvider(new SpectralAggregatePlotViewProvider(openmct));
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new SpectralPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new SpectralAggregatePlotCompositionPolicy(openmct).allow);
};
}

View File

@@ -24,10 +24,12 @@ import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} f
import PlotVuePlugin from "./plugin";
import Vue from "vue";
import StackedPlot from "./stackedPlot/StackedPlot.vue";
// import SpectralPlot from "./spectralPlot/SpectralPlot.vue";
import configStore from "./configuration/configStore";
import EventEmitter from "EventEmitter";
import PlotOptions from "./inspector/PlotOptions.vue";
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
import { SPECTRAL_AGGREGATE_VIEW, SPECTRAL_AGGREGATE_KEY } from './spectralAggregatePlot/SpectralAggregateConstants';
describe("the plugin", function () {
let element;
@@ -312,6 +314,38 @@ describe("the plugin", function () {
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-stacked");
expect(plotView).toBeDefined();
});
it("provides a spectral plot view for objects with telemetry", () => {
const testTelemetryObject = {
id: "test-object",
type: "telemetry.plot.spectral",
telemetry: {
values: [{
key: "a-very-fine-key"
}]
}
};
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-spectral");
expect(plotView).toBeDefined();
});
it("provides a spectral aggregate plot view for objects with telemetry", () => {
const testTelemetryObject = {
id: "test-object",
type: SPECTRAL_AGGREGATE_KEY,
telemetry: {
values: [{
key: "lots-of-aggregate-telemetry"
}]
}
};
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
let plotView = applicableViews.find((viewProvider) => viewProvider.key === SPECTRAL_AGGREGATE_VIEW);
expect(plotView).toBeDefined();
});
});
describe("The single plot view", () => {
@@ -462,6 +496,146 @@ describe("the plugin", function () {
});
});
/*
* disabling this until we develop the plot view
describe("The spectral plot view", () => {
let testTelemetryObject;
// eslint-disable-next-line no-unused-vars
let testTelemetryObject2;
// eslint-disable-next-line no-unused-vars
let config;
let spectralPlotObject;
let component;
let mockComposition;
// eslint-disable-next-line no-unused-vars
let plotViewComponentObject;
beforeEach(() => {
const getFunc = openmct.$injector.get;
spyOn(openmct.$injector, "get")
.withArgs("exportImageService").and.returnValue({
exportPNG: () => {},
exportJPG: () => {}
})
.and.callFake(getFunc);
spectralPlotObject = {
identifier: {
namespace: "",
key: "test-spectral-plot"
},
type: "telemetry.plot.spectral",
name: "Test Spectral Plot"
};
testTelemetryObject = {
identifier: {
namespace: "",
key: "test-object"
},
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "some-key",
name: "Some attribute",
hints: {
range: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 2
}
}]
}
};
testTelemetryObject2 = {
identifier: {
namespace: "",
key: "test-object2"
},
type: "test-object",
name: "Test Object2",
telemetry: {
values: [{
key: "utc",
format: "utc",
name: "Time",
hints: {
domain: 1
}
}, {
key: "wavelength",
name: "Wavelength",
hints: {
range: 1
}
}, {
key: "some-other-key2",
name: "Another attribute2",
hints: {
range: 2
}
}]
}
};
mockComposition = new EventEmitter();
mockComposition.load = () => {
mockComposition.emit('add', testTelemetryObject);
return [testTelemetryObject];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement("div");
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
SpectralPlot
},
provide: {
openmct: openmct,
domainObject: spectralPlotObject,
composition: openmct.composition.get(spectralPlotObject)
},
template: "<spectral-plot></spectral-plot>"
});
cleanupFirst.push(() => {
component.$destroy();
component = undefined;
});
return telemetryPromise
.then(Vue.nextTick())
.then(() => {
plotViewComponentObject = component.$root.$children[0];
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
config = configStore.get(configId);
});
});
it("Renders a collapsed legend for every telemetry", () => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(1);
expect(legend[0].innerHTML).toEqual("Test Object");
});
}); */
describe("The stacked plot view", () => {
let testTelemetryObject;
let testTelemetryObject2;
@@ -990,4 +1164,39 @@ describe("the plugin", function () {
});
});
describe("the spectral plot", () => {
const mockObject = {
name: 'A Very Nice Spectral Plot',
key: 'telemetry.plot.spectral',
creatable: true
};
it('defines a spectral plot object type with the correct key', () => {
const objectDef = openmct.types.get('telemetry.plot.spectral').definition;
expect(objectDef.key).toEqual(mockObject.key);
});
it('is creatable', () => {
const objectDef = openmct.types.get('telemetry.plot.spectral').definition;
expect(objectDef.creatable).toEqual(mockObject.creatable);
});
});
describe("the aggregate spectral plot", () => {
const mockObject = {
name: 'An Even Nicer Aggregate Spectral Plot',
key: SPECTRAL_AGGREGATE_KEY,
creatable: true
};
it('defines a spectral plot object type with the correct key', () => {
const objectDef = openmct.types.get(SPECTRAL_AGGREGATE_KEY).definition;
expect(objectDef.key).toEqual(mockObject.key);
});
it('is creatable', () => {
const objectDef = openmct.types.get(SPECTRAL_AGGREGATE_KEY).definition;
expect(objectDef.creatable).toEqual(mockObject.creatable);
});
});
});

View File

@@ -0,0 +1,10 @@
export const CONFIGURATION_CHANGED = 'configurationChanged';
export const HOVER_VALUES_CHANGED = 'hoverValuesChanged';
export const HOVER_VALUES_CLEARED = 'hoverValuesCleared';
export const LEGEND_EXPANDED = 'plot-legends-expanded';
export const REALTIME_POLL_INTERVAL_IN_MS = 5000;
export const SPECTRAL_AGGREGATE_VIEW = 'plot.spectral.aggregate.view';
export const SPECTRAL_AGGREGATE_KEY = 'telemetry.plot.spectral.aggregate';
export const SUBSCRIBE = 'subscribe';
export const UNSUBSCRIBE = 'unsubscribe';

View File

@@ -0,0 +1,289 @@
<template>
<div ref="plotWrapper"
class="has-local-controls"
:class="{ 's-unsynced' : isZoomed }"
>
<div v-if="isZoomed"
class="l-state-indicators"
>
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
></span>
</div>
<div ref="plot"
class="c-spectral-aggregate-plot__plot"
></div>
<div ref="localControl"
class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
>
<button v-if="data.length"
class="c-button icon-reset"
:disabled="!isZoomed"
title="Reset pan/zoom"
@click="reset()"
>
</button>
</div>
</div>
</template>
<script>
import Plotly from 'plotly.js/dist/plotly';
import { HOVER_VALUES_CLEARED, HOVER_VALUES_CHANGED, SUBSCRIBE, UNSUBSCRIBE } from './SpectralAggregateConstants';
const PLOT_PADDING_IN_PERCENT = 1;
const MULTI_AXES_X_PADDING_PERCENT = {
LEFT: 8,
RIGHT: 94
};
export default {
inject: ['openmct', 'domainObject'],
props: [
'data',
'legendExpanded',
'plotAxisTitle'
],
data() {
return {
isZoomed: false,
primaryYAxisRange: {
min: '',
max: ''
},
xAxisRange: {
min: '',
max: ''
}
};
},
watch: {
data: {
immediate: false,
handler: 'updateData'
},
legendExpanded: {
immediate: false,
handler: 'updatePlot'
}
},
mounted() {
Plotly.newPlot(this.$refs.plot, Array.from(this.data), this.getLayout(), {
responsive: true,
displayModeBar: false
});
this.registerListeners();
},
beforeDestroy() {
this.$refs.plot.removeAllListeners();
if (this.plotResizeObserver) {
this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
clearTimeout(this.resizeTimer);
}
},
methods: {
getAxisMinMax(axis) {
const min = axis.autoSize
? ''
: axis.min;
const max = axis.autoSize
? ''
: axis.max;
return {
min,
max
};
},
getAxisPadding(min, max) {
return (Math.abs(max - min) * PLOT_PADDING_IN_PERCENT / 100) || 0;
},
getLayout() {
const yAxesMeta = this.getYAxisMeta();
const primaryYaxis = this.getYaxisLayout(yAxesMeta['1']);
const xAxisDomain = this.getXAxisDomain(yAxesMeta);
return {
// hovermode: 'closest',
// hoverdistance: -1,
autosize: true,
showlegend: false,
font: {
family: 'Helvetica Neue, Helvetica, Arial, sans-serif',
size: '12px',
color: '#666'
},
xaxis: {
domain: xAxisDomain,
// hoverformat: '.2r',
range: [this.xAxisRange.min, this.xAxisRange.max],
title: this.plotAxisTitle.xAxisTitle
// zeroline: false
},
yaxis: primaryYaxis,
margin: {
l: 40,
r: 10,
b: 40,
t: 20
},
paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent'
};
},
getYAxisMeta() {
const yAxisMeta = {};
this.data.forEach(d => {
const yAxisMetadata = d.yAxisMetadata;
const range = '1';
const side = 'left';
const name = '';
const unit = yAxisMetadata.units;
yAxisMeta[range] = {
range,
side,
name,
unit
};
});
return yAxisMeta;
},
getXAxisDomain(yAxisMeta) {
let leftPaddingPerc = 0;
let rightPaddingPerc = 100;
let rightSide = yAxisMeta && Object.values(yAxisMeta).filter((axisMeta => axisMeta.side === 'right'));
let leftSide = yAxisMeta && Object.values(yAxisMeta).filter((axisMeta => axisMeta.side === 'left'));
if (yAxisMeta && rightSide.length > 1) {
rightPaddingPerc = MULTI_AXES_X_PADDING_PERCENT.RIGHT;
}
if (yAxisMeta && leftSide.length > 1) {
leftPaddingPerc = MULTI_AXES_X_PADDING_PERCENT.LEFT;
}
return [leftPaddingPerc / 100, rightPaddingPerc / 100];
},
getYaxisLayout(yAxisMeta) {
if (!yAxisMeta) {
return {};
}
const { name, range, side = 'left', unit } = yAxisMeta;
const title = `${name} ${unit ? '(' + unit + ')' : ''}`;
const yaxis = {
// hoverformat: '.2r',
// showgrid: true,
title
// zeroline: false
};
let yAxistype = this.primaryYAxisRange;
if (range === '1') {
yaxis.range = [yAxistype.min, yAxistype.max];
return yaxis;
}
yaxis.range = [yAxistype.min, yAxistype.max];
yaxis.anchor = side.toLowerCase() === 'left'
? 'free'
: 'x';
yaxis.showline = side.toLowerCase() === 'left';
yaxis.side = side.toLowerCase();
yaxis.overlaying = 'y';
yaxis.position = 0.01;
return yaxis;
},
handleHover(isHovered, itemValues) {
return () => {
this.updateLocalControlPosition(isHovered);
const eventName = isHovered ? HOVER_VALUES_CHANGED : HOVER_VALUES_CLEARED;
this.$emit(eventName, { itemValues });
};
},
registerListeners() {
this.$refs.plot.on('plotly_hover', this.handleHover.bind(this, true));
this.$refs.plot.on('plotly_unhover', this.handleHover.bind(this, false));
this.$refs.plot.on('plotly_relayout', this.zoom);
this.resizeTimer = false;
if (window.ResizeObserver) {
this.plotResizeObserver = new ResizeObserver(() => {
// debounce and trigger window resize so that plotly can resize the plot
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
window.dispatchEvent(new Event('resize'));
}, 250);
});
this.plotResizeObserver.observe(this.$refs.plotWrapper);
}
},
reset() {
this.updatePlot();
this.isZoomed = false;
this.$emit(SUBSCRIBE);
},
updateData() {
this.updatePlot();
},
updateLocalControlPosition() {
const localControl = this.$refs.localControl;
localControl.style.display = 'none';
const plot = this.$refs.plot;
const bgLayer = this.$el.querySelector('.bglayer');
const plotBoundingRect = plot.getBoundingClientRect();
const bgLayerBoundingRect = bgLayer.getBoundingClientRect();
const top = bgLayerBoundingRect.top - plotBoundingRect.top + 5;
const left = bgLayerBoundingRect.left - plotBoundingRect.left + 5;
localControl.style.top = `${top}px`;
localControl.style.left = `${left}px`;
localControl.style.display = 'block';
},
updatePlot() {
if (!this.$refs || !this.$refs.plot) {
return;
}
Plotly.react(this.$refs.plot, Array.from(this.data), this.getLayout());
},
zoom(eventData) {
const autorange = eventData['xaxis.autorange'];
const { autosize } = eventData;
if (autosize || autorange) {
this.isZoomed = false;
this.reset();
return;
}
this.isZoomed = true;
this.$emit(UNSUBSCRIBE);
}
}
};
</script>
<style lang="scss">
.c-spectral-aggregate-plot__plot {
height: 100%;
}
.c-spectral-aggregate-plot-view .gl-plot__local-controls {
top: 25px;
}
.has-local-controls {
border: 1px solid transparent;
}
</style>

View File

@@ -0,0 +1,58 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 { SPECTRAL_AGGREGATE_KEY } from './SpectralAggregateConstants';
export default function SpectralAggregatePlotCompositionPolicy(openmct) {
function hasAggregateDomainAndRange(metadata) {
const rangeValues = metadata.valuesForHints(['domain']);
const domainValues = metadata.valuesForHints(['range']);
return rangeValues.length > 0
|| domainValues.length > 0;
}
function hasSpectralAggregateTelemetry(domainObject) {
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
return false;
}
let metadata = openmct.telemetry.getMetadata(domainObject);
return metadata.values().length > 0 && hasAggregateDomainAndRange(metadata);
}
function hasNoChildren(parentObject) {
return parentObject.composition && parentObject.composition.length < 1;
}
return {
allow: function (parent, child) {
if ((parent.type === SPECTRAL_AGGREGATE_KEY)
&& ((child.type !== 'telemetry.plot.overlay') && (hasSpectralAggregateTelemetry(child) === false) || (hasNoChildren(parent) === false))
) {
return false;
}
return true;
}
};
}

View File

@@ -0,0 +1,376 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 SpectralAggregatePlotCompositionPolicy from "./SpectralAggregatePlotCompositionPolicy";
import { createOpenMct } from "utils/testing";
describe("The spectral aggregation plot composition policy", () => {
let openmct;
const mockNonSpectralMetaData = {
"period": 10,
"amplitude": 1,
"offset": 0,
"dataRateInHz": 1,
"phase": 0,
"randomness": 0,
valuesForHints: () => {
return [
{
"key": "sin",
"name": "Sine",
"unit": "Hz",
"formatString": "%0.2f",
"hints": {
"range": 1,
"priority": 4
},
"source": "sin"
},
{
"key": "cos",
"name": "Cosine",
"unit": "deg",
"formatString": "%0.2f",
"hints": {
"range": 2,
"priority": 5
},
"source": "cos"
}
];
},
values: [
{
"key": "name",
"name": "Name",
"format": "string",
"source": "name",
"hints": {
"priority": 0
}
},
{
"key": "utc",
"name": "Time",
"format": "utc",
"hints": {
"domain": 1,
"priority": 1
},
"source": "utc"
},
{
"key": "yesterday",
"name": "Yesterday",
"format": "utc",
"hints": {
"domain": 2,
"priority": 2
},
"source": "yesterday"
},
{
"key": "cos",
"name": "Cosine",
"unit": "deg",
"formatString": "%0.2f",
"hints": {
"domain": 3,
"priority": 3
},
"source": "cos"
},
{
"key": "sin",
"name": "Sine",
"unit": "Hz",
"formatString": "%0.2f",
"hints": {
"range": 1,
"priority": 4
},
"source": "sin"
},
{
"key": "cos",
"name": "Cosine",
"unit": "deg",
"formatString": "%0.2f",
"hints": {
"range": 2,
"priority": 5
},
"source": "cos"
}
]
};
const mockGoodSpectralMetaData = {
"period": 10,
"amplitude": 1,
"offset": 0,
"dataRateInHz": 1,
"phase": 0,
"randomness": 0,
"wavelength": 0,
valuesForHints: () => {
return [
{
"key": "sin",
"name": "Sine",
"unit": "Hz",
"formatString": "%0.2f",
"hints": {
"range": 1,
"priority": 4
},
"source": "sin"
},
{
"key": "cos",
"name": "Cosine",
"unit": "deg",
"formatString": "%0.2f",
"hints": {
"range": 2,
"priority": 5
},
"source": "cos"
}
];
},
values: [
{
"key": "name",
"name": "Name",
"format": "string",
"source": "name",
"hints": {
"priority": 0
}
},
{
"key": "utc",
"name": "Time",
"format": "utc",
"hints": {
"domain": 1,
"priority": 1
},
"source": "utc"
},
{
"key": "yesterday",
"name": "Yesterday",
"format": "utc",
"hints": {
"domain": 2,
"priority": 2
},
"source": "yesterday"
},
{
"key": "cos",
"name": "Cosine",
"unit": "deg",
"formatString": "%0.2f",
"hints": {
"domain": 3,
"priority": 3
},
"source": "cos"
},
{
"key": "sin",
"name": "Sine",
"unit": "Hz",
"formatString": "%0.2f",
"hints": {
"range": 1,
"spectralAttribute": true
},
"source": "sin"
},
{
"key": "cos",
"name": "Cosine",
"unit": "deg",
"formatString": "%0.2f",
"hints": {
"range": 2,
"priority": 5
},
"source": "cos"
}
]
};
beforeEach(() => {
openmct = createOpenMct();
const mockTypeDef = {
telemetry: mockGoodSpectralMetaData
};
const mockTypeService = {
getType: () => {
return {
typeDef: mockTypeDef
};
}
};
openmct.$injector = {
get: () => {
return mockTypeService;
}
};
openmct.telemetry.isTelemetryObject = function (domainObject) {
return true;
};
});
it("exists", () => {
expect(SpectralAggregatePlotCompositionPolicy(openmct).allow).toBeDefined();
});
it("allow composition only for telemetry that provides/supports spectral data", () => {
const parent = {
"composition": [],
"configuration": {},
"name": "Some Spectral Aggregate Plot",
"type": "telemetry.plot.spectral.aggregate",
"location": "mine",
"modified": 1631005183584,
"persisted": 1631005183502,
"identifier": {
"namespace": "",
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
}
};
const child = {
"telemetry": {
"period": 10,
"amplitude": 1,
"offset": 0,
"dataRateInHz": 1,
"phase": 0,
"randomness": 0
},
"name": "Unnamed Sine Wave Generator",
"type": "generator",
"location": "mine",
"modified": 1630399715531,
"persisted": 1630399715531,
"identifier": {
"namespace": "",
"key": "21d61f2d-6d2d-4bea-8b0a-7f59fd504c6c"
}
};
expect(SpectralAggregatePlotCompositionPolicy(openmct).allow(parent, child)).toEqual(true);
});
it("disallows composition for telemetry that contain anything else", () => {
const mockTypeDef = {
telemetry: mockNonSpectralMetaData
};
const mockTypeService = {
getType: () => {
return {
typeDef: mockTypeDef
};
}
};
openmct.$injector = {
get: () => {
return mockTypeService;
}
};
const parent = {
"composition": [],
"configuration": {},
"name": "Some Spectral Aggregate Plot",
"type": "telemetry.plot.spectral.aggregate",
"location": "mine",
"modified": 1631005183584,
"persisted": 1631005183502,
"identifier": {
"namespace": "",
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
}
};
const child = {
"telemetry": {
"period": 10,
"amplitude": 1,
"offset": 0,
"dataRateInHz": 1,
"phase": 0,
"randomness": 0
},
"name": "Unnamed Sine Wave Generator",
"type": "generator",
"location": "mine",
"modified": 1630399715531,
"persisted": 1630399715531,
"identifier": {
"namespace": "",
"key": "21d61f2d-6d2d-4bea-8b0a-7f59fd504c6c"
}
};
expect(SpectralAggregatePlotCompositionPolicy(openmct).allow(parent, child)).toEqual(false);
});
it("passthrough for composition for non spectral aggregate plots", () => {
const parent = {
"composition": [],
"configuration": {},
"name": "Some Stacked Plot",
"type": "telemetry.plot.stacked",
"location": "mine",
"modified": 1631005183584,
"persisted": 1631005183502,
"identifier": {
"namespace": "",
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
}
};
const child = {
"telemetry": {
"period": 10,
"amplitude": 1,
"offset": 0,
"dataRateInHz": 1,
"phase": 0,
"randomness": 0
},
"name": "Unnamed Sine Wave Generator",
"type": "generator",
"location": "mine",
"modified": 1630399715531,
"persisted": 1630399715531,
"identifier": {
"namespace": "",
"key": "21d61f2d-6d2d-4bea-8b0a-7f59fd504c6c"
}
};
expect(SpectralAggregatePlotCompositionPolicy(openmct).allow(parent, child)).toEqual(true);
});
});

View File

@@ -0,0 +1,76 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 SpectralAggregateView from './SpectralAggregateView.vue';
import { SPECTRAL_AGGREGATE_KEY, SPECTRAL_AGGREGATE_VIEW } from './SpectralAggregateConstants';
import Vue from 'vue';
export default function SpectralAggregatePlotViewProvider(openmct) {
function isCompactView(objectPath) {
return objectPath.find(object => object.type === 'time-strip');
}
return {
key: SPECTRAL_AGGREGATE_VIEW,
name: 'Spectral Aggregate Plot',
cssClass: 'icon-telemetry',
canView(domainObject, objectPath) {
return domainObject && domainObject.type === SPECTRAL_AGGREGATE_KEY;
},
canEdit(domainObject, objectPath) {
return domainObject && domainObject.type === SPECTRAL_AGGREGATE_KEY;
},
view: function (domainObject, objectPath) {
let component;
return {
show: function (element) {
let isCompact = isCompactView(objectPath);
component = new Vue({
el: element,
components: {
SpectralAggregateView
},
provide: {
openmct,
domainObject
},
data() {
return {
options: {
compact: isCompact
}
};
},
template: '<spectral-aggregate-view :options="options"></spectral-aggregate-view>'
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
};
}
};
}

View File

@@ -0,0 +1,332 @@
<!--
Open MCT, Copyright (c) 2014-2021, 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-spectral-aggregate-plot-view gl-plot plot-legend-bottom"
:class="{'plot-legend-expanded': legendExpanded, 'plot-legend-collapsed': !legendExpanded }"
>
<SpectralAggregatePlot ref="spectralAggregatePlot"
class="c-spectral-aggregate-plot__plot-wrapper"
:data="visibleData"
:plot-axis-title="plotAxisTitle"
:legend-expanded="legendExpanded"
/>
</div>
</template>
<script>
import * as SPECTRAL_AGGREGATE from './SpectralAggregateConstants';
import ColorPalette from '../lib/ColorPalette';
import objectUtils from 'objectUtils';
import SpectralAggregatePlot from './SpectralAggregatePlot.vue';
export default {
components: {
SpectralAggregatePlot
},
inject: ['openmct', 'domainObject'],
data() {
return {
colorMapping: {},
composition: {},
currentDomainObject: this.domainObject,
isRealTime: (this.openmct.time.clock() !== undefined),
spectralAggregateTypes: {},
subscriptions: [],
telemetryObjects: {},
trace: [],
legendExpanded: false
};
},
computed: {
activeClock() {
return this.openmct.time.activeClock;
},
plotAxisTitle() {
const { xAxisMetadata = {}, yAxisMetadata = {} } = this.trace[0] || {};
const xAxisUnit = xAxisMetadata.units ? `(${xAxisMetadata.units})` : '';
const yAxisUnit = yAxisMetadata.units ? `(${yAxisMetadata.units})` : '';
return {
xAxisTitle: `${xAxisMetadata.name || ''} ${xAxisUnit}`,
yAxisTitle: `${yAxisMetadata.name || ''} ${yAxisUnit}`
};
},
visibleData() {
return this.trace.filter((trace) => trace.hidden !== true);
}
},
mounted() {
this.colorPalette = new ColorPalette();
this.loadComposition();
this.openmct.time.on('bounds', this.refreshData);
this.openmct.time.on('clock', this.clockChanged);
this.$refs.spectralAggregatePlot.$on(SPECTRAL_AGGREGATE.SUBSCRIBE, this.subscribeToAll);
this.$refs.spectralAggregatePlot.$on(SPECTRAL_AGGREGATE.UNSUBSCRIBE, this.removeAllSubscriptions);
this.unobserve = this.openmct.objects.observe(this.currentDomainObject, '*', this.updateDomainObject);
},
beforeDestroy() {
this.$refs.spectralAggregatePlot.$off();
this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.off('clock', this.clockChanged);
this.removeAllSubscriptions();
this.unobserve();
if (!this.composition) {
return;
}
this.composition.off('add', this.addTelemetryObject);
this.composition.off('remove', this.removeTelemetryObject);
},
methods: {
addColorForTelemetry(key) {
const color = this.colorPalette.getNextColor().asHexString();
this.colorMapping[key] = color;
return color;
},
addSpectralAggregateType(key, name, timestamp, color) {
this.$set(this.spectralAggregateTypes, key, {
name,
timestamp,
color
});
},
addTelemetryObject(telemetryObject) {
const key = objectUtils.makeKeyString(telemetryObject.identifier);
if (!this.colorMapping[key]) {
this.addColorForTelemetry(key);
}
this.telemetryObjects[key] = telemetryObject;
this.requestDataFor(telemetryObject);
this.subscribeToObject(telemetryObject);
},
addTrace(trace, key) {
if (!this.trace.length) {
this.trace = this.trace.concat([trace]);
return;
}
let isInTrace = false;
const newTrace = this.trace.map((currentTrace, index) => {
if (currentTrace.key !== key) {
return currentTrace;
}
isInTrace = true;
return trace;
});
this.trace = isInTrace ? newTrace : newTrace.concat([trace]);
this.updateTraceVisibility();
},
clockChanged() {
this.isRealTime = this.openmct.time.clock() !== undefined;
this.removeAllSubscriptions();
this.subscribeToAll();
},
getAxisMetadata(telemetryObject) {
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
const yAxisMetadata = metadata.valuesForHints(['range'])[0];
const xAxisMetadata = metadata.valueMetadatas.filter((valueMetadata => {
return valueMetadata.key !== 'name' && !this.openmct.time.getAllTimeSystems().find((timeSystem) => timeSystem.key === valueMetadata.key);
}));
return {
xAxisMetadata,
yAxisMetadata
};
},
getOptions(telemetryObject) {
const { start, end } = this.openmct.time.bounds();
return {
averages: 0,
end,
start,
startTime: null,
spectra: true
};
},
loadComposition() {
this.composition = this.openmct.composition.get(this.currentDomainObject);
if (!this.composition) {
this.addTelemetryObject(this.currentDomainObject);
return;
}
this.composition.on('add', this.addTelemetryObject);
this.composition.on('remove', this.removeTelemetryObject);
this.composition.load();
},
refreshData(bounds, isTick) {
if (!isTick) {
this.colorPalette.reset();
const telemetryObjects = Object.values(this.telemetryObjects);
telemetryObjects.forEach(this.requestDataFor);
}
},
removeAllSubscriptions() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
this.subscriptions = [];
},
removeTelemetryObject(identifier) {
const key = objectUtils.makeKeyString(identifier);
delete this.telemetryObjects[key];
this.$delete(this.spectralAggregateTypes, key);
this.subscriptions.forEach(subscription => {
if (subscription.key !== key) {
return;
}
subscription.unsubscribe();
delete this.subscriptions[key];
});
this.trace = this.trace.filter(t => t.key !== key);
},
processData(telemetryObject, data, axisMetadata) {
const key = objectUtils.makeKeyString(telemetryObject.identifier);
const formattedTimestamp = 'N/A';
const color = this.colorMapping[key];
const spectralAggregateValue = this.spectralAggregateTypes[key];
if (!spectralAggregateValue) {
this.addSpectralAggregateType(key, telemetryObject.name, formattedTimestamp, color);
}
if (data.message) {
this.openmct.notifications.alert(data.message);
}
let xValues = [];
let yValues = [];
axisMetadata.xAxisMetadata.forEach((metadata) => {
xValues.push(metadata.name);
if (data[metadata.key]) {
yValues.push(data[metadata.key]);
} else {
yValues.push('');
}
});
const trace = {
key,
name: telemetryObject.name,
x: xValues,
y: yValues,
xAxisMetadata: axisMetadata.xAxisMetadata,
yAxisMetadata: axisMetadata.yAxisMetadata,
type: 'bar'
};
this.addTrace(trace, key);
},
requestDataFor(telemetryObject) {
const axisMetadata = this.getAxisMetadata(telemetryObject);
this.openmct.telemetry.request(telemetryObject, this.getOptions(telemetryObject))
.then(data => {
data.forEach((datum) => {
this.processData(telemetryObject, datum, axisMetadata);
});
});
},
subscribeToObject(telemetryObject) {
const key = objectUtils.makeKeyString(telemetryObject.identifier);
const found = Object.values(this.subscriptions).findIndex(objectKey => objectKey === key);
if (found > -1) {
this.subscriptions[found].unsubscribe();
delete this.subscriptions[found];
}
const options = this.getOptions(telemetryObject);
const axisMetadata = this.getAxisMetadata(telemetryObject);
const unsubscribe = this.openmct.telemetry.subscribe(telemetryObject,
data => this.processData(telemetryObject, data, axisMetadata)
, options);
this.subscriptions.push({
key,
unsubscribe
});
},
subscribeToAll() {
this.colorPalette.reset();
const telemetryObjects = Object.values(this.telemetryObjects);
telemetryObjects.forEach(this.subscribeToObject);
},
updateDomainObject(newDomainObject) {
this.currentDomainObject = newDomainObject;
},
updateTraceVisibility() {
// We create a copy here to improve performance since visibleData - a computed property - is dependent on this.trace.
this.trace = this.trace.map((currentTrace, index) => {
currentTrace.hidden = this.spectralAggregateTypes[currentTrace.key].hidden === true;
return currentTrace;
});
}
}
};
</script>
<style lang="scss">
.c-spectral-aggregate-plot {
> * + * {
margin-top: 5px;
}
&-view {
display: flex;
flex-direction: column;
overflow: hidden;
}
&__plot-wrapper {
flex: 1 1 auto;
min-height: 300px;
min-width: 300px;
}
&__legend-wrapper {
flex: 0 1 auto;
overflow: auto;
padding-right: 5px;
}
}
</style>

View File

@@ -0,0 +1,36 @@
export default function SpectralPlotCompositionPolicy(openmct) {
function hasSpectralDomainAndRange(metadata) {
const rangeValues = metadata.valuesForHints(['range']);
const domainValues = metadata.valuesForHints(['domain']);
const containsSomeSpectralData = domainValues.some(value => {
return ((value.key === 'wavelength') || (value.key === 'frequency'));
});
return rangeValues.length > 0
&& domainValues.length > 0
&& containsSomeSpectralData;
}
function hasSpectralTelemetry(domainObject) {
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
return false;
}
let metadata = openmct.telemetry.getMetadata(domainObject);
return metadata.values().length > 0 && hasSpectralDomainAndRange(metadata);
}
return {
allow: function (parent, child) {
if ((parent.type === 'telemetry.plot.spectral')
&& ((child.type !== 'telemetry.plot.overlay') && (hasSpectralTelemetry(child) === false))
) {
return false;
}
return true;
}
};
}

View File

@@ -0,0 +1,75 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 SpectralView from './SpectralView.vue';
import Vue from 'vue';
export default function SpectralPlotViewProvider(openmct) {
function isCompactView(objectPath) {
return objectPath.find(object => object.type === 'time-strip');
}
return {
key: 'plot-spectral',
name: 'Spectral Plot',
cssClass: 'icon-telemetry',
canView(domainObject, objectPath) {
return domainObject && domainObject.type === 'telemetry.plot.spectral';
},
canEdit(domainObject, objectPath) {
return domainObject && domainObject.type === 'telemetry.plot.spectral';
},
view: function (domainObject, objectPath) {
let component;
return {
show: function (element) {
let isCompact = isCompactView(objectPath);
component = new Vue({
el: element,
components: {
SpectralView
},
provide: {
openmct,
domainObject
},
data() {
return {
options: {
compact: isCompact
}
};
},
template: '<spectral-view :options="options"></spectral-view>'
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
};
}
};
}

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script>
export default {
inject: ['openmct', 'domainObject']
};
</script>

View File

@@ -154,6 +154,7 @@ $glyph-icon-flag: '\e92a';
$glyph-icon-eye-disabled: '\e92b';
$glyph-icon-notebook-page: '\e92c';
$glyph-icon-unlocked: '\e92d';
$glyph-icon-circle: '\e92e';
$glyph-icon-arrows-right-left: '\ea00';
$glyph-icon-arrows-up-down: '\ea01';
$glyph-icon-bullet: '\ea02';
@@ -257,6 +258,7 @@ $glyph-icon-conditional: '\eb27';
$glyph-icon-condition-widget: '\eb28';
$glyph-icon-alphanumeric: '\eb29';
$glyph-icon-image-telemetry: '\eb2a';
$glyph-icon-telemetry-aggregate: '\eb2b';
/************************** GLYPHS AS DATA URI */
// Only objects have been converted, for use in Create menu and folder views

View File

@@ -85,6 +85,7 @@
.icon-eye-disabled { @include glyphBefore($glyph-icon-eye-disabled); }
.icon-notebook-page { @include glyphBefore($glyph-icon-notebook-page); }
.icon-unlocked { @include glyphBefore($glyph-icon-unlocked); }
.icon-circle { @include glyphBefore($glyph-icon-circle); }
.icon-arrows-right-left { @include glyphBefore($glyph-icon-arrows-right-left); }
.icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); }
.icon-bullet { @include glyphBefore($glyph-icon-bullet); }
@@ -188,6 +189,7 @@
.icon-condition-widget { @include glyphBefore($glyph-icon-condition-widget); }
.icon-alphanumeric { @include glyphBefore($glyph-icon-alphanumeric); }
.icon-image-telemetry { @include glyphBefore($glyph-icon-image-telemetry); }
.icon-telemetry-aggregate { @include glyphBefore($glyph-icon-telemetry-aggregate); }
/************************** 12 PX CLASSES */
// TODO: sync with 16px redo as of 10/25/18

View File

@@ -2,7 +2,7 @@
"metadata": {
"name": "Open MCT Symbols 16px",
"lastOpened": 0,
"created": 1621648023886
"created": 1629996145999
},
"iconSets": [
{
@@ -375,13 +375,21 @@
"code": 59693,
"tempChar": ""
},
{
"order": 197,
"id": 169,
"name": "icon-circle",
"prevSize": 24,
"code": 59694,
"tempChar": ""
},
{
"order": 27,
"id": 105,
"name": "icon-arrows-right-left",
"prevSize": 24,
"code": 59904,
"tempChar": ""
"tempChar": ""
},
{
"order": 26,
@@ -389,7 +397,7 @@
"name": "icon-arrows-up-down",
"prevSize": 24,
"code": 59905,
"tempChar": ""
"tempChar": ""
},
{
"order": 68,
@@ -397,7 +405,7 @@
"name": "icon-bullet",
"prevSize": 24,
"code": 59906,
"tempChar": ""
"tempChar": ""
},
{
"order": 150,
@@ -405,7 +413,7 @@
"prevSize": 24,
"code": 59907,
"name": "icon-calendar",
"tempChar": ""
"tempChar": ""
},
{
"order": 45,
@@ -413,7 +421,7 @@
"name": "icon-chain-links",
"prevSize": 24,
"code": 59908,
"tempChar": ""
"tempChar": ""
},
{
"order": 73,
@@ -421,7 +429,7 @@
"name": "icon-download",
"prevSize": 24,
"code": 59909,
"tempChar": ""
"tempChar": ""
},
{
"order": 39,
@@ -429,7 +437,7 @@
"name": "icon-duplicate",
"prevSize": 24,
"code": 59910,
"tempChar": ""
"tempChar": ""
},
{
"order": 50,
@@ -437,7 +445,7 @@
"name": "icon-folder-new",
"prevSize": 24,
"code": 59911,
"tempChar": ""
"tempChar": ""
},
{
"order": 138,
@@ -445,7 +453,7 @@
"name": "icon-fullscreen-collapse",
"prevSize": 24,
"code": 59912,
"tempChar": ""
"tempChar": ""
},
{
"order": 139,
@@ -453,7 +461,7 @@
"name": "icon-fullscreen-expand",
"prevSize": 24,
"code": 59913,
"tempChar": ""
"tempChar": ""
},
{
"order": 122,
@@ -461,7 +469,7 @@
"name": "icon-layers",
"prevSize": 24,
"code": 59914,
"tempChar": ""
"tempChar": ""
},
{
"order": 151,
@@ -469,7 +477,7 @@
"name": "icon-line-horz",
"prevSize": 24,
"code": 59915,
"tempChar": ""
"tempChar": ""
},
{
"order": 100,
@@ -477,7 +485,7 @@
"name": "icon-magnify",
"prevSize": 24,
"code": 59916,
"tempChar": ""
"tempChar": ""
},
{
"order": 99,
@@ -485,7 +493,7 @@
"name": "icon-magnify-in",
"prevSize": 24,
"code": 59917,
"tempChar": ""
"tempChar": ""
},
{
"order": 101,
@@ -493,7 +501,7 @@
"name": "icon-magnify-out-v2",
"prevSize": 24,
"code": 59918,
"tempChar": ""
"tempChar": ""
},
{
"order": 103,
@@ -501,7 +509,7 @@
"name": "icon-menu",
"prevSize": 24,
"code": 59919,
"tempChar": ""
"tempChar": ""
},
{
"order": 124,
@@ -509,7 +517,7 @@
"name": "icon-move",
"prevSize": 24,
"code": 59920,
"tempChar": ""
"tempChar": ""
},
{
"order": 7,
@@ -517,7 +525,7 @@
"name": "icon-new-window",
"prevSize": 24,
"code": 59921,
"tempChar": ""
"tempChar": ""
},
{
"order": 63,
@@ -525,7 +533,7 @@
"name": "icon-paint-bucket-v2",
"prevSize": 24,
"code": 59922,
"tempChar": ""
"tempChar": ""
},
{
"order": 15,
@@ -533,7 +541,7 @@
"name": "icon-pencil",
"prevSize": 24,
"code": 59923,
"tempChar": ""
"tempChar": ""
},
{
"order": 54,
@@ -541,7 +549,7 @@
"name": "icon-pencil-edit-in-place",
"prevSize": 24,
"code": 59924,
"tempChar": ""
"tempChar": ""
},
{
"order": 40,
@@ -549,7 +557,7 @@
"name": "icon-play",
"prevSize": 24,
"code": 59925,
"tempChar": ""
"tempChar": ""
},
{
"order": 125,
@@ -557,7 +565,7 @@
"name": "icon-pause",
"prevSize": 24,
"code": 59926,
"tempChar": ""
"tempChar": ""
},
{
"order": 119,
@@ -565,7 +573,7 @@
"name": "icon-plot-resource",
"prevSize": 24,
"code": 59927,
"tempChar": ""
"tempChar": ""
},
{
"order": 48,
@@ -573,7 +581,7 @@
"name": "icon-pointer-left",
"prevSize": 24,
"code": 59928,
"tempChar": ""
"tempChar": ""
},
{
"order": 47,
@@ -581,7 +589,7 @@
"name": "icon-pointer-right",
"prevSize": 24,
"code": 59929,
"tempChar": ""
"tempChar": ""
},
{
"order": 85,
@@ -589,7 +597,7 @@
"name": "icon-refresh",
"prevSize": 24,
"code": 59930,
"tempChar": ""
"tempChar": ""
},
{
"order": 55,
@@ -597,7 +605,7 @@
"name": "icon-save",
"prevSize": 24,
"code": 59931,
"tempChar": ""
"tempChar": ""
},
{
"order": 56,
@@ -605,7 +613,7 @@
"name": "icon-save-as",
"prevSize": 24,
"code": 59932,
"tempChar": ""
"tempChar": ""
},
{
"order": 58,
@@ -613,7 +621,7 @@
"name": "icon-sine",
"prevSize": 24,
"code": 59933,
"tempChar": ""
"tempChar": ""
},
{
"order": 113,
@@ -621,7 +629,7 @@
"name": "icon-font",
"prevSize": 24,
"code": 59934,
"tempChar": ""
"tempChar": ""
},
{
"order": 41,
@@ -629,7 +637,7 @@
"name": "icon-thumbs-strip",
"prevSize": 24,
"code": 59935,
"tempChar": ""
"tempChar": ""
},
{
"order": 146,
@@ -637,7 +645,7 @@
"name": "icon-two-parts-both",
"prevSize": 24,
"code": 59936,
"tempChar": ""
"tempChar": ""
},
{
"order": 145,
@@ -645,7 +653,7 @@
"name": "icon-two-parts-one-only",
"prevSize": 24,
"code": 59937,
"tempChar": ""
"tempChar": ""
},
{
"order": 82,
@@ -653,7 +661,7 @@
"name": "icon-resync",
"prevSize": 24,
"code": 59938,
"tempChar": ""
"tempChar": ""
},
{
"order": 86,
@@ -661,7 +669,7 @@
"name": "icon-reset",
"prevSize": 24,
"code": 59939,
"tempChar": ""
"tempChar": ""
},
{
"order": 61,
@@ -669,7 +677,7 @@
"name": "icon-x-in-circle",
"prevSize": 24,
"code": 59940,
"tempChar": ""
"tempChar": ""
},
{
"order": 84,
@@ -677,7 +685,7 @@
"name": "icon-brightness",
"prevSize": 24,
"code": 59941,
"tempChar": ""
"tempChar": ""
},
{
"order": 83,
@@ -685,7 +693,7 @@
"name": "icon-contrast",
"prevSize": 24,
"code": 59942,
"tempChar": ""
"tempChar": ""
},
{
"order": 87,
@@ -693,7 +701,7 @@
"name": "icon-expand",
"prevSize": 24,
"code": 59943,
"tempChar": ""
"tempChar": ""
},
{
"order": 89,
@@ -701,7 +709,7 @@
"name": "icon-list-view",
"prevSize": 24,
"code": 59944,
"tempChar": ""
"tempChar": ""
},
{
"order": 133,
@@ -709,7 +717,7 @@
"name": "icon-grid-snap-to",
"prevSize": 24,
"code": 59945,
"tempChar": ""
"tempChar": ""
},
{
"order": 132,
@@ -717,7 +725,7 @@
"name": "icon-grid-snap-no",
"prevSize": 24,
"code": 59946,
"tempChar": ""
"tempChar": ""
},
{
"order": 94,
@@ -725,7 +733,7 @@
"name": "icon-frame-show",
"prevSize": 24,
"code": 59947,
"tempChar": ""
"tempChar": ""
},
{
"order": 95,
@@ -733,7 +741,7 @@
"name": "icon-frame-hide",
"prevSize": 24,
"code": 59948,
"tempChar": ""
"tempChar": ""
},
{
"order": 97,
@@ -741,7 +749,7 @@
"name": "icon-import",
"prevSize": 24,
"code": 59949,
"tempChar": ""
"tempChar": ""
},
{
"order": 96,
@@ -749,7 +757,7 @@
"name": "icon-export",
"prevSize": 24,
"code": 59950,
"tempChar": ""
"tempChar": ""
},
{
"order": 194,
@@ -757,7 +765,7 @@
"name": "icon-font-size",
"prevSize": 24,
"code": 59951,
"tempChar": ""
"tempChar": ""
},
{
"order": 163,
@@ -765,7 +773,7 @@
"name": "icon-clear-data",
"prevSize": 24,
"code": 59952,
"tempChar": ""
"tempChar": ""
},
{
"order": 173,
@@ -773,7 +781,7 @@
"name": "icon-history",
"prevSize": 24,
"code": 59953,
"tempChar": ""
"tempChar": ""
},
{
"order": 181,
@@ -781,7 +789,7 @@
"name": "icon-arrow-up-to-parent",
"prevSize": 24,
"code": 59954,
"tempChar": ""
"tempChar": ""
},
{
"order": 184,
@@ -789,7 +797,7 @@
"name": "icon-crosshair-in-circle",
"prevSize": 24,
"code": 59955,
"tempChar": ""
"tempChar": ""
},
{
"order": 185,
@@ -797,7 +805,7 @@
"name": "icon-target",
"prevSize": 24,
"code": 59956,
"tempChar": ""
"tempChar": ""
},
{
"order": 187,
@@ -805,7 +813,7 @@
"name": "icon-items-collapse",
"prevSize": 24,
"code": 59957,
"tempChar": ""
"tempChar": ""
},
{
"order": 188,
@@ -813,7 +821,7 @@
"name": "icon-items-expand",
"prevSize": 24,
"code": 59958,
"tempChar": ""
"tempChar": ""
},
{
"order": 190,
@@ -821,7 +829,7 @@
"name": "icon-3-dots",
"prevSize": 24,
"code": 59959,
"tempChar": ""
"tempChar": ""
},
{
"order": 193,
@@ -829,7 +837,7 @@
"name": "icon-grid-on",
"prevSize": 24,
"code": 59960,
"tempChar": ""
"tempChar": ""
},
{
"order": 192,
@@ -837,7 +845,7 @@
"name": "icon-grid-off",
"prevSize": 24,
"code": 59961,
"tempChar": ""
"tempChar": ""
},
{
"order": 191,
@@ -845,7 +853,7 @@
"name": "icon-camera",
"prevSize": 24,
"code": 59962,
"tempChar": ""
"tempChar": ""
},
{
"order": 196,
@@ -853,7 +861,7 @@
"name": "icon-folders-collapse",
"prevSize": 24,
"code": 59963,
"tempChar": ""
"tempChar": ""
},
{
"order": 144,
@@ -861,7 +869,7 @@
"name": "icon-activity",
"prevSize": 24,
"code": 60160,
"tempChar": ""
"tempChar": ""
},
{
"order": 104,
@@ -869,7 +877,7 @@
"name": "icon-activity-mode",
"prevSize": 24,
"code": 60161,
"tempChar": ""
"tempChar": ""
},
{
"order": 137,
@@ -877,7 +885,7 @@
"name": "icon-autoflow-tabular",
"prevSize": 24,
"code": 60162,
"tempChar": ""
"tempChar": ""
},
{
"order": 115,
@@ -885,7 +893,7 @@
"name": "icon-clock",
"prevSize": 24,
"code": 60163,
"tempChar": ""
"tempChar": ""
},
{
"order": 2,
@@ -893,7 +901,7 @@
"name": "icon-database",
"prevSize": 24,
"code": 60164,
"tempChar": ""
"tempChar": ""
},
{
"order": 3,
@@ -901,7 +909,7 @@
"name": "icon-database-query",
"prevSize": 24,
"code": 60165,
"tempChar": ""
"tempChar": ""
},
{
"order": 67,
@@ -909,7 +917,7 @@
"name": "icon-dataset",
"prevSize": 24,
"code": 60166,
"tempChar": ""
"tempChar": ""
},
{
"order": 59,
@@ -917,7 +925,7 @@
"name": "icon-datatable",
"prevSize": 24,
"code": 60167,
"tempChar": ""
"tempChar": ""
},
{
"order": 136,
@@ -925,7 +933,7 @@
"name": "icon-dictionary",
"prevSize": 24,
"code": 60168,
"tempChar": ""
"tempChar": ""
},
{
"order": 51,
@@ -933,7 +941,7 @@
"name": "icon-folder",
"prevSize": 24,
"code": 60169,
"tempChar": ""
"tempChar": ""
},
{
"order": 147,
@@ -941,7 +949,7 @@
"name": "icon-image",
"prevSize": 24,
"code": 60170,
"tempChar": ""
"tempChar": ""
},
{
"order": 4,
@@ -949,7 +957,7 @@
"name": "icon-layout",
"prevSize": 24,
"code": 60171,
"tempChar": ""
"tempChar": ""
},
{
"order": 24,
@@ -957,7 +965,7 @@
"name": "icon-object",
"prevSize": 24,
"code": 60172,
"tempChar": ""
"tempChar": ""
},
{
"order": 52,
@@ -965,7 +973,7 @@
"name": "icon-object-unknown",
"prevSize": 24,
"code": 60173,
"tempChar": ""
"tempChar": ""
},
{
"order": 105,
@@ -973,7 +981,7 @@
"name": "icon-packet",
"prevSize": 24,
"code": 60174,
"tempChar": ""
"tempChar": ""
},
{
"order": 126,
@@ -981,7 +989,7 @@
"name": "icon-page",
"prevSize": 24,
"code": 60175,
"tempChar": ""
"tempChar": ""
},
{
"order": 130,
@@ -989,7 +997,7 @@
"name": "icon-plot-overlay",
"prevSize": 24,
"code": 60176,
"tempChar": ""
"tempChar": ""
},
{
"order": 80,
@@ -997,7 +1005,7 @@
"name": "icon-plot-stacked",
"prevSize": 24,
"code": 60177,
"tempChar": ""
"tempChar": ""
},
{
"order": 134,
@@ -1005,7 +1013,7 @@
"name": "icon-session",
"prevSize": 24,
"code": 60178,
"tempChar": ""
"tempChar": ""
},
{
"order": 109,
@@ -1013,7 +1021,7 @@
"name": "icon-tabular",
"prevSize": 24,
"code": 60179,
"tempChar": ""
"tempChar": ""
},
{
"order": 107,
@@ -1021,7 +1029,7 @@
"name": "icon-tabular-lad",
"prevSize": 24,
"code": 60180,
"tempChar": ""
"tempChar": ""
},
{
"order": 106,
@@ -1029,7 +1037,7 @@
"name": "icon-tabular-lad-set",
"prevSize": 24,
"code": 60181,
"tempChar": ""
"tempChar": ""
},
{
"order": 70,
@@ -1037,7 +1045,7 @@
"name": "icon-tabular-realtime",
"prevSize": 24,
"code": 60182,
"tempChar": ""
"tempChar": ""
},
{
"order": 60,
@@ -1045,7 +1053,7 @@
"name": "icon-tabular-scrolling",
"prevSize": 24,
"code": 60183,
"tempChar": ""
"tempChar": ""
},
{
"order": 131,
@@ -1053,7 +1061,7 @@
"name": "icon-telemetry",
"prevSize": 24,
"code": 60184,
"tempChar": ""
"tempChar": ""
},
{
"order": 108,
@@ -1061,7 +1069,7 @@
"name": "icon-timeline",
"prevSize": 24,
"code": 60185,
"tempChar": ""
"tempChar": ""
},
{
"order": 81,
@@ -1069,7 +1077,7 @@
"name": "icon-timer",
"prevSize": 24,
"code": 60186,
"tempChar": ""
"tempChar": ""
},
{
"order": 69,
@@ -1077,7 +1085,7 @@
"name": "icon-topic",
"prevSize": 24,
"code": 60187,
"tempChar": ""
"tempChar": ""
},
{
"order": 79,
@@ -1085,7 +1093,7 @@
"name": "icon-box-with-dashed-lines-v2",
"prevSize": 24,
"code": 60188,
"tempChar": ""
"tempChar": ""
},
{
"order": 90,
@@ -1093,7 +1101,7 @@
"name": "icon-summary-widget",
"prevSize": 24,
"code": 60189,
"tempChar": ""
"tempChar": ""
},
{
"order": 92,
@@ -1101,7 +1109,7 @@
"name": "icon-notebook",
"prevSize": 24,
"code": 60190,
"tempChar": ""
"tempChar": ""
},
{
"order": 168,
@@ -1109,7 +1117,7 @@
"name": "icon-tabs-view",
"prevSize": 24,
"code": 60191,
"tempChar": ""
"tempChar": ""
},
{
"order": 117,
@@ -1117,7 +1125,7 @@
"name": "icon-flexible-layout",
"prevSize": 24,
"code": 60192,
"tempChar": ""
"tempChar": ""
},
{
"order": 166,
@@ -1125,7 +1133,7 @@
"name": "icon-generator-sine",
"prevSize": 24,
"code": 60193,
"tempChar": ""
"tempChar": ""
},
{
"order": 167,
@@ -1133,7 +1141,7 @@
"name": "icon-generator-event",
"prevSize": 24,
"code": 60194,
"tempChar": ""
"tempChar": ""
},
{
"order": 165,
@@ -1141,7 +1149,7 @@
"name": "icon-gauge-v2",
"prevSize": 24,
"code": 60195,
"tempChar": ""
"tempChar": ""
},
{
"order": 170,
@@ -1149,7 +1157,7 @@
"name": "icon-spectra",
"prevSize": 24,
"code": 60196,
"tempChar": ""
"tempChar": ""
},
{
"order": 171,
@@ -1157,7 +1165,7 @@
"name": "icon-telemetry-spectra",
"prevSize": 24,
"code": 60197,
"tempChar": ""
"tempChar": ""
},
{
"order": 172,
@@ -1165,7 +1173,7 @@
"name": "icon-pushbutton",
"prevSize": 24,
"code": 60198,
"tempChar": ""
"tempChar": ""
},
{
"order": 174,
@@ -1173,7 +1181,7 @@
"name": "icon-conditional",
"prevSize": 24,
"code": 60199,
"tempChar": ""
"tempChar": ""
},
{
"order": 178,
@@ -1181,7 +1189,7 @@
"name": "icon-condition-widget",
"prevSize": 24,
"code": 60200,
"tempChar": ""
"tempChar": ""
},
{
"order": 180,
@@ -1189,7 +1197,7 @@
"name": "icon-alphanumeric",
"prevSize": 24,
"code": 60201,
"tempChar": ""
"tempChar": ""
},
{
"order": 183,
@@ -1197,7 +1205,15 @@
"name": "icon-image-telemetry",
"prevSize": 24,
"code": 60202,
"tempChar": ""
"tempChar": ""
},
{
"order": 198,
"id": 170,
"name": "icon-telemetry-aggregate",
"prevSize": 24,
"code": 60203,
"tempChar": ""
}
],
"id": 0,
@@ -2000,6 +2016,26 @@
]
}
},
{
"id": 169,
"paths": [
"M1024 512c0 282.77-229.23 512-512 512s-512-229.23-512-512c0-282.77 229.23-512 512-512s512 229.23 512 512z"
],
"attrs": [
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 16,
"tags": [
"icon-circle"
],
"colorPermutations": {
"12552552551": [
{}
]
}
},
{
"id": 105,
"paths": [
@@ -3784,6 +3820,32 @@
{}
]
}
},
{
"id": 170,
"paths": [
"M78 395.44c14-41.44 37.48-100.8 69.2-148.36 38.62-57.78 82.38-87.080 130.14-87.080s91.5 29.3 130 87.080c31.72 47.56 55.14 106.92 69.2 148.36 30.88 90.96 63.12 134.98 78 146.54 14.94-11.56 47.2-55.58 78-146.54 14-41.44 37.48-100.8 69.22-148.36q27.8-41.7 59.12-63.5c-75.7-111.377-201.81-183.58-344.783-183.58-0.034 0-0.068 0-0.103 0l0.006-0c-229.76 0-416 186.24-416 416-0 0.071-0 0.156-0 0.24 0 39.119 5.396 76.977 15.484 112.871l-0.704-2.931c16.78-21.74 40.4-63.34 63.22-130.74z",
"M754 436.56c-14 41.44-37.48 100.8-69.2 148.36-38.56 57.78-82.32 87.080-130 87.080s-91.5-29.3-130-87.080c-31.72-47.56-55.14-106.92-69.2-148.36-30.88-90.96-63.14-134.98-78-146.54-14.94 11.56-47.2 55.58-78 146.54-14.38 41.44-37.8 100.8-69.6 148.36q-27.8 41.7-59.12 63.5c75.7 111.378 201.81 183.58 344.783 183.58 0.119 0 0.237-0 0.356-0l-0.019 0c229.76 0 416-186.24 416-416 0-0.071 0-0.156 0-0.24 0-39.119-5.396-76.977-15.484-112.871l0.704 2.931c-16.78 21.74-40.4 63.34-63.22 130.74z",
"M921.56 334.62c4.098 24.449 6.44 52.617 6.44 81.332 0 0.017-0 0.034-0 0.051l0-0.003c0 0.095 0 0.208 0 0.32 0 282.593-229.087 511.68-511.68 511.68-0.113 0-0.225-0-0.338-0l0.018 0c-0.014 0-0.031 0-0.048 0-28.716 0-56.884-2.342-84.325-6.845l2.993 0.405c72.483 63.623 168.109 102.44 272.802 102.44 0.203 0 0.406-0 0.61-0l-0.031 0c229.76 0 416-186.24 416-416 0-0.172 0-0.375 0-0.578 0-104.692-38.817-200.319-102.844-273.271l0.404 0.47z"
],
"attrs": [
{},
{},
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 16,
"tags": [
"icon-telemetry-aggregate"
],
"colorPermutations": {
"12552552551": [
{},
{},
{}
]
}
}
],
"invisible": false,

View File

@@ -53,6 +53,7 @@
<glyph unicode="&#xe92b;" glyph-name="icon-eye-disabled" d="M209.46 223.32q-7.46 9.86-14.26 20.28c-14.737 21.984-27.741 47.184-37.759 73.847l-0.841 2.553c11.078 29.259 24.068 54.443 39.51 77.869l-0.91-1.469c23.221 34.963 50.705 64.8 82.207 89.793l0.793 0.607c57.663 45.719 130.179 75.053 209.311 79.947l1.069 0.053 114.48 140.88c-27.366 5.017-58.869 7.898-91.041 7.92h-0.019c-245.8 0-452.2-168-510.8-395.6 21.856-82.93 60.906-154.847 113.325-214.773l-0.525 0.613zM814.76 416.92q7.52-10 14.44-20.52c14.737-21.984 27.741-47.184 37.759-73.847l0.841-2.553c-10.859-29.216-23.863-54.416-39.447-77.748l0.847 1.348c-23.221-34.963-50.705-64.8-82.207-89.793l-0.793-0.607c-57.762-45.834-130.437-75.216-209.743-80.049l-1.057-0.051-114.46-140.86c27.346-4.988 58.817-7.84 90.955-7.84 0.037 0 0.074 0 0.111 0h-0.005c245.8 0 452.2 168 510.8 395.6-21.856 82.93-60.906 154.847-113.325 214.773l0.525-0.613zM832 832l-832-1024h192l832 1024h-192z" />
<glyph unicode="&#xe92c;" glyph-name="icon-notebook-page" d="M830 770h-830l-4-702c0-106.6 87.4-194 194-194h640c106.6 0 194 87.4 194 194v508c0 106.8-87.4 194-194 194zM832 386l-384-384-192 192v256l192-192 384 384v-256z" />
<glyph unicode="&#xe92d;" glyph-name="icon-unlocked" d="M768 832c-141.339-0.114-255.886-114.661-256-255.989v-128.011h-448c-35.301-0.113-63.887-28.699-64-63.989v-512.011c0.113-35.301 28.699-63.887 63.989-64h638.011c35.301 0.113 63.887 28.699 64 63.989v512.011c-0.113 35.301-28.699 63.887-63.989 64h-62.011v128c0 70.692 57.308 128 128 128s128-57.308 128-128v0-128h128v128c-0.114 141.339-114.661 255.886-255.989 256h-0.011z" />
<glyph unicode="&#xe92e;" glyph-name="icon-circle" d="M1024 320c0-282.77-229.23-512-512-512s-512 229.23-512 512c0 282.77 229.23 512 512 512s512-229.23 512-512z" />
<glyph unicode="&#xea00;" glyph-name="icon-arrows-right-left" d="M1024 320l-448-512v1024zM448 832l-448-512 448-512z" />
<glyph unicode="&#xea01;" glyph-name="icon-arrows-up-down" d="M512 832l512-448h-1024zM0 256l512-448 512 448z" />
<glyph unicode="&#xea02;" glyph-name="icon-bullet" d="M832 80c0-44-36-80-80-80h-480c-44 0-80 36-80 80v480c0 44 36 80 80 80h480c44 0 80-36 80-80v-480z" />
@@ -156,4 +157,5 @@
<glyph unicode="&#xeb28;" glyph-name="icon-condition-widget" d="M832 832h-640c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM512 64l-384 256 384 256 384-256z" />
<glyph unicode="&#xeb29;" glyph-name="icon-alphanumeric" d="M535.6 301.4c-8.4-1.6-17.2-3-26.2-4s-18.2-2.4-27.2-4c-10.196-1.861-18.808-4.010-27.21-6.633l1.61 0.433c-8.609-2.674-16.105-6.348-22.89-10.987l0.29 0.187c-6.693-4.517-12.283-10.107-16.663-16.585l-0.137-0.215c-4.6-6.8-7.4-15.6-8.8-26s-0.4-18.4 2.4-25.2c2.746-6.688 7.224-12.195 12.881-16.122l0.119-0.078c5.967-4.053 13.057-6.94 20.704-8.161l0.296-0.039c7.592-1.527 16.319-2.4 25.25-2.4 0.123 0 0.246 0 0.369 0h-0.019c22.2 0 39.6 3.6 52.6 11s23.2 16.2 30.2 26.4c6.273 8.873 11.271 19.191 14.426 30.285l0.174 0.715c1.853 6.809 3.601 15.41 4.855 24.169l0.145 1.231 5.2 41.6c-5.4-4.217-11.723-7.564-18.583-9.689l-0.417-0.111c-6.489-2.241-14.362-4.255-22.444-5.662l-0.956-0.138zM1024 448v192h-152l24 192h-192l-24-192h-256l24 192h-192l-24-192h-232v-192h208l-32-256h-176v-192h152l-24-192h192l24 192h256l-24-192h192l24 192h232v192h-208l32 256zM702.8 420.2l-26.4-211.8c-2.231-15.809-3.537-34.122-3.6-52.727v-0.073c0-16.8 2.2-29.4 6.4-37.8h-113.4c-1.342 5.556-2.338 12.122-2.781 18.84l-0.019 0.36c-0.261 3.524-0.409 7.634-0.409 11.778 0 2.962 0.076 5.907 0.226 8.832l-0.017-0.41c-18.663-17.401-41.395-30.694-66.597-38.289l-1.203-0.311c-22.627-6.956-48.639-10.974-75.586-11h-0.014c-0.764-0.011-1.666-0.018-2.569-0.018-18.098 0-35.598 2.563-52.156 7.345l1.325-0.328c-15.991 4.512-29.851 12.090-41.545 22.122l0.145-0.122c-11.233 9.982-19.792 22.733-24.624 37.192l-0.176 0.608c-5.2 15.2-6.4 33.4-3.8 54.4s9.4 42.2 19.4 57.2c9.524 14.399 21.535 26.346 35.532 35.512l0.468 0.288c13.387 8.662 28.922 15.533 45.512 19.765l1.088 0.235c13.436 3.792 30.801 7.554 48.47 10.41l2.93 0.39c17 2.6 33.8 4.6 50.4 6.2 16.628 1.527 31.69 4.070 46.349 7.643l-2.149-0.443c13 3 23.6 7.6 31.6 13.6s12.6 15 13.6 26.4 0.8 21.8-2.4 28.8c-2.849 6.902-7.542 12.56-13.468 16.517l-0.132 0.083c-6.217 4.011-13.604 6.78-21.543 7.774l-0.257 0.026c-7.897 1.277-17 2.007-26.274 2.007-0.537 0-1.073-0.002-1.609-0.007l0.082 0.001c-22 0-40-4.6-53.8-14.2s-23-25.2-28-47.2h-111.8c4.8 26.2 14.2 48 27.8 65.4 13.475 16.978 29.89 30.968 48.574 41.377l0.826 0.423c18.192 10.038 39.297 17.806 61.619 22.175l1.381 0.225c20.488 4.162 44.053 6.563 68.171 6.6h0.029c21.8-0.005 43.239-1.532 64.222-4.479l-2.422 0.279c20.641-2.809 39.324-8.783 56.401-17.461l-1.001 0.461c15.909-8.108 28.858-20.031 37.967-34.601l0.233-0.399c9-15 12.2-34.8 9-59.6z" />
<glyph unicode="&#xeb2a;" glyph-name="icon-image-telemetry" d="M512 832c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM783.6 48.4c-69.581-69.675-165.757-112.776-272-112.776-212.298 0-384.4 172.102-384.4 384.4s172.102 384.4 384.4 384.4c212.298 0 384.4-172.102 384.4-384.4 0-0.008 0-0.017 0-0.025v0.001c0.001-0.264 0.001-0.575 0.001-0.887 0-105.769-42.964-201.503-112.391-270.703l-0.010-0.010zM704 448l-128-128-192 192-192-192c0-176.731 143.269-320 320-320s320 143.269 320 320v0z" />
<glyph unicode="&#xeb2b;" glyph-name="icon-telemetry-aggregate" d="M78 436.56c14 41.44 37.48 100.8 69.2 148.36 38.62 57.78 82.38 87.080 130.14 87.080s91.5-29.3 130-87.080c31.72-47.56 55.14-106.92 69.2-148.36 30.88-90.96 63.12-134.98 78-146.54 14.94 11.56 47.2 55.58 78 146.54 14 41.44 37.48 100.8 69.22 148.36q27.8 41.7 59.12 63.5c-75.7 111.377-201.81 183.58-344.783 183.58-0.034 0-0.068 0-0.103 0h0.006c-229.76 0-416-186.24-416-416 0-0.071 0-0.156 0-0.24 0-39.119 5.396-76.977 15.484-112.871l-0.704 2.931c16.78 21.74 40.4 63.34 63.22 130.74zM754 395.44c-14-41.44-37.48-100.8-69.2-148.36-38.56-57.78-82.32-87.080-130-87.080s-91.5 29.3-130 87.080c-31.72 47.56-55.14 106.92-69.2 148.36-30.88 90.96-63.14 134.98-78 146.54-14.94-11.56-47.2-55.58-78-146.54-14.38-41.44-37.8-100.8-69.6-148.36q-27.8-41.7-59.12-63.5c75.7-111.378 201.81-183.58 344.783-183.58 0.119 0 0.237 0 0.356 0h-0.019c229.76 0 416 186.24 416 416 0 0.071 0 0.156 0 0.24 0 39.119-5.396 76.977-15.484 112.871l0.704-2.931c-16.78-21.74-40.4-63.34-63.22-130.74zM921.56 497.38c4.098-24.449 6.44-52.617 6.44-81.332 0-0.017 0-0.034 0-0.051v0.003c0-0.095 0-0.208 0-0.32 0-282.593-229.087-511.68-511.68-511.68-0.113 0-0.225 0-0.338 0h0.018c-0.014 0-0.031 0-0.048 0-28.716 0-56.884 2.342-84.325 6.845l2.993-0.405c72.483-63.623 168.109-102.44 272.802-102.44 0.203 0 0.406 0 0.61 0h-0.031c229.76 0 416 186.24 416 416 0 0.172 0 0.375 0 0.578 0 104.692-38.817 200.319-102.844 273.271l0.404-0.47z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -53,7 +53,7 @@
class="l-shell__pane-tree"
handle="after"
label="Browse"
collapsable
hide-param="hideTree"
@start-resizing="onStartResizing"
@end-resizing="onEndResizing"
>
@@ -104,7 +104,7 @@
class="l-shell__pane-inspector l-pane--holds-multipane"
handle="before"
label="Inspect"
collapsable
hide-param="hideInspector"
@start-resizing="onStartResizing"
@end-resizing="onEndResizing"
>

171
src/ui/layout/LayoutSpec.js Normal file
View File

@@ -0,0 +1,171 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 Vue from 'vue';
import Layout from './Layout.vue';
describe('Open MCT Layout:', () => {
let openmct;
let element;
let components;
beforeEach((done) => {
openmct = createOpenMct();
openmct.on('start', done);
// to silence error from BrowseBar.vue
spyOn(openmct.objectViews, 'get')
.and.callFake(() => []);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
describe('the pane:', () => {
it('is displayed on layout load', async () => {
await createLayout();
await Vue.nextTick();
Object.entries(components).forEach(([name, component]) => {
expect(
component.pane
).toBeTruthy();
expect(
isCollapsed(component.pane)
).toBeFalse();
});
});
it('is collapsed on layout load if specified by a hide param', async () => {
setHideParams();
await createLayout();
await Vue.nextTick();
Object.entries(components).forEach(([name, component]) => {
expect(
isCollapsed(component.pane)
).toBeTrue();
});
});
it('on toggle collapses if expanded', async () => {
await createLayout();
toggleCollapseButtons();
await Vue.nextTick();
Object.entries(components).forEach(([name, component]) => {
expect(
openmct.router.getSearchParam(component.param)
).toEqual('true');
expect(
isCollapsed(component.pane)
).toBeTrue();
});
});
it('on toggle expands if collapsed', async () => {
setHideParams();
await createLayout();
toggleExpandButtons();
await Vue.nextTick();
Object.entries(components).forEach(([name, component]) => {
expect(
openmct.router.getSearchParam(component.param)
).not.toEqual('true');
expect(
isCollapsed(component.pane)
).toBeFalse();
});
});
});
async function createLayout() {
const el = document.createElement('div');
const child = document.createElement('div');
el.appendChild(child);
element = await new Vue({
el,
components: {
Layout
},
provide: {
openmct
},
template: `<Layout ref="layout"/>`
}).$mount().$el;
setComponents();
}
function setComponents() {
components = {
tree: {
param: 'hideTree',
pane: element.querySelector('.l-shell__pane-tree'),
collapseButton: element.querySelector('.l-shell__pane-tree .l-pane__collapse-button'),
expandButton: element.querySelector('.l-shell__pane-tree .l-pane__expand-button')
},
inspector: {
param: 'hideInspector',
pane: element.querySelector('.l-shell__pane-inspector'),
collapseButton: element.querySelector('.l-shell__pane-inspector .l-pane__collapse-button'),
expandButton: element.querySelector('.l-shell__pane-inspector .l-pane__expand-button')
}
};
}
function isCollapsed(el) {
return el.classList.contains('l-pane--collapsed');
}
function setHideParams() {
Object.entries(components).forEach(([name, component]) => {
openmct.router.setSearchParam(component.param, true);
});
}
function toggleCollapseButtons() {
Object.entries(components).forEach(([name, component]) => {
component.collapseButton.click();
});
}
function toggleExpandButtons() {
Object.entries(components).forEach(([name, component]) => {
component.expandButton.click();
});
}
});

View File

@@ -41,10 +41,6 @@
<script>
const COLLAPSE_THRESHOLD_PX = 40;
const HIDE_TREE_PARAM = 'hideTree';
const HIDE_INSPECTOR_PARAM = 'hideInspector';
const PANE_INSPECTOR = 'Inspect';
const PANE_TREE = 'Browse';
export default {
inject: ['openmct'],
@@ -56,13 +52,13 @@ export default {
return ['', 'before', 'after'].indexOf(value) !== -1;
}
},
collapsable: {
type: Boolean,
default: false
},
label: {
type: String,
default: ''
},
hideParam: {
type: String,
default: ''
}
},
data() {
@@ -71,6 +67,11 @@ export default {
resizing: false
};
},
computed: {
collapsable() {
return this.hideParam && this.hideParam.length;
}
},
beforeMount() {
this.type = this.$parent.type;
this.styleProp = (this.type === 'horizontal') ? 'width' : 'height';
@@ -78,39 +79,25 @@ export default {
async mounted() {
await this.$nextTick();
// Hide tree and/or inspector pane if specified in URL
this.handleHideUrl();
this.openmct.router.on('change:params', this.handleHideUrl);
},
beforeDestroy() {
this.openmct.router.off('change:params', this.handleHideUrl);
if (this.collapsable) {
this.handleHideUrl();
}
},
methods: {
toggleCollapse: function (e) {
let target = this.label === PANE_TREE ? HIDE_TREE_PARAM : HIDE_INSPECTOR_PARAM;
this.collapsed = !this.collapsed;
if (this.collapsed) {
this.handleCollapse();
this.addHideParam(target);
} else {
this.handleExpand();
this.removeHideParam(target);
this.removeHideParam(this.hideParam);
} else {
this.handleCollapse();
this.addHideParam(this.hideParam);
}
},
handleHideUrl: function () {
if (!this.collapsable) {
return;
}
const hideParam = this.openmct.router.getSearchParam(this.hideParam);
let hideTreeParam = this.openmct.router.getSearchParam(HIDE_TREE_PARAM);
let hideInspectorParam = this.openmct.router.getSearchParam(HIDE_INSPECTOR_PARAM);
let hideTree = hideTreeParam === 'true' && this.label === PANE_TREE;
let hideInspector = hideInspectorParam === 'true' && this.label === PANE_INSPECTOR;
if (hideTree || hideInspector) {
this.collapsed = true;
if (hideParam === 'true') {
this.handleCollapse();
} else {
this.collapsed = false;
this.handleExpand();
}
},
addHideParam: function (target) {
@@ -122,11 +109,13 @@ export default {
handleCollapse: function () {
this.currentSize = (this.dragCollapse === true) ? this.initial : this.$el.style[this.styleProp];
this.$el.style[this.styleProp] = '';
this.collapsed = true;
},
handleExpand: function () {
this.$el.style[this.styleProp] = this.currentSize;
delete this.currentSize;
delete this.dragCollapse;
this.collapsed = false;
},
trackSize: function () {
if (!this.dragCollapse === true) {

View File

@@ -1,90 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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';
describe("the pane", () => {
let openmct;
let appHolder;
let element;
let child;
let resolveFunction;
beforeEach((done) => {
openmct = createOpenMct();
appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalTimeSystem());
openmct.install(openmct.plugins.UTCTimeSystem());
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct.on('start', done);
openmct.start(appHolder);
document.body.append(appHolder);
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('toggling tree will toggle tree hide params', (done) => {
document.querySelector('.l-shell__pane-tree .l-pane__collapse-button').click();
expect(openmct.router.getSearchParam('hideTree')).toBe('true');
done();
});
it('tree pane collapses when adding hide tree param in URL', () => {
openmct.router.setSearchParam('hideTree', 'true');
expect(document.querySelector('.l-shell__pane-tree.l-pane--collapsed')).toBeDefined();
});
it('inspector pane collapses when adding hide inspector param in URL', () => {
openmct.router.setSearchParam('hideInspector', 'true');
expect(document.querySelector('.l-shell__pane-inspector.l-pane--collapsed')).toBeDefined();
});
it('toggle inspector pane will toggle inspector hide param', (done) => {
// There's a short delay on addubg the param.
resolveFunction = () => {
setTimeout(() => {
expect(openmct.router.getSearchParam('hideInspector')).toBe('true');
done();
}, 500);
};
openmct.router.on('change:params', resolveFunction);
document.querySelector('.l-shell__pane-inspector .l-pane__collapse-button').click();
});
});