Compare commits

..

24 Commits

Author SHA1 Message Date
Andrew Henry
15926cc9a2 Merge branch 'master' into yield-request-callback 2021-09-17 17:45:00 -07:00
Jamie V
d647e287fd Merge branch 'master' into yield-request-callback 2021-09-17 15:56:56 -07:00
Jamie V
7998ee3f61 Merge branch 'master' into yield-request-callback 2021-09-17 09:54:37 -07:00
Jamie V
e7d94d9447 Merge branch 'master' into yield-request-callback 2021-09-16 11:18:08 -07:00
Jamie Vigliotta
336babdb15 moving requestAbort undefined assignment after try/catch block, since it happens eitherway 2021-09-16 11:17:19 -07:00
Jamie Vigliotta
0b4624fc8f adding onPartialResponse back in, done testing without 2021-09-16 11:11:46 -07:00
Jamie Vigliotta
62c54e732d suppressing abort error 2021-09-16 11:11:08 -07:00
Jamie Vigliotta
e63393b0c4 looking to suppress errors related to aborting and testing reguarl requests (not yield) are still working with new updates to requests 2021-09-16 10:20:18 -07:00
Jamie Vigliotta
02c97ae10b Merge branch 'yield-request-callback' of https://github.com/nasa/openmct into yield-request-callback
Merge'n master
2021-09-15 10:43:07 -07:00
Jamie Vigliotta
5b499368ae updating key for processing method 2021-09-15 10:42:56 -07:00
Andrew Henry
dd556c3335 Merge branch 'master' into yield-request-callback 2021-09-14 17:34:20 -07:00
Jamie V
1a71708b63 Merge branch 'master' into yield-request-callback 2021-09-14 09:13:53 -07:00
Jamie V
ab425ed04e Merge branch 'master' into yield-request-callback 2021-09-09 12:30:36 -07:00
Jamie Vigliotta
514181c2fd debuggin 2021-09-08 14:15:55 -07:00
Jamie Vigliotta
757ff1c85a debuggin 2021-09-08 13:48:25 -07:00
Jamie Vigliotta
e7f21654e0 was doin generators all wrong 2021-09-08 13:04:13 -07:00
Jamie Vigliotta
c895869cbf wrapping generator function 2021-09-08 12:40:53 -07:00
Jamie Vigliotta
5d40a55a20 debugging processGenerator 2021-09-08 12:19:17 -07:00
Jamie Vigliotta
54477f2bb9 debugging processGenerator 2021-09-08 12:09:03 -07:00
Jamie Vigliotta
eb894894b5 debugging processGenerator 2021-09-08 11:59:07 -07:00
Jamie Vigliotta
3f06bc5c48 telemetry check 2021-09-08 11:29:30 -07:00
Jamie Vigliotta
c3238e50ee fixing random module auto-complete inserted 2021-09-08 11:11:29 -07:00
Jamie Vigliotta
d83e919541 fixing random module auto-complete inserted 2021-09-07 15:34:55 -07:00
Jamie Vigliotta
5fcf0bd7de added processor generator to the request options 2021-09-07 11:19:12 -07:00
108 changed files with 1461 additions and 5126 deletions

View File

@@ -42,7 +42,6 @@ jobs:
- ~/.npm
- ~/.cache
- node_modules
- run: npm run lint
- run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
- store_test_results:
path: dist/reports/tests/

View File

@@ -2,7 +2,6 @@
* [ ] 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?
* [ ] 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

View File

@@ -317,7 +317,6 @@ checklist).
### Reviewer Checklist
* [ ] Changes appear to address issue?
* [ ] Changes appear not to be breaking changes?
* [ ] Appropriate unit tests included?
* [ ] Code style and in-line documentation are appropriate?
* [ ] Commit messages meet standards?

View File

@@ -28,15 +28,6 @@ define([
domain: 2
}
},
{
key: "cos",
name: "Cosine",
unit: "deg",
formatString: '%0.2f',
hints: {
domain: 3
}
},
// Need to enable "LocalTimeSystem" plugin to make use of this
// {
// key: "local",
@@ -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
}
}
]
}
};

View File

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

View File

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

View File

@@ -54,38 +54,23 @@
var start = Date.now();
var step = 1000 / data.dataRateInHz;
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;
};
} else {
work = function (now) {
while (nextStep < now) {
self.postMessage({
id: message.id,
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
wavelength: wavelength(start, nextStep),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
}
});
nextStep += step;
}
function work(now) {
while (nextStep < now) {
self.postMessage({
id: message.id,
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
}
});
nextStep += step;
}
return nextStep;
};
return nextStep;
}
subscriptions[message.id] = work;
@@ -126,21 +111,13 @@
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
wavelength: wavelength(start, nextStep),
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
});
}
self.postMessage({
id: message.id,
data: request.spectra ? {
wavelength: data.map((item) => {
return item.wavelength;
}),
cos: data.map((item) => {
return item.cos;
})
} : data
data: data
});
}
@@ -154,10 +131,6 @@
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
}
function wavelength(start, nextStep) {
return (nextStep - start) / 10;
}
function sendError(error, message) {
self.postMessage({
error: error.name + ': ' + error.message,

View File

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

View File

@@ -25,7 +25,7 @@
const devMode = process.env.NODE_ENV !== 'production';
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
const coverageEnabled = process.env.COVERAGE === 'true';
const reporters = ['spec', 'html', 'junit'];
const reporters = ['progress', 'html', 'junit'];
if (coverageEnabled) {
reporters.push('coverage-istanbul');
@@ -60,7 +60,7 @@ module.exports = (config) => {
client: {
jasmine: {
random: false,
timeoutInterval: 5000
timeoutInterval: 30000
}
},
customLaunchers: {
@@ -88,6 +88,11 @@ module.exports = (config) => {
outputFile: "test-results.xml",
useBrowserName: false
},
browserConsoleLogOptions: {
level: "error",
format: "%b %T: %m",
terminal: true
},
coverageIstanbulReporter: {
fixWebpackSourcePaths: true,
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: {
'indexTest.js': ['webpack', 'sourcemap']
},

View File

@@ -2,6 +2,7 @@
"name": "openmct",
"version": "1.7.8-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {},
"devDependencies": {
"angular": ">=1.8.0",
"angular-route": "1.4.14",
@@ -11,9 +12,16 @@
"copy-webpack-plugin": "^4.5.2",
"cross-env": "^6.0.3",
"css-loader": "^1.0.0",
"d3-array": "1.2.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-selection": "1.3.x",
"d3-time": "1.0.x",
"d3-time-format": "2.1.x",
"eslint": "7.0.0",
"eslint-plugin-vue": "^7.5.0",
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
@@ -33,15 +41,14 @@
"jsdoc": "^3.3.2",
"karma": "6.3.4",
"karma-chrome-launcher": "3.1.0",
"karma-firefox-launcher": "2.1.1",
"karma-cli": "2.0.0",
"karma-coverage": "2.0.3",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-firefox-launcher": "2.1.1",
"karma-junit-reporter": "2.0.1",
"karma-html-reporter": "0.2.7",
"karma-jasmine": "4.0.1",
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "4.0.2",
"location-bar": "^3.0.1",
"lodash": "^4.17.12",
@@ -55,8 +62,6 @@
"node-bourbon": "^4.2.3",
"node-sass": "^4.14.1",
"painterro": "^1.2.56",
"plotly.js-basic-dist": "^2.5.0",
"plotly.js-gl2d-dist": "^2.5.0",
"printj": "^1.2.1",
"raw-loader": "^0.5.1",
"request": "^2.69.0",
@@ -65,7 +70,6 @@
"uuid": "^3.3.3",
"v8-compile-cache": "^1.1.0",
"vue": "2.5.6",
"vue-eslint-parser": "7.11.0",
"vue-loader": "^15.2.6",
"vue-template-compiler": "2.5.6",
"webpack": "^4.16.2",

View File

@@ -50,6 +50,8 @@ define(
* or finish() are called.
*/
EditorCapability.prototype.edit = function () {
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
if (!this.openmct.editor.isEditing()) {
this.openmct.editor.edit();
this.domainObject.getCapability('status').set('editing', true);
@@ -80,6 +82,8 @@ define(
* @returns {*}
*/
EditorCapability.prototype.save = function () {
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
return Promise.resolve();
};
@@ -91,6 +95,8 @@ define(
* @returns {*}
*/
EditorCapability.prototype.finish = function () {
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
return Promise.resolve();
};

View File

@@ -25,14 +25,15 @@ define([
], function (
moment
) {
const DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS";
const DATE_FORMATS = [
DATE_FORMAT,
DATE_FORMAT + "Z",
"YYYY-MM-DD HH:mm:ss",
"YYYY-MM-DD HH:mm",
"YYYY-MM-DD"
];
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
DATE_FORMATS = [
DATE_FORMAT,
DATE_FORMAT + "Z",
"YYYY-MM-DD HH:mm:ss",
"YYYY-MM-DD HH:mm",
"YYYY-MM-DD"
];
/**
* @typedef Scale
@@ -52,27 +53,15 @@ define([
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 {string} formatString The string format to format. Default "YYYY-MM-DD HH:mm:ss.SSS" + "Z"
* @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
* @returns {string} the formatted date(s). 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
* in the array.
*/
UTCTimeFormat.prototype.format = function (value, formatString) {
UTCTimeFormat.prototype.format = function (value) {
if (value !== undefined) {
const format = validateFormatString(formatString);
return moment.utc(value).format(format) + (formatString ? '' : 'Z');
return moment.utc(value).format(DATE_FORMAT) + "Z";
} else {
return value;
}

View File

@@ -136,7 +136,7 @@ define([
* @memberof module:openmct.MCT#
* @name conductor
*/
this.time = new api.TimeAPI(this);
this.time = new api.TimeAPI();
/**
* An interface for interacting with the composition of domain objects.

View File

@@ -28,6 +28,8 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
return true;
}
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
return false;
}

View File

@@ -29,6 +29,7 @@ define([
'./capabilities/APICapabilityDecorator',
'./policies/AdaptedViewPolicy',
'./runs/AlternateCompositionInitializer',
'./runs/TypeDeprecationChecker',
'./runs/LegacyTelemetryProvider',
'./runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor',
@@ -45,6 +46,7 @@ define([
APICapabilityDecorator,
AdaptedViewPolicy,
AlternateCompositionInitializer,
TypeDeprecationChecker,
LegacyTelemetryProvider,
RegisterLegacyTypes,
LegacyObjectAPIInterceptor,
@@ -133,6 +135,10 @@ define([
}
],
runs: [
{
implementation: TypeDeprecationChecker,
depends: ["types[]"]
},
{
implementation: AlternateCompositionInitializer,
depends: ["openmct"]

View File

@@ -4,6 +4,12 @@ define([
) {
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);
}

View File

@@ -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
* 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.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
@@ -14,27 +14,33 @@
* License for the specific language governing permissions and limitations
* 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
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function timelineInterceptor(openmct) {
define([
openmct.objects.addGetInterceptor({
appliesTo: (identifier, domainObject) => {
return domainObject && domainObject.type === 'time-strip';
},
invoke: (identifier, object) => {
], function (
if (object && object.configuration === undefined) {
object.configuration = {
useIndependentTime: true
};
}
) {
return object;
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
);
}
});
}
}
function TypeDeprecationChecker(types) {
types.forEach(checkForDeprecatedFunctionality);
}
return TypeDeprecationChecker;
});

View File

@@ -15,6 +15,8 @@ define([
};
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 {
key: legacyView.key,
name: legacyView.name,

View File

@@ -4,6 +4,7 @@ define([
) {
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[]')
.filter((r) => r.key === typeDefinition.inspector)[0];

View File

@@ -46,7 +46,7 @@ define([
StatusAPI
) {
return {
TimeAPI: TimeAPI.default,
TimeAPI: TimeAPI,
ObjectAPI: ObjectAPI,
CompositionAPI: CompositionAPI,
TypeRegistry: TypeRegistry,

View File

@@ -182,12 +182,6 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
let objectPromise = provider.get(identifier, abortSignal).then(result => {
delete this.cache[keystring];
result = this.applyGetInterceptors(identifier, result);
if (result.isMutable) {
result.$refresh(result);
} else {
let mutableDomainObject = this._toMutable(result);
mutableDomainObject.$refresh(result);
}
return result;
});
@@ -304,15 +298,10 @@ ObjectAPI.prototype.save = function (domainObject) {
savedResolve = resolve;
});
domainObject.persisted = persistedTime;
const newObjectPromise = provider.create(domainObject);
if (newObjectPromise) {
newObjectPromise.then(response => {
this.mutate(domainObject, 'persisted', persistedTime);
savedResolve(response);
});
} else {
result = Promise.reject(`[ObjectAPI][save] Object provider returned ${newObjectPromise} when creating new object.`);
}
provider.create(domainObject).then((response) => {
this.mutate(domainObject, 'persisted', persistedTime);
savedResolve(response);
});
} else {
domainObject.persisted = persistedTime;
this.mutate(domainObject, 'persisted', persistedTime);

View File

@@ -180,6 +180,12 @@ define([
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/
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))
|| Boolean(this.findRequestProvider(domainObject));
};

View File

@@ -31,6 +31,11 @@ define([
valueMetadata.hints = valueMetadata.hints || {};
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')) {
valueMetadata.hints.domain = valueMetadata.hints.x;
}
@@ -39,6 +44,11 @@ define([
}
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')) {
valueMetadata.hints.range = valueMetadata.hints.y;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -20,35 +20,51 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import GlobalTimeContext from "./GlobalTimeContext";
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
define(['EventEmitter'], function (EventEmitter) {
/**
* The public API for setting and querying the temporal state of the
* application. The concept of time is integral to Open MCT, and at least
* one {@link TimeSystem}, as well as some default time bounds must be
* registered and enabled via {@link TimeAPI.addTimeSystem} and
* {@link TimeAPI.timeSystem} respectively for Open MCT to work.
*
* Time-sensitive views will typically respond to changes to bounds or other
* properties of the time conductor and update the data displayed based on
* the temporal state of the application. The current time bounds are also
* used in queries for historical data.
*
* The TimeAPI extends the EventEmitter class. A number of events are
* fired when properties of the time conductor change, which are documented
* below.
*
* @interface
* @memberof module:openmct
*/
function TimeAPI() {
EventEmitter.call(this);
//The Time System
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);
/**
* The public API for setting and querying the temporal state of the
* application. The concept of time is integral to Open MCT, and at least
* one {@link TimeSystem}, as well as some default time bounds must be
* registered and enabled via {@link TimeAPI.addTimeSystem} and
* {@link TimeAPI.timeSystem} respectively for Open MCT to work.
*
* Time-sensitive views will typically respond to changes to bounds or other
* properties of the time conductor and update the data displayed based on
* the temporal state of the application. The current time bounds are also
* used in queries for historical data.
*
* The TimeAPI extends the GlobalTimeContext which in turn extends the TimeContext/EventEmitter class. A number of events are
* fired when properties of the time conductor change, which are documented
* below.
*
* @interface
* @memberof module:openmct
*/
class TimeAPI extends GlobalTimeContext {
constructor(openmct) {
super();
this.openmct = openmct;
this.independentContexts = new Map();
}
TimeAPI.prototype = Object.create(EventEmitter.prototype);
/**
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
* MCT supports multiple different types of time values, although all are
@@ -78,16 +94,16 @@ class TimeAPI extends GlobalTimeContext {
* @memberof module:openmct.TimeAPI#
* @param {TimeSystem} timeSystem A time system object.
*/
addTimeSystem(timeSystem) {
TimeAPI.prototype.addTimeSystem = function (timeSystem) {
this.timeSystems.set(timeSystem.key, timeSystem);
}
};
/**
* @returns {TimeSystem[]}
*/
getAllTimeSystems() {
TimeAPI.prototype.getAllTimeSystems = function () {
return Array.from(this.timeSystems.values());
}
};
/**
* Clocks provide a timing source that is used to
@@ -110,81 +126,340 @@ class TimeAPI extends GlobalTimeContext {
* @memberof module:openmct.TimeAPI#
* @param {Clock} clock
*/
addClock(clock) {
TimeAPI.prototype.addClock = function (clock) {
this.clocks.set(clock.key, clock);
}
};
/**
* @memberof module:openmct.TimeAPI#
* @returns {Clock[]}
* @memberof module:openmct.TimeAPI#
*/
getAllClocks() {
TimeAPI.prototype.getAllClocks = function () {
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
* 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 {string | true} A validation error, or true if valid
* @memberof module:openmct.TimeAPI#
* @method addIndependentTimeContext
* @method validateBounds
*/
addIndependentContext(key, value, clockKey) {
let timeContext = this.independentContexts.get(key);
if (!timeContext) {
timeContext = new IndependentTimeContext(this, key);
this.independentContexts.set(key, timeContext);
TimeAPI.prototype.validateBounds = function (bounds) {
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";
}
if (clockKey) {
timeContext.clock(clockKey, value);
} else {
timeContext.stopClock();
timeContext.bounds(value);
return true;
};
/**
* 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 {string | true} A validation error, or true if valid
* @memberof module:openmct.TimeAPI#
* @method validateBounds
*/
TimeAPI.prototype.validateOffsets = function (offsets) {
if ((offsets.start === undefined)
|| (offsets.end === undefined)
|| isNaN(offsets.start)
|| isNaN(offsets.end)
) {
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";
}
this.emit('timeContext', key);
return () => {
this.independentContexts.delete(key);
timeContext.emit('timeContext', key);
};
}
return true;
};
/**
* Get the independent time context which follows the TimeAPI timeSystem,
* but with different offsets.
* @param {key | string} key The identifier key of the domain object these offsets
* @memberof module:openmct.TimeAPI#
* @method getIndependentTimeContext
* @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~
*/
getIndependentContext(key) {
return this.independentContexts.get(key);
}
/**
* 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.
* Otherwise, the global time context will be returned.
* @param { Array } objectPath The view's objectPath
* 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 getContextForView
* @method bounds
*/
getContextForView(objectPath) {
let timeContext = this;
objectPath.forEach(item => {
const key = this.openmct.objects.makeKeyString(item.identifier);
if (this.independentContexts.get(key)) {
timeContext = this.independentContexts.get(key);
TimeAPI.prototype.bounds = function (newBounds) {
if (arguments.length > 0) {
const validationResult = this.validateBounds(newBounds);
if (validationResult !== true) {
throw new Error(validationResult);
}
});
return timeContext;
}
//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);
}
}
export default TimeAPI;
//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;
});

View File

@@ -19,243 +19,241 @@
* 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 Time API", function () {
let api;
let timeSystemKey;
let timeSystem;
let clockKey;
let clock;
let bounds;
let eventListener;
let toi;
let openmct;
beforeEach(function () {
openmct = createOpenMct();
api = new TimeAPI(openmct);
timeSystemKey = "timeSystemKey";
timeSystem = {key: timeSystemKey};
clockKey = "someClockKey";
clock = jasmine.createSpyObj("clock", [
"on",
"off",
"currentValue"
]);
clock.currentValue.and.returnValue(100);
clock.key = clockKey;
bounds = {
start: 0,
end: 1
};
eventListener = jasmine.createSpy("eventListener");
toi = 111;
});
it("Supports setting and querying of time of interest", function () {
expect(api.timeOfInterest()).not.toBe(toi);
api.timeOfInterest(toi);
expect(api.timeOfInterest()).toBe(toi);
});
it("Allows setting of valid bounds", function () {
bounds = {
start: 0,
end: 1
};
expect(api.bounds()).not.toBe(bounds);
expect(api.bounds.bind(api, bounds)).not.toThrow();
expect(api.bounds()).toEqual(bounds);
});
it("Disallows setting of invalid bounds", function () {
bounds = {
start: 1,
end: 0
};
expect(api.bounds()).not.toEqual(bounds);
expect(api.bounds.bind(api, bounds)).toThrow();
expect(api.bounds()).not.toEqual(bounds);
bounds = {start: 1};
expect(api.bounds()).not.toEqual(bounds);
expect(api.bounds.bind(api, bounds)).toThrow();
expect(api.bounds()).not.toEqual(bounds);
});
it("Allows setting of previously registered time system with bounds", function () {
api.addTimeSystem(timeSystem);
expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () {
api.timeSystem(timeSystem, bounds);
}).not.toThrow();
expect(api.timeSystem()).toBe(timeSystem);
});
it("Disallows setting of time system without bounds", function () {
api.addTimeSystem(timeSystem);
expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () {
api.timeSystem(timeSystemKey);
}).toThrow();
expect(api.timeSystem()).not.toBe(timeSystem);
});
it("allows setting of timesystem without bounds with clock", function () {
api.addTimeSystem(timeSystem);
api.addClock(clock);
api.clock(clockKey, {
start: 0,
end: 1
});
expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () {
api.timeSystem(timeSystemKey);
}).not.toThrow();
expect(api.timeSystem()).toBe(timeSystem);
});
it("Emits an event when time system changes", function () {
api.addTimeSystem(timeSystem);
expect(eventListener).not.toHaveBeenCalled();
api.on("timeSystem", eventListener);
api.timeSystem(timeSystemKey, bounds);
expect(eventListener).toHaveBeenCalledWith(timeSystem);
});
it("Emits an event when time of interest changes", function () {
expect(eventListener).not.toHaveBeenCalled();
api.on("timeOfInterest", eventListener);
api.timeOfInterest(toi);
expect(eventListener).toHaveBeenCalledWith(toi);
});
it("Emits an event when bounds change", function () {
expect(eventListener).not.toHaveBeenCalled();
api.on("bounds", eventListener);
api.bounds(bounds);
expect(eventListener).toHaveBeenCalledWith(bounds, false);
});
it("If bounds are set and TOI lies inside them, do not change TOI", function () {
api.timeOfInterest(6);
api.bounds({
start: 1,
end: 10
});
expect(api.timeOfInterest()).toEqual(6);
});
it("If bounds are set and TOI lies outside them, reset TOI", function () {
api.timeOfInterest(11);
api.bounds({
start: 1,
end: 10
});
expect(api.timeOfInterest()).toBeUndefined();
});
it("Maintains delta during tick", function () {
});
it("Allows registered time system to be activated", function () {
});
it("Allows a registered tick source to be activated", function () {
const mockTickSource = jasmine.createSpyObj("mockTickSource", [
"on",
"off",
"currentValue"
]);
mockTickSource.key = 'mockTickSource';
});
describe(" when enabling a tick source", function () {
let mockTickSource;
let anotherMockTickSource;
const mockOffsets = {
start: 0,
end: 1
};
define(['./TimeAPI'], function (TimeAPI) {
describe("The Time API", function () {
let api;
let timeSystemKey;
let timeSystem;
let clockKey;
let clock;
let bounds;
let eventListener;
let toi;
beforeEach(function () {
mockTickSource = jasmine.createSpyObj("clock", [
api = new TimeAPI();
timeSystemKey = "timeSystemKey";
timeSystem = {key: timeSystemKey};
clockKey = "someClockKey";
clock = jasmine.createSpyObj("clock", [
"on",
"off",
"currentValue"
]);
mockTickSource.currentValue.and.returnValue(10);
clock.currentValue.and.returnValue(100);
clock.key = clockKey;
bounds = {
start: 0,
end: 1
};
eventListener = jasmine.createSpy("eventListener");
toi = 111;
});
it("Supports setting and querying of time of interest", function () {
expect(api.timeOfInterest()).not.toBe(toi);
api.timeOfInterest(toi);
expect(api.timeOfInterest()).toBe(toi);
});
it("Allows setting of valid bounds", function () {
bounds = {
start: 0,
end: 1
};
expect(api.bounds()).not.toBe(bounds);
expect(api.bounds.bind(api, bounds)).not.toThrow();
expect(api.bounds()).toEqual(bounds);
});
it("Disallows setting of invalid bounds", function () {
bounds = {
start: 1,
end: 0
};
expect(api.bounds()).not.toEqual(bounds);
expect(api.bounds.bind(api, bounds)).toThrow();
expect(api.bounds()).not.toEqual(bounds);
bounds = {start: 1};
expect(api.bounds()).not.toEqual(bounds);
expect(api.bounds.bind(api, bounds)).toThrow();
expect(api.bounds()).not.toEqual(bounds);
});
it("Allows setting of previously registered time system with bounds", function () {
api.addTimeSystem(timeSystem);
expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () {
api.timeSystem(timeSystem, bounds);
}).not.toThrow();
expect(api.timeSystem()).toBe(timeSystem);
});
it("Disallows setting of time system without bounds", function () {
api.addTimeSystem(timeSystem);
expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () {
api.timeSystem(timeSystemKey);
}).toThrow();
expect(api.timeSystem()).not.toBe(timeSystem);
});
it("allows setting of timesystem without bounds with clock", function () {
api.addTimeSystem(timeSystem);
api.addClock(clock);
api.clock(clockKey, {
start: 0,
end: 1
});
expect(api.timeSystem()).not.toBe(timeSystem);
expect(function () {
api.timeSystem(timeSystemKey);
}).not.toThrow();
expect(api.timeSystem()).toBe(timeSystem);
});
it("Emits an event when time system changes", function () {
api.addTimeSystem(timeSystem);
expect(eventListener).not.toHaveBeenCalled();
api.on("timeSystem", eventListener);
api.timeSystem(timeSystemKey, bounds);
expect(eventListener).toHaveBeenCalledWith(timeSystem);
});
it("Emits an event when time of interest changes", function () {
expect(eventListener).not.toHaveBeenCalled();
api.on("timeOfInterest", eventListener);
api.timeOfInterest(toi);
expect(eventListener).toHaveBeenCalledWith(toi);
});
it("Emits an event when bounds change", function () {
expect(eventListener).not.toHaveBeenCalled();
api.on("bounds", eventListener);
api.bounds(bounds);
expect(eventListener).toHaveBeenCalledWith(bounds, false);
});
it("If bounds are set and TOI lies inside them, do not change TOI", function () {
api.timeOfInterest(6);
api.bounds({
start: 1,
end: 10
});
expect(api.timeOfInterest()).toEqual(6);
});
it("If bounds are set and TOI lies outside them, reset TOI", function () {
api.timeOfInterest(11);
api.bounds({
start: 1,
end: 10
});
expect(api.timeOfInterest()).toBeUndefined();
});
it("Maintains delta during tick", function () {
});
it("Allows registered time system to be activated", function () {
});
it("Allows a registered tick source to be activated", function () {
const mockTickSource = jasmine.createSpyObj("mockTickSource", [
"on",
"off",
"currentValue"
]);
mockTickSource.key = 'mockTickSource';
});
describe(" when enabling a tick source", function () {
let mockTickSource;
let anotherMockTickSource;
const mockOffsets = {
start: 0,
end: 1
};
beforeEach(function () {
mockTickSource = jasmine.createSpyObj("clock", [
"on",
"off",
"currentValue"
]);
mockTickSource.currentValue.and.returnValue(10);
mockTickSource.key = "mts";
anotherMockTickSource = jasmine.createSpyObj("clock", [
"on",
"off",
"currentValue"
]);
anotherMockTickSource.key = "amts";
anotherMockTickSource.currentValue.and.returnValue(10);
api.addClock(mockTickSource);
api.addClock(anotherMockTickSource);
});
it("sets bounds based on current value", function () {
api.clock("mts", mockOffsets);
expect(api.bounds()).toEqual({
start: 10,
end: 11
});
});
it("a new tick listener is registered", function () {
api.clock("mts", mockOffsets);
expect(mockTickSource.on).toHaveBeenCalledWith("tick", jasmine.any(Function));
});
it("listener of existing tick source is reregistered", function () {
api.clock("mts", mockOffsets);
api.clock("amts", mockOffsets);
expect(mockTickSource.off).toHaveBeenCalledWith("tick", jasmine.any(Function));
});
it("Allows the active clock to be set and unset", function () {
expect(api.clock()).toBeUndefined();
api.clock("mts", mockOffsets);
expect(api.clock()).toBeDefined();
api.stopClock();
expect(api.clock()).toBeUndefined();
});
});
it("on tick, observes offsets, and indicates tick in bounds callback", function () {
const mockTickSource = jasmine.createSpyObj("clock", [
"on",
"off",
"currentValue"
]);
mockTickSource.currentValue.and.returnValue(100);
let tickCallback;
const boundsCallback = jasmine.createSpy("boundsCallback");
const clockOffsets = {
start: -100,
end: 100
};
mockTickSource.key = "mts";
anotherMockTickSource = jasmine.createSpyObj("clock", [
"on",
"off",
"currentValue"
]);
anotherMockTickSource.key = "amts";
anotherMockTickSource.currentValue.and.returnValue(10);
api.addClock(mockTickSource);
api.addClock(anotherMockTickSource);
api.clock("mts", clockOffsets);
api.on("bounds", boundsCallback);
tickCallback = mockTickSource.on.calls.mostRecent().args[1];
tickCallback(1000);
expect(boundsCallback).toHaveBeenCalledWith({
start: 900,
end: 1100
}, true);
});
it("sets bounds based on current value", function () {
api.clock("mts", mockOffsets);
expect(api.bounds()).toEqual({
start: 10,
end: 11
});
});
it("a new tick listener is registered", function () {
api.clock("mts", mockOffsets);
expect(mockTickSource.on).toHaveBeenCalledWith("tick", jasmine.any(Function));
});
it("listener of existing tick source is reregistered", function () {
api.clock("mts", mockOffsets);
api.clock("amts", mockOffsets);
expect(mockTickSource.off).toHaveBeenCalledWith("tick", jasmine.any(Function));
});
it("Allows the active clock to be set and unset", function () {
expect(api.clock()).toBeUndefined();
api.clock("mts", mockOffsets);
expect(api.clock()).toBeDefined();
api.stopClock();
expect(api.clock()).toBeUndefined();
});
});
it("on tick, observes offsets, and indicates tick in bounds callback", function () {
const mockTickSource = jasmine.createSpyObj("clock", [
"on",
"off",
"currentValue"
]);
mockTickSource.currentValue.and.returnValue(100);
let tickCallback;
const boundsCallback = jasmine.createSpy("boundsCallback");
const clockOffsets = {
start: -100,
end: 100
};
mockTickSource.key = "mts";
api.addClock(mockTickSource);
api.clock("mts", clockOffsets);
api.on("bounds", boundsCallback);
tickCallback = mockTickSource.on.calls.mostRecent().args[1];
tickCallback(1000);
expect(boundsCallback).toHaveBeenCalledWith({
start: 900,
end: 1100
}, true);
});
});

View File

@@ -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;

View File

@@ -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();
});
});
});

View File

@@ -63,6 +63,12 @@ define(['./Type'], function (Type) {
*/
TypeRegistry.prototype.standardizeType = function (typeDef) {
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) {
typeDef.name = typeDef.label;
}

View File

@@ -30,10 +30,10 @@
<div v-if="staticStyle"
class="c-inspect-styles__style"
>
<StyleEditor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="isEditing"
@persist="updateStaticStyle"
<style-editor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="isEditing"
@persist="updateStaticStyle"
/>
</div>
<button
@@ -87,10 +87,10 @@
<condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<StyleEditor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:is-editing="isEditing"
@persist="updateConditionalStyle"
<style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:is-editing="isEditing"
@persist="updateConditionalStyle"
/>
</div>
</div>
@@ -240,10 +240,10 @@ export default {
}
let vm = new Vue({
components: {ConditionSetSelectorDialog},
provide: {
openmct: this.openmct
},
components: {ConditionSetSelectorDialog},
data() {
return {
handleItemSelection

View File

@@ -40,13 +40,13 @@
<div v-if="staticStyle"
class="c-inspect-styles__style"
>
<StyleEditor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="allowEditing"
:mixed-styles="mixedStyles"
:non-specific-font-properties="nonSpecificFontProperties"
@persist="updateStaticStyle"
@save-style="saveStyle"
<style-editor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="allowEditing"
:mixed-styles="mixedStyles"
:non-specific-font-properties="nonSpecificFontProperties"
@persist="updateStaticStyle"
@save-style="saveStyle"
/>
</div>
<button
@@ -108,12 +108,12 @@
<condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<StyleEditor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:non-specific-font-properties="nonSpecificFontProperties"
:is-editing="allowEditing"
@persist="updateConditionalStyle"
@save-style="saveStyle"
<style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:non-specific-font-properties="nonSpecificFontProperties"
:is-editing="allowEditing"
@persist="updateConditionalStyle"
@save-style="saveStyle"
/>
</div>
</div>
@@ -556,10 +556,10 @@ export default {
}
let vm = new Vue({
components: {ConditionSetSelectorDialog},
provide: {
openmct: this.openmct
},
components: {ConditionSetSelectorDialog},
data() {
return {
handleItemSelection

View File

@@ -38,16 +38,12 @@ a.c-condition-widget {
// Make Condition Widget expand when in a hidden frame Layout context
// For both static and Flexible Layouts
.c-so-view--conditionWidget.c-so-view--no-frame {
.c-condition-widget {
@include abs();
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.c-so-view__frame-controls { display: none; }
.c-so-view--no-frame > .c-so-view__object-view > .c-condition-widget {
@include abs();
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
// Add some margin when a Condition Widget is in a Flexible Layout

View File

@@ -47,8 +47,8 @@
.is-editing {
.l-shell__main-container {
[s-selected],
[s-selected-parent] {
&[s-selected],
&[s-selected-parent] {
// Display grid and allow edit marquee to display in main layout holder when editing
> .l-layout {
background: $editUIGridColorBg;

View File

@@ -39,13 +39,10 @@ describe("The Compass component", () => {
sunAngle: 30
};
let propsData = {
containerWidth: 600,
containerHeight: 600,
naturalAspectRatio: 0.9,
image: imageDatum,
sizedImageDimensions: {
width: 100,
height: 100
},
compassRoseSizingClasses: '--rose-small --rose-min'
image: imageDatum
};
app = new Vue({
@@ -54,13 +51,13 @@ describe("The Compass component", () => {
return propsData;
},
template: `<Compass
:compass-rose-sizing-classes="compassRoseSizingClasses"
:image="image"
:container-width="containerWidth"
:container-height="containerHeight"
:natural-aspect-ratio="naturalAspectRatio"
:sized-image-dimensions="sizedImageDimensions"
/>`
:image="image" />`
});
instance = app.$mount();
});
afterAll(() => {

View File

@@ -40,6 +40,7 @@ import PreviewAction from "@/ui/preview/PreviewAction";
import _ from "lodash";
const PADDING = 1;
const RESIZE_POLL_INTERVAL = 200;
const ROW_HEIGHT = 100;
const IMAGE_WIDTH_THRESHOLD = 40;

View File

@@ -84,18 +84,18 @@
/>
</div>
</div>
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev"
title="Previous image"
:disabled="isPrevDisabled"
@click="prevImage()"
></button>
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next"
title="Next image"
:disabled="isNextDisabled"
@click="nextImage()"
></button>
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
<button class="c-nav c-nav--prev"
title="Previous image"
:disabled="isPrevDisabled"
@click="prevImage()"
></button>
<button class="c-nav c-nav--next"
title="Next image"
:disabled="isNextDisabled"
@click="nextImage()"
></button>
</div>
<div class="c-imagery__control-bar">
<div class="c-imagery__time">

View File

@@ -285,17 +285,17 @@
}
}
.c-imagery__prev-next-button {
pointer-events: all;
.c-imagery__prev-next-buttons {
display: flex;
width: 100%;
justify-content: space-between;
pointer-events: none;
position: absolute;
top: 50%;
transform: translateY(-75%); // 75% due to transform: rotation approach to the button
transform: translateY(-75%);
&.c-nav {
position: absolute;
&--prev { left: 0; }
&--next { right: 0; }
.c-nav {
pointer-events: all;
}
.s-status-taking-snapshot & {

View File

@@ -32,19 +32,19 @@ const TEN_MINUTES = ONE_MINUTE * 10;
const MAIN_IMAGE_CLASS = '.js-imageryView-image';
const NEW_IMAGE_CLASS = '.c-imagery__age.c-imagery--new';
const REFRESH_CSS_MS = 500;
// const TOLERANCE = 0.50;
const TOLERANCE = 0.50;
// function comparisonFunction(valueOne, valueTwo) {
// let larger = valueOne;
// let smaller = valueTwo;
//
// if (larger < smaller) {
// larger = valueTwo;
// smaller = valueOne;
// }
//
// return (larger - smaller) < TOLERANCE;
// }
function comparisonFunction(valueOne, valueTwo) {
let larger = valueOne;
let smaller = valueTwo;
if (larger < smaller) {
larger = valueTwo;
smaller = valueOne;
}
return (larger - smaller) < TOLERANCE;
}
function getImageInfo(doc) {
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.objects, 'get').and.returnValue(Promise.resolve(imageryObject));
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
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;
parent.querySelectorAll(`img[src='${target}']`)[0].click();
Vue.nextTick(() => {
const imageIsNew = isNew(parent);
// used in code, need to wait to the 500ms here too
setTimeout(() => {
const imageIsNew = isNew(parent);
expect(imageIsNew).toBeFalse();
done();
expect(imageIsNew).toBeFalse();
done();
}, REFRESH_CSS_MS);
});
});

View File

@@ -45,7 +45,8 @@
</div>
</div>
</template>
<style lang="sass">
</style>
<script>
import packages from './third-party-licenses.json';

View File

@@ -96,9 +96,6 @@ export function setDefaultNotebookPageId(pageId) {
export function validateNotebookStorageObject() {
const notebookStorage = getDefaultNotebook();
if (!notebookStorage) {
return true;
}
let valid = false;
if (notebookStorage) {

View File

@@ -34,7 +34,7 @@ describe('the plugin', () => {
let countFramesPromise;
beforeEach((done) => {
openmct = createOpenMct();
openmct = createOpenMct(false);
element = document.createElement('div');
child = document.createElement('div');

View File

@@ -49,7 +49,7 @@ describe('the plugin', () => {
filter: {},
disableObserve: true
};
openmct = createOpenMct();
openmct = createOpenMct(false);
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
mockIdentifierService = jasmine.createSpyObj(

View File

@@ -67,7 +67,7 @@ export default {
TimelineAxis,
SwimLane
},
inject: ['openmct', 'domainObject', 'path'],
inject: ['openmct', 'domainObject'],
props: {
options: {
type: Object,
@@ -99,37 +99,21 @@ export default {
this.canvasContext = this.canvas.getContext('2d');
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.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
},
beforeDestroy() {
clearInterval(this.resizeTimer);
this.stopFollowingTimeContext();
this.openmct.time.off("timeSystem", this.setScaleAndPlotActivities);
this.openmct.time.off("bounds", this.updateViewBounds);
if (this.unlisten) {
this.unlisten();
}
},
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) {
this.getPlanData(mutatedObject);
this.setScaleAndPlotActivities();
@@ -157,9 +141,13 @@ export default {
getPlanData(domainObject) {
this.planData = getValidatedPlan(domainObject);
},
updateViewBounds(bounds) {
if (bounds) {
this.viewBounds = Object.create(bounds);
updateViewBounds() {
this.viewBounds = this.openmct.time.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) {

View File

@@ -54,8 +54,7 @@ export default function PlanViewProvider(openmct) {
},
provide: {
openmct,
domainObject,
path: objectPath
domainObject
},
data() {
return {

View File

@@ -36,15 +36,7 @@ describe('the plugin', function () {
appHolder.style.width = '640px';
appHolder.style.height = '480px';
const timeSystemOptions = {
timeSystemKey: 'utc',
bounds: {
start: 1597160002854,
end: 1597181232854
}
};
openmct = createOpenMct(timeSystemOptions);
openmct = createOpenMct();
openmct.install(new PlanPlugin());
planDefinition = openmct.types.get('plan').definition;
@@ -56,6 +48,7 @@ describe('the plugin', function () {
child.style.width = '640px';
child.style.height = '480px';
element.appendChild(child);
openmct.on('start', done);
openmct.start(appHolder);
});
@@ -79,6 +72,7 @@ describe('the plugin', function () {
});
describe('the plan view', () => {
it('provides a plan view', () => {
const testViewObject = {
id: "test-object",
@@ -89,6 +83,7 @@ describe('the plugin', function () {
let planView = applicableViews.find((viewProvider) => viewProvider.key === 'plan.view');
expect(planView).toBeDefined();
});
});
describe('the plan view displays activities', () => {
@@ -160,22 +155,12 @@ describe('the plugin', function () {
expect(labelEl.innerHTML).toEqual('TEST-GROUP');
});
it('displays the activities and their labels', (done) => {
const bounds = {
start: 1597160002854,
end: 1597181232854
};
openmct.time.bounds(bounds);
Vue.nextTick(() => {
const rectEls = element.querySelectorAll('.c-plan__contents rect');
expect(rectEls.length).toEqual(2);
const textEls = element.querySelectorAll('.c-plan__contents text');
expect(textEls.length).toEqual(3);
done();
});
it('displays the activities and their labels', () => {
const rectEls = element.querySelectorAll('.c-plan__contents rect');
expect(rectEls.length).toEqual(2);
const textEls = element.querySelectorAll('.c-plan__contents text');
expect(textEls.length).toEqual(3);
});
});
});

View File

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

View File

@@ -156,7 +156,7 @@
import eventHelpers from './lib/eventHelpers';
import LinearScale from "./LinearScale";
import PlotConfigurationModel from './configuration/PlotConfigurationModel';
import configStore from './configuration/ConfigStore';
import configStore from './configuration/configStore';
import PlotLegend from "./legend/PlotLegend.vue";
import MctTicks from "./MctTicks.vue";
@@ -173,7 +173,7 @@ export default {
MctTicks,
MctChart
},
inject: ['openmct', 'domainObject', 'path'],
inject: ['openmct', 'domainObject'],
props: {
options: {
type: Object,
@@ -244,9 +244,6 @@ export default {
},
mounted() {
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.legend = this.config.legend;
@@ -264,7 +261,7 @@ export default {
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.updateStatus);
this.openmct.objectViews.on('clearData', this.clearData);
this.setTimeContext();
this.followTimeConductor();
this.loaded = true;
@@ -277,27 +274,11 @@ export default {
this.destroy();
},
methods: {
setTimeContext() {
this.stopFollowingTimeContext();
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);
followTimeConductor() {
this.openmct.time.on('clock', this.updateRealTime);
this.openmct.time.on('bounds', this.updateDisplayBounds);
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() {
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
let config = configStore.get(configId);
@@ -504,7 +485,7 @@ export default {
* displays can update accordingly.
*/
synchronized(value) {
const isLocalClock = this.timeContext.clock();
const isLocalClock = this.openmct.time.clock();
if (typeof value !== 'undefined') {
this._synchronized = value;
@@ -977,7 +958,7 @@ export default {
},
showSynchronizeDialog() {
const isLocalClock = this.timeContext.clock();
const isLocalClock = this.openmct.time.clock();
if (isLocalClock !== undefined) {
const message = `
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() {
this.timeContext.stopClock();
this.openmct.time.stopClock();
const range = this.config.xAxis.get('displayRange');
this.timeContext.bounds({
this.openmct.time.bounds({
start: range.min,
end: range.max
});
@@ -1025,7 +1006,6 @@ export default {
configStore.deleteStore(this.config.id);
this.stopListening();
if (this.checkForSize) {
clearInterval(this.checkForSize);
delete this.checkForSize;
@@ -1041,7 +1021,8 @@ export default {
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);
},
updateStatus(status) {

View File

@@ -77,7 +77,7 @@
<script>
import eventHelpers from "./lib/eventHelpers";
import { ticks, getFormattedTicks } from "./tickUtils";
import configStore from "./configuration/ConfigStore";
import configStore from "./configuration/configStore";
export default {
inject: ['openmct', 'domainObject'],

View File

@@ -80,7 +80,7 @@ export default {
components: {
MctPlot
},
inject: ['openmct', 'domainObject', 'path'],
inject: ['openmct', 'domainObject'],
props: {
options: {
type: Object,

View File

@@ -68,8 +68,7 @@ export default function PlotViewProvider(openmct) {
},
provide: {
openmct,
domainObject,
path: objectPath
domainObject
},
data() {
return {

View File

@@ -54,7 +54,7 @@
<script>
import MctTicks from "../MctTicks.vue";
import eventHelpers from '../lib/eventHelpers';
import configStore from "../configuration/ConfigStore";
import configStore from "../configuration/configStore";
export default {
components: {

View File

@@ -57,7 +57,7 @@
<script>
import MctTicks from "../MctTicks.vue";
import configStore from "../configuration/ConfigStore";
import configStore from "../configuration/configStore";
export default {
components: {

View File

@@ -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;
}
};
}

View File

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

View File

@@ -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';

View File

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

View File

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

View File

@@ -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;
}
};
}
};
}

View File

@@ -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;
}
};
}

View File

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

View File

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

View File

@@ -19,9 +19,6 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<!-- eslint-disable vue/no-v-html -->
<template>
<div class="gl-plot-chart-area">
<span v-html="canvasTemplate"></span>
@@ -41,7 +38,7 @@ import MCTChartLineStepAfter from './MCTChartLineStepAfter';
import MCTChartPointSet from './MCTChartPointSet';
import MCTChartAlarmPointSet from './MCTChartAlarmPointSet';
import MCTChartAlarmLineSet from "./MCTChartAlarmLineSet";
import configStore from "../configuration/ConfigStore";
import configStore from "../configuration/configStore";
import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
import LimitLine from "./LimitLine.vue";
import LimitLabel from "./LimitLabel.vue";

View File

@@ -22,7 +22,7 @@
import _ from 'lodash';
import Model from "./Model";
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

View File

@@ -115,7 +115,7 @@
<script>
import PlotOptionsItem from "./PlotOptionsItem.vue";
import configStore from "../configuration/ConfigStore";
import configStore from "../configuration/configStore";
import eventHelpers from "../lib/eventHelpers";
export default {

View File

@@ -49,7 +49,7 @@ import SeriesForm from "./forms/SeriesForm.vue";
import YAxisForm from "./forms/YAxisForm.vue";
import LegendForm from "./forms/LegendForm.vue";
import eventHelpers from "../lib/eventHelpers";
import configStore from "../configuration/ConfigStore";
import configStore from "../configuration/configStore";
export default {
components: {

View File

@@ -68,23 +68,26 @@
{{ limitLines ? "Enabled" : "Disabled" }}
</div>
</li>
<ColorSwatch :current-color="seriesHexColor"
edit-title="Manually set the plot line and marker color for this series."
view-title="The plot line and marker color for this series."
short-label="Color"
/>
<li class="grid-row">
<div class="grid-cell label"
title="The plot line and marker color for this series."
>Color</div>
<div class="grid-cell value">
<span class="c-color-swatch"
:style="{
'background': seriesHexColor
}"
>
</span>
</div>
</li>
</ul>
</li>
</ul>
</template>
<script>
import ColorSwatch from "@/plugins/plot/ColorSwatch.vue";
export default {
components: {
ColorSwatch
},
inject: ['openmct', 'domainObject', 'path'],
props: {
series: {

View File

@@ -117,27 +117,49 @@
<li v-show="interpolate !== 'none' || markers"
class="grid-row"
>
<ColorSwatch :current-color="currentColor"
edit-title="Manually set the plot line and marker color for this series."
view-title="The plot line and marker color for this series."
short-label="Color"
@colorSet="setColor"
/>
<div class="grid-cell label"
title="Manually set the plot line and marker color for this series."
>Color</div>
<div class="grid-cell value">
<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>
</ul>
</ul>
</template>
<script>
import ColorSwatch from '../../ColorSwatch.vue';
import { MARKER_SHAPES } from "../../draw/MarkerShapes";
import { objectPath, validate, coerce } from "./formUtil";
import _ from 'lodash';
export default {
components: {
ColorSwatch
},
inject: ['openmct', 'domainObject', 'path'],
props: {
series: {
@@ -187,7 +209,7 @@ export default {
expandedCssClass() {
return this.expanded ? 'c-disclosure-triangle--expanded' : '';
},
currentColor() {
seriesColorAsHex() {
return this.series.get('color').asHexString();
}
},

View File

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

View File

@@ -53,8 +53,7 @@ export default function OverlayPlotViewProvider(openmct) {
},
provide: {
openmct,
domainObject,
path: objectPath
domainObject
},
data() {
return {

View File

@@ -19,18 +19,13 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { BAR_GRAPH_KEY } from './barGraph/BarGraphConstants';
import PlotViewProvider from './PlotViewProvider';
import SpectralPlotViewProvider from './spectralPlot/SpectralPlotViewProvider';
import BarGraphViewProvider from './barGraph/BarGraphViewProvider';
import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider';
import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider';
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
import BarGraphInspectorViewProvider from './barGraph/inspector/BarGraphInspectorViewProvider';
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
import SpectralPlotCompositionPolicy from './spectralPlot/SpectralPlotCompositionPolicy';
import BarGraphCompositionPolicy from './barGraph/BarGraphCompositionPolicy';
export default function () {
return function install(openmct) {
@@ -64,48 +59,13 @@ export default function () {
},
priority: 890
});
openmct.types.addType('telemetry.plot.spectral', {
key: "telemetry.plot.spectral",
name: "Spectral Plot",
cssClass: "icon-plot-stacked",
description: "View Spectra on Y Axes with non-time domain on the X axis. Can be added to Display Layouts.",
//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 OverlayPlotViewProvider(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 BarGraphInspectorViewProvider(openmct));
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new SpectralPlotCompositionPolicy(openmct).allow);
openmct.composition.addPolicy(new BarGraphCompositionPolicy(openmct).allow);
};
}

View File

@@ -24,12 +24,10 @@ import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} f
import PlotVuePlugin from "./plugin";
import Vue from "vue";
import StackedPlot from "./stackedPlot/StackedPlot.vue";
// import SpectralPlot from "./spectralPlot/SpectralPlot.vue";
import configStore from "./configuration/ConfigStore";
import configStore from "./configuration/configStore";
import EventEmitter from "EventEmitter";
import PlotOptions from "./inspector/PlotOptions.vue";
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
import { BAR_GRAPH_VIEW, BAR_GRAPH_KEY } from './barGraph/BarGraphConstants';
describe("the plugin", function () {
let element;
@@ -37,6 +35,7 @@ describe("the plugin", function () {
let openmct;
let telemetryPromise;
let telemetryPromiseResolve;
let cleanupFirst;
let mockObjectPath;
let telemetrylimitProvider;
@@ -76,16 +75,9 @@ describe("the plugin", function () {
'some-other-key': 'some-other-value 3'
}
];
cleanupFirst = [];
const timeSystem = {
timeSystemKey: 'utc',
bounds: {
start: 0,
end: 4
}
};
openmct = createOpenMct(timeSystem);
openmct = createOpenMct();
telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve;
@@ -152,6 +144,11 @@ describe("the plugin", function () {
disconnect() {}
});
openmct.time.timeSystem("utc", {
start: 0,
end: 4
});
openmct.types.addType("test-object", {
creatable: true
});
@@ -171,8 +168,19 @@ describe("the plugin", function () {
end: 1
});
configStore.deleteAll();
resetApplicationState(openmct).then(done).catch(done);
// 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();
resetApplicationState(openmct).then(done).catch(done);
});
});
describe("the plot views", () => {
@@ -304,38 +312,6 @@ describe("the plugin", function () {
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-stacked");
expect(plotView).toBeDefined();
});
it("provides a spectral plot view for objects with telemetry", () => {
const testTelemetryObject = {
id: "test-object",
type: "telemetry.plot.spectral",
telemetry: {
values: [{
key: "a-very-fine-key"
}]
}
};
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-spectral");
expect(plotView).toBeDefined();
});
it("provides a spectral aggregate plot view for objects with telemetry", () => {
const testTelemetryObject = {
id: "test-object",
type: 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", () => {
@@ -385,6 +361,10 @@ describe("the plugin", function () {
plotView = plotViewProvider.view(testTelemetryObject, [testTelemetryObject]);
plotView.show(child, true);
cleanupFirst.push(() => {
plotView.destroy();
});
return Vue.nextTick();
});
@@ -404,23 +384,12 @@ describe("the plugin", function () {
expect(legend.length).toBe(6);
});
it("Renders X-axis ticks for the telemetry object", (done) => {
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
const config = configStore.get(configId);
config.xAxis.set('displayRange', {
min: 0,
max: 4
});
it("Renders X-axis ticks for the telemetry object", () => {
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
expect(xAxisElement.length).toBe(1);
Vue.nextTick(() => {
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
expect(xAxisElement.length).toBe(1);
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(5);
done();
});
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(5);
});
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", () => {
let testTelemetryObject;
let testTelemetryObject2;
@@ -741,12 +570,16 @@ describe("the plugin", function () {
provide: {
openmct: openmct,
domainObject: stackedPlotObject,
composition: openmct.composition.get(stackedPlotObject),
path: [stackedPlotObject]
composition: openmct.composition.get(stackedPlotObject)
},
template: "<stacked-plot></stacked-plot>"
});
cleanupFirst.push(() => {
component.$destroy();
component = undefined;
});
return telemetryPromise
.then(Vue.nextTick())
.then(() => {
@@ -772,21 +605,12 @@ describe("the plugin", function () {
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");
expect(xAxisElement.length).toBe(1);
config.xAxis.set('displayRange', {
min: 0,
max: 4
});
Vue.nextTick(() => {
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(5);
done();
});
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(5);
});
it("Renders Y-axis ticks for the telemetry object", (done) => {
@@ -1138,7 +962,7 @@ describe("the plugin", function () {
expandControl.dispatchEvent(clickEvent);
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
expect(plotOptionsProperties.length).toEqual(8);
expect(plotOptionsProperties.length).toEqual(7);
});
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);
});
});
});

View File

@@ -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;
}
};
}

View File

@@ -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;
}
};
}
};
}

View File

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

View File

@@ -52,24 +52,22 @@
>
</button>
</div>
<div class="l-view-section">
<stacked-plot-item v-for="object in compositionObjects"
:key="object.id"
class="c-plot--stacked-container"
:object="object"
:options="options"
:grid-lines="gridLines"
:cursor-guide="cursorGuide"
:plot-tick-width="maxTickWidth"
@plotTickWidth="onTickWidthChange"
@loadingUpdated="loadingUpdated"
/>
</div>
<stacked-plot-item v-for="object in compositionObjects"
:key="object.id"
class="c-plot--stacked-container"
:object="object"
:options="options"
:grid-lines="gridLines"
:cursor-guide="cursorGuide"
:plot-tick-width="maxTickWidth"
@plotTickWidth="onTickWidthChange"
@loadingUpdated="loadingUpdated"
/>
</div>
</template>
<script>
import eventHelpers from '../lib/eventHelpers';
import StackedPlotItem from './StackedPlotItem.vue';
import ImageExporter from '../../../exporters/ImageExporter';
@@ -77,7 +75,7 @@ export default {
components: {
StackedPlotItem
},
inject: ['openmct', 'domainObject', 'composition', 'path'],
inject: ['openmct', 'domainObject', 'composition'],
props: {
options: {
type: Object,
@@ -104,6 +102,8 @@ export default {
this.destroy();
},
mounted() {
eventHelpers.extend(this);
this.imageExporter = new ImageExporter(this.openmct);
this.tickWidthMap = {};
@@ -118,6 +118,7 @@ export default {
this.loading = loaded;
},
destroy() {
this.stopListening();
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
this.composition.off('reorder', this.compositionReorder);

View File

@@ -28,7 +28,7 @@ import MctPlot from '../MctPlot.vue';
import Vue from "vue";
export default {
inject: ['openmct', 'domainObject', 'path'],
inject: ['openmct', 'domainObject'],
props: {
object: {
type: Object,
@@ -75,11 +75,6 @@ export default {
mounted() {
this.updateView();
},
beforeDestroy() {
if (this.component) {
this.component.$destroy();
}
},
methods: {
updateComponentProp(prop, value) {
if (this.component) {
@@ -99,7 +94,6 @@ export default {
const openmct = this.openmct;
const object = this.object;
const path = this.path;
const getProps = this.getProps;
let viewContainer = document.createElement('div');
@@ -112,8 +106,7 @@ export default {
},
provide: {
openmct,
domainObject: object,
path
domainObject: object
},
data() {
return {

View File

@@ -55,8 +55,7 @@ export default function StackedPlotViewProvider(openmct) {
provide: {
openmct,
domainObject,
composition: openmct.composition.get(domainObject),
path: objectPath
composition: openmct.composition.get(domainObject)
},
data() {
return {

View File

@@ -48,17 +48,17 @@ define([
components: {
TabsComponent: TabsComponent.default
},
data() {
return {
isEditing: editMode
};
},
provide: {
openmct,
domainObject,
objectPath,
composition: openmct.composition.get(domainObject)
},
data() {
return {
isEditing: editMode
};
},
template: '<tabs-component :isEditing="isEditing"></tabs-component>'
});
},

View File

@@ -528,6 +528,7 @@ export default {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(() => {
let start = 0;
let end = VISIBLE_ROW_COUNT;
let tableRows = this.table.tableRows.getRows();

View File

@@ -222,13 +222,9 @@ describe("the plugin", () => {
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');
Vue.nextTick(() => {
expect(rows.length).toBe(3);
done();
});
expect(rows.length).toBe(3);
});
it("Renders a column for every item in telemetry metadata", () => {

View File

@@ -29,36 +29,144 @@
isFixed ? 'is-fixed-mode' : 'is-realtime-mode'
]"
>
<div class="c-conductor__time-bounds">
<conductor-inputs-fixed v-if="isFixed"
@updated="saveFixedOffsets"
/>
<conductor-inputs-realtime v-else
@updated="saveClockOffsets"
/>
<ConductorModeIcon class="c-conductor__mode-icon" />
<conductor-axis
class="c-conductor__ticks"
:view-bounds="viewBounds"
:is-fixed="isFixed"
:alt-pressed="altPressed"
@endPan="endPan"
@endZoom="endZoom"
@panAxis="pan"
@zoomAxis="zoom"
/>
</div>
<div class="c-conductor__controls">
<ConductorMode class="c-conductor__mode-select" />
<ConductorTimeSystem class="c-conductor__time-system-select" />
<ConductorHistory
class="c-conductor__history-select"
:offsets="openmct.time.clockOffsets()"
:bounds="bounds"
:time-system="timeSystem"
:mode="timeMode"
/>
</div>
<form
ref="conductorForm"
class="u-contents"
@submit.prevent="updateTimeFromConductor"
>
<div class="c-conductor__time-bounds">
<button
ref="submitButton"
class="c-input--submit"
type="submit"
></button>
<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
class="c-conductor__ticks"
:view-bounds="viewBounds"
:is-fixed="isFixed"
:alt-pressed="altPressed"
@endPan="endPan"
@endZoom="endZoom"
@panAxis="pan"
@zoomAxis="zoom"
/>
</div>
<div class="c-conductor__controls">
<ConductorMode class="c-conductor__mode-select" />
<ConductorTimeSystem class="c-conductor__time-system-select" />
<ConductorHistory
class="c-conductor__history-select"
:offsets="openmct.time.clockOffsets()"
:bounds="bounds"
:time-system="timeSystem"
:mode="timeMode"
/>
</div>
<input
type="submit"
class="invisible"
>
</form>
</div>
</template>
@@ -66,23 +174,23 @@
import _ from 'lodash';
import ConductorMode from './ConductorMode.vue';
import ConductorTimeSystem from './ConductorTimeSystem.vue';
import DatePicker from './DatePicker.vue';
import ConductorAxis from './ConductorAxis.vue';
import ConductorModeIcon from './ConductorModeIcon.vue';
import ConductorHistory from './ConductorHistory.vue';
import ConductorInputsFixed from "./ConductorInputsFixed.vue";
import ConductorInputsRealtime from "./ConductorInputsRealtime.vue";
import TimePopup from './timePopup.vue';
const DEFAULT_DURATION_FORMATTER = 'duration';
export default {
components: {
ConductorInputsRealtime,
ConductorInputsFixed,
ConductorMode,
ConductorTimeSystem,
DatePicker,
ConductorAxis,
ConductorModeIcon,
ConductorHistory
ConductorHistory,
TimePopup
},
inject: ['openmct', 'configuration'],
data() {
@@ -134,6 +242,7 @@ export default {
this.openmct.time.on('bounds', _.throttle(this.handleNewBounds, 300));
this.openmct.time.on('timeSystem', this.setTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
this.openmct.time.on('clockOffsets', this.setViewFromOffsets);
},
beforeDestroy() {
document.removeEventListener('keydown', this.handleKeyDown);
@@ -168,16 +277,13 @@ export default {
}
},
zoom(bounds) {
if (isNaN(bounds.start) || isNaN(bounds.end)) {
this.isZooming = false;
} else {
this.isZooming = true;
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
}
this.isZooming = true;
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
},
endZoom(bounds) {
this.isZooming = false;
if (bounds) {
this.openmct.time.bounds(bounds);
} else {
@@ -191,8 +297,42 @@ export default {
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
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) {
// this.clearAllValidation();
this.clearAllValidation();
this.isFixed = clock === undefined;
},
setViewFromBounds(bounds) {
@@ -201,16 +341,158 @@ export default {
this.viewBounds.start = bounds.start;
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) {
return this.openmct.telemetry.getValueFormatter({
format: key
}).formatter;
},
saveClockOffsets(offsets) {
this.openmct.time.clockOffsets(offsets);
startDateSelected(date) {
this.formattedBounds.start = this.timeFormatter.format(date);
this.validateAllBounds('startDate');
this.submitForm();
},
saveFixedOffsets(bounds) {
this.openmct.time.bounds(bounds);
endDateSelected(date) {
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();
}
}
};

View File

@@ -40,7 +40,7 @@ const LOCAL_STORAGE_HISTORY_KEY_FIXED = 'tcHistory';
const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
const DEFAULT_RECORDS = 10;
import { millisecondsToDHMS } from "utils/duration";
import { getDuration } from "utils/duration";
export default {
inject: ['openmct', 'configuration'],
@@ -142,7 +142,7 @@ export default {
let description = `${startTime} - ${this.formatTime(timespan.end)}`;
if (this.timeSystem.isUTCBased && !this.openmct.time.clock()) {
name = `${startTime} ${millisecondsToDHMS(timespan.end - timespan.start)}`;
name = `${startTime} ${getDuration(timespan.end - timespan.start)}`;
} else {
name = description;
}
@@ -263,7 +263,7 @@ export default {
format: format
}).formatter;
return (isNegativeOffset ? '-' : '') + formatter.format(time, 'YYYY-MM-DD HH:mm:ss');
return (isNegativeOffset ? '-' : '') + formatter.format(time);
},
showHistoryMenu() {
const elementBoundingClientRect = this.$refs.historyButton.getBoundingClientRect();

View File

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

View File

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

View File

@@ -22,8 +22,7 @@
<template>
<div
ref="calendarHolder"
class="c-ctrl-wrapper c-datetime-picker__wrapper"
:class="{'c-ctrl-wrapper--menus-up': bottom !== true, 'c-ctrl-wrapper--menus-down': bottom === true}"
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up c-datetime-picker__wrapper"
>
<a
class="c-icon-button icon-calendar"
@@ -119,12 +118,6 @@ export default {
formatter: {
type: Object,
required: true
},
bottom: {
type: Boolean,
default() {
return false;
}
}
},
data: function () {

View File

@@ -8,10 +8,6 @@
/*********************************************** CONDUCTOR LAYOUT */
.c-conductor {
&__inputs {
display: contents;
}
&__time-bounds {
display: grid;
grid-column-gap: $interiorMargin;
@@ -54,6 +50,13 @@
}
}
[class*='-delta'] {
&:before {
content: $glyph-icon-clock;
font-family: symbolsfont;
}
}
&.is-fixed-mode {
.c-conductor-axis {
&__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 {
color: $colorInputFg;
display: flex;
@@ -268,22 +250,18 @@
box-shadow: $shdwMenu;
padding: $interiorMargin;
position: absolute;
left: 8px;
bottom: 24px;
z-index: 99;
&[class*='--bottom'] {
bottom: auto;
top: 24px;
&[class*='--start'] {
left: -25px;
}
&[class*='--end'] {
right: 0;
}
}
.l-shell__time-conductor .pr-tc-input-menu--end {
left: auto;
right: 0;
}
[class^='pr-time'] {
&[class*='label'] {
font-size: 0.8em;

View File

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

View File

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

View File

@@ -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');
});
});
});

View File

@@ -1,7 +1,6 @@
<template>
<div
class="pr-tc-input-menu"
:class="{'pr-tc-input-menu--bottom' : bottom === true}"
@keydown.enter.prevent
@keyup.enter.prevent="submit"
@keydown.esc.prevent
@@ -89,12 +88,6 @@ export default {
offset: {
type: String,
required: true
},
bottom: {
type: Boolean,
default() {
return false;
}
}
},
data() {

View File

@@ -86,16 +86,15 @@ export default {
return {
items: [],
timeSystems: [],
height: 0,
useIndependentTime: this.domainObject.configuration.useIndependentTime === true,
timeOptions: this.domainObject.configuration.timeOptions
height: 0
};
},
beforeDestroy() {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.reorder);
this.stopFollowingTimeContext();
this.openmct.time.off("bounds", this.updateViewBounds);
},
mounted() {
if (this.composition) {
@@ -105,8 +104,8 @@ export default {
this.composition.load();
}
this.setTimeContext();
this.getTimeSystems();
this.openmct.time.on("bounds", this.updateViewBounds);
},
methods: {
addItem(domainObject) {
@@ -133,8 +132,8 @@ export default {
},
removeItem(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.updateContentHeight();
},
reorder(reorderPlan) {
let oldItems = this.items.slice();
@@ -155,7 +154,7 @@ export default {
});
},
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
return currentBounds;
@@ -165,20 +164,6 @@ export default {
if (currentTimeSystem) {
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);
}
}
}
};

View File

@@ -20,8 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import TimelineViewProvider from './TimelineViewProvider';
import timelineInterceptor from "./timelineInterceptor";
import TimelineViewProvider from '../timeline/TimelineViewProvider';
export default function () {
return function install(openmct) {
@@ -33,12 +32,8 @@ export default function () {
cssClass: 'icon-timeline',
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
useIndependentTime: false
};
}
});
timelineInterceptor(openmct);
openmct.objectViews.addProvider(new TimelineViewProvider(openmct));
};
}

View File

@@ -51,15 +51,7 @@ describe('the plugin', function () {
}
];
const timeSystem = {
timeSystemKey: 'utc',
bounds: {
start: 1597160002854,
end: 1597181232854
}
};
openmct = createOpenMct(timeSystem);
openmct = createOpenMct();
openmct.install(new TimelinePlugin());
objectDef = openmct.types.get('time-strip').definition;
@@ -72,6 +64,11 @@ describe('the plugin', function () {
child.style.height = '480px';
element.appendChild(child);
openmct.time.timeSystem('utc', {
start: 1597160002854,
end: 1597181232854
});
openmct.on('start', done);
openmct.startHeadless();
});
@@ -91,6 +88,7 @@ describe('the plugin', function () {
});
describe('the time-strip object', () => {
it('is creatable', () => {
expect(objectDef.creatable).toEqual(mockObject.creatable);
});
@@ -98,15 +96,10 @@ describe('the plugin', function () {
describe('the view', () => {
let timelineView;
let testViewObject;
beforeEach(() => {
testViewObject = {
const testViewObject = {
id: "test-object",
identifier: {
key: "test-object",
namespace: ''
},
type: "time-strip"
};
@@ -126,106 +119,6 @@ describe('the plugin', function () {
const el = element.querySelector('.c-timesystem-axis');
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);
});
});
});
});

View File

@@ -1,10 +1,4 @@
.c-timeline-holder {
@include abs();
display: flex;
flex-direction: column;
overflow-x: hidden;
> * + * {
margin-top: $interiorMargin;
}
}
}

View File

@@ -453,16 +453,12 @@ select {
}
}
.c-so-view--hyperlink.c-so-view--no-frame {
.c-hyperlink--button {
.c-so-view--no-frame > .c-so-view__object-view > .c-hyperlink--button {
@include abs();
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.c-so-view__frame-controls { display: none; }
}
/******************************************************** MENUS */
@@ -1010,9 +1006,6 @@ input[type="range"] {
transition: $transIn;
opacity: 1;
pointer-events: inherit;
&[disabled] { opacity: $controlDisabledOpacity; }
}
}

View File

@@ -46,6 +46,9 @@ mct-plot {
.c-plot,
.gl-plot {
overflow: hidden;
min-height: 100px;
.s-status-taking-snapshot & {
.c-control-bar {
display: none;
@@ -64,17 +67,16 @@ mct-plot {
.c-plot {
@include abs($mainViewPad);
display: flex;
flex-direction: column;
overflow: hidden;
min-height: $plotMinH;
.c-control-bar {
flex: 0 0 auto;
margin-bottom: $interiorMargin;
}
.l-view-section {
.l-view-section, .c-plot--stacked-container {
display: flex;
flex: 1 1 auto;
flex-direction: column;
@@ -82,18 +84,7 @@ mct-plot {
overflow-x: hidden;
}
.c-plot--stacked-container {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-height: $plotMinH;
overflow: hidden;
}
;
&--stacked {
min-height: auto !important;
.child-frame {
.has-control-bar {
.c-control-bar {
@@ -133,7 +124,7 @@ mct-plot {
.plot-wrapper-axis-and-display-area {
position: relative;
flex: 1 1 auto;
//min-height: $plotMinH;
min-height: $plotMinH;
}
.gl-plot-wrapper-display-area-and-x-axis {
@@ -474,7 +465,6 @@ mct-plot {
.gl-plot-legend,
.c-plot-legend {
overflow: hidden;
flex: 0 0 auto; // Prevents clipping for all legend placements (top, bottom, etc.)
&__wrapper {
// 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 */
[class*='c-cursor-guide'] {
box-shadow: $shdwCursorGuide;

View File

@@ -582,12 +582,6 @@
}
}
&[class*='--menus-bottom'] {
.c-menu {
top: auto; bottom: 100%;
}
}
&[class*='--menus-left'],
&[class*='menus-to-left'] {
.c-menu {
@@ -599,6 +593,8 @@
@mixin cArrowButtonBase($colorBg: transparent, $colorFg: $colorBtnFg, $filterHov: $filterHov) {
// Copied from branch new-tree-refactor
flex: 0 0 auto;
position: relative;
background: $colorBg;
&:before {

View File

@@ -40,11 +40,6 @@
}
}
.zerolinelayer {
// Hide unneeded plotly-styled horizontal line
display: none;
}
path.xy2-y {
stroke: $colorPlotHash !important; // Using this instead of $colorPlotAreaBorder because that is an rgba
opacity: $opacityPlotHash !important;

Some files were not shown because too many files have changed in this diff Show More