Compare commits
24 Commits
stackplots
...
yield-requ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15926cc9a2 | ||
|
|
d647e287fd | ||
|
|
7998ee3f61 | ||
|
|
e7d94d9447 | ||
|
|
336babdb15 | ||
|
|
0b4624fc8f | ||
|
|
62c54e732d | ||
|
|
e63393b0c4 | ||
|
|
02c97ae10b | ||
|
|
5b499368ae | ||
|
|
dd556c3335 | ||
|
|
1a71708b63 | ||
|
|
ab425ed04e | ||
|
|
514181c2fd | ||
|
|
757ff1c85a | ||
|
|
e7f21654e0 | ||
|
|
c895869cbf | ||
|
|
5d40a55a20 | ||
|
|
54477f2bb9 | ||
|
|
eb894894b5 | ||
|
|
3f06bc5c48 | ||
|
|
c3238e50ee | ||
|
|
d83e919541 | ||
|
|
5fcf0bd7de |
@@ -42,7 +42,6 @@ jobs:
|
|||||||
- ~/.npm
|
- ~/.npm
|
||||||
- ~/.cache
|
- ~/.cache
|
||||||
- node_modules
|
- node_modules
|
||||||
- run: npm run lint
|
|
||||||
- run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
|
- run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: dist/reports/tests/
|
path: dist/reports/tests/
|
||||||
|
|||||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
|
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
|
||||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
|
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
|
||||||
* [ ] Is this change backwards compatible? For example, developers won't need to change how they are calling the API or how they've extended core plugins such as Tables or Plots.
|
|
||||||
|
|
||||||
### Author Checklist
|
### Author Checklist
|
||||||
|
|
||||||
|
|||||||
@@ -317,7 +317,6 @@ checklist).
|
|||||||
### Reviewer Checklist
|
### Reviewer Checklist
|
||||||
|
|
||||||
* [ ] Changes appear to address issue?
|
* [ ] Changes appear to address issue?
|
||||||
* [ ] Changes appear not to be breaking changes?
|
|
||||||
* [ ] Appropriate unit tests included?
|
* [ ] Appropriate unit tests included?
|
||||||
* [ ] Code style and in-line documentation are appropriate?
|
* [ ] Code style and in-line documentation are appropriate?
|
||||||
* [ ] Commit messages meet standards?
|
* [ ] Commit messages meet standards?
|
||||||
|
|||||||
@@ -28,15 +28,6 @@ define([
|
|||||||
domain: 2
|
domain: 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: "cos",
|
|
||||||
name: "Cosine",
|
|
||||||
unit: "deg",
|
|
||||||
formatString: '%0.2f',
|
|
||||||
hints: {
|
|
||||||
domain: 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Need to enable "LocalTimeSystem" plugin to make use of this
|
// Need to enable "LocalTimeSystem" plugin to make use of this
|
||||||
// {
|
// {
|
||||||
// key: "local",
|
// key: "local",
|
||||||
@@ -118,100 +109,6 @@ 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,86 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,102 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
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'
|
|
||||||
];
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
@@ -54,21 +54,8 @@
|
|||||||
var start = Date.now();
|
var start = Date.now();
|
||||||
var step = 1000 / data.dataRateInHz;
|
var step = 1000 / data.dataRateInHz;
|
||||||
var nextStep = start - (start % step) + step;
|
var nextStep = start - (start % step) + step;
|
||||||
let work;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextStep;
|
function work(now) {
|
||||||
};
|
|
||||||
} else {
|
|
||||||
work = function (now) {
|
|
||||||
while (nextStep < now) {
|
while (nextStep < now) {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
@@ -77,7 +64,6 @@
|
|||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
|
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)
|
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -85,7 +71,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nextStep;
|
return nextStep;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptions[message.id] = work;
|
subscriptions[message.id] = work;
|
||||||
@@ -126,21 +111,13 @@
|
|||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||||
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
||||||
wavelength: wavelength(start, nextStep),
|
|
||||||
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
|
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
data: request.spectra ? {
|
data: data
|
||||||
wavelength: data.map((item) => {
|
|
||||||
return item.wavelength;
|
|
||||||
}),
|
|
||||||
cos: data.map((item) => {
|
|
||||||
return item.cos;
|
|
||||||
})
|
|
||||||
} : data
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,10 +131,6 @@
|
|||||||
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
* 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) {
|
function sendError(error, message) {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
error: error.name + ': ' + error.message,
|
error: error.name + ': ' + error.message,
|
||||||
|
|||||||
@@ -24,15 +24,11 @@ define([
|
|||||||
"./GeneratorProvider",
|
"./GeneratorProvider",
|
||||||
"./SinewaveLimitProvider",
|
"./SinewaveLimitProvider",
|
||||||
"./StateGeneratorProvider",
|
"./StateGeneratorProvider",
|
||||||
"./SpectralGeneratorProvider",
|
|
||||||
"./SpectralAggregateGeneratorProvider",
|
|
||||||
"./GeneratorMetadataProvider"
|
"./GeneratorMetadataProvider"
|
||||||
], function (
|
], function (
|
||||||
GeneratorProvider,
|
GeneratorProvider,
|
||||||
SinewaveLimitProvider,
|
SinewaveLimitProvider,
|
||||||
StateGeneratorProvider,
|
StateGeneratorProvider,
|
||||||
SpectralGeneratorProvider,
|
|
||||||
SpectralAggregateGeneratorProvider,
|
|
||||||
GeneratorMetadataProvider
|
GeneratorMetadataProvider
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -65,37 +61,6 @@ define([
|
|||||||
|
|
||||||
openmct.telemetry.addProvider(new StateGeneratorProvider());
|
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", {
|
openmct.types.addType("generator", {
|
||||||
name: "Sine Wave Generator",
|
name: "Sine Wave Generator",
|
||||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
const devMode = process.env.NODE_ENV !== 'production';
|
const devMode = process.env.NODE_ENV !== 'production';
|
||||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
|
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
|
||||||
const coverageEnabled = process.env.COVERAGE === 'true';
|
const coverageEnabled = process.env.COVERAGE === 'true';
|
||||||
const reporters = ['spec', 'html', 'junit'];
|
const reporters = ['progress', 'html', 'junit'];
|
||||||
|
|
||||||
if (coverageEnabled) {
|
if (coverageEnabled) {
|
||||||
reporters.push('coverage-istanbul');
|
reporters.push('coverage-istanbul');
|
||||||
@@ -60,7 +60,7 @@ module.exports = (config) => {
|
|||||||
client: {
|
client: {
|
||||||
jasmine: {
|
jasmine: {
|
||||||
random: false,
|
random: false,
|
||||||
timeoutInterval: 5000
|
timeoutInterval: 30000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
customLaunchers: {
|
customLaunchers: {
|
||||||
@@ -88,6 +88,11 @@ module.exports = (config) => {
|
|||||||
outputFile: "test-results.xml",
|
outputFile: "test-results.xml",
|
||||||
useBrowserName: false
|
useBrowserName: false
|
||||||
},
|
},
|
||||||
|
browserConsoleLogOptions: {
|
||||||
|
level: "error",
|
||||||
|
format: "%b %T: %m",
|
||||||
|
terminal: true
|
||||||
|
},
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
fixWebpackSourcePaths: true,
|
fixWebpackSourcePaths: true,
|
||||||
dir: process.env.CIRCLE_ARTIFACTS
|
dir: process.env.CIRCLE_ARTIFACTS
|
||||||
@@ -100,15 +105,6 @@ module.exports = (config) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
specReporter: {
|
|
||||||
maxLogLines: 5,
|
|
||||||
suppressErrorSummary: true,
|
|
||||||
suppressFailed: false,
|
|
||||||
suppressPassed: false,
|
|
||||||
suppressSkipped: true,
|
|
||||||
showSpecTiming: true,
|
|
||||||
failFast: false
|
|
||||||
},
|
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
'indexTest.js': ['webpack', 'sourcemap']
|
'indexTest.js': ['webpack', 'sourcemap']
|
||||||
},
|
},
|
||||||
|
|||||||
16
package.json
16
package.json
@@ -2,6 +2,7 @@
|
|||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.7.8-SNAPSHOT",
|
"version": "1.7.8-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"angular": ">=1.8.0",
|
"angular": ">=1.8.0",
|
||||||
"angular-route": "1.4.14",
|
"angular-route": "1.4.14",
|
||||||
@@ -11,9 +12,16 @@
|
|||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
"cross-env": "^6.0.3",
|
"cross-env": "^6.0.3",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
|
"d3-array": "1.2.x",
|
||||||
"d3-axis": "1.0.x",
|
"d3-axis": "1.0.x",
|
||||||
|
"d3-collection": "1.0.x",
|
||||||
|
"d3-color": "1.0.x",
|
||||||
|
"d3-format": "1.2.x",
|
||||||
|
"d3-interpolate": "1.1.x",
|
||||||
"d3-scale": "1.0.x",
|
"d3-scale": "1.0.x",
|
||||||
"d3-selection": "1.3.x",
|
"d3-selection": "1.3.x",
|
||||||
|
"d3-time": "1.0.x",
|
||||||
|
"d3-time-format": "2.1.x",
|
||||||
"eslint": "7.0.0",
|
"eslint": "7.0.0",
|
||||||
"eslint-plugin-vue": "^7.5.0",
|
"eslint-plugin-vue": "^7.5.0",
|
||||||
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
|
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
|
||||||
@@ -33,15 +41,14 @@
|
|||||||
"jsdoc": "^3.3.2",
|
"jsdoc": "^3.3.2",
|
||||||
"karma": "6.3.4",
|
"karma": "6.3.4",
|
||||||
"karma-chrome-launcher": "3.1.0",
|
"karma-chrome-launcher": "3.1.0",
|
||||||
|
"karma-firefox-launcher": "2.1.1",
|
||||||
"karma-cli": "2.0.0",
|
"karma-cli": "2.0.0",
|
||||||
"karma-coverage": "2.0.3",
|
"karma-coverage": "2.0.3",
|
||||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||||
"karma-firefox-launcher": "2.1.1",
|
"karma-junit-reporter": "2.0.1",
|
||||||
"karma-html-reporter": "0.2.7",
|
"karma-html-reporter": "0.2.7",
|
||||||
"karma-jasmine": "4.0.1",
|
"karma-jasmine": "4.0.1",
|
||||||
"karma-junit-reporter": "2.0.1",
|
|
||||||
"karma-sourcemap-loader": "0.3.8",
|
"karma-sourcemap-loader": "0.3.8",
|
||||||
"karma-spec-reporter": "0.0.32",
|
|
||||||
"karma-webpack": "4.0.2",
|
"karma-webpack": "4.0.2",
|
||||||
"location-bar": "^3.0.1",
|
"location-bar": "^3.0.1",
|
||||||
"lodash": "^4.17.12",
|
"lodash": "^4.17.12",
|
||||||
@@ -55,8 +62,6 @@
|
|||||||
"node-bourbon": "^4.2.3",
|
"node-bourbon": "^4.2.3",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"painterro": "^1.2.56",
|
"painterro": "^1.2.56",
|
||||||
"plotly.js-basic-dist": "^2.5.0",
|
|
||||||
"plotly.js-gl2d-dist": "^2.5.0",
|
|
||||||
"printj": "^1.2.1",
|
"printj": "^1.2.1",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"request": "^2.69.0",
|
"request": "^2.69.0",
|
||||||
@@ -65,7 +70,6 @@
|
|||||||
"uuid": "^3.3.3",
|
"uuid": "^3.3.3",
|
||||||
"v8-compile-cache": "^1.1.0",
|
"v8-compile-cache": "^1.1.0",
|
||||||
"vue": "2.5.6",
|
"vue": "2.5.6",
|
||||||
"vue-eslint-parser": "7.11.0",
|
|
||||||
"vue-loader": "^15.2.6",
|
"vue-loader": "^15.2.6",
|
||||||
"vue-template-compiler": "2.5.6",
|
"vue-template-compiler": "2.5.6",
|
||||||
"webpack": "^4.16.2",
|
"webpack": "^4.16.2",
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ define(
|
|||||||
* or finish() are called.
|
* or finish() are called.
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.edit = function () {
|
EditorCapability.prototype.edit = function () {
|
||||||
|
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
|
||||||
|
|
||||||
if (!this.openmct.editor.isEditing()) {
|
if (!this.openmct.editor.isEditing()) {
|
||||||
this.openmct.editor.edit();
|
this.openmct.editor.edit();
|
||||||
this.domainObject.getCapability('status').set('editing', true);
|
this.domainObject.getCapability('status').set('editing', true);
|
||||||
@@ -80,6 +82,8 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.save = function () {
|
EditorCapability.prototype.save = function () {
|
||||||
|
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,6 +95,8 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.finish = function () {
|
EditorCapability.prototype.finish = function () {
|
||||||
|
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ define([
|
|||||||
], function (
|
], function (
|
||||||
moment
|
moment
|
||||||
) {
|
) {
|
||||||
const DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS";
|
|
||||||
const DATE_FORMATS = [
|
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
||||||
|
DATE_FORMATS = [
|
||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
DATE_FORMAT + "Z",
|
DATE_FORMAT + "Z",
|
||||||
"YYYY-MM-DD HH:mm:ss",
|
"YYYY-MM-DD HH:mm:ss",
|
||||||
@@ -52,27 +53,15 @@ define([
|
|||||||
this.key = "utc";
|
this.key = "utc";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} formatString
|
|
||||||
* @returns the value of formatString if the value is a string type and exists in the DATE_FORMATS array; otherwise the DATE_FORMAT value.
|
|
||||||
*/
|
|
||||||
function validateFormatString(formatString) {
|
|
||||||
return typeof formatString === 'string' && DATE_FORMATS.includes(formatString) ? formatString : DATE_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} value The value to format.
|
* @param {number} value The value to format.
|
||||||
* @param {string} formatString The string format to format. Default "YYYY-MM-DD HH:mm:ss.SSS" + "Z"
|
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||||
* @returns {string} the formatted date(s) according to the proper parameter of formatString or the default value of "YYYY-MM-DD HH:mm:ss.SSS" + "Z".
|
|
||||||
* If multiple values were requested, then an array of
|
|
||||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||||
* in the array.
|
* in the array.
|
||||||
*/
|
*/
|
||||||
UTCTimeFormat.prototype.format = function (value, formatString) {
|
UTCTimeFormat.prototype.format = function (value) {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
const format = validateFormatString(formatString);
|
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||||
|
|
||||||
return moment.utc(value).format(format) + (formatString ? '' : 'Z');
|
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ define([
|
|||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name conductor
|
* @name conductor
|
||||||
*/
|
*/
|
||||||
this.time = new api.TimeAPI(this);
|
this.time = new api.TimeAPI();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for interacting with the composition of domain objects.
|
* An interface for interacting with the composition of domain objects.
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ define([
|
|||||||
'./capabilities/APICapabilityDecorator',
|
'./capabilities/APICapabilityDecorator',
|
||||||
'./policies/AdaptedViewPolicy',
|
'./policies/AdaptedViewPolicy',
|
||||||
'./runs/AlternateCompositionInitializer',
|
'./runs/AlternateCompositionInitializer',
|
||||||
|
'./runs/TypeDeprecationChecker',
|
||||||
'./runs/LegacyTelemetryProvider',
|
'./runs/LegacyTelemetryProvider',
|
||||||
'./runs/RegisterLegacyTypes',
|
'./runs/RegisterLegacyTypes',
|
||||||
'./services/LegacyObjectAPIInterceptor',
|
'./services/LegacyObjectAPIInterceptor',
|
||||||
@@ -45,6 +46,7 @@ define([
|
|||||||
APICapabilityDecorator,
|
APICapabilityDecorator,
|
||||||
AdaptedViewPolicy,
|
AdaptedViewPolicy,
|
||||||
AlternateCompositionInitializer,
|
AlternateCompositionInitializer,
|
||||||
|
TypeDeprecationChecker,
|
||||||
LegacyTelemetryProvider,
|
LegacyTelemetryProvider,
|
||||||
RegisterLegacyTypes,
|
RegisterLegacyTypes,
|
||||||
LegacyObjectAPIInterceptor,
|
LegacyObjectAPIInterceptor,
|
||||||
@@ -133,6 +135,10 @@ define([
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
runs: [
|
runs: [
|
||||||
|
{
|
||||||
|
implementation: TypeDeprecationChecker,
|
||||||
|
depends: ["types[]"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
implementation: AlternateCompositionInitializer,
|
implementation: AlternateCompositionInitializer,
|
||||||
depends: ["openmct"]
|
depends: ["openmct"]
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ define([
|
|||||||
|
|
||||||
) {
|
) {
|
||||||
function RegisterLegacyTypes(types, openmct) {
|
function RegisterLegacyTypes(types, openmct) {
|
||||||
|
types.forEach(function (legacyDefinition) {
|
||||||
|
if (!openmct.types.get(legacyDefinition.key)) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate type ${legacyDefinition.key} from ${legacyDefinition.bundle.path} to use the new Types API. Legacy type support will be removed soon.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
openmct.types.importLegacyTypes(types);
|
openmct.types.importLegacyTypes(types);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
* Open openmct, Copyright (c) 2014-2021, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
* Open openmct is licensed under the Apache License, Version 2.0 (the
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
@@ -14,27 +14,33 @@
|
|||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*
|
*
|
||||||
* Open MCT includes source code licensed under additional open source
|
* Open openmct includes source code licensed under additional open source
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
export default function timelineInterceptor(openmct) {
|
define([
|
||||||
|
|
||||||
openmct.objects.addGetInterceptor({
|
], function (
|
||||||
appliesTo: (identifier, domainObject) => {
|
|
||||||
return domainObject && domainObject.type === 'time-strip';
|
|
||||||
},
|
|
||||||
invoke: (identifier, object) => {
|
|
||||||
|
|
||||||
if (object && object.configuration === undefined) {
|
) {
|
||||||
object.configuration = {
|
|
||||||
useIndependentTime: true
|
function checkForDeprecatedFunctionality(typeDef) {
|
||||||
};
|
if (Object.prototype.hasOwnProperty.call(typeDef, 'telemetry')) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING: Telemetry data on type '
|
||||||
|
+ 'registrations will be deprecated in a future version, '
|
||||||
|
+ 'please convert to a custom telemetry metadata provider '
|
||||||
|
+ 'for type: ' + typeDef.key
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
function TypeDeprecationChecker(types) {
|
||||||
|
types.forEach(checkForDeprecatedFunctionality);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return TypeDeprecationChecker;
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
@@ -15,6 +15,8 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: legacyView.key,
|
key: legacyView.key,
|
||||||
name: legacyView.name,
|
name: legacyView.name,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ define([
|
|||||||
|
|
||||||
) {
|
) {
|
||||||
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
|
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs. Legacy Inspector view support will be removed soon.`);
|
||||||
let representation = openmct.$injector.get('representations[]')
|
let representation = openmct.$injector.get('representations[]')
|
||||||
.filter((r) => r.key === typeDefinition.inspector)[0];
|
.filter((r) => r.key === typeDefinition.inspector)[0];
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ define([
|
|||||||
StatusAPI
|
StatusAPI
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
TimeAPI: TimeAPI.default,
|
TimeAPI: TimeAPI,
|
||||||
ObjectAPI: ObjectAPI,
|
ObjectAPI: ObjectAPI,
|
||||||
CompositionAPI: CompositionAPI,
|
CompositionAPI: CompositionAPI,
|
||||||
TypeRegistry: TypeRegistry,
|
TypeRegistry: TypeRegistry,
|
||||||
|
|||||||
@@ -182,12 +182,6 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
|||||||
let objectPromise = provider.get(identifier, abortSignal).then(result => {
|
let objectPromise = provider.get(identifier, abortSignal).then(result => {
|
||||||
delete this.cache[keystring];
|
delete this.cache[keystring];
|
||||||
result = this.applyGetInterceptors(identifier, result);
|
result = this.applyGetInterceptors(identifier, result);
|
||||||
if (result.isMutable) {
|
|
||||||
result.$refresh(result);
|
|
||||||
} else {
|
|
||||||
let mutableDomainObject = this._toMutable(result);
|
|
||||||
mutableDomainObject.$refresh(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
@@ -304,15 +298,10 @@ ObjectAPI.prototype.save = function (domainObject) {
|
|||||||
savedResolve = resolve;
|
savedResolve = resolve;
|
||||||
});
|
});
|
||||||
domainObject.persisted = persistedTime;
|
domainObject.persisted = persistedTime;
|
||||||
const newObjectPromise = provider.create(domainObject);
|
provider.create(domainObject).then((response) => {
|
||||||
if (newObjectPromise) {
|
|
||||||
newObjectPromise.then(response => {
|
|
||||||
this.mutate(domainObject, 'persisted', persistedTime);
|
this.mutate(domainObject, 'persisted', persistedTime);
|
||||||
savedResolve(response);
|
savedResolve(response);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
result = Promise.reject(`[ObjectAPI][save] Object provider returned ${newObjectPromise} when creating new object.`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
domainObject.persisted = persistedTime;
|
domainObject.persisted = persistedTime;
|
||||||
this.mutate(domainObject, 'persisted', persistedTime);
|
this.mutate(domainObject, 'persisted', persistedTime);
|
||||||
|
|||||||
@@ -180,6 +180,12 @@ define([
|
|||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) {
|
TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING: openmct.telemetry.canProvideTelemetry '
|
||||||
|
+ 'will not be supported in future versions of Open MCT. Please '
|
||||||
|
+ 'use openmct.telemetry.isTelemetryObject instead.'
|
||||||
|
);
|
||||||
|
|
||||||
return Boolean(this.findSubscriptionProvider(domainObject))
|
return Boolean(this.findSubscriptionProvider(domainObject))
|
||||||
|| Boolean(this.findRequestProvider(domainObject));
|
|| Boolean(this.findRequestProvider(domainObject));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ define([
|
|||||||
valueMetadata.hints = valueMetadata.hints || {};
|
valueMetadata.hints = valueMetadata.hints || {};
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
|
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING: `x` hints should be replaced with '
|
||||||
|
+ '`domain` hints moving forward. '
|
||||||
|
+ 'https://github.com/nasa/openmct/issues/1546'
|
||||||
|
);
|
||||||
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
|
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
|
||||||
valueMetadata.hints.domain = valueMetadata.hints.x;
|
valueMetadata.hints.domain = valueMetadata.hints.x;
|
||||||
}
|
}
|
||||||
@@ -39,6 +44,11 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
|
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING: `y` hints should be replaced with '
|
||||||
|
+ '`range` hints moving forward. '
|
||||||
|
+ 'https://github.com/nasa/openmct/issues/1546'
|
||||||
|
);
|
||||||
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
|
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
|
||||||
valueMetadata.hints.range = valueMetadata.hints.y;
|
valueMetadata.hints.range = valueMetadata.hints.y;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +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 TimeContext from "./TimeContext";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GlobalContext handles getting and setting time of the openmct application in general.
|
|
||||||
* Views will use this context unless they specify an alternate/independent time context
|
|
||||||
*/
|
|
||||||
class GlobalTimeContext extends TimeContext {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
//The Time Of Interest
|
|
||||||
this.toi = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set the start and end time of the time conductor. Basic validation
|
|
||||||
* of bounds is performed.
|
|
||||||
*
|
|
||||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
|
||||||
* @throws {Error} Validation error
|
|
||||||
* @fires module:openmct.TimeAPI~bounds
|
|
||||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method bounds
|
|
||||||
*/
|
|
||||||
bounds(newBounds) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
super.bounds.call(this, ...arguments);
|
|
||||||
// If a bounds change results in a TOI outside of the current
|
|
||||||
// bounds, unset it
|
|
||||||
if (this.toi < newBounds.start || this.toi > newBounds.end) {
|
|
||||||
this.timeOfInterest(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
|
||||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update bounds based on provided time and current offsets
|
|
||||||
* @private
|
|
||||||
* @param {number} timestamp A time from which bounds will be calculated
|
|
||||||
* using current offsets.
|
|
||||||
*/
|
|
||||||
tick(timestamp) {
|
|
||||||
super.tick.call(this, ...arguments);
|
|
||||||
|
|
||||||
// If a bounds change results in a TOI outside of the current
|
|
||||||
// bounds, unset it
|
|
||||||
if (this.toi < this.boundsVal.start || this.toi > this.boundsVal.end) {
|
|
||||||
this.timeOfInterest(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set the Time of Interest. The Time of Interest is a single point
|
|
||||||
* in time, and constitutes the temporal focus of application views. It can
|
|
||||||
* be manipulated by the user from the time conductor or from other views.
|
|
||||||
* The time of interest can effectively be unset by assigning a value of
|
|
||||||
* 'undefined'.
|
|
||||||
* @fires module:openmct.TimeAPI~timeOfInterest
|
|
||||||
* @param newTOI
|
|
||||||
* @returns {number} the current time of interest
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method timeOfInterest
|
|
||||||
*/
|
|
||||||
timeOfInterest(newTOI) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
this.toi = newTOI;
|
|
||||||
/**
|
|
||||||
* The Time of Interest has moved.
|
|
||||||
* @event timeOfInterest
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {number} Current time of interest
|
|
||||||
*/
|
|
||||||
this.emit('timeOfInterest', this.toi);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.toi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GlobalTimeContext;
|
|
||||||
@@ -1,94 +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 TimeContext from "./TimeContext";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The IndependentTimeContext handles getting and setting time of the openmct application in general.
|
|
||||||
* Views will use the GlobalTimeContext unless they specify an alternate/independent time context here.
|
|
||||||
*/
|
|
||||||
class IndependentTimeContext extends TimeContext {
|
|
||||||
constructor(globalTimeContext, key) {
|
|
||||||
super();
|
|
||||||
this.key = key;
|
|
||||||
|
|
||||||
this.globalTimeContext = globalTimeContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the active clock. Tick source will be immediately subscribed to
|
|
||||||
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
|
||||||
* can be unset by calling {@link stopClock}.
|
|
||||||
*
|
|
||||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
|
||||||
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
|
||||||
* the start and end bounds. This maintains a sliding time window of a fixed
|
|
||||||
* width that automatically updates.
|
|
||||||
* @fires module:openmct.TimeAPI~clock
|
|
||||||
* @return {Clock} the currently active clock;
|
|
||||||
*/
|
|
||||||
clock(keyOrClock, offsets) {
|
|
||||||
if (arguments.length === 2) {
|
|
||||||
let clock;
|
|
||||||
|
|
||||||
if (typeof keyOrClock === 'string') {
|
|
||||||
clock = this.globalTimeContext.clocks.get(keyOrClock);
|
|
||||||
if (clock === undefined) {
|
|
||||||
throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
|
|
||||||
}
|
|
||||||
} else if (typeof keyOrClock === 'object') {
|
|
||||||
clock = keyOrClock;
|
|
||||||
if (!this.globalTimeContext.clocks.has(clock.key)) {
|
|
||||||
throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousClock = this.activeClock;
|
|
||||||
if (previousClock !== undefined) {
|
|
||||||
previousClock.off("tick", this.tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activeClock = clock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
|
||||||
* @event clock
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {Clock} clock The newly activated clock, or undefined
|
|
||||||
* if the system is no longer following a clock source
|
|
||||||
*/
|
|
||||||
this.emit("clock", this.activeClock);
|
|
||||||
|
|
||||||
if (this.activeClock !== undefined) {
|
|
||||||
this.clockOffsets(offsets);
|
|
||||||
this.activeClock.on("tick", this.tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (arguments.length === 1) {
|
|
||||||
throw "When setting the clock, clock offsets must also be provided";
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.activeClock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default IndependentTimeContext;
|
|
||||||
@@ -20,8 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import GlobalTimeContext from "./GlobalTimeContext";
|
define(['EventEmitter'], function (EventEmitter) {
|
||||||
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The public API for setting and querying the temporal state of the
|
* The public API for setting and querying the temporal state of the
|
||||||
@@ -35,20 +34,37 @@ import IndependentTimeContext from "@/api/time/IndependentTimeContext";
|
|||||||
* the temporal state of the application. The current time bounds are also
|
* the temporal state of the application. The current time bounds are also
|
||||||
* used in queries for historical data.
|
* used in queries for historical data.
|
||||||
*
|
*
|
||||||
* The TimeAPI extends the GlobalTimeContext which in turn extends the TimeContext/EventEmitter class. A number of events are
|
* The TimeAPI extends the EventEmitter class. A number of events are
|
||||||
* fired when properties of the time conductor change, which are documented
|
* fired when properties of the time conductor change, which are documented
|
||||||
* below.
|
* below.
|
||||||
*
|
*
|
||||||
* @interface
|
* @interface
|
||||||
* @memberof module:openmct
|
* @memberof module:openmct
|
||||||
*/
|
*/
|
||||||
class TimeAPI extends GlobalTimeContext {
|
function TimeAPI() {
|
||||||
constructor(openmct) {
|
EventEmitter.call(this);
|
||||||
super();
|
|
||||||
this.openmct = openmct;
|
//The Time System
|
||||||
this.independentContexts = new Map();
|
this.system = undefined;
|
||||||
|
//The Time Of Interest
|
||||||
|
this.toi = undefined;
|
||||||
|
|
||||||
|
this.boundsVal = {
|
||||||
|
start: undefined,
|
||||||
|
end: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
this.timeSystems = new Map();
|
||||||
|
this.clocks = new Map();
|
||||||
|
this.activeClock = undefined;
|
||||||
|
this.offsets = undefined;
|
||||||
|
|
||||||
|
this.tick = this.tick.bind(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimeAPI.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
|
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
|
||||||
* MCT supports multiple different types of time values, although all are
|
* MCT supports multiple different types of time values, although all are
|
||||||
@@ -78,16 +94,16 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @param {TimeSystem} timeSystem A time system object.
|
* @param {TimeSystem} timeSystem A time system object.
|
||||||
*/
|
*/
|
||||||
addTimeSystem(timeSystem) {
|
TimeAPI.prototype.addTimeSystem = function (timeSystem) {
|
||||||
this.timeSystems.set(timeSystem.key, timeSystem);
|
this.timeSystems.set(timeSystem.key, timeSystem);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {TimeSystem[]}
|
* @returns {TimeSystem[]}
|
||||||
*/
|
*/
|
||||||
getAllTimeSystems() {
|
TimeAPI.prototype.getAllTimeSystems = function () {
|
||||||
return Array.from(this.timeSystems.values());
|
return Array.from(this.timeSystems.values());
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clocks provide a timing source that is used to
|
* Clocks provide a timing source that is used to
|
||||||
@@ -110,81 +126,340 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @param {Clock} clock
|
* @param {Clock} clock
|
||||||
*/
|
*/
|
||||||
addClock(clock) {
|
TimeAPI.prototype.addClock = function (clock) {
|
||||||
this.clocks.set(clock.key, clock);
|
this.clocks.set(clock.key, clock);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @returns {Clock[]}
|
* @returns {Clock[]}
|
||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
*/
|
*/
|
||||||
getAllClocks() {
|
TimeAPI.prototype.getAllClocks = function () {
|
||||||
return Array.from(this.clocks.values());
|
return Array.from(this.clocks.values());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set an independent time context which follows the TimeAPI timeSystem,
|
|
||||||
* but with different offsets for a given domain object
|
|
||||||
* @param {key | string} key The identifier key of the domain object these offsets are set for
|
|
||||||
* @param {ClockOffsets | TimeBounds} value This maintains a sliding time window of a fixed width that automatically updates
|
|
||||||
* @param {key | string} clockKey the real time clock key currently in use
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method addIndependentTimeContext
|
|
||||||
*/
|
|
||||||
addIndependentContext(key, value, clockKey) {
|
|
||||||
let timeContext = this.independentContexts.get(key);
|
|
||||||
if (!timeContext) {
|
|
||||||
timeContext = new IndependentTimeContext(this, key);
|
|
||||||
this.independentContexts.set(key, timeContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clockKey) {
|
|
||||||
timeContext.clock(clockKey, value);
|
|
||||||
} else {
|
|
||||||
timeContext.stopClock();
|
|
||||||
timeContext.bounds(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('timeContext', key);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
this.independentContexts.delete(key);
|
|
||||||
timeContext.emit('timeContext', key);
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the independent time context which follows the TimeAPI timeSystem,
|
* Validate the given bounds. This can be used for pre-validation of bounds,
|
||||||
* but with different offsets.
|
* for example by views validating user inputs.
|
||||||
* @param {key | string} key The identifier key of the domain object these offsets
|
* @param {TimeBounds} bounds The start and end time of the conductor.
|
||||||
|
* @returns {string | true} A validation error, or true if valid
|
||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @method getIndependentTimeContext
|
* @method validateBounds
|
||||||
*/
|
*/
|
||||||
getIndependentContext(key) {
|
TimeAPI.prototype.validateBounds = function (bounds) {
|
||||||
return this.independentContexts.get(key);
|
if ((bounds.start === undefined)
|
||||||
|
|| (bounds.end === undefined)
|
||||||
|
|| isNaN(bounds.start)
|
||||||
|
|| isNaN(bounds.end)
|
||||||
|
) {
|
||||||
|
return "Start and end must be specified as integer values";
|
||||||
|
} else if (bounds.start > bounds.end) {
|
||||||
|
return "Specified start date exceeds end bound";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the a timeContext for a view based on it's objectPath. If there is any object in the objectPath with an independent time context, it will be returned.
|
* Validate the given offsets. This can be used for pre-validation of
|
||||||
* Otherwise, the global time context will be returned.
|
* offsets, for example by views validating user inputs.
|
||||||
* @param { Array } objectPath The view's objectPath
|
* @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
|
||||||
|
* @returns {string | true} A validation error, or true if valid
|
||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @method getContextForView
|
* @method validateBounds
|
||||||
*/
|
*/
|
||||||
getContextForView(objectPath) {
|
TimeAPI.prototype.validateOffsets = function (offsets) {
|
||||||
let timeContext = this;
|
if ((offsets.start === undefined)
|
||||||
|
|| (offsets.end === undefined)
|
||||||
objectPath.forEach(item => {
|
|| isNaN(offsets.start)
|
||||||
const key = this.openmct.objects.makeKeyString(item.identifier);
|
|| isNaN(offsets.end)
|
||||||
if (this.independentContexts.get(key)) {
|
) {
|
||||||
timeContext = this.independentContexts.get(key);
|
return "Start and end offsets must be specified as integer values";
|
||||||
|
} else if (offsets.start >= offsets.end) {
|
||||||
|
return "Specified start offset must be < end offset";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} TimeBounds
|
||||||
|
* @property {number} start The start time displayed by the time conductor
|
||||||
|
* in ms since epoch. Epoch determined by currently active time system
|
||||||
|
* @property {number} end The end time displayed by the time conductor in ms
|
||||||
|
* since epoch.
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the start and end time of the time conductor. Basic validation
|
||||||
|
* of bounds is performed.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||||
|
* @throws {Error} Validation error
|
||||||
|
* @fires module:openmct.TimeAPI~bounds
|
||||||
|
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method bounds
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.bounds = function (newBounds) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
const validationResult = this.validateBounds(newBounds);
|
||||||
|
if (validationResult !== true) {
|
||||||
|
throw new Error(validationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a copy to avoid direct mutation of conductor bounds
|
||||||
|
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
|
||||||
|
/**
|
||||||
|
* The start time, end time, or both have been updated.
|
||||||
|
* @event bounds
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {TimeConductorBounds} bounds The newly updated bounds
|
||||||
|
* @property {boolean} [tick] `true` if the bounds update was due to
|
||||||
|
* a "tick" event (ie. was an automatic update), false otherwise.
|
||||||
|
*/
|
||||||
|
this.emit('bounds', this.boundsVal, false);
|
||||||
|
|
||||||
|
// If a bounds change results in a TOI outside of the current
|
||||||
|
// bounds, unset it
|
||||||
|
if (this.toi < newBounds.start || this.toi > newBounds.end) {
|
||||||
|
this.timeOfInterest(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||||
|
return JSON.parse(JSON.stringify(this.boundsVal));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the time system of the TimeAPI.
|
||||||
|
* @param {TimeSystem | string} timeSystem
|
||||||
|
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||||
|
* @fires module:openmct.TimeAPI~timeSystem
|
||||||
|
* @returns {TimeSystem} The currently applied time system
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method timeSystem
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.timeSystem = function (timeSystemOrKey, bounds) {
|
||||||
|
if (arguments.length >= 1) {
|
||||||
|
if (arguments.length === 1 && !this.activeClock) {
|
||||||
|
throw new Error(
|
||||||
|
"Must specify bounds when changing time system without "
|
||||||
|
+ "an active clock."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeSystem;
|
||||||
|
|
||||||
|
if (timeSystemOrKey === undefined) {
|
||||||
|
throw "Please provide a time system";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof timeSystemOrKey === 'string') {
|
||||||
|
timeSystem = this.timeSystems.get(timeSystemOrKey);
|
||||||
|
|
||||||
|
if (timeSystem === undefined) {
|
||||||
|
throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
|
||||||
|
}
|
||||||
|
} else if (typeof timeSystemOrKey === 'object') {
|
||||||
|
timeSystem = timeSystemOrKey;
|
||||||
|
|
||||||
|
if (!this.timeSystems.has(timeSystem.key)) {
|
||||||
|
throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.system = timeSystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time system used by the time
|
||||||
|
* conductor has changed. A change in Time System will always be
|
||||||
|
* followed by a bounds event specifying new query bounds.
|
||||||
|
*
|
||||||
|
* @event module:openmct.TimeAPI~timeSystem
|
||||||
|
* @property {TimeSystem} The value of the currently applied
|
||||||
|
* Time System
|
||||||
|
* */
|
||||||
|
this.emit('timeSystem', this.system);
|
||||||
|
if (bounds) {
|
||||||
|
this.bounds(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.system;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the Time of Interest. The Time of Interest is a single point
|
||||||
|
* in time, and constitutes the temporal focus of application views. It can
|
||||||
|
* be manipulated by the user from the time conductor or from other views.
|
||||||
|
* The time of interest can effectively be unset by assigning a value of
|
||||||
|
* 'undefined'.
|
||||||
|
* @fires module:openmct.TimeAPI~timeOfInterest
|
||||||
|
* @param newTOI
|
||||||
|
* @returns {number} the current time of interest
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method timeOfInterest
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.timeOfInterest = function (newTOI) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.toi = newTOI;
|
||||||
|
/**
|
||||||
|
* The Time of Interest has moved.
|
||||||
|
* @event timeOfInterest
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {number} Current time of interest
|
||||||
|
*/
|
||||||
|
this.emit('timeOfInterest', this.toi);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.toi;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update bounds based on provided time and current offsets
|
||||||
|
* @private
|
||||||
|
* @param {number} timestamp A time from which boudns will be calculated
|
||||||
|
* using current offsets.
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.tick = function (timestamp) {
|
||||||
|
const newBounds = {
|
||||||
|
start: timestamp + this.offsets.start,
|
||||||
|
end: timestamp + this.offsets.end
|
||||||
|
};
|
||||||
|
|
||||||
|
this.boundsVal = newBounds;
|
||||||
|
this.emit('bounds', this.boundsVal, true);
|
||||||
|
|
||||||
|
// If a bounds change results in a TOI outside of the current
|
||||||
|
// bounds, unset it
|
||||||
|
if (this.toi < newBounds.start || this.toi > newBounds.end) {
|
||||||
|
this.timeOfInterest(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active clock. Tick source will be immediately subscribed to
|
||||||
|
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
||||||
|
* can be unset by calling {@link stopClock}.
|
||||||
|
*
|
||||||
|
* @param {Clock || string} The clock to activate, or its key
|
||||||
|
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
||||||
|
* the start and end bounds. This maintains a sliding time window of a fixed
|
||||||
|
* width that automatically updates.
|
||||||
|
* @fires module:openmct.TimeAPI~clock
|
||||||
|
* @return {Clock} the currently active clock;
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.clock = function (keyOrClock, offsets) {
|
||||||
|
if (arguments.length === 2) {
|
||||||
|
let clock;
|
||||||
|
|
||||||
|
if (typeof keyOrClock === 'string') {
|
||||||
|
clock = this.clocks.get(keyOrClock);
|
||||||
|
if (clock === undefined) {
|
||||||
|
throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
|
||||||
|
}
|
||||||
|
} else if (typeof keyOrClock === 'object') {
|
||||||
|
clock = keyOrClock;
|
||||||
|
if (!this.clocks.has(clock.key)) {
|
||||||
|
throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousClock = this.activeClock;
|
||||||
|
if (previousClock !== undefined) {
|
||||||
|
previousClock.off("tick", this.tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeClock = clock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
||||||
|
* @event clock
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {Clock} clock The newly activated clock, or undefined
|
||||||
|
* if the system is no longer following a clock source
|
||||||
|
*/
|
||||||
|
this.emit("clock", this.activeClock);
|
||||||
|
|
||||||
|
if (this.activeClock !== undefined) {
|
||||||
|
this.clockOffsets(offsets);
|
||||||
|
this.activeClock.on("tick", this.tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (arguments.length === 1) {
|
||||||
|
throw "When setting the clock, clock offsets must also be provided";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.activeClock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clock offsets are used to calculate temporal bounds when the system is
|
||||||
|
* ticking on a clock source.
|
||||||
|
*
|
||||||
|
* @typedef {object} ClockOffsets
|
||||||
|
* @property {number} start A time span relative to the current value of the
|
||||||
|
* ticking clock, from which start bounds will be calculated. This value must
|
||||||
|
* be < 0. When a clock is active, bounds will be calculated automatically
|
||||||
|
* based on the value provided by the clock, and the defined clock offsets.
|
||||||
|
* @property {number} end A time span relative to the current value of the
|
||||||
|
* ticking clock, from which end bounds will be calculated. This value must
|
||||||
|
* be >= 0.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Get or set the currently applied clock offsets. If no parameter is provided,
|
||||||
|
* the current value will be returned. If provided, the new value will be
|
||||||
|
* used as the new clock offsets.
|
||||||
|
* @param {ClockOffsets} offsets
|
||||||
|
* @returns {ClockOffsets}
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.clockOffsets = function (offsets) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
|
||||||
|
const validationResult = this.validateOffsets(offsets);
|
||||||
|
if (validationResult !== true) {
|
||||||
|
throw new Error(validationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.offsets = offsets;
|
||||||
|
|
||||||
|
const currentValue = this.activeClock.currentValue();
|
||||||
|
const newBounds = {
|
||||||
|
start: currentValue + offsets.start,
|
||||||
|
end: currentValue + offsets.end
|
||||||
|
};
|
||||||
|
|
||||||
|
this.bounds(newBounds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event that is triggered when clock offsets change.
|
||||||
|
* @event clockOffsets
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {ClockOffsets} clockOffsets The newly activated clock
|
||||||
|
* offsets.
|
||||||
|
*/
|
||||||
|
this.emit("clockOffsets", offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.offsets;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the currently active clock from ticking, and unset it. This will
|
||||||
|
* revert all views to showing a static time frame defined by the current
|
||||||
|
* bounds.
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.stopClock = function () {
|
||||||
|
if (this.activeClock) {
|
||||||
|
this.clock(undefined, undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return TimeAPI;
|
||||||
});
|
});
|
||||||
|
|
||||||
return timeContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TimeAPI;
|
|
||||||
|
|||||||
@@ -19,9 +19,8 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import TimeAPI from "./TimeAPI";
|
|
||||||
import {createOpenMct} from "utils/testing";
|
|
||||||
|
|
||||||
|
define(['./TimeAPI'], function (TimeAPI) {
|
||||||
describe("The Time API", function () {
|
describe("The Time API", function () {
|
||||||
let api;
|
let api;
|
||||||
let timeSystemKey;
|
let timeSystemKey;
|
||||||
@@ -31,11 +30,9 @@ describe("The Time API", function () {
|
|||||||
let bounds;
|
let bounds;
|
||||||
let eventListener;
|
let eventListener;
|
||||||
let toi;
|
let toi;
|
||||||
let openmct;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
openmct = createOpenMct();
|
api = new TimeAPI();
|
||||||
api = new TimeAPI(openmct);
|
|
||||||
timeSystemKey = "timeSystemKey";
|
timeSystemKey = "timeSystemKey";
|
||||||
timeSystem = {key: timeSystemKey};
|
timeSystem = {key: timeSystemKey};
|
||||||
clockKey = "someClockKey";
|
clockKey = "someClockKey";
|
||||||
@@ -259,3 +256,4 @@ describe("The Time API", function () {
|
|||||||
}, true);
|
}, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,360 +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 EventEmitter from 'EventEmitter';
|
|
||||||
|
|
||||||
class TimeContext extends EventEmitter {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
//The Time System
|
|
||||||
this.timeSystems = new Map();
|
|
||||||
|
|
||||||
this.system = undefined;
|
|
||||||
|
|
||||||
this.clocks = new Map();
|
|
||||||
|
|
||||||
this.boundsVal = {
|
|
||||||
start: undefined,
|
|
||||||
end: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
this.activeClock = undefined;
|
|
||||||
this.offsets = undefined;
|
|
||||||
|
|
||||||
this.tick = this.tick.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set the time system of the TimeAPI.
|
|
||||||
* @param {TimeSystem | string} timeSystem
|
|
||||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
|
||||||
* @fires module:openmct.TimeAPI~timeSystem
|
|
||||||
* @returns {TimeSystem} The currently applied time system
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method timeSystem
|
|
||||||
*/
|
|
||||||
timeSystem(timeSystemOrKey, bounds) {
|
|
||||||
if (arguments.length >= 1) {
|
|
||||||
if (arguments.length === 1 && !this.activeClock) {
|
|
||||||
throw new Error(
|
|
||||||
"Must specify bounds when changing time system without "
|
|
||||||
+ "an active clock."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeSystem;
|
|
||||||
|
|
||||||
if (timeSystemOrKey === undefined) {
|
|
||||||
throw "Please provide a time system";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof timeSystemOrKey === 'string') {
|
|
||||||
timeSystem = this.timeSystems.get(timeSystemOrKey);
|
|
||||||
|
|
||||||
if (timeSystem === undefined) {
|
|
||||||
throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
|
|
||||||
}
|
|
||||||
} else if (typeof timeSystemOrKey === 'object') {
|
|
||||||
timeSystem = timeSystemOrKey;
|
|
||||||
|
|
||||||
if (!this.timeSystems.has(timeSystem.key)) {
|
|
||||||
throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.system = timeSystem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The time system used by the time
|
|
||||||
* conductor has changed. A change in Time System will always be
|
|
||||||
* followed by a bounds event specifying new query bounds.
|
|
||||||
*
|
|
||||||
* @event module:openmct.TimeAPI~timeSystem
|
|
||||||
* @property {TimeSystem} The value of the currently applied
|
|
||||||
* Time System
|
|
||||||
* */
|
|
||||||
this.emit('timeSystem', this.system);
|
|
||||||
if (bounds) {
|
|
||||||
this.bounds(bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.system;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clock offsets are used to calculate temporal bounds when the system is
|
|
||||||
* ticking on a clock source.
|
|
||||||
*
|
|
||||||
* @typedef {object} ValidationResult
|
|
||||||
* @property {boolean} valid Result of the validation - true or false.
|
|
||||||
* @property {string} message An error message if valid is false.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Validate the given bounds. This can be used for pre-validation of bounds,
|
|
||||||
* for example by views validating user inputs.
|
|
||||||
* @param {TimeBounds} bounds The start and end time of the conductor.
|
|
||||||
* @returns {ValidationResult} A validation error, or true if valid
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method validateBounds
|
|
||||||
*/
|
|
||||||
validateBounds(bounds) {
|
|
||||||
if ((bounds.start === undefined)
|
|
||||||
|| (bounds.end === undefined)
|
|
||||||
|| isNaN(bounds.start)
|
|
||||||
|| isNaN(bounds.end)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Start and end must be specified as integer values"
|
|
||||||
};
|
|
||||||
} else if (bounds.start > bounds.end) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Specified start date exceeds end bound"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
message: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set the start and end time of the time conductor. Basic validation
|
|
||||||
* of bounds is performed.
|
|
||||||
*
|
|
||||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
|
||||||
* @throws {Error} Validation error
|
|
||||||
* @fires module:openmct.TimeAPI~bounds
|
|
||||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method bounds
|
|
||||||
*/
|
|
||||||
bounds(newBounds) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
const validationResult = this.validateBounds(newBounds);
|
|
||||||
if (validationResult.valid !== true) {
|
|
||||||
throw new Error(validationResult.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a copy to avoid direct mutation of conductor bounds
|
|
||||||
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
|
|
||||||
/**
|
|
||||||
* The start time, end time, or both have been updated.
|
|
||||||
* @event bounds
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {TimeConductorBounds} bounds The newly updated bounds
|
|
||||||
* @property {boolean} [tick] `true` if the bounds update was due to
|
|
||||||
* a "tick" event (ie. was an automatic update), false otherwise.
|
|
||||||
*/
|
|
||||||
this.emit('bounds', this.boundsVal, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
|
||||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the given offsets. This can be used for pre-validation of
|
|
||||||
* offsets, for example by views validating user inputs.
|
|
||||||
* @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
|
|
||||||
* @returns { ValidationResult } A validation error, and true/false if valid or not
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method validateOffsets
|
|
||||||
*/
|
|
||||||
validateOffsets(offsets) {
|
|
||||||
if ((offsets.start === undefined)
|
|
||||||
|| (offsets.end === undefined)
|
|
||||||
|| isNaN(offsets.start)
|
|
||||||
|| isNaN(offsets.end)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Start and end offsets must be specified as integer values"
|
|
||||||
};
|
|
||||||
} else if (offsets.start >= offsets.end) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Specified start offset must be < end offset"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
message: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TimeBounds
|
|
||||||
* @property {number} start The start time displayed by the time conductor
|
|
||||||
* in ms since epoch. Epoch determined by currently active time system
|
|
||||||
* @property {number} end The end time displayed by the time conductor in ms
|
|
||||||
* since epoch.
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clock offsets are used to calculate temporal bounds when the system is
|
|
||||||
* ticking on a clock source.
|
|
||||||
*
|
|
||||||
* @typedef {object} ClockOffsets
|
|
||||||
* @property {number} start A time span relative to the current value of the
|
|
||||||
* ticking clock, from which start bounds will be calculated. This value must
|
|
||||||
* be < 0. When a clock is active, bounds will be calculated automatically
|
|
||||||
* based on the value provided by the clock, and the defined clock offsets.
|
|
||||||
* @property {number} end A time span relative to the current value of the
|
|
||||||
* ticking clock, from which end bounds will be calculated. This value must
|
|
||||||
* be >= 0.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Get or set the currently applied clock offsets. If no parameter is provided,
|
|
||||||
* the current value will be returned. If provided, the new value will be
|
|
||||||
* used as the new clock offsets.
|
|
||||||
* @param {ClockOffsets} offsets
|
|
||||||
* @returns {ClockOffsets}
|
|
||||||
*/
|
|
||||||
clockOffsets(offsets) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
|
|
||||||
const validationResult = this.validateOffsets(offsets);
|
|
||||||
if (validationResult.valid !== true) {
|
|
||||||
throw new Error(validationResult.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.offsets = offsets;
|
|
||||||
|
|
||||||
const currentValue = this.activeClock.currentValue();
|
|
||||||
const newBounds = {
|
|
||||||
start: currentValue + offsets.start,
|
|
||||||
end: currentValue + offsets.end
|
|
||||||
};
|
|
||||||
|
|
||||||
this.bounds(newBounds);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event that is triggered when clock offsets change.
|
|
||||||
* @event clockOffsets
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {ClockOffsets} clockOffsets The newly activated clock
|
|
||||||
* offsets.
|
|
||||||
*/
|
|
||||||
this.emit("clockOffsets", offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.offsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the currently active clock from ticking, and unset it. This will
|
|
||||||
* revert all views to showing a static time frame defined by the current
|
|
||||||
* bounds.
|
|
||||||
*/
|
|
||||||
stopClock() {
|
|
||||||
if (this.activeClock) {
|
|
||||||
this.clock(undefined, undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the active clock. Tick source will be immediately subscribed to
|
|
||||||
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
|
||||||
* can be unset by calling {@link stopClock}.
|
|
||||||
*
|
|
||||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
|
||||||
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
|
||||||
* the start and end bounds. This maintains a sliding time window of a fixed
|
|
||||||
* width that automatically updates.
|
|
||||||
* @fires module:openmct.TimeAPI~clock
|
|
||||||
* @return {Clock} the currently active clock;
|
|
||||||
*/
|
|
||||||
clock(keyOrClock, offsets) {
|
|
||||||
if (arguments.length === 2) {
|
|
||||||
let clock;
|
|
||||||
|
|
||||||
if (typeof keyOrClock === 'string') {
|
|
||||||
clock = this.clocks.get(keyOrClock);
|
|
||||||
if (clock === undefined) {
|
|
||||||
throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
|
|
||||||
}
|
|
||||||
} else if (typeof keyOrClock === 'object') {
|
|
||||||
clock = keyOrClock;
|
|
||||||
if (!this.clocks.has(clock.key)) {
|
|
||||||
throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousClock = this.activeClock;
|
|
||||||
if (previousClock !== undefined) {
|
|
||||||
previousClock.off("tick", this.tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activeClock = clock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
|
||||||
* @event clock
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {Clock} clock The newly activated clock, or undefined
|
|
||||||
* if the system is no longer following a clock source
|
|
||||||
*/
|
|
||||||
this.emit("clock", this.activeClock);
|
|
||||||
|
|
||||||
if (this.activeClock !== undefined) {
|
|
||||||
this.clockOffsets(offsets);
|
|
||||||
this.activeClock.on("tick", this.tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (arguments.length === 1) {
|
|
||||||
throw "When setting the clock, clock offsets must also be provided";
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.activeClock;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update bounds based on provided time and current offsets
|
|
||||||
* @param {number} timestamp A time from which bounds will be calculated
|
|
||||||
* using current offsets.
|
|
||||||
*/
|
|
||||||
tick(timestamp) {
|
|
||||||
if (!this.activeClock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newBounds = {
|
|
||||||
start: timestamp + this.offsets.start,
|
|
||||||
end: timestamp + this.offsets.end
|
|
||||||
};
|
|
||||||
|
|
||||||
this.boundsVal = newBounds;
|
|
||||||
this.emit('bounds', this.boundsVal, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TimeContext;
|
|
||||||
@@ -1,155 +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 TimeAPI from "./TimeAPI";
|
|
||||||
import {createOpenMct} from "utils/testing";
|
|
||||||
describe("The Independent Time API", function () {
|
|
||||||
let api;
|
|
||||||
let domainObjectKey;
|
|
||||||
let clockKey;
|
|
||||||
let clock;
|
|
||||||
let bounds;
|
|
||||||
let independentBounds;
|
|
||||||
let eventListener;
|
|
||||||
let openmct;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
openmct = createOpenMct();
|
|
||||||
api = new TimeAPI(openmct);
|
|
||||||
clockKey = "someClockKey";
|
|
||||||
clock = jasmine.createSpyObj("clock", [
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"currentValue"
|
|
||||||
]);
|
|
||||||
clock.currentValue.and.returnValue(100);
|
|
||||||
clock.key = clockKey;
|
|
||||||
api.addClock(clock);
|
|
||||||
domainObjectKey = 'test-key';
|
|
||||||
bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 1
|
|
||||||
};
|
|
||||||
api.bounds(bounds);
|
|
||||||
independentBounds = {
|
|
||||||
start: 10,
|
|
||||||
end: 11
|
|
||||||
};
|
|
||||||
eventListener = jasmine.createSpy("eventListener");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Creates an independent time context", () => {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getIndependentContext(domainObjectKey);
|
|
||||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Gets an independent time context given the objectPath", () => {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{
|
|
||||||
identifier: {
|
|
||||||
namespace: '',
|
|
||||||
key: 'blah'
|
|
||||||
}
|
|
||||||
}, { identifier: domainObjectKey }]);
|
|
||||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("defaults to the global time context given the objectPath", () => {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{
|
|
||||||
identifier: {
|
|
||||||
namespace: '',
|
|
||||||
key: 'blah'
|
|
||||||
}
|
|
||||||
}]);
|
|
||||||
expect(timeContext.bounds()).toEqual(bounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Allows setting of valid bounds", function () {
|
|
||||||
bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 1
|
|
||||||
};
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
|
||||||
expect(timeContext.bounds()).not.toEqual(bounds);
|
|
||||||
timeContext.bounds(bounds);
|
|
||||||
expect(timeContext.bounds()).toEqual(bounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Disallows setting of invalid bounds", function () {
|
|
||||||
bounds = {
|
|
||||||
start: 1,
|
|
||||||
end: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
|
||||||
expect(timeContext.bounds()).not.toBe(bounds);
|
|
||||||
|
|
||||||
expect(timeContext.bounds.bind(timeContext, bounds)).toThrow();
|
|
||||||
expect(timeContext.bounds()).not.toEqual(bounds);
|
|
||||||
|
|
||||||
bounds = {start: 1};
|
|
||||||
expect(timeContext.bounds()).not.toEqual(bounds);
|
|
||||||
expect(timeContext.bounds.bind(timeContext, bounds)).toThrow();
|
|
||||||
expect(timeContext.bounds()).not.toEqual(bounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Emits an event when bounds change", function () {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
|
||||||
expect(eventListener).not.toHaveBeenCalled();
|
|
||||||
timeContext.on('bounds', eventListener);
|
|
||||||
timeContext.bounds(bounds);
|
|
||||||
expect(eventListener).toHaveBeenCalledWith(bounds, false);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(" when using real time clock", function () {
|
|
||||||
const mockOffsets = {
|
|
||||||
start: 10,
|
|
||||||
end: 11
|
|
||||||
};
|
|
||||||
|
|
||||||
it("Emits an event when bounds change based on current value", function () {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
|
||||||
expect(eventListener).not.toHaveBeenCalled();
|
|
||||||
timeContext.clock('someClockKey', mockOffsets);
|
|
||||||
timeContext.on('bounds', eventListener);
|
|
||||||
timeContext.tick(10);
|
|
||||||
expect(eventListener).toHaveBeenCalledWith({
|
|
||||||
start: 20,
|
|
||||||
end: 21
|
|
||||||
}, true);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -63,6 +63,12 @@ define(['./Type'], function (Type) {
|
|||||||
*/
|
*/
|
||||||
TypeRegistry.prototype.standardizeType = function (typeDef) {
|
TypeRegistry.prototype.standardizeType = function (typeDef) {
|
||||||
if (Object.prototype.hasOwnProperty.call(typeDef, 'label')) {
|
if (Object.prototype.hasOwnProperty.call(typeDef, 'label')) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING typeDef: ' + typeDef.label + '. '
|
||||||
|
+ '`label` is deprecated in type definitions. Please use '
|
||||||
|
+ '`name` instead. This will cause errors in a future version '
|
||||||
|
+ 'of Open MCT. For more information, see '
|
||||||
|
+ 'https://github.com/nasa/openmct/issues/1568');
|
||||||
if (!typeDef.name) {
|
if (!typeDef.name) {
|
||||||
typeDef.name = typeDef.label;
|
typeDef.name = typeDef.label;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<div v-if="staticStyle"
|
<div v-if="staticStyle"
|
||||||
class="c-inspect-styles__style"
|
class="c-inspect-styles__style"
|
||||||
>
|
>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<style-editor class="c-inspect-styles__editor"
|
||||||
:style-item="staticStyle"
|
:style-item="staticStyle"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@persist="updateStaticStyle"
|
@persist="updateStaticStyle"
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
<condition-description :show-label="true"
|
<condition-description :show-label="true"
|
||||||
:condition="getCondition(conditionStyle.conditionId)"
|
:condition="getCondition(conditionStyle.conditionId)"
|
||||||
/>
|
/>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<style-editor class="c-inspect-styles__editor"
|
||||||
:style-item="conditionStyle"
|
:style-item="conditionStyle"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@persist="updateConditionalStyle"
|
@persist="updateConditionalStyle"
|
||||||
@@ -240,10 +240,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vm = new Vue({
|
let vm = new Vue({
|
||||||
components: {ConditionSetSelectorDialog},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct: this.openmct
|
openmct: this.openmct
|
||||||
},
|
},
|
||||||
|
components: {ConditionSetSelectorDialog},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
handleItemSelection
|
handleItemSelection
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<div v-if="staticStyle"
|
<div v-if="staticStyle"
|
||||||
class="c-inspect-styles__style"
|
class="c-inspect-styles__style"
|
||||||
>
|
>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<style-editor class="c-inspect-styles__editor"
|
||||||
:style-item="staticStyle"
|
:style-item="staticStyle"
|
||||||
:is-editing="allowEditing"
|
:is-editing="allowEditing"
|
||||||
:mixed-styles="mixedStyles"
|
:mixed-styles="mixedStyles"
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
<condition-description :show-label="true"
|
<condition-description :show-label="true"
|
||||||
:condition="getCondition(conditionStyle.conditionId)"
|
:condition="getCondition(conditionStyle.conditionId)"
|
||||||
/>
|
/>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<style-editor class="c-inspect-styles__editor"
|
||||||
:style-item="conditionStyle"
|
:style-item="conditionStyle"
|
||||||
:non-specific-font-properties="nonSpecificFontProperties"
|
:non-specific-font-properties="nonSpecificFontProperties"
|
||||||
:is-editing="allowEditing"
|
:is-editing="allowEditing"
|
||||||
@@ -556,10 +556,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vm = new Vue({
|
let vm = new Vue({
|
||||||
components: {ConditionSetSelectorDialog},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct: this.openmct
|
openmct: this.openmct
|
||||||
},
|
},
|
||||||
|
components: {ConditionSetSelectorDialog},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
handleItemSelection
|
handleItemSelection
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ a.c-condition-widget {
|
|||||||
|
|
||||||
// Make Condition Widget expand when in a hidden frame Layout context
|
// Make Condition Widget expand when in a hidden frame Layout context
|
||||||
// For both static and Flexible Layouts
|
// For both static and Flexible Layouts
|
||||||
.c-so-view--conditionWidget.c-so-view--no-frame {
|
.c-so-view--no-frame > .c-so-view__object-view > .c-condition-widget {
|
||||||
.c-condition-widget {
|
|
||||||
@include abs();
|
@include abs();
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -47,9 +46,6 @@ a.c-condition-widget {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-so-view__frame-controls { display: none; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add some margin when a Condition Widget is in a Flexible Layout
|
// Add some margin when a Condition Widget is in a Flexible Layout
|
||||||
.c-fl .c-so-view--no-frame .c-condition-widget {
|
.c-fl .c-so-view--no-frame .c-condition-widget {
|
||||||
@include abs(1px);
|
@include abs(1px);
|
||||||
|
|||||||
@@ -47,8 +47,8 @@
|
|||||||
|
|
||||||
.is-editing {
|
.is-editing {
|
||||||
.l-shell__main-container {
|
.l-shell__main-container {
|
||||||
[s-selected],
|
&[s-selected],
|
||||||
[s-selected-parent] {
|
&[s-selected-parent] {
|
||||||
// Display grid and allow edit marquee to display in main layout holder when editing
|
// Display grid and allow edit marquee to display in main layout holder when editing
|
||||||
> .l-layout {
|
> .l-layout {
|
||||||
background: $editUIGridColorBg;
|
background: $editUIGridColorBg;
|
||||||
|
|||||||
@@ -39,13 +39,10 @@ describe("The Compass component", () => {
|
|||||||
sunAngle: 30
|
sunAngle: 30
|
||||||
};
|
};
|
||||||
let propsData = {
|
let propsData = {
|
||||||
|
containerWidth: 600,
|
||||||
|
containerHeight: 600,
|
||||||
naturalAspectRatio: 0.9,
|
naturalAspectRatio: 0.9,
|
||||||
image: imageDatum,
|
image: imageDatum
|
||||||
sizedImageDimensions: {
|
|
||||||
width: 100,
|
|
||||||
height: 100
|
|
||||||
},
|
|
||||||
compassRoseSizingClasses: '--rose-small --rose-min'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
app = new Vue({
|
app = new Vue({
|
||||||
@@ -54,13 +51,13 @@ describe("The Compass component", () => {
|
|||||||
return propsData;
|
return propsData;
|
||||||
},
|
},
|
||||||
template: `<Compass
|
template: `<Compass
|
||||||
:compass-rose-sizing-classes="compassRoseSizingClasses"
|
:container-width="containerWidth"
|
||||||
:image="image"
|
:container-height="containerHeight"
|
||||||
:natural-aspect-ratio="naturalAspectRatio"
|
:natural-aspect-ratio="naturalAspectRatio"
|
||||||
:sized-image-dimensions="sizedImageDimensions"
|
:image="image" />`
|
||||||
/>`
|
|
||||||
});
|
});
|
||||||
instance = app.$mount();
|
instance = app.$mount();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import PreviewAction from "@/ui/preview/PreviewAction";
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
const PADDING = 1;
|
const PADDING = 1;
|
||||||
|
const RESIZE_POLL_INTERVAL = 200;
|
||||||
const ROW_HEIGHT = 100;
|
const ROW_HEIGHT = 100;
|
||||||
const IMAGE_WIDTH_THRESHOLD = 40;
|
const IMAGE_WIDTH_THRESHOLD = 40;
|
||||||
|
|
||||||
|
|||||||
@@ -84,18 +84,18 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
||||||
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev"
|
<button class="c-nav c-nav--prev"
|
||||||
title="Previous image"
|
title="Previous image"
|
||||||
:disabled="isPrevDisabled"
|
:disabled="isPrevDisabled"
|
||||||
@click="prevImage()"
|
@click="prevImage()"
|
||||||
></button>
|
></button>
|
||||||
|
<button class="c-nav c-nav--next"
|
||||||
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next"
|
|
||||||
title="Next image"
|
title="Next image"
|
||||||
:disabled="isNextDisabled"
|
:disabled="isNextDisabled"
|
||||||
@click="nextImage()"
|
@click="nextImage()"
|
||||||
></button>
|
></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="c-imagery__control-bar">
|
<div class="c-imagery__control-bar">
|
||||||
<div class="c-imagery__time">
|
<div class="c-imagery__time">
|
||||||
|
|||||||
@@ -285,17 +285,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-imagery__prev-next-button {
|
.c-imagery__prev-next-buttons {
|
||||||
pointer-events: all;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-75%); // 75% due to transform: rotation approach to the button
|
transform: translateY(-75%);
|
||||||
|
|
||||||
&.c-nav {
|
.c-nav {
|
||||||
position: absolute;
|
pointer-events: all;
|
||||||
|
|
||||||
&--prev { left: 0; }
|
|
||||||
&--next { right: 0; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-status-taking-snapshot & {
|
.s-status-taking-snapshot & {
|
||||||
|
|||||||
@@ -32,19 +32,19 @@ const TEN_MINUTES = ONE_MINUTE * 10;
|
|||||||
const MAIN_IMAGE_CLASS = '.js-imageryView-image';
|
const MAIN_IMAGE_CLASS = '.js-imageryView-image';
|
||||||
const NEW_IMAGE_CLASS = '.c-imagery__age.c-imagery--new';
|
const NEW_IMAGE_CLASS = '.c-imagery__age.c-imagery--new';
|
||||||
const REFRESH_CSS_MS = 500;
|
const REFRESH_CSS_MS = 500;
|
||||||
// const TOLERANCE = 0.50;
|
const TOLERANCE = 0.50;
|
||||||
|
|
||||||
// function comparisonFunction(valueOne, valueTwo) {
|
function comparisonFunction(valueOne, valueTwo) {
|
||||||
// let larger = valueOne;
|
let larger = valueOne;
|
||||||
// let smaller = valueTwo;
|
let smaller = valueTwo;
|
||||||
//
|
|
||||||
// if (larger < smaller) {
|
if (larger < smaller) {
|
||||||
// larger = valueTwo;
|
larger = valueTwo;
|
||||||
// smaller = valueOne;
|
smaller = valueOne;
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// return (larger - smaller) < TOLERANCE;
|
return (larger - smaller) < TOLERANCE;
|
||||||
// }
|
}
|
||||||
|
|
||||||
function getImageInfo(doc) {
|
function getImageInfo(doc) {
|
||||||
let imageElement = doc.querySelectorAll(MAIN_IMAGE_CLASS)[0];
|
let imageElement = doc.querySelectorAll(MAIN_IMAGE_CLASS)[0];
|
||||||
@@ -220,7 +220,7 @@ describe("The Imagery View Layouts", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve(imageryObject));
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
originalRouterPath = openmct.router.path;
|
originalRouterPath = openmct.router.path;
|
||||||
|
|
||||||
@@ -370,15 +370,18 @@ describe("The Imagery View Layouts", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show that an image is not new", (done) => {
|
xit("should show that an image is not new", (done) => {
|
||||||
const target = imageTelemetry[2].url;
|
const target = imageTelemetry[2].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
|
// used in code, need to wait to the 500ms here too
|
||||||
|
setTimeout(() => {
|
||||||
const imageIsNew = isNew(parent);
|
const imageIsNew = isNew(parent);
|
||||||
|
|
||||||
expect(imageIsNew).toBeFalse();
|
expect(imageIsNew).toBeFalse();
|
||||||
done();
|
done();
|
||||||
|
}, REFRESH_CSS_MS);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style lang="sass">
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import packages from './third-party-licenses.json';
|
import packages from './third-party-licenses.json';
|
||||||
|
|
||||||
|
|||||||
@@ -96,9 +96,6 @@ export function setDefaultNotebookPageId(pageId) {
|
|||||||
|
|
||||||
export function validateNotebookStorageObject() {
|
export function validateNotebookStorageObject() {
|
||||||
const notebookStorage = getDefaultNotebook();
|
const notebookStorage = getDefaultNotebook();
|
||||||
if (!notebookStorage) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let valid = false;
|
let valid = false;
|
||||||
if (notebookStorage) {
|
if (notebookStorage) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ describe('the plugin', () => {
|
|||||||
let countFramesPromise;
|
let countFramesPromise;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct(false);
|
||||||
|
|
||||||
element = document.createElement('div');
|
element = document.createElement('div');
|
||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ describe('the plugin', () => {
|
|||||||
filter: {},
|
filter: {},
|
||||||
disableObserve: true
|
disableObserve: true
|
||||||
};
|
};
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct(false);
|
||||||
|
|
||||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||||
mockIdentifierService = jasmine.createSpyObj(
|
mockIdentifierService = jasmine.createSpyObj(
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export default {
|
|||||||
TimelineAxis,
|
TimelineAxis,
|
||||||
SwimLane
|
SwimLane
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject', 'path'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -99,37 +99,21 @@ export default {
|
|||||||
this.canvasContext = this.canvas.getContext('2d');
|
this.canvasContext = this.canvas.getContext('2d');
|
||||||
|
|
||||||
this.setDimensions();
|
this.setDimensions();
|
||||||
this.setTimeContext();
|
this.updateViewBounds();
|
||||||
|
this.openmct.time.on("timeSystem", this.setScaleAndPlotActivities);
|
||||||
|
this.openmct.time.on("bounds", this.updateViewBounds);
|
||||||
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||||
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
|
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
clearInterval(this.resizeTimer);
|
clearInterval(this.resizeTimer);
|
||||||
this.stopFollowingTimeContext();
|
this.openmct.time.off("timeSystem", this.setScaleAndPlotActivities);
|
||||||
|
this.openmct.time.off("bounds", this.updateViewBounds);
|
||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setTimeContext() {
|
|
||||||
this.stopFollowingTimeContext();
|
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
|
||||||
this.timeContext.on("timeContext", this.setTimeContext);
|
|
||||||
this.followTimeContext();
|
|
||||||
},
|
|
||||||
followTimeContext() {
|
|
||||||
this.updateViewBounds(this.timeContext.bounds());
|
|
||||||
|
|
||||||
this.timeContext.on("timeSystem", this.setScaleAndPlotActivities);
|
|
||||||
this.timeContext.on("bounds", this.updateViewBounds);
|
|
||||||
},
|
|
||||||
stopFollowingTimeContext() {
|
|
||||||
if (this.timeContext) {
|
|
||||||
this.timeContext.off("timeSystem", this.setScaleAndPlotActivities);
|
|
||||||
this.timeContext.off("bounds", this.updateViewBounds);
|
|
||||||
this.timeContext.off("timeContext", this.setTimeContext);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
observeForChanges(mutatedObject) {
|
observeForChanges(mutatedObject) {
|
||||||
this.getPlanData(mutatedObject);
|
this.getPlanData(mutatedObject);
|
||||||
this.setScaleAndPlotActivities();
|
this.setScaleAndPlotActivities();
|
||||||
@@ -157,9 +141,13 @@ export default {
|
|||||||
getPlanData(domainObject) {
|
getPlanData(domainObject) {
|
||||||
this.planData = getValidatedPlan(domainObject);
|
this.planData = getValidatedPlan(domainObject);
|
||||||
},
|
},
|
||||||
updateViewBounds(bounds) {
|
updateViewBounds() {
|
||||||
if (bounds) {
|
this.viewBounds = this.openmct.time.bounds();
|
||||||
this.viewBounds = Object.create(bounds);
|
if (!this.options.compact) {
|
||||||
|
//Add a 50% padding to the end bounds to look ahead
|
||||||
|
let timespan = (this.viewBounds.end - this.viewBounds.start);
|
||||||
|
let padding = timespan / 2;
|
||||||
|
this.viewBounds.end = this.viewBounds.end + padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.timeSystem === undefined) {
|
if (this.timeSystem === undefined) {
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ export default function PlanViewProvider(openmct) {
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject,
|
domainObject
|
||||||
path: objectPath
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -36,15 +36,7 @@ describe('the plugin', function () {
|
|||||||
appHolder.style.width = '640px';
|
appHolder.style.width = '640px';
|
||||||
appHolder.style.height = '480px';
|
appHolder.style.height = '480px';
|
||||||
|
|
||||||
const timeSystemOptions = {
|
openmct = createOpenMct();
|
||||||
timeSystemKey: 'utc',
|
|
||||||
bounds: {
|
|
||||||
start: 1597160002854,
|
|
||||||
end: 1597181232854
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct = createOpenMct(timeSystemOptions);
|
|
||||||
openmct.install(new PlanPlugin());
|
openmct.install(new PlanPlugin());
|
||||||
|
|
||||||
planDefinition = openmct.types.get('plan').definition;
|
planDefinition = openmct.types.get('plan').definition;
|
||||||
@@ -56,6 +48,7 @@ describe('the plugin', function () {
|
|||||||
child.style.width = '640px';
|
child.style.width = '640px';
|
||||||
child.style.height = '480px';
|
child.style.height = '480px';
|
||||||
element.appendChild(child);
|
element.appendChild(child);
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.start(appHolder);
|
openmct.start(appHolder);
|
||||||
});
|
});
|
||||||
@@ -79,6 +72,7 @@ describe('the plugin', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('the plan view', () => {
|
describe('the plan view', () => {
|
||||||
|
|
||||||
it('provides a plan view', () => {
|
it('provides a plan view', () => {
|
||||||
const testViewObject = {
|
const testViewObject = {
|
||||||
id: "test-object",
|
id: "test-object",
|
||||||
@@ -89,6 +83,7 @@ describe('the plugin', function () {
|
|||||||
let planView = applicableViews.find((viewProvider) => viewProvider.key === 'plan.view');
|
let planView = applicableViews.find((viewProvider) => viewProvider.key === 'plan.view');
|
||||||
expect(planView).toBeDefined();
|
expect(planView).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('the plan view displays activities', () => {
|
describe('the plan view displays activities', () => {
|
||||||
@@ -160,22 +155,12 @@ describe('the plugin', function () {
|
|||||||
expect(labelEl.innerHTML).toEqual('TEST-GROUP');
|
expect(labelEl.innerHTML).toEqual('TEST-GROUP');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays the activities and their labels', (done) => {
|
it('displays the activities and their labels', () => {
|
||||||
const bounds = {
|
|
||||||
start: 1597160002854,
|
|
||||||
end: 1597181232854
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct.time.bounds(bounds);
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
const rectEls = element.querySelectorAll('.c-plan__contents rect');
|
const rectEls = element.querySelectorAll('.c-plan__contents rect');
|
||||||
expect(rectEls.length).toEqual(2);
|
expect(rectEls.length).toEqual(2);
|
||||||
const textEls = element.querySelectorAll('.c-plan__contents text');
|
const textEls = element.querySelectorAll('.c-plan__contents text');
|
||||||
expect(textEls.length).toEqual(3);
|
expect(textEls.length).toEqual(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, 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="u-contents">
|
|
||||||
<ul v-if="canEdit"
|
|
||||||
class="l-inspector-part"
|
|
||||||
>
|
|
||||||
<h2 v-if="heading"
|
|
||||||
:title="heading"
|
|
||||||
>{{ heading }}</h2>
|
|
||||||
<li class="grid-row">
|
|
||||||
<div class="grid-cell label"
|
|
||||||
:title="editTitle"
|
|
||||||
>{{ shortLabel }}</div>
|
|
||||||
<div class="grid-cell value">
|
|
||||||
<div class="c-click-swatch c-click-swatch--menu"
|
|
||||||
@click="toggleSwatch()"
|
|
||||||
>
|
|
||||||
<span class="c-color-swatch"
|
|
||||||
:style="{ background: currentColor }"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="c-palette c-palette--color">
|
|
||||||
<div v-show="swatchActive"
|
|
||||||
class="c-palette__items"
|
|
||||||
>
|
|
||||||
<div v-for="group in colorPaletteGroups"
|
|
||||||
:key="group.id"
|
|
||||||
class="u-contents"
|
|
||||||
>
|
|
||||||
<div v-for="color in group"
|
|
||||||
:key="color.id"
|
|
||||||
class="c-palette__item"
|
|
||||||
:class="{ 'selected': currentColor === color.hexString }"
|
|
||||||
:style="{ background: color.hexString }"
|
|
||||||
@click="setColor(color)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul v-else
|
|
||||||
class="l-inspector-part"
|
|
||||||
>
|
|
||||||
<h2 v-if="heading"
|
|
||||||
:title="heading"
|
|
||||||
>{{ heading }}</h2>
|
|
||||||
<li class="grid-row">
|
|
||||||
<div class="grid-cell label"
|
|
||||||
:title="viewTitle"
|
|
||||||
>{{ shortLabel }}</div>
|
|
||||||
<div class="grid-cell value">
|
|
||||||
<span class="c-color-swatch"
|
|
||||||
:style="{
|
|
||||||
'background': currentColor
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ColorPalette from './lib/ColorPalette';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
props: {
|
|
||||||
currentColor: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editTitle: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return 'Set the color.';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
viewTitle: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return 'The current color.';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shortLabel: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return 'Color';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
heading: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
swatchActive: false,
|
|
||||||
colorPaletteGroups: [],
|
|
||||||
isEditing: this.openmct.editor.isEditing()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
canEdit() {
|
|
||||||
return this.isEditing && !this.domainObject.locked;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.colorPalette = new ColorPalette();
|
|
||||||
this.openmct.editor.on('isEditing', this.setEditState);
|
|
||||||
this.initialize();
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.openmct.editor.off('isEditing', this.setEditState);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initialize() {
|
|
||||||
const colorPaletteGroups = this.colorPalette.groups();
|
|
||||||
colorPaletteGroups.forEach((group, index) => {
|
|
||||||
let groupId = [];
|
|
||||||
group.forEach(color => {
|
|
||||||
color.hexString = color.asHexString();
|
|
||||||
color.id = `${color.hexString}-${index}`;
|
|
||||||
groupId.push(color.id);
|
|
||||||
});
|
|
||||||
group.id = groupId.join('-');
|
|
||||||
});
|
|
||||||
this.colorPaletteGroups = colorPaletteGroups;
|
|
||||||
},
|
|
||||||
setEditState(isEditing) {
|
|
||||||
this.isEditing = isEditing;
|
|
||||||
},
|
|
||||||
setColor(chosenColor) {
|
|
||||||
this.$emit('colorSet', chosenColor);
|
|
||||||
},
|
|
||||||
toggleSwatch() {
|
|
||||||
this.swatchActive = !this.swatchActive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
import eventHelpers from './lib/eventHelpers';
|
import eventHelpers from './lib/eventHelpers';
|
||||||
import LinearScale from "./LinearScale";
|
import LinearScale from "./LinearScale";
|
||||||
import PlotConfigurationModel from './configuration/PlotConfigurationModel';
|
import PlotConfigurationModel from './configuration/PlotConfigurationModel';
|
||||||
import configStore from './configuration/ConfigStore';
|
import configStore from './configuration/configStore';
|
||||||
|
|
||||||
import PlotLegend from "./legend/PlotLegend.vue";
|
import PlotLegend from "./legend/PlotLegend.vue";
|
||||||
import MctTicks from "./MctTicks.vue";
|
import MctTicks from "./MctTicks.vue";
|
||||||
@@ -173,7 +173,7 @@ export default {
|
|||||||
MctTicks,
|
MctTicks,
|
||||||
MctChart
|
MctChart
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject', 'path'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -244,9 +244,6 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
this.updateRealTime = this.updateRealTime.bind(this);
|
|
||||||
this.updateDisplayBounds = this.updateDisplayBounds.bind(this);
|
|
||||||
this.setTimeContext = this.setTimeContext.bind(this);
|
|
||||||
|
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
this.legend = this.config.legend;
|
this.legend = this.config.legend;
|
||||||
@@ -264,7 +261,7 @@ export default {
|
|||||||
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.updateStatus);
|
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.updateStatus);
|
||||||
|
|
||||||
this.openmct.objectViews.on('clearData', this.clearData);
|
this.openmct.objectViews.on('clearData', this.clearData);
|
||||||
this.setTimeContext();
|
this.followTimeConductor();
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
|
||||||
@@ -277,27 +274,11 @@ export default {
|
|||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setTimeContext() {
|
followTimeConductor() {
|
||||||
this.stopFollowingTimeContext();
|
this.openmct.time.on('clock', this.updateRealTime);
|
||||||
|
this.openmct.time.on('bounds', this.updateDisplayBounds);
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
|
||||||
this.timeContext.on('timeContext', this.setTimeContext);
|
|
||||||
this.followTimeContext();
|
|
||||||
|
|
||||||
},
|
|
||||||
followTimeContext() {
|
|
||||||
this.updateDisplayBounds(this.timeContext.bounds());
|
|
||||||
this.timeContext.on('clock', this.updateRealTime);
|
|
||||||
this.timeContext.on('bounds', this.updateDisplayBounds);
|
|
||||||
this.synchronized(true);
|
this.synchronized(true);
|
||||||
},
|
},
|
||||||
stopFollowingTimeContext() {
|
|
||||||
if (this.timeContext) {
|
|
||||||
this.timeContext.off("clock", this.updateRealTime);
|
|
||||||
this.timeContext.off("bounds", this.updateDisplayBounds);
|
|
||||||
this.timeContext.off("timeContext", this.setTimeContext);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getConfig() {
|
getConfig() {
|
||||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
let config = configStore.get(configId);
|
let config = configStore.get(configId);
|
||||||
@@ -504,7 +485,7 @@ export default {
|
|||||||
* displays can update accordingly.
|
* displays can update accordingly.
|
||||||
*/
|
*/
|
||||||
synchronized(value) {
|
synchronized(value) {
|
||||||
const isLocalClock = this.timeContext.clock();
|
const isLocalClock = this.openmct.time.clock();
|
||||||
|
|
||||||
if (typeof value !== 'undefined') {
|
if (typeof value !== 'undefined') {
|
||||||
this._synchronized = value;
|
this._synchronized = value;
|
||||||
@@ -977,7 +958,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
showSynchronizeDialog() {
|
showSynchronizeDialog() {
|
||||||
const isLocalClock = this.timeContext.clock();
|
const isLocalClock = this.openmct.time.clock();
|
||||||
if (isLocalClock !== undefined) {
|
if (isLocalClock !== undefined) {
|
||||||
const message = `
|
const message = `
|
||||||
This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds.
|
This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds.
|
||||||
@@ -1012,9 +993,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
synchronizeTimeConductor() {
|
synchronizeTimeConductor() {
|
||||||
this.timeContext.stopClock();
|
this.openmct.time.stopClock();
|
||||||
const range = this.config.xAxis.get('displayRange');
|
const range = this.config.xAxis.get('displayRange');
|
||||||
this.timeContext.bounds({
|
this.openmct.time.bounds({
|
||||||
start: range.min,
|
start: range.min,
|
||||||
end: range.max
|
end: range.max
|
||||||
});
|
});
|
||||||
@@ -1025,7 +1006,6 @@ export default {
|
|||||||
configStore.deleteStore(this.config.id);
|
configStore.deleteStore(this.config.id);
|
||||||
|
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
|
|
||||||
if (this.checkForSize) {
|
if (this.checkForSize) {
|
||||||
clearInterval(this.checkForSize);
|
clearInterval(this.checkForSize);
|
||||||
delete this.checkForSize;
|
delete this.checkForSize;
|
||||||
@@ -1041,7 +1021,8 @@ export default {
|
|||||||
|
|
||||||
this.plotContainerResizeObserver.disconnect();
|
this.plotContainerResizeObserver.disconnect();
|
||||||
|
|
||||||
this.stopFollowingTimeContext();
|
this.openmct.time.off('clock', this.updateRealTime);
|
||||||
|
this.openmct.time.off('bounds', this.updateDisplayBounds);
|
||||||
this.openmct.objectViews.off('clearData', this.clearData);
|
this.openmct.objectViews.off('clearData', this.clearData);
|
||||||
},
|
},
|
||||||
updateStatus(status) {
|
updateStatus(status) {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import eventHelpers from "./lib/eventHelpers";
|
import eventHelpers from "./lib/eventHelpers";
|
||||||
import { ticks, getFormattedTicks } from "./tickUtils";
|
import { ticks, getFormattedTicks } from "./tickUtils";
|
||||||
import configStore from "./configuration/ConfigStore";
|
import configStore from "./configuration/configStore";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
MctPlot
|
MctPlot
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject', 'path'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -68,8 +68,7 @@ export default function PlotViewProvider(openmct) {
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject,
|
domainObject
|
||||||
path: objectPath
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import MctTicks from "../MctTicks.vue";
|
import MctTicks from "../MctTicks.vue";
|
||||||
import eventHelpers from '../lib/eventHelpers';
|
import eventHelpers from '../lib/eventHelpers';
|
||||||
import configStore from "../configuration/ConfigStore";
|
import configStore from "../configuration/configStore";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MctTicks from "../MctTicks.vue";
|
import MctTicks from "../MctTicks.vue";
|
||||||
import configStore from "../configuration/ConfigStore";
|
import configStore from "../configuration/configStore";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -1,53 +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 { BAR_GRAPH_KEY } from './BarGraphConstants';
|
|
||||||
|
|
||||||
export default function BarGraphCompositionPolicy(openmct) {
|
|
||||||
function hasAggregateDomainAndRange(metadata) {
|
|
||||||
const rangeValues = metadata.valuesForHints(['range']);
|
|
||||||
|
|
||||||
return rangeValues.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasBarGraphTelemetry(domainObject) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
|
||||||
|
|
||||||
return metadata.values().length > 0 && hasAggregateDomainAndRange(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
allow: function (parent, child) {
|
|
||||||
if ((parent.type === BAR_GRAPH_KEY)
|
|
||||||
&& ((child.type !== 'telemetry.plot.overlay') && (hasBarGraphTelemetry(child) === false))
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,346 +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 BarGraphCompositionPolicy from "./BarGraphCompositionPolicy";
|
|
||||||
import { createOpenMct } from "utils/testing";
|
|
||||||
|
|
||||||
describe("The bar graph composition policy", () => {
|
|
||||||
let openmct;
|
|
||||||
const mockMetaDataWithNoRangeHints = {
|
|
||||||
"period": 10,
|
|
||||||
"amplitude": 1,
|
|
||||||
"offset": 0,
|
|
||||||
"dataRateInHz": 1,
|
|
||||||
"phase": 0,
|
|
||||||
"randomness": 0,
|
|
||||||
valuesForHints: () => {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
"key": "name",
|
|
||||||
"name": "Name",
|
|
||||||
"format": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "utc",
|
|
||||||
"name": "Time",
|
|
||||||
"format": "utc",
|
|
||||||
"hints": {
|
|
||||||
"domain": 1,
|
|
||||||
"priority": 1
|
|
||||||
},
|
|
||||||
"source": "utc"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
const mockMetaDataWithRangeHints = {
|
|
||||||
"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": "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: mockMetaDataWithRangeHints
|
|
||||||
};
|
|
||||||
const mockTypeService = {
|
|
||||||
getType: () => {
|
|
||||||
return {
|
|
||||||
typeDef: mockTypeDef
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
openmct.$injector = {
|
|
||||||
get: () => {
|
|
||||||
return mockTypeService;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct.telemetry.isTelemetryObject = function (domainObject) {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("exists", () => {
|
|
||||||
expect(BarGraphCompositionPolicy(openmct).allow).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
xit("allow composition for telemetry that provides/supports bar graph meta data", () => {
|
|
||||||
const parent = {
|
|
||||||
"composition": [],
|
|
||||||
"configuration": {},
|
|
||||||
"name": "Some Bar Graph",
|
|
||||||
"type": "telemetry.plot.bar-graph",
|
|
||||||
"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(BarGraphCompositionPolicy(openmct).allow(parent, child)).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows composition for telemetry that contain at least one range", () => {
|
|
||||||
const mockTypeDef = {
|
|
||||||
telemetry: mockMetaDataWithRangeHints
|
|
||||||
};
|
|
||||||
const mockTypeService = {
|
|
||||||
getType: () => {
|
|
||||||
return {
|
|
||||||
typeDef: mockTypeDef
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
openmct.$injector = {
|
|
||||||
get: () => {
|
|
||||||
return mockTypeService;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const parent = {
|
|
||||||
"composition": [],
|
|
||||||
"configuration": {},
|
|
||||||
"name": "Some Bar Graph",
|
|
||||||
"type": "telemetry.plot.bar-graph",
|
|
||||||
"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(BarGraphCompositionPolicy(openmct).allow(parent, child)).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("disallows composition for telemetry that don't contain any range hints", () => {
|
|
||||||
const mockTypeDef = {
|
|
||||||
telemetry: mockMetaDataWithNoRangeHints
|
|
||||||
};
|
|
||||||
const mockTypeService = {
|
|
||||||
getType: () => {
|
|
||||||
return {
|
|
||||||
typeDef: mockTypeDef
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
openmct.$injector = {
|
|
||||||
get: () => {
|
|
||||||
return mockTypeService;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const parent = {
|
|
||||||
"composition": [],
|
|
||||||
"configuration": {},
|
|
||||||
"name": "Some Bar Graph",
|
|
||||||
"type": "telemetry.plot.bar-graph",
|
|
||||||
"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(BarGraphCompositionPolicy(openmct).allow(parent, child)).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("passthrough for composition for non bar graph 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(BarGraphCompositionPolicy(openmct).allow(parent, child)).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export const BAR_GRAPH_VIEW = 'bar-graph.view';
|
|
||||||
export const BAR_GRAPH_KEY = 'telemetry.plot.bar-graph';
|
|
||||||
export const BAR_GRAPH_INSPECTOR_KEY = 'telemetry.plot.bar-graph.inspector';
|
|
||||||
export const SUBSCRIBE = 'subscribe';
|
|
||||||
export const UNSUBSCRIBE = 'unsubscribe';
|
|
||||||
@@ -1,293 +0,0 @@
|
|||||||
<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-bar-chart"
|
|
||||||
></div>
|
|
||||||
<div v-if="false"
|
|
||||||
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-basic-dist';
|
|
||||||
import { SUBSCRIBE, UNSUBSCRIBE } from './BarGraphConstants';
|
|
||||||
|
|
||||||
const MULTI_AXES_X_PADDING_PERCENT = {
|
|
||||||
LEFT: 8,
|
|
||||||
RIGHT: 94
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
props: {
|
|
||||||
data: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plotAxisTitle: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isZoomed: false,
|
|
||||||
primaryYAxisRange: {
|
|
||||||
min: '',
|
|
||||||
max: ''
|
|
||||||
},
|
|
||||||
xAxisRange: {
|
|
||||||
min: '',
|
|
||||||
max: ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
data: {
|
|
||||||
immediate: false,
|
|
||||||
handler: 'updateData'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.removeBarColorListener) {
|
|
||||||
this.removeBarColorListener();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getAxisMinMax(axis) {
|
|
||||||
const min = axis.autoSize
|
|
||||||
? ''
|
|
||||||
: axis.min;
|
|
||||||
const max = axis.autoSize
|
|
||||||
? ''
|
|
||||||
: axis.max;
|
|
||||||
|
|
||||||
return {
|
|
||||||
min,
|
|
||||||
max
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getLayout() {
|
|
||||||
const yAxesMeta = this.getYAxisMeta();
|
|
||||||
const primaryYaxis = this.getYaxisLayout(yAxesMeta['1']);
|
|
||||||
const xAxisDomain = this.getXAxisDomain(yAxesMeta);
|
|
||||||
|
|
||||||
return {
|
|
||||||
autosize: true,
|
|
||||||
showlegend: false,
|
|
||||||
textposition: 'auto',
|
|
||||||
font: {
|
|
||||||
family: 'Helvetica Neue, Helvetica, Arial, sans-serif',
|
|
||||||
size: '12px',
|
|
||||||
color: '#666'
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
domain: xAxisDomain,
|
|
||||||
range: [this.xAxisRange.min, this.xAxisRange.max],
|
|
||||||
title: this.plotAxisTitle.xAxisTitle,
|
|
||||||
automargin: true,
|
|
||||||
fixedrange: true
|
|
||||||
},
|
|
||||||
yaxis: primaryYaxis,
|
|
||||||
margin: {
|
|
||||||
l: 5,
|
|
||||||
r: 5,
|
|
||||||
t: 5,
|
|
||||||
b: 0
|
|
||||||
},
|
|
||||||
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 = {
|
|
||||||
automargin: true,
|
|
||||||
fixedrange: true,
|
|
||||||
title
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
registerListeners() {
|
|
||||||
this.$refs.plot.on('plotly_relayout', this.zoom);
|
|
||||||
|
|
||||||
this.removeBarColorListener = this.openmct.objects.observe(
|
|
||||||
this.domainObject,
|
|
||||||
'configuration.barStyles',
|
|
||||||
this.barColorChanged
|
|
||||||
);
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
barColorChanged() {
|
|
||||||
const colors = [];
|
|
||||||
const indices = [];
|
|
||||||
this.data.forEach((item, index) => {
|
|
||||||
const key = item.key;
|
|
||||||
const color = this.domainObject.configuration.barStyles[key] && this.domainObject.configuration.barStyles[key].color;
|
|
||||||
indices.push(index);
|
|
||||||
if (color) {
|
|
||||||
colors.push();
|
|
||||||
} else {
|
|
||||||
colors.push(item.marker.color);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const plotUpdate = {
|
|
||||||
'marker.color': colors
|
|
||||||
};
|
|
||||||
Plotly.restyle(this.$refs.plot, plotUpdate, indices);
|
|
||||||
},
|
|
||||||
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>
|
|
||||||
|
|
||||||
@@ -1,286 +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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BarGraph ref="barGraph"
|
|
||||||
class="c-plot c-bar-chart-view"
|
|
||||||
:data="trace"
|
|
||||||
:plot-axis-title="plotAxisTitle"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import * as SPECTRAL_AGGREGATE from './BarGraphConstants';
|
|
||||||
import ColorPalette from '../lib/ColorPalette';
|
|
||||||
import BarGraph from './BarGraphPlot.vue';
|
|
||||||
import Color from "@/plugins/plot/lib/Color";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
BarGraph
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
composition: {},
|
|
||||||
currentDomainObject: this.domainObject,
|
|
||||||
subscriptions: [],
|
|
||||||
telemetryObjects: {},
|
|
||||||
trace: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
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}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.colorPalette = new ColorPalette();
|
|
||||||
this.loadComposition();
|
|
||||||
|
|
||||||
this.openmct.time.on('bounds', this.refreshData);
|
|
||||||
this.openmct.time.on('clock', this.clockChanged);
|
|
||||||
|
|
||||||
this.$refs.barGraph.$on(SPECTRAL_AGGREGATE.SUBSCRIBE, this.subscribeToAll);
|
|
||||||
this.$refs.barGraph.$on(SPECTRAL_AGGREGATE.UNSUBSCRIBE, this.removeAllSubscriptions);
|
|
||||||
|
|
||||||
this.unobserve = this.openmct.objects.observe(this.currentDomainObject, '*', this.updateDomainObject);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.$refs.barGraph.$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: {
|
|
||||||
addTelemetryObject(telemetryObject) {
|
|
||||||
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
|
||||||
|
|
||||||
if (!this.domainObject.configuration.barStyles) {
|
|
||||||
this.domainObject.configuration.barStyles = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// check to see if we've set a bar color
|
|
||||||
if (!this.domainObject.configuration.barStyles[key] || !this.domainObject.configuration.barStyles[key].color) {
|
|
||||||
const color = this.colorPalette.getNextColor().asHexString();
|
|
||||||
this.domainObject.configuration.barStyles[key] = {
|
|
||||||
name: telemetryObject.name,
|
|
||||||
color
|
|
||||||
};
|
|
||||||
this.openmct.objects.mutate(
|
|
||||||
this.domainObject,
|
|
||||||
`configuration.barStyles[${this.key}]`,
|
|
||||||
this.domainObject.configuration.barStyles[key]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let color = this.domainObject.configuration.barStyles[key].color;
|
|
||||||
if (!(color instanceof Color)) {
|
|
||||||
color = Color.fromHexString(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.colorPalette.remove(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
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]);
|
|
||||||
},
|
|
||||||
clockChanged() {
|
|
||||||
this.removeAllSubscriptions();
|
|
||||||
this.subscribeToAll();
|
|
||||||
},
|
|
||||||
getAxisMetadata(telemetryObject) {
|
|
||||||
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
|
||||||
const yAxisMetadata = metadata.valuesForHints(['range'])[0];
|
|
||||||
//Exclude 'name' and 'time' based metadata specifically, from the x-Axis values by using range hints only
|
|
||||||
const xAxisMetadata = metadata.valuesForHints(['range']);
|
|
||||||
|
|
||||||
return {
|
|
||||||
xAxisMetadata,
|
|
||||||
yAxisMetadata
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getOptions(telemetryObject) {
|
|
||||||
const { start, end } = this.openmct.time.bounds();
|
|
||||||
|
|
||||||
return {
|
|
||||||
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) {
|
|
||||||
const telemetryObjects = Object.values(this.telemetryObjects);
|
|
||||||
telemetryObjects.forEach(this.requestDataFor);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeAllSubscriptions() {
|
|
||||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
|
||||||
this.subscriptions = [];
|
|
||||||
},
|
|
||||||
removeSubscription(key) {
|
|
||||||
const found = this.subscriptions.findIndex(subscription => subscription.key === key);
|
|
||||||
if (found > -1) {
|
|
||||||
this.subscriptions[found].unsubscribe();
|
|
||||||
this.subscriptions.splice(found, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeTelemetryObject(identifier) {
|
|
||||||
const key = this.openmct.objects.makeKeyString(identifier);
|
|
||||||
delete this.telemetryObjects[key];
|
|
||||||
if (this.domainObject.configuration.barStyles[key]) {
|
|
||||||
delete this.domainObject.configuration.barStyles[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeSubscription(key);
|
|
||||||
|
|
||||||
this.trace = this.trace.filter(t => t.key !== key);
|
|
||||||
},
|
|
||||||
processData(telemetryObject, data, axisMetadata) {
|
|
||||||
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
|
||||||
|
|
||||||
if (data.message) {
|
|
||||||
this.openmct.notifications.alert(data.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
let xValues = [];
|
|
||||||
let yValues = [];
|
|
||||||
|
|
||||||
//populate X and Y values for plotly
|
|
||||||
axisMetadata.xAxisMetadata.forEach((metadata) => {
|
|
||||||
xValues.push(metadata.name);
|
|
||||||
if (data[metadata.key]) {
|
|
||||||
//TODO: Format the data?
|
|
||||||
yValues.push(data[metadata.key]);
|
|
||||||
} else {
|
|
||||||
yValues.push('');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const trace = {
|
|
||||||
key,
|
|
||||||
name: telemetryObject.name,
|
|
||||||
x: xValues,
|
|
||||||
y: yValues,
|
|
||||||
text: yValues.map(String),
|
|
||||||
xAxisMetadata: axisMetadata.xAxisMetadata,
|
|
||||||
yAxisMetadata: axisMetadata.yAxisMetadata,
|
|
||||||
type: 'bar',
|
|
||||||
marker: {
|
|
||||||
color: this.domainObject.configuration.barStyles[key].color
|
|
||||||
},
|
|
||||||
hoverinfo: 'skip'
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
|
||||||
|
|
||||||
this.removeSubscription(key);
|
|
||||||
|
|
||||||
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() {
|
|
||||||
const telemetryObjects = Object.values(this.telemetryObjects);
|
|
||||||
telemetryObjects.forEach(this.subscribeToObject);
|
|
||||||
},
|
|
||||||
updateDomainObject(newDomainObject) {
|
|
||||||
this.currentDomainObject = newDomainObject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -1,76 +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 BarGraphView from './BarGraphView.vue';
|
|
||||||
import { BAR_GRAPH_KEY, BAR_GRAPH_VIEW } from './BarGraphConstants';
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
export default function BarGraphViewProvider(openmct) {
|
|
||||||
function isCompactView(objectPath) {
|
|
||||||
return objectPath.find(object => object.type === 'time-strip');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: BAR_GRAPH_VIEW,
|
|
||||||
name: 'Spectral Aggregate Plot',
|
|
||||||
cssClass: 'icon-telemetry',
|
|
||||||
canView(domainObject, objectPath) {
|
|
||||||
return domainObject && domainObject.type === BAR_GRAPH_KEY;
|
|
||||||
},
|
|
||||||
|
|
||||||
canEdit(domainObject, objectPath) {
|
|
||||||
return domainObject && domainObject.type === BAR_GRAPH_KEY;
|
|
||||||
},
|
|
||||||
|
|
||||||
view: function (domainObject, objectPath) {
|
|
||||||
let component;
|
|
||||||
|
|
||||||
return {
|
|
||||||
show: function (element) {
|
|
||||||
let isCompact = isCompactView(objectPath);
|
|
||||||
component = new Vue({
|
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
BarGraphView
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct,
|
|
||||||
domainObject
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
options: {
|
|
||||||
compact: isCompact
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
template: '<bar-graph-view :options="options"></bar-graph-view>'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy: function () {
|
|
||||||
component.$destroy();
|
|
||||||
component = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { BAR_GRAPH_INSPECTOR_KEY, BAR_GRAPH_KEY } from '../BarGraphConstants';
|
|
||||||
import Vue from 'vue';
|
|
||||||
import Options from "./Options.vue";
|
|
||||||
|
|
||||||
export default function BarGraphInspectorViewProvider(openmct) {
|
|
||||||
return {
|
|
||||||
key: BAR_GRAPH_INSPECTOR_KEY,
|
|
||||||
name: 'Bar Graph Inspector View',
|
|
||||||
canView: function (selection) {
|
|
||||||
if (selection.length === 0 || selection[0].length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let object = selection[0][0].context.item;
|
|
||||||
|
|
||||||
return object
|
|
||||||
&& object.type === BAR_GRAPH_KEY;
|
|
||||||
},
|
|
||||||
view: function (selection) {
|
|
||||||
let component;
|
|
||||||
|
|
||||||
return {
|
|
||||||
show: function (element) {
|
|
||||||
component = new Vue({
|
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
Options
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct,
|
|
||||||
domainObject: selection[0][0].context.item
|
|
||||||
},
|
|
||||||
template: '<options></options>'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy: function () {
|
|
||||||
if (component) {
|
|
||||||
component.$destroy();
|
|
||||||
component = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
priority: function () {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, 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>
|
|
||||||
<ul>
|
|
||||||
<li class="c-tree__item menus-to-left">
|
|
||||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
|
||||||
:class="expandedCssClass"
|
|
||||||
@click="expanded = !expanded"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
<div>
|
|
||||||
<div class="c-object-label__name">{{ name }}</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<ColorSwatch v-if="expanded"
|
|
||||||
:current-color="currentColor"
|
|
||||||
title="Manually set the color for this bar graph."
|
|
||||||
edit-title="Manually set the color for this bar graph"
|
|
||||||
view-title="The color for this bar graph."
|
|
||||||
short-label="Color"
|
|
||||||
class="grid-properties"
|
|
||||||
@colorSet="setColor"
|
|
||||||
/>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ColorSwatch from '../../ColorSwatch.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
ColorSwatch
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
props: {
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentColor: undefined,
|
|
||||||
name: '',
|
|
||||||
expanded: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
expandedCssClass() {
|
|
||||||
return this.expanded ? 'c-disclosure-triangle--expanded' : '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
item: {
|
|
||||||
handler() {
|
|
||||||
this.initColor();
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.key = this.openmct.objects.makeKeyString(this.item);
|
|
||||||
this.initColor();
|
|
||||||
this.unObserve = this.openmct.objects.observe(this.domainObject, `this.domainObject.configuration.barStyles[${this.key}]`, this.initColor);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
if (this.unObserve) {
|
|
||||||
this.unObserve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initColor() {
|
|
||||||
if (this.domainObject.configuration.barStyles && this.domainObject.configuration.barStyles[this.key]) {
|
|
||||||
this.currentColor = this.domainObject.configuration.barStyles[this.key].color;
|
|
||||||
this.name = this.domainObject.configuration.barStyles[this.key].name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setColor(chosenColor) {
|
|
||||||
this.currentColor = chosenColor.asHexString();
|
|
||||||
this.openmct.objects.mutate(
|
|
||||||
this.domainObject,
|
|
||||||
`configuration.barStyles[${this.key}].color`,
|
|
||||||
this.currentColor
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, 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>
|
|
||||||
<ul class="c-tree">
|
|
||||||
<li v-for="series in domainObject.composition"
|
|
||||||
:key="series.key"
|
|
||||||
>
|
|
||||||
<bar-graph-options :item="series" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import BarGraphOptions from "./BarGraphOptions.vue";
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
BarGraphOptions
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isEditing: this.openmct.editor.isEditing()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
canEdit() {
|
|
||||||
return this.isEditing && !this.domainObject.locked;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.openmct.editor.on('isEditing', this.setEditState);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.openmct.editor.off('isEditing', this.setEditState);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setEditState(isEditing) {
|
|
||||||
this.isEditing = isEditing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -19,9 +19,6 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="gl-plot-chart-area">
|
<div class="gl-plot-chart-area">
|
||||||
<span v-html="canvasTemplate"></span>
|
<span v-html="canvasTemplate"></span>
|
||||||
@@ -41,7 +38,7 @@ import MCTChartLineStepAfter from './MCTChartLineStepAfter';
|
|||||||
import MCTChartPointSet from './MCTChartPointSet';
|
import MCTChartPointSet from './MCTChartPointSet';
|
||||||
import MCTChartAlarmPointSet from './MCTChartAlarmPointSet';
|
import MCTChartAlarmPointSet from './MCTChartAlarmPointSet';
|
||||||
import MCTChartAlarmLineSet from "./MCTChartAlarmLineSet";
|
import MCTChartAlarmLineSet from "./MCTChartAlarmLineSet";
|
||||||
import configStore from "../configuration/ConfigStore";
|
import configStore from "../configuration/configStore";
|
||||||
import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
|
import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
|
||||||
import LimitLine from "./LimitLine.vue";
|
import LimitLine from "./LimitLine.vue";
|
||||||
import LimitLabel from "./LimitLabel.vue";
|
import LimitLabel from "./LimitLabel.vue";
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Model from "./Model";
|
import Model from "./Model";
|
||||||
import { MARKER_SHAPES } from '../draw/MarkerShapes';
|
import { MARKER_SHAPES } from '../draw/MarkerShapes';
|
||||||
import configStore from "../configuration/ConfigStore";
|
import configStore from "../configuration/configStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plot series handle interpreting telemetry metadata for a single telemetry
|
* Plot series handle interpreting telemetry metadata for a single telemetry
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PlotOptionsItem from "./PlotOptionsItem.vue";
|
import PlotOptionsItem from "./PlotOptionsItem.vue";
|
||||||
import configStore from "../configuration/ConfigStore";
|
import configStore from "../configuration/configStore";
|
||||||
import eventHelpers from "../lib/eventHelpers";
|
import eventHelpers from "../lib/eventHelpers";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import SeriesForm from "./forms/SeriesForm.vue";
|
|||||||
import YAxisForm from "./forms/YAxisForm.vue";
|
import YAxisForm from "./forms/YAxisForm.vue";
|
||||||
import LegendForm from "./forms/LegendForm.vue";
|
import LegendForm from "./forms/LegendForm.vue";
|
||||||
import eventHelpers from "../lib/eventHelpers";
|
import eventHelpers from "../lib/eventHelpers";
|
||||||
import configStore from "../configuration/ConfigStore";
|
import configStore from "../configuration/configStore";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -68,23 +68,26 @@
|
|||||||
{{ limitLines ? "Enabled" : "Disabled" }}
|
{{ limitLines ? "Enabled" : "Disabled" }}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<ColorSwatch :current-color="seriesHexColor"
|
<li class="grid-row">
|
||||||
edit-title="Manually set the plot line and marker color for this series."
|
<div class="grid-cell label"
|
||||||
view-title="The plot line and marker color for this series."
|
title="The plot line and marker color for this series."
|
||||||
short-label="Color"
|
>Color</div>
|
||||||
/>
|
<div class="grid-cell value">
|
||||||
|
<span class="c-color-swatch"
|
||||||
|
:style="{
|
||||||
|
'background': seriesHexColor
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ColorSwatch from "@/plugins/plot/ColorSwatch.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
ColorSwatch
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject', 'path'],
|
inject: ['openmct', 'domainObject', 'path'],
|
||||||
props: {
|
props: {
|
||||||
series: {
|
series: {
|
||||||
|
|||||||
@@ -117,27 +117,49 @@
|
|||||||
<li v-show="interpolate !== 'none' || markers"
|
<li v-show="interpolate !== 'none' || markers"
|
||||||
class="grid-row"
|
class="grid-row"
|
||||||
>
|
>
|
||||||
<ColorSwatch :current-color="currentColor"
|
<div class="grid-cell label"
|
||||||
edit-title="Manually set the plot line and marker color for this series."
|
title="Manually set the plot line and marker color for this series."
|
||||||
view-title="The plot line and marker color for this series."
|
>Color</div>
|
||||||
short-label="Color"
|
<div class="grid-cell value">
|
||||||
@colorSet="setColor"
|
<div class="c-click-swatch c-click-swatch--menu"
|
||||||
/>
|
@click="toggleSwatch()"
|
||||||
|
>
|
||||||
|
<span class="c-color-swatch"
|
||||||
|
:style="{ background: seriesColorAsHex }"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="c-palette c-palette--color">
|
||||||
|
<div v-show="swatchActive"
|
||||||
|
class="c-palette__items"
|
||||||
|
>
|
||||||
|
<div v-for="(group, index) in colorPalette"
|
||||||
|
:key="index"
|
||||||
|
class="u-contents"
|
||||||
|
>
|
||||||
|
<div v-for="(color, colorIndex) in group"
|
||||||
|
:key="colorIndex"
|
||||||
|
class="c-palette__item"
|
||||||
|
:class="{ 'selected': series.get('color').equalTo(color) }"
|
||||||
|
:style="{ background: color.asHexString() }"
|
||||||
|
@click="setColor(color)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ColorSwatch from '../../ColorSwatch.vue';
|
|
||||||
import { MARKER_SHAPES } from "../../draw/MarkerShapes";
|
import { MARKER_SHAPES } from "../../draw/MarkerShapes";
|
||||||
import { objectPath, validate, coerce } from "./formUtil";
|
import { objectPath, validate, coerce } from "./formUtil";
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
ColorSwatch
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject', 'path'],
|
inject: ['openmct', 'domainObject', 'path'],
|
||||||
props: {
|
props: {
|
||||||
series: {
|
series: {
|
||||||
@@ -187,7 +209,7 @@ export default {
|
|||||||
expandedCssClass() {
|
expandedCssClass() {
|
||||||
return this.expanded ? 'c-disclosure-triangle--expanded' : '';
|
return this.expanded ? 'c-disclosure-triangle--expanded' : '';
|
||||||
},
|
},
|
||||||
currentColor() {
|
seriesColorAsHex() {
|
||||||
return this.series.get('color').asHexString();
|
return this.series.get('color').asHexString();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -89,4 +89,13 @@ ColorPalette.prototype.getNextColor = function () {
|
|||||||
return this.availableColors.shift();
|
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;
|
export default ColorPalette;
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ export default function OverlayPlotViewProvider(openmct) {
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject,
|
domainObject
|
||||||
path: objectPath
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -19,18 +19,13 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import { BAR_GRAPH_KEY } from './barGraph/BarGraphConstants';
|
|
||||||
import PlotViewProvider from './PlotViewProvider';
|
import PlotViewProvider from './PlotViewProvider';
|
||||||
import SpectralPlotViewProvider from './spectralPlot/SpectralPlotViewProvider';
|
|
||||||
import BarGraphViewProvider from './barGraph/BarGraphViewProvider';
|
|
||||||
import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider';
|
import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider';
|
||||||
import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider';
|
import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider';
|
||||||
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
|
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
|
||||||
import BarGraphInspectorViewProvider from './barGraph/inspector/BarGraphInspectorViewProvider';
|
|
||||||
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
|
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
|
||||||
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
|
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
|
||||||
import SpectralPlotCompositionPolicy from './spectralPlot/SpectralPlotCompositionPolicy';
|
|
||||||
import BarGraphCompositionPolicy from './barGraph/BarGraphCompositionPolicy';
|
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
@@ -64,48 +59,13 @@ export default function () {
|
|||||||
},
|
},
|
||||||
priority: 890
|
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.",
|
|
||||||
//Temporarily disabling spectral plots
|
|
||||||
creatable: false,
|
|
||||||
initialize: function (domainObject) {
|
|
||||||
domainObject.composition = [];
|
|
||||||
domainObject.configuration = {};
|
|
||||||
},
|
|
||||||
priority: 890
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.types.addType(BAR_GRAPH_KEY, {
|
|
||||||
key: BAR_GRAPH_KEY,
|
|
||||||
name: "Bar Graph",
|
|
||||||
cssClass: "icon-bar-chart",
|
|
||||||
description: "View data as a bar graph. 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 StackedPlotViewProvider(openmct));
|
||||||
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
||||||
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
||||||
openmct.objectViews.addProvider(new SpectralPlotViewProvider(openmct));
|
|
||||||
openmct.objectViews.addProvider(new BarGraphViewProvider(openmct));
|
|
||||||
|
|
||||||
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
|
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
|
||||||
openmct.inspectorViews.addProvider(new BarGraphInspectorViewProvider(openmct));
|
|
||||||
|
|
||||||
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
|
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
|
||||||
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
|
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
|
||||||
openmct.composition.addPolicy(new SpectralPlotCompositionPolicy(openmct).allow);
|
|
||||||
openmct.composition.addPolicy(new BarGraphCompositionPolicy(openmct).allow);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,10 @@ import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} f
|
|||||||
import PlotVuePlugin from "./plugin";
|
import PlotVuePlugin from "./plugin";
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import StackedPlot from "./stackedPlot/StackedPlot.vue";
|
import StackedPlot from "./stackedPlot/StackedPlot.vue";
|
||||||
// import SpectralPlot from "./spectralPlot/SpectralPlot.vue";
|
import configStore from "./configuration/configStore";
|
||||||
import configStore from "./configuration/ConfigStore";
|
|
||||||
import EventEmitter from "EventEmitter";
|
import EventEmitter from "EventEmitter";
|
||||||
import PlotOptions from "./inspector/PlotOptions.vue";
|
import PlotOptions from "./inspector/PlotOptions.vue";
|
||||||
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
|
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
|
||||||
import { BAR_GRAPH_VIEW, BAR_GRAPH_KEY } from './barGraph/BarGraphConstants';
|
|
||||||
|
|
||||||
describe("the plugin", function () {
|
describe("the plugin", function () {
|
||||||
let element;
|
let element;
|
||||||
@@ -37,6 +35,7 @@ describe("the plugin", function () {
|
|||||||
let openmct;
|
let openmct;
|
||||||
let telemetryPromise;
|
let telemetryPromise;
|
||||||
let telemetryPromiseResolve;
|
let telemetryPromiseResolve;
|
||||||
|
let cleanupFirst;
|
||||||
let mockObjectPath;
|
let mockObjectPath;
|
||||||
let telemetrylimitProvider;
|
let telemetrylimitProvider;
|
||||||
|
|
||||||
@@ -76,16 +75,9 @@ describe("the plugin", function () {
|
|||||||
'some-other-key': 'some-other-value 3'
|
'some-other-key': 'some-other-value 3'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
cleanupFirst = [];
|
||||||
|
|
||||||
const timeSystem = {
|
openmct = createOpenMct();
|
||||||
timeSystemKey: 'utc',
|
|
||||||
bounds: {
|
|
||||||
start: 0,
|
|
||||||
end: 4
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct = createOpenMct(timeSystem);
|
|
||||||
|
|
||||||
telemetryPromise = new Promise((resolve) => {
|
telemetryPromise = new Promise((resolve) => {
|
||||||
telemetryPromiseResolve = resolve;
|
telemetryPromiseResolve = resolve;
|
||||||
@@ -152,6 +144,11 @@ describe("the plugin", function () {
|
|||||||
disconnect() {}
|
disconnect() {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
openmct.time.timeSystem("utc", {
|
||||||
|
start: 0,
|
||||||
|
end: 4
|
||||||
|
});
|
||||||
|
|
||||||
openmct.types.addType("test-object", {
|
openmct.types.addType("test-object", {
|
||||||
creatable: true
|
creatable: true
|
||||||
});
|
});
|
||||||
@@ -171,9 +168,20 @@ describe("the plugin", function () {
|
|||||||
end: 1
|
end: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Needs to be in a timeout because plots use a bunch of setTimeouts, some of which can resolve during or after
|
||||||
|
// teardown, which causes problems
|
||||||
|
// This is hacky, we should find a better approach here.
|
||||||
|
setTimeout(() => {
|
||||||
|
//Cleanup code that needs to happen before dom elements start being destroyed
|
||||||
|
cleanupFirst.forEach(cleanup => cleanup());
|
||||||
|
cleanupFirst = [];
|
||||||
|
document.body.removeChild(element);
|
||||||
|
|
||||||
configStore.deleteAll();
|
configStore.deleteAll();
|
||||||
|
|
||||||
resetApplicationState(openmct).then(done).catch(done);
|
resetApplicationState(openmct).then(done).catch(done);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("the plot views", () => {
|
describe("the plot views", () => {
|
||||||
|
|
||||||
@@ -304,38 +312,6 @@ describe("the plugin", function () {
|
|||||||
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-stacked");
|
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-stacked");
|
||||||
expect(plotView).toBeDefined();
|
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: BAR_GRAPH_KEY,
|
|
||||||
telemetry: {
|
|
||||||
values: [{
|
|
||||||
key: "lots-of-aggregate-telemetry"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
|
||||||
let plotView = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
|
|
||||||
expect(plotView).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("The single plot view", () => {
|
describe("The single plot view", () => {
|
||||||
@@ -385,6 +361,10 @@ describe("the plugin", function () {
|
|||||||
plotView = plotViewProvider.view(testTelemetryObject, [testTelemetryObject]);
|
plotView = plotViewProvider.view(testTelemetryObject, [testTelemetryObject]);
|
||||||
plotView.show(child, true);
|
plotView.show(child, true);
|
||||||
|
|
||||||
|
cleanupFirst.push(() => {
|
||||||
|
plotView.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
return Vue.nextTick();
|
return Vue.nextTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -404,23 +384,12 @@ describe("the plugin", function () {
|
|||||||
expect(legend.length).toBe(6);
|
expect(legend.length).toBe(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Renders X-axis ticks for the telemetry object", (done) => {
|
it("Renders X-axis ticks for the telemetry object", () => {
|
||||||
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
|
|
||||||
const config = configStore.get(configId);
|
|
||||||
config.xAxis.set('displayRange', {
|
|
||||||
min: 0,
|
|
||||||
max: 4
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
|
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
|
||||||
expect(xAxisElement.length).toBe(1);
|
expect(xAxisElement.length).toBe(1);
|
||||||
|
|
||||||
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
|
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
|
||||||
expect(ticks.length).toBe(5);
|
expect(ticks.length).toBe(5);
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Renders Y-axis options for the telemetry object", () => {
|
it("Renders Y-axis options for the telemetry object", () => {
|
||||||
@@ -493,146 +462,6 @@ 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", () => {
|
describe("The stacked plot view", () => {
|
||||||
let testTelemetryObject;
|
let testTelemetryObject;
|
||||||
let testTelemetryObject2;
|
let testTelemetryObject2;
|
||||||
@@ -741,12 +570,16 @@ describe("the plugin", function () {
|
|||||||
provide: {
|
provide: {
|
||||||
openmct: openmct,
|
openmct: openmct,
|
||||||
domainObject: stackedPlotObject,
|
domainObject: stackedPlotObject,
|
||||||
composition: openmct.composition.get(stackedPlotObject),
|
composition: openmct.composition.get(stackedPlotObject)
|
||||||
path: [stackedPlotObject]
|
|
||||||
},
|
},
|
||||||
template: "<stacked-plot></stacked-plot>"
|
template: "<stacked-plot></stacked-plot>"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cleanupFirst.push(() => {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
return telemetryPromise
|
return telemetryPromise
|
||||||
.then(Vue.nextTick())
|
.then(Vue.nextTick())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -772,21 +605,12 @@ describe("the plugin", function () {
|
|||||||
expect(legend.length).toBe(6);
|
expect(legend.length).toBe(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Renders X-axis ticks for the telemetry object", (done) => {
|
xit("Renders X-axis ticks for the telemetry object", () => {
|
||||||
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
|
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
|
||||||
expect(xAxisElement.length).toBe(1);
|
expect(xAxisElement.length).toBe(1);
|
||||||
|
|
||||||
config.xAxis.set('displayRange', {
|
|
||||||
min: 0,
|
|
||||||
max: 4
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
|
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
|
||||||
expect(ticks.length).toBe(5);
|
expect(ticks.length).toBe(5);
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Renders Y-axis ticks for the telemetry object", (done) => {
|
it("Renders Y-axis ticks for the telemetry object", (done) => {
|
||||||
@@ -1138,7 +962,7 @@ describe("the plugin", function () {
|
|||||||
expandControl.dispatchEvent(clickEvent);
|
expandControl.dispatchEvent(clickEvent);
|
||||||
|
|
||||||
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
|
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
|
||||||
expect(plotOptionsProperties.length).toEqual(8);
|
expect(plotOptionsProperties.length).toEqual(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows yKeyOptions', () => {
|
it('shows yKeyOptions', () => {
|
||||||
@@ -1166,39 +990,4 @@ 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);
|
|
||||||
});
|
|
||||||
|
|
||||||
xit('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: BAR_GRAPH_KEY,
|
|
||||||
creatable: true
|
|
||||||
};
|
|
||||||
|
|
||||||
it('defines a spectral plot object type with the correct key', () => {
|
|
||||||
const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition;
|
|
||||||
expect(objectDef.key).toEqual(mockObject.key);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is creatable', () => {
|
|
||||||
const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition;
|
|
||||||
expect(objectDef.creatable).toEqual(mockObject.creatable);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,75 +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 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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
inject: ['openmct', 'domainObject']
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -52,7 +52,6 @@
|
|||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="l-view-section">
|
|
||||||
<stacked-plot-item v-for="object in compositionObjects"
|
<stacked-plot-item v-for="object in compositionObjects"
|
||||||
:key="object.id"
|
:key="object.id"
|
||||||
class="c-plot--stacked-container"
|
class="c-plot--stacked-container"
|
||||||
@@ -65,11 +64,10 @@
|
|||||||
@loadingUpdated="loadingUpdated"
|
@loadingUpdated="loadingUpdated"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import eventHelpers from '../lib/eventHelpers';
|
||||||
import StackedPlotItem from './StackedPlotItem.vue';
|
import StackedPlotItem from './StackedPlotItem.vue';
|
||||||
import ImageExporter from '../../../exporters/ImageExporter';
|
import ImageExporter from '../../../exporters/ImageExporter';
|
||||||
|
|
||||||
@@ -77,7 +75,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
StackedPlotItem
|
StackedPlotItem
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject', 'composition', 'path'],
|
inject: ['openmct', 'domainObject', 'composition'],
|
||||||
props: {
|
props: {
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -104,6 +102,8 @@ export default {
|
|||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
eventHelpers.extend(this);
|
||||||
|
|
||||||
this.imageExporter = new ImageExporter(this.openmct);
|
this.imageExporter = new ImageExporter(this.openmct);
|
||||||
|
|
||||||
this.tickWidthMap = {};
|
this.tickWidthMap = {};
|
||||||
@@ -118,6 +118,7 @@ export default {
|
|||||||
this.loading = loaded;
|
this.loading = loaded;
|
||||||
},
|
},
|
||||||
destroy() {
|
destroy() {
|
||||||
|
this.stopListening();
|
||||||
this.composition.off('add', this.addChild);
|
this.composition.off('add', this.addChild);
|
||||||
this.composition.off('remove', this.removeChild);
|
this.composition.off('remove', this.removeChild);
|
||||||
this.composition.off('reorder', this.compositionReorder);
|
this.composition.off('reorder', this.compositionReorder);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import MctPlot from '../MctPlot.vue';
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject', 'path'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
object: {
|
object: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -75,11 +75,6 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.updateView();
|
this.updateView();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
|
||||||
if (this.component) {
|
|
||||||
this.component.$destroy();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
updateComponentProp(prop, value) {
|
updateComponentProp(prop, value) {
|
||||||
if (this.component) {
|
if (this.component) {
|
||||||
@@ -99,7 +94,6 @@ export default {
|
|||||||
|
|
||||||
const openmct = this.openmct;
|
const openmct = this.openmct;
|
||||||
const object = this.object;
|
const object = this.object;
|
||||||
const path = this.path;
|
|
||||||
|
|
||||||
const getProps = this.getProps;
|
const getProps = this.getProps;
|
||||||
let viewContainer = document.createElement('div');
|
let viewContainer = document.createElement('div');
|
||||||
@@ -112,8 +106,7 @@ export default {
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject: object,
|
domainObject: object
|
||||||
path
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ export default function StackedPlotViewProvider(openmct) {
|
|||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject,
|
domainObject,
|
||||||
composition: openmct.composition.get(domainObject),
|
composition: openmct.composition.get(domainObject)
|
||||||
path: objectPath
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -48,17 +48,17 @@ define([
|
|||||||
components: {
|
components: {
|
||||||
TabsComponent: TabsComponent.default
|
TabsComponent: TabsComponent.default
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isEditing: editMode
|
||||||
|
};
|
||||||
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject,
|
domainObject,
|
||||||
objectPath,
|
objectPath,
|
||||||
composition: openmct.composition.get(domainObject)
|
composition: openmct.composition.get(domainObject)
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isEditing: editMode
|
|
||||||
};
|
|
||||||
},
|
|
||||||
template: '<tabs-component :isEditing="isEditing"></tabs-component>'
|
template: '<tabs-component :isEditing="isEditing"></tabs-component>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -528,6 +528,7 @@ export default {
|
|||||||
if (!this.updatingView) {
|
if (!this.updatingView) {
|
||||||
this.updatingView = true;
|
this.updatingView = true;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
|
||||||
let start = 0;
|
let start = 0;
|
||||||
let end = VISIBLE_ROW_COUNT;
|
let end = VISIBLE_ROW_COUNT;
|
||||||
let tableRows = this.table.tableRows.getRows();
|
let tableRows = this.table.tableRows.getRows();
|
||||||
|
|||||||
@@ -222,13 +222,9 @@ describe("the plugin", () => {
|
|||||||
openmct.router.path = originalRouterPath;
|
openmct.router.path = originalRouterPath;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Renders a row for every telemetry datum returned", (done) => {
|
it("Renders a row for every telemetry datum returned", () => {
|
||||||
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
|
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
Vue.nextTick(() => {
|
|
||||||
expect(rows.length).toBe(3);
|
expect(rows.length).toBe(3);
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Renders a column for every item in telemetry metadata", () => {
|
it("Renders a column for every item in telemetry metadata", () => {
|
||||||
|
|||||||
@@ -28,15 +28,117 @@
|
|||||||
{ 'alt-pressed': altPressed },
|
{ 'alt-pressed': altPressed },
|
||||||
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
|
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
|
||||||
]"
|
]"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
ref="conductorForm"
|
||||||
|
class="u-contents"
|
||||||
|
@submit.prevent="updateTimeFromConductor"
|
||||||
>
|
>
|
||||||
<div class="c-conductor__time-bounds">
|
<div class="c-conductor__time-bounds">
|
||||||
<conductor-inputs-fixed v-if="isFixed"
|
<button
|
||||||
@updated="saveFixedOffsets"
|
ref="submitButton"
|
||||||
/>
|
class="c-input--submit"
|
||||||
<conductor-inputs-realtime v-else
|
type="submit"
|
||||||
@updated="saveClockOffsets"
|
></button>
|
||||||
/>
|
|
||||||
<ConductorModeIcon class="c-conductor__mode-icon" />
|
<ConductorModeIcon class="c-conductor__mode-icon" />
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isFixed"
|
||||||
|
class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed"
|
||||||
|
>
|
||||||
|
<!-- Fixed start -->
|
||||||
|
<div class="c-conductor__start-fixed__label">
|
||||||
|
Start
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref="startDate"
|
||||||
|
v-model="formattedBounds.start"
|
||||||
|
class="c-input--datetime"
|
||||||
|
type="text"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
@change="validateAllBounds('startDate'); submitForm()"
|
||||||
|
>
|
||||||
|
<date-picker
|
||||||
|
v-if="isFixed && isUTCBased"
|
||||||
|
:default-date-time="formattedBounds.start"
|
||||||
|
:formatter="timeFormatter"
|
||||||
|
@date-selected="startDateSelected"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="!isFixed"
|
||||||
|
class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta"
|
||||||
|
>
|
||||||
|
<!-- RT start -->
|
||||||
|
<div class="c-direction-indicator icon-minus"></div>
|
||||||
|
<time-popup
|
||||||
|
v-if="showTCInputStart"
|
||||||
|
class="pr-tc-input-menu--start"
|
||||||
|
:type="'start'"
|
||||||
|
:offset="offsets.start"
|
||||||
|
@focus.native="$event.target.select()"
|
||||||
|
@hide="hideAllTimePopups"
|
||||||
|
@update="timePopUpdate"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
ref="startOffset"
|
||||||
|
class="c-button c-conductor__delta-button"
|
||||||
|
@click.prevent="showTimePopupStart"
|
||||||
|
>
|
||||||
|
{{ offsets.start }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
|
||||||
|
<!-- Fixed end and RT 'last update' display -->
|
||||||
|
<div class="c-conductor__end-fixed__label">
|
||||||
|
{{ isFixed ? 'End' : 'Updated' }}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref="endDate"
|
||||||
|
v-model="formattedBounds.end"
|
||||||
|
class="c-input--datetime"
|
||||||
|
type="text"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
:disabled="!isFixed"
|
||||||
|
@change="validateAllBounds('endDate'); submitForm()"
|
||||||
|
>
|
||||||
|
<date-picker
|
||||||
|
v-if="isFixed && isUTCBased"
|
||||||
|
class="c-ctrl-wrapper--menus-left"
|
||||||
|
:default-date-time="formattedBounds.end"
|
||||||
|
:formatter="timeFormatter"
|
||||||
|
@date-selected="endDateSelected"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="!isFixed"
|
||||||
|
class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
|
||||||
|
>
|
||||||
|
<!-- RT end -->
|
||||||
|
<div class="c-direction-indicator icon-plus"></div>
|
||||||
|
<time-popup
|
||||||
|
v-if="showTCInputEnd"
|
||||||
|
class="pr-tc-input-menu--end"
|
||||||
|
:type="'end'"
|
||||||
|
:offset="offsets.end"
|
||||||
|
@focus.native="$event.target.select()"
|
||||||
|
@hide="hideAllTimePopups"
|
||||||
|
@update="timePopUpdate"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
ref="endOffset"
|
||||||
|
class="c-button c-conductor__delta-button"
|
||||||
|
@click.prevent="showTimePopupEnd"
|
||||||
|
>
|
||||||
|
{{ offsets.end }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<conductor-axis
|
<conductor-axis
|
||||||
class="c-conductor__ticks"
|
class="c-conductor__ticks"
|
||||||
:view-bounds="viewBounds"
|
:view-bounds="viewBounds"
|
||||||
@@ -47,6 +149,7 @@
|
|||||||
@panAxis="pan"
|
@panAxis="pan"
|
||||||
@zoomAxis="zoom"
|
@zoomAxis="zoom"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="c-conductor__controls">
|
<div class="c-conductor__controls">
|
||||||
<ConductorMode class="c-conductor__mode-select" />
|
<ConductorMode class="c-conductor__mode-select" />
|
||||||
@@ -59,6 +162,11 @@
|
|||||||
:mode="timeMode"
|
:mode="timeMode"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
class="invisible"
|
||||||
|
>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -66,23 +174,23 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import ConductorMode from './ConductorMode.vue';
|
import ConductorMode from './ConductorMode.vue';
|
||||||
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
||||||
|
import DatePicker from './DatePicker.vue';
|
||||||
import ConductorAxis from './ConductorAxis.vue';
|
import ConductorAxis from './ConductorAxis.vue';
|
||||||
import ConductorModeIcon from './ConductorModeIcon.vue';
|
import ConductorModeIcon from './ConductorModeIcon.vue';
|
||||||
import ConductorHistory from './ConductorHistory.vue';
|
import ConductorHistory from './ConductorHistory.vue';
|
||||||
import ConductorInputsFixed from "./ConductorInputsFixed.vue";
|
import TimePopup from './timePopup.vue';
|
||||||
import ConductorInputsRealtime from "./ConductorInputsRealtime.vue";
|
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ConductorInputsRealtime,
|
|
||||||
ConductorInputsFixed,
|
|
||||||
ConductorMode,
|
ConductorMode,
|
||||||
ConductorTimeSystem,
|
ConductorTimeSystem,
|
||||||
|
DatePicker,
|
||||||
ConductorAxis,
|
ConductorAxis,
|
||||||
ConductorModeIcon,
|
ConductorModeIcon,
|
||||||
ConductorHistory
|
ConductorHistory,
|
||||||
|
TimePopup
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'configuration'],
|
inject: ['openmct', 'configuration'],
|
||||||
data() {
|
data() {
|
||||||
@@ -134,6 +242,7 @@ export default {
|
|||||||
this.openmct.time.on('bounds', _.throttle(this.handleNewBounds, 300));
|
this.openmct.time.on('bounds', _.throttle(this.handleNewBounds, 300));
|
||||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
||||||
this.openmct.time.on('clock', this.setViewFromClock);
|
this.openmct.time.on('clock', this.setViewFromClock);
|
||||||
|
this.openmct.time.on('clockOffsets', this.setViewFromOffsets);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
document.removeEventListener('keydown', this.handleKeyDown);
|
document.removeEventListener('keydown', this.handleKeyDown);
|
||||||
@@ -168,16 +277,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
zoom(bounds) {
|
zoom(bounds) {
|
||||||
if (isNaN(bounds.start) || isNaN(bounds.end)) {
|
|
||||||
this.isZooming = false;
|
|
||||||
} else {
|
|
||||||
this.isZooming = true;
|
this.isZooming = true;
|
||||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
endZoom(bounds) {
|
endZoom(bounds) {
|
||||||
this.isZooming = false;
|
this.isZooming = false;
|
||||||
|
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
this.openmct.time.bounds(bounds);
|
this.openmct.time.bounds(bounds);
|
||||||
} else {
|
} else {
|
||||||
@@ -191,8 +297,42 @@ export default {
|
|||||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
this.isUTCBased = timeSystem.isUTCBased;
|
this.isUTCBased = timeSystem.isUTCBased;
|
||||||
},
|
},
|
||||||
|
setOffsetsFromView($event) {
|
||||||
|
if (this.$refs.conductorForm.checkValidity()) {
|
||||||
|
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
||||||
|
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
||||||
|
|
||||||
|
this.openmct.time.clockOffsets({
|
||||||
|
start: startOffset,
|
||||||
|
end: endOffset
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event) {
|
||||||
|
$event.preventDefault();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setBoundsFromView($event) {
|
||||||
|
if (this.$refs.conductorForm.checkValidity()) {
|
||||||
|
let start = this.timeFormatter.parse(this.formattedBounds.start);
|
||||||
|
let end = this.timeFormatter.parse(this.formattedBounds.end);
|
||||||
|
|
||||||
|
this.openmct.time.bounds({
|
||||||
|
start: start,
|
||||||
|
end: end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event) {
|
||||||
|
$event.preventDefault();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
setViewFromClock(clock) {
|
setViewFromClock(clock) {
|
||||||
// this.clearAllValidation();
|
this.clearAllValidation();
|
||||||
this.isFixed = clock === undefined;
|
this.isFixed = clock === undefined;
|
||||||
},
|
},
|
||||||
setViewFromBounds(bounds) {
|
setViewFromBounds(bounds) {
|
||||||
@@ -201,16 +341,158 @@ export default {
|
|||||||
this.viewBounds.start = bounds.start;
|
this.viewBounds.start = bounds.start;
|
||||||
this.viewBounds.end = bounds.end;
|
this.viewBounds.end = bounds.end;
|
||||||
},
|
},
|
||||||
|
setViewFromOffsets(offsets) {
|
||||||
|
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
|
||||||
|
this.offsets.end = this.durationFormatter.format(Math.abs(offsets.end));
|
||||||
|
},
|
||||||
|
updateTimeFromConductor() {
|
||||||
|
if (this.isFixed) {
|
||||||
|
this.setBoundsFromView();
|
||||||
|
} else {
|
||||||
|
this.setOffsetsFromView();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getBoundsLimit() {
|
||||||
|
const configuration = this.configuration.menuOptions
|
||||||
|
.filter(option => option.timeSystem === this.timeSystem.key)
|
||||||
|
.find(option => option.limit);
|
||||||
|
|
||||||
|
const limit = configuration ? configuration.limit : undefined;
|
||||||
|
|
||||||
|
return limit;
|
||||||
|
},
|
||||||
|
clearAllValidation() {
|
||||||
|
if (this.isFixed) {
|
||||||
|
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
|
||||||
|
} else {
|
||||||
|
[this.$refs.startOffset, this.$refs.endOffset].forEach(this.clearValidationForInput);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearValidationForInput(input) {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
input.title = '';
|
||||||
|
},
|
||||||
|
validateAllBounds(ref) {
|
||||||
|
if (!this.areBoundsFormatsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let validationResult = true;
|
||||||
|
const currentInput = this.$refs[ref];
|
||||||
|
|
||||||
|
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||||
|
let boundsValues = {
|
||||||
|
start: this.timeFormatter.parse(this.formattedBounds.start),
|
||||||
|
end: this.timeFormatter.parse(this.formattedBounds.end)
|
||||||
|
};
|
||||||
|
const limit = this.getBoundsLimit();
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.timeSystem.isUTCBased
|
||||||
|
&& limit
|
||||||
|
&& boundsValues.end - boundsValues.start > limit
|
||||||
|
) {
|
||||||
|
if (input === currentInput) {
|
||||||
|
validationResult = "Start and end difference exceeds allowable limit";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (input === currentInput) {
|
||||||
|
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.handleValidationResults(input, validationResult);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
areBoundsFormatsValid() {
|
||||||
|
let validationResult = true;
|
||||||
|
|
||||||
|
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
||||||
|
const formattedDate = input === this.$refs.startDate
|
||||||
|
? this.formattedBounds.start
|
||||||
|
: this.formattedBounds.end
|
||||||
|
;
|
||||||
|
|
||||||
|
if (!this.timeFormatter.validate(formattedDate)) {
|
||||||
|
validationResult = 'Invalid date';
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.handleValidationResults(input, validationResult);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
validateAllOffsets(event) {
|
||||||
|
return [this.$refs.startOffset, this.$refs.endOffset].every((input) => {
|
||||||
|
let validationResult = true;
|
||||||
|
let formattedOffset;
|
||||||
|
|
||||||
|
if (input === this.$refs.startOffset) {
|
||||||
|
formattedOffset = this.offsets.start;
|
||||||
|
} else {
|
||||||
|
formattedOffset = this.offsets.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.durationFormatter.validate(formattedOffset)) {
|
||||||
|
validationResult = 'Offsets must be in the format hh:mm:ss and less than 24 hours in duration';
|
||||||
|
} else {
|
||||||
|
let offsetValues = {
|
||||||
|
start: 0 - this.durationFormatter.parse(this.offsets.start),
|
||||||
|
end: this.durationFormatter.parse(this.offsets.end)
|
||||||
|
};
|
||||||
|
validationResult = this.openmct.time.validateOffsets(offsetValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.handleValidationResults(input, validationResult);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleValidationResults(input, validationResult) {
|
||||||
|
if (validationResult !== true) {
|
||||||
|
input.setCustomValidity(validationResult);
|
||||||
|
input.title = validationResult;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
input.title = '';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
// Allow Vue model to catch up to user input.
|
||||||
|
// Submitting form will cause validation messages to display (but only if triggered by button click)
|
||||||
|
this.$nextTick(() => this.$refs.submitButton.click());
|
||||||
|
},
|
||||||
getFormatter(key) {
|
getFormatter(key) {
|
||||||
return this.openmct.telemetry.getValueFormatter({
|
return this.openmct.telemetry.getValueFormatter({
|
||||||
format: key
|
format: key
|
||||||
}).formatter;
|
}).formatter;
|
||||||
},
|
},
|
||||||
saveClockOffsets(offsets) {
|
startDateSelected(date) {
|
||||||
this.openmct.time.clockOffsets(offsets);
|
this.formattedBounds.start = this.timeFormatter.format(date);
|
||||||
|
this.validateAllBounds('startDate');
|
||||||
|
this.submitForm();
|
||||||
},
|
},
|
||||||
saveFixedOffsets(bounds) {
|
endDateSelected(date) {
|
||||||
this.openmct.time.bounds(bounds);
|
this.formattedBounds.end = this.timeFormatter.format(date);
|
||||||
|
this.validateAllBounds('endDate');
|
||||||
|
this.submitForm();
|
||||||
|
},
|
||||||
|
hideAllTimePopups() {
|
||||||
|
this.showTCInputStart = false;
|
||||||
|
this.showTCInputEnd = false;
|
||||||
|
},
|
||||||
|
showTimePopupStart() {
|
||||||
|
this.hideAllTimePopups();
|
||||||
|
this.showTCInputStart = !this.showTCInputStart;
|
||||||
|
},
|
||||||
|
showTimePopupEnd() {
|
||||||
|
this.hideAllTimePopups();
|
||||||
|
this.showTCInputEnd = !this.showTCInputEnd;
|
||||||
|
},
|
||||||
|
timePopUpdate({ type, hours, minutes, seconds }) {
|
||||||
|
this.offsets[type] = [hours, minutes, seconds].join(':');
|
||||||
|
this.setOffsetsFromView();
|
||||||
|
this.hideAllTimePopups();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const LOCAL_STORAGE_HISTORY_KEY_FIXED = 'tcHistory';
|
|||||||
const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
|
const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
|
||||||
const DEFAULT_RECORDS = 10;
|
const DEFAULT_RECORDS = 10;
|
||||||
|
|
||||||
import { millisecondsToDHMS } from "utils/duration";
|
import { getDuration } from "utils/duration";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'configuration'],
|
inject: ['openmct', 'configuration'],
|
||||||
@@ -142,7 +142,7 @@ export default {
|
|||||||
let description = `${startTime} - ${this.formatTime(timespan.end)}`;
|
let description = `${startTime} - ${this.formatTime(timespan.end)}`;
|
||||||
|
|
||||||
if (this.timeSystem.isUTCBased && !this.openmct.time.clock()) {
|
if (this.timeSystem.isUTCBased && !this.openmct.time.clock()) {
|
||||||
name = `${startTime} ${millisecondsToDHMS(timespan.end - timespan.start)}`;
|
name = `${startTime} ${getDuration(timespan.end - timespan.start)}`;
|
||||||
} else {
|
} else {
|
||||||
name = description;
|
name = description;
|
||||||
}
|
}
|
||||||
@@ -263,7 +263,7 @@ export default {
|
|||||||
format: format
|
format: format
|
||||||
}).formatter;
|
}).formatter;
|
||||||
|
|
||||||
return (isNegativeOffset ? '-' : '') + formatter.format(time, 'YYYY-MM-DD HH:mm:ss');
|
return (isNegativeOffset ? '-' : '') + formatter.format(time);
|
||||||
},
|
},
|
||||||
showHistoryMenu() {
|
showHistoryMenu() {
|
||||||
const elementBoundingClientRect = this.$refs.historyButton.getBoundingClientRect();
|
const elementBoundingClientRect = this.$refs.historyButton.getBoundingClientRect();
|
||||||
|
|||||||
@@ -1,280 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form ref="fixedDeltaInput"
|
|
||||||
class="c-conductor__inputs"
|
|
||||||
@submit.prevent="updateTimeFromConductor"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
ref="submitButton"
|
|
||||||
class="c-input--submit"
|
|
||||||
type="submit"
|
|
||||||
></button>
|
|
||||||
<div
|
|
||||||
class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed"
|
|
||||||
>
|
|
||||||
<!-- Fixed start -->
|
|
||||||
<div class="c-conductor__start-fixed__label">
|
|
||||||
Start
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
ref="startDate"
|
|
||||||
v-model="formattedBounds.start"
|
|
||||||
class="c-input--datetime"
|
|
||||||
type="text"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
@change="validateAllBounds('startDate'); submitForm()"
|
|
||||||
>
|
|
||||||
<date-picker
|
|
||||||
v-if="isUTCBased"
|
|
||||||
class="c-ctrl-wrapper--menus-left"
|
|
||||||
:bottom="keyString !== undefined"
|
|
||||||
:default-date-time="formattedBounds.start"
|
|
||||||
:formatter="timeFormatter"
|
|
||||||
@date-selected="startDateSelected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
|
|
||||||
<!-- Fixed end and RT 'last update' display -->
|
|
||||||
<div class="c-conductor__end-fixed__label">
|
|
||||||
End
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
ref="endDate"
|
|
||||||
v-model="formattedBounds.end"
|
|
||||||
class="c-input--datetime"
|
|
||||||
type="text"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
@change="validateAllBounds('endDate'); submitForm()"
|
|
||||||
>
|
|
||||||
<date-picker
|
|
||||||
v-if="isUTCBased"
|
|
||||||
class="c-ctrl-wrapper--menus-left"
|
|
||||||
:bottom="keyString !== undefined"
|
|
||||||
:default-date-time="formattedBounds.end"
|
|
||||||
:formatter="timeFormatter"
|
|
||||||
@date-selected="endDateSelected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
class="invisible"
|
|
||||||
>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import DatePicker from "./DatePicker.vue";
|
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
DatePicker
|
|
||||||
},
|
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
|
||||||
keyString: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
|
||||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
|
||||||
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
|
||||||
let bounds = this.bounds || this.openmct.time.bounds();
|
|
||||||
|
|
||||||
return {
|
|
||||||
showTCInputStart: true,
|
|
||||||
showTCInputEnd: true,
|
|
||||||
durationFormatter,
|
|
||||||
timeFormatter,
|
|
||||||
bounds: {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end
|
|
||||||
},
|
|
||||||
formattedBounds: {
|
|
||||||
start: timeFormatter.format(bounds.start),
|
|
||||||
end: timeFormatter.format(bounds.end)
|
|
||||||
},
|
|
||||||
isUTCBased: timeSystem.isUTCBased
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
|
||||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
|
||||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
|
||||||
this.setTimeContext();
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.clearAllValidation();
|
|
||||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
|
||||||
this.stopFollowingTimeContext();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setTimeContext() {
|
|
||||||
this.stopFollowingTimeContext();
|
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? [{identifier: this.keyString}] : []);
|
|
||||||
this.timeContext.on('timeContext', this.setTimeContext);
|
|
||||||
|
|
||||||
this.handleNewBounds(this.timeContext.bounds());
|
|
||||||
this.timeContext.on('bounds', this.handleNewBounds);
|
|
||||||
this.timeContext.on('clock', this.clearAllValidation);
|
|
||||||
},
|
|
||||||
stopFollowingTimeContext() {
|
|
||||||
if (this.timeContext) {
|
|
||||||
this.timeContext.off('bounds', this.handleNewBounds);
|
|
||||||
this.timeContext.off('clock', this.clearAllValidation);
|
|
||||||
this.timeContext.off('timeContext', this.setTimeContext);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleNewBounds(bounds) {
|
|
||||||
this.setBounds(bounds);
|
|
||||||
this.setViewFromBounds(bounds);
|
|
||||||
},
|
|
||||||
clearAllValidation() {
|
|
||||||
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
|
|
||||||
},
|
|
||||||
clearValidationForInput(input) {
|
|
||||||
input.setCustomValidity('');
|
|
||||||
input.title = '';
|
|
||||||
},
|
|
||||||
setBounds(bounds) {
|
|
||||||
this.bounds = bounds;
|
|
||||||
},
|
|
||||||
setViewFromBounds(bounds) {
|
|
||||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
|
||||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
|
||||||
},
|
|
||||||
setTimeSystem(timeSystem) {
|
|
||||||
this.timeSystem = timeSystem;
|
|
||||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
|
||||||
this.durationFormatter = this.getFormatter(
|
|
||||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
|
||||||
this.isUTCBased = timeSystem.isUTCBased;
|
|
||||||
},
|
|
||||||
getFormatter(key) {
|
|
||||||
return this.openmct.telemetry.getValueFormatter({
|
|
||||||
format: key
|
|
||||||
}).formatter;
|
|
||||||
},
|
|
||||||
setBoundsFromView($event) {
|
|
||||||
if (this.$refs.fixedDeltaInput.checkValidity()) {
|
|
||||||
let start = this.timeFormatter.parse(this.formattedBounds.start);
|
|
||||||
let end = this.timeFormatter.parse(this.formattedBounds.end);
|
|
||||||
|
|
||||||
this.$emit('updated', {
|
|
||||||
start: start,
|
|
||||||
end: end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($event) {
|
|
||||||
$event.preventDefault();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submitForm() {
|
|
||||||
// Allow Vue model to catch up to user input.
|
|
||||||
// Submitting form will cause validation messages to display (but only if triggered by button click)
|
|
||||||
this.$nextTick(() => this.$refs.submitButton.click());
|
|
||||||
},
|
|
||||||
updateTimeFromConductor() {
|
|
||||||
this.setBoundsFromView();
|
|
||||||
},
|
|
||||||
validateAllBounds(ref) {
|
|
||||||
if (!this.areBoundsFormatsValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let validationResult = {
|
|
||||||
valid: true
|
|
||||||
};
|
|
||||||
const currentInput = this.$refs[ref];
|
|
||||||
|
|
||||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
|
||||||
let boundsValues = {
|
|
||||||
start: this.timeFormatter.parse(this.formattedBounds.start),
|
|
||||||
end: this.timeFormatter.parse(this.formattedBounds.end)
|
|
||||||
};
|
|
||||||
//TODO: Do we need limits here? We have conductor limits disabled right now
|
|
||||||
// const limit = this.getBoundsLimit();
|
|
||||||
const limit = false;
|
|
||||||
|
|
||||||
if (this.timeSystem.isUTCBased && limit
|
|
||||||
&& boundsValues.end - boundsValues.start > limit) {
|
|
||||||
if (input === currentInput) {
|
|
||||||
validationResult = {
|
|
||||||
valid: false,
|
|
||||||
message: "Start and end difference exceeds allowable limit"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (input === currentInput) {
|
|
||||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleValidationResults(input, validationResult);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
areBoundsFormatsValid() {
|
|
||||||
let validationResult = {
|
|
||||||
valid: true
|
|
||||||
};
|
|
||||||
|
|
||||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
|
||||||
const formattedDate = input === this.$refs.startDate
|
|
||||||
? this.formattedBounds.start
|
|
||||||
: this.formattedBounds.end
|
|
||||||
;
|
|
||||||
|
|
||||||
if (!this.timeFormatter.validate(formattedDate)) {
|
|
||||||
validationResult = {
|
|
||||||
valid: false,
|
|
||||||
message: 'Invalid date'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleValidationResults(input, validationResult);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getBoundsLimit() {
|
|
||||||
const configuration = this.configuration.menuOptions
|
|
||||||
.filter(option => option.timeSystem === this.timeSystem.key)
|
|
||||||
.find(option => option.limit);
|
|
||||||
|
|
||||||
const limit = configuration ? configuration.limit : undefined;
|
|
||||||
|
|
||||||
return limit;
|
|
||||||
},
|
|
||||||
handleValidationResults(input, validationResult) {
|
|
||||||
if (validationResult.valid !== true) {
|
|
||||||
input.setCustomValidity(validationResult.message);
|
|
||||||
input.title = validationResult.message;
|
|
||||||
} else {
|
|
||||||
input.setCustomValidity('');
|
|
||||||
input.title = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return validationResult.valid;
|
|
||||||
},
|
|
||||||
startDateSelected(date) {
|
|
||||||
this.formattedBounds.start = this.timeFormatter.format(date);
|
|
||||||
this.validateAllBounds('startDate');
|
|
||||||
this.submitForm();
|
|
||||||
},
|
|
||||||
endDateSelected(date) {
|
|
||||||
this.formattedBounds.end = this.timeFormatter.format(date);
|
|
||||||
this.validateAllBounds('endDate');
|
|
||||||
this.submitForm();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form ref="deltaInput"
|
|
||||||
class="c-conductor__inputs"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta"
|
|
||||||
>
|
|
||||||
<!-- RT start -->
|
|
||||||
<div class="c-direction-indicator icon-minus"></div>
|
|
||||||
<time-popup
|
|
||||||
v-if="showTCInputStart"
|
|
||||||
class="pr-tc-input-menu--start"
|
|
||||||
:bottom="keyString !== undefined"
|
|
||||||
:type="'start'"
|
|
||||||
:offset="offsets.start"
|
|
||||||
@focus.native="$event.target.select()"
|
|
||||||
@hide="hideAllTimePopups"
|
|
||||||
@update="timePopUpdate"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
ref="startOffset"
|
|
||||||
class="c-button c-conductor__delta-button"
|
|
||||||
title="Set the time offset after now"
|
|
||||||
@click.prevent="showTimePopupStart"
|
|
||||||
>
|
|
||||||
{{ offsets.start }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
|
|
||||||
<!-- RT 'last update' display -->
|
|
||||||
<div class="c-conductor__end-fixed__label">
|
|
||||||
Current
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
ref="endDate"
|
|
||||||
v-model="formattedBounds.end"
|
|
||||||
class="c-input--datetime"
|
|
||||||
type="text"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
:disabled="true"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
|
|
||||||
>
|
|
||||||
<!-- RT end -->
|
|
||||||
<div class="c-direction-indicator icon-plus"></div>
|
|
||||||
<time-popup
|
|
||||||
v-if="showTCInputEnd"
|
|
||||||
class="pr-tc-input-menu--end"
|
|
||||||
:bottom="keyString !== undefined"
|
|
||||||
:type="'end'"
|
|
||||||
:offset="offsets.end"
|
|
||||||
@focus.native="$event.target.select()"
|
|
||||||
@hide="hideAllTimePopups"
|
|
||||||
@update="timePopUpdate"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
ref="endOffset"
|
|
||||||
class="c-button c-conductor__delta-button"
|
|
||||||
title="Set the time offset preceding now"
|
|
||||||
@click.prevent="showTimePopupEnd"
|
|
||||||
>
|
|
||||||
{{ offsets.end }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import timePopup from "./timePopup.vue";
|
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
timePopup
|
|
||||||
},
|
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
|
||||||
keyString: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
|
||||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
|
||||||
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
|
||||||
let bounds = this.bounds || this.openmct.time.bounds();
|
|
||||||
let offsets = this.openmct.time.clockOffsets();
|
|
||||||
|
|
||||||
return {
|
|
||||||
showTCInputStart: false,
|
|
||||||
showTCInputEnd: false,
|
|
||||||
durationFormatter,
|
|
||||||
timeFormatter,
|
|
||||||
bounds: {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end
|
|
||||||
},
|
|
||||||
offsets: {
|
|
||||||
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
|
|
||||||
end: offsets && durationFormatter.format(Math.abs(offsets.end))
|
|
||||||
},
|
|
||||||
formattedBounds: {
|
|
||||||
start: timeFormatter.format(bounds.start),
|
|
||||||
end: timeFormatter.format(bounds.end)
|
|
||||||
},
|
|
||||||
isUTCBased: timeSystem.isUTCBased
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
|
||||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
|
||||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
|
||||||
this.setTimeContext();
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
|
||||||
this.stopFollowingTime();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
followTime() {
|
|
||||||
this.handleNewBounds(this.timeContext.bounds());
|
|
||||||
this.setViewFromOffsets(this.timeContext.clockOffsets());
|
|
||||||
this.timeContext.on('bounds', this.handleNewBounds);
|
|
||||||
this.timeContext.on('clock', this.clearAllValidation);
|
|
||||||
this.timeContext.on('clockOffsets', this.setViewFromOffsets);
|
|
||||||
},
|
|
||||||
stopFollowingTime() {
|
|
||||||
if (this.timeContext) {
|
|
||||||
this.timeContext.off('bounds', this.handleNewBounds);
|
|
||||||
this.timeContext.off('clock', this.clearAllValidation);
|
|
||||||
this.timeContext.off('clockOffsets', this.setViewFromOffsets);
|
|
||||||
this.timeContext.off('timeContext', this.setTimeContext);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setTimeContext() {
|
|
||||||
this.stopFollowingTime();
|
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? [{identifier: this.keyString}] : []);
|
|
||||||
this.timeContext.on('timeContext', this.setTimeContext);
|
|
||||||
this.followTime();
|
|
||||||
},
|
|
||||||
handleNewBounds(bounds) {
|
|
||||||
this.setBounds(bounds);
|
|
||||||
this.setViewFromBounds(bounds);
|
|
||||||
},
|
|
||||||
clearAllValidation() {
|
|
||||||
[this.$refs.startOffset, this.$refs.endOffset].forEach(this.clearValidationForInput);
|
|
||||||
},
|
|
||||||
clearValidationForInput(input) {
|
|
||||||
input.setCustomValidity('');
|
|
||||||
input.title = '';
|
|
||||||
},
|
|
||||||
setViewFromOffsets(offsets) {
|
|
||||||
if (offsets) {
|
|
||||||
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
|
|
||||||
this.offsets.end = this.durationFormatter.format(Math.abs(offsets.end));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setBounds(bounds) {
|
|
||||||
this.bounds = bounds;
|
|
||||||
},
|
|
||||||
setViewFromBounds(bounds) {
|
|
||||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
|
||||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
|
||||||
},
|
|
||||||
setTimeSystem(timeSystem) {
|
|
||||||
this.timeSystem = timeSystem;
|
|
||||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
|
||||||
this.durationFormatter = this.getFormatter(
|
|
||||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
|
||||||
this.isUTCBased = timeSystem.isUTCBased;
|
|
||||||
},
|
|
||||||
getFormatter(key) {
|
|
||||||
return this.openmct.telemetry.getValueFormatter({
|
|
||||||
format: key
|
|
||||||
}).formatter;
|
|
||||||
},
|
|
||||||
hideAllTimePopups() {
|
|
||||||
this.showTCInputStart = false;
|
|
||||||
this.showTCInputEnd = false;
|
|
||||||
},
|
|
||||||
showTimePopupStart() {
|
|
||||||
this.hideAllTimePopups();
|
|
||||||
this.showTCInputStart = !this.showTCInputStart;
|
|
||||||
},
|
|
||||||
showTimePopupEnd() {
|
|
||||||
this.hideAllTimePopups();
|
|
||||||
this.showTCInputEnd = !this.showTCInputEnd;
|
|
||||||
},
|
|
||||||
timePopUpdate({ type, hours, minutes, seconds }) {
|
|
||||||
this.offsets[type] = [hours, minutes, seconds].join(':');
|
|
||||||
this.setOffsetsFromView();
|
|
||||||
this.hideAllTimePopups();
|
|
||||||
},
|
|
||||||
setOffsetsFromView($event) {
|
|
||||||
if (this.$refs.deltaInput.checkValidity()) {
|
|
||||||
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
|
||||||
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
|
||||||
|
|
||||||
this.$emit('updated', {
|
|
||||||
start: startOffset,
|
|
||||||
end: endOffset
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($event) {
|
|
||||||
$event.preventDefault();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
validateAllBounds(ref) {
|
|
||||||
if (!this.areBoundsFormatsValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let validationResult = {
|
|
||||||
valid: true
|
|
||||||
};
|
|
||||||
const currentInput = this.$refs[ref];
|
|
||||||
|
|
||||||
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
|
|
||||||
let boundsValues = {
|
|
||||||
start: this.timeFormatter.parse(this.formattedBounds.start),
|
|
||||||
end: this.timeFormatter.parse(this.formattedBounds.end)
|
|
||||||
};
|
|
||||||
//TODO: Do we need limits here? We have conductor limits disabled right now
|
|
||||||
// const limit = this.getBoundsLimit();
|
|
||||||
const limit = false;
|
|
||||||
|
|
||||||
if (this.timeSystem.isUTCBased && limit
|
|
||||||
&& boundsValues.end - boundsValues.start > limit) {
|
|
||||||
if (input === currentInput) {
|
|
||||||
validationResult = {
|
|
||||||
valid: false,
|
|
||||||
message: "Start and end difference exceeds allowable limit"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (input === currentInput) {
|
|
||||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.handleValidationResults(input, validationResult);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleValidationResults(input, validationResult) {
|
|
||||||
if (validationResult.valid !== true) {
|
|
||||||
input.setCustomValidity(validationResult.message);
|
|
||||||
input.title = validationResult.message;
|
|
||||||
} else {
|
|
||||||
input.setCustomValidity('');
|
|
||||||
input.title = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return validationResult.valid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -22,8 +22,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="calendarHolder"
|
ref="calendarHolder"
|
||||||
class="c-ctrl-wrapper c-datetime-picker__wrapper"
|
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up c-datetime-picker__wrapper"
|
||||||
:class="{'c-ctrl-wrapper--menus-up': bottom !== true, 'c-ctrl-wrapper--menus-down': bottom === true}"
|
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="c-icon-button icon-calendar"
|
class="c-icon-button icon-calendar"
|
||||||
@@ -119,12 +118,6 @@ export default {
|
|||||||
formatter: {
|
formatter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
|
||||||
bottom: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
|
|||||||
@@ -8,10 +8,6 @@
|
|||||||
|
|
||||||
/*********************************************** CONDUCTOR LAYOUT */
|
/*********************************************** CONDUCTOR LAYOUT */
|
||||||
.c-conductor {
|
.c-conductor {
|
||||||
&__inputs {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__time-bounds {
|
&__time-bounds {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-column-gap: $interiorMargin;
|
grid-column-gap: $interiorMargin;
|
||||||
@@ -54,6 +50,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[class*='-delta'] {
|
||||||
|
&:before {
|
||||||
|
content: $glyph-icon-clock;
|
||||||
|
font-family: symbolsfont;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.is-fixed-mode {
|
&.is-fixed-mode {
|
||||||
.c-conductor-axis {
|
.c-conductor-axis {
|
||||||
&__zoom-indicator {
|
&__zoom-indicator {
|
||||||
@@ -178,27 +181,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-conductor-holder--compact {
|
|
||||||
min-height: 22px;
|
|
||||||
|
|
||||||
.c-conductor {
|
|
||||||
&__inputs,
|
|
||||||
&__time-bounds {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__inputs {
|
|
||||||
> * + * {
|
|
||||||
margin-left: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-realtime-mode .c-conductor__end-fixed {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-conductor-input {
|
.c-conductor-input {
|
||||||
color: $colorInputFg;
|
color: $colorInputFg;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -268,21 +250,17 @@
|
|||||||
box-shadow: $shdwMenu;
|
box-shadow: $shdwMenu;
|
||||||
padding: $interiorMargin;
|
padding: $interiorMargin;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 8px;
|
|
||||||
bottom: 24px;
|
bottom: 24px;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
|
|
||||||
&[class*='--bottom'] {
|
&[class*='--start'] {
|
||||||
bottom: auto;
|
left: -25px;
|
||||||
top: 24px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-shell__time-conductor .pr-tc-input-menu--end {
|
&[class*='--end'] {
|
||||||
left: auto;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[class^='pr-time'] {
|
[class^='pr-time'] {
|
||||||
&[class*='label'] {
|
&[class*='label'] {
|
||||||
|
|||||||
@@ -1,231 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web 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 Web 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-conductor"
|
|
||||||
:class="[
|
|
||||||
isFixed ? 'is-fixed-mode' : independentTCEnabled ? 'is-realtime-mode' : 'is-fixed-mode'
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<div class="c-conductor__time-bounds">
|
|
||||||
<toggle-switch
|
|
||||||
id="independentTCToggle"
|
|
||||||
:checked="independentTCEnabled"
|
|
||||||
:title="`${independentTCEnabled ? 'Disable' : 'Enable'} independent Time Conductor`"
|
|
||||||
@change="toggleIndependentTC"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ConductorModeIcon />
|
|
||||||
|
|
||||||
<div v-if="timeOptions && independentTCEnabled"
|
|
||||||
class="c-conductor__controls"
|
|
||||||
>
|
|
||||||
<Mode v-if="mode"
|
|
||||||
class="c-conductor__mode-select"
|
|
||||||
:key-string="domainObject.identifier.key"
|
|
||||||
:mode="timeOptions.mode"
|
|
||||||
:enabled="independentTCEnabled"
|
|
||||||
@modeChanged="saveMode"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<conductor-inputs-fixed v-if="isFixed"
|
|
||||||
:key-string="domainObject.identifier.key"
|
|
||||||
@updated="saveFixedOffsets"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<conductor-inputs-realtime v-else
|
|
||||||
:key-string="domainObject.identifier.key"
|
|
||||||
@updated="saveClockOffsets"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ConductorInputsFixed from "../ConductorInputsFixed.vue";
|
|
||||||
import ConductorInputsRealtime from "../ConductorInputsRealtime.vue";
|
|
||||||
import ConductorModeIcon from "@/plugins/timeConductor/ConductorModeIcon.vue";
|
|
||||||
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
|
|
||||||
import Mode from "./Mode.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
Mode,
|
|
||||||
ConductorModeIcon,
|
|
||||||
ConductorInputsRealtime,
|
|
||||||
ConductorInputsFixed,
|
|
||||||
ToggleSwitch
|
|
||||||
},
|
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
|
||||||
domainObject: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
timeOptions: this.domainObject.configuration.timeOptions || {
|
|
||||||
clockOffsets: this.openmct.time.clockOffsets(),
|
|
||||||
fixedOffsets: this.openmct.time.bounds()
|
|
||||||
},
|
|
||||||
mode: undefined,
|
|
||||||
independentTCEnabled: this.domainObject.configuration.useIndependentTime === true
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isFixed() {
|
|
||||||
if (!this.mode || !this.mode.key) {
|
|
||||||
return this.openmct.time.clock() === undefined;
|
|
||||||
} else {
|
|
||||||
return this.mode.key === 'fixed';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
domainObject: {
|
|
||||||
handler(domainObject) {
|
|
||||||
if (!domainObject.configuration.timeOptions || !this.independentTCEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.timeOptions.start !== domainObject.configuration.timeOptions.start
|
|
||||||
|| this.timeOptions.end !== domainObject.configuration.timeOptions.end) {
|
|
||||||
this.timeOptions = domainObject.configuration.timeOptions;
|
|
||||||
this.destroyIndependentTime();
|
|
||||||
this.registerIndependentTimeOffsets();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.setTimeContext();
|
|
||||||
|
|
||||||
if (this.timeOptions.mode) {
|
|
||||||
this.mode = this.timeOptions.mode;
|
|
||||||
} else {
|
|
||||||
this.timeOptions.mode = this.mode = this.timeContext.clock() === undefined ? { key: 'fixed' } : { key: Object.create(this.timeContext.clock()).key};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.independentTCEnabled) {
|
|
||||||
this.registerIndependentTimeOffsets();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.stopFollowingTimeContext();
|
|
||||||
this.destroyIndependentTime();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleIndependentTC() {
|
|
||||||
this.independentTCEnabled = !this.independentTCEnabled;
|
|
||||||
if (this.independentTCEnabled) {
|
|
||||||
this.registerIndependentTimeOffsets();
|
|
||||||
} else {
|
|
||||||
this.destroyIndependentTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('stateChanged', this.independentTCEnabled);
|
|
||||||
},
|
|
||||||
setTimeContext() {
|
|
||||||
this.stopFollowingTimeContext();
|
|
||||||
this.timeContext = this.openmct.time.getContextForView([this.domainObject]);
|
|
||||||
this.timeContext.on('timeContext', this.setTimeContext);
|
|
||||||
this.timeContext.on('clock', this.setViewFromClock);
|
|
||||||
},
|
|
||||||
stopFollowingTimeContext() {
|
|
||||||
if (this.timeContext) {
|
|
||||||
this.timeContext.off('timeContext', this.setTimeContext);
|
|
||||||
this.timeContext.off('clock', this.setViewFromClock);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setViewFromClock(clock) {
|
|
||||||
if (!this.timeOptions.mode) {
|
|
||||||
this.setTimeOptions(clock);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setTimeOptions() {
|
|
||||||
if (!this.timeOptions || !this.timeOptions.mode) {
|
|
||||||
this.mode = this.timeContext.clock() === undefined ? { key: 'fixed' } : { key: Object.create(this.timeContext.clock()).key};
|
|
||||||
this.timeOptions = {
|
|
||||||
clockOffsets: this.timeContext.clockOffsets(),
|
|
||||||
fixedOffsets: this.timeContext.bounds()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.registerIndependentTimeOffsets();
|
|
||||||
},
|
|
||||||
saveFixedOffsets(offsets) {
|
|
||||||
const newOptions = Object.assign({}, this.timeOptions, {
|
|
||||||
fixedOffsets: offsets
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updateTimeOptions(newOptions);
|
|
||||||
},
|
|
||||||
saveClockOffsets(offsets) {
|
|
||||||
const newOptions = Object.assign({}, this.timeOptions, {
|
|
||||||
clockOffsets: offsets
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updateTimeOptions(newOptions);
|
|
||||||
},
|
|
||||||
saveMode(mode) {
|
|
||||||
this.mode = mode;
|
|
||||||
const newOptions = Object.assign({}, this.timeOptions, {
|
|
||||||
mode: this.mode
|
|
||||||
});
|
|
||||||
this.updateTimeOptions(newOptions);
|
|
||||||
},
|
|
||||||
updateTimeOptions(options) {
|
|
||||||
this.timeOptions = options;
|
|
||||||
if (!this.timeOptions.mode) {
|
|
||||||
this.timeOptions.mode = this.mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.registerIndependentTimeOffsets();
|
|
||||||
this.$emit('updated', this.timeOptions);
|
|
||||||
},
|
|
||||||
registerIndependentTimeOffsets() {
|
|
||||||
if (!this.timeOptions.mode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let offsets;
|
|
||||||
|
|
||||||
if (this.isFixed) {
|
|
||||||
offsets = this.timeOptions.fixedOffsets;
|
|
||||||
} else {
|
|
||||||
offsets = this.timeOptions.clockOffsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
this.unregisterIndependentTime = this.openmct.time.addIndependentContext(key, offsets, this.isFixed ? undefined : this.mode.key);
|
|
||||||
},
|
|
||||||
destroyIndependentTime() {
|
|
||||||
if (this.unregisterIndependentTime) {
|
|
||||||
this.unregisterIndependentTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web 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 Web 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 ref="modeMenuButton"
|
|
||||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
|
||||||
>
|
|
||||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
|
||||||
<button v-if="selectedMode"
|
|
||||||
class="c-button--menu c-mode-button"
|
|
||||||
@click.prevent.stop="showModesMenu"
|
|
||||||
>
|
|
||||||
<span class="c-button__label">{{ selectedMode.name }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import toggleMixin from '../../../ui/mixins/toggle-mixin';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mixins: [toggleMixin],
|
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
|
||||||
mode: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
let clock;
|
|
||||||
if (this.mode && this.mode.key === 'fixed') {
|
|
||||||
clock = undefined;
|
|
||||||
} else {
|
|
||||||
//We want the clock from the global time context here
|
|
||||||
clock = this.openmct.time.clock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clock !== undefined) {
|
|
||||||
//Create copy of active clock so the time API does not get reactified.
|
|
||||||
clock = Object.create(clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
selectedMode: this.getModeOptionForClock(clock),
|
|
||||||
modes: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
mode: {
|
|
||||||
deep: true,
|
|
||||||
handler(newMode) {
|
|
||||||
if (newMode) {
|
|
||||||
this.setViewFromClock(newMode.key === 'fixed' ? undefined : newMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled(newValue, oldValue) {
|
|
||||||
if (newValue !== undefined && (newValue !== oldValue) && (newValue === true)) {
|
|
||||||
this.setViewFromClock(this.mode.key === 'fixed' ? undefined : this.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted: function () {
|
|
||||||
if (this.mode) {
|
|
||||||
this.setViewFromClock(this.mode.key === 'fixed' ? undefined : this.mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.followTimeConductor();
|
|
||||||
},
|
|
||||||
destroyed: function () {
|
|
||||||
this.stopFollowTimeConductor();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
followTimeConductor() {
|
|
||||||
this.openmct.time.on('clock', this.setViewFromClock);
|
|
||||||
},
|
|
||||||
stopFollowTimeConductor() {
|
|
||||||
this.openmct.time.off('clock', this.setViewFromClock);
|
|
||||||
},
|
|
||||||
showModesMenu() {
|
|
||||||
const elementBoundingClientRect = this.$refs.modeMenuButton.getBoundingClientRect();
|
|
||||||
const x = elementBoundingClientRect.x;
|
|
||||||
const y = elementBoundingClientRect.y;
|
|
||||||
|
|
||||||
const menuOptions = {
|
|
||||||
menuClass: 'c-conductor__mode-menu',
|
|
||||||
placement: this.openmct.menus.menuPlacement.TOP_RIGHT
|
|
||||||
};
|
|
||||||
this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
|
||||||
},
|
|
||||||
|
|
||||||
getMenuOptions() {
|
|
||||||
let clocks = [{
|
|
||||||
name: 'Fixed Timespan',
|
|
||||||
timeSystem: 'utc'
|
|
||||||
}];
|
|
||||||
let currentGlobalClock = this.openmct.time.clock();
|
|
||||||
if (currentGlobalClock !== undefined) {
|
|
||||||
//Create copy of active clock so the time API does not get reactified.
|
|
||||||
currentGlobalClock = Object.assign({}, {
|
|
||||||
name: currentGlobalClock.name,
|
|
||||||
clock: currentGlobalClock.key,
|
|
||||||
timeSystem: this.openmct.time.timeSystem().key
|
|
||||||
});
|
|
||||||
|
|
||||||
clocks.push(currentGlobalClock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clocks;
|
|
||||||
},
|
|
||||||
loadClocks() {
|
|
||||||
let clocks = this.getMenuOptions()
|
|
||||||
.map(menuOption => menuOption.clock)
|
|
||||||
.filter(isDefinedAndUnique)
|
|
||||||
.map(this.getClock);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Populate the modes menu with metadata from the available clocks
|
|
||||||
* "Fixed Mode" is always first, and has no defined clock
|
|
||||||
*/
|
|
||||||
this.modes = [undefined]
|
|
||||||
.concat(clocks)
|
|
||||||
.map(this.getModeOptionForClock);
|
|
||||||
|
|
||||||
function isDefinedAndUnique(key, index, array) {
|
|
||||||
return key !== undefined && array.indexOf(key) === index;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getModeOptionForClock(clock) {
|
|
||||||
if (clock === undefined) {
|
|
||||||
const key = 'fixed';
|
|
||||||
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
name: 'Fixed Timespan',
|
|
||||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
|
||||||
cssClass: 'icon-tabular',
|
|
||||||
onItemClicked: () => this.setOption(key)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const key = clock.key;
|
|
||||||
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
name: clock.name,
|
|
||||||
description: "Monitor streaming data in real-time. The Time "
|
|
||||||
+ "Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
|
||||||
cssClass: clock.cssClass || 'icon-clock',
|
|
||||||
onItemClicked: () => this.setOption(key)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getClock(key) {
|
|
||||||
return this.openmct.time.getAllClocks().filter(function (clock) {
|
|
||||||
return clock.key === key;
|
|
||||||
})[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
setOption(clockKey) {
|
|
||||||
let key = clockKey;
|
|
||||||
if (clockKey === 'fixed') {
|
|
||||||
key = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchingOptions = this.getMenuOptions().filter(option => option.clock === key);
|
|
||||||
const clock = matchingOptions.length && matchingOptions[0].clock ? Object.assign({}, matchingOptions[0], { key: matchingOptions[0].clock }) : undefined;
|
|
||||||
this.selectedMode = this.getModeOptionForClock(clock);
|
|
||||||
|
|
||||||
if (this.mode) {
|
|
||||||
this.$emit('modeChanged', { key: clockKey });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setViewFromClock(clock) {
|
|
||||||
this.loadClocks();
|
|
||||||
//retain the mode chosen by the user
|
|
||||||
if (this.mode) {
|
|
||||||
let found = this.modes.find(mode => mode.key === this.selectedMode.key);
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
found = this.modes.find(mode => mode.key === clock && clock.key);
|
|
||||||
this.setOption(found ? this.getModeOptionForClock(clock).key : this.getModeOptionForClock().key);
|
|
||||||
} else if (this.mode.key !== this.selectedMode.key) {
|
|
||||||
this.setOption(this.selectedMode.key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.setOption(this.getModeOptionForClock(clock).key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,128 +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 {createMouseEvent, createOpenMct, resetApplicationState} from "utils/testing";
|
|
||||||
import ConductorPlugin from "./plugin";
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
const THIRTY_SECONDS = 30 * 1000;
|
|
||||||
const ONE_MINUTE = THIRTY_SECONDS * 2;
|
|
||||||
const FIVE_MINUTES = ONE_MINUTE * 5;
|
|
||||||
const FIFTEEN_MINUTES = FIVE_MINUTES * 3;
|
|
||||||
const THIRTY_MINUTES = FIFTEEN_MINUTES * 2;
|
|
||||||
const date = new Date(Date.UTC(78, 0, 20, 0, 0, 0)).getTime();
|
|
||||||
|
|
||||||
describe('time conductor', () => {
|
|
||||||
let element;
|
|
||||||
let child;
|
|
||||||
let appHolder;
|
|
||||||
let openmct;
|
|
||||||
let config = {
|
|
||||||
menuOptions: [
|
|
||||||
{
|
|
||||||
name: "FixedTimeRange",
|
|
||||||
timeSystem: 'utc',
|
|
||||||
bounds: {
|
|
||||||
start: date - THIRTY_MINUTES,
|
|
||||||
end: date
|
|
||||||
},
|
|
||||||
presets: [],
|
|
||||||
records: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "LocalClock",
|
|
||||||
timeSystem: 'utc',
|
|
||||||
clock: 'local',
|
|
||||||
clockOffsets: {
|
|
||||||
start: -THIRTY_MINUTES,
|
|
||||||
end: THIRTY_SECONDS
|
|
||||||
},
|
|
||||||
presets: []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach((done) => {
|
|
||||||
openmct = createOpenMct();
|
|
||||||
openmct.install(new ConductorPlugin(config));
|
|
||||||
|
|
||||||
element = document.createElement('div');
|
|
||||||
element.style.width = '640px';
|
|
||||||
element.style.height = '480px';
|
|
||||||
child = document.createElement('div');
|
|
||||||
child.style.width = '640px';
|
|
||||||
child.style.height = '480px';
|
|
||||||
element.appendChild(child);
|
|
||||||
|
|
||||||
openmct.on('start', () => {
|
|
||||||
openmct.time.bounds({
|
|
||||||
start: config.menuOptions[0].bounds.start,
|
|
||||||
end: config.menuOptions[0].bounds.end
|
|
||||||
});
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
appHolder = document.createElement("div");
|
|
||||||
openmct.start(appHolder);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
appHolder = undefined;
|
|
||||||
openmct = undefined;
|
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows delta inputs in fixed mode', () => {
|
|
||||||
const fixedModeEl = appHolder.querySelector('.is-fixed-mode');
|
|
||||||
const dateTimeInputs = fixedModeEl.querySelectorAll('.c-input--datetime');
|
|
||||||
expect(dateTimeInputs[0].value).toEqual('1978-01-19 23:30:00.000Z');
|
|
||||||
expect(dateTimeInputs[1].value).toEqual('1978-01-20 00:00:00.000Z');
|
|
||||||
expect(fixedModeEl.querySelector('.c-mode-button .c-button__label').innerHTML).toEqual('Fixed Timespan');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('shows delta inputs in realtime mode', () => {
|
|
||||||
beforeEach((done) => {
|
|
||||||
const switcher = appHolder.querySelector('.c-mode-button');
|
|
||||||
const clickEvent = createMouseEvent("click");
|
|
||||||
|
|
||||||
switcher.dispatchEvent(clickEvent);
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
const clockItem = document.querySelectorAll('.c-conductor__mode-menu li')[1];
|
|
||||||
clockItem.dispatchEvent(clickEvent);
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows clock options', () => {
|
|
||||||
const realtimeModeEl = appHolder.querySelector('.is-realtime-mode');
|
|
||||||
const dateTimeInputs = realtimeModeEl.querySelectorAll('.c-conductor__delta-button');
|
|
||||||
expect(dateTimeInputs[0].innerHTML.replace(/[^(\d|:)]/g, '')).toEqual('00:30:00');
|
|
||||||
expect(dateTimeInputs[1].innerHTML.replace(/[^(\d|:)]/g, '')).toEqual('00:00:30');
|
|
||||||
expect(realtimeModeEl.querySelector('.c-mode-button .c-button__label').innerHTML).toEqual('Local Clock');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="pr-tc-input-menu"
|
class="pr-tc-input-menu"
|
||||||
:class="{'pr-tc-input-menu--bottom' : bottom === true}"
|
|
||||||
@keydown.enter.prevent
|
@keydown.enter.prevent
|
||||||
@keyup.enter.prevent="submit"
|
@keyup.enter.prevent="submit"
|
||||||
@keydown.esc.prevent
|
@keydown.esc.prevent
|
||||||
@@ -89,12 +88,6 @@ export default {
|
|||||||
offset: {
|
offset: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
|
||||||
bottom: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -86,16 +86,15 @@ export default {
|
|||||||
return {
|
return {
|
||||||
items: [],
|
items: [],
|
||||||
timeSystems: [],
|
timeSystems: [],
|
||||||
height: 0,
|
height: 0
|
||||||
useIndependentTime: this.domainObject.configuration.useIndependentTime === true,
|
|
||||||
timeOptions: this.domainObject.configuration.timeOptions
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.composition.off('add', this.addItem);
|
this.composition.off('add', this.addItem);
|
||||||
this.composition.off('remove', this.removeItem);
|
this.composition.off('remove', this.removeItem);
|
||||||
this.composition.off('reorder', this.reorder);
|
this.composition.off('reorder', this.reorder);
|
||||||
this.stopFollowingTimeContext();
|
this.openmct.time.off("bounds", this.updateViewBounds);
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.composition) {
|
if (this.composition) {
|
||||||
@@ -105,8 +104,8 @@ export default {
|
|||||||
this.composition.load();
|
this.composition.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setTimeContext();
|
|
||||||
this.getTimeSystems();
|
this.getTimeSystems();
|
||||||
|
this.openmct.time.on("bounds", this.updateViewBounds);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addItem(domainObject) {
|
addItem(domainObject) {
|
||||||
@@ -133,8 +132,8 @@ export default {
|
|||||||
},
|
},
|
||||||
removeItem(identifier) {
|
removeItem(identifier) {
|
||||||
let index = this.items.findIndex(item => this.openmct.objects.areIdsEqual(identifier, item.domainObject.identifier));
|
let index = this.items.findIndex(item => this.openmct.objects.areIdsEqual(identifier, item.domainObject.identifier));
|
||||||
|
this.removeSelectable(this.items[index]);
|
||||||
this.items.splice(index, 1);
|
this.items.splice(index, 1);
|
||||||
this.updateContentHeight();
|
|
||||||
},
|
},
|
||||||
reorder(reorderPlan) {
|
reorder(reorderPlan) {
|
||||||
let oldItems = this.items.slice();
|
let oldItems = this.items.slice();
|
||||||
@@ -155,7 +154,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
getBoundsForTimeSystem(timeSystem) {
|
getBoundsForTimeSystem(timeSystem) {
|
||||||
const currentBounds = this.timeContext.bounds();
|
const currentBounds = this.openmct.time.bounds();
|
||||||
|
|
||||||
//TODO: Some kind of translation via an offset? of current bounds to target timeSystem
|
//TODO: Some kind of translation via an offset? of current bounds to target timeSystem
|
||||||
return currentBounds;
|
return currentBounds;
|
||||||
@@ -165,20 +164,6 @@ export default {
|
|||||||
if (currentTimeSystem) {
|
if (currentTimeSystem) {
|
||||||
currentTimeSystem.bounds = bounds;
|
currentTimeSystem.bounds = bounds;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
setTimeContext() {
|
|
||||||
this.stopFollowingTimeContext();
|
|
||||||
|
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
|
||||||
this.timeContext.on('timeContext', this.setTimeContext);
|
|
||||||
this.updateViewBounds(this.timeContext.bounds());
|
|
||||||
this.timeContext.on('bounds', this.updateViewBounds);
|
|
||||||
},
|
|
||||||
stopFollowingTimeContext() {
|
|
||||||
if (this.timeContext) {
|
|
||||||
this.timeContext.off('bounds', this.updateViewBounds);
|
|
||||||
this.timeContext.off('timeContext', this.setTimeContext);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,8 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import TimelineViewProvider from './TimelineViewProvider';
|
import TimelineViewProvider from '../timeline/TimelineViewProvider';
|
||||||
import timelineInterceptor from "./timelineInterceptor";
|
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
@@ -33,12 +32,8 @@ export default function () {
|
|||||||
cssClass: 'icon-timeline',
|
cssClass: 'icon-timeline',
|
||||||
initialize: function (domainObject) {
|
initialize: function (domainObject) {
|
||||||
domainObject.composition = [];
|
domainObject.composition = [];
|
||||||
domainObject.configuration = {
|
|
||||||
useIndependentTime: false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
timelineInterceptor(openmct);
|
|
||||||
openmct.objectViews.addProvider(new TimelineViewProvider(openmct));
|
openmct.objectViews.addProvider(new TimelineViewProvider(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,15 +51,7 @@ describe('the plugin', function () {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const timeSystem = {
|
openmct = createOpenMct();
|
||||||
timeSystemKey: 'utc',
|
|
||||||
bounds: {
|
|
||||||
start: 1597160002854,
|
|
||||||
end: 1597181232854
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct = createOpenMct(timeSystem);
|
|
||||||
openmct.install(new TimelinePlugin());
|
openmct.install(new TimelinePlugin());
|
||||||
|
|
||||||
objectDef = openmct.types.get('time-strip').definition;
|
objectDef = openmct.types.get('time-strip').definition;
|
||||||
@@ -72,6 +64,11 @@ describe('the plugin', function () {
|
|||||||
child.style.height = '480px';
|
child.style.height = '480px';
|
||||||
element.appendChild(child);
|
element.appendChild(child);
|
||||||
|
|
||||||
|
openmct.time.timeSystem('utc', {
|
||||||
|
start: 1597160002854,
|
||||||
|
end: 1597181232854
|
||||||
|
});
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
});
|
});
|
||||||
@@ -91,6 +88,7 @@ describe('the plugin', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('the time-strip object', () => {
|
describe('the time-strip object', () => {
|
||||||
|
|
||||||
it('is creatable', () => {
|
it('is creatable', () => {
|
||||||
expect(objectDef.creatable).toEqual(mockObject.creatable);
|
expect(objectDef.creatable).toEqual(mockObject.creatable);
|
||||||
});
|
});
|
||||||
@@ -98,15 +96,10 @@ describe('the plugin', function () {
|
|||||||
|
|
||||||
describe('the view', () => {
|
describe('the view', () => {
|
||||||
let timelineView;
|
let timelineView;
|
||||||
let testViewObject;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
testViewObject = {
|
const testViewObject = {
|
||||||
id: "test-object",
|
id: "test-object",
|
||||||
identifier: {
|
|
||||||
key: "test-object",
|
|
||||||
namespace: ''
|
|
||||||
},
|
|
||||||
type: "time-strip"
|
type: "time-strip"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,106 +119,6 @@ describe('the plugin', function () {
|
|||||||
const el = element.querySelector('.c-timesystem-axis');
|
const el = element.querySelector('.c-timesystem-axis');
|
||||||
expect(el).toBeDefined();
|
expect(el).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not show the independent time conductor based on configuration', () => {
|
|
||||||
const independentTimeConductorEl = element.querySelector('.c-timeline-holder > .c-conductor__controls');
|
|
||||||
expect(independentTimeConductorEl).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('the independent time conductor', () => {
|
|
||||||
let timelineView;
|
|
||||||
let testViewObject = {
|
|
||||||
id: "test-object",
|
|
||||||
identifier: {
|
|
||||||
key: "test-object",
|
|
||||||
namespace: ''
|
|
||||||
},
|
|
||||||
type: "time-strip",
|
|
||||||
configuration: {
|
|
||||||
useIndependentTime: true,
|
|
||||||
timeOptions: {
|
|
||||||
mode: {
|
|
||||||
key: 'local'
|
|
||||||
},
|
|
||||||
fixedOffsets: {
|
|
||||||
start: 10,
|
|
||||||
end: 11
|
|
||||||
},
|
|
||||||
clockOffsets: {
|
|
||||||
start: -(30 * 60 * 1000),
|
|
||||||
end: (30 * 60 * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(done => {
|
|
||||||
const applicableViews = openmct.objectViews.get(testViewObject, mockObjectPath);
|
|
||||||
timelineView = applicableViews.find((viewProvider) => viewProvider.key === 'time-strip.view');
|
|
||||||
let view = timelineView.view(testViewObject, element);
|
|
||||||
view.show(child, true);
|
|
||||||
|
|
||||||
Vue.nextTick(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays an independent time conductor with saved options - local clock', () => {
|
|
||||||
|
|
||||||
return Vue.nextTick(() => {
|
|
||||||
const independentTimeConductorEl = element.querySelector('.c-timeline-holder > .c-conductor__controls');
|
|
||||||
expect(independentTimeConductorEl).toBeDefined();
|
|
||||||
|
|
||||||
const independentTimeContext = openmct.time.getIndependentContext(testViewObject.identifier.key);
|
|
||||||
expect(independentTimeContext.clockOffsets()).toEqual(testViewObject.configuration.timeOptions.clockOffsets);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('the independent time conductor', () => {
|
|
||||||
let timelineView;
|
|
||||||
let testViewObject2 = {
|
|
||||||
id: "test-object2",
|
|
||||||
identifier: {
|
|
||||||
key: "test-object2",
|
|
||||||
namespace: ''
|
|
||||||
},
|
|
||||||
type: "time-strip",
|
|
||||||
configuration: {
|
|
||||||
useIndependentTime: true,
|
|
||||||
timeOptions: {
|
|
||||||
mode: {
|
|
||||||
key: 'fixed'
|
|
||||||
},
|
|
||||||
fixedOffsets: {
|
|
||||||
start: 10,
|
|
||||||
end: 11
|
|
||||||
},
|
|
||||||
clockOffsets: {
|
|
||||||
start: -(30 * 60 * 1000),
|
|
||||||
end: (30 * 60 * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach((done) => {
|
|
||||||
const applicableViews = openmct.objectViews.get(testViewObject2, mockObjectPath);
|
|
||||||
timelineView = applicableViews.find((viewProvider) => viewProvider.key === 'time-strip.view');
|
|
||||||
let view = timelineView.view(testViewObject2, element);
|
|
||||||
view.show(child, true);
|
|
||||||
|
|
||||||
Vue.nextTick(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays an independent time conductor with saved options - fixed timespan', () => {
|
|
||||||
return Vue.nextTick(() => {
|
|
||||||
const independentTimeConductorEl = element.querySelector('.c-timeline-holder > .c-conductor__controls');
|
|
||||||
expect(independentTimeConductorEl).toBeDefined();
|
|
||||||
|
|
||||||
const independentTimeContext = openmct.time.getIndependentContext(testViewObject2.identifier.key);
|
|
||||||
expect(independentTimeContext.bounds()).toEqual(testViewObject2.configuration.timeOptions.fixedOffsets);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
.c-timeline-holder {
|
.c-timeline-holder {
|
||||||
@include abs();
|
@include abs();
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
> * + * {
|
|
||||||
margin-top: $interiorMargin;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -453,8 +453,7 @@ select {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-so-view--hyperlink.c-so-view--no-frame {
|
.c-so-view--no-frame > .c-so-view__object-view > .c-hyperlink--button {
|
||||||
.c-hyperlink--button {
|
|
||||||
@include abs();
|
@include abs();
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -462,9 +461,6 @@ select {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-so-view__frame-controls { display: none; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************** MENUS */
|
/******************************************************** MENUS */
|
||||||
@mixin menuOuter() {
|
@mixin menuOuter() {
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
@@ -1010,9 +1006,6 @@ input[type="range"] {
|
|||||||
transition: $transIn;
|
transition: $transIn;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: inherit;
|
pointer-events: inherit;
|
||||||
|
|
||||||
&[disabled] { opacity: $controlDisabledOpacity; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ mct-plot {
|
|||||||
|
|
||||||
.c-plot,
|
.c-plot,
|
||||||
.gl-plot {
|
.gl-plot {
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 100px;
|
||||||
|
|
||||||
.s-status-taking-snapshot & {
|
.s-status-taking-snapshot & {
|
||||||
.c-control-bar {
|
.c-control-bar {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -64,17 +67,16 @@ mct-plot {
|
|||||||
|
|
||||||
.c-plot {
|
.c-plot {
|
||||||
@include abs($mainViewPad);
|
@include abs($mainViewPad);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
|
||||||
min-height: $plotMinH;
|
|
||||||
|
|
||||||
.c-control-bar {
|
.c-control-bar {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin-bottom: $interiorMargin;
|
margin-bottom: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-view-section {
|
.l-view-section, .c-plot--stacked-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -82,18 +84,7 @@ mct-plot {
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-plot--stacked-container {
|
|
||||||
display: flex;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: $plotMinH;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
&--stacked {
|
&--stacked {
|
||||||
min-height: auto !important;
|
|
||||||
|
|
||||||
.child-frame {
|
.child-frame {
|
||||||
.has-control-bar {
|
.has-control-bar {
|
||||||
.c-control-bar {
|
.c-control-bar {
|
||||||
@@ -133,7 +124,7 @@ mct-plot {
|
|||||||
.plot-wrapper-axis-and-display-area {
|
.plot-wrapper-axis-and-display-area {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
//min-height: $plotMinH;
|
min-height: $plotMinH;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gl-plot-wrapper-display-area-and-x-axis {
|
.gl-plot-wrapper-display-area-and-x-axis {
|
||||||
@@ -474,7 +465,6 @@ mct-plot {
|
|||||||
.gl-plot-legend,
|
.gl-plot-legend,
|
||||||
.c-plot-legend {
|
.c-plot-legend {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 0 0 auto; // Prevents clipping for all legend placements (top, bottom, etc.)
|
|
||||||
|
|
||||||
&__wrapper {
|
&__wrapper {
|
||||||
// Holds view-control and both collapsed and expanded legends
|
// Holds view-control and both collapsed and expanded legends
|
||||||
@@ -739,12 +729,6 @@ mct-plot {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************************* BAR CHARTS */
|
|
||||||
.c-bar-chart {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***************** CURSOR GUIDES */
|
/***************** CURSOR GUIDES */
|
||||||
[class*='c-cursor-guide'] {
|
[class*='c-cursor-guide'] {
|
||||||
box-shadow: $shdwCursorGuide;
|
box-shadow: $shdwCursorGuide;
|
||||||
|
|||||||
@@ -582,12 +582,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[class*='--menus-bottom'] {
|
|
||||||
.c-menu {
|
|
||||||
top: auto; bottom: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='--menus-left'],
|
&[class*='--menus-left'],
|
||||||
&[class*='menus-to-left'] {
|
&[class*='menus-to-left'] {
|
||||||
.c-menu {
|
.c-menu {
|
||||||
@@ -599,6 +593,8 @@
|
|||||||
@mixin cArrowButtonBase($colorBg: transparent, $colorFg: $colorBtnFg, $filterHov: $filterHov) {
|
@mixin cArrowButtonBase($colorBg: transparent, $colorFg: $colorBtnFg, $filterHov: $filterHov) {
|
||||||
// Copied from branch new-tree-refactor
|
// Copied from branch new-tree-refactor
|
||||||
|
|
||||||
|
flex: 0 0 auto;
|
||||||
|
position: relative;
|
||||||
background: $colorBg;
|
background: $colorBg;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
|
|||||||
@@ -40,11 +40,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.zerolinelayer {
|
|
||||||
// Hide unneeded plotly-styled horizontal line
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
path.xy2-y {
|
path.xy2-y {
|
||||||
stroke: $colorPlotHash !important; // Using this instead of $colorPlotAreaBorder because that is an rgba
|
stroke: $colorPlotHash !important; // Using this instead of $colorPlotAreaBorder because that is an rgba
|
||||||
opacity: $opacityPlotHash !important;
|
opacity: $opacityPlotHash !important;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user