mirror of
https://github.com/nasa/openmct.git
synced 2025-06-26 03:00:13 +00:00
Compare commits
18 Commits
stackplots
...
plot-perfo
Author | SHA1 | Date | |
---|---|---|---|
fd4dcc8513 | |||
9ebd18318b | |||
4a89b81f4f | |||
98e1abd7b1 | |||
56c25762ac | |||
5c8e726b87 | |||
d80f4a1f7d | |||
3fe4c7a954 | |||
676ef60128 | |||
5a90d28450 | |||
2bb6822e6b | |||
383b4c0d8d | |||
404ab720ad | |||
259ab53060 | |||
1db7ac55b4 | |||
82b3383834 | |||
ac240d524c | |||
1b034f6125 |
@ -42,7 +42,6 @@ jobs:
|
|||||||
- ~/.npm
|
- ~/.npm
|
||||||
- ~/.cache
|
- ~/.cache
|
||||||
- node_modules
|
- node_modules
|
||||||
- run: npm run lint
|
|
||||||
- run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
|
- run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: dist/reports/tests/
|
path: dist/reports/tests/
|
||||||
@ -57,38 +56,14 @@ workflows:
|
|||||||
browser: ChromeHeadless
|
browser: ChromeHeadless
|
||||||
always-pass: false
|
always-pass: false
|
||||||
- test:
|
- test:
|
||||||
name: node12-firefoxESR-build-only
|
name: node12-firefoxESR
|
||||||
node-version: lts/erbium
|
node-version: lts/erbium
|
||||||
browser: FirefoxESR
|
browser: FirefoxESR
|
||||||
always-pass: true
|
always-pass: true
|
||||||
- test:
|
- test:
|
||||||
name: node14-chrome-build-only
|
name: node14-chrome
|
||||||
node-version: lts/fermium
|
node-version: lts/fermium
|
||||||
browser: ChromeHeadless
|
browser: ChromeHeadless
|
||||||
always-pass: true
|
always-pass: true
|
||||||
nightly:
|
|
||||||
jobs:
|
|
||||||
- test:
|
|
||||||
name: node10-chrome-nightly
|
|
||||||
node-version: lts/dubnium
|
|
||||||
browser: ChromeHeadless
|
|
||||||
always-pass: false
|
|
||||||
- test:
|
|
||||||
name: node12-firefoxESR-nightly
|
|
||||||
node-version: lts/erbium
|
|
||||||
browser: FirefoxESR
|
|
||||||
always-pass: false
|
|
||||||
- test:
|
|
||||||
name: node14-chrome-nightly
|
|
||||||
node-version: lts/fermium
|
|
||||||
browser: ChromeHeadless
|
|
||||||
always-pass: false
|
|
||||||
triggers:
|
|
||||||
- schedule:
|
|
||||||
cron: "0 0 * * *"
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
||||||
|
|
||||||
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
|
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
|
||||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
|
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
|
||||||
* [ ] Is this change backwards compatible? For example, developers won't need to change how they are calling the API or how they've extended core plugins such as Tables or Plots.
|
|
||||||
|
|
||||||
### Author Checklist
|
### Author Checklist
|
||||||
|
|
||||||
|
33
.github/workflows/codeql-analysis.yml
vendored
33
.github/workflows/codeql-analysis.yml
vendored
@ -1,33 +0,0 @@
|
|||||||
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
schedule:
|
|
||||||
- cron: '28 21 * * 3'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v1
|
|
||||||
with:
|
|
||||||
languages: javascript
|
|
||||||
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v1
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v1
|
|
2
API.md
2
API.md
@ -996,7 +996,7 @@ reveal additional information when the mouse cursor is hovered over it.
|
|||||||
A common use case for indicators is to convey the state of some external system such as a
|
A common use case for indicators is to convey the state of some external system such as a
|
||||||
persistence backend or HTTP server. So long as this system is accessible via HTTP request,
|
persistence backend or HTTP server. So long as this system is accessible via HTTP request,
|
||||||
Open MCT provides a general purpose indicator to show whether the server is available and
|
Open MCT provides a general purpose indicator to show whether the server is available and
|
||||||
returning a 2xx status code. The URL Status Indicator is made available as a default plugin. See
|
returing a 2xx status code. The URL Status Indicator is made available as a default plugin. See
|
||||||
the [documentation](./src/plugins/URLIndicatorPlugin) for details on how to install and configure the
|
the [documentation](./src/plugins/URLIndicatorPlugin) for details on how to install and configure the
|
||||||
URL Status Indicator.
|
URL Status Indicator.
|
||||||
|
|
||||||
|
@ -317,7 +317,6 @@ checklist).
|
|||||||
### Reviewer Checklist
|
### Reviewer Checklist
|
||||||
|
|
||||||
* [ ] Changes appear to address issue?
|
* [ ] Changes appear to address issue?
|
||||||
* [ ] Changes appear not to be breaking changes?
|
|
||||||
* [ ] Appropriate unit tests included?
|
* [ ] Appropriate unit tests included?
|
||||||
* [ ] Code style and in-line documentation are appropriate?
|
* [ ] Code style and in-line documentation are appropriate?
|
||||||
* [ ] Commit messages meet standards?
|
* [ ] Commit messages meet standards?
|
||||||
|
@ -423,7 +423,7 @@ which can help with this, however.
|
|||||||
instead of separate approaches for static and substitutable
|
instead of separate approaches for static and substitutable
|
||||||
dependencies.
|
dependencies.
|
||||||
* Removes need to understand Angular's DI mechanism.
|
* Removes need to understand Angular's DI mechanism.
|
||||||
* Improves usability of documentation (`typeService` is an
|
* Improves useability of documentation (`typeService` is an
|
||||||
instance of `CompositeService` and implements `TypeService`
|
instance of `CompositeService` and implements `TypeService`
|
||||||
so you can easily traverse links in the JSDoc.)
|
so you can easily traverse links in the JSDoc.)
|
||||||
* Can be used more easily from Web Workers, allowing services
|
* Can be used more easily from Web Workers, allowing services
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
## Legacy Documentation
|
## Legacy Documentation
|
||||||
|
|
||||||
As we transition to a new API, the following documentation for the old API
|
As we transition to a new API, the following documentation for the old API
|
||||||
(which is supported during the transition) may be useful as well:
|
(which is supported during the transtion) may be useful as well:
|
||||||
|
|
||||||
* The [Architecture Overview](architecture/) describes the concepts used
|
* The [Architecture Overview](architecture/) describes the concepts used
|
||||||
throughout Open MCT, and gives a high level overview of the platform's design.
|
throughout Open MCT, and gives a high level overview of the platform's design.
|
||||||
|
@ -28,15 +28,6 @@ define([
|
|||||||
domain: 2
|
domain: 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: "cos",
|
|
||||||
name: "Cosine",
|
|
||||||
unit: "deg",
|
|
||||||
formatString: '%0.2f',
|
|
||||||
hints: {
|
|
||||||
domain: 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Need to enable "LocalTimeSystem" plugin to make use of this
|
// Need to enable "LocalTimeSystem" plugin to make use of this
|
||||||
// {
|
// {
|
||||||
// key: "local",
|
// key: "local",
|
||||||
@ -118,100 +109,6 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
'example.spectral-generator': {
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
name: "Name",
|
|
||||||
format: "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "utc",
|
|
||||||
name: "Time",
|
|
||||||
format: "utc",
|
|
||||||
hints: {
|
|
||||||
domain: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "wavelength",
|
|
||||||
name: "Wavelength",
|
|
||||||
unit: "Hz",
|
|
||||||
formatString: '%0.2f',
|
|
||||||
hints: {
|
|
||||||
domain: 2,
|
|
||||||
spectralAttribute: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "cos",
|
|
||||||
name: "Cosine",
|
|
||||||
unit: "deg",
|
|
||||||
formatString: '%0.2f',
|
|
||||||
hints: {
|
|
||||||
range: 2,
|
|
||||||
spectralAttribute: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'example.spectral-aggregate-generator': {
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
name: "Name",
|
|
||||||
format: "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "utc",
|
|
||||||
name: "Time",
|
|
||||||
format: "utc",
|
|
||||||
hints: {
|
|
||||||
domain: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "ch1",
|
|
||||||
name: "Channel 1",
|
|
||||||
format: "string",
|
|
||||||
hints: {
|
|
||||||
range: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "ch2",
|
|
||||||
name: "Channel 2",
|
|
||||||
format: "string",
|
|
||||||
hints: {
|
|
||||||
range: 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "ch3",
|
|
||||||
name: "Channel 3",
|
|
||||||
format: "string",
|
|
||||||
hints: {
|
|
||||||
range: 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "ch4",
|
|
||||||
name: "Channel 4",
|
|
||||||
format: "string",
|
|
||||||
hints: {
|
|
||||||
range: 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "ch5",
|
|
||||||
name: "Channel 5",
|
|
||||||
format: "string",
|
|
||||||
hints: {
|
|
||||||
range: 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
|
|
||||||
], function (
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
function SpectralAggregateGeneratorProvider() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, count, name) {
|
|
||||||
return {
|
|
||||||
name: name,
|
|
||||||
utc: String(Math.floor(timestamp / count) * count),
|
|
||||||
ch1: String(Math.floor(timestamp / count) % 1),
|
|
||||||
ch2: String(Math.floor(timestamp / count) % 2),
|
|
||||||
ch3: String(Math.floor(timestamp / count) % 3),
|
|
||||||
ch4: String(Math.floor(timestamp / count) % 4),
|
|
||||||
ch5: String(Math.floor(timestamp / count) % 5)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
SpectralAggregateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
|
|
||||||
return domainObject.type === 'example.spectral-aggregate-generator';
|
|
||||||
};
|
|
||||||
|
|
||||||
SpectralAggregateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
|
||||||
var count = 5000;
|
|
||||||
|
|
||||||
var interval = setInterval(function () {
|
|
||||||
var now = Date.now();
|
|
||||||
var datum = pointForTimestamp(now, count, domainObject.name);
|
|
||||||
callback(datum);
|
|
||||||
}, count);
|
|
||||||
|
|
||||||
return function () {
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
SpectralAggregateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
|
|
||||||
return domainObject.type === 'example.spectral-aggregate-generator';
|
|
||||||
};
|
|
||||||
|
|
||||||
SpectralAggregateGeneratorProvider.prototype.request = function (domainObject, options) {
|
|
||||||
var start = options.start;
|
|
||||||
var end = Math.min(Date.now(), options.end); // no future values
|
|
||||||
var count = 5000;
|
|
||||||
if (options.strategy === 'latest' || options.size === 1) {
|
|
||||||
start = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = [];
|
|
||||||
while (start <= end && data.length < 5000) {
|
|
||||||
data.push(pointForTimestamp(start, count, domainObject.name));
|
|
||||||
start += count;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return SpectralAggregateGeneratorProvider;
|
|
||||||
|
|
||||||
});
|
|
@ -1,102 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./WorkerInterface'
|
|
||||||
], function (
|
|
||||||
WorkerInterface
|
|
||||||
) {
|
|
||||||
|
|
||||||
var REQUEST_DEFAULTS = {
|
|
||||||
amplitude: 1,
|
|
||||||
wavelength: 1,
|
|
||||||
period: 10,
|
|
||||||
offset: 0,
|
|
||||||
dataRateInHz: 1,
|
|
||||||
randomness: 0,
|
|
||||||
phase: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
function SpectralGeneratorProvider() {
|
|
||||||
this.workerInterface = new WorkerInterface();
|
|
||||||
}
|
|
||||||
|
|
||||||
SpectralGeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
|
|
||||||
return domainObject.type === 'example.spectral-generator';
|
|
||||||
};
|
|
||||||
|
|
||||||
SpectralGeneratorProvider.prototype.supportsRequest =
|
|
||||||
SpectralGeneratorProvider.prototype.supportsSubscribe =
|
|
||||||
SpectralGeneratorProvider.prototype.canProvideTelemetry;
|
|
||||||
|
|
||||||
SpectralGeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request = {}) {
|
|
||||||
var props = [
|
|
||||||
'amplitude',
|
|
||||||
'wavelength',
|
|
||||||
'period',
|
|
||||||
'offset',
|
|
||||||
'dataRateInHz',
|
|
||||||
'phase',
|
|
||||||
'randomness'
|
|
||||||
];
|
|
||||||
|
|
||||||
var workerRequest = {};
|
|
||||||
|
|
||||||
props.forEach(function (prop) {
|
|
||||||
if (domainObject.telemetry && Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)) {
|
|
||||||
workerRequest[prop] = domainObject.telemetry[prop];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
|
|
||||||
workerRequest[prop] = request[prop];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
|
|
||||||
workerRequest[prop] = REQUEST_DEFAULTS[prop];
|
|
||||||
}
|
|
||||||
|
|
||||||
workerRequest[prop] = Number(workerRequest[prop]);
|
|
||||||
});
|
|
||||||
|
|
||||||
workerRequest.name = domainObject.name;
|
|
||||||
|
|
||||||
return workerRequest;
|
|
||||||
};
|
|
||||||
|
|
||||||
SpectralGeneratorProvider.prototype.request = function (domainObject, request) {
|
|
||||||
var workerRequest = this.makeWorkerRequest(domainObject, request);
|
|
||||||
workerRequest.start = request.start;
|
|
||||||
workerRequest.end = request.end;
|
|
||||||
workerRequest.spectra = true;
|
|
||||||
|
|
||||||
return this.workerInterface.request(workerRequest);
|
|
||||||
};
|
|
||||||
|
|
||||||
SpectralGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
|
||||||
var workerRequest = this.makeWorkerRequest(domainObject, {});
|
|
||||||
workerRequest.spectra = true;
|
|
||||||
|
|
||||||
return this.workerInterface.subscribe(workerRequest, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
return SpectralGeneratorProvider;
|
|
||||||
});
|
|
@ -63,7 +63,7 @@ define([
|
|||||||
|
|
||||||
StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
||||||
var start = options.start;
|
var start = options.start;
|
||||||
var end = Math.min(Date.now(), options.end); // no future values
|
var end = options.end;
|
||||||
var duration = domainObject.telemetry.duration * 1000;
|
var duration = domainObject.telemetry.duration * 1000;
|
||||||
if (options.strategy === 'latest' || options.size === 1) {
|
if (options.strategy === 'latest' || options.size === 1) {
|
||||||
start = end;
|
start = end;
|
||||||
|
@ -54,21 +54,8 @@
|
|||||||
var start = Date.now();
|
var start = Date.now();
|
||||||
var step = 1000 / data.dataRateInHz;
|
var step = 1000 / data.dataRateInHz;
|
||||||
var nextStep = start - (start % step) + step;
|
var nextStep = start - (start % step) + step;
|
||||||
let work;
|
|
||||||
if (data.spectra) {
|
|
||||||
work = function (now) {
|
|
||||||
while (nextStep < now) {
|
|
||||||
const messageCopy = Object.create(message);
|
|
||||||
message.data.start = nextStep - (60 * 1000);
|
|
||||||
message.data.end = nextStep;
|
|
||||||
onRequest(messageCopy);
|
|
||||||
nextStep += step;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextStep;
|
function work(now) {
|
||||||
};
|
|
||||||
} else {
|
|
||||||
work = function (now) {
|
|
||||||
while (nextStep < now) {
|
while (nextStep < now) {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
@ -77,7 +64,6 @@
|
|||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
|
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
|
||||||
wavelength: wavelength(start, nextStep),
|
|
||||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
|
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -85,7 +71,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nextStep;
|
return nextStep;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptions[message.id] = work;
|
subscriptions[message.id] = work;
|
||||||
@ -126,21 +111,13 @@
|
|||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||||
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
||||||
wavelength: wavelength(start, nextStep),
|
|
||||||
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
|
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
data: request.spectra ? {
|
data: data
|
||||||
wavelength: data.map((item) => {
|
|
||||||
return item.wavelength;
|
|
||||||
}),
|
|
||||||
cos: data.map((item) => {
|
|
||||||
return item.cos;
|
|
||||||
})
|
|
||||||
} : data
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,10 +131,6 @@
|
|||||||
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function wavelength(start, nextStep) {
|
|
||||||
return (nextStep - start) / 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendError(error, message) {
|
function sendError(error, message) {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
error: error.name + ': ' + error.message,
|
error: error.name + ': ' + error.message,
|
||||||
|
@ -24,15 +24,11 @@ define([
|
|||||||
"./GeneratorProvider",
|
"./GeneratorProvider",
|
||||||
"./SinewaveLimitProvider",
|
"./SinewaveLimitProvider",
|
||||||
"./StateGeneratorProvider",
|
"./StateGeneratorProvider",
|
||||||
"./SpectralGeneratorProvider",
|
|
||||||
"./SpectralAggregateGeneratorProvider",
|
|
||||||
"./GeneratorMetadataProvider"
|
"./GeneratorMetadataProvider"
|
||||||
], function (
|
], function (
|
||||||
GeneratorProvider,
|
GeneratorProvider,
|
||||||
SinewaveLimitProvider,
|
SinewaveLimitProvider,
|
||||||
StateGeneratorProvider,
|
StateGeneratorProvider,
|
||||||
SpectralGeneratorProvider,
|
|
||||||
SpectralAggregateGeneratorProvider,
|
|
||||||
GeneratorMetadataProvider
|
GeneratorMetadataProvider
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -65,37 +61,6 @@ define([
|
|||||||
|
|
||||||
openmct.telemetry.addProvider(new StateGeneratorProvider());
|
openmct.telemetry.addProvider(new StateGeneratorProvider());
|
||||||
|
|
||||||
openmct.types.addType("example.spectral-generator", {
|
|
||||||
name: "Spectral Generator",
|
|
||||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
|
||||||
cssClass: "icon-generator-telemetry",
|
|
||||||
creatable: true,
|
|
||||||
initialize: function (object) {
|
|
||||||
object.telemetry = {
|
|
||||||
period: 10,
|
|
||||||
amplitude: 1,
|
|
||||||
wavelength: 1,
|
|
||||||
frequency: 1,
|
|
||||||
offset: 0,
|
|
||||||
dataRateInHz: 1,
|
|
||||||
phase: 0,
|
|
||||||
randomness: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
openmct.telemetry.addProvider(new SpectralGeneratorProvider());
|
|
||||||
|
|
||||||
openmct.types.addType("example.spectral-aggregate-generator", {
|
|
||||||
name: "Spectral Aggregate Generator",
|
|
||||||
description: "For development use. Generates example streaming telemetry data using a simple state algorithm.",
|
|
||||||
cssClass: "icon-generator-telemetry",
|
|
||||||
creatable: true,
|
|
||||||
initialize: function (object) {
|
|
||||||
object.telemetry = {};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
openmct.telemetry.addProvider(new SpectralAggregateGeneratorProvider());
|
|
||||||
|
|
||||||
openmct.types.addType("generator", {
|
openmct.types.addType("generator", {
|
||||||
name: "Sine Wave Generator",
|
name: "Sine Wave Generator",
|
||||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||||
|
@ -152,7 +152,7 @@
|
|||||||
<h2>How to Use Glyphs</h2>
|
<h2>How to Use Glyphs</h2>
|
||||||
<div class="cols cols1-1">
|
<div class="cols cols1-1">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p>The easiest way to use a glyph is to include its CSS class in an element. The CSS adds a pseudo <code>:before</code> HTML element to whatever element it's attached to that makes proper use of the symbols font.</p>
|
<p>The easiest way to use a glyph is to include its CSS class in an element. The CSS adds a psuedo <code>:before</code> HTML element to whatever element it's attached to that makes proper use of the symbols font.</p>
|
||||||
<p>Alternately, you can use the <code>.ui-symbol</code> class in an object that contains encoded HTML entities. This method is only recommended if you cannot use the aforementioned CSS class approach.</p>
|
<p>Alternately, you can use the <code>.ui-symbol</code> class in an object that contains encoded HTML entities. This method is only recommended if you cannot use the aforementioned CSS class approach.</p>
|
||||||
</div>
|
</div>
|
||||||
<mct-example><a class="s-button icon-gear" title="Settings"></a>
|
<mct-example><a class="s-button icon-gear" title="Settings"></a>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
const devMode = process.env.NODE_ENV !== 'production';
|
const devMode = process.env.NODE_ENV !== 'production';
|
||||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
|
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
|
||||||
const coverageEnabled = process.env.COVERAGE === 'true';
|
const coverageEnabled = process.env.COVERAGE === 'true';
|
||||||
const reporters = ['spec', 'html', 'junit'];
|
const reporters = ['progress', 'html', 'junit'];
|
||||||
|
|
||||||
if (coverageEnabled) {
|
if (coverageEnabled) {
|
||||||
reporters.push('coverage-istanbul');
|
reporters.push('coverage-istanbul');
|
||||||
@ -60,7 +60,7 @@ module.exports = (config) => {
|
|||||||
client: {
|
client: {
|
||||||
jasmine: {
|
jasmine: {
|
||||||
random: false,
|
random: false,
|
||||||
timeoutInterval: 5000
|
timeoutInterval: 30000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
customLaunchers: {
|
customLaunchers: {
|
||||||
@ -88,6 +88,11 @@ module.exports = (config) => {
|
|||||||
outputFile: "test-results.xml",
|
outputFile: "test-results.xml",
|
||||||
useBrowserName: false
|
useBrowserName: false
|
||||||
},
|
},
|
||||||
|
browserConsoleLogOptions: {
|
||||||
|
level: "error",
|
||||||
|
format: "%b %T: %m",
|
||||||
|
terminal: true
|
||||||
|
},
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
fixWebpackSourcePaths: true,
|
fixWebpackSourcePaths: true,
|
||||||
dir: process.env.CIRCLE_ARTIFACTS
|
dir: process.env.CIRCLE_ARTIFACTS
|
||||||
@ -100,15 +105,6 @@ module.exports = (config) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
specReporter: {
|
|
||||||
maxLogLines: 5,
|
|
||||||
suppressErrorSummary: true,
|
|
||||||
suppressFailed: false,
|
|
||||||
suppressPassed: false,
|
|
||||||
suppressSkipped: true,
|
|
||||||
showSpecTiming: true,
|
|
||||||
failFast: false
|
|
||||||
},
|
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
'indexTest.js': ['webpack', 'sourcemap']
|
'indexTest.js': ['webpack', 'sourcemap']
|
||||||
},
|
},
|
||||||
|
18
package.json
18
package.json
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.7.8-SNAPSHOT",
|
"version": "1.7.6-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"angular": ">=1.8.0",
|
"angular": ">=1.8.0",
|
||||||
"angular-route": "1.4.14",
|
"angular-route": "1.4.14",
|
||||||
@ -11,9 +12,16 @@
|
|||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
"cross-env": "^6.0.3",
|
"cross-env": "^6.0.3",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
|
"d3-array": "1.2.x",
|
||||||
"d3-axis": "1.0.x",
|
"d3-axis": "1.0.x",
|
||||||
|
"d3-collection": "1.0.x",
|
||||||
|
"d3-color": "1.0.x",
|
||||||
|
"d3-format": "1.2.x",
|
||||||
|
"d3-interpolate": "1.1.x",
|
||||||
"d3-scale": "1.0.x",
|
"d3-scale": "1.0.x",
|
||||||
"d3-selection": "1.3.x",
|
"d3-selection": "1.3.x",
|
||||||
|
"d3-time": "1.0.x",
|
||||||
|
"d3-time-format": "2.1.x",
|
||||||
"eslint": "7.0.0",
|
"eslint": "7.0.0",
|
||||||
"eslint-plugin-vue": "^7.5.0",
|
"eslint-plugin-vue": "^7.5.0",
|
||||||
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
|
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
|
||||||
@ -33,15 +41,14 @@
|
|||||||
"jsdoc": "^3.3.2",
|
"jsdoc": "^3.3.2",
|
||||||
"karma": "6.3.4",
|
"karma": "6.3.4",
|
||||||
"karma-chrome-launcher": "3.1.0",
|
"karma-chrome-launcher": "3.1.0",
|
||||||
|
"karma-firefox-launcher": "2.1.1",
|
||||||
"karma-cli": "2.0.0",
|
"karma-cli": "2.0.0",
|
||||||
"karma-coverage": "2.0.3",
|
"karma-coverage": "2.0.3",
|
||||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||||
"karma-firefox-launcher": "2.1.1",
|
"karma-junit-reporter": "2.0.1",
|
||||||
"karma-html-reporter": "0.2.7",
|
"karma-html-reporter": "0.2.7",
|
||||||
"karma-jasmine": "4.0.1",
|
"karma-jasmine": "4.0.1",
|
||||||
"karma-junit-reporter": "2.0.1",
|
|
||||||
"karma-sourcemap-loader": "0.3.8",
|
"karma-sourcemap-loader": "0.3.8",
|
||||||
"karma-spec-reporter": "0.0.32",
|
|
||||||
"karma-webpack": "4.0.2",
|
"karma-webpack": "4.0.2",
|
||||||
"location-bar": "^3.0.1",
|
"location-bar": "^3.0.1",
|
||||||
"lodash": "^4.17.12",
|
"lodash": "^4.17.12",
|
||||||
@ -55,8 +62,6 @@
|
|||||||
"node-bourbon": "^4.2.3",
|
"node-bourbon": "^4.2.3",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"painterro": "^1.2.56",
|
"painterro": "^1.2.56",
|
||||||
"plotly.js-basic-dist": "^2.5.0",
|
|
||||||
"plotly.js-gl2d-dist": "^2.5.0",
|
|
||||||
"printj": "^1.2.1",
|
"printj": "^1.2.1",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"request": "^2.69.0",
|
"request": "^2.69.0",
|
||||||
@ -65,7 +70,6 @@
|
|||||||
"uuid": "^3.3.3",
|
"uuid": "^3.3.3",
|
||||||
"v8-compile-cache": "^1.1.0",
|
"v8-compile-cache": "^1.1.0",
|
||||||
"vue": "2.5.6",
|
"vue": "2.5.6",
|
||||||
"vue-eslint-parser": "7.11.0",
|
|
||||||
"vue-loader": "^15.2.6",
|
"vue-loader": "^15.2.6",
|
||||||
"vue-template-compiler": "2.5.6",
|
"vue-template-compiler": "2.5.6",
|
||||||
"webpack": "^4.16.2",
|
"webpack": "^4.16.2",
|
||||||
|
@ -64,7 +64,7 @@ define(
|
|||||||
*
|
*
|
||||||
* @param {DomainObject} domainObject the domain object to navigate to
|
* @param {DomainObject} domainObject the domain object to navigate to
|
||||||
* @param {Boolean} force if true, force navigation to occur.
|
* @param {Boolean} force if true, force navigation to occur.
|
||||||
* @returns {Boolean} true if navigation occurred, otherwise false.
|
* @returns {Boolean} true if navigation occured, otherwise false.
|
||||||
*/
|
*/
|
||||||
NavigationService.prototype.setNavigation = function (domainObject, force) {
|
NavigationService.prototype.setNavigation = function (domainObject, force) {
|
||||||
if (force) {
|
if (force) {
|
||||||
|
@ -50,6 +50,8 @@ define(
|
|||||||
* or finish() are called.
|
* or finish() are called.
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.edit = function () {
|
EditorCapability.prototype.edit = function () {
|
||||||
|
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
|
||||||
|
|
||||||
if (!this.openmct.editor.isEditing()) {
|
if (!this.openmct.editor.isEditing()) {
|
||||||
this.openmct.editor.edit();
|
this.openmct.editor.edit();
|
||||||
this.domainObject.getCapability('status').set('editing', true);
|
this.domainObject.getCapability('status').set('editing', true);
|
||||||
@ -80,6 +82,8 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.save = function () {
|
EditorCapability.prototype.save = function () {
|
||||||
|
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -91,6 +95,8 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.finish = function () {
|
EditorCapability.prototype.finish = function () {
|
||||||
|
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,8 +25,9 @@ define([
|
|||||||
], function (
|
], function (
|
||||||
moment
|
moment
|
||||||
) {
|
) {
|
||||||
const DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS";
|
|
||||||
const DATE_FORMATS = [
|
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
||||||
|
DATE_FORMATS = [
|
||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
DATE_FORMAT + "Z",
|
DATE_FORMAT + "Z",
|
||||||
"YYYY-MM-DD HH:mm:ss",
|
"YYYY-MM-DD HH:mm:ss",
|
||||||
@ -52,27 +53,15 @@ define([
|
|||||||
this.key = "utc";
|
this.key = "utc";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} formatString
|
|
||||||
* @returns the value of formatString if the value is a string type and exists in the DATE_FORMATS array; otherwise the DATE_FORMAT value.
|
|
||||||
*/
|
|
||||||
function validateFormatString(formatString) {
|
|
||||||
return typeof formatString === 'string' && DATE_FORMATS.includes(formatString) ? formatString : DATE_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} value The value to format.
|
* @param {number} value The value to format.
|
||||||
* @param {string} formatString The string format to format. Default "YYYY-MM-DD HH:mm:ss.SSS" + "Z"
|
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||||
* @returns {string} the formatted date(s) according to the proper parameter of formatString or the default value of "YYYY-MM-DD HH:mm:ss.SSS" + "Z".
|
|
||||||
* If multiple values were requested, then an array of
|
|
||||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||||
* in the array.
|
* in the array.
|
||||||
*/
|
*/
|
||||||
UTCTimeFormat.prototype.format = function (value, formatString) {
|
UTCTimeFormat.prototype.format = function (value) {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
const format = validateFormatString(formatString);
|
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||||
|
|
||||||
return moment.utc(value).format(format) + (formatString ? '' : 'Z');
|
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,28 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
define([
|
||||||
"./src/AgentService"
|
"./src/MCTDevice",
|
||||||
|
"./src/AgentService",
|
||||||
|
"./src/DeviceClassifier"
|
||||||
], function (
|
], function (
|
||||||
AgentService
|
MCTDevice,
|
||||||
|
AgentService,
|
||||||
|
DeviceClassifier
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "platform/commonUI/mobile",
|
name: "platform/commonUI/mobile",
|
||||||
definition: {
|
definition: {
|
||||||
"extensions": {
|
"extensions": {
|
||||||
|
"directives": [
|
||||||
|
{
|
||||||
|
"key": "mctDevice",
|
||||||
|
"implementation": MCTDevice,
|
||||||
|
"depends": [
|
||||||
|
"agentService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"key": "agentService",
|
"key": "agentService",
|
||||||
@ -37,6 +51,15 @@ define([
|
|||||||
"$window"
|
"$window"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"implementation": DeviceClassifier,
|
||||||
|
"depends": [
|
||||||
|
"agentService",
|
||||||
|
"$document"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,122 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(["../../../../src/utils/agent/Agent.js"], function (Agent) {
|
/**
|
||||||
function AngularAgentServiceWrapper(window) {
|
* Provides features which support variant behavior on mobile devices.
|
||||||
const AS = Agent.default;
|
*
|
||||||
|
* @namespace platform/commonUI/mobile
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
return new AS(window);
|
/**
|
||||||
|
* The query service handles calls for browser and userAgent
|
||||||
|
* info using a comparison between the userAgent and key
|
||||||
|
* device names
|
||||||
|
* @constructor
|
||||||
|
* @param $window Angular-injected instance of the window
|
||||||
|
* @memberof platform/commonUI/mobile
|
||||||
|
*/
|
||||||
|
function AgentService($window) {
|
||||||
|
var userAgent = $window.navigator.userAgent,
|
||||||
|
matches = userAgent.match(/iPad|iPhone|Android/i) || [];
|
||||||
|
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
this.mobileName = matches[0];
|
||||||
|
this.$window = $window;
|
||||||
|
this.touchEnabled = ($window.ontouchstart !== undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AngularAgentServiceWrapper;
|
/**
|
||||||
});
|
* Check if the user is on a mobile device.
|
||||||
|
* @returns {boolean} true on mobile
|
||||||
|
*/
|
||||||
|
AgentService.prototype.isMobile = function () {
|
||||||
|
return Boolean(this.mobileName);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user is on a phone-sized mobile device.
|
||||||
|
* @returns {boolean} true on a phone
|
||||||
|
*/
|
||||||
|
AgentService.prototype.isPhone = function () {
|
||||||
|
if (this.isMobile()) {
|
||||||
|
if (this.isAndroidTablet()) {
|
||||||
|
return false;
|
||||||
|
} else if (this.mobileName === 'iPad') {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user is on a tablet sized android device
|
||||||
|
* @returns {boolean} true on an android tablet
|
||||||
|
*/
|
||||||
|
AgentService.prototype.isAndroidTablet = function () {
|
||||||
|
if (this.mobileName === 'Android') {
|
||||||
|
if (this.isPortrait() && window.innerWidth >= 768) {
|
||||||
|
return true;
|
||||||
|
} else if (this.isLandscape() && window.innerHeight >= 768) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user is on a tablet-sized mobile device.
|
||||||
|
* @returns {boolean} true on a tablet
|
||||||
|
*/
|
||||||
|
AgentService.prototype.isTablet = function () {
|
||||||
|
return (this.isMobile() && !this.isPhone() && this.mobileName !== 'Android') || (this.isMobile() && this.isAndroidTablet());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user's device is in a portrait-style
|
||||||
|
* orientation (display width is narrower than display height.)
|
||||||
|
* @returns {boolean} true in portrait mode
|
||||||
|
*/
|
||||||
|
AgentService.prototype.isPortrait = function () {
|
||||||
|
return this.$window.innerWidth < this.$window.innerHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user's device is in a landscape-style
|
||||||
|
* orientation (display width is greater than display height.)
|
||||||
|
* @returns {boolean} true in landscape mode
|
||||||
|
*/
|
||||||
|
AgentService.prototype.isLandscape = function () {
|
||||||
|
return !this.isPortrait();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user's device supports a touch interface.
|
||||||
|
* @returns {boolean} true if touch is supported
|
||||||
|
*/
|
||||||
|
AgentService.prototype.isTouch = function () {
|
||||||
|
return this.touchEnabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user agent matches a certain named device,
|
||||||
|
* as indicated by checking for a case-insensitive substring
|
||||||
|
* match.
|
||||||
|
* @param {string} name the name to check for
|
||||||
|
* @returns {boolean} true if the user agent includes that name
|
||||||
|
*/
|
||||||
|
AgentService.prototype.isBrowser = function (name) {
|
||||||
|
name = name.toLowerCase();
|
||||||
|
|
||||||
|
return this.userAgent.toLowerCase().indexOf(name) !== -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
return AgentService;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -1,96 +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 AgentService from "./AgentService";
|
|
||||||
|
|
||||||
const TEST_USER_AGENTS = {
|
|
||||||
DESKTOP:
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36",
|
|
||||||
IPAD:
|
|
||||||
"Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53",
|
|
||||||
IPHONE:
|
|
||||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("The AgentService", function () {
|
|
||||||
let testWindow;
|
|
||||||
let agentService;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
testWindow = {
|
|
||||||
innerWidth: 640,
|
|
||||||
innerHeight: 480,
|
|
||||||
navigator: {
|
|
||||||
userAgent: TEST_USER_AGENTS.DESKTOP
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("recognizes desktop devices as non-mobile", function () {
|
|
||||||
testWindow.navigator.userAgent = TEST_USER_AGENTS.DESKTOP;
|
|
||||||
agentService = new AgentService(testWindow);
|
|
||||||
expect(agentService.isMobile()).toBeFalsy();
|
|
||||||
expect(agentService.isPhone()).toBeFalsy();
|
|
||||||
expect(agentService.isTablet()).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("detects iPhones", function () {
|
|
||||||
testWindow.navigator.userAgent = TEST_USER_AGENTS.IPHONE;
|
|
||||||
agentService = new AgentService(testWindow);
|
|
||||||
expect(agentService.isMobile()).toBeTruthy();
|
|
||||||
expect(agentService.isPhone()).toBeTruthy();
|
|
||||||
expect(agentService.isTablet()).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("detects iPads", function () {
|
|
||||||
testWindow.navigator.userAgent = TEST_USER_AGENTS.IPAD;
|
|
||||||
agentService = new AgentService(testWindow);
|
|
||||||
expect(agentService.isMobile()).toBeTruthy();
|
|
||||||
expect(agentService.isPhone()).toBeFalsy();
|
|
||||||
expect(agentService.isTablet()).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("detects display orientation", function () {
|
|
||||||
agentService = new AgentService(testWindow);
|
|
||||||
testWindow.innerWidth = 1024;
|
|
||||||
testWindow.innerHeight = 400;
|
|
||||||
expect(agentService.isPortrait()).toBeFalsy();
|
|
||||||
expect(agentService.isLandscape()).toBeTruthy();
|
|
||||||
testWindow.innerWidth = 400;
|
|
||||||
testWindow.innerHeight = 1024;
|
|
||||||
expect(agentService.isPortrait()).toBeTruthy();
|
|
||||||
expect(agentService.isLandscape()).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("detects touch support", function () {
|
|
||||||
testWindow.ontouchstart = null;
|
|
||||||
expect(new AgentService(testWindow).isTouch()).toBe(true);
|
|
||||||
delete testWindow.ontouchstart;
|
|
||||||
expect(new AgentService(testWindow).isTouch()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows for checking browser type", function () {
|
|
||||||
testWindow.navigator.userAgent = "Chromezilla Safarifox";
|
|
||||||
agentService = new AgentService(testWindow);
|
|
||||||
expect(agentService.isBrowser("Chrome")).toBe(true);
|
|
||||||
expect(agentService.isBrowser("Firefox")).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
72
platform/commonUI/mobile/src/DeviceClassifier.js
Normal file
72
platform/commonUI/mobile/src/DeviceClassifier.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
['./DeviceMatchers'],
|
||||||
|
function (DeviceMatchers) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs at application startup and adds a subset of the following
|
||||||
|
* CSS classes to the body of the document, depending on device
|
||||||
|
* attributes:
|
||||||
|
*
|
||||||
|
* * `mobile`: Phones or tablets.
|
||||||
|
* * `phone`: Phones specifically.
|
||||||
|
* * `tablet`: Tablets specifically.
|
||||||
|
* * `desktop`: Non-mobile devices.
|
||||||
|
* * `portrait`: Devices in a portrait-style orientation.
|
||||||
|
* * `landscape`: Devices in a landscape-style orientation.
|
||||||
|
* * `touch`: Device supports touch events.
|
||||||
|
*
|
||||||
|
* @param {platform/commonUI/mobile.AgentService} agentService
|
||||||
|
* the service used to examine the user agent
|
||||||
|
* @param $document Angular's jqLite-wrapped document element
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function MobileClassifier(agentService, $document) {
|
||||||
|
var body = $document.find('body');
|
||||||
|
|
||||||
|
Object.keys(DeviceMatchers).forEach(function (key, index, array) {
|
||||||
|
if (DeviceMatchers[key](agentService)) {
|
||||||
|
body.addClass(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (agentService.isMobile()) {
|
||||||
|
var mediaQuery = window.matchMedia('(orientation: landscape)');
|
||||||
|
|
||||||
|
mediaQuery.addListener(function (event) {
|
||||||
|
if (event.matches) {
|
||||||
|
body.removeClass('portrait');
|
||||||
|
body.addClass('landscape');
|
||||||
|
} else {
|
||||||
|
body.removeClass('landscape');
|
||||||
|
body.addClass('portrait');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MobileClassifier;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
@ -19,35 +19,40 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
define(function () {
|
||||||
|
|
||||||
import { BAR_GRAPH_KEY } from './BarGraphConstants';
|
/**
|
||||||
|
* An object containing key-value pairs, where keys are symbolic of
|
||||||
export default function BarGraphCompositionPolicy(openmct) {
|
* device attributes, and values are functions that take the
|
||||||
function hasAggregateDomainAndRange(metadata) {
|
* `agentService` as inputs and return boolean values indicating
|
||||||
const rangeValues = metadata.valuesForHints(['range']);
|
* whether or not the current device has these attributes.
|
||||||
|
*
|
||||||
return rangeValues.length > 0;
|
* For internal use by the mobile support bundle.
|
||||||
}
|
*
|
||||||
|
* @memberof platform/commonUI/mobile
|
||||||
function hasBarGraphTelemetry(domainObject) {
|
* @private
|
||||||
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
|
*/
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
|
||||||
|
|
||||||
return metadata.values().length > 0 && hasAggregateDomainAndRange(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allow: function (parent, child) {
|
mobile: function (agentService) {
|
||||||
if ((parent.type === BAR_GRAPH_KEY)
|
return agentService.isMobile();
|
||||||
&& ((child.type !== 'telemetry.plot.overlay') && (hasBarGraphTelemetry(child) === false))
|
},
|
||||||
) {
|
phone: function (agentService) {
|
||||||
return false;
|
return agentService.isPhone();
|
||||||
}
|
},
|
||||||
|
tablet: function (agentService) {
|
||||||
return true;
|
return agentService.isTablet();
|
||||||
|
},
|
||||||
|
desktop: function (agentService) {
|
||||||
|
return !agentService.isMobile();
|
||||||
|
},
|
||||||
|
portrait: function (agentService) {
|
||||||
|
return agentService.isPortrait();
|
||||||
|
},
|
||||||
|
landscape: function (agentService) {
|
||||||
|
return agentService.isLandscape();
|
||||||
|
},
|
||||||
|
touch: function (agentService) {
|
||||||
|
return agentService.isTouch();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
});
|
88
platform/commonUI/mobile/src/MCTDevice.js
Normal file
88
platform/commonUI/mobile/src/MCTDevice.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
['./DeviceMatchers'],
|
||||||
|
function (DeviceMatchers) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `mct-device` directive, when applied as an attribute,
|
||||||
|
* only includes the element when the device being used matches
|
||||||
|
* a set of characteristics required.
|
||||||
|
*
|
||||||
|
* Required characteristics are given as space-separated strings
|
||||||
|
* as the value to this attribute, e.g.:
|
||||||
|
*
|
||||||
|
* <span mct-device="mobile portrait">Hello world!</span>
|
||||||
|
*
|
||||||
|
* ...will only show Hello world! when viewed on a mobile device
|
||||||
|
* in the portrait orientation.
|
||||||
|
*
|
||||||
|
* Valid device characteristics to detect are:
|
||||||
|
*
|
||||||
|
* * `mobile`: Phones or tablets.
|
||||||
|
* * `phone`: Phones specifically.
|
||||||
|
* * `tablet`: Tablets specifically.
|
||||||
|
* * `desktop`: Non-mobile devices.
|
||||||
|
* * `portrait`: Devices in a portrait-style orientation.
|
||||||
|
* * `landscape`: Devices in a landscape-style orientation.
|
||||||
|
* * `touch`: Device supports touch events.
|
||||||
|
*
|
||||||
|
* @param {AgentService} agentService used to detect device type
|
||||||
|
* based on information about the user agent
|
||||||
|
*/
|
||||||
|
function MCTDevice(agentService) {
|
||||||
|
|
||||||
|
function deviceMatches(tokens) {
|
||||||
|
tokens = tokens || "";
|
||||||
|
|
||||||
|
return tokens.split(" ").every(function (token) {
|
||||||
|
var fn = DeviceMatchers[token];
|
||||||
|
|
||||||
|
return fn && fn(agentService);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function link(scope, element, attrs, ctrl, transclude) {
|
||||||
|
if (deviceMatches(attrs.mctDevice)) {
|
||||||
|
transclude(function (clone) {
|
||||||
|
element.replaceWith(clone);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link,
|
||||||
|
// We are transcluding the whole element (like ng-if)
|
||||||
|
transclude: 'element',
|
||||||
|
// 1 more than ng-if
|
||||||
|
priority: 601,
|
||||||
|
// Also terminal, since element will be transcluded
|
||||||
|
terminal: true,
|
||||||
|
// Only apply as an attribute
|
||||||
|
restrict: "A"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTDevice;
|
||||||
|
}
|
||||||
|
);
|
99
platform/commonUI/mobile/test/AgentServiceSpec.js
Normal file
99
platform/commonUI/mobile/test/AgentServiceSpec.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
["../src/AgentService"],
|
||||||
|
function (AgentService) {
|
||||||
|
|
||||||
|
var TEST_USER_AGENTS = {
|
||||||
|
DESKTOP: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36",
|
||||||
|
IPAD: "Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53",
|
||||||
|
IPHONE: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("The AgentService", function () {
|
||||||
|
var testWindow, agentService;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testWindow = {
|
||||||
|
innerWidth: 640,
|
||||||
|
innerHeight: 480,
|
||||||
|
navigator: {
|
||||||
|
userAgent: TEST_USER_AGENTS.DESKTOP
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("recognizes desktop devices as non-mobile", function () {
|
||||||
|
testWindow.navigator.userAgent = TEST_USER_AGENTS.DESKTOP;
|
||||||
|
agentService = new AgentService(testWindow);
|
||||||
|
expect(agentService.isMobile()).toBeFalsy();
|
||||||
|
expect(agentService.isPhone()).toBeFalsy();
|
||||||
|
expect(agentService.isTablet()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects iPhones", function () {
|
||||||
|
testWindow.navigator.userAgent = TEST_USER_AGENTS.IPHONE;
|
||||||
|
agentService = new AgentService(testWindow);
|
||||||
|
expect(agentService.isMobile()).toBeTruthy();
|
||||||
|
expect(agentService.isPhone()).toBeTruthy();
|
||||||
|
expect(agentService.isTablet()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects iPads", function () {
|
||||||
|
testWindow.navigator.userAgent = TEST_USER_AGENTS.IPAD;
|
||||||
|
agentService = new AgentService(testWindow);
|
||||||
|
expect(agentService.isMobile()).toBeTruthy();
|
||||||
|
expect(agentService.isPhone()).toBeFalsy();
|
||||||
|
expect(agentService.isTablet()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects display orientation", function () {
|
||||||
|
agentService = new AgentService(testWindow);
|
||||||
|
testWindow.innerWidth = 1024;
|
||||||
|
testWindow.innerHeight = 400;
|
||||||
|
expect(agentService.isPortrait()).toBeFalsy();
|
||||||
|
expect(agentService.isLandscape()).toBeTruthy();
|
||||||
|
testWindow.innerWidth = 400;
|
||||||
|
testWindow.innerHeight = 1024;
|
||||||
|
expect(agentService.isPortrait()).toBeTruthy();
|
||||||
|
expect(agentService.isLandscape()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects touch support", function () {
|
||||||
|
testWindow.ontouchstart = null;
|
||||||
|
expect(new AgentService(testWindow).isTouch())
|
||||||
|
.toBe(true);
|
||||||
|
delete testWindow.ontouchstart;
|
||||||
|
expect(new AgentService(testWindow).isTouch())
|
||||||
|
.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows for checking browser type", function () {
|
||||||
|
testWindow.navigator.userAgent = "Chromezilla Safarifox";
|
||||||
|
agentService = new AgentService(testWindow);
|
||||||
|
expect(agentService.isBrowser("Chrome")).toBe(true);
|
||||||
|
expect(agentService.isBrowser("Firefox")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
109
platform/commonUI/mobile/test/DeviceClassifierSpec.js
Normal file
109
platform/commonUI/mobile/test/DeviceClassifierSpec.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
["../src/DeviceClassifier", "../src/DeviceMatchers"],
|
||||||
|
function (DeviceClassifier, DeviceMatchers) {
|
||||||
|
|
||||||
|
var AGENT_SERVICE_METHODS = [
|
||||||
|
'isMobile',
|
||||||
|
'isPhone',
|
||||||
|
'isTablet',
|
||||||
|
'isPortrait',
|
||||||
|
'isLandscape',
|
||||||
|
'isTouch'
|
||||||
|
],
|
||||||
|
TEST_PERMUTATIONS = [
|
||||||
|
['isMobile', 'isPhone', 'isTouch', 'isPortrait'],
|
||||||
|
['isMobile', 'isPhone', 'isTouch', 'isLandscape'],
|
||||||
|
['isMobile', 'isTablet', 'isTouch', 'isPortrait'],
|
||||||
|
['isMobile', 'isTablet', 'isTouch', 'isLandscape'],
|
||||||
|
['isTouch'],
|
||||||
|
[]
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("DeviceClassifier", function () {
|
||||||
|
var mockAgentService,
|
||||||
|
mockDocument,
|
||||||
|
mockBody;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockAgentService = jasmine.createSpyObj(
|
||||||
|
'agentService',
|
||||||
|
AGENT_SERVICE_METHODS
|
||||||
|
);
|
||||||
|
mockDocument = jasmine.createSpyObj(
|
||||||
|
'$document',
|
||||||
|
['find']
|
||||||
|
);
|
||||||
|
mockBody = jasmine.createSpyObj(
|
||||||
|
'body',
|
||||||
|
['addClass']
|
||||||
|
);
|
||||||
|
mockDocument.find.and.callFake(function (sel) {
|
||||||
|
return sel === 'body' && mockBody;
|
||||||
|
});
|
||||||
|
AGENT_SERVICE_METHODS.forEach(function (m) {
|
||||||
|
mockAgentService[m].and.returnValue(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
TEST_PERMUTATIONS.forEach(function (trueMethods) {
|
||||||
|
var summary = trueMethods.length === 0
|
||||||
|
? "device has no detected characteristics"
|
||||||
|
: "device " + (trueMethods.join(", "));
|
||||||
|
|
||||||
|
describe("when " + summary, function () {
|
||||||
|
var classifier; // eslint-disable-line
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
trueMethods.forEach(function (m) {
|
||||||
|
mockAgentService[m].and.returnValue(true);
|
||||||
|
});
|
||||||
|
classifier = new DeviceClassifier(
|
||||||
|
mockAgentService,
|
||||||
|
mockDocument
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds classes for matching, detected characteristics", function () {
|
||||||
|
Object.keys(DeviceMatchers).filter(function (m) {
|
||||||
|
return DeviceMatchers[m](mockAgentService);
|
||||||
|
}).forEach(function (key) {
|
||||||
|
expect(mockBody.addClass)
|
||||||
|
.toHaveBeenCalledWith(key);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not add classes for non-matching characteristics", function () {
|
||||||
|
Object.keys(DeviceMatchers).filter(function (m) {
|
||||||
|
return !DeviceMatchers[m](mockAgentService);
|
||||||
|
}).forEach(function (key) {
|
||||||
|
expect(mockBody.addClass)
|
||||||
|
.not.toHaveBeenCalledWith(key);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
78
platform/commonUI/mobile/test/DeviceMatchersSpec.js
Normal file
78
platform/commonUI/mobile/test/DeviceMatchersSpec.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
["../src/DeviceMatchers"],
|
||||||
|
function (DeviceMatchers) {
|
||||||
|
|
||||||
|
describe("DeviceMatchers", function () {
|
||||||
|
var mockAgentService;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockAgentService = jasmine.createSpyObj(
|
||||||
|
'agentService',
|
||||||
|
[
|
||||||
|
'isMobile',
|
||||||
|
'isPhone',
|
||||||
|
'isTablet',
|
||||||
|
'isPortrait',
|
||||||
|
'isLandscape',
|
||||||
|
'isTouch'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects when a device is a desktop device", function () {
|
||||||
|
mockAgentService.isMobile.and.returnValue(false);
|
||||||
|
expect(DeviceMatchers.desktop(mockAgentService))
|
||||||
|
.toBe(true);
|
||||||
|
mockAgentService.isMobile.and.returnValue(true);
|
||||||
|
expect(DeviceMatchers.desktop(mockAgentService))
|
||||||
|
.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
function method(deviceType) {
|
||||||
|
return "is" + deviceType[0].toUpperCase() + deviceType.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[
|
||||||
|
"mobile",
|
||||||
|
"phone",
|
||||||
|
"tablet",
|
||||||
|
"landscape",
|
||||||
|
"portrait",
|
||||||
|
"landscape",
|
||||||
|
"touch"
|
||||||
|
].forEach(function (deviceType) {
|
||||||
|
it("detects when a device is a " + deviceType + " device", function () {
|
||||||
|
mockAgentService[method(deviceType)].and.returnValue(true);
|
||||||
|
expect(DeviceMatchers[deviceType](mockAgentService))
|
||||||
|
.toBe(true);
|
||||||
|
mockAgentService[method(deviceType)].and.returnValue(false);
|
||||||
|
expect(DeviceMatchers[deviceType](mockAgentService))
|
||||||
|
.toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
168
platform/commonUI/mobile/test/MCTDeviceSpec.js
Normal file
168
platform/commonUI/mobile/test/MCTDeviceSpec.js
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
['../src/MCTDevice'],
|
||||||
|
function (MCTDevice) {
|
||||||
|
|
||||||
|
var JQLITE_METHODS = ['replaceWith'];
|
||||||
|
|
||||||
|
describe("The mct-device directive", function () {
|
||||||
|
var mockAgentService,
|
||||||
|
mockTransclude,
|
||||||
|
mockElement,
|
||||||
|
mockClone,
|
||||||
|
testAttrs,
|
||||||
|
directive;
|
||||||
|
|
||||||
|
function link() {
|
||||||
|
directive.link(null, mockElement, testAttrs, null, mockTransclude);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockAgentService = jasmine.createSpyObj(
|
||||||
|
"agentService",
|
||||||
|
["isMobile", "isPhone", "isTablet", "isPortrait", "isLandscape"]
|
||||||
|
);
|
||||||
|
mockTransclude = jasmine.createSpy("$transclude");
|
||||||
|
mockElement = jasmine.createSpyObj(name, JQLITE_METHODS);
|
||||||
|
mockClone = jasmine.createSpyObj(name, JQLITE_METHODS);
|
||||||
|
|
||||||
|
mockTransclude.and.callFake(function (fn) {
|
||||||
|
fn(mockClone);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Look desktop-like by default
|
||||||
|
mockAgentService.isLandscape.and.returnValue(true);
|
||||||
|
|
||||||
|
testAttrs = {};
|
||||||
|
|
||||||
|
directive = new MCTDevice(mockAgentService);
|
||||||
|
});
|
||||||
|
|
||||||
|
function expectInclusion() {
|
||||||
|
expect(mockElement.replaceWith)
|
||||||
|
.toHaveBeenCalledWith(mockClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectExclusion() {
|
||||||
|
expect(mockElement.replaceWith).not.toHaveBeenCalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
it("is applicable at the attribute level", function () {
|
||||||
|
expect(directive.restrict).toEqual("A");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("transcludes at the element level", function () {
|
||||||
|
expect(directive.transclude).toEqual('element');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has a greater priority number than ng-if", function () {
|
||||||
|
expect(directive.priority > 600).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restricts element inclusion for mobile devices", function () {
|
||||||
|
testAttrs.mctDevice = "mobile";
|
||||||
|
link();
|
||||||
|
expectExclusion();
|
||||||
|
|
||||||
|
mockAgentService.isMobile.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectInclusion();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restricts element inclusion for tablet devices", function () {
|
||||||
|
testAttrs.mctDevice = "tablet";
|
||||||
|
mockAgentService.isMobile.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectExclusion();
|
||||||
|
|
||||||
|
mockAgentService.isTablet.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectInclusion();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restricts element inclusion for phone devices", function () {
|
||||||
|
testAttrs.mctDevice = "phone";
|
||||||
|
mockAgentService.isMobile.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectExclusion();
|
||||||
|
|
||||||
|
mockAgentService.isPhone.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectInclusion();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restricts element inclusion for desktop devices", function () {
|
||||||
|
testAttrs.mctDevice = "desktop";
|
||||||
|
mockAgentService.isMobile.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectExclusion();
|
||||||
|
|
||||||
|
mockAgentService.isMobile.and.returnValue(false);
|
||||||
|
link();
|
||||||
|
expectInclusion();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restricts element inclusion for portrait orientation", function () {
|
||||||
|
testAttrs.mctDevice = "portrait";
|
||||||
|
link();
|
||||||
|
expectExclusion();
|
||||||
|
|
||||||
|
mockAgentService.isPortrait.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectInclusion();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restricts element inclusion for landscape orientation", function () {
|
||||||
|
testAttrs.mctDevice = "landscape";
|
||||||
|
mockAgentService.isLandscape.and.returnValue(false);
|
||||||
|
mockAgentService.isPortrait.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectExclusion();
|
||||||
|
|
||||||
|
mockAgentService.isLandscape.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectInclusion();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows multiple device characteristics to be requested", function () {
|
||||||
|
// Won't try to test every permutation here, just
|
||||||
|
// make sure the multi-characteristic feature has support.
|
||||||
|
testAttrs.mctDevice = "portrait mobile";
|
||||||
|
link();
|
||||||
|
// Neither portrait nor mobile, not called
|
||||||
|
expectExclusion();
|
||||||
|
|
||||||
|
mockAgentService.isPortrait.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
|
||||||
|
// Was portrait, but not mobile, so no
|
||||||
|
expectExclusion();
|
||||||
|
|
||||||
|
mockAgentService.isMobile.and.returnValue(true);
|
||||||
|
link();
|
||||||
|
expectInclusion();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -379,7 +379,7 @@ define([
|
|||||||
{
|
{
|
||||||
"name": "Math.uuid.js",
|
"name": "Math.uuid.js",
|
||||||
"version": "1.4.7",
|
"version": "1.4.7",
|
||||||
"description": "Unique identifier generation (code adapted.)",
|
"description": "Unique identifer generation (code adapted.)",
|
||||||
"author": "Robert Kieffer",
|
"author": "Robert Kieffer",
|
||||||
"website": "https://github.com/broofa/node-uuid",
|
"website": "https://github.com/broofa/node-uuid",
|
||||||
"copyright": "Copyright (c) 2010-2012 Robert Kieffer",
|
"copyright": "Copyright (c) 2010-2012 Robert Kieffer",
|
||||||
|
@ -30,8 +30,8 @@ define([
|
|||||||
|
|
||||||
return function ImportExportPlugin() {
|
return function ImportExportPlugin() {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
ExportAsJSONAction.prototype.appliesTo = function (context) {
|
ExportAsJSONAction.appliesTo = function (context) {
|
||||||
return this.openmct.$injector.get('policyService')
|
return openmct.$injector.get('policyService')
|
||||||
.allow("creation", context.domainObject.getCapability("type")
|
.allow("creation", context.domainObject.getCapability("type")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -29,7 +29,7 @@ define(
|
|||||||
],
|
],
|
||||||
function (ExportAsJSONAction, domainObjectFactory, MCT, AdapterCapability) {
|
function (ExportAsJSONAction, domainObjectFactory, MCT, AdapterCapability) {
|
||||||
|
|
||||||
describe("The export JSON action", function () {
|
xdescribe("The export JSON action", function () {
|
||||||
|
|
||||||
var context,
|
var context,
|
||||||
action,
|
action,
|
||||||
@ -102,7 +102,7 @@ define(
|
|||||||
expect(action).toBeDefined();
|
expect(action).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
xit("doesn't export non-creatable objects in tree", function () {
|
it("doesn't export non-creatable objects in tree", function () {
|
||||||
var nonCreatableType = {
|
var nonCreatableType = {
|
||||||
hasFeature:
|
hasFeature:
|
||||||
function (feature) {
|
function (feature) {
|
||||||
@ -149,7 +149,7 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
xit("can export self-containing objects", function () {
|
it("can export self-containing objects", function () {
|
||||||
var parent = domainObjectFactory({
|
var parent = domainObjectFactory({
|
||||||
name: 'parent',
|
name: 'parent',
|
||||||
model: {
|
model: {
|
||||||
@ -191,7 +191,7 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
xit("exports links to external objects as new objects", function () {
|
it("exports links to external objects as new objects", function () {
|
||||||
var parent = domainObjectFactory({
|
var parent = domainObjectFactory({
|
||||||
name: 'parent',
|
name: 'parent',
|
||||||
model: {
|
model: {
|
||||||
|
@ -27,7 +27,7 @@ define(
|
|||||||
],
|
],
|
||||||
function (ImportAsJSONAction, domainObjectFactory) {
|
function (ImportAsJSONAction, domainObjectFactory) {
|
||||||
|
|
||||||
describe("The import JSON action", function () {
|
xdescribe("The import JSON action", function () {
|
||||||
|
|
||||||
var context = {};
|
var context = {};
|
||||||
var action,
|
var action,
|
||||||
@ -146,7 +146,7 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
xit("can import self-containing objects", function () {
|
it("can import self-containing objects", function () {
|
||||||
var compDomainObject = domainObjectFactory({
|
var compDomainObject = domainObjectFactory({
|
||||||
name: 'compObject',
|
name: 'compObject',
|
||||||
model: { name: 'compObject'},
|
model: { name: 'compObject'},
|
||||||
@ -198,7 +198,7 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
xit("assigns new ids to each imported object", function () {
|
it("assigns new ids to each imported object", function () {
|
||||||
dialogService.getUserInput.and.returnValue(Promise.resolve(
|
dialogService.getUserInput.and.returnValue(Promise.resolve(
|
||||||
{
|
{
|
||||||
selectFile: {
|
selectFile: {
|
||||||
|
@ -47,7 +47,7 @@ define(
|
|||||||
* @param $interval Angular's $interval service
|
* @param $interval Angular's $interval service
|
||||||
* @param {string} space the name of the persistence space being served
|
* @param {string} space the name of the persistence space being served
|
||||||
* @param {string} root the root of the path to ElasticSearch
|
* @param {string} root the root of the path to ElasticSearch
|
||||||
* @param {string} path the path to domain objects within ElasticSearch
|
* @param {stirng} path the path to domain objects within ElasticSearch
|
||||||
*/
|
*/
|
||||||
function ElasticPersistenceProvider($http, $q, space, root, path) {
|
function ElasticPersistenceProvider($http, $q, space, root, path) {
|
||||||
this.spaces = [space];
|
this.spaces = [space];
|
||||||
|
@ -136,7 +136,7 @@ define([
|
|||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name conductor
|
* @name conductor
|
||||||
*/
|
*/
|
||||||
this.time = new api.TimeAPI(this);
|
this.time = new api.TimeAPI();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for interacting with the composition of domain objects.
|
* An interface for interacting with the composition of domain objects.
|
||||||
@ -287,7 +287,6 @@ define([
|
|||||||
this.install(this.plugins.ViewLargeAction());
|
this.install(this.plugins.ViewLargeAction());
|
||||||
this.install(this.plugins.ObjectInterceptors());
|
this.install(this.plugins.ObjectInterceptors());
|
||||||
this.install(this.plugins.NonEditableFolder());
|
this.install(this.plugins.NonEditableFolder());
|
||||||
this.install(this.plugins.DeviceClassifier());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
@ -28,6 +28,8 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ define([
|
|||||||
'./capabilities/APICapabilityDecorator',
|
'./capabilities/APICapabilityDecorator',
|
||||||
'./policies/AdaptedViewPolicy',
|
'./policies/AdaptedViewPolicy',
|
||||||
'./runs/AlternateCompositionInitializer',
|
'./runs/AlternateCompositionInitializer',
|
||||||
|
'./runs/TypeDeprecationChecker',
|
||||||
'./runs/LegacyTelemetryProvider',
|
'./runs/LegacyTelemetryProvider',
|
||||||
'./runs/RegisterLegacyTypes',
|
'./runs/RegisterLegacyTypes',
|
||||||
'./services/LegacyObjectAPIInterceptor',
|
'./services/LegacyObjectAPIInterceptor',
|
||||||
@ -45,6 +46,7 @@ define([
|
|||||||
APICapabilityDecorator,
|
APICapabilityDecorator,
|
||||||
AdaptedViewPolicy,
|
AdaptedViewPolicy,
|
||||||
AlternateCompositionInitializer,
|
AlternateCompositionInitializer,
|
||||||
|
TypeDeprecationChecker,
|
||||||
LegacyTelemetryProvider,
|
LegacyTelemetryProvider,
|
||||||
RegisterLegacyTypes,
|
RegisterLegacyTypes,
|
||||||
LegacyObjectAPIInterceptor,
|
LegacyObjectAPIInterceptor,
|
||||||
@ -133,6 +135,10 @@ define([
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
runs: [
|
runs: [
|
||||||
|
{
|
||||||
|
implementation: TypeDeprecationChecker,
|
||||||
|
depends: ["types[]"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
implementation: AlternateCompositionInitializer,
|
implementation: AlternateCompositionInitializer,
|
||||||
depends: ["openmct"]
|
depends: ["openmct"]
|
||||||
|
@ -4,6 +4,12 @@ define([
|
|||||||
|
|
||||||
) {
|
) {
|
||||||
function RegisterLegacyTypes(types, openmct) {
|
function RegisterLegacyTypes(types, openmct) {
|
||||||
|
types.forEach(function (legacyDefinition) {
|
||||||
|
if (!openmct.types.get(legacyDefinition.key)) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate type ${legacyDefinition.key} from ${legacyDefinition.bundle.path} to use the new Types API. Legacy type support will be removed soon.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
openmct.types.importLegacyTypes(types);
|
openmct.types.importLegacyTypes(types);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
* Open openmct, Copyright (c) 2014-2021, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
* Open openmct is licensed under the Apache License, Version 2.0 (the
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
@ -14,27 +14,33 @@
|
|||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*
|
*
|
||||||
* Open MCT includes source code licensed under additional open source
|
* Open openmct includes source code licensed under additional open source
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
export default function timelineInterceptor(openmct) {
|
define([
|
||||||
|
|
||||||
openmct.objects.addGetInterceptor({
|
], function (
|
||||||
appliesTo: (identifier, domainObject) => {
|
|
||||||
return domainObject && domainObject.type === 'time-strip';
|
|
||||||
},
|
|
||||||
invoke: (identifier, object) => {
|
|
||||||
|
|
||||||
if (object && object.configuration === undefined) {
|
) {
|
||||||
object.configuration = {
|
|
||||||
useIndependentTime: true
|
function checkForDeprecatedFunctionality(typeDef) {
|
||||||
};
|
if (Object.prototype.hasOwnProperty.call(typeDef, 'telemetry')) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING: Telemetry data on type '
|
||||||
|
+ 'registrations will be deprecated in a future version, '
|
||||||
|
+ 'please convert to a custom telemetry metadata provider '
|
||||||
|
+ 'for type: ' + typeDef.key
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
function TypeDeprecationChecker(types) {
|
||||||
|
types.forEach(checkForDeprecatedFunctionality);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return TypeDeprecationChecker;
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
|
@ -15,6 +15,8 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: legacyView.key,
|
key: legacyView.key,
|
||||||
name: legacyView.name,
|
name: legacyView.name,
|
||||||
|
@ -4,6 +4,7 @@ define([
|
|||||||
|
|
||||||
) {
|
) {
|
||||||
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
|
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs. Legacy Inspector view support will be removed soon.`);
|
||||||
let representation = openmct.$injector.get('representations[]')
|
let representation = openmct.$injector.get('representations[]')
|
||||||
.filter((r) => r.key === typeDefinition.inspector)[0];
|
.filter((r) => r.key === typeDefinition.inspector)[0];
|
||||||
|
|
||||||
|
@ -60,7 +60,9 @@ class ActionsAPI extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getCachedActionCollection(objectPath, view) {
|
_getCachedActionCollection(objectPath, view) {
|
||||||
return this._actionCollections.get(view);
|
let cachedActionCollection = this._actionCollections.get(view);
|
||||||
|
|
||||||
|
return cachedActionCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
_newActionCollection(objectPath, view, skipEnvironmentObservers) {
|
_newActionCollection(objectPath, view, skipEnvironmentObservers) {
|
||||||
|
@ -46,7 +46,7 @@ define([
|
|||||||
StatusAPI
|
StatusAPI
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
TimeAPI: TimeAPI.default,
|
TimeAPI: TimeAPI,
|
||||||
ObjectAPI: ObjectAPI,
|
ObjectAPI: ObjectAPI,
|
||||||
CompositionAPI: CompositionAPI,
|
CompositionAPI: CompositionAPI,
|
||||||
TypeRegistry: TypeRegistry,
|
TypeRegistry: TypeRegistry,
|
||||||
|
@ -42,7 +42,7 @@ import EventEmitter from 'EventEmitter';
|
|||||||
*
|
*
|
||||||
* @typedef {object} NotificationModel
|
* @typedef {object} NotificationModel
|
||||||
* @property {string} message The message to be displayed by the notification
|
* @property {string} message The message to be displayed by the notification
|
||||||
* @property {number | 'unknown'} [progress] The progress of some ongoing task. Should be a number between 0 and 100, or
|
* @property {number | 'unknown'} [progress] The progres of some ongoing task. Should be a number between 0 and 100, or
|
||||||
* with the string literal 'unknown'.
|
* with the string literal 'unknown'.
|
||||||
* @property {string} [progressText] A message conveying progress of some ongoing task.
|
* @property {string} [progressText] A message conveying progress of some ongoing task.
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* Present an alert to the user.
|
* Present an alert to the user.
|
||||||
* @param {string} message The message to display to the user.
|
* @param {string} message The message to display to the user.
|
||||||
* @param {Object} [options] object with following properties
|
* @param {Object} [options] object with following properties
|
||||||
* autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
|
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
||||||
* link: {Object} Add a link to notifications for navigation
|
* link: {Object} Add a link to notifications for navigation
|
||||||
* onClick: callback function
|
* onClick: callback function
|
||||||
* cssClass: css class name to add style on link
|
* cssClass: css class name to add style on link
|
||||||
@ -119,7 +119,7 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* Present an error message to the user
|
* Present an error message to the user
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
* @param {Object} [options] object with following properties
|
* @param {Object} [options] object with following properties
|
||||||
* autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
|
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
||||||
* link: {Object} Add a link to notifications for navigation
|
* link: {Object} Add a link to notifications for navigation
|
||||||
* onClick: callback function
|
* onClick: callback function
|
||||||
* cssClass: css class name to add style on link
|
* cssClass: css class name to add style on link
|
||||||
|
@ -182,12 +182,6 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
|||||||
let objectPromise = provider.get(identifier, abortSignal).then(result => {
|
let objectPromise = provider.get(identifier, abortSignal).then(result => {
|
||||||
delete this.cache[keystring];
|
delete this.cache[keystring];
|
||||||
result = this.applyGetInterceptors(identifier, result);
|
result = this.applyGetInterceptors(identifier, result);
|
||||||
if (result.isMutable) {
|
|
||||||
result.$refresh(result);
|
|
||||||
} else {
|
|
||||||
let mutableDomainObject = this._toMutable(result);
|
|
||||||
mutableDomainObject.$refresh(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
@ -304,15 +298,10 @@ ObjectAPI.prototype.save = function (domainObject) {
|
|||||||
savedResolve = resolve;
|
savedResolve = resolve;
|
||||||
});
|
});
|
||||||
domainObject.persisted = persistedTime;
|
domainObject.persisted = persistedTime;
|
||||||
const newObjectPromise = provider.create(domainObject);
|
provider.create(domainObject).then((response) => {
|
||||||
if (newObjectPromise) {
|
|
||||||
newObjectPromise.then(response => {
|
|
||||||
this.mutate(domainObject, 'persisted', persistedTime);
|
this.mutate(domainObject, 'persisted', persistedTime);
|
||||||
savedResolve(response);
|
savedResolve(response);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
result = Promise.reject(`[ObjectAPI][save] Object provider returned ${newObjectPromise} when creating new object.`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
domainObject.persisted = persistedTime;
|
domainObject.persisted = persistedTime;
|
||||||
this.mutate(domainObject, 'persisted', persistedTime);
|
this.mutate(domainObject, 'persisted', persistedTime);
|
||||||
@ -369,20 +358,6 @@ ObjectAPI.prototype.applyGetInterceptors = function (identifier, domainObject) {
|
|||||||
return domainObject;
|
return domainObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return relative url path from a given object path
|
|
||||||
* eg: #/browse/mine/cb56f6bf-c900-43b7-b923-2e3b64b412db/6e89e858-77ce-46e4-a1ad-749240286497/....
|
|
||||||
* @param {Array} objectPath
|
|
||||||
* @returns {string} relative url for object
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.getRelativePath = function (objectPath) {
|
|
||||||
return objectPath
|
|
||||||
.map(p => this.makeKeyString(p.identifier))
|
|
||||||
.reverse()
|
|
||||||
.join('/')
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modify a domain object.
|
* Modify a domain object.
|
||||||
* @param {module:openmct.DomainObject} object the object to mutate
|
* @param {module:openmct.DomainObject} object the object to mutate
|
||||||
|
@ -10,37 +10,28 @@ const cssClasses = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class Overlay extends EventEmitter {
|
class Overlay extends EventEmitter {
|
||||||
constructor({
|
constructor(options) {
|
||||||
buttons,
|
|
||||||
autoHide = true,
|
|
||||||
dismissable = true,
|
|
||||||
element,
|
|
||||||
onDestroy,
|
|
||||||
size
|
|
||||||
} = {}) {
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.dismissable = options.dismissable !== false;
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
this.container.classList.add('l-overlay-wrapper', cssClasses[size]);
|
this.container.classList.add('l-overlay-wrapper', cssClasses[options.size]);
|
||||||
|
|
||||||
this.autoHide = autoHide;
|
|
||||||
this.dismissable = dismissable !== false;
|
|
||||||
|
|
||||||
this.component = new Vue({
|
this.component = new Vue({
|
||||||
components: {
|
|
||||||
OverlayComponent: OverlayComponent
|
|
||||||
},
|
|
||||||
provide: {
|
provide: {
|
||||||
dismiss: this.dismiss.bind(this),
|
dismiss: this.dismiss.bind(this),
|
||||||
element,
|
element: options.element,
|
||||||
buttons,
|
buttons: options.buttons,
|
||||||
dismissable: this.dismissable
|
dismissable: this.dismissable
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
OverlayComponent: OverlayComponent
|
||||||
|
},
|
||||||
template: '<overlay-component></overlay-component>'
|
template: '<overlay-component></overlay-component>'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (onDestroy) {
|
if (options.onDestroy) {
|
||||||
this.once('destroy', onDestroy);
|
this.once('destroy', options.onDestroy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,10 +30,7 @@ class OverlayAPI {
|
|||||||
*/
|
*/
|
||||||
showOverlay(overlay) {
|
showOverlay(overlay) {
|
||||||
if (this.activeOverlays.length) {
|
if (this.activeOverlays.length) {
|
||||||
const previousOverlay = this.activeOverlays[this.activeOverlays.length - 1];
|
this.activeOverlays[this.activeOverlays.length - 1].container.classList.add('invisible');
|
||||||
if (previousOverlay.autoHide) {
|
|
||||||
previousOverlay.container.classList.add('invisible');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeOverlays.push(overlay);
|
this.activeOverlays.push(overlay);
|
||||||
@ -63,7 +60,7 @@ class OverlayAPI {
|
|||||||
* A description of option properties that can be passed into the overlay
|
* A description of option properties that can be passed into the overlay
|
||||||
* @typedef options
|
* @typedef options
|
||||||
* @property {object} element DOMElement that is to be inserted/shown on the overlay
|
* @property {object} element DOMElement that is to be inserted/shown on the overlay
|
||||||
* @property {string} size preferred size of the overlay (large, small, fit)
|
* @property {string} size prefered size of the overlay (large, small, fit)
|
||||||
* @property {array} buttons optional button objects with label and callback properties
|
* @property {array} buttons optional button objects with label and callback properties
|
||||||
* @property {function} onDestroy callback to be called when overlay is destroyed
|
* @property {function} onDestroy callback to be called when overlay is destroyed
|
||||||
* @property {boolean} dismissable allow user to dismiss overlay by using esc, and clicking away
|
* @property {boolean} dismissable allow user to dismiss overlay by using esc, and clicking away
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
></button>
|
></button>
|
||||||
<div
|
<div
|
||||||
ref="element"
|
ref="element"
|
||||||
class="c-overlay__contents js-notebook-snapshot-item-wrapper"
|
class="c-overlay__contents"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
|
@ -20,8 +20,6 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
const { TelemetryCollection } = require("./TelemetryCollection");
|
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'../../plugins/displayLayout/CustomStringFormatter',
|
'../../plugins/displayLayout/CustomStringFormatter',
|
||||||
'./TelemetryMetadataManager',
|
'./TelemetryMetadataManager',
|
||||||
@ -180,6 +178,12 @@ define([
|
|||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) {
|
TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING: openmct.telemetry.canProvideTelemetry '
|
||||||
|
+ 'will not be supported in future versions of Open MCT. Please '
|
||||||
|
+ 'use openmct.telemetry.isTelemetryObject instead.'
|
||||||
|
);
|
||||||
|
|
||||||
return Boolean(this.findSubscriptionProvider(domainObject))
|
return Boolean(this.findSubscriptionProvider(domainObject))
|
||||||
|| Boolean(this.findRequestProvider(domainObject));
|
|| Boolean(this.findRequestProvider(domainObject));
|
||||||
};
|
};
|
||||||
@ -269,28 +273,6 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Request telemetry collection for a domain object.
|
|
||||||
* The `options` argument allows you to specify filters
|
|
||||||
* (start, end, etc.), sort order, and strategies for retrieving
|
|
||||||
* telemetry (aggregation, latest available, etc.).
|
|
||||||
*
|
|
||||||
* @method requestCollection
|
|
||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
|
||||||
* @param {module:openmct.DomainObject} domainObject the object
|
|
||||||
* which has associated telemetry
|
|
||||||
* @param {module:openmct.TelemetryAPI~TelemetryRequest} options
|
|
||||||
* options for this telemetry collection request
|
|
||||||
* @returns {TelemetryCollection} a TelemetryCollection instance
|
|
||||||
*/
|
|
||||||
TelemetryAPI.prototype.requestCollection = function (domainObject, options = {}) {
|
|
||||||
return new TelemetryCollection(
|
|
||||||
this.openmct,
|
|
||||||
domainObject,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request historical telemetry for a domain object.
|
* Request historical telemetry for a domain object.
|
||||||
* The `options` argument allows you to specify filters
|
* The `options` argument allows you to specify filters
|
||||||
|
@ -19,11 +19,13 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import TelemetryAPI from './TelemetryAPI';
|
|
||||||
const { TelemetryCollection } = require("./TelemetryCollection");
|
|
||||||
|
|
||||||
describe('Telemetry API', function () {
|
define([
|
||||||
const NO_PROVIDER = 'No provider found';
|
'./TelemetryAPI'
|
||||||
|
], function (
|
||||||
|
TelemetryAPI
|
||||||
|
) {
|
||||||
|
xdescribe('Telemetry API', function () {
|
||||||
let openmct;
|
let openmct;
|
||||||
let telemetryAPI;
|
let telemetryAPI;
|
||||||
let mockTypeService;
|
let mockTypeService;
|
||||||
@ -71,23 +73,17 @@ describe('Telemetry API', function () {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('provides consistent results without providers', function (done) {
|
it('provides consistent results without providers', function () {
|
||||||
const unsubscribe = telemetryAPI.subscribe(domainObject);
|
const unsubscribe = telemetryAPI.subscribe(domainObject);
|
||||||
|
|
||||||
expect(unsubscribe).toEqual(jasmine.any(Function));
|
expect(unsubscribe).toEqual(jasmine.any(Function));
|
||||||
|
|
||||||
telemetryAPI.request(domainObject).then(
|
const response = telemetryAPI.request(domainObject);
|
||||||
() => {},
|
expect(response).toEqual(jasmine.any(Promise));
|
||||||
(error) => {
|
|
||||||
expect(error).toBe(NO_PROVIDER);
|
|
||||||
}
|
|
||||||
).finally(done);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips providers that do not match', function (done) {
|
it('skips providers that do not match', function () {
|
||||||
telemetryProvider.supportsSubscribe.and.returnValue(false);
|
telemetryProvider.supportsSubscribe.and.returnValue(false);
|
||||||
telemetryProvider.supportsRequest.and.returnValue(false);
|
telemetryProvider.supportsRequest.and.returnValue(false);
|
||||||
telemetryProvider.request.and.returnValue(Promise.resolve([]));
|
|
||||||
telemetryAPI.addProvider(telemetryProvider);
|
telemetryAPI.addProvider(telemetryProvider);
|
||||||
|
|
||||||
const callback = jasmine.createSpy('callback');
|
const callback = jasmine.createSpy('callback');
|
||||||
@ -97,13 +93,11 @@ describe('Telemetry API', function () {
|
|||||||
expect(telemetryProvider.subscribe).not.toHaveBeenCalled();
|
expect(telemetryProvider.subscribe).not.toHaveBeenCalled();
|
||||||
expect(unsubscribe).toEqual(jasmine.any(Function));
|
expect(unsubscribe).toEqual(jasmine.any(Function));
|
||||||
|
|
||||||
telemetryAPI.request(domainObject).then((response) => {
|
const response = telemetryAPI.request(domainObject);
|
||||||
expect(telemetryProvider.supportsRequest)
|
expect(telemetryProvider.supportsRequest)
|
||||||
.toHaveBeenCalledWith(domainObject, jasmine.any(Object));
|
.toHaveBeenCalledWith(domainObject, jasmine.any(Object));
|
||||||
expect(telemetryProvider.request).not.toHaveBeenCalled();
|
expect(telemetryProvider.request).not.toHaveBeenCalled();
|
||||||
}, (error) => {
|
expect(response).toEqual(jasmine.any(Promise));
|
||||||
expect(error).toBe(NO_PROVIDER);
|
|
||||||
}).finally(done);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends subscribe calls to matching providers', function () {
|
it('sends subscribe calls to matching providers', function () {
|
||||||
@ -119,7 +113,7 @@ describe('Telemetry API', function () {
|
|||||||
.toHaveBeenCalledWith(domainObject);
|
.toHaveBeenCalledWith(domainObject);
|
||||||
expect(telemetryProvider.subscribe.calls.count()).toBe(1);
|
expect(telemetryProvider.subscribe.calls.count()).toBe(1);
|
||||||
expect(telemetryProvider.subscribe)
|
expect(telemetryProvider.subscribe)
|
||||||
.toHaveBeenCalledWith(domainObject, jasmine.any(Function), undefined);
|
.toHaveBeenCalledWith(domainObject, jasmine.any(Function));
|
||||||
|
|
||||||
const notify = telemetryProvider.subscribe.calls.mostRecent().args[1];
|
const notify = telemetryProvider.subscribe.calls.mostRecent().args[1];
|
||||||
notify('someValue');
|
notify('someValue');
|
||||||
@ -244,13 +238,14 @@ describe('Telemetry API', function () {
|
|||||||
expect(unsubFuncs[1]).toHaveBeenCalled();
|
expect(unsubFuncs[1]).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends requests to matching providers', function (done) {
|
it('sends requests to matching providers', function () {
|
||||||
const telemPromise = Promise.resolve([]);
|
const telemPromise = Promise.resolve([]);
|
||||||
telemetryProvider.supportsRequest.and.returnValue(true);
|
telemetryProvider.supportsRequest.and.returnValue(true);
|
||||||
telemetryProvider.request.and.returnValue(telemPromise);
|
telemetryProvider.request.and.returnValue(telemPromise);
|
||||||
telemetryAPI.addProvider(telemetryProvider);
|
telemetryAPI.addProvider(telemetryProvider);
|
||||||
|
|
||||||
telemetryAPI.request(domainObject).then(() => {
|
const result = telemetryAPI.request(domainObject);
|
||||||
|
expect(result).toBe(telemPromise);
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(
|
||||||
domainObject,
|
domainObject,
|
||||||
jasmine.any(Object)
|
jasmine.any(Object)
|
||||||
@ -259,15 +254,13 @@ describe('Telemetry API', function () {
|
|||||||
domainObject,
|
domainObject,
|
||||||
jasmine.any(Object)
|
jasmine.any(Object)
|
||||||
);
|
);
|
||||||
}).finally(done);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates default request options', function (done) {
|
it('generates default request options', function () {
|
||||||
telemetryProvider.supportsRequest.and.returnValue(true);
|
telemetryProvider.supportsRequest.and.returnValue(true);
|
||||||
telemetryProvider.request.and.returnValue(Promise.resolve([]));
|
|
||||||
telemetryAPI.addProvider(telemetryProvider);
|
telemetryAPI.addProvider(telemetryProvider);
|
||||||
|
|
||||||
telemetryAPI.request(domainObject).then(() => {
|
telemetryAPI.request(domainObject);
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(
|
||||||
jasmine.any(Object),
|
jasmine.any(Object),
|
||||||
{
|
{
|
||||||
@ -289,7 +282,7 @@ describe('Telemetry API', function () {
|
|||||||
telemetryProvider.supportsRequest.calls.reset();
|
telemetryProvider.supportsRequest.calls.reset();
|
||||||
telemetryProvider.request.calls.reset();
|
telemetryProvider.request.calls.reset();
|
||||||
|
|
||||||
telemetryAPI.request(domainObject, {}).then(() => {
|
telemetryAPI.request(domainObject, {});
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(
|
||||||
jasmine.any(Object),
|
jasmine.any(Object),
|
||||||
{
|
{
|
||||||
@ -308,20 +301,17 @@ describe('Telemetry API', function () {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}).finally(done);
|
|
||||||
|
|
||||||
});
|
it('does not overwrite existing request options', function () {
|
||||||
|
|
||||||
it('do not overwrite existing request options', function (done) {
|
|
||||||
telemetryProvider.supportsRequest.and.returnValue(true);
|
telemetryProvider.supportsRequest.and.returnValue(true);
|
||||||
telemetryProvider.request.and.returnValue(Promise.resolve([]));
|
|
||||||
telemetryAPI.addProvider(telemetryProvider);
|
telemetryAPI.addProvider(telemetryProvider);
|
||||||
|
|
||||||
telemetryAPI.request(domainObject, {
|
telemetryAPI.request(domainObject, {
|
||||||
start: 20,
|
start: 20,
|
||||||
end: 30,
|
end: 30,
|
||||||
domain: 'someDomain'
|
domain: 'someDomain'
|
||||||
}).then(() => {
|
});
|
||||||
|
|
||||||
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(
|
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(
|
||||||
jasmine.any(Object),
|
jasmine.any(Object),
|
||||||
{
|
{
|
||||||
@ -339,11 +329,8 @@ describe('Telemetry API', function () {
|
|||||||
domain: 'someDomain'
|
domain: 'someDomain'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
}).finally(done);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('metadata', function () {
|
describe('metadata', function () {
|
||||||
let mockMetadata = {};
|
let mockMetadata = {};
|
||||||
let mockObjectType = {
|
let mockObjectType = {
|
||||||
@ -361,7 +348,6 @@ describe('Telemetry API', function () {
|
|||||||
});
|
});
|
||||||
mockTypeService.getType.and.returnValue(mockObjectType);
|
mockTypeService.getType.and.returnValue(mockObjectType);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('respects explicit priority', function () {
|
it('respects explicit priority', function () {
|
||||||
mockMetadata.values = [
|
mockMetadata.values = [
|
||||||
{
|
{
|
||||||
@ -573,41 +559,5 @@ describe('Telemetry API', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('telemetry collections', () => {
|
|
||||||
let domainObject;
|
|
||||||
let mockMetadata = {};
|
|
||||||
let mockObjectType = {
|
|
||||||
typeDef: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
openmct.telemetry = telemetryAPI;
|
|
||||||
telemetryAPI.addProvider({
|
|
||||||
key: 'mockMetadataProvider',
|
|
||||||
supportsMetadata() {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
getMetadata() {
|
|
||||||
return mockMetadata;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mockTypeService.getType.and.returnValue(mockObjectType);
|
|
||||||
domainObject = {
|
|
||||||
identifier: {
|
|
||||||
key: 'a',
|
|
||||||
namespace: 'b'
|
|
||||||
},
|
|
||||||
type: 'sample-type'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('when requested, returns an instance of telemetry collection', () => {
|
|
||||||
const telemetryCollection = telemetryAPI.requestCollection(domainObject);
|
|
||||||
|
|
||||||
expect(telemetryCollection).toBeInstanceOf(TelemetryCollection);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,396 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
import _ from 'lodash';
|
|
||||||
import EventEmitter from 'EventEmitter';
|
|
||||||
|
|
||||||
const ERRORS = {
|
|
||||||
TIMESYSTEM_KEY: 'All telemetry metadata must have a telemetry value with a key that matches the key of the active time system.',
|
|
||||||
LOADED: 'Telemetry Collection has already been loaded.'
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Class representing a Telemetry Collection. */
|
|
||||||
|
|
||||||
export class TelemetryCollection extends EventEmitter {
|
|
||||||
/**
|
|
||||||
* Creates a Telemetry Collection
|
|
||||||
*
|
|
||||||
* @param {object} openmct - Openm MCT
|
|
||||||
* @param {object} domainObject - Domain Object to user for telemetry collection
|
|
||||||
* @param {object} options - Any options passed in for request/subscribe
|
|
||||||
*/
|
|
||||||
constructor(openmct, domainObject, options) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.loaded = false;
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.domainObject = domainObject;
|
|
||||||
this.boundedTelemetry = [];
|
|
||||||
this.futureBuffer = [];
|
|
||||||
this.parseTime = undefined;
|
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
|
||||||
this.unsubscribe = undefined;
|
|
||||||
this.historicalProvider = undefined;
|
|
||||||
this.options = options;
|
|
||||||
this.pageState = undefined;
|
|
||||||
this.lastBounds = undefined;
|
|
||||||
this.requestAbort = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This will start the requests for historical and realtime data,
|
|
||||||
* as well as setting up initial values and watchers
|
|
||||||
*/
|
|
||||||
load() {
|
|
||||||
if (this.loaded) {
|
|
||||||
this._error(ERRORS.LOADED);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._timeSystem(this.openmct.time.timeSystem());
|
|
||||||
this.lastBounds = this.openmct.time.bounds();
|
|
||||||
|
|
||||||
this._watchBounds();
|
|
||||||
this._watchTimeSystem();
|
|
||||||
|
|
||||||
this._initiateHistoricalRequests();
|
|
||||||
this._initiateSubscriptionTelemetry();
|
|
||||||
|
|
||||||
this.loaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* can/should be called by the requester of the telemetry collection
|
|
||||||
* to remove any listeners
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
if (this.requestAbort) {
|
|
||||||
this.requestAbort.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._unwatchBounds();
|
|
||||||
this._unwatchTimeSystem();
|
|
||||||
if (this.unsubscribe) {
|
|
||||||
this.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeAllListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This will start the requests for historical and realtime data,
|
|
||||||
* as well as setting up initial values and watchers
|
|
||||||
*/
|
|
||||||
getAll() {
|
|
||||||
return this.boundedTelemetry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the telemetry collection for historical requests,
|
|
||||||
* this uses the "standardizeRequestOptions" from Telemetry API
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_initiateHistoricalRequests() {
|
|
||||||
this.openmct.telemetry.standardizeRequestOptions(this.options);
|
|
||||||
this.historicalProvider = this.openmct.telemetry.
|
|
||||||
findRequestProvider(this.domainObject, this.options);
|
|
||||||
|
|
||||||
this._requestHistoricalTelemetry();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a historical provider exists, then historical requests will be made
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _requestHistoricalTelemetry() {
|
|
||||||
if (!this.historicalProvider) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let historicalData;
|
|
||||||
|
|
||||||
this.options.onPartialResponse = this._processNewTelemetry.bind(this);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.requestAbort = new AbortController();
|
|
||||||
this.options.signal = this.requestAbort.signal;
|
|
||||||
historicalData = await this.historicalProvider.request(this.domainObject, this.options);
|
|
||||||
} catch (error) {
|
|
||||||
if (error.name !== 'AbortError') {
|
|
||||||
console.error('Error requesting telemetry data...');
|
|
||||||
this._error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requestAbort = undefined;
|
|
||||||
|
|
||||||
this._processNewTelemetry(historicalData);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This uses the built in subscription function from Telemetry API
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_initiateSubscriptionTelemetry() {
|
|
||||||
|
|
||||||
if (this.unsubscribe) {
|
|
||||||
this.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.unsubscribe = this.openmct.telemetry
|
|
||||||
.subscribe(
|
|
||||||
this.domainObject,
|
|
||||||
datum => this._processNewTelemetry(datum),
|
|
||||||
this.options
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter any new telemetry (add/page, historical, subscription) based on
|
|
||||||
* time bounds and dupes
|
|
||||||
*
|
|
||||||
* @param {(Object|Object[])} telemetryData - telemetry data object or
|
|
||||||
* array of telemetry data objects
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_processNewTelemetry(telemetryData) {
|
|
||||||
let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData];
|
|
||||||
let parsedValue;
|
|
||||||
let beforeStartOfBounds;
|
|
||||||
let afterEndOfBounds;
|
|
||||||
let added = [];
|
|
||||||
|
|
||||||
for (let datum of data) {
|
|
||||||
parsedValue = this.parseTime(datum);
|
|
||||||
beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
|
||||||
afterEndOfBounds = parsedValue > this.lastBounds.end;
|
|
||||||
|
|
||||||
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
|
||||||
let isDuplicate = false;
|
|
||||||
let startIndex = this._sortedIndex(datum);
|
|
||||||
let endIndex = undefined;
|
|
||||||
|
|
||||||
// dupe check
|
|
||||||
if (startIndex !== this.boundedTelemetry.length) {
|
|
||||||
endIndex = _.sortedLastIndexBy(
|
|
||||||
this.boundedTelemetry,
|
|
||||||
datum,
|
|
||||||
boundedDatum => this.parseTime(boundedDatum)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (endIndex > startIndex) {
|
|
||||||
let potentialDupes = this.boundedTelemetry.slice(startIndex, endIndex);
|
|
||||||
|
|
||||||
isDuplicate = potentialDupes.some(_.isEqual.bind(undefined, datum));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDuplicate) {
|
|
||||||
let index = endIndex || startIndex;
|
|
||||||
|
|
||||||
this.boundedTelemetry.splice(index, 0, datum);
|
|
||||||
added.push(datum);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (afterEndOfBounds) {
|
|
||||||
this.futureBuffer.push(datum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (added.length) {
|
|
||||||
this.emit('add', added);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the correct insertion point for the given telemetry datum.
|
|
||||||
* Leverages lodash's `sortedIndexBy` function which implements a binary search.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_sortedIndex(datum) {
|
|
||||||
if (this.boundedTelemetry.length === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let parsedValue = this.parseTime(datum);
|
|
||||||
let lastValue = this.parseTime(this.boundedTelemetry[this.boundedTelemetry.length - 1]);
|
|
||||||
|
|
||||||
if (parsedValue > lastValue || parsedValue === lastValue) {
|
|
||||||
return this.boundedTelemetry.length;
|
|
||||||
} else {
|
|
||||||
return _.sortedIndexBy(
|
|
||||||
this.boundedTelemetry,
|
|
||||||
datum,
|
|
||||||
boundedDatum => this.parseTime(boundedDatum)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* when the start time, end time, or both have been updated.
|
|
||||||
* data could be added OR removed here we update the current
|
|
||||||
* bounded telemetry
|
|
||||||
*
|
|
||||||
* @param {TimeConductorBounds} bounds The newly updated bounds
|
|
||||||
* @param {boolean} [tick] `true` if the bounds update was due to
|
|
||||||
* a "tick" event (ie. was an automatic update), false otherwise.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_bounds(bounds, isTick) {
|
|
||||||
let startChanged = this.lastBounds.start !== bounds.start;
|
|
||||||
let endChanged = this.lastBounds.end !== bounds.end;
|
|
||||||
|
|
||||||
this.lastBounds = bounds;
|
|
||||||
|
|
||||||
if (isTick) {
|
|
||||||
// need to check futureBuffer and need to check
|
|
||||||
// if anything has fallen out of bounds
|
|
||||||
let startIndex = 0;
|
|
||||||
let endIndex = 0;
|
|
||||||
|
|
||||||
let discarded = [];
|
|
||||||
let added = [];
|
|
||||||
let testDatum = {};
|
|
||||||
|
|
||||||
if (startChanged) {
|
|
||||||
testDatum[this.timeKey] = bounds.start;
|
|
||||||
// Calculate the new index of the first item within the bounds
|
|
||||||
startIndex = _.sortedIndexBy(
|
|
||||||
this.boundedTelemetry,
|
|
||||||
testDatum,
|
|
||||||
datum => this.parseTime(datum)
|
|
||||||
);
|
|
||||||
discarded = this.boundedTelemetry.splice(0, startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endChanged) {
|
|
||||||
testDatum[this.timeKey] = bounds.end;
|
|
||||||
// Calculate the new index of the last item in bounds
|
|
||||||
endIndex = _.sortedLastIndexBy(
|
|
||||||
this.futureBuffer,
|
|
||||||
testDatum,
|
|
||||||
datum => this.parseTime(datum)
|
|
||||||
);
|
|
||||||
added = this.futureBuffer.splice(0, endIndex);
|
|
||||||
this.boundedTelemetry = [...this.boundedTelemetry, ...added];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discarded.length > 0) {
|
|
||||||
this.emit('remove', discarded);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (added.length > 0) {
|
|
||||||
this.emit('add', added);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// user bounds change, reset
|
|
||||||
this._reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* whenever the time system is updated need to update related values in
|
|
||||||
* the Telemetry Collection and reset the telemetry collection
|
|
||||||
*
|
|
||||||
* @param {TimeSystem} timeSystem - the value of the currently applied
|
|
||||||
* Time System
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_timeSystem(timeSystem) {
|
|
||||||
let domains = this.metadata.valuesForHints(['domain']);
|
|
||||||
let domain = domains.find((d) => d.key === timeSystem.key);
|
|
||||||
|
|
||||||
if (domain === undefined) {
|
|
||||||
this._error(ERRORS.TIMESYSTEM_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// timeKey is used to create a dummy datum used for sorting
|
|
||||||
this.timeKey = domain.source; // this defaults to key if no source is set
|
|
||||||
let metadataValue = this.metadata.value(timeSystem.key) || { format: timeSystem.key };
|
|
||||||
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
|
||||||
|
|
||||||
this.parseTime = (datum) => {
|
|
||||||
return valueFormatter.parse(datum);
|
|
||||||
};
|
|
||||||
|
|
||||||
this._reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the telemetry data of the collection, and re-request
|
|
||||||
* historical telemetry
|
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* @todo handle subscriptions more granually
|
|
||||||
*/
|
|
||||||
_reset() {
|
|
||||||
this.boundedTelemetry = [];
|
|
||||||
this.futureBuffer = [];
|
|
||||||
|
|
||||||
this.emit('clear');
|
|
||||||
|
|
||||||
this._requestHistoricalTelemetry();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* adds the _bounds callback to the 'bounds' timeAPI listener
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_watchBounds() {
|
|
||||||
this.openmct.time.on('bounds', this._bounds, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* removes the _bounds callback from the 'bounds' timeAPI listener
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_unwatchBounds() {
|
|
||||||
this.openmct.time.off('bounds', this._bounds, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* adds the _timeSystem callback to the 'timeSystem' timeAPI listener
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_watchTimeSystem() {
|
|
||||||
this.openmct.time.on('timeSystem', this._timeSystem, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* removes the _timeSystem callback from the 'timeSystem' timeAPI listener
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_unwatchTimeSystem() {
|
|
||||||
this.openmct.time.off('timeSystem', this._timeSystem, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* will throw a new Error, for passed in message
|
|
||||||
* @param {string} message Message describing the error
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_error(message) {
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,6 +31,11 @@ define([
|
|||||||
valueMetadata.hints = valueMetadata.hints || {};
|
valueMetadata.hints = valueMetadata.hints || {};
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
|
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING: `x` hints should be replaced with '
|
||||||
|
+ '`domain` hints moving forward. '
|
||||||
|
+ 'https://github.com/nasa/openmct/issues/1546'
|
||||||
|
);
|
||||||
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
|
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
|
||||||
valueMetadata.hints.domain = valueMetadata.hints.x;
|
valueMetadata.hints.domain = valueMetadata.hints.x;
|
||||||
}
|
}
|
||||||
@ -39,6 +44,11 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
|
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING: `y` hints should be replaced with '
|
||||||
|
+ '`range` hints moving forward. '
|
||||||
|
+ 'https://github.com/nasa/openmct/issues/1546'
|
||||||
|
);
|
||||||
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
|
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
|
||||||
valueMetadata.hints.range = valueMetadata.hints.y;
|
valueMetadata.hints.range = valueMetadata.hints.y;
|
||||||
}
|
}
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
import TimeContext from "./TimeContext";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GlobalContext handles getting and setting time of the openmct application in general.
|
|
||||||
* Views will use this context unless they specify an alternate/independent time context
|
|
||||||
*/
|
|
||||||
class GlobalTimeContext extends TimeContext {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
//The Time Of Interest
|
|
||||||
this.toi = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set the start and end time of the time conductor. Basic validation
|
|
||||||
* of bounds is performed.
|
|
||||||
*
|
|
||||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
|
||||||
* @throws {Error} Validation error
|
|
||||||
* @fires module:openmct.TimeAPI~bounds
|
|
||||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method bounds
|
|
||||||
*/
|
|
||||||
bounds(newBounds) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
super.bounds.call(this, ...arguments);
|
|
||||||
// If a bounds change results in a TOI outside of the current
|
|
||||||
// bounds, unset it
|
|
||||||
if (this.toi < newBounds.start || this.toi > newBounds.end) {
|
|
||||||
this.timeOfInterest(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
|
||||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update bounds based on provided time and current offsets
|
|
||||||
* @private
|
|
||||||
* @param {number} timestamp A time from which bounds will be calculated
|
|
||||||
* using current offsets.
|
|
||||||
*/
|
|
||||||
tick(timestamp) {
|
|
||||||
super.tick.call(this, ...arguments);
|
|
||||||
|
|
||||||
// If a bounds change results in a TOI outside of the current
|
|
||||||
// bounds, unset it
|
|
||||||
if (this.toi < this.boundsVal.start || this.toi > this.boundsVal.end) {
|
|
||||||
this.timeOfInterest(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set the Time of Interest. The Time of Interest is a single point
|
|
||||||
* in time, and constitutes the temporal focus of application views. It can
|
|
||||||
* be manipulated by the user from the time conductor or from other views.
|
|
||||||
* The time of interest can effectively be unset by assigning a value of
|
|
||||||
* 'undefined'.
|
|
||||||
* @fires module:openmct.TimeAPI~timeOfInterest
|
|
||||||
* @param newTOI
|
|
||||||
* @returns {number} the current time of interest
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method timeOfInterest
|
|
||||||
*/
|
|
||||||
timeOfInterest(newTOI) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
this.toi = newTOI;
|
|
||||||
/**
|
|
||||||
* The Time of Interest has moved.
|
|
||||||
* @event timeOfInterest
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {number} Current time of interest
|
|
||||||
*/
|
|
||||||
this.emit('timeOfInterest', this.toi);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.toi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GlobalTimeContext;
|
|
@ -1,94 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
import TimeContext from "./TimeContext";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The IndependentTimeContext handles getting and setting time of the openmct application in general.
|
|
||||||
* Views will use the GlobalTimeContext unless they specify an alternate/independent time context here.
|
|
||||||
*/
|
|
||||||
class IndependentTimeContext extends TimeContext {
|
|
||||||
constructor(globalTimeContext, key) {
|
|
||||||
super();
|
|
||||||
this.key = key;
|
|
||||||
|
|
||||||
this.globalTimeContext = globalTimeContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the active clock. Tick source will be immediately subscribed to
|
|
||||||
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
|
||||||
* can be unset by calling {@link stopClock}.
|
|
||||||
*
|
|
||||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
|
||||||
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
|
||||||
* the start and end bounds. This maintains a sliding time window of a fixed
|
|
||||||
* width that automatically updates.
|
|
||||||
* @fires module:openmct.TimeAPI~clock
|
|
||||||
* @return {Clock} the currently active clock;
|
|
||||||
*/
|
|
||||||
clock(keyOrClock, offsets) {
|
|
||||||
if (arguments.length === 2) {
|
|
||||||
let clock;
|
|
||||||
|
|
||||||
if (typeof keyOrClock === 'string') {
|
|
||||||
clock = this.globalTimeContext.clocks.get(keyOrClock);
|
|
||||||
if (clock === undefined) {
|
|
||||||
throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
|
|
||||||
}
|
|
||||||
} else if (typeof keyOrClock === 'object') {
|
|
||||||
clock = keyOrClock;
|
|
||||||
if (!this.globalTimeContext.clocks.has(clock.key)) {
|
|
||||||
throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousClock = this.activeClock;
|
|
||||||
if (previousClock !== undefined) {
|
|
||||||
previousClock.off("tick", this.tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activeClock = clock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
|
||||||
* @event clock
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {Clock} clock The newly activated clock, or undefined
|
|
||||||
* if the system is no longer following a clock source
|
|
||||||
*/
|
|
||||||
this.emit("clock", this.activeClock);
|
|
||||||
|
|
||||||
if (this.activeClock !== undefined) {
|
|
||||||
this.clockOffsets(offsets);
|
|
||||||
this.activeClock.on("tick", this.tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (arguments.length === 1) {
|
|
||||||
throw "When setting the clock, clock offsets must also be provided";
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.activeClock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default IndependentTimeContext;
|
|
@ -20,8 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import GlobalTimeContext from "./GlobalTimeContext";
|
define(['EventEmitter'], function (EventEmitter) {
|
||||||
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The public API for setting and querying the temporal state of the
|
* The public API for setting and querying the temporal state of the
|
||||||
@ -35,20 +34,37 @@ import IndependentTimeContext from "@/api/time/IndependentTimeContext";
|
|||||||
* the temporal state of the application. The current time bounds are also
|
* the temporal state of the application. The current time bounds are also
|
||||||
* used in queries for historical data.
|
* used in queries for historical data.
|
||||||
*
|
*
|
||||||
* The TimeAPI extends the GlobalTimeContext which in turn extends the TimeContext/EventEmitter class. A number of events are
|
* The TimeAPI extends the EventEmitter class. A number of events are
|
||||||
* fired when properties of the time conductor change, which are documented
|
* fired when properties of the time conductor change, which are documented
|
||||||
* below.
|
* below.
|
||||||
*
|
*
|
||||||
* @interface
|
* @interface
|
||||||
* @memberof module:openmct
|
* @memberof module:openmct
|
||||||
*/
|
*/
|
||||||
class TimeAPI extends GlobalTimeContext {
|
function TimeAPI() {
|
||||||
constructor(openmct) {
|
EventEmitter.call(this);
|
||||||
super();
|
|
||||||
this.openmct = openmct;
|
//The Time System
|
||||||
this.independentContexts = new Map();
|
this.system = undefined;
|
||||||
|
//The Time Of Interest
|
||||||
|
this.toi = undefined;
|
||||||
|
|
||||||
|
this.boundsVal = {
|
||||||
|
start: undefined,
|
||||||
|
end: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
this.timeSystems = new Map();
|
||||||
|
this.clocks = new Map();
|
||||||
|
this.activeClock = undefined;
|
||||||
|
this.offsets = undefined;
|
||||||
|
|
||||||
|
this.tick = this.tick.bind(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimeAPI.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
|
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
|
||||||
* MCT supports multiple different types of time values, although all are
|
* MCT supports multiple different types of time values, although all are
|
||||||
@ -78,16 +94,16 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @param {TimeSystem} timeSystem A time system object.
|
* @param {TimeSystem} timeSystem A time system object.
|
||||||
*/
|
*/
|
||||||
addTimeSystem(timeSystem) {
|
TimeAPI.prototype.addTimeSystem = function (timeSystem) {
|
||||||
this.timeSystems.set(timeSystem.key, timeSystem);
|
this.timeSystems.set(timeSystem.key, timeSystem);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {TimeSystem[]}
|
* @returns {TimeSystem[]}
|
||||||
*/
|
*/
|
||||||
getAllTimeSystems() {
|
TimeAPI.prototype.getAllTimeSystems = function () {
|
||||||
return Array.from(this.timeSystems.values());
|
return Array.from(this.timeSystems.values());
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clocks provide a timing source that is used to
|
* Clocks provide a timing source that is used to
|
||||||
@ -110,81 +126,340 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @param {Clock} clock
|
* @param {Clock} clock
|
||||||
*/
|
*/
|
||||||
addClock(clock) {
|
TimeAPI.prototype.addClock = function (clock) {
|
||||||
this.clocks.set(clock.key, clock);
|
this.clocks.set(clock.key, clock);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @returns {Clock[]}
|
* @returns {Clock[]}
|
||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
*/
|
*/
|
||||||
getAllClocks() {
|
TimeAPI.prototype.getAllClocks = function () {
|
||||||
return Array.from(this.clocks.values());
|
return Array.from(this.clocks.values());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set an independent time context which follows the TimeAPI timeSystem,
|
|
||||||
* but with different offsets for a given domain object
|
|
||||||
* @param {key | string} key The identifier key of the domain object these offsets are set for
|
|
||||||
* @param {ClockOffsets | TimeBounds} value This maintains a sliding time window of a fixed width that automatically updates
|
|
||||||
* @param {key | string} clockKey the real time clock key currently in use
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method addIndependentTimeContext
|
|
||||||
*/
|
|
||||||
addIndependentContext(key, value, clockKey) {
|
|
||||||
let timeContext = this.independentContexts.get(key);
|
|
||||||
if (!timeContext) {
|
|
||||||
timeContext = new IndependentTimeContext(this, key);
|
|
||||||
this.independentContexts.set(key, timeContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clockKey) {
|
|
||||||
timeContext.clock(clockKey, value);
|
|
||||||
} else {
|
|
||||||
timeContext.stopClock();
|
|
||||||
timeContext.bounds(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('timeContext', key);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
this.independentContexts.delete(key);
|
|
||||||
timeContext.emit('timeContext', key);
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the independent time context which follows the TimeAPI timeSystem,
|
* Validate the given bounds. This can be used for pre-validation of bounds,
|
||||||
* but with different offsets.
|
* for example by views validating user inputs.
|
||||||
* @param {key | string} key The identifier key of the domain object these offsets
|
* @param {TimeBounds} bounds The start and end time of the conductor.
|
||||||
|
* @returns {string | true} A validation error, or true if valid
|
||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @method getIndependentTimeContext
|
* @method validateBounds
|
||||||
*/
|
*/
|
||||||
getIndependentContext(key) {
|
TimeAPI.prototype.validateBounds = function (bounds) {
|
||||||
return this.independentContexts.get(key);
|
if ((bounds.start === undefined)
|
||||||
|
|| (bounds.end === undefined)
|
||||||
|
|| isNaN(bounds.start)
|
||||||
|
|| isNaN(bounds.end)
|
||||||
|
) {
|
||||||
|
return "Start and end must be specified as integer values";
|
||||||
|
} else if (bounds.start > bounds.end) {
|
||||||
|
return "Specified start date exceeds end bound";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the a timeContext for a view based on it's objectPath. If there is any object in the objectPath with an independent time context, it will be returned.
|
* Validate the given offsets. This can be used for pre-validation of
|
||||||
* Otherwise, the global time context will be returned.
|
* offsets, for example by views validating user inputs.
|
||||||
* @param { Array } objectPath The view's objectPath
|
* @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
|
||||||
|
* @returns {string | true} A validation error, or true if valid
|
||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @method getContextForView
|
* @method validateBounds
|
||||||
*/
|
*/
|
||||||
getContextForView(objectPath) {
|
TimeAPI.prototype.validateOffsets = function (offsets) {
|
||||||
let timeContext = this;
|
if ((offsets.start === undefined)
|
||||||
|
|| (offsets.end === undefined)
|
||||||
objectPath.forEach(item => {
|
|| isNaN(offsets.start)
|
||||||
const key = this.openmct.objects.makeKeyString(item.identifier);
|
|| isNaN(offsets.end)
|
||||||
if (this.independentContexts.get(key)) {
|
) {
|
||||||
timeContext = this.independentContexts.get(key);
|
return "Start and end offsets must be specified as integer values";
|
||||||
|
} else if (offsets.start >= offsets.end) {
|
||||||
|
return "Specified start offset must be < end offset";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} TimeBounds
|
||||||
|
* @property {number} start The start time displayed by the time conductor
|
||||||
|
* in ms since epoch. Epoch determined by currently active time system
|
||||||
|
* @property {number} end The end time displayed by the time conductor in ms
|
||||||
|
* since epoch.
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the start and end time of the time conductor. Basic validation
|
||||||
|
* of bounds is performed.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||||
|
* @throws {Error} Validation error
|
||||||
|
* @fires module:openmct.TimeAPI~bounds
|
||||||
|
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method bounds
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.bounds = function (newBounds) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
const validationResult = this.validateBounds(newBounds);
|
||||||
|
if (validationResult !== true) {
|
||||||
|
throw new Error(validationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create a copy to avoid direct mutation of conductor bounds
|
||||||
|
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
|
||||||
|
/**
|
||||||
|
* The start time, end time, or both have been updated.
|
||||||
|
* @event bounds
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {TimeConductorBounds} bounds The newly updated bounds
|
||||||
|
* @property {boolean} [tick] `true` if the bounds update was due to
|
||||||
|
* a "tick" event (ie. was an automatic update), false otherwise.
|
||||||
|
*/
|
||||||
|
this.emit('bounds', this.boundsVal, false);
|
||||||
|
|
||||||
|
// If a bounds change results in a TOI outside of the current
|
||||||
|
// bounds, unset it
|
||||||
|
if (this.toi < newBounds.start || this.toi > newBounds.end) {
|
||||||
|
this.timeOfInterest(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||||
|
return JSON.parse(JSON.stringify(this.boundsVal));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the time system of the TimeAPI.
|
||||||
|
* @param {TimeSystem | string} timeSystem
|
||||||
|
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||||
|
* @fires module:openmct.TimeAPI~timeSystem
|
||||||
|
* @returns {TimeSystem} The currently applied time system
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method timeSystem
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.timeSystem = function (timeSystemOrKey, bounds) {
|
||||||
|
if (arguments.length >= 1) {
|
||||||
|
if (arguments.length === 1 && !this.activeClock) {
|
||||||
|
throw new Error(
|
||||||
|
"Must specify bounds when changing time system without "
|
||||||
|
+ "an active clock."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeSystem;
|
||||||
|
|
||||||
|
if (timeSystemOrKey === undefined) {
|
||||||
|
throw "Please provide a time system";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof timeSystemOrKey === 'string') {
|
||||||
|
timeSystem = this.timeSystems.get(timeSystemOrKey);
|
||||||
|
|
||||||
|
if (timeSystem === undefined) {
|
||||||
|
throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
|
||||||
|
}
|
||||||
|
} else if (typeof timeSystemOrKey === 'object') {
|
||||||
|
timeSystem = timeSystemOrKey;
|
||||||
|
|
||||||
|
if (!this.timeSystems.has(timeSystem.key)) {
|
||||||
|
throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.system = timeSystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time system used by the time
|
||||||
|
* conductor has changed. A change in Time System will always be
|
||||||
|
* followed by a bounds event specifying new query bounds.
|
||||||
|
*
|
||||||
|
* @event module:openmct.TimeAPI~timeSystem
|
||||||
|
* @property {TimeSystem} The value of the currently applied
|
||||||
|
* Time System
|
||||||
|
* */
|
||||||
|
this.emit('timeSystem', this.system);
|
||||||
|
if (bounds) {
|
||||||
|
this.bounds(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.system;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the Time of Interest. The Time of Interest is a single point
|
||||||
|
* in time, and constitutes the temporal focus of application views. It can
|
||||||
|
* be manipulated by the user from the time conductor or from other views.
|
||||||
|
* The time of interest can effectively be unset by assigning a value of
|
||||||
|
* 'undefined'.
|
||||||
|
* @fires module:openmct.TimeAPI~timeOfInterest
|
||||||
|
* @param newTOI
|
||||||
|
* @returns {number} the current time of interest
|
||||||
|
* @memberof module:openmct.TimeAPI#
|
||||||
|
* @method timeOfInterest
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.timeOfInterest = function (newTOI) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.toi = newTOI;
|
||||||
|
/**
|
||||||
|
* The Time of Interest has moved.
|
||||||
|
* @event timeOfInterest
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {number} Current time of interest
|
||||||
|
*/
|
||||||
|
this.emit('timeOfInterest', this.toi);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.toi;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update bounds based on provided time and current offsets
|
||||||
|
* @private
|
||||||
|
* @param {number} timestamp A time from which boudns will be calculated
|
||||||
|
* using current offsets.
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.tick = function (timestamp) {
|
||||||
|
const newBounds = {
|
||||||
|
start: timestamp + this.offsets.start,
|
||||||
|
end: timestamp + this.offsets.end
|
||||||
|
};
|
||||||
|
|
||||||
|
this.boundsVal = newBounds;
|
||||||
|
this.emit('bounds', this.boundsVal, true);
|
||||||
|
|
||||||
|
// If a bounds change results in a TOI outside of the current
|
||||||
|
// bounds, unset it
|
||||||
|
if (this.toi < newBounds.start || this.toi > newBounds.end) {
|
||||||
|
this.timeOfInterest(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active clock. Tick source will be immediately subscribed to
|
||||||
|
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
||||||
|
* can be unset by calling {@link stopClock}.
|
||||||
|
*
|
||||||
|
* @param {Clock || string} The clock to activate, or its key
|
||||||
|
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
||||||
|
* the start and end bounds. This maintains a sliding time window of a fixed
|
||||||
|
* width that automatically updates.
|
||||||
|
* @fires module:openmct.TimeAPI~clock
|
||||||
|
* @return {Clock} the currently active clock;
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.clock = function (keyOrClock, offsets) {
|
||||||
|
if (arguments.length === 2) {
|
||||||
|
let clock;
|
||||||
|
|
||||||
|
if (typeof keyOrClock === 'string') {
|
||||||
|
clock = this.clocks.get(keyOrClock);
|
||||||
|
if (clock === undefined) {
|
||||||
|
throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
|
||||||
|
}
|
||||||
|
} else if (typeof keyOrClock === 'object') {
|
||||||
|
clock = keyOrClock;
|
||||||
|
if (!this.clocks.has(clock.key)) {
|
||||||
|
throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousClock = this.activeClock;
|
||||||
|
if (previousClock !== undefined) {
|
||||||
|
previousClock.off("tick", this.tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeClock = clock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
||||||
|
* @event clock
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {Clock} clock The newly activated clock, or undefined
|
||||||
|
* if the system is no longer following a clock source
|
||||||
|
*/
|
||||||
|
this.emit("clock", this.activeClock);
|
||||||
|
|
||||||
|
if (this.activeClock !== undefined) {
|
||||||
|
this.clockOffsets(offsets);
|
||||||
|
this.activeClock.on("tick", this.tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (arguments.length === 1) {
|
||||||
|
throw "When setting the clock, clock offsets must also be provided";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.activeClock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clock offsets are used to calculate temporal bounds when the system is
|
||||||
|
* ticking on a clock source.
|
||||||
|
*
|
||||||
|
* @typedef {object} ClockOffsets
|
||||||
|
* @property {number} start A time span relative to the current value of the
|
||||||
|
* ticking clock, from which start bounds will be calculated. This value must
|
||||||
|
* be < 0. When a clock is active, bounds will be calculated automatically
|
||||||
|
* based on the value provided by the clock, and the defined clock offsets.
|
||||||
|
* @property {number} end A time span relative to the current value of the
|
||||||
|
* ticking clock, from which end bounds will be calculated. This value must
|
||||||
|
* be >= 0.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Get or set the currently applied clock offsets. If no parameter is provided,
|
||||||
|
* the current value will be returned. If provided, the new value will be
|
||||||
|
* used as the new clock offsets.
|
||||||
|
* @param {ClockOffsets} offsets
|
||||||
|
* @returns {ClockOffsets}
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.clockOffsets = function (offsets) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
|
||||||
|
const validationResult = this.validateOffsets(offsets);
|
||||||
|
if (validationResult !== true) {
|
||||||
|
throw new Error(validationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.offsets = offsets;
|
||||||
|
|
||||||
|
const currentValue = this.activeClock.currentValue();
|
||||||
|
const newBounds = {
|
||||||
|
start: currentValue + offsets.start,
|
||||||
|
end: currentValue + offsets.end
|
||||||
|
};
|
||||||
|
|
||||||
|
this.bounds(newBounds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event that is triggered when clock offsets change.
|
||||||
|
* @event clockOffsets
|
||||||
|
* @memberof module:openmct.TimeAPI~
|
||||||
|
* @property {ClockOffsets} clockOffsets The newly activated clock
|
||||||
|
* offsets.
|
||||||
|
*/
|
||||||
|
this.emit("clockOffsets", offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.offsets;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the currently active clock from ticking, and unset it. This will
|
||||||
|
* revert all views to showing a static time frame defined by the current
|
||||||
|
* bounds.
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.stopClock = function () {
|
||||||
|
if (this.activeClock) {
|
||||||
|
this.clock(undefined, undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return TimeAPI;
|
||||||
});
|
});
|
||||||
|
|
||||||
return timeContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TimeAPI;
|
|
||||||
|
@ -19,9 +19,8 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import TimeAPI from "./TimeAPI";
|
|
||||||
import {createOpenMct} from "utils/testing";
|
|
||||||
|
|
||||||
|
define(['./TimeAPI'], function (TimeAPI) {
|
||||||
describe("The Time API", function () {
|
describe("The Time API", function () {
|
||||||
let api;
|
let api;
|
||||||
let timeSystemKey;
|
let timeSystemKey;
|
||||||
@ -31,11 +30,9 @@ describe("The Time API", function () {
|
|||||||
let bounds;
|
let bounds;
|
||||||
let eventListener;
|
let eventListener;
|
||||||
let toi;
|
let toi;
|
||||||
let openmct;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
openmct = createOpenMct();
|
api = new TimeAPI();
|
||||||
api = new TimeAPI(openmct);
|
|
||||||
timeSystemKey = "timeSystemKey";
|
timeSystemKey = "timeSystemKey";
|
||||||
timeSystem = {key: timeSystemKey};
|
timeSystem = {key: timeSystemKey};
|
||||||
clockKey = "someClockKey";
|
clockKey = "someClockKey";
|
||||||
@ -259,3 +256,4 @@ describe("The Time API", function () {
|
|||||||
}, true);
|
}, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
@ -1,360 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
import EventEmitter from 'EventEmitter';
|
|
||||||
|
|
||||||
class TimeContext extends EventEmitter {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
//The Time System
|
|
||||||
this.timeSystems = new Map();
|
|
||||||
|
|
||||||
this.system = undefined;
|
|
||||||
|
|
||||||
this.clocks = new Map();
|
|
||||||
|
|
||||||
this.boundsVal = {
|
|
||||||
start: undefined,
|
|
||||||
end: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
this.activeClock = undefined;
|
|
||||||
this.offsets = undefined;
|
|
||||||
|
|
||||||
this.tick = this.tick.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set the time system of the TimeAPI.
|
|
||||||
* @param {TimeSystem | string} timeSystem
|
|
||||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
|
||||||
* @fires module:openmct.TimeAPI~timeSystem
|
|
||||||
* @returns {TimeSystem} The currently applied time system
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method timeSystem
|
|
||||||
*/
|
|
||||||
timeSystem(timeSystemOrKey, bounds) {
|
|
||||||
if (arguments.length >= 1) {
|
|
||||||
if (arguments.length === 1 && !this.activeClock) {
|
|
||||||
throw new Error(
|
|
||||||
"Must specify bounds when changing time system without "
|
|
||||||
+ "an active clock."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeSystem;
|
|
||||||
|
|
||||||
if (timeSystemOrKey === undefined) {
|
|
||||||
throw "Please provide a time system";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof timeSystemOrKey === 'string') {
|
|
||||||
timeSystem = this.timeSystems.get(timeSystemOrKey);
|
|
||||||
|
|
||||||
if (timeSystem === undefined) {
|
|
||||||
throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
|
|
||||||
}
|
|
||||||
} else if (typeof timeSystemOrKey === 'object') {
|
|
||||||
timeSystem = timeSystemOrKey;
|
|
||||||
|
|
||||||
if (!this.timeSystems.has(timeSystem.key)) {
|
|
||||||
throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.system = timeSystem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The time system used by the time
|
|
||||||
* conductor has changed. A change in Time System will always be
|
|
||||||
* followed by a bounds event specifying new query bounds.
|
|
||||||
*
|
|
||||||
* @event module:openmct.TimeAPI~timeSystem
|
|
||||||
* @property {TimeSystem} The value of the currently applied
|
|
||||||
* Time System
|
|
||||||
* */
|
|
||||||
this.emit('timeSystem', this.system);
|
|
||||||
if (bounds) {
|
|
||||||
this.bounds(bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.system;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clock offsets are used to calculate temporal bounds when the system is
|
|
||||||
* ticking on a clock source.
|
|
||||||
*
|
|
||||||
* @typedef {object} ValidationResult
|
|
||||||
* @property {boolean} valid Result of the validation - true or false.
|
|
||||||
* @property {string} message An error message if valid is false.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Validate the given bounds. This can be used for pre-validation of bounds,
|
|
||||||
* for example by views validating user inputs.
|
|
||||||
* @param {TimeBounds} bounds The start and end time of the conductor.
|
|
||||||
* @returns {ValidationResult} A validation error, or true if valid
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method validateBounds
|
|
||||||
*/
|
|
||||||
validateBounds(bounds) {
|
|
||||||
if ((bounds.start === undefined)
|
|
||||||
|| (bounds.end === undefined)
|
|
||||||
|| isNaN(bounds.start)
|
|
||||||
|| isNaN(bounds.end)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Start and end must be specified as integer values"
|
|
||||||
};
|
|
||||||
} else if (bounds.start > bounds.end) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Specified start date exceeds end bound"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
message: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set the start and end time of the time conductor. Basic validation
|
|
||||||
* of bounds is performed.
|
|
||||||
*
|
|
||||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
|
||||||
* @throws {Error} Validation error
|
|
||||||
* @fires module:openmct.TimeAPI~bounds
|
|
||||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method bounds
|
|
||||||
*/
|
|
||||||
bounds(newBounds) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
const validationResult = this.validateBounds(newBounds);
|
|
||||||
if (validationResult.valid !== true) {
|
|
||||||
throw new Error(validationResult.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Create a copy to avoid direct mutation of conductor bounds
|
|
||||||
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
|
|
||||||
/**
|
|
||||||
* The start time, end time, or both have been updated.
|
|
||||||
* @event bounds
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {TimeConductorBounds} bounds The newly updated bounds
|
|
||||||
* @property {boolean} [tick] `true` if the bounds update was due to
|
|
||||||
* a "tick" event (ie. was an automatic update), false otherwise.
|
|
||||||
*/
|
|
||||||
this.emit('bounds', this.boundsVal, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
|
||||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the given offsets. This can be used for pre-validation of
|
|
||||||
* offsets, for example by views validating user inputs.
|
|
||||||
* @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
|
|
||||||
* @returns { ValidationResult } A validation error, and true/false if valid or not
|
|
||||||
* @memberof module:openmct.TimeAPI#
|
|
||||||
* @method validateOffsets
|
|
||||||
*/
|
|
||||||
validateOffsets(offsets) {
|
|
||||||
if ((offsets.start === undefined)
|
|
||||||
|| (offsets.end === undefined)
|
|
||||||
|| isNaN(offsets.start)
|
|
||||||
|| isNaN(offsets.end)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Start and end offsets must be specified as integer values"
|
|
||||||
};
|
|
||||||
} else if (offsets.start >= offsets.end) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
message: "Specified start offset must be < end offset"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
message: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TimeBounds
|
|
||||||
* @property {number} start The start time displayed by the time conductor
|
|
||||||
* in ms since epoch. Epoch determined by currently active time system
|
|
||||||
* @property {number} end The end time displayed by the time conductor in ms
|
|
||||||
* since epoch.
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clock offsets are used to calculate temporal bounds when the system is
|
|
||||||
* ticking on a clock source.
|
|
||||||
*
|
|
||||||
* @typedef {object} ClockOffsets
|
|
||||||
* @property {number} start A time span relative to the current value of the
|
|
||||||
* ticking clock, from which start bounds will be calculated. This value must
|
|
||||||
* be < 0. When a clock is active, bounds will be calculated automatically
|
|
||||||
* based on the value provided by the clock, and the defined clock offsets.
|
|
||||||
* @property {number} end A time span relative to the current value of the
|
|
||||||
* ticking clock, from which end bounds will be calculated. This value must
|
|
||||||
* be >= 0.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Get or set the currently applied clock offsets. If no parameter is provided,
|
|
||||||
* the current value will be returned. If provided, the new value will be
|
|
||||||
* used as the new clock offsets.
|
|
||||||
* @param {ClockOffsets} offsets
|
|
||||||
* @returns {ClockOffsets}
|
|
||||||
*/
|
|
||||||
clockOffsets(offsets) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
|
|
||||||
const validationResult = this.validateOffsets(offsets);
|
|
||||||
if (validationResult.valid !== true) {
|
|
||||||
throw new Error(validationResult.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.offsets = offsets;
|
|
||||||
|
|
||||||
const currentValue = this.activeClock.currentValue();
|
|
||||||
const newBounds = {
|
|
||||||
start: currentValue + offsets.start,
|
|
||||||
end: currentValue + offsets.end
|
|
||||||
};
|
|
||||||
|
|
||||||
this.bounds(newBounds);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event that is triggered when clock offsets change.
|
|
||||||
* @event clockOffsets
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {ClockOffsets} clockOffsets The newly activated clock
|
|
||||||
* offsets.
|
|
||||||
*/
|
|
||||||
this.emit("clockOffsets", offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.offsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the currently active clock from ticking, and unset it. This will
|
|
||||||
* revert all views to showing a static time frame defined by the current
|
|
||||||
* bounds.
|
|
||||||
*/
|
|
||||||
stopClock() {
|
|
||||||
if (this.activeClock) {
|
|
||||||
this.clock(undefined, undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the active clock. Tick source will be immediately subscribed to
|
|
||||||
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
|
||||||
* can be unset by calling {@link stopClock}.
|
|
||||||
*
|
|
||||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
|
||||||
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
|
||||||
* the start and end bounds. This maintains a sliding time window of a fixed
|
|
||||||
* width that automatically updates.
|
|
||||||
* @fires module:openmct.TimeAPI~clock
|
|
||||||
* @return {Clock} the currently active clock;
|
|
||||||
*/
|
|
||||||
clock(keyOrClock, offsets) {
|
|
||||||
if (arguments.length === 2) {
|
|
||||||
let clock;
|
|
||||||
|
|
||||||
if (typeof keyOrClock === 'string') {
|
|
||||||
clock = this.clocks.get(keyOrClock);
|
|
||||||
if (clock === undefined) {
|
|
||||||
throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
|
|
||||||
}
|
|
||||||
} else if (typeof keyOrClock === 'object') {
|
|
||||||
clock = keyOrClock;
|
|
||||||
if (!this.clocks.has(clock.key)) {
|
|
||||||
throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousClock = this.activeClock;
|
|
||||||
if (previousClock !== undefined) {
|
|
||||||
previousClock.off("tick", this.tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activeClock = clock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
|
||||||
* @event clock
|
|
||||||
* @memberof module:openmct.TimeAPI~
|
|
||||||
* @property {Clock} clock The newly activated clock, or undefined
|
|
||||||
* if the system is no longer following a clock source
|
|
||||||
*/
|
|
||||||
this.emit("clock", this.activeClock);
|
|
||||||
|
|
||||||
if (this.activeClock !== undefined) {
|
|
||||||
this.clockOffsets(offsets);
|
|
||||||
this.activeClock.on("tick", this.tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (arguments.length === 1) {
|
|
||||||
throw "When setting the clock, clock offsets must also be provided";
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.activeClock;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update bounds based on provided time and current offsets
|
|
||||||
* @param {number} timestamp A time from which bounds will be calculated
|
|
||||||
* using current offsets.
|
|
||||||
*/
|
|
||||||
tick(timestamp) {
|
|
||||||
if (!this.activeClock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newBounds = {
|
|
||||||
start: timestamp + this.offsets.start,
|
|
||||||
end: timestamp + this.offsets.end
|
|
||||||
};
|
|
||||||
|
|
||||||
this.boundsVal = newBounds;
|
|
||||||
this.emit('bounds', this.boundsVal, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TimeContext;
|
|
@ -1,155 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
import TimeAPI from "./TimeAPI";
|
|
||||||
import {createOpenMct} from "utils/testing";
|
|
||||||
describe("The Independent Time API", function () {
|
|
||||||
let api;
|
|
||||||
let domainObjectKey;
|
|
||||||
let clockKey;
|
|
||||||
let clock;
|
|
||||||
let bounds;
|
|
||||||
let independentBounds;
|
|
||||||
let eventListener;
|
|
||||||
let openmct;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
openmct = createOpenMct();
|
|
||||||
api = new TimeAPI(openmct);
|
|
||||||
clockKey = "someClockKey";
|
|
||||||
clock = jasmine.createSpyObj("clock", [
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"currentValue"
|
|
||||||
]);
|
|
||||||
clock.currentValue.and.returnValue(100);
|
|
||||||
clock.key = clockKey;
|
|
||||||
api.addClock(clock);
|
|
||||||
domainObjectKey = 'test-key';
|
|
||||||
bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 1
|
|
||||||
};
|
|
||||||
api.bounds(bounds);
|
|
||||||
independentBounds = {
|
|
||||||
start: 10,
|
|
||||||
end: 11
|
|
||||||
};
|
|
||||||
eventListener = jasmine.createSpy("eventListener");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Creates an independent time context", () => {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getIndependentContext(domainObjectKey);
|
|
||||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Gets an independent time context given the objectPath", () => {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{
|
|
||||||
identifier: {
|
|
||||||
namespace: '',
|
|
||||||
key: 'blah'
|
|
||||||
}
|
|
||||||
}, { identifier: domainObjectKey }]);
|
|
||||||
expect(timeContext.bounds()).toEqual(independentBounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("defaults to the global time context given the objectPath", () => {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{
|
|
||||||
identifier: {
|
|
||||||
namespace: '',
|
|
||||||
key: 'blah'
|
|
||||||
}
|
|
||||||
}]);
|
|
||||||
expect(timeContext.bounds()).toEqual(bounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Allows setting of valid bounds", function () {
|
|
||||||
bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 1
|
|
||||||
};
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
|
||||||
expect(timeContext.bounds()).not.toEqual(bounds);
|
|
||||||
timeContext.bounds(bounds);
|
|
||||||
expect(timeContext.bounds()).toEqual(bounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Disallows setting of invalid bounds", function () {
|
|
||||||
bounds = {
|
|
||||||
start: 1,
|
|
||||||
end: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
|
||||||
expect(timeContext.bounds()).not.toBe(bounds);
|
|
||||||
|
|
||||||
expect(timeContext.bounds.bind(timeContext, bounds)).toThrow();
|
|
||||||
expect(timeContext.bounds()).not.toEqual(bounds);
|
|
||||||
|
|
||||||
bounds = {start: 1};
|
|
||||||
expect(timeContext.bounds()).not.toEqual(bounds);
|
|
||||||
expect(timeContext.bounds.bind(timeContext, bounds)).toThrow();
|
|
||||||
expect(timeContext.bounds()).not.toEqual(bounds);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Emits an event when bounds change", function () {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
|
||||||
expect(eventListener).not.toHaveBeenCalled();
|
|
||||||
timeContext.on('bounds', eventListener);
|
|
||||||
timeContext.bounds(bounds);
|
|
||||||
expect(eventListener).toHaveBeenCalledWith(bounds, false);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(" when using real time clock", function () {
|
|
||||||
const mockOffsets = {
|
|
||||||
start: 10,
|
|
||||||
end: 11
|
|
||||||
};
|
|
||||||
|
|
||||||
it("Emits an event when bounds change based on current value", function () {
|
|
||||||
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
|
|
||||||
let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
|
|
||||||
expect(eventListener).not.toHaveBeenCalled();
|
|
||||||
timeContext.clock('someClockKey', mockOffsets);
|
|
||||||
timeContext.on('bounds', eventListener);
|
|
||||||
timeContext.tick(10);
|
|
||||||
expect(eventListener).toHaveBeenCalledWith({
|
|
||||||
start: 20,
|
|
||||||
end: 21
|
|
||||||
}, true);
|
|
||||||
destroyTimeContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
@ -63,6 +63,12 @@ define(['./Type'], function (Type) {
|
|||||||
*/
|
*/
|
||||||
TypeRegistry.prototype.standardizeType = function (typeDef) {
|
TypeRegistry.prototype.standardizeType = function (typeDef) {
|
||||||
if (Object.prototype.hasOwnProperty.call(typeDef, 'label')) {
|
if (Object.prototype.hasOwnProperty.call(typeDef, 'label')) {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATION WARNING typeDef: ' + typeDef.label + '. '
|
||||||
|
+ '`label` is deprecated in type definitions. Please use '
|
||||||
|
+ '`name` instead. This will cause errors in a future version '
|
||||||
|
+ 'of Open MCT. For more information, see '
|
||||||
|
+ 'https://github.com/nasa/openmct/issues/1568');
|
||||||
if (!typeDef.name) {
|
if (!typeDef.name) {
|
||||||
typeDef.name = typeDef.label;
|
typeDef.name = typeDef.label;
|
||||||
}
|
}
|
||||||
|
@ -78,9 +78,6 @@ class ImageExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html2canvas(element, {
|
return html2canvas(element, {
|
||||||
useCORS: true,
|
|
||||||
allowTaint: true,
|
|
||||||
logging: false,
|
|
||||||
onclone: function (document) {
|
onclone: function (document) {
|
||||||
if (className) {
|
if (className) {
|
||||||
const clonedElement = document.getElementById(exportId);
|
const clonedElement = document.getElementById(exportId);
|
||||||
@ -90,7 +87,7 @@ class ImageExporter {
|
|||||||
element.id = oldId;
|
element.id = oldId;
|
||||||
},
|
},
|
||||||
removeContainer: true // Set to false to debug what html2canvas renders
|
removeContainer: true // Set to false to debug what html2canvas renders
|
||||||
}).then(canvas => {
|
}).then(function (canvas) {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
@ -105,10 +102,9 @@ class ImageExporter {
|
|||||||
|
|
||||||
return canvas.toBlob(blob => resolve({ blob }), mimeType);
|
return canvas.toBlob(blob => resolve({ blob }), mimeType);
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}, function (error) {
|
||||||
|
console.log('error capturing image', error);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
|
|
||||||
console.error('error capturing image', error);
|
|
||||||
const errorDialog = overlays.dialog({
|
const errorDialog = overlays.dialog({
|
||||||
iconClass: 'error',
|
iconClass: 'error',
|
||||||
message: 'Image was not captured successfully!',
|
message: 'Image was not captured successfully!',
|
||||||
|
@ -1,32 +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 Agent from "../../utils/agent/Agent";
|
|
||||||
import DeviceClassifier from "./src/DeviceClassifier";
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return (openmct) => {
|
|
||||||
openmct.on("start", () => {
|
|
||||||
const agent = new Agent(window);
|
|
||||||
DeviceClassifier(agent, window.document);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,72 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs at application startup and adds a subset of the following
|
|
||||||
* CSS classes to the body of the document, depending on device
|
|
||||||
* attributes:
|
|
||||||
*
|
|
||||||
* * `mobile`: Phones or tablets.
|
|
||||||
* * `phone`: Phones specifically.
|
|
||||||
* * `tablet`: Tablets specifically.
|
|
||||||
* * `desktop`: Non-mobile devices.
|
|
||||||
* * `portrait`: Devices in a portrait-style orientation.
|
|
||||||
* * `landscape`: Devices in a landscape-style orientation.
|
|
||||||
* * `touch`: Device supports touch events.
|
|
||||||
*
|
|
||||||
* @param {utils/agent/Agent} agent
|
|
||||||
* the service used to examine the user agent
|
|
||||||
* @param document the HTML DOM document object
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
import DeviceMatchers from "./DeviceMatchers";
|
|
||||||
|
|
||||||
export default (agent, document) => {
|
|
||||||
const body = document.body;
|
|
||||||
|
|
||||||
Object.keys(DeviceMatchers).forEach((key, index, array) => {
|
|
||||||
if (DeviceMatchers[key](agent)) {
|
|
||||||
body.classList.add(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (agent.isMobile()) {
|
|
||||||
const mediaQuery = window.matchMedia("(orientation: landscape)");
|
|
||||||
function eventHandler(event) {
|
|
||||||
console.log("changed");
|
|
||||||
if (event.matches) {
|
|
||||||
body.classList.remove("portrait");
|
|
||||||
body.classList.add("landscape");
|
|
||||||
} else {
|
|
||||||
body.classList.remove("landscape");
|
|
||||||
body.classList.add("portrait");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaQuery.addEventListener) {
|
|
||||||
mediaQuery.addEventListener(`change`, eventHandler);
|
|
||||||
} else {
|
|
||||||
// Deprecated 'MediaQueryList' API, <Safari 14, IE, <Edge 16
|
|
||||||
mediaQuery.addListener(eventHandler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,105 +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 DeviceClassifier from "./DeviceClassifier";
|
|
||||||
import DeviceMatchers from "./DeviceMatchers";
|
|
||||||
|
|
||||||
const AGENT_METHODS = [
|
|
||||||
"isMobile",
|
|
||||||
"isPhone",
|
|
||||||
"isTablet",
|
|
||||||
"isPortrait",
|
|
||||||
"isLandscape",
|
|
||||||
"isTouch"
|
|
||||||
];
|
|
||||||
const TEST_PERMUTATIONS = [
|
|
||||||
["isMobile", "isPhone", "isTouch", "isPortrait"],
|
|
||||||
["isMobile", "isPhone", "isTouch", "isLandscape"],
|
|
||||||
["isMobile", "isTablet", "isTouch", "isPortrait"],
|
|
||||||
["isMobile", "isTablet", "isTouch", "isLandscape"],
|
|
||||||
["isTouch"],
|
|
||||||
[]
|
|
||||||
];
|
|
||||||
|
|
||||||
describe("DeviceClassifier", function () {
|
|
||||||
let mockAgent;
|
|
||||||
let mockDocument;
|
|
||||||
let mockClassList;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockAgent = jasmine.createSpyObj(
|
|
||||||
"agent",
|
|
||||||
AGENT_METHODS
|
|
||||||
);
|
|
||||||
|
|
||||||
mockClassList = jasmine.createSpyObj("classList", ["add"]);
|
|
||||||
|
|
||||||
mockDocument = jasmine.createSpyObj(
|
|
||||||
"document",
|
|
||||||
{},
|
|
||||||
{ body: { classList: mockClassList } }
|
|
||||||
);
|
|
||||||
|
|
||||||
AGENT_METHODS.forEach(function (m) {
|
|
||||||
mockAgent[m].and.returnValue(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
TEST_PERMUTATIONS.forEach(function (trueMethods) {
|
|
||||||
const summary =
|
|
||||||
trueMethods.length === 0
|
|
||||||
? "device has no detected characteristics"
|
|
||||||
: "device " + trueMethods.join(", ");
|
|
||||||
|
|
||||||
describe("when " + summary, function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
trueMethods.forEach(function (m) {
|
|
||||||
mockAgent[m].and.returnValue(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-new
|
|
||||||
DeviceClassifier(mockAgent, mockDocument);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds classes for matching, detected characteristics", function () {
|
|
||||||
Object.keys(DeviceMatchers)
|
|
||||||
.filter(function (m) {
|
|
||||||
return DeviceMatchers[m](mockAgent);
|
|
||||||
})
|
|
||||||
.forEach(function (key) {
|
|
||||||
expect(mockDocument.body.classList.add).toHaveBeenCalledWith(key);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not add classes for non-matching characteristics", function () {
|
|
||||||
Object.keys(DeviceMatchers)
|
|
||||||
.filter(function (m) {
|
|
||||||
return !DeviceMatchers[m](mockAgent);
|
|
||||||
})
|
|
||||||
.forEach(function (key) {
|
|
||||||
expect(mockDocument.body.classList.add).not.toHaveBeenCalledWith(
|
|
||||||
key
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,57 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object containing key-value pairs, where keys are symbolic of
|
|
||||||
* device attributes, and values are functions that take the
|
|
||||||
* `agent` as inputs and return boolean values indicating
|
|
||||||
* whether or not the current device has these attributes.
|
|
||||||
*
|
|
||||||
* For internal use by the mobile support bundle.
|
|
||||||
*
|
|
||||||
* @memberof src/plugins/DeviceClassifier
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mobile: function (agent) {
|
|
||||||
return agent.isMobile();
|
|
||||||
},
|
|
||||||
phone: function (agent) {
|
|
||||||
return agent.isPhone();
|
|
||||||
},
|
|
||||||
tablet: function (agent) {
|
|
||||||
return agent.isTablet();
|
|
||||||
},
|
|
||||||
desktop: function (agent) {
|
|
||||||
return !agent.isMobile();
|
|
||||||
},
|
|
||||||
portrait: function (agent) {
|
|
||||||
return agent.isPortrait();
|
|
||||||
},
|
|
||||||
landscape: function (agent) {
|
|
||||||
return agent.isLandscape();
|
|
||||||
},
|
|
||||||
touch: function (agent) {
|
|
||||||
return agent.isTouch();
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,65 +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 DeviceMatchers from "./DeviceMatchers";
|
|
||||||
|
|
||||||
describe("DeviceMatchers", function () {
|
|
||||||
let mockAgent;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockAgent = jasmine.createSpyObj("agent", [
|
|
||||||
"isMobile",
|
|
||||||
"isPhone",
|
|
||||||
"isTablet",
|
|
||||||
"isPortrait",
|
|
||||||
"isLandscape",
|
|
||||||
"isTouch"
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("detects when a device is a desktop device", function () {
|
|
||||||
mockAgent.isMobile.and.returnValue(false);
|
|
||||||
expect(DeviceMatchers.desktop(mockAgent)).toBe(true);
|
|
||||||
mockAgent.isMobile.and.returnValue(true);
|
|
||||||
expect(DeviceMatchers.desktop(mockAgent)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
function method(deviceType) {
|
|
||||||
return "is" + deviceType[0].toUpperCase() + deviceType.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[
|
|
||||||
"mobile",
|
|
||||||
"phone",
|
|
||||||
"tablet",
|
|
||||||
"landscape",
|
|
||||||
"portrait",
|
|
||||||
"landscape",
|
|
||||||
"touch"
|
|
||||||
].forEach(function (deviceType) {
|
|
||||||
it("detects when a device is a " + deviceType + " device", function () {
|
|
||||||
mockAgent[method(deviceType)].and.returnValue(true);
|
|
||||||
expect(DeviceMatchers[deviceType](mockAgent)).toBe(true);
|
|
||||||
mockAgent[method(deviceType)].and.returnValue(false);
|
|
||||||
expect(DeviceMatchers[deviceType](mockAgent)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,78 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
function inSelectionPath(openmct, domainObject) {
|
|
||||||
const domainObjectIdentifier = domainObject.identifier;
|
|
||||||
|
|
||||||
return openmct.selection.get().some(selectionPath => {
|
|
||||||
return selectionPath.some(objectInPath => {
|
|
||||||
const objectInPathIdentifier = objectInPath.context.item.identifier;
|
|
||||||
|
|
||||||
return openmct.objects.areIdsEqual(objectInPathIdentifier, domainObjectIdentifier);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ClearDataAction {
|
|
||||||
constructor(openmct, appliesToObjects) {
|
|
||||||
this.name = 'Clear Data for Object';
|
|
||||||
this.key = 'clear-data-action';
|
|
||||||
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
|
|
||||||
this.cssClass = 'icon-clear-data';
|
|
||||||
|
|
||||||
this._openmct = openmct;
|
|
||||||
this._appliesToObjects = appliesToObjects;
|
|
||||||
}
|
|
||||||
invoke(objectPath) {
|
|
||||||
let domainObject = null;
|
|
||||||
if (objectPath) {
|
|
||||||
domainObject = objectPath[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._openmct.objectViews.emit('clearData', domainObject);
|
|
||||||
}
|
|
||||||
appliesTo(objectPath) {
|
|
||||||
if (!objectPath) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contextualDomainObject = objectPath[0];
|
|
||||||
// first check to see if this action applies to this sort of object at all
|
|
||||||
const appliesToThisObject = this._appliesToObjects.some(type => {
|
|
||||||
return contextualDomainObject.type === type;
|
|
||||||
});
|
|
||||||
if (!appliesToThisObject) {
|
|
||||||
// we've selected something not applicable
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const objectInSelectionPath = inSelectionPath(this._openmct, contextualDomainObject);
|
|
||||||
if (objectInSelectionPath) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// if this it doesn't match up, check to see if we're in a composition (i.e., layout)
|
|
||||||
const routerPath = this._openmct.router.path[0];
|
|
||||||
|
|
||||||
return routerPath.type === 'layout';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,24 +20,22 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
export default class ClearDataAction {
|
||||||
<li class="c-inspect-properties__row">
|
constructor(openmct, appliesToObjects) {
|
||||||
<div class="c-inspect-properties__label">
|
this.name = 'Clear Data for Object';
|
||||||
{{ detail.name }}
|
this.key = 'clear-data-action';
|
||||||
</div>
|
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
|
||||||
<div class="c-inspect-properties__value">
|
this.cssClass = 'icon-clear-data';
|
||||||
{{ detail.value }}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
this._openmct = openmct;
|
||||||
export default {
|
this._appliesToObjects = appliesToObjects;
|
||||||
props: {
|
}
|
||||||
detail: {
|
invoke(objectPath) {
|
||||||
type: Object,
|
this._openmct.objectViews.emit('clearData', objectPath[0]);
|
||||||
required: true
|
}
|
||||||
|
appliesTo(objectPath) {
|
||||||
|
let contextualDomainObject = objectPath[0];
|
||||||
|
|
||||||
|
return this._appliesToObjects.filter(type => contextualDomainObject.type === type).length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
</script>
|
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
'./components/globalClearIndicator.vue',
|
'./components/globalClearIndicator.vue',
|
||||||
'./ClearDataAction',
|
'./clearDataAction',
|
||||||
'vue'
|
'vue'
|
||||||
], function (
|
], function (
|
||||||
GlobaClearIndicator,
|
GlobaClearIndicator,
|
||||||
|
@ -1,140 +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 ClearDataActionPlugin from '../plugin.js';
|
|
||||||
import ClearDataAction from '../ClearDataAction.js';
|
|
||||||
|
|
||||||
describe('When the Clear Data Plugin is installed,', () => {
|
|
||||||
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
|
|
||||||
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
|
|
||||||
const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
|
|
||||||
const goodMockSelectionPath = [[{
|
|
||||||
context: {
|
|
||||||
item: {
|
|
||||||
identifier: {
|
|
||||||
key: 'apple',
|
|
||||||
namespace: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]];
|
|
||||||
|
|
||||||
const openmct = {
|
|
||||||
objectViews: mockObjectViews,
|
|
||||||
indicators: mockIndicatorProvider,
|
|
||||||
actions: mockActionsProvider,
|
|
||||||
install: function (plugin) {
|
|
||||||
plugin(this);
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
get: function () {
|
|
||||||
return goodMockSelectionPath;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
objects: {
|
|
||||||
areIdsEqual: function (obj1, obj2) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockObjectPath = [
|
|
||||||
{
|
|
||||||
name: 'mockObject1',
|
|
||||||
type: 'apple'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mockObject2',
|
|
||||||
type: 'banana'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
it('Global Clear Indicator is installed', () => {
|
|
||||||
openmct.install(ClearDataActionPlugin([]));
|
|
||||||
|
|
||||||
expect(mockIndicatorProvider.add).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Clear Data context menu action is installed', () => {
|
|
||||||
openmct.install(ClearDataActionPlugin([]));
|
|
||||||
|
|
||||||
expect(mockActionsProvider.register).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('clear data action emits a clearData event when invoked', () => {
|
|
||||||
const action = new ClearDataAction(openmct);
|
|
||||||
|
|
||||||
action.invoke(mockObjectPath);
|
|
||||||
|
|
||||||
expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('clears data on applicable objects', () => {
|
|
||||||
let action = new ClearDataAction(openmct, ['apple']);
|
|
||||||
|
|
||||||
const actionApplies = action.appliesTo(mockObjectPath);
|
|
||||||
|
|
||||||
expect(actionApplies).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not clear data on inapplicable objects', () => {
|
|
||||||
let action = new ClearDataAction(openmct, ['pineapple']);
|
|
||||||
|
|
||||||
const actionApplies = action.appliesTo(mockObjectPath);
|
|
||||||
|
|
||||||
expect(actionApplies).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not clear data if not in the selection path and not a layout', () => {
|
|
||||||
openmct.objects = {
|
|
||||||
areIdsEqual: function (obj1, obj2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
openmct.router = {
|
|
||||||
path: [{type: 'not-a-layout'}]
|
|
||||||
};
|
|
||||||
|
|
||||||
let action = new ClearDataAction(openmct, ['apple']);
|
|
||||||
|
|
||||||
const actionApplies = action.appliesTo(mockObjectPath);
|
|
||||||
|
|
||||||
expect(actionApplies).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does clear data if not in the selection path and is a layout', () => {
|
|
||||||
openmct.objects = {
|
|
||||||
areIdsEqual: function (obj1, obj2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
openmct.router = {
|
|
||||||
path: [{type: 'layout'}]
|
|
||||||
};
|
|
||||||
|
|
||||||
let action = new ClearDataAction(openmct, ['apple']);
|
|
||||||
|
|
||||||
const actionApplies = action.appliesTo(mockObjectPath);
|
|
||||||
|
|
||||||
expect(actionApplies).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
64
src/plugins/clearData/test/clearDataActionSpec.js
Normal file
64
src/plugins/clearData/test/clearDataActionSpec.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 ClearDataActionPlugin from '../plugin.js';
|
||||||
|
import ClearDataAction from '../clearDataAction.js';
|
||||||
|
|
||||||
|
describe('When the Clear Data Plugin is installed,', function () {
|
||||||
|
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
|
||||||
|
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
|
||||||
|
const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
|
||||||
|
|
||||||
|
const openmct = {
|
||||||
|
objectViews: mockObjectViews,
|
||||||
|
indicators: mockIndicatorProvider,
|
||||||
|
actions: mockActionsProvider,
|
||||||
|
install: function (plugin) {
|
||||||
|
plugin(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockObjectPath = [
|
||||||
|
{name: 'mockObject1'},
|
||||||
|
{name: 'mockObject2'}
|
||||||
|
];
|
||||||
|
|
||||||
|
it('Global Clear Indicator is installed', function () {
|
||||||
|
openmct.install(ClearDataActionPlugin([]));
|
||||||
|
|
||||||
|
expect(mockIndicatorProvider.add).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Clear Data context menu action is installed', function () {
|
||||||
|
openmct.install(ClearDataActionPlugin([]));
|
||||||
|
|
||||||
|
expect(mockActionsProvider.register).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clear data action emits a clearData event when invoked', function () {
|
||||||
|
let action = new ClearDataAction(openmct);
|
||||||
|
|
||||||
|
action.invoke(mockObjectPath);
|
||||||
|
|
||||||
|
expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
|
||||||
|
});
|
||||||
|
});
|
@ -46,7 +46,7 @@ export default function ClockViewProvider(openmct) {
|
|||||||
openmct,
|
openmct,
|
||||||
domainObject
|
domainObject
|
||||||
},
|
},
|
||||||
template: '<clock />'
|
template: '<clock></clock>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
|
@ -99,7 +99,7 @@ export default function ClockPlugin(options) {
|
|||||||
});
|
});
|
||||||
openmct.objectViews.addProvider(new ClockViewProvider(openmct));
|
openmct.objectViews.addProvider(new ClockViewProvider(openmct));
|
||||||
|
|
||||||
if (options && options.enableClockIndicator === true) {
|
if (options && options.enableClockIndicator) {
|
||||||
const clockIndicator = new Vue ({
|
const clockIndicator = new Vue ({
|
||||||
components: {
|
components: {
|
||||||
ClockIndicator
|
ClockIndicator
|
||||||
@ -112,7 +112,7 @@ export default function ClockPlugin(options) {
|
|||||||
indicatorFormat: CLOCK_INDICATOR_FORMAT
|
indicatorFormat: CLOCK_INDICATOR_FORMAT
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template: '<ClockIndicator :indicator-format="indicatorFormat" />'
|
template: '<ClockIndicator :indicator-format="indicatorFormat"></ClockIndicator>'
|
||||||
});
|
});
|
||||||
const indicator = {
|
const indicator = {
|
||||||
element: clockIndicator.$mount().$el,
|
element: clockIndicator.$mount().$el,
|
||||||
|
@ -1,231 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
|
||||||
import clockPlugin from './plugin';
|
|
||||||
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
describe("Clock plugin:", () => {
|
|
||||||
let openmct;
|
|
||||||
let clockDefinition;
|
|
||||||
let element;
|
|
||||||
let child;
|
|
||||||
let appHolder;
|
|
||||||
|
|
||||||
let clockDomainObject;
|
|
||||||
|
|
||||||
function setupClock(enableClockIndicator) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
clockDomainObject = {
|
|
||||||
identifier: {
|
|
||||||
key: 'clock',
|
|
||||||
namespace: 'test-namespace'
|
|
||||||
},
|
|
||||||
type: 'clock'
|
|
||||||
};
|
|
||||||
|
|
||||||
appHolder = document.createElement('div');
|
|
||||||
appHolder.style.width = '640px';
|
|
||||||
appHolder.style.height = '480px';
|
|
||||||
document.body.appendChild(appHolder);
|
|
||||||
|
|
||||||
openmct = createOpenMct();
|
|
||||||
|
|
||||||
element = document.createElement('div');
|
|
||||||
child = document.createElement('div');
|
|
||||||
element.appendChild(child);
|
|
||||||
|
|
||||||
openmct.install(clockPlugin({ enableClockIndicator }));
|
|
||||||
|
|
||||||
clockDefinition = openmct.types.get('clock').definition;
|
|
||||||
clockDefinition.initialize(clockDomainObject);
|
|
||||||
|
|
||||||
openmct.on('start', resolve);
|
|
||||||
openmct.start(appHolder);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Clock view:", () => {
|
|
||||||
let clockViewProvider;
|
|
||||||
let clockView;
|
|
||||||
let clockViewObject;
|
|
||||||
let mutableClockObject;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await setupClock(true);
|
|
||||||
|
|
||||||
clockViewObject = {
|
|
||||||
...clockDomainObject,
|
|
||||||
id: "test-object",
|
|
||||||
name: 'Clock',
|
|
||||||
configuration: {
|
|
||||||
baseFormat: 'YYYY/MM/DD hh:mm:ss',
|
|
||||||
use24: 'clock12',
|
|
||||||
timezone: 'UTC'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve(clockViewObject));
|
|
||||||
spyOn(openmct.objects, 'save').and.returnValue(Promise.resolve(true));
|
|
||||||
|
|
||||||
const applicableViews = openmct.objectViews.get(clockViewObject, [clockViewObject]);
|
|
||||||
clockViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'clock.view');
|
|
||||||
|
|
||||||
mutableClockObject = await openmct.objects.getMutable(clockViewObject.identifier);
|
|
||||||
|
|
||||||
clockView = clockViewProvider.view(mutableClockObject);
|
|
||||||
clockView.show(child);
|
|
||||||
|
|
||||||
await Vue.nextTick();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
clockView.destroy();
|
|
||||||
openmct.objects.destroyMutable(mutableClockObject);
|
|
||||||
if (appHolder) {
|
|
||||||
appHolder.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has name as Clock", () => {
|
|
||||||
expect(clockDefinition.name).toEqual('Clock');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("is creatable", () => {
|
|
||||||
expect(clockDefinition.creatable).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("provides clock view", () => {
|
|
||||||
expect(clockViewProvider).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders clock element", () => {
|
|
||||||
const clockElement = element.querySelectorAll('.c-clock');
|
|
||||||
expect(clockElement.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders major elements", () => {
|
|
||||||
const clockElement = element.querySelector('.c-clock');
|
|
||||||
const timezone = clockElement.querySelector('.c-clock__timezone');
|
|
||||||
const time = clockElement.querySelector('.c-clock__value');
|
|
||||||
const amPm = clockElement.querySelector('.c-clock__ampm');
|
|
||||||
const hasMajorElements = Boolean(timezone && time && amPm);
|
|
||||||
|
|
||||||
expect(hasMajorElements).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders time in UTC", () => {
|
|
||||||
const clockElement = element.querySelector('.c-clock');
|
|
||||||
const timezone = clockElement.querySelector('.c-clock__timezone').textContent.trim();
|
|
||||||
|
|
||||||
expect(timezone).toBe('UTC');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the 24 hour option in the configuration", (done) => {
|
|
||||||
expect(clockDomainObject.configuration.use24).toBe('clock12');
|
|
||||||
const new24Option = 'clock24';
|
|
||||||
|
|
||||||
openmct.objects.observe(clockViewObject, 'configuration', (changedDomainObject) => {
|
|
||||||
expect(changedDomainObject.use24).toBe(new24Option);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.objects.mutate(clockViewObject, 'configuration.use24', new24Option);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the timezone option in the configuration", (done) => {
|
|
||||||
expect(clockDomainObject.configuration.timezone).toBe('UTC');
|
|
||||||
const newZone = 'CST6CDT';
|
|
||||||
|
|
||||||
openmct.objects.observe(clockViewObject, 'configuration', (changedDomainObject) => {
|
|
||||||
expect(changedDomainObject.timezone).toBe(newZone);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.objects.mutate(clockViewObject, 'configuration.timezone', newZone);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the time format option in the configuration", (done) => {
|
|
||||||
expect(clockDomainObject.configuration.baseFormat).toBe('YYYY/MM/DD hh:mm:ss');
|
|
||||||
const newFormat = 'hh:mm:ss';
|
|
||||||
|
|
||||||
openmct.objects.observe(clockViewObject, 'configuration', (changedDomainObject) => {
|
|
||||||
expect(changedDomainObject.baseFormat).toBe(newFormat);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.objects.mutate(clockViewObject, 'configuration.baseFormat', newFormat);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Clock Indicator view:", () => {
|
|
||||||
let clockIndicator;
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (clockIndicator) {
|
|
||||||
clockIndicator.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
clockIndicator = undefined;
|
|
||||||
if (appHolder) {
|
|
||||||
appHolder.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't exist", async () => {
|
|
||||||
await setupClock(false);
|
|
||||||
|
|
||||||
clockIndicator = openmct.indicators.indicatorObjects
|
|
||||||
.find(indicator => indicator.key === 'clock-indicator');
|
|
||||||
|
|
||||||
const clockIndicatorMissing = clockIndicator === null || clockIndicator === undefined;
|
|
||||||
expect(clockIndicatorMissing).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("exists", async () => {
|
|
||||||
await setupClock(true);
|
|
||||||
|
|
||||||
clockIndicator = openmct.indicators.indicatorObjects
|
|
||||||
.find(indicator => indicator.key === 'clock-indicator').element;
|
|
||||||
|
|
||||||
const hasClockIndicator = clockIndicator !== null && clockIndicator !== undefined;
|
|
||||||
expect(hasClockIndicator).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("contains text", async () => {
|
|
||||||
await setupClock(true);
|
|
||||||
|
|
||||||
clockIndicator = openmct.indicators.indicatorObjects
|
|
||||||
.find(indicator => indicator.key === 'clock-indicator').element;
|
|
||||||
|
|
||||||
const clockIndicatorText = clockIndicator.textContent.trim();
|
|
||||||
const textIncludesUTC = clockIndicatorText.includes('UTC');
|
|
||||||
|
|
||||||
expect(textIncludesUTC).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -30,7 +30,7 @@
|
|||||||
<div v-if="staticStyle"
|
<div v-if="staticStyle"
|
||||||
class="c-inspect-styles__style"
|
class="c-inspect-styles__style"
|
||||||
>
|
>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<style-editor class="c-inspect-styles__editor"
|
||||||
:style-item="staticStyle"
|
:style-item="staticStyle"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@persist="updateStaticStyle"
|
@persist="updateStaticStyle"
|
||||||
@ -87,7 +87,7 @@
|
|||||||
<condition-description :show-label="true"
|
<condition-description :show-label="true"
|
||||||
:condition="getCondition(conditionStyle.conditionId)"
|
:condition="getCondition(conditionStyle.conditionId)"
|
||||||
/>
|
/>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<style-editor class="c-inspect-styles__editor"
|
||||||
:style-item="conditionStyle"
|
:style-item="conditionStyle"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@persist="updateConditionalStyle"
|
@persist="updateConditionalStyle"
|
||||||
@ -240,10 +240,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vm = new Vue({
|
let vm = new Vue({
|
||||||
components: {ConditionSetSelectorDialog},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct: this.openmct
|
openmct: this.openmct
|
||||||
},
|
},
|
||||||
|
components: {ConditionSetSelectorDialog},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
handleItemSelection
|
handleItemSelection
|
||||||
@ -273,7 +273,10 @@ export default {
|
|||||||
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
|
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
|
||||||
(objectPath) => {
|
(objectPath) => {
|
||||||
this.objectPath = objectPath;
|
this.objectPath = objectPath;
|
||||||
this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath);
|
this.navigateToPath = '#/browse/' + this.objectPath
|
||||||
|
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
|
||||||
|
.reverse()
|
||||||
|
.join('/');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
<div v-if="staticStyle"
|
<div v-if="staticStyle"
|
||||||
class="c-inspect-styles__style"
|
class="c-inspect-styles__style"
|
||||||
>
|
>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<style-editor class="c-inspect-styles__editor"
|
||||||
:style-item="staticStyle"
|
:style-item="staticStyle"
|
||||||
:is-editing="allowEditing"
|
:is-editing="allowEditing"
|
||||||
:mixed-styles="mixedStyles"
|
:mixed-styles="mixedStyles"
|
||||||
@ -108,7 +108,7 @@
|
|||||||
<condition-description :show-label="true"
|
<condition-description :show-label="true"
|
||||||
:condition="getCondition(conditionStyle.conditionId)"
|
:condition="getCondition(conditionStyle.conditionId)"
|
||||||
/>
|
/>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<style-editor class="c-inspect-styles__editor"
|
||||||
:style-item="conditionStyle"
|
:style-item="conditionStyle"
|
||||||
:non-specific-font-properties="nonSpecificFontProperties"
|
:non-specific-font-properties="nonSpecificFontProperties"
|
||||||
:is-editing="allowEditing"
|
:is-editing="allowEditing"
|
||||||
@ -141,7 +141,6 @@ const NON_STYLEABLE_CONTAINER_TYPES = [
|
|||||||
const NON_STYLEABLE_LAYOUT_ITEM_TYPES = [
|
const NON_STYLEABLE_LAYOUT_ITEM_TYPES = [
|
||||||
'line-view',
|
'line-view',
|
||||||
'box-view',
|
'box-view',
|
||||||
'ellipse-view',
|
|
||||||
'image-view'
|
'image-view'
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -297,7 +296,10 @@ export default {
|
|||||||
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
|
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
|
||||||
(objectPath) => {
|
(objectPath) => {
|
||||||
this.objectPath = objectPath;
|
this.objectPath = objectPath;
|
||||||
this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath);
|
this.navigateToPath = '#/browse/' + this.objectPath
|
||||||
|
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
|
||||||
|
.reverse()
|
||||||
|
.join('/');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -319,7 +321,7 @@ export default {
|
|||||||
if (item) {
|
if (item) {
|
||||||
const type = this.openmct.types.get(item.type);
|
const type = this.openmct.types.get(item.type);
|
||||||
if (type && type.definition) {
|
if (type && type.definition) {
|
||||||
creatable = (type.definition.creatable !== undefined && (type.definition.creatable === 'true' || type.definition.creatable === true));
|
creatable = (type.definition.creatable === true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,10 +558,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vm = new Vue({
|
let vm = new Vue({
|
||||||
components: {ConditionSetSelectorDialog},
|
|
||||||
provide: {
|
provide: {
|
||||||
openmct: this.openmct
|
openmct: this.openmct
|
||||||
},
|
},
|
||||||
|
components: {ConditionSetSelectorDialog},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
handleItemSelection
|
handleItemSelection
|
||||||
|
@ -230,7 +230,7 @@ describe('the plugin', function () {
|
|||||||
};
|
};
|
||||||
const staticStyle = {
|
const staticStyle = {
|
||||||
"style": {
|
"style": {
|
||||||
"backgroundColor": "#666666",
|
"backgroundColor": "#717171",
|
||||||
"border": "1px solid #00ffff"
|
"border": "1px solid #00ffff"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -238,7 +238,7 @@ describe('the plugin', function () {
|
|||||||
"conditionId": "39584410-cbf9-499e-96dc-76f27e69885d",
|
"conditionId": "39584410-cbf9-499e-96dc-76f27e69885d",
|
||||||
"style": {
|
"style": {
|
||||||
"isStyleInvisible": "",
|
"isStyleInvisible": "",
|
||||||
"backgroundColor": "#666666",
|
"backgroundColor": "#717171",
|
||||||
"border": "1px solid #ffff00"
|
"border": "1px solid #ffff00"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -250,7 +250,7 @@ describe('the plugin', function () {
|
|||||||
"configuration": {
|
"configuration": {
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"fill": "#666666",
|
"fill": "#717171",
|
||||||
"stroke": "",
|
"stroke": "",
|
||||||
"x": 1,
|
"x": 1,
|
||||||
"y": 1,
|
"y": 1,
|
||||||
@ -259,22 +259,12 @@ describe('the plugin', function () {
|
|||||||
"type": "box-view",
|
"type": "box-view",
|
||||||
"id": "89b88746-d325-487b-aec4-11b79afff9e8"
|
"id": "89b88746-d325-487b-aec4-11b79afff9e8"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fill": "#666666",
|
|
||||||
"stroke": "",
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"width": 10,
|
|
||||||
"height": 5,
|
|
||||||
"type": "ellipse-view",
|
|
||||||
"id": "19b88746-d325-487b-aec4-11b79afff9z8"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"x": 18,
|
"x": 18,
|
||||||
"y": 9,
|
"y": 9,
|
||||||
"x2": 23,
|
"x2": 23,
|
||||||
"y2": 4,
|
"y2": 4,
|
||||||
"stroke": "#666666",
|
"stroke": "#717171",
|
||||||
"type": "line-view",
|
"type": "line-view",
|
||||||
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
||||||
},
|
},
|
||||||
@ -309,12 +299,12 @@ describe('the plugin', function () {
|
|||||||
"y": 9,
|
"y": 9,
|
||||||
"x2": 23,
|
"x2": 23,
|
||||||
"y2": 4,
|
"y2": 4,
|
||||||
"stroke": "#666666",
|
"stroke": "#717171",
|
||||||
"type": "line-view",
|
"type": "line-view",
|
||||||
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
||||||
};
|
};
|
||||||
boxLayoutItem = {
|
boxLayoutItem = {
|
||||||
"fill": "#666666",
|
"fill": "#717171",
|
||||||
"stroke": "",
|
"stroke": "",
|
||||||
"x": 1,
|
"x": 1,
|
||||||
"y": 1,
|
"y": 1,
|
||||||
|
@ -31,7 +31,6 @@ const styleProps = {
|
|||||||
return !type ? true : (type === 'text-view'
|
return !type ? true : (type === 'text-view'
|
||||||
|| type === 'telemetry-view'
|
|| type === 'telemetry-view'
|
||||||
|| type === 'box-view'
|
|| type === 'box-view'
|
||||||
|| type === 'ellipse-view'
|
|
||||||
|| type === 'subobject-view');
|
|| type === 'subobject-view');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -42,7 +41,6 @@ const styleProps = {
|
|||||||
return !type ? true : (type === 'text-view'
|
return !type ? true : (type === 'text-view'
|
||||||
|| type === 'telemetry-view'
|
|| type === 'telemetry-view'
|
||||||
|| type === 'box-view'
|
|| type === 'box-view'
|
||||||
|| type === 'ellipse-view'
|
|
||||||
|| type === 'image-view'
|
|| type === 'image-view'
|
||||||
|| type === 'line-view'
|
|| type === 'line-view'
|
||||||
|| type === 'subobject-view');
|
|| type === 'subobject-view');
|
||||||
|
@ -38,8 +38,7 @@ a.c-condition-widget {
|
|||||||
|
|
||||||
// Make Condition Widget expand when in a hidden frame Layout context
|
// Make Condition Widget expand when in a hidden frame Layout context
|
||||||
// For both static and Flexible Layouts
|
// For both static and Flexible Layouts
|
||||||
.c-so-view--conditionWidget.c-so-view--no-frame {
|
.c-so-view--no-frame > .c-so-view__object-view > .c-condition-widget {
|
||||||
.c-condition-widget {
|
|
||||||
@include abs();
|
@include abs();
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -47,9 +46,6 @@ a.c-condition-widget {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-so-view__frame-controls { display: none; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add some margin when a Condition Widget is in a Flexible Layout
|
// Add some margin when a Condition Widget is in a Flexible Layout
|
||||||
.c-fl .c-so-view--no-frame .c-condition-widget {
|
.c-fl .c-so-view--no-frame .c-condition-widget {
|
||||||
@include abs(1px);
|
@include abs(1px);
|
||||||
|
@ -149,7 +149,6 @@ define(['lodash'], function (_) {
|
|||||||
return type === 'text-view'
|
return type === 'text-view'
|
||||||
|| type === 'telemetry-view'
|
|| type === 'telemetry-view'
|
||||||
|| type === 'box-view'
|
|| type === 'box-view'
|
||||||
|| type === 'ellipse-view'
|
|
||||||
|| type === 'image-view'
|
|| type === 'image-view'
|
||||||
|| type === 'line-view'
|
|| type === 'line-view'
|
||||||
|| type === 'subobject-view';
|
|| type === 'subobject-view';
|
||||||
@ -181,10 +180,6 @@ define(['lodash'], function (_) {
|
|||||||
"name": "Box",
|
"name": "Box",
|
||||||
"class": "icon-box-round-corners"
|
"class": "icon-box-round-corners"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Ellipse",
|
|
||||||
"class": "icon-circle"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Line",
|
"name": "Line",
|
||||||
"class": "icon-line-horz"
|
"class": "icon-line-horz"
|
||||||
@ -750,7 +745,7 @@ define(['lodash'], function (_) {
|
|||||||
if (toolbar.remove.length === 0) {
|
if (toolbar.remove.length === 0) {
|
||||||
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
|
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
|
||||||
}
|
}
|
||||||
} else if (layoutItem.type === 'box-view' || layoutItem.type === 'ellipse-view') {
|
} else if (layoutItem.type === 'box-view') {
|
||||||
if (toolbar.position.length === 0) {
|
if (toolbar.position.length === 0) {
|
||||||
toolbar.position = [
|
toolbar.position = [
|
||||||
getStackOrder(selectedParent, selectionPath),
|
getStackOrder(selectedParent, selectionPath),
|
||||||
|
@ -43,7 +43,7 @@ import conditionalStylesMixin from '../mixins/objectStyles-mixin';
|
|||||||
export default {
|
export default {
|
||||||
makeDefinition() {
|
makeDefinition() {
|
||||||
return {
|
return {
|
||||||
fill: '#666666',
|
fill: '#717171',
|
||||||
stroke: '',
|
stroke: '',
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
|
@ -76,7 +76,6 @@ import uuid from 'uuid';
|
|||||||
import SubobjectView from './SubobjectView.vue';
|
import SubobjectView from './SubobjectView.vue';
|
||||||
import TelemetryView from './TelemetryView.vue';
|
import TelemetryView from './TelemetryView.vue';
|
||||||
import BoxView from './BoxView.vue';
|
import BoxView from './BoxView.vue';
|
||||||
import EllipseView from './EllipseView.vue';
|
|
||||||
import TextView from './TextView.vue';
|
import TextView from './TextView.vue';
|
||||||
import LineView from './LineView.vue';
|
import LineView from './LineView.vue';
|
||||||
import ImageView from './ImageView.vue';
|
import ImageView from './ImageView.vue';
|
||||||
@ -113,7 +112,6 @@ const ITEM_TYPE_VIEW_MAP = {
|
|||||||
'subobject-view': SubobjectView,
|
'subobject-view': SubobjectView,
|
||||||
'telemetry-view': TelemetryView,
|
'telemetry-view': TelemetryView,
|
||||||
'box-view': BoxView,
|
'box-view': BoxView,
|
||||||
'ellipse-view': EllipseView,
|
|
||||||
'line-view': LineView,
|
'line-view': LineView,
|
||||||
'text-view': TextView,
|
'text-view': TextView,
|
||||||
'image-view': ImageView
|
'image-view': ImageView
|
||||||
|
@ -28,19 +28,19 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-frame-edit__handle c-frame-edit__handle--nw"
|
class="c-frame-edit__handle c-frame-edit__handle--nw"
|
||||||
@mousedown.left="startResize([1,1], [-1,-1], $event)"
|
@mousedown="startResize([1,1], [-1,-1], $event)"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
class="c-frame-edit__handle c-frame-edit__handle--ne"
|
class="c-frame-edit__handle c-frame-edit__handle--ne"
|
||||||
@mousedown.left="startResize([0,1], [1,-1], $event)"
|
@mousedown="startResize([0,1], [1,-1], $event)"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
class="c-frame-edit__handle c-frame-edit__handle--sw"
|
class="c-frame-edit__handle c-frame-edit__handle--sw"
|
||||||
@mousedown.left="startResize([1,0], [-1,1], $event)"
|
@mousedown="startResize([1,0], [-1,1], $event)"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
class="c-frame-edit__handle c-frame-edit__handle--se"
|
class="c-frame-edit__handle c-frame-edit__handle--se"
|
||||||
@mousedown.left="startResize([0,0], [1,1], $event)"
|
@mousedown="startResize([0,0], [1,1], $event)"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,122 +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>
|
|
||||||
<layout-frame
|
|
||||||
:item="item"
|
|
||||||
:grid-size="gridSize"
|
|
||||||
:is-editing="isEditing"
|
|
||||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
|
||||||
@endMove="() => $emit('endMove')"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="c-ellipse-view u-style-receiver js-style-receiver"
|
|
||||||
:class="[styleClass]"
|
|
||||||
:style="style"
|
|
||||||
></div>
|
|
||||||
</layout-frame>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import LayoutFrame from './LayoutFrame.vue';
|
|
||||||
import conditionalStylesMixin from '../mixins/objectStyles-mixin';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
makeDefinition() {
|
|
||||||
return {
|
|
||||||
fill: '#666666',
|
|
||||||
stroke: '',
|
|
||||||
x: 1,
|
|
||||||
y: 1,
|
|
||||||
width: 10,
|
|
||||||
height: 10
|
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
LayoutFrame
|
|
||||||
},
|
|
||||||
mixins: [conditionalStylesMixin],
|
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
gridSize: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
validator: (arr) => arr && arr.length === 2
|
|
||||||
&& arr.every(el => typeof el === 'number')
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
initSelect: Boolean,
|
|
||||||
isEditing: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
style() {
|
|
||||||
if (this.itemStyle) {
|
|
||||||
return this.itemStyle;
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
backgroundColor: this.item.fill,
|
|
||||||
border: this.item.stroke ? '1px solid ' + this.item.stroke : ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
index(newIndex) {
|
|
||||||
if (!this.context) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.context.index = newIndex;
|
|
||||||
},
|
|
||||||
item(newItem) {
|
|
||||||
if (!this.context) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.context.layoutItem = newItem;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.context = {
|
|
||||||
layoutItem: this.item,
|
|
||||||
index: this.index
|
|
||||||
};
|
|
||||||
this.removeSelectable = this.openmct.selection.selectable(
|
|
||||||
this.$el, this.context, this.initSelect);
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
if (this.removeSelectable) {
|
|
||||||
this.removeSelectable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -33,7 +33,7 @@
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div
|
<div
|
||||||
class="c-frame__move-bar"
|
class="c-frame__move-bar"
|
||||||
@mousedown.left="startMove($event)"
|
@mousedown="isEditing ? startMove([1,1], [0,0], $event) : null"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -93,11 +93,7 @@ export default {
|
|||||||
return value - this.initialPosition[index];
|
return value - this.initialPosition[index];
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
startMove(event, posFactor = [1, 1], dimFactor = [0, 0]) {
|
startMove(posFactor, dimFactor, event) {
|
||||||
if (!this.isEditing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.addEventListener('mousemove', this.continueMove);
|
document.body.addEventListener('mousemove', this.continueMove);
|
||||||
document.body.addEventListener('mouseup', this.endMove);
|
document.body.addEventListener('mouseup', this.endMove);
|
||||||
this.dragPosition = {
|
this.dragPosition = {
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="c-frame__move-bar"
|
class="c-frame__move-bar"
|
||||||
@mousedown.left="startDrag($event)"
|
@mousedown="startDrag($event)"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
v-if="showFrameEdit"
|
v-if="showFrameEdit"
|
||||||
@ -96,7 +96,7 @@ export default {
|
|||||||
y: 10,
|
y: 10,
|
||||||
x2: 10,
|
x2: 10,
|
||||||
y2: 5,
|
y2: 5,
|
||||||
stroke: '#666666'
|
stroke: '#717171'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mixins: [conditionalStylesMixin],
|
mixins: [conditionalStylesMixin],
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import LayoutFrame from './LayoutFrame.vue';
|
import LayoutFrame from './LayoutFrame.vue';
|
||||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
||||||
import { getDefaultNotebook, getNotebookSectionAndPage } from '@/plugins/notebook/utils/notebook-storage.js';
|
import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js';
|
||||||
|
|
||||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
|
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
|
||||||
const DEFAULT_POSITION = [1, 1];
|
const DEFAULT_POSITION = [1, 1];
|
||||||
@ -336,16 +336,13 @@ export default {
|
|||||||
},
|
},
|
||||||
async getContextMenuActions() {
|
async getContextMenuActions() {
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
|
const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
|
||||||
|
|
||||||
let defaultNotebookName;
|
let defaultNotebookName;
|
||||||
if (defaultNotebook) {
|
if (defaultNotebook) {
|
||||||
const domainObject = await this.openmct.objects.get(defaultNotebook.identifier);
|
const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
|
||||||
const { section, page } = getNotebookSectionAndPage(domainObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
|
|
||||||
if (section && page) {
|
|
||||||
const defaultPath = domainObject && `${domainObject.name} - ${section.name} - ${page.name}`;
|
|
||||||
defaultNotebookName = `Copy to Notebook ${defaultPath}`;
|
defaultNotebookName = `Copy to Notebook ${defaultPath}`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return CONTEXT_MENU_ACTIONS
|
return CONTEXT_MENU_ACTIONS
|
||||||
.map(actionKey => {
|
.map(actionKey => {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
.c-box-view,
|
.c-box-view {
|
||||||
.c-ellipse-view {
|
|
||||||
border-width: $drawingObjBorderW !important;
|
border-width: $drawingObjBorderW !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
@ -9,10 +8,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-ellipse-view {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-line-view {
|
.c-line-view {
|
||||||
&.c-frame {
|
&.c-frame {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
|
@ -47,8 +47,8 @@
|
|||||||
|
|
||||||
.is-editing {
|
.is-editing {
|
||||||
.l-shell__main-container {
|
.l-shell__main-container {
|
||||||
[s-selected],
|
&[s-selected],
|
||||||
[s-selected-parent] {
|
&[s-selected-parent] {
|
||||||
// Display grid and allow edit marquee to display in main layout holder when editing
|
// Display grid and allow edit marquee to display in main layout holder when editing
|
||||||
> .l-layout {
|
> .l-layout {
|
||||||
background: $editUIGridColorBg;
|
background: $editUIGridColorBg;
|
||||||
|
@ -186,7 +186,7 @@ describe('the plugin', function () {
|
|||||||
'configuration': {
|
'configuration': {
|
||||||
'items': [
|
'items': [
|
||||||
{
|
{
|
||||||
'fill': '#666666',
|
'fill': '#717171',
|
||||||
'stroke': '',
|
'stroke': '',
|
||||||
'x': 1,
|
'x': 1,
|
||||||
'y': 1,
|
'y': 1,
|
||||||
@ -195,22 +195,12 @@ describe('the plugin', function () {
|
|||||||
'type': 'box-view',
|
'type': 'box-view',
|
||||||
'id': '89b88746-d325-487b-aec4-11b79afff9e8'
|
'id': '89b88746-d325-487b-aec4-11b79afff9e8'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'fill': '#666666',
|
|
||||||
'stroke': '',
|
|
||||||
'x': 1,
|
|
||||||
'y': 1,
|
|
||||||
'width': 10,
|
|
||||||
'height': 10,
|
|
||||||
'type': 'ellipse-view',
|
|
||||||
'id': '19b88746-d325-487b-aec4-11b79afff9z8'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
'x': 18,
|
'x': 18,
|
||||||
'y': 9,
|
'y': 9,
|
||||||
'x2': 23,
|
'x2': 23,
|
||||||
'y2': 4,
|
'y2': 4,
|
||||||
'stroke': '#666666',
|
'stroke': '#717171',
|
||||||
'type': 'line-view',
|
'type': 'line-view',
|
||||||
'id': '57d49a28-7863-43bd-9593-6570758916f0'
|
'id': '57d49a28-7863-43bd-9593-6570758916f0'
|
||||||
},
|
},
|
||||||
@ -351,7 +341,7 @@ describe('the plugin', function () {
|
|||||||
it('provides controls including separators', () => {
|
it('provides controls including separators', () => {
|
||||||
const displayLayoutToolbar = openmct.toolbars.get(selection);
|
const displayLayoutToolbar = openmct.toolbars.get(selection);
|
||||||
|
|
||||||
expect(displayLayoutToolbar.length).toBe(7);
|
expect(displayLayoutToolbar.length).toBe(9);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,73 +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 ImageryTimeView from './components/ImageryTimeView.vue';
|
|
||||||
import Vue from "vue";
|
|
||||||
|
|
||||||
export default function ImageryTimestripViewProvider(openmct) {
|
|
||||||
const type = 'example.imagery.time-strip.view';
|
|
||||||
|
|
||||||
function hasImageTelemetry(domainObject) {
|
|
||||||
const metadata = openmct.telemetry.getMetadata(domainObject);
|
|
||||||
if (!metadata) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata.valuesForHints(['image']).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: type,
|
|
||||||
name: 'Imagery Timestrip View',
|
|
||||||
cssClass: 'icon-image',
|
|
||||||
canView: function (domainObject, objectPath) {
|
|
||||||
let isChildOfTimeStrip = objectPath.find(object => object.type === 'time-strip');
|
|
||||||
|
|
||||||
return hasImageTelemetry(domainObject) && isChildOfTimeStrip && !openmct.router.isNavigatedObject(objectPath);
|
|
||||||
},
|
|
||||||
view: function (domainObject, objectPath) {
|
|
||||||
let component;
|
|
||||||
|
|
||||||
return {
|
|
||||||
show: function (element) {
|
|
||||||
component = new Vue({
|
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
ImageryTimeView
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct: openmct,
|
|
||||||
domainObject: domainObject,
|
|
||||||
objectPath: objectPath
|
|
||||||
},
|
|
||||||
template: '<imagery-time-view></imagery-time-view>'
|
|
||||||
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy: function () {
|
|
||||||
component.$destroy();
|
|
||||||
component = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
import ImageryViewComponent from './components/ImageryView.vue';
|
import ImageryViewLayout from './components/ImageryViewLayout.vue';
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export default class ImageryView {
|
|||||||
this.component = new Vue({
|
this.component = new Vue({
|
||||||
el: element,
|
el: element,
|
||||||
components: {
|
components: {
|
||||||
'imagery-view': ImageryViewComponent
|
ImageryViewLayout
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct: this.openmct,
|
openmct: this.openmct,
|
||||||
@ -22,8 +22,7 @@ export default class ImageryView {
|
|||||||
objectPath: this.objectPath,
|
objectPath: this.objectPath,
|
||||||
currentView: this
|
currentView: this
|
||||||
},
|
},
|
||||||
template: '<imagery-view ref="ImageryContainer"></imagery-view>'
|
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,10 +37,8 @@ export default function ImageryViewProvider(openmct) {
|
|||||||
key: type,
|
key: type,
|
||||||
name: 'Imagery Layout',
|
name: 'Imagery Layout',
|
||||||
cssClass: 'icon-image',
|
cssClass: 'icon-image',
|
||||||
canView: function (domainObject, objectPath) {
|
canView: function (domainObject) {
|
||||||
let isChildOfTimeStrip = objectPath.find(object => object.type === 'time-strip');
|
return hasImageTelemetry(domainObject);
|
||||||
|
|
||||||
return hasImageTelemetry(domainObject) && (!isChildOfTimeStrip || openmct.router.isNavigatedObject(objectPath));
|
|
||||||
},
|
},
|
||||||
view: function (domainObject, objectPath) {
|
view: function (domainObject, objectPath) {
|
||||||
return new ImageryView(openmct, domainObject, objectPath);
|
return new ImageryView(openmct, domainObject, objectPath);
|
||||||
|
@ -39,13 +39,10 @@ describe("The Compass component", () => {
|
|||||||
sunAngle: 30
|
sunAngle: 30
|
||||||
};
|
};
|
||||||
let propsData = {
|
let propsData = {
|
||||||
|
containerWidth: 600,
|
||||||
|
containerHeight: 600,
|
||||||
naturalAspectRatio: 0.9,
|
naturalAspectRatio: 0.9,
|
||||||
image: imageDatum,
|
image: imageDatum
|
||||||
sizedImageDimensions: {
|
|
||||||
width: 100,
|
|
||||||
height: 100
|
|
||||||
},
|
|
||||||
compassRoseSizingClasses: '--rose-small --rose-min'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
app = new Vue({
|
app = new Vue({
|
||||||
@ -54,13 +51,13 @@ describe("The Compass component", () => {
|
|||||||
return propsData;
|
return propsData;
|
||||||
},
|
},
|
||||||
template: `<Compass
|
template: `<Compass
|
||||||
:compass-rose-sizing-classes="compassRoseSizingClasses"
|
:container-width="containerWidth"
|
||||||
:image="image"
|
:container-height="containerHeight"
|
||||||
:natural-aspect-ratio="naturalAspectRatio"
|
:natural-aspect-ratio="naturalAspectRatio"
|
||||||
:sized-image-dimensions="sizedImageDimensions"
|
:image="image" />`
|
||||||
/>`
|
|
||||||
});
|
});
|
||||||
instance = app.$mount();
|
instance = app.$mount();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
@ -1,475 +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>
|
|
||||||
<div ref="imagery"
|
|
||||||
class="c-imagery-tsv c-timeline-holder"
|
|
||||||
>
|
|
||||||
<div ref="imageryHolder"
|
|
||||||
class="c-imagery-tsv__contents u-contents"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import * as d3Scale from 'd3-scale';
|
|
||||||
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
|
||||||
import Vue from "vue";
|
|
||||||
import imageryData from "../../imagery/mixins/imageryData";
|
|
||||||
import PreviewAction from "@/ui/preview/PreviewAction";
|
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
const PADDING = 1;
|
|
||||||
const ROW_HEIGHT = 100;
|
|
||||||
const IMAGE_WIDTH_THRESHOLD = 40;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mixins: [imageryData],
|
|
||||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
|
||||||
data() {
|
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
|
||||||
this.metadata = {};
|
|
||||||
this.requestCount = 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
viewBounds: undefined,
|
|
||||||
height: 0,
|
|
||||||
durationFormatter: undefined,
|
|
||||||
imageHistory: [],
|
|
||||||
timeSystem: timeSystem,
|
|
||||||
keyString: undefined
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
imageHistorySize() {
|
|
||||||
return this.imageHistory.length;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
imageHistorySize(newSize, oldSize) {
|
|
||||||
this.updatePlotImagery();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.previewAction = new PreviewAction(this.openmct);
|
|
||||||
|
|
||||||
this.canvas = this.$refs.imagery.appendChild(document.createElement('canvas'));
|
|
||||||
this.canvas.height = 0;
|
|
||||||
this.canvasContext = this.canvas.getContext('2d');
|
|
||||||
this.setDimensions();
|
|
||||||
|
|
||||||
this.updateViewBounds();
|
|
||||||
|
|
||||||
this.openmct.time.on("timeSystem", this.setScaleAndPlotImagery);
|
|
||||||
this.openmct.time.on("bounds", this.updateViewBounds);
|
|
||||||
|
|
||||||
this.resize = _.debounce(this.resize, 400);
|
|
||||||
this.imageryStripResizeObserver = new ResizeObserver(this.resize);
|
|
||||||
this.imageryStripResizeObserver.observe(this.$refs.imagery);
|
|
||||||
|
|
||||||
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
if (this.unsubscribe) {
|
|
||||||
this.unsubscribe();
|
|
||||||
delete this.unsubscribe;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.imageryStripResizeObserver) {
|
|
||||||
this.imageryStripResizeObserver.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.openmct.time.off("timeSystem", this.setScaleAndPlotImagery);
|
|
||||||
this.openmct.time.off("bounds", this.updateViewBounds);
|
|
||||||
if (this.unlisten) {
|
|
||||||
this.unlisten();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
expand(index) {
|
|
||||||
const path = this.objectPath[0];
|
|
||||||
this.previewAction.invoke([path]);
|
|
||||||
},
|
|
||||||
observeForChanges(mutatedObject) {
|
|
||||||
this.updateViewBounds();
|
|
||||||
},
|
|
||||||
resize() {
|
|
||||||
let clientWidth = this.getClientWidth();
|
|
||||||
if (clientWidth !== this.width) {
|
|
||||||
this.setDimensions();
|
|
||||||
this.updateViewBounds();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getClientWidth() {
|
|
||||||
let clientWidth = this.$refs.imagery.clientWidth;
|
|
||||||
|
|
||||||
if (!clientWidth) {
|
|
||||||
//this is a hack - need a better way to find the parent of this component
|
|
||||||
let parent = this.openmct.layout.$refs.browseObject.$el;
|
|
||||||
if (parent) {
|
|
||||||
clientWidth = parent.getBoundingClientRect().width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientWidth;
|
|
||||||
},
|
|
||||||
updateViewBounds(bounds, isTick) {
|
|
||||||
this.viewBounds = this.openmct.time.bounds();
|
|
||||||
//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) {
|
|
||||||
this.timeSystem = this.openmct.time.timeSystem();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setScaleAndPlotImagery(this.timeSystem, !isTick);
|
|
||||||
|
|
||||||
},
|
|
||||||
setScaleAndPlotImagery(timeSystem, clearAllImagery) {
|
|
||||||
if (timeSystem !== undefined) {
|
|
||||||
this.timeSystem = timeSystem;
|
|
||||||
this.timeFormatter = this.getFormatter(this.timeSystem.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setScale(this.timeSystem);
|
|
||||||
this.updatePlotImagery(clearAllImagery);
|
|
||||||
},
|
|
||||||
getFormatter(key) {
|
|
||||||
const metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
|
||||||
|
|
||||||
let metadataValue = metadata.value(key) || { format: key };
|
|
||||||
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
|
||||||
|
|
||||||
return valueFormatter;
|
|
||||||
},
|
|
||||||
updatePlotImagery(clearAllImagery) {
|
|
||||||
this.clearPreviousImagery(clearAllImagery);
|
|
||||||
if (this.xScale) {
|
|
||||||
this.drawImagery();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearPreviousImagery(clearAllImagery) {
|
|
||||||
//TODO: Only clear items that are out of bounds
|
|
||||||
let noItemsEl = this.$el.querySelectorAll(".c-imagery-tsv__no-items");
|
|
||||||
noItemsEl.forEach(item => {
|
|
||||||
item.remove();
|
|
||||||
});
|
|
||||||
let imagery = this.$el.querySelectorAll(".c-imagery-tsv__image-wrapper");
|
|
||||||
imagery.forEach(item => {
|
|
||||||
if (clearAllImagery) {
|
|
||||||
item.remove();
|
|
||||||
} else {
|
|
||||||
const id = this.getNSAttributesForElement(item, 'id');
|
|
||||||
if (id) {
|
|
||||||
const timestamp = id.replace('id-', '');
|
|
||||||
if (!this.isImageryInBounds({
|
|
||||||
time: timestamp
|
|
||||||
})) {
|
|
||||||
item.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setDimensions() {
|
|
||||||
const imageryHolder = this.$refs.imagery;
|
|
||||||
this.width = this.getClientWidth();
|
|
||||||
|
|
||||||
this.height = Math.round(imageryHolder.getBoundingClientRect().height);
|
|
||||||
},
|
|
||||||
setScale(timeSystem) {
|
|
||||||
if (!this.width) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeSystem === undefined) {
|
|
||||||
timeSystem = this.openmct.time.timeSystem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeSystem.isUTCBased) {
|
|
||||||
this.xScale = d3Scale.scaleUtc();
|
|
||||||
this.xScale.domain(
|
|
||||||
[new Date(this.viewBounds.start), new Date(this.viewBounds.end)]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.xScale = d3Scale.scaleLinear();
|
|
||||||
this.xScale.domain(
|
|
||||||
[this.viewBounds.start, this.viewBounds.end]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
|
||||||
},
|
|
||||||
isImageryInBounds(imageObj) {
|
|
||||||
return (imageObj.time < this.viewBounds.end) && (imageObj.time > this.viewBounds.start);
|
|
||||||
},
|
|
||||||
getImageryContainer() {
|
|
||||||
let svgHeight = 100;
|
|
||||||
let svgWidth = this.imageHistory.length ? this.width : 200;
|
|
||||||
let groupSVG;
|
|
||||||
|
|
||||||
let existingSVG = this.$el.querySelector(".c-imagery-tsv__contents svg");
|
|
||||||
if (existingSVG) {
|
|
||||||
groupSVG = existingSVG;
|
|
||||||
this.setNSAttributesForElement(groupSVG, {
|
|
||||||
width: svgWidth
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let component = new Vue({
|
|
||||||
components: {
|
|
||||||
SwimLane
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct: this.openmct
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isNested: true,
|
|
||||||
height: svgHeight,
|
|
||||||
width: svgWidth
|
|
||||||
};
|
|
||||||
},
|
|
||||||
template: `<swim-lane :is-nested="isNested" :hide-label="true"><template slot="object"><svg class="c-imagery-tsv-container" :height="height" :width="width"></svg></template></swim-lane>`
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$refs.imageryHolder.appendChild(component.$mount().$el);
|
|
||||||
|
|
||||||
groupSVG = component.$el.querySelector('svg');
|
|
||||||
|
|
||||||
groupSVG.addEventListener('mouseout', (event) => {
|
|
||||||
if (event.target.nodeName === 'svg' || event.target.nodeName === 'use') {
|
|
||||||
this.removeFromForeground();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupSVG;
|
|
||||||
},
|
|
||||||
isImageryWidthAcceptable() {
|
|
||||||
// We're calculating if there is enough space between images to show the thumbnails.
|
|
||||||
// This algorithm could probably be enhanced to check the x co-ordinate distance between 2 consecutive images, but
|
|
||||||
// we will go with this for now assuming imagery is not sorted by asc time so it's difficult to calculate.
|
|
||||||
// TODO: Use telemetry.requestCollection to get sorted telemetry
|
|
||||||
const currentStart = this.viewBounds.start;
|
|
||||||
const currentEnd = this.viewBounds.end;
|
|
||||||
const rectX = this.xScale(currentStart);
|
|
||||||
const rectY = this.xScale(currentEnd);
|
|
||||||
const imageContainerWidth = this.imageHistory.length ? (rectY - rectX) / this.imageHistory.length : 0;
|
|
||||||
|
|
||||||
return imageContainerWidth < IMAGE_WIDTH_THRESHOLD;
|
|
||||||
},
|
|
||||||
drawImagery() {
|
|
||||||
let groupSVG = this.getImageryContainer();
|
|
||||||
const showImagePlaceholders = this.isImageryWidthAcceptable();
|
|
||||||
|
|
||||||
if (this.imageHistory.length) {
|
|
||||||
this.imageHistory.forEach((currentImageObject, index) => {
|
|
||||||
if (this.isImageryInBounds(currentImageObject)) {
|
|
||||||
this.plotImagery(currentImageObject, showImagePlaceholders, groupSVG, index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.plotNoItems(groupSVG);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plotNoItems(svgElement) {
|
|
||||||
let textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
||||||
this.setNSAttributesForElement(textElement, {
|
|
||||||
x: "10",
|
|
||||||
y: "20",
|
|
||||||
class: "c-imagery-tsv__no-items"
|
|
||||||
});
|
|
||||||
textElement.innerHTML = 'No images within timeframe';
|
|
||||||
|
|
||||||
svgElement.appendChild(textElement);
|
|
||||||
},
|
|
||||||
setNSAttributesForElement(element, attributes) {
|
|
||||||
Object.keys(attributes).forEach((key) => {
|
|
||||||
if (key === 'url') {
|
|
||||||
element.setAttributeNS('http://www.w3.org/1999/xlink', 'href', attributes[key]);
|
|
||||||
} else {
|
|
||||||
element.setAttributeNS(null, key, attributes[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getNSAttributesForElement(element, attribute) {
|
|
||||||
return element.getAttributeNS(null, attribute);
|
|
||||||
},
|
|
||||||
getImageWrapper(item) {
|
|
||||||
const id = `id-${item.time}`;
|
|
||||||
|
|
||||||
return this.$el.querySelector(`.c-imagery-tsv__contents g[id=${id}]`);
|
|
||||||
},
|
|
||||||
plotImagery(item, showImagePlaceholders, svgElement, index) {
|
|
||||||
//TODO: Placeholder image
|
|
||||||
let existingImageWrapper = this.getImageWrapper(item);
|
|
||||||
//imageWrapper wraps the vertical tick rect and the image
|
|
||||||
if (existingImageWrapper) {
|
|
||||||
this.updateExistingImageWrapper(existingImageWrapper, item, showImagePlaceholders);
|
|
||||||
} else {
|
|
||||||
let imageWrapper = this.createImageWrapper(index, item, showImagePlaceholders, svgElement);
|
|
||||||
svgElement.appendChild(imageWrapper);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateExistingImageWrapper(existingImageWrapper, item, showImagePlaceholders) {
|
|
||||||
//Update the x co-ordinates of the handle and image elements and the url of image
|
|
||||||
//this is to avoid tearing down all elements completely and re-drawing them
|
|
||||||
this.setNSAttributesForElement(existingImageWrapper, {
|
|
||||||
showImagePlaceholders
|
|
||||||
});
|
|
||||||
let imageTickElement = existingImageWrapper.querySelector('rect.c-imagery-tsv__image-handle');
|
|
||||||
this.setNSAttributesForElement(imageTickElement, {
|
|
||||||
x: this.xScale(item.time)
|
|
||||||
});
|
|
||||||
|
|
||||||
let imageRect = existingImageWrapper.querySelector('rect.c-imagery-tsv__image-placeholder');
|
|
||||||
this.setNSAttributesForElement(imageRect, {
|
|
||||||
x: this.xScale(item.time) + 2
|
|
||||||
});
|
|
||||||
|
|
||||||
let imageElement = existingImageWrapper.querySelector('image');
|
|
||||||
const selector = `href*=${existingImageWrapper.id}`;
|
|
||||||
let hoverEl = this.$el.querySelector(`.c-imagery-tsv__contents use[${selector}]`);
|
|
||||||
const hideImageUrl = (showImagePlaceholders && !hoverEl);
|
|
||||||
this.setNSAttributesForElement(imageElement, {
|
|
||||||
x: this.xScale(item.time) + 2,
|
|
||||||
url: hideImageUrl ? '' : item.url
|
|
||||||
});
|
|
||||||
},
|
|
||||||
createImageWrapper(index, item, showImagePlaceholders, svgElement) {
|
|
||||||
const id = `id-${item.time}`;
|
|
||||||
const imgSize = String(ROW_HEIGHT - 15);
|
|
||||||
let imageWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
||||||
this.setNSAttributesForElement(imageWrapper, {
|
|
||||||
id,
|
|
||||||
class: 'c-imagery-tsv__image-wrapper',
|
|
||||||
showImagePlaceholders
|
|
||||||
});
|
|
||||||
//create image tick indicator
|
|
||||||
let imageTickElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
||||||
this.setNSAttributesForElement(imageTickElement, {
|
|
||||||
class: 'c-imagery-tsv__image-handle',
|
|
||||||
x: this.xScale(item.time),
|
|
||||||
y: 5,
|
|
||||||
rx: 0,
|
|
||||||
width: 2,
|
|
||||||
height: String(ROW_HEIGHT - 10)
|
|
||||||
});
|
|
||||||
imageWrapper.appendChild(imageTickElement);
|
|
||||||
|
|
||||||
let imageRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
||||||
this.setNSAttributesForElement(imageRect, {
|
|
||||||
class: 'c-imagery-tsv__image-placeholder',
|
|
||||||
x: this.xScale(item.time) + 2,
|
|
||||||
y: 10,
|
|
||||||
rx: 0,
|
|
||||||
width: imgSize,
|
|
||||||
height: imgSize,
|
|
||||||
mask: `#image-${item.time}`
|
|
||||||
});
|
|
||||||
imageWrapper.appendChild(imageRect);
|
|
||||||
|
|
||||||
let imageElement = document.createElementNS('http://www.w3.org/2000/svg', 'image');
|
|
||||||
this.setNSAttributesForElement(imageElement, {
|
|
||||||
id: `image-${item.time}`,
|
|
||||||
x: this.xScale(item.time) + 2,
|
|
||||||
y: 10,
|
|
||||||
rx: 0,
|
|
||||||
width: imgSize,
|
|
||||||
height: imgSize,
|
|
||||||
url: showImagePlaceholders ? '' : item.url
|
|
||||||
});
|
|
||||||
imageWrapper.appendChild(imageElement);
|
|
||||||
|
|
||||||
//TODO: Don't add the hover listener if the width is too small
|
|
||||||
imageWrapper.addEventListener('mouseover', this.bringToForeground.bind(this, svgElement, imageWrapper, index, item.url));
|
|
||||||
|
|
||||||
return imageWrapper;
|
|
||||||
},
|
|
||||||
bringToForeground(svgElement, imageWrapper, index, url, event) {
|
|
||||||
const selector = `href*=${imageWrapper.id}`;
|
|
||||||
let hoverEls = this.$el.querySelectorAll(`.c-imagery-tsv__contents use:not([${selector}])`);
|
|
||||||
if (hoverEls.length > 0) {
|
|
||||||
this.removeFromForeground(hoverEls);
|
|
||||||
}
|
|
||||||
|
|
||||||
hoverEls = this.$el.querySelectorAll(`.c-imagery-tsv__contents use[${selector}]`);
|
|
||||||
if (hoverEls.length) {
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let imageElement = imageWrapper.querySelector('image');
|
|
||||||
this.setNSAttributesForElement(imageElement, {
|
|
||||||
url: url,
|
|
||||||
fill: 'none'
|
|
||||||
});
|
|
||||||
let hoverElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
|
||||||
this.setNSAttributesForElement(hoverElement, {
|
|
||||||
class: 'image-highlight',
|
|
||||||
x: 0,
|
|
||||||
href: `#${imageWrapper.id}`
|
|
||||||
});
|
|
||||||
this.setNSAttributesForElement(imageWrapper, {
|
|
||||||
class: 'c-imagery-tsv__image-wrapper is-hovered'
|
|
||||||
});
|
|
||||||
// We're using mousedown here and not 'click' because 'click' doesn't seem to be triggered reliably
|
|
||||||
hoverElement.addEventListener('mousedown', (e) => {
|
|
||||||
if (e.button === 0) {
|
|
||||||
this.expand(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
svgElement.appendChild(hoverElement);
|
|
||||||
|
|
||||||
},
|
|
||||||
removeFromForeground(items) {
|
|
||||||
let hoverEls;
|
|
||||||
if (items) {
|
|
||||||
hoverEls = items;
|
|
||||||
} else {
|
|
||||||
hoverEls = this.$el.querySelectorAll(".c-imagery-tsv__contents use");
|
|
||||||
}
|
|
||||||
|
|
||||||
hoverEls.forEach(item => {
|
|
||||||
let selector = `id*=${this.getNSAttributesForElement(item, 'href').replace('#', '')}`;
|
|
||||||
let imageWrapper = this.$el.querySelector(`.c-imagery-tsv__contents g[${selector}]`);
|
|
||||||
this.setNSAttributesForElement(imageWrapper, {
|
|
||||||
class: 'c-imagery-tsv__image-wrapper'
|
|
||||||
});
|
|
||||||
let showImagePlaceholders = this.getNSAttributesForElement(imageWrapper, 'showImagePlaceholders');
|
|
||||||
if (showImagePlaceholders === 'true') {
|
|
||||||
let imageElement = imageWrapper.querySelector('image');
|
|
||||||
this.setNSAttributesForElement(imageElement, {
|
|
||||||
url: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
item.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -84,18 +84,18 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
||||||
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev"
|
<button class="c-nav c-nav--prev"
|
||||||
title="Previous image"
|
title="Previous image"
|
||||||
:disabled="isPrevDisabled"
|
:disabled="isPrevDisabled"
|
||||||
@click="prevImage()"
|
@click="prevImage()"
|
||||||
></button>
|
></button>
|
||||||
|
<button class="c-nav c-nav--next"
|
||||||
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next"
|
|
||||||
title="Next image"
|
title="Next image"
|
||||||
:disabled="isNextDisabled"
|
:disabled="isNextDisabled"
|
||||||
@click="nextImage()"
|
@click="nextImage()"
|
||||||
></button>
|
></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="c-imagery__control-bar">
|
<div class="c-imagery__control-bar">
|
||||||
<div class="c-imagery__time">
|
<div class="c-imagery__time">
|
||||||
@ -129,7 +129,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-imagery__thumbs-wrapper"
|
<div
|
||||||
|
class="c-imagery__thumbs-wrapper"
|
||||||
:class="[
|
:class="[
|
||||||
{ 'is-paused': isPaused },
|
{ 'is-paused': isPaused },
|
||||||
{ 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
|
{ 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
|
||||||
@ -174,8 +175,7 @@ import moment from 'moment';
|
|||||||
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
|
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
|
||||||
import Compass from './Compass/Compass.vue';
|
import Compass from './Compass/Compass.vue';
|
||||||
|
|
||||||
import imageryData from "../../imagery/mixins/imageryData";
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
|
||||||
const REFRESH_CSS_MS = 500;
|
const REFRESH_CSS_MS = 500;
|
||||||
const DURATION_TRACK_MS = 1000;
|
const DURATION_TRACK_MS = 1000;
|
||||||
const ARROW_DOWN_DELAY_CHECK_MS = 400;
|
const ARROW_DOWN_DELAY_CHECK_MS = 400;
|
||||||
@ -197,29 +197,30 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
Compass
|
Compass
|
||||||
},
|
},
|
||||||
mixins: [imageryData],
|
|
||||||
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
|
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
|
||||||
data() {
|
data() {
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
this.metadata = {};
|
|
||||||
this.requestCount = 0;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
durationFormatter: undefined,
|
|
||||||
imageHistory: [],
|
|
||||||
timeSystem: timeSystem,
|
|
||||||
keyString: undefined,
|
|
||||||
autoScroll: true,
|
autoScroll: true,
|
||||||
|
durationFormatter: undefined,
|
||||||
filters: {
|
filters: {
|
||||||
brightness: 100,
|
brightness: 100,
|
||||||
contrast: 100
|
contrast: 100
|
||||||
},
|
},
|
||||||
|
imageHistory: [],
|
||||||
thumbnailClick: THUMBNAIL_CLICKED,
|
thumbnailClick: THUMBNAIL_CLICKED,
|
||||||
isPaused: false,
|
isPaused: false,
|
||||||
|
metadata: {},
|
||||||
|
requestCount: 0,
|
||||||
|
timeSystem: timeSystem,
|
||||||
|
timeFormatter: undefined,
|
||||||
refreshCSS: false,
|
refreshCSS: false,
|
||||||
|
keyString: undefined,
|
||||||
focusedImageIndex: undefined,
|
focusedImageIndex: undefined,
|
||||||
focusedImageRelatedTelemetry: {},
|
focusedImageRelatedTelemetry: {},
|
||||||
numericDuration: undefined,
|
numericDuration: undefined,
|
||||||
|
metadataEndpoints: {},
|
||||||
relatedTelemetry: {},
|
relatedTelemetry: {},
|
||||||
latestRelatedTelemetry: {},
|
latestRelatedTelemetry: {},
|
||||||
focusedImageNaturalAspectRatio: undefined,
|
focusedImageNaturalAspectRatio: undefined,
|
||||||
@ -230,9 +231,6 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
imageHistorySize() {
|
|
||||||
return this.imageHistory.length;
|
|
||||||
},
|
|
||||||
compassRoseSizingClasses() {
|
compassRoseSizingClasses() {
|
||||||
let compassRoseSizingClasses = '';
|
let compassRoseSizingClasses = '';
|
||||||
if (this.sizedImageDimensions.width < 300) {
|
if (this.sizedImageDimensions.width < 300) {
|
||||||
@ -260,6 +258,9 @@ export default {
|
|||||||
canTrackDuration() {
|
canTrackDuration() {
|
||||||
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
||||||
},
|
},
|
||||||
|
focusedImageDownloadName() {
|
||||||
|
return this.getImageDownloadName(this.focusedImage);
|
||||||
|
},
|
||||||
isNextDisabled() {
|
isNextDisabled() {
|
||||||
let disabled = false;
|
let disabled = false;
|
||||||
|
|
||||||
@ -382,10 +383,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
imageHistorySize(newSize, oldSize) {
|
|
||||||
this.setFocusedImage(newSize - 1, false);
|
|
||||||
this.scrollToRight();
|
|
||||||
},
|
|
||||||
focusedImageIndex() {
|
focusedImageIndex() {
|
||||||
this.trackDuration();
|
this.trackDuration();
|
||||||
this.resetAgeCSS();
|
this.resetAgeCSS();
|
||||||
@ -395,8 +392,17 @@ export default {
|
|||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
// listen
|
// listen
|
||||||
this.openmct.time.on('timeSystem', this.trackDuration);
|
this.openmct.time.on('bounds', this.boundsChange);
|
||||||
this.openmct.time.on('clock', this.trackDuration);
|
this.openmct.time.on('timeSystem', this.timeSystemChange);
|
||||||
|
this.openmct.time.on('clock', this.clockChange);
|
||||||
|
|
||||||
|
// set
|
||||||
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
|
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
||||||
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
||||||
|
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
|
||||||
|
|
||||||
// related telemetry keys
|
// related telemetry keys
|
||||||
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
||||||
@ -404,49 +410,56 @@ export default {
|
|||||||
this.cameraKeys = ['cameraPan', 'cameraTilt'];
|
this.cameraKeys = ['cameraPan', 'cameraTilt'];
|
||||||
this.sunKeys = ['sunOrientation'];
|
this.sunKeys = ['sunOrientation'];
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
this.timeKey = this.timeSystem.key;
|
||||||
|
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||||
|
|
||||||
|
// kickoff
|
||||||
|
this.subscribe();
|
||||||
|
this.requestHistory();
|
||||||
|
|
||||||
// related telemetry
|
// related telemetry
|
||||||
await this.initializeRelatedTelemetry();
|
await this.initializeRelatedTelemetry();
|
||||||
await this.updateRelatedTelemetryForFocusedImage();
|
this.updateRelatedTelemetryForFocusedImage();
|
||||||
this.trackLatestRelatedTelemetry();
|
this.trackLatestRelatedTelemetry();
|
||||||
|
|
||||||
// for scrolling through images quickly and resizing the object view
|
// for scrolling through images quickly and resizing the object view
|
||||||
this.updateRelatedTelemetryForFocusedImage = _.debounce(this.updateRelatedTelemetryForFocusedImage, 400);
|
_.debounce(this.updateRelatedTelemetryForFocusedImage, 400);
|
||||||
|
_.debounce(this.resizeImageContainer, 400);
|
||||||
|
|
||||||
// for resizing the object view
|
|
||||||
this.resizeImageContainer = _.debounce(this.resizeImageContainer, 400);
|
|
||||||
|
|
||||||
if (this.$refs.imageBG) {
|
|
||||||
this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer);
|
this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer);
|
||||||
this.imageContainerResizeObserver.observe(this.$refs.imageBG);
|
this.imageContainerResizeObserver.observe(this.$refs.imageBG);
|
||||||
}
|
|
||||||
|
|
||||||
// For adjusting scroll bar size and position when resizing thumbs wrapper
|
// For adjusting scroll bar size and position when resizing thumbs wrapper
|
||||||
this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
|
this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
|
||||||
this.handleThumbWindowResizeEnded = _.debounce(this.handleThumbWindowResizeEnded, SCROLL_LATENCY);
|
this.handleThumbWindowResizeEnded = _.debounce(this.handleThumbWindowResizeEnded, SCROLL_LATENCY);
|
||||||
this.handleThumbWindowResizeStart = _.debounce(this.handleThumbWindowResizeStart, SCROLL_LATENCY);
|
|
||||||
|
|
||||||
if (this.$refs.thumbsWrapper) {
|
|
||||||
this.thumbWrapperResizeObserver = new ResizeObserver(this.handleThumbWindowResizeStart);
|
this.thumbWrapperResizeObserver = new ResizeObserver(this.handleThumbWindowResizeStart);
|
||||||
this.thumbWrapperResizeObserver.observe(this.$refs.thumbsWrapper);
|
this.thumbWrapperResizeObserver.observe(this.$refs.thumbsWrapper);
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.openmct.time.off('timeSystem', this.trackDuration);
|
if (this.unsubscribe) {
|
||||||
this.openmct.time.off('clock', this.trackDuration);
|
this.unsubscribe();
|
||||||
|
delete this.unsubscribe;
|
||||||
if (this.thumbWrapperResizeObserver) {
|
|
||||||
this.thumbWrapperResizeObserver.disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.imageContainerResizeObserver) {
|
if (this.imageContainerResizeObserver) {
|
||||||
this.imageContainerResizeObserver.disconnect();
|
this.imageContainerResizeObserver.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.thumbWrapperResizeObserver) {
|
||||||
|
this.thumbWrapperResizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
||||||
this.relatedTelemetry.destroy();
|
this.relatedTelemetry.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.stopDurationTracking();
|
||||||
|
this.openmct.time.off('bounds', this.boundsChange);
|
||||||
|
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
||||||
|
this.openmct.time.off('clock', this.clockChange);
|
||||||
|
|
||||||
// unsubscribe from related telemetry
|
// unsubscribe from related telemetry
|
||||||
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
||||||
for (let key of this.relatedTelemetry.keys) {
|
for (let key of this.relatedTelemetry.keys) {
|
||||||
@ -563,6 +576,56 @@ export default {
|
|||||||
focusElement() {
|
focusElement() {
|
||||||
this.$el.focus();
|
this.$el.focus();
|
||||||
},
|
},
|
||||||
|
datumIsNotValid(datum) {
|
||||||
|
if (this.imageHistory.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const datumURL = this.formatImageUrl(datum);
|
||||||
|
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
|
||||||
|
|
||||||
|
// datum is not valid if it matches the last datum in history,
|
||||||
|
// or it is before the last datum in the history
|
||||||
|
const datumTimeCheck = this.parseTime(datum);
|
||||||
|
const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
|
||||||
|
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
|
||||||
|
const isStale = datumTimeCheck < historyTimeCheck;
|
||||||
|
|
||||||
|
return matchesLast || isStale;
|
||||||
|
},
|
||||||
|
formatImageUrl(datum) {
|
||||||
|
if (!datum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.imageFormatter.format(datum);
|
||||||
|
},
|
||||||
|
formatTime(datum) {
|
||||||
|
if (!datum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dateTimeStr = this.timeFormatter.format(datum);
|
||||||
|
|
||||||
|
// Replace ISO "T" with a space to allow wrapping
|
||||||
|
return dateTimeStr.replace("T", " ");
|
||||||
|
},
|
||||||
|
getImageDownloadName(datum) {
|
||||||
|
let imageDownloadName = '';
|
||||||
|
if (datum) {
|
||||||
|
const key = this.imageDownloadNameHints.key;
|
||||||
|
imageDownloadName = datum[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageDownloadName;
|
||||||
|
},
|
||||||
|
parseTime(datum) {
|
||||||
|
if (!datum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.timeFormatter.parse(datum);
|
||||||
|
},
|
||||||
handleScroll() {
|
handleScroll() {
|
||||||
const thumbsWrapper = this.$refs.thumbsWrapper;
|
const thumbsWrapper = this.$refs.thumbsWrapper;
|
||||||
if (!thumbsWrapper || this.resizingWindow) {
|
if (!thumbsWrapper || this.resizingWindow) {
|
||||||
@ -620,10 +683,6 @@ export default {
|
|||||||
setFocusedImage(index, thumbnailClick = false) {
|
setFocusedImage(index, thumbnailClick = false) {
|
||||||
if (this.isPaused && !thumbnailClick) {
|
if (this.isPaused && !thumbnailClick) {
|
||||||
this.nextImageIndex = index;
|
this.nextImageIndex = index;
|
||||||
//this could happen if bounds changes
|
|
||||||
if (this.focusedImageIndex > this.imageHistory.length - 1) {
|
|
||||||
this.focusedImageIndex = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -634,6 +693,70 @@ export default {
|
|||||||
this.paused(true);
|
this.paused(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
boundsChange(bounds, isTick) {
|
||||||
|
if (!isTick) {
|
||||||
|
this.requestHistory();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async requestHistory() {
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
this.requestCount++;
|
||||||
|
const requestId = this.requestCount;
|
||||||
|
this.imageHistory = [];
|
||||||
|
|
||||||
|
let data = await this.openmct.telemetry
|
||||||
|
.request(this.domainObject, bounds) || [];
|
||||||
|
|
||||||
|
if (this.requestCount === requestId) {
|
||||||
|
data.forEach((datum, index) => {
|
||||||
|
this.updateHistory(datum, index === data.length - 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeSystemChange(system) {
|
||||||
|
this.timeSystem = this.openmct.time.timeSystem();
|
||||||
|
this.timeKey = this.timeSystem.key;
|
||||||
|
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||||
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
this.trackDuration();
|
||||||
|
},
|
||||||
|
clockChange(clock) {
|
||||||
|
this.trackDuration();
|
||||||
|
},
|
||||||
|
subscribe() {
|
||||||
|
this.unsubscribe = this.openmct.telemetry
|
||||||
|
.subscribe(this.domainObject, (datum) => {
|
||||||
|
let parsedTimestamp = this.parseTime(datum);
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
|
||||||
|
if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
|
||||||
|
this.updateHistory(datum);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateHistory(datum, setFocused = true) {
|
||||||
|
if (this.datumIsNotValid(datum)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let image = { ...datum };
|
||||||
|
image.formattedTime = this.formatTime(datum);
|
||||||
|
image.url = this.formatImageUrl(datum);
|
||||||
|
image.time = datum[this.timeKey];
|
||||||
|
image.imageDownloadName = this.getImageDownloadName(datum);
|
||||||
|
|
||||||
|
this.imageHistory.push(image);
|
||||||
|
if (setFocused) {
|
||||||
|
this.setFocusedImage(this.imageHistory.length - 1);
|
||||||
|
this.scrollToRight();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFormatter(key) {
|
||||||
|
let metadataValue = this.metadata.value(key) || { format: key };
|
||||||
|
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||||
|
|
||||||
|
return valueFormatter;
|
||||||
|
},
|
||||||
trackDuration() {
|
trackDuration() {
|
||||||
if (this.canTrackDuration) {
|
if (this.canTrackDuration) {
|
||||||
this.stopDurationTracking();
|
this.stopDurationTracking();
|
||||||
@ -753,10 +876,6 @@ export default {
|
|||||||
}, { once: true });
|
}, { once: true });
|
||||||
},
|
},
|
||||||
resizeImageContainer() {
|
resizeImageContainer() {
|
||||||
if (!this.$refs.imageBG) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.$refs.imageBG.clientWidth !== this.imageContainerWidth) {
|
if (this.$refs.imageBG.clientWidth !== this.imageContainerWidth) {
|
||||||
this.imageContainerWidth = this.$refs.imageBG.clientWidth;
|
this.imageContainerWidth = this.$refs.imageBG.clientWidth;
|
||||||
}
|
}
|
@ -285,17 +285,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-imagery__prev-next-button {
|
.c-imagery__prev-next-buttons {
|
||||||
pointer-events: all;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-75%); // 75% due to transform: rotation approach to the button
|
transform: translateY(-75%);
|
||||||
|
|
||||||
&.c-nav {
|
.c-nav {
|
||||||
position: absolute;
|
pointer-events: all;
|
||||||
|
|
||||||
&--prev { left: 0; }
|
|
||||||
&--next { right: 0; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-status-taking-snapshot & {
|
.s-status-taking-snapshot & {
|
||||||
@ -312,34 +312,3 @@
|
|||||||
@include cArrowButtonSizing($dimOuter: 32px);
|
@include cArrowButtonSizing($dimOuter: 32px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************** IMAGERY IN TIMESTRIP VIEWS */
|
|
||||||
.c-imagery-tsv {
|
|
||||||
g.c-imagery-tsv__image-wrapper {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.is-hovered {
|
|
||||||
filter: brightness(1) contrast(1) !important;
|
|
||||||
[class*='__image-handle'] {
|
|
||||||
fill: $colorBodyFg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__no-items {
|
|
||||||
fill: $colorBodyFg !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__image-handle {
|
|
||||||
fill: rgba($colorBodyFg, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__image-placeholder {
|
|
||||||
fill: pushBack($colorBodyBg, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover g.c-imagery-tsv__image-wrapper {
|
|
||||||
// TODO CH: convert to theme constants
|
|
||||||
filter: brightness(0.5) contrast(0.7);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,174 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
|
||||||
mounted() {
|
|
||||||
// listen
|
|
||||||
this.openmct.time.on('bounds', this.boundsChange);
|
|
||||||
this.openmct.time.on('timeSystem', this.timeSystemChange);
|
|
||||||
|
|
||||||
// set
|
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
|
||||||
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
|
||||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
|
||||||
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
|
||||||
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
|
|
||||||
|
|
||||||
// initialize
|
|
||||||
this.timeKey = this.timeSystem.key;
|
|
||||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
|
||||||
|
|
||||||
// kickoff
|
|
||||||
this.subscribe();
|
|
||||||
this.requestHistory();
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
if (this.unsubscribe) {
|
|
||||||
this.unsubscribe();
|
|
||||||
delete this.unsubscribe;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.openmct.time.off('bounds', this.boundsChange);
|
|
||||||
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
datumIsNotValid(datum) {
|
|
||||||
if (this.imageHistory.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const datumURL = this.formatImageUrl(datum);
|
|
||||||
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
|
|
||||||
|
|
||||||
// datum is not valid if it matches the last datum in history,
|
|
||||||
// or it is before the last datum in the history
|
|
||||||
const datumTimeCheck = this.parseTime(datum);
|
|
||||||
const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
|
|
||||||
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
|
|
||||||
const isStale = datumTimeCheck < historyTimeCheck;
|
|
||||||
|
|
||||||
return matchesLast || isStale;
|
|
||||||
},
|
|
||||||
formatImageUrl(datum) {
|
|
||||||
if (!datum) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.imageFormatter.format(datum);
|
|
||||||
},
|
|
||||||
formatTime(datum) {
|
|
||||||
if (!datum) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dateTimeStr = this.timeFormatter.format(datum);
|
|
||||||
|
|
||||||
// Replace ISO "T" with a space to allow wrapping
|
|
||||||
return dateTimeStr.replace("T", " ");
|
|
||||||
},
|
|
||||||
getImageDownloadName(datum) {
|
|
||||||
let imageDownloadName = '';
|
|
||||||
if (datum) {
|
|
||||||
const key = this.imageDownloadNameHints.key;
|
|
||||||
imageDownloadName = datum[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageDownloadName;
|
|
||||||
},
|
|
||||||
parseTime(datum) {
|
|
||||||
if (!datum) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.timeFormatter.parse(datum);
|
|
||||||
},
|
|
||||||
boundsChange(bounds, isTick) {
|
|
||||||
if (!isTick) {
|
|
||||||
this.requestHistory();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async requestHistory() {
|
|
||||||
let bounds = this.openmct.time.bounds();
|
|
||||||
this.requestCount++;
|
|
||||||
const requestId = this.requestCount;
|
|
||||||
this.imageHistory = [];
|
|
||||||
|
|
||||||
let data = await this.openmct.telemetry
|
|
||||||
.request(this.domainObject, bounds) || [];
|
|
||||||
|
|
||||||
if (this.requestCount === requestId) {
|
|
||||||
let imagery = [];
|
|
||||||
data.forEach((datum) => {
|
|
||||||
let image = this.normalizeDatum(datum);
|
|
||||||
if (image) {
|
|
||||||
imagery.push(image);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//this is to optimize anything that reacts to imageHistory length
|
|
||||||
this.imageHistory = imagery;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
timeSystemChange() {
|
|
||||||
this.timeSystem = this.openmct.time.timeSystem();
|
|
||||||
this.timeKey = this.timeSystem.key;
|
|
||||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
|
||||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
|
||||||
},
|
|
||||||
subscribe() {
|
|
||||||
this.unsubscribe = this.openmct.telemetry
|
|
||||||
.subscribe(this.domainObject, (datum) => {
|
|
||||||
let parsedTimestamp = this.parseTime(datum);
|
|
||||||
let bounds = this.openmct.time.bounds();
|
|
||||||
|
|
||||||
if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
|
|
||||||
let image = this.normalizeDatum(datum);
|
|
||||||
if (image) {
|
|
||||||
this.imageHistory.push(image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
normalizeDatum(datum) {
|
|
||||||
if (this.datumIsNotValid(datum)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = { ...datum };
|
|
||||||
image.formattedTime = this.formatTime(datum);
|
|
||||||
image.url = this.formatImageUrl(datum);
|
|
||||||
image.time = datum[this.timeKey];
|
|
||||||
image.imageDownloadName = this.getImageDownloadName(datum);
|
|
||||||
|
|
||||||
return image;
|
|
||||||
},
|
|
||||||
getFormatter(key) {
|
|
||||||
let metadataValue = this.metadata.value(key) || { format: key };
|
|
||||||
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
|
||||||
|
|
||||||
return valueFormatter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -21,12 +21,10 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import ImageryViewProvider from './ImageryViewProvider';
|
import ImageryViewProvider from './ImageryViewProvider';
|
||||||
import ImageryTimestripViewProvider from './ImageryTimestripViewProvider';
|
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
openmct.objectViews.addProvider(new ImageryViewProvider(openmct));
|
openmct.objectViews.addProvider(new ImageryViewProvider(openmct));
|
||||||
openmct.objectViews.addProvider(new ImageryTimestripViewProvider(openmct));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,19 +32,19 @@ const TEN_MINUTES = ONE_MINUTE * 10;
|
|||||||
const MAIN_IMAGE_CLASS = '.js-imageryView-image';
|
const MAIN_IMAGE_CLASS = '.js-imageryView-image';
|
||||||
const NEW_IMAGE_CLASS = '.c-imagery__age.c-imagery--new';
|
const NEW_IMAGE_CLASS = '.c-imagery__age.c-imagery--new';
|
||||||
const REFRESH_CSS_MS = 500;
|
const REFRESH_CSS_MS = 500;
|
||||||
// const TOLERANCE = 0.50;
|
const TOLERANCE = 0.50;
|
||||||
|
|
||||||
// function comparisonFunction(valueOne, valueTwo) {
|
function comparisonFunction(valueOne, valueTwo) {
|
||||||
// let larger = valueOne;
|
let larger = valueOne;
|
||||||
// let smaller = valueTwo;
|
let smaller = valueTwo;
|
||||||
//
|
|
||||||
// if (larger < smaller) {
|
if (larger < smaller) {
|
||||||
// larger = valueTwo;
|
larger = valueTwo;
|
||||||
// smaller = valueOne;
|
smaller = valueOne;
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// return (larger - smaller) < TOLERANCE;
|
return (larger - smaller) < TOLERANCE;
|
||||||
// }
|
}
|
||||||
|
|
||||||
function getImageInfo(doc) {
|
function getImageInfo(doc) {
|
||||||
let imageElement = doc.querySelectorAll(MAIN_IMAGE_CLASS)[0];
|
let imageElement = doc.querySelectorAll(MAIN_IMAGE_CLASS)[0];
|
||||||
@ -84,14 +84,12 @@ function generateTelemetry(start, count) {
|
|||||||
return telemetry;
|
return telemetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("The Imagery View Layouts", () => {
|
describe("The Imagery View Layout", () => {
|
||||||
const imageryKey = 'example.imagery';
|
const imageryKey = 'example.imagery';
|
||||||
const imageryForTimeStripKey = 'example.imagery.time-strip.view';
|
|
||||||
const START = Date.now();
|
const START = Date.now();
|
||||||
const COUNT = 10;
|
const COUNT = 10;
|
||||||
|
|
||||||
let resolveFunction;
|
let resolveFunction;
|
||||||
let originalRouterPath;
|
|
||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
let appHolder;
|
let appHolder;
|
||||||
@ -118,51 +116,51 @@ describe("The Imagery View Layouts", () => {
|
|||||||
"image": 1,
|
"image": 1,
|
||||||
"priority": 3
|
"priority": 3
|
||||||
},
|
},
|
||||||
"source": "url"
|
"source": "url",
|
||||||
// "relatedTelemetry": {
|
"relatedTelemetry": {
|
||||||
// "heading": {
|
"heading": {
|
||||||
// "comparisonFunction": comparisonFunction,
|
"comparisonFunction": comparisonFunction,
|
||||||
// "historical": {
|
"historical": {
|
||||||
// "telemetryObjectId": "heading",
|
"telemetryObjectId": "heading",
|
||||||
// "valueKey": "value"
|
"valueKey": "value"
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// "roll": {
|
"roll": {
|
||||||
// "comparisonFunction": comparisonFunction,
|
"comparisonFunction": comparisonFunction,
|
||||||
// "historical": {
|
"historical": {
|
||||||
// "telemetryObjectId": "roll",
|
"telemetryObjectId": "roll",
|
||||||
// "valueKey": "value"
|
"valueKey": "value"
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// "pitch": {
|
"pitch": {
|
||||||
// "comparisonFunction": comparisonFunction,
|
"comparisonFunction": comparisonFunction,
|
||||||
// "historical": {
|
"historical": {
|
||||||
// "telemetryObjectId": "pitch",
|
"telemetryObjectId": "pitch",
|
||||||
// "valueKey": "value"
|
"valueKey": "value"
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// "cameraPan": {
|
"cameraPan": {
|
||||||
// "comparisonFunction": comparisonFunction,
|
"comparisonFunction": comparisonFunction,
|
||||||
// "historical": {
|
"historical": {
|
||||||
// "telemetryObjectId": "cameraPan",
|
"telemetryObjectId": "cameraPan",
|
||||||
// "valueKey": "value"
|
"valueKey": "value"
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// "cameraTilt": {
|
"cameraTilt": {
|
||||||
// "comparisonFunction": comparisonFunction,
|
"comparisonFunction": comparisonFunction,
|
||||||
// "historical": {
|
"historical": {
|
||||||
// "telemetryObjectId": "cameraTilt",
|
"telemetryObjectId": "cameraTilt",
|
||||||
// "valueKey": "value"
|
"valueKey": "value"
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// "sunOrientation": {
|
"sunOrientation": {
|
||||||
// "comparisonFunction": comparisonFunction,
|
"comparisonFunction": comparisonFunction,
|
||||||
// "historical": {
|
"historical": {
|
||||||
// "telemetryObjectId": "sunOrientation",
|
"telemetryObjectId": "sunOrientation",
|
||||||
// "valueKey": "value"
|
"valueKey": "value"
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@ -220,9 +218,7 @@ describe("The Imagery View Layouts", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve(imageryObject));
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
originalRouterPath = openmct.router.path;
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.start(appHolder);
|
openmct.start(appHolder);
|
||||||
@ -233,34 +229,10 @@ describe("The Imagery View Layouts", () => {
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 1
|
end: 1
|
||||||
});
|
});
|
||||||
openmct.router.path = originalRouterPath;
|
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should provide an imagery time strip view when in a time strip", () => {
|
|
||||||
openmct.router.path = [{
|
|
||||||
identifier: {
|
|
||||||
key: 'test-timestrip',
|
|
||||||
namespace: ''
|
|
||||||
},
|
|
||||||
type: 'time-strip'
|
|
||||||
}];
|
|
||||||
|
|
||||||
let applicableViews = openmct.objectViews.get(imageryObject, [imageryObject, {
|
|
||||||
identifier: {
|
|
||||||
key: 'test-timestrip',
|
|
||||||
namespace: ''
|
|
||||||
},
|
|
||||||
type: 'time-strip'
|
|
||||||
}]);
|
|
||||||
let imageryView = applicableViews.find(
|
|
||||||
viewProvider => viewProvider.key === imageryForTimeStripKey
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(imageryView).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should provide an imagery view only for imagery producing objects", () => {
|
it("should provide an imagery view only for imagery producing objects", () => {
|
||||||
let applicableViews = openmct.objectViews.get(imageryObject, []);
|
let applicableViews = openmct.objectViews.get(imageryObject, []);
|
||||||
let imageryView = applicableViews.find(
|
let imageryView = applicableViews.find(
|
||||||
@ -270,46 +242,6 @@ describe("The Imagery View Layouts", () => {
|
|||||||
expect(imageryView).toBeDefined();
|
expect(imageryView).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not provide an imagery view when in a time strip", () => {
|
|
||||||
openmct.router.path = [{
|
|
||||||
identifier: {
|
|
||||||
key: 'test-timestrip',
|
|
||||||
namespace: ''
|
|
||||||
},
|
|
||||||
type: 'time-strip'
|
|
||||||
}];
|
|
||||||
|
|
||||||
let applicableViews = openmct.objectViews.get(imageryObject, [imageryObject, {
|
|
||||||
identifier: {
|
|
||||||
key: 'test-timestrip',
|
|
||||||
namespace: ''
|
|
||||||
},
|
|
||||||
type: 'time-strip'
|
|
||||||
}]);
|
|
||||||
let imageryView = applicableViews.find(
|
|
||||||
viewProvider => viewProvider.key === imageryKey
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(imageryView).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should provide an imagery view when navigated to in the composition of a time strip", () => {
|
|
||||||
openmct.router.path = [imageryObject];
|
|
||||||
|
|
||||||
let applicableViews = openmct.objectViews.get(imageryObject, [imageryObject, {
|
|
||||||
identifier: {
|
|
||||||
key: 'test-timestrip',
|
|
||||||
namespace: ''
|
|
||||||
},
|
|
||||||
type: 'time-strip'
|
|
||||||
}]);
|
|
||||||
let imageryView = applicableViews.find(
|
|
||||||
viewProvider => viewProvider.key === imageryKey
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(imageryView).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("imagery view", () => {
|
describe("imagery view", () => {
|
||||||
let applicableViews;
|
let applicableViews;
|
||||||
let imageryViewProvider;
|
let imageryViewProvider;
|
||||||
@ -370,15 +302,18 @@ describe("The Imagery View Layouts", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show that an image is not new", (done) => {
|
xit("should show that an image is not new", (done) => {
|
||||||
const target = imageTelemetry[2].url;
|
const target = imageTelemetry[2].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
|
// used in code, need to wait to the 500ms here too
|
||||||
|
setTimeout(() => {
|
||||||
const imageIsNew = isNew(parent);
|
const imageIsNew = isNew(parent);
|
||||||
|
|
||||||
expect(imageIsNew).toBeFalse();
|
expect(imageIsNew).toBeFalse();
|
||||||
done();
|
done();
|
||||||
|
}, REFRESH_CSS_MS);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -432,18 +367,18 @@ describe("The Imagery View Layouts", () => {
|
|||||||
});
|
});
|
||||||
it ('shows an auto scroll button when scroll to left', async () => {
|
it ('shows an auto scroll button when scroll to left', async () => {
|
||||||
// to mock what a scroll would do
|
// to mock what a scroll would do
|
||||||
imageryView._getInstance().$refs.ImageryContainer.autoScroll = false;
|
imageryView._getInstance().$refs.ImageryLayout.autoScroll = false;
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
let autoScrollButton = parent.querySelector('.c-imagery__auto-scroll-resume-button');
|
let autoScrollButton = parent.querySelector('.c-imagery__auto-scroll-resume-button');
|
||||||
expect(autoScrollButton).toBeTruthy();
|
expect(autoScrollButton).toBeTruthy();
|
||||||
});
|
});
|
||||||
it ('scrollToRight is called when clicking on auto scroll button', async () => {
|
it ('scrollToRight is called when clicking on auto scroll button', async () => {
|
||||||
// use spyon to spy the scroll function
|
// use spyon to spy the scroll function
|
||||||
spyOn(imageryView._getInstance().$refs.ImageryContainer, 'scrollToRight');
|
spyOn(imageryView._getInstance().$refs.ImageryLayout, 'scrollToRight');
|
||||||
imageryView._getInstance().$refs.ImageryContainer.autoScroll = false;
|
imageryView._getInstance().$refs.ImageryLayout.autoScroll = false;
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
parent.querySelector('.c-imagery__auto-scroll-resume-button').click();
|
parent.querySelector('.c-imagery__auto-scroll-resume-button').click();
|
||||||
expect(imageryView._getInstance().$refs.ImageryContainer.scrollToRight).toHaveBeenCalledWith('reset');
|
expect(imageryView._getInstance().$refs.ImageryLayout.scrollToRight).toHaveBeenCalledWith('reset');
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user