Compare commits
32 Commits
1.7.5-mast
...
condition-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a4eeb6a32 | ||
|
|
4df88ddccf | ||
|
|
f2b2953a5d | ||
|
|
62de310686 | ||
|
|
4b9ff67e49 | ||
|
|
d5e32ec494 | ||
|
|
38880ba3d1 | ||
|
|
a99ce7733c | ||
|
|
9f48764210 | ||
|
|
a1aaa0dd41 | ||
|
|
bee15e98c8 | ||
|
|
092bbe547d | ||
|
|
6cbe05317c | ||
|
|
3b92fcdf6c | ||
|
|
6dde54bd25 | ||
|
|
359e7377ac | ||
|
|
9f4190f781 | ||
|
|
f3fc991a74 | ||
|
|
2564e75fc9 | ||
|
|
f42fe78acf | ||
|
|
fe928a1386 | ||
|
|
b329ed6ed5 | ||
|
|
9b7a0d7e4c | ||
|
|
5c15e53abb | ||
|
|
f58b3881f2 | ||
|
|
071a13b219 | ||
|
|
ca66898e51 | ||
|
|
94c7b2343a | ||
|
|
c397c336ab | ||
|
|
eea23f2caf | ||
|
|
6665641c02 | ||
|
|
c3ebf52dd2 |
@@ -56,14 +56,38 @@ workflows:
|
||||
browser: ChromeHeadless
|
||||
always-pass: false
|
||||
- test:
|
||||
name: node12-firefoxESR
|
||||
name: node12-firefoxESR-build-only
|
||||
node-version: lts/erbium
|
||||
browser: FirefoxESR
|
||||
always-pass: true
|
||||
- test:
|
||||
name: node14-chrome
|
||||
name: node14-chrome-build-only
|
||||
node-version: lts/fermium
|
||||
browser: ChromeHeadless
|
||||
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
|
||||
|
||||
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,11 +1,12 @@
|
||||
<!--- This is for filing bugs. If you have a general question, please -->
|
||||
<!--- visit https://github.com/nasa/openmct/discussions -->
|
||||
|
||||
---
|
||||
name: Bug Report
|
||||
name: Bug report
|
||||
about: File a Bug !
|
||||
title: ''
|
||||
labels: type:bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--- Focus on user impact in the title. Use the Summary Field to -->
|
||||
<!--- describe the problem technically. -->
|
||||
|
||||
@@ -35,7 +36,7 @@ about: File a Bug !
|
||||
|
||||
#### Environment
|
||||
* Open MCT Version: <!--- date of build, version, or SHA -->
|
||||
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yams? -->
|
||||
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yamcs? -->
|
||||
* OS:
|
||||
* Browser:
|
||||
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Discussions
|
||||
url: https://github.com/nasa/openmct/discussions
|
||||
about: Got a question?
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Enhancement request
|
||||
about: Suggest an enhancement or new improvement for this project
|
||||
title: ''
|
||||
labels: type:enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
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
|
||||
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
|
||||
returing a 2xx status code. The URL Status Indicator is made available as a default plugin. See
|
||||
returning 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
|
||||
URL Status Indicator.
|
||||
|
||||
|
||||
@@ -423,7 +423,7 @@ which can help with this, however.
|
||||
instead of separate approaches for static and substitutable
|
||||
dependencies.
|
||||
* Removes need to understand Angular's DI mechanism.
|
||||
* Improves useability of documentation (`typeService` is an
|
||||
* Improves usability of documentation (`typeService` is an
|
||||
instance of `CompositeService` and implements `TypeService`
|
||||
so you can easily traverse links in the JSDoc.)
|
||||
* Can be used more easily from Web Workers, allowing services
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
## Legacy Documentation
|
||||
|
||||
As we transition to a new API, the following documentation for the old API
|
||||
(which is supported during the transtion) may be useful as well:
|
||||
(which is supported during the transition) may be useful as well:
|
||||
|
||||
* The [Architecture Overview](architecture/) describes the concepts used
|
||||
throughout Open MCT, and gives a high level overview of the platform's design.
|
||||
|
||||
@@ -63,7 +63,7 @@ define([
|
||||
|
||||
StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
||||
var start = options.start;
|
||||
var end = options.end;
|
||||
var end = Math.min(Date.now(), options.end); // no future values
|
||||
var duration = domainObject.telemetry.duration * 1000;
|
||||
if (options.strategy === 'latest' || options.size === 1) {
|
||||
start = end;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Vue from 'Vue';
|
||||
import Vue from 'vue';
|
||||
import HelloWorld from './HelloWorld.vue';
|
||||
|
||||
function SimpleVuePlugin() {
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
<h2>How to Use Glyphs</h2>
|
||||
<div class="cols cols1-1">
|
||||
<div class="col">
|
||||
<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>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>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>
|
||||
<mct-example><a class="s-button icon-gear" title="Settings"></a>
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
openmct.install(openmct.plugins.ExampleImagery());
|
||||
openmct.install(openmct.plugins.PlanLayout());
|
||||
openmct.install(openmct.plugins.Timeline());
|
||||
openmct.install(openmct.plugins.Hyperlink());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
openmct.install(openmct.plugins.AutoflowView({
|
||||
type: "telemetry.panel"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "1.7.4",
|
||||
"version": "1.7.7",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
@@ -41,7 +41,7 @@
|
||||
"jsdoc": "^3.3.2",
|
||||
"karma": "6.3.4",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-firefox-launcher": "2.1.0",
|
||||
"karma-firefox-launcher": "2.1.1",
|
||||
"karma-cli": "2.0.0",
|
||||
"karma-coverage": "2.0.3",
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
|
||||
@@ -64,7 +64,7 @@ define(
|
||||
*
|
||||
* @param {DomainObject} domainObject the domain object to navigate to
|
||||
* @param {Boolean} force if true, force navigation to occur.
|
||||
* @returns {Boolean} true if navigation occured, otherwise false.
|
||||
* @returns {Boolean} true if navigation occurred, otherwise false.
|
||||
*/
|
||||
NavigationService.prototype.setNavigation = function (domainObject, force) {
|
||||
if (force) {
|
||||
|
||||
@@ -21,28 +21,14 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/MCTDevice",
|
||||
"./src/AgentService",
|
||||
"./src/DeviceClassifier"
|
||||
"./src/AgentService"
|
||||
], function (
|
||||
MCTDevice,
|
||||
AgentService,
|
||||
DeviceClassifier
|
||||
AgentService
|
||||
) {
|
||||
|
||||
return {
|
||||
name: "platform/commonUI/mobile",
|
||||
definition: {
|
||||
"extensions": {
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctDevice",
|
||||
"implementation": MCTDevice,
|
||||
"depends": [
|
||||
"agentService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "agentService",
|
||||
@@ -51,15 +37,6 @@ define([
|
||||
"$window"
|
||||
]
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
{
|
||||
"implementation": DeviceClassifier,
|
||||
"depends": [
|
||||
"agentService",
|
||||
"$document"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,122 +20,12 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Provides features which support variant behavior on mobile devices.
|
||||
*
|
||||
* @namespace platform/commonUI/mobile
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
define(["../../../../src/utils/agent/Agent.js"], function (Agent) {
|
||||
function AngularAgentServiceWrapper(window) {
|
||||
const AS = Agent.default;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
return new AS(window);
|
||||
}
|
||||
);
|
||||
|
||||
return AngularAgentServiceWrapper;
|
||||
});
|
||||
|
||||
96
platform/commonUI/mobile/src/AgentServiceSpec.js
Normal file
96
platform/commonUI/mobile/src/AgentServiceSpec.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/*****************************************************************************
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
@@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
);
|
||||
@@ -1,88 +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(
|
||||
['./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;
|
||||
}
|
||||
);
|
||||
@@ -1,99 +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(
|
||||
["../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);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,109 +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(
|
||||
["../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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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.
|
||||
*****************************************************************************/
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,168 +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(
|
||||
['../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",
|
||||
"version": "1.4.7",
|
||||
"description": "Unique identifer generation (code adapted.)",
|
||||
"description": "Unique identifier generation (code adapted.)",
|
||||
"author": "Robert Kieffer",
|
||||
"website": "https://github.com/broofa/node-uuid",
|
||||
"copyright": "Copyright (c) 2010-2012 Robert Kieffer",
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, 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/HyperlinkController',
|
||||
'./res/templates/hyperlink.html'
|
||||
], function (
|
||||
HyperlinkController,
|
||||
hyperlinkTemplate
|
||||
) {
|
||||
return {
|
||||
name: "platform/features/hyperlink",
|
||||
definition: {
|
||||
"name": "Hyperlink",
|
||||
"description": "Insert a hyperlink to reference a link",
|
||||
"extensions": {
|
||||
"types": [
|
||||
{
|
||||
"key": "hyperlink",
|
||||
"name": "Hyperlink",
|
||||
"cssClass": "icon-chain-links",
|
||||
"description": "A hyperlink to redirect to a different link",
|
||||
"features": ["creation"],
|
||||
"properties": [
|
||||
{
|
||||
"key": "url",
|
||||
"name": "URL",
|
||||
"control": "textfield",
|
||||
"required": true,
|
||||
"cssClass": "l-input-lg"
|
||||
},
|
||||
|
||||
{
|
||||
"key": "displayText",
|
||||
"name": "Text to Display",
|
||||
"control": "textfield",
|
||||
"required": true,
|
||||
"cssClass": "l-input-lg"
|
||||
},
|
||||
{
|
||||
"key": "displayFormat",
|
||||
"name": "Display Format",
|
||||
"control": "select",
|
||||
"options": [
|
||||
{
|
||||
"name": "Link",
|
||||
"value": "link"
|
||||
},
|
||||
{
|
||||
"value": "button",
|
||||
"name": "Button"
|
||||
}
|
||||
],
|
||||
"cssClass": "l-inline"
|
||||
},
|
||||
{
|
||||
"key": "openNewTab",
|
||||
"name": "Tab to Open Hyperlink",
|
||||
"control": "select",
|
||||
"options": [
|
||||
{
|
||||
"name": "Open in this tab",
|
||||
"value": "thisTab"
|
||||
},
|
||||
{
|
||||
"value": "newTab",
|
||||
"name": "Open in a new tab"
|
||||
}
|
||||
],
|
||||
"cssClass": "l-inline"
|
||||
|
||||
}
|
||||
],
|
||||
"model": {
|
||||
"displayFormat": "link",
|
||||
"openNewTab": "thisTab",
|
||||
"removeTitle": true
|
||||
}
|
||||
|
||||
}
|
||||
],
|
||||
"views": [
|
||||
{
|
||||
"key": "hyperlink",
|
||||
"type": "hyperlink",
|
||||
"name": "Hyperlink Display",
|
||||
"template": hyperlinkTemplate,
|
||||
"editable": false
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "HyperlinkController",
|
||||
"implementation": HyperlinkController,
|
||||
"depends": ["$scope"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,28 +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.
|
||||
-->
|
||||
<a class="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
|
||||
ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
|
||||
ng-class="{
|
||||
'c-hyperlink--button u-fills-container' : hyperlink.isButton(),
|
||||
'c-hyperlink--link' : !hyperlink.isButton() }">
|
||||
<span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span>
|
||||
</a>
|
||||
@@ -1,61 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* This bundle adds the Hyperlink object type, which can be used to add hyperlinks as a domain Object type
|
||||
and into display Layouts as either a button or link that can be chosen to open in either the same tab or
|
||||
create a new tab to open the link in
|
||||
* @namespace platform/features/hyperlink
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
function HyperlinkController($scope) {
|
||||
this.$scope = $scope;
|
||||
}
|
||||
|
||||
/**Function to analyze the location in which to open the hyperlink
|
||||
@returns true if the hyperlink is chosen to open in a different tab, false if the same tab
|
||||
**/
|
||||
HyperlinkController.prototype.openNewTab = function () {
|
||||
if (this.$scope.domainObject.getModel().openNewTab === "thisTab") {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**Function to specify the format in which the hyperlink should be created
|
||||
@returns true if the hyperlink is chosen to be created as a button, false if a link
|
||||
**/
|
||||
HyperlinkController.prototype.isButton = function () {
|
||||
if (this.$scope.domainObject.getModel().displayFormat === "link") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return HyperlinkController;
|
||||
}
|
||||
|
||||
);
|
||||
@@ -1,89 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, 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/HyperlinkController"],
|
||||
function (HyperlinkController) {
|
||||
|
||||
describe("The controller for hyperlinks", function () {
|
||||
var domainObject,
|
||||
controller,
|
||||
scope;
|
||||
beforeEach(function () {
|
||||
scope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
["domainObject"]
|
||||
);
|
||||
domainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getModel"]
|
||||
);
|
||||
scope.domainObject = domainObject;
|
||||
controller = new HyperlinkController(scope);
|
||||
});
|
||||
it("knows when it should open a new tab", function () {
|
||||
scope.domainObject.getModel.and.returnValue({
|
||||
"displayFormat": "link",
|
||||
"openNewTab": "newTab",
|
||||
"showTitle": false
|
||||
}
|
||||
);
|
||||
controller = new HyperlinkController(scope);
|
||||
expect(controller.openNewTab())
|
||||
.toBe(true);
|
||||
});
|
||||
it("knows when it is a button", function () {
|
||||
scope.domainObject.getModel.and.returnValue({
|
||||
"displayFormat": "button",
|
||||
"openNewTab": "thisTab",
|
||||
"showTitle": false
|
||||
}
|
||||
);
|
||||
controller = new HyperlinkController(scope);
|
||||
expect(controller.isButton())
|
||||
.toEqual(true);
|
||||
});
|
||||
it("knows when it should open in the same tab", function () {
|
||||
scope.domainObject.getModel.and.returnValue({
|
||||
"displayFormat": "link",
|
||||
"openNewTab": "thisTab",
|
||||
"showTitle": false
|
||||
}
|
||||
);
|
||||
controller = new HyperlinkController(scope);
|
||||
expect(controller.openNewTab())
|
||||
.toBe(false);
|
||||
});
|
||||
it("knows when it is a link", function () {
|
||||
scope.domainObject.getModel.and.returnValue({
|
||||
"displayFormat": "link",
|
||||
"openNewTab": "thisTab",
|
||||
"showTitle": false
|
||||
}
|
||||
);
|
||||
controller = new HyperlinkController(scope);
|
||||
expect(controller.openNewTab())
|
||||
.toBe(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -47,7 +47,7 @@ define(
|
||||
* @param $interval Angular's $interval service
|
||||
* @param {string} space the name of the persistence space being served
|
||||
* @param {string} root the root of the path to ElasticSearch
|
||||
* @param {stirng} path the path to domain objects within ElasticSearch
|
||||
* @param {string} path the path to domain objects within ElasticSearch
|
||||
*/
|
||||
function ElasticPersistenceProvider($http, $q, space, root, path) {
|
||||
this.spaces = [space];
|
||||
|
||||
@@ -122,6 +122,7 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
this.destroy = this.destroy.bind(this);
|
||||
/**
|
||||
* Tracks current selection state of the application.
|
||||
* @private
|
||||
@@ -262,7 +263,7 @@ define([
|
||||
// Plugins that are installed by default
|
||||
|
||||
this.install(this.plugins.Plot());
|
||||
this.install(this.plugins.TelemetryTable());
|
||||
this.install(this.plugins.TelemetryTable.default());
|
||||
this.install(PreviewPlugin.default());
|
||||
this.install(LegacyIndicatorsPlugin());
|
||||
this.install(LicensesPlugin.default());
|
||||
@@ -283,8 +284,10 @@ define([
|
||||
this.install(this.plugins.NotificationIndicator());
|
||||
this.install(this.plugins.NewFolderAction());
|
||||
this.install(this.plugins.ViewDatumAction());
|
||||
this.install(this.plugins.ViewLargeAction());
|
||||
this.install(this.plugins.ObjectInterceptors());
|
||||
this.install(this.plugins.NonEditableFolder());
|
||||
this.install(this.plugins.DeviceClassifier());
|
||||
}
|
||||
|
||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||
@@ -434,6 +437,8 @@ define([
|
||||
Browse(this);
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', this.destroy);
|
||||
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
}.bind(this));
|
||||
@@ -457,6 +462,7 @@ define([
|
||||
};
|
||||
|
||||
MCT.prototype.destroy = function () {
|
||||
window.removeEventListener('beforeunload', this.destroy);
|
||||
this.emit('destroy');
|
||||
this.router.destroy();
|
||||
};
|
||||
|
||||
@@ -36,8 +36,7 @@ define([
|
||||
'./views/installLegacyViews',
|
||||
'./policies/LegacyCompositionPolicyAdapter',
|
||||
'./actions/LegacyActionAdapter',
|
||||
'./services/LegacyPersistenceAdapter',
|
||||
'./services/ExportImageService'
|
||||
'./services/LegacyPersistenceAdapter'
|
||||
], function (
|
||||
ActionDialogDecorator,
|
||||
AdapterCapability,
|
||||
@@ -54,8 +53,7 @@ define([
|
||||
installLegacyViews,
|
||||
legacyCompositionPolicyAdapter,
|
||||
LegacyActionAdapter,
|
||||
LegacyPersistenceAdapter,
|
||||
ExportImageService
|
||||
LegacyPersistenceAdapter
|
||||
) {
|
||||
return {
|
||||
name: 'src/adapter',
|
||||
@@ -84,13 +82,6 @@ define([
|
||||
"identifierService",
|
||||
"cacheService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "exportImageService",
|
||||
"implementation": ExportImageService,
|
||||
"depends": [
|
||||
"dialogService"
|
||||
]
|
||||
}
|
||||
],
|
||||
components: [
|
||||
|
||||
@@ -1,218 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining ExportImageService. Created by hudsonfoo on 09/02/16
|
||||
*/
|
||||
define(
|
||||
[
|
||||
"html2canvas",
|
||||
"saveAs"
|
||||
],
|
||||
function (
|
||||
html2canvas,
|
||||
{ saveAs }
|
||||
) {
|
||||
|
||||
/**
|
||||
* The export image service will export any HTML node to
|
||||
* JPG, or PNG.
|
||||
* @param {object} dialogService
|
||||
* @constructor
|
||||
*/
|
||||
function ExportImageService(dialogService) {
|
||||
this.dialogService = dialogService;
|
||||
this.exportCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HTML element into a PNG or JPG Blob.
|
||||
* @private
|
||||
* @param {node} element that will be converted to an image
|
||||
* @param {object} options Image options.
|
||||
* @returns {promise}
|
||||
*/
|
||||
ExportImageService.prototype.renderElement = function (element, {imageType, className, thumbnailSize}) {
|
||||
const self = this;
|
||||
const dialogService = this.dialogService;
|
||||
const dialog = dialogService.showBlockingMessage({
|
||||
title: "Capturing...",
|
||||
hint: "Capturing an image",
|
||||
unknownProgress: true,
|
||||
severity: "info",
|
||||
delay: true
|
||||
});
|
||||
|
||||
let mimeType = "image/png";
|
||||
if (imageType === "jpg") {
|
||||
mimeType = "image/jpeg";
|
||||
}
|
||||
|
||||
let exportId = undefined;
|
||||
let oldId = undefined;
|
||||
if (className) {
|
||||
exportId = 'export-element-' + this.exportCount;
|
||||
this.exportCount++;
|
||||
oldId = element.id;
|
||||
element.id = exportId;
|
||||
}
|
||||
|
||||
return html2canvas(element, {
|
||||
onclone: function (document) {
|
||||
if (className) {
|
||||
const clonedElement = document.getElementById(exportId);
|
||||
clonedElement.classList.add(className);
|
||||
}
|
||||
|
||||
element.id = oldId;
|
||||
},
|
||||
removeContainer: true // Set to false to debug what html2canvas renders
|
||||
}).then(function (canvas) {
|
||||
dialog.dismiss();
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (thumbnailSize) {
|
||||
const thumbnail = self.getThumbnail(canvas, mimeType, thumbnailSize);
|
||||
|
||||
return canvas.toBlob(blob => resolve({
|
||||
blob,
|
||||
thumbnail
|
||||
}), mimeType);
|
||||
}
|
||||
|
||||
return canvas.toBlob(blob => resolve({ blob }), mimeType);
|
||||
});
|
||||
}, function (error) {
|
||||
console.log('error capturing image', error);
|
||||
dialog.dismiss();
|
||||
const errorDialog = dialogService.showBlockingMessage({
|
||||
title: "Error capturing image",
|
||||
severity: "error",
|
||||
hint: "Image was not captured successfully!",
|
||||
options: [{
|
||||
label: "OK",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
}]
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ExportImageService.prototype.getThumbnail = function (canvas, mimeType, size) {
|
||||
const thumbnailCanvas = document.createElement('canvas');
|
||||
thumbnailCanvas.setAttribute('width', size.width);
|
||||
thumbnailCanvas.setAttribute('height', size.height);
|
||||
const ctx = thumbnailCanvas.getContext('2d');
|
||||
ctx.globalCompositeOperation = "copy";
|
||||
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, size.width, size.height);
|
||||
|
||||
return thumbnailCanvas.toDataURL(mimeType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a screenshot of a DOM node and exports to JPG.
|
||||
* @param {node} element to be exported
|
||||
* @param {string} filename the exported image
|
||||
* @param {string} className to be added to element before capturing (optional)
|
||||
* @returns {promise}
|
||||
*/
|
||||
ExportImageService.prototype.exportJPG = function (element, filename, className) {
|
||||
const processedFilename = replaceDotsWithUnderscores(filename);
|
||||
|
||||
return this.renderElement(element, {
|
||||
imageType: 'jpg',
|
||||
className
|
||||
})
|
||||
.then(function (img) {
|
||||
saveAs(img.blob, processedFilename);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a screenshot of a DOM node and exports to PNG.
|
||||
* @param {node} element to be exported
|
||||
* @param {string} filename the exported image
|
||||
* @param {string} className to be added to element before capturing (optional)
|
||||
* @returns {promise}
|
||||
*/
|
||||
ExportImageService.prototype.exportPNG = function (element, filename, className) {
|
||||
const processedFilename = replaceDotsWithUnderscores(filename);
|
||||
|
||||
return this.renderElement(element, {
|
||||
imageType: 'png',
|
||||
className
|
||||
})
|
||||
.then(function (img) {
|
||||
saveAs(img.blob, processedFilename);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a screenshot of a DOM node in PNG format.
|
||||
* @param {node} element to be exported
|
||||
* @param {string} filename the exported image
|
||||
* @returns {promise}
|
||||
*/
|
||||
|
||||
ExportImageService.prototype.exportPNGtoSRC = function (element, options) {
|
||||
|
||||
return this.renderElement(element, {
|
||||
imageType: 'png',
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
function replaceDotsWithUnderscores(filename) {
|
||||
const regex = /\./gi;
|
||||
|
||||
return filename.replace(regex, '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* canvas.toBlob() not supported in IE < 10, Opera, and Safari. This polyfill
|
||||
* implements the method in browsers that would not otherwise support it.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
|
||||
*/
|
||||
function polyfillToBlob() {
|
||||
if (!HTMLCanvasElement.prototype.toBlob) {
|
||||
Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", {
|
||||
value: function (callback, mimeType, quality) {
|
||||
const binStr = atob(this.toDataURL(mimeType, quality).split(',')[1]);
|
||||
const len = binStr.length;
|
||||
const arr = new Uint8Array(len);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
arr[i] = binStr.charCodeAt(i);
|
||||
}
|
||||
|
||||
callback(new Blob([arr], {type: mimeType || "image/png"}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
polyfillToBlob();
|
||||
|
||||
return ExportImageService;
|
||||
}
|
||||
);
|
||||
@@ -46,8 +46,6 @@ class ActionCollection extends EventEmitter {
|
||||
this._observeObjectPath();
|
||||
this.openmct.editor.on('isEditing', this._updateActions);
|
||||
}
|
||||
|
||||
this._initializeActions();
|
||||
}
|
||||
|
||||
disable(actionKeys) {
|
||||
@@ -156,19 +154,10 @@ class ActionCollection extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
_initializeActions() {
|
||||
Object.keys(this.applicableActions).forEach(key => {
|
||||
this.applicableActions[key].callBack = () => {
|
||||
return this.applicableActions[key].invoke(this.objectPath, this.view);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
_updateActions() {
|
||||
let newApplicableActions = this.openmct.actions._applicableActions(this.objectPath, this.view);
|
||||
|
||||
this.applicableActions = this._mergeOldAndNewActions(this.applicableActions, newApplicableActions);
|
||||
this._initializeActions();
|
||||
this._update();
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class ActionsAPI extends EventEmitter {
|
||||
this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json'];
|
||||
|
||||
this.register = this.register.bind(this);
|
||||
this.get = this.get.bind(this);
|
||||
this.getActionsCollection = this.getActionsCollection.bind(this);
|
||||
this._applicableActions = this._applicableActions.bind(this);
|
||||
this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this);
|
||||
}
|
||||
@@ -43,12 +43,14 @@ class ActionsAPI extends EventEmitter {
|
||||
this._allActions[actionDefinition.key] = actionDefinition;
|
||||
}
|
||||
|
||||
get(objectPath, view) {
|
||||
if (view) {
|
||||
getAction(key) {
|
||||
return this._allActions[key];
|
||||
}
|
||||
|
||||
getActionsCollection(objectPath, view) {
|
||||
if (view) {
|
||||
return this._getCachedActionCollection(objectPath, view) || this._newActionCollection(objectPath, view, true);
|
||||
} else {
|
||||
|
||||
return this._newActionCollection(objectPath, view, true);
|
||||
}
|
||||
}
|
||||
@@ -57,25 +59,24 @@ class ActionsAPI extends EventEmitter {
|
||||
this._groupOrder = groupArray;
|
||||
}
|
||||
|
||||
_get(objectPath, view) {
|
||||
let actionCollection = this._newActionCollection(objectPath, view);
|
||||
|
||||
this._actionCollections.set(view, actionCollection);
|
||||
actionCollection.on('destroy', this._updateCachedActionCollections);
|
||||
|
||||
return actionCollection;
|
||||
}
|
||||
|
||||
_getCachedActionCollection(objectPath, view) {
|
||||
let cachedActionCollection = this._actionCollections.get(view);
|
||||
|
||||
return cachedActionCollection;
|
||||
return this._actionCollections.get(view);
|
||||
}
|
||||
|
||||
_newActionCollection(objectPath, view, skipEnvironmentObservers) {
|
||||
let applicableActions = this._applicableActions(objectPath, view);
|
||||
|
||||
return new ActionCollection(applicableActions, objectPath, view, this._openmct, skipEnvironmentObservers);
|
||||
const actionCollection = new ActionCollection(applicableActions, objectPath, view, this._openmct, skipEnvironmentObservers);
|
||||
if (view) {
|
||||
this._cacheActionCollection(view, actionCollection);
|
||||
}
|
||||
|
||||
return actionCollection;
|
||||
}
|
||||
|
||||
_cacheActionCollection(view, actionCollection) {
|
||||
this._actionCollections.set(view, actionCollection);
|
||||
actionCollection.on('destroy', this._updateCachedActionCollections);
|
||||
}
|
||||
|
||||
_updateCachedActionCollections(key) {
|
||||
|
||||
@@ -106,7 +106,7 @@ describe('The Actions API', () => {
|
||||
it("adds action to ActionsAPI", () => {
|
||||
actionsAPI.register(mockAction);
|
||||
|
||||
let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
|
||||
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
|
||||
let action = actionCollection.getActionsObject()[mockAction.key];
|
||||
|
||||
expect(action.key).toEqual(mockAction.key);
|
||||
@@ -121,21 +121,21 @@ describe('The Actions API', () => {
|
||||
});
|
||||
|
||||
it("returns an ActionCollection when invoked with an objectPath only", () => {
|
||||
let actionCollection = actionsAPI.get(mockObjectPath);
|
||||
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath);
|
||||
let instanceOfActionCollection = actionCollection instanceof ActionCollection;
|
||||
|
||||
expect(instanceOfActionCollection).toBeTrue();
|
||||
});
|
||||
|
||||
it("returns an ActionCollection when invoked with an objectPath and view", () => {
|
||||
let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
|
||||
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
|
||||
let instanceOfActionCollection = actionCollection instanceof ActionCollection;
|
||||
|
||||
expect(instanceOfActionCollection).toBeTrue();
|
||||
});
|
||||
|
||||
it("returns relevant actions when invoked with objectPath only", () => {
|
||||
let actionCollection = actionsAPI.get(mockObjectPath);
|
||||
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath);
|
||||
let action = actionCollection.getActionsObject()[mockObjectPathAction.key];
|
||||
|
||||
expect(action.key).toEqual(mockObjectPathAction.key);
|
||||
@@ -143,7 +143,7 @@ describe('The Actions API', () => {
|
||||
});
|
||||
|
||||
it("returns relevant actions when invoked with objectPath and view", () => {
|
||||
let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
|
||||
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
|
||||
let action = actionCollection.getActionsObject()[mockAction.key];
|
||||
|
||||
expect(action.key).toEqual(mockAction.key);
|
||||
|
||||
@@ -37,7 +37,7 @@ import Menu, { MENU_PLACEMENT } from './menu.js';
|
||||
* @property {Boolean} isDisabled adds disable class if true
|
||||
* @property {String} name Menu item text
|
||||
* @property {String} description Menu item description
|
||||
* @property {Function} callBack callback function: invoked when item is clicked
|
||||
* @property {Function} onItemClicked callback function: invoked when item is clicked
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -66,12 +66,27 @@ class MenuAPI {
|
||||
* @param {Array.<Action>|Array.<Array.<Action>>} actions collection of actions{@link Action} or collection of groups of actions {@link Action}
|
||||
* @param {MenuOptions} [menuOptions] [Optional] The {@link MenuOptions} options for Menu
|
||||
*/
|
||||
showMenu(x, y, actions, menuOptions) {
|
||||
this._createMenuComponent(x, y, actions, menuOptions);
|
||||
showMenu(x, y, items, menuOptions) {
|
||||
this._createMenuComponent(x, y, items, menuOptions);
|
||||
|
||||
this.menuComponent.showMenu();
|
||||
}
|
||||
|
||||
actionsToMenuItems(actions, objectPath, view) {
|
||||
return actions.map(action => {
|
||||
const isActionGroup = Array.isArray(action);
|
||||
if (isActionGroup) {
|
||||
action = this.actionsToMenuItems(action, objectPath, view);
|
||||
} else {
|
||||
action.onItemClicked = () => {
|
||||
action.invoke(objectPath, view);
|
||||
};
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show popup menu with description of item on hover
|
||||
* @param {number} x x-coordinates for popup
|
||||
|
||||
@@ -57,7 +57,7 @@ describe ('The Menu API', () => {
|
||||
name: 'Test Action 1',
|
||||
cssClass: 'icon-clock',
|
||||
description: 'This is a test action',
|
||||
callBack: () => {
|
||||
onItemClicked: () => {
|
||||
result = 'Test Action 1 Invoked';
|
||||
}
|
||||
},
|
||||
@@ -66,7 +66,7 @@ describe ('The Menu API', () => {
|
||||
name: 'Test Action 2',
|
||||
cssClass: 'icon-clock',
|
||||
description: 'This is a test action',
|
||||
callBack: () => {
|
||||
onItemClicked: () => {
|
||||
result = 'Test Action 2 Invoked';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
:key="action.name"
|
||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||
:title="action.description"
|
||||
@click="action.callBack"
|
||||
@click="action.onItemClicked"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
@@ -36,7 +36,7 @@
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
:title="action.description"
|
||||
@click="action.callBack"
|
||||
@click="action.onItemClicked"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
:key="action.name"
|
||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||
:title="action.description"
|
||||
@click="action.callBack"
|
||||
@click="action.onItemClicked"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
>
|
||||
@@ -42,7 +42,7 @@
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
:title="action.description"
|
||||
@click="action.callBack"
|
||||
@click="action.onItemClicked"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
>
|
||||
|
||||
@@ -71,12 +71,12 @@ class Menu extends EventEmitter {
|
||||
|
||||
showMenu() {
|
||||
this.component = new Vue({
|
||||
provide: {
|
||||
options: this.options
|
||||
},
|
||||
components: {
|
||||
MenuComponent
|
||||
},
|
||||
provide: {
|
||||
options: this.options
|
||||
},
|
||||
template: '<menu-component />'
|
||||
});
|
||||
|
||||
@@ -85,12 +85,12 @@ class Menu extends EventEmitter {
|
||||
|
||||
showSuperMenu() {
|
||||
this.component = new Vue({
|
||||
provide: {
|
||||
options: this.options
|
||||
},
|
||||
components: {
|
||||
SuperMenuComponent
|
||||
},
|
||||
provide: {
|
||||
options: this.options
|
||||
},
|
||||
template: '<super-menu-component />'
|
||||
});
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import EventEmitter from 'EventEmitter';
|
||||
*
|
||||
* @typedef {object} NotificationModel
|
||||
* @property {string} message The message to be displayed by the notification
|
||||
* @property {number | 'unknown'} [progress] The progres of some ongoing task. Should be a number between 0 and 100, or
|
||||
* @property {number | 'unknown'} [progress] The progress of some ongoing task. Should be a number between 0 and 100, or
|
||||
* with the string literal 'unknown'.
|
||||
* @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.
|
||||
* @param {string} message The message to display to the user.
|
||||
* @param {Object} [options] object with following properties
|
||||
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
||||
* autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
|
||||
* link: {Object} Add a link to notifications for navigation
|
||||
* onClick: callback function
|
||||
* 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
|
||||
* @param {string} message
|
||||
* @param {Object} [options] object with following properties
|
||||
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
||||
* autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
|
||||
* link: {Object} Add a link to notifications for navigation
|
||||
* onClick: callback function
|
||||
* cssClass: css class name to add style on link
|
||||
|
||||
@@ -60,7 +60,7 @@ class OverlayAPI {
|
||||
* A description of option properties that can be passed into the overlay
|
||||
* @typedef options
|
||||
* @property {object} element DOMElement that is to be inserted/shown on the overlay
|
||||
* @property {string} size prefered size of the overlay (large, small, fit)
|
||||
* @property {string} size preferred size of the overlay (large, small, fit)
|
||||
* @property {array} buttons optional button objects with label and callback properties
|
||||
* @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
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
></button>
|
||||
<div
|
||||
ref="element"
|
||||
class="c-overlay__contents"
|
||||
class="c-overlay__contents js-notebook-snapshot-item-wrapper"
|
||||
tabindex="0"
|
||||
></div>
|
||||
<div
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
const { TelemetryCollection } = require("./TelemetryCollection");
|
||||
|
||||
define([
|
||||
'../../plugins/displayLayout/CustomStringFormatter',
|
||||
'./TelemetryMetadataManager',
|
||||
@@ -273,6 +275,28 @@ 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.
|
||||
* The `options` argument allows you to specify filters
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
388
src/api/telemetry/TelemetryCollection.js
Normal file
388
src/api/telemetry/TelemetryCollection.js
Normal file
@@ -0,0 +1,388 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
|
||||
try {
|
||||
this.requestAbort = new AbortController();
|
||||
this.options.signal = this.requestAbort.signal;
|
||||
historicalData = await this.historicalProvider.request(this.domainObject, this.options);
|
||||
this.requestAbort = undefined;
|
||||
} catch (error) {
|
||||
console.error('Error requesting telemetry data...');
|
||||
this.requestAbort = undefined;
|
||||
this._error(error);
|
||||
}
|
||||
|
||||
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._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);
|
||||
}
|
||||
}
|
||||
188
src/exporters/ImageExporter.js
Normal file
188
src/exporters/ImageExporter.js
Normal file
@@ -0,0 +1,188 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Class defining an image exporter for JPG/PNG output.
|
||||
* Originally created by hudsonfoo on 09/02/16
|
||||
*/
|
||||
|
||||
function replaceDotsWithUnderscores(filename) {
|
||||
const regex = /\./gi;
|
||||
|
||||
return filename.replace(regex, '_');
|
||||
}
|
||||
|
||||
import {saveAs} from 'file-saver/FileSaver';
|
||||
import html2canvas from 'html2canvas';
|
||||
import uuid from 'uuid';
|
||||
|
||||
class ImageExporter {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
/**
|
||||
* Converts an HTML element into a PNG or JPG Blob.
|
||||
* @private
|
||||
* @param {node} element that will be converted to an image
|
||||
* @param {object} options Image options.
|
||||
* @returns {promise}
|
||||
*/
|
||||
renderElement(element, { imageType, className, thumbnailSize }) {
|
||||
const self = this;
|
||||
const overlays = this.openmct.overlays;
|
||||
const dialog = overlays.dialog({
|
||||
iconClass: 'info',
|
||||
message: 'Caputuring an image',
|
||||
buttons: [
|
||||
{
|
||||
label: 'Cancel',
|
||||
emphasis: true,
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let mimeType = 'image/png';
|
||||
if (imageType === 'jpg') {
|
||||
mimeType = 'image/jpeg';
|
||||
}
|
||||
|
||||
let exportId = undefined;
|
||||
let oldId = undefined;
|
||||
if (className) {
|
||||
const newUUID = uuid();
|
||||
exportId = `$export-element-${newUUID}`;
|
||||
oldId = element.id;
|
||||
element.id = exportId;
|
||||
}
|
||||
|
||||
return html2canvas(element, {
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
logging: false,
|
||||
onclone: function (document) {
|
||||
if (className) {
|
||||
const clonedElement = document.getElementById(exportId);
|
||||
clonedElement.classList.add(className);
|
||||
}
|
||||
|
||||
element.id = oldId;
|
||||
},
|
||||
removeContainer: true // Set to false to debug what html2canvas renders
|
||||
}).then(function (canvas) {
|
||||
dialog.dismiss();
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (thumbnailSize) {
|
||||
const thumbnail = self.getThumbnail(canvas, mimeType, thumbnailSize);
|
||||
|
||||
return canvas.toBlob(blob => resolve({
|
||||
blob,
|
||||
thumbnail
|
||||
}), mimeType);
|
||||
}
|
||||
|
||||
return canvas.toBlob(blob => resolve({ blob }), mimeType);
|
||||
});
|
||||
}, function (error) {
|
||||
console.log('error capturing image', error);
|
||||
dialog.dismiss();
|
||||
const errorDialog = overlays.dialog({
|
||||
iconClass: 'error',
|
||||
message: 'Image was not captured successfully!',
|
||||
buttons: [
|
||||
{
|
||||
label: "OK",
|
||||
emphasis: true,
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getThumbnail(canvas, mimeType, size) {
|
||||
const thumbnailCanvas = document.createElement('canvas');
|
||||
thumbnailCanvas.setAttribute('width', size.width);
|
||||
thumbnailCanvas.setAttribute('height', size.height);
|
||||
const ctx = thumbnailCanvas.getContext('2d');
|
||||
ctx.globalCompositeOperation = "copy";
|
||||
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, size.width, size.height);
|
||||
|
||||
return thumbnailCanvas.toDataURL(mimeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a screenshot of a DOM node and exports to JPG.
|
||||
* @param {node} element to be exported
|
||||
* @param {string} filename the exported image
|
||||
* @param {string} className to be added to element before capturing (optional)
|
||||
* @returns {promise}
|
||||
*/
|
||||
async exportJPG(element, filename, className) {
|
||||
const processedFilename = replaceDotsWithUnderscores(filename);
|
||||
|
||||
const img = await this.renderElement(element, {
|
||||
imageType: 'jpg',
|
||||
className
|
||||
});
|
||||
saveAs(img.blob, processedFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a screenshot of a DOM node and exports to PNG.
|
||||
* @param {node} element to be exported
|
||||
* @param {string} filename the exported image
|
||||
* @param {string} className to be added to element before capturing (optional)
|
||||
* @returns {promise}
|
||||
*/
|
||||
async exportPNG(element, filename, className) {
|
||||
const processedFilename = replaceDotsWithUnderscores(filename);
|
||||
|
||||
const img = await this.renderElement(element, {
|
||||
imageType: 'png',
|
||||
className
|
||||
});
|
||||
saveAs(img.blob, processedFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a screenshot of a DOM node in PNG format.
|
||||
* @param {node} element to be exported
|
||||
* @param {string} filename the exported image
|
||||
* @returns {promise}
|
||||
*/
|
||||
|
||||
exportPNGtoSRC(element, options) {
|
||||
return this.renderElement(element, {
|
||||
imageType: 'png',
|
||||
...options
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ImageExporter;
|
||||
|
||||
58
src/exporters/ImageExporterSpec.js
Normal file
58
src/exporters/ImageExporterSpec.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import ImageExporter from './ImageExporter';
|
||||
import { createOpenMct, resetApplicationState } from '../utils/testing';
|
||||
|
||||
describe('The Image Exporter', () => {
|
||||
let openmct;
|
||||
let imageExporter;
|
||||
|
||||
beforeEach(() => {
|
||||
openmct = createOpenMct();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
describe("basic instatation", () => {
|
||||
it("can be instatiated", () => {
|
||||
imageExporter = new ImageExporter(openmct);
|
||||
|
||||
expect(imageExporter).not.toEqual(null);
|
||||
});
|
||||
it("can render an element to a blob", async () => {
|
||||
const mockHeadElement = document.createElement("h1");
|
||||
const mockTextNode = document.createTextNode('foo bar');
|
||||
mockHeadElement.appendChild(mockTextNode);
|
||||
document.body.appendChild(mockHeadElement);
|
||||
imageExporter = new ImageExporter(openmct);
|
||||
const returnedBlob = await imageExporter.renderElement(document.body, {
|
||||
imageType: 'png'
|
||||
});
|
||||
expect(returnedBlob).not.toEqual(null);
|
||||
expect(returnedBlob.blob).not.toEqual(null);
|
||||
expect(returnedBlob.blob).toBeInstanceOf(Blob);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,6 @@ const DEFAULTS = [
|
||||
'platform/exporters',
|
||||
'platform/telemetry',
|
||||
'platform/features/clock',
|
||||
'platform/features/hyperlink',
|
||||
'platform/forms',
|
||||
'platform/identity',
|
||||
'platform/persistence/aggregator',
|
||||
@@ -81,7 +80,6 @@ define([
|
||||
'../platform/exporters/bundle',
|
||||
'../platform/features/clock/bundle',
|
||||
'../platform/features/my-items/bundle',
|
||||
'../platform/features/hyperlink/bundle',
|
||||
'../platform/features/static-markup/bundle',
|
||||
'../platform/forms/bundle',
|
||||
'../platform/framework/bundle',
|
||||
|
||||
32
src/plugins/DeviceClassifier/plugin.js
Normal file
32
src/plugins/DeviceClassifier/plugin.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/*****************************************************************************
|
||||
* 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);
|
||||
});
|
||||
};
|
||||
};
|
||||
72
src/plugins/DeviceClassifier/src/DeviceClassifier.js
Normal file
72
src/plugins/DeviceClassifier/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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
105
src/plugins/DeviceClassifier/src/DeviceClassifierSpec.js
Normal file
105
src/plugins/DeviceClassifier/src/DeviceClassifierSpec.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/*****************************************************************************
|
||||
* 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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
57
src/plugins/DeviceClassifier/src/DeviceMatchers.js
Normal file
57
src/plugins/DeviceClassifier/src/DeviceMatchers.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/*****************************************************************************
|
||||
* 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();
|
||||
}
|
||||
};
|
||||
65
src/plugins/DeviceClassifier/src/DeviceMatchersSpec.js
Normal file
65
src/plugins/DeviceClassifier/src/DeviceMatchersSpec.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/*****************************************************************************
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -19,8 +19,8 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import LadTableSet from './components/LadTableSet.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
import LadTableSetView from './LadTableSetView';
|
||||
|
||||
export default function LADTableSetViewProvider(openmct) {
|
||||
return {
|
||||
@@ -34,32 +34,7 @@ export default function LADTableSetViewProvider(openmct) {
|
||||
return domainObject.type === 'LadTableSet';
|
||||
},
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
LadTableSet: LadTableSet
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
objectPath
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject
|
||||
};
|
||||
},
|
||||
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
return new LadTableSetView(openmct, domainObject, objectPath);
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
|
||||
45
src/plugins/LADTable/LADTableView.js
Normal file
45
src/plugins/LADTable/LADTableView.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import LadTable from './components/LADTable.vue';
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
export default class LADTableView {
|
||||
constructor(openmct, domainObject, objectPath) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = objectPath;
|
||||
this.component = undefined;
|
||||
}
|
||||
|
||||
show(element) {
|
||||
this.component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
LadTable
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
currentView: this
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
domainObject: this.domainObject,
|
||||
objectPath: this.objectPath
|
||||
};
|
||||
},
|
||||
template: '<lad-table ref="ladTable" :domain-object="domainObject" :object-path="objectPath"></lad-table>'
|
||||
});
|
||||
}
|
||||
|
||||
getViewContext() {
|
||||
if (!this.component) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return this.component.$refs.ladTable.getViewContext();
|
||||
}
|
||||
|
||||
destroy(element) {
|
||||
this.component.$destroy();
|
||||
this.component = undefined;
|
||||
}
|
||||
}
|
||||
@@ -19,50 +19,30 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import LadTable from './components/LADTable.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function LADTableViewProvider(openmct) {
|
||||
return {
|
||||
key: 'LadTable',
|
||||
name: 'LAD Table',
|
||||
cssClass: 'icon-tabular-lad',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'LadTable';
|
||||
},
|
||||
canEdit: function (domainObject) {
|
||||
return domainObject.type === 'LadTable';
|
||||
},
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
import LADTableView from './LADTableView';
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
LadTableComponent: LadTable
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
domainObject,
|
||||
objectPath
|
||||
};
|
||||
},
|
||||
template: '<lad-table-component :domain-object="domainObject" :object-path="objectPath"></lad-table-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
export default class LADTableViewProvider {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.name = 'LAD Table';
|
||||
this.key = 'LadTable';
|
||||
this.cssClass = 'icon-tabular-lad';
|
||||
}
|
||||
|
||||
canView(domainObject) {
|
||||
return domainObject.type === 'LadTable';
|
||||
}
|
||||
|
||||
canEdit(domainObject) {
|
||||
return domainObject.type === 'LadTable';
|
||||
}
|
||||
|
||||
view(domainObject, objectPath) {
|
||||
return new LADTableView(this.openmct, domainObject, objectPath);
|
||||
}
|
||||
|
||||
priority(domainObject) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
45
src/plugins/LADTable/LadTableSetView.js
Normal file
45
src/plugins/LADTable/LadTableSetView.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import LadTableSet from './components/LadTableSet.vue';
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
export default class LadTableSetView {
|
||||
constructor(openmct, domainObject, objectPath) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = objectPath;
|
||||
this.component = undefined;
|
||||
}
|
||||
|
||||
show(element) {
|
||||
this.component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
LadTableSet
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
objectPath: this.objectPath,
|
||||
currentView: this
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
domainObject: this.domainObject
|
||||
};
|
||||
},
|
||||
template: '<lad-table-set ref="ladTableSet" :domain-object="domainObject"></lad-table-set>'
|
||||
});
|
||||
}
|
||||
|
||||
getViewContext() {
|
||||
if (!this.component) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return this.component.$refs.ladTableSet.getViewContext();
|
||||
}
|
||||
|
||||
destroy(element) {
|
||||
this.component.$destroy();
|
||||
this.component = undefined;
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ const CONTEXT_MENU_ACTIONS = [
|
||||
];
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'currentView'],
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
@@ -167,25 +167,23 @@ export default {
|
||||
this.resetValues();
|
||||
this.timestampKey = timeSystem.key;
|
||||
},
|
||||
getView() {
|
||||
return {
|
||||
getViewContext: () => {
|
||||
return {
|
||||
viewHistoricalData: true,
|
||||
viewDatumAction: true,
|
||||
getDatum: () => {
|
||||
return this.datum;
|
||||
}
|
||||
};
|
||||
updateViewContext() {
|
||||
this.$emit('rowContextClick', {
|
||||
viewHistoricalData: true,
|
||||
viewDatumAction: true,
|
||||
getDatum: () => {
|
||||
return this.datum;
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
showContextMenu(event) {
|
||||
let actionCollection = this.openmct.actions.get(this.objectPath, this.getView());
|
||||
let allActions = actionCollection.getActionsObject();
|
||||
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
||||
this.updateViewContext();
|
||||
|
||||
this.openmct.menus.showMenu(event.x, event.y, applicableActions);
|
||||
const actions = CONTEXT_MENU_ACTIONS.map(key => this.openmct.actions.getAction(key));
|
||||
const menuItems = this.openmct.menus.actionsToMenuItems(actions, this.objectPath, this.currentView);
|
||||
if (menuItems.length) {
|
||||
this.openmct.menus.showMenu(event.x, event.y, menuItems);
|
||||
}
|
||||
},
|
||||
resetValues() {
|
||||
this.value = '---';
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
:domain-object="ladRow.domainObject"
|
||||
:path-to-table="objectPath"
|
||||
:has-units="hasUnits"
|
||||
@rowContextClick="updateViewContext"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -51,7 +52,7 @@ export default {
|
||||
components: {
|
||||
LadRow
|
||||
},
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'currentView'],
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
@@ -64,7 +65,8 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: []
|
||||
items: [],
|
||||
viewContext: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -114,6 +116,12 @@ export default {
|
||||
let metadataWithUnits = valueMetadatas.filter(metadatum => metadatum.unit);
|
||||
|
||||
return metadataWithUnits.length > 0;
|
||||
},
|
||||
updateViewContext(rowContext) {
|
||||
this.viewContext.row = rowContext;
|
||||
},
|
||||
getViewContext() {
|
||||
return this.viewContext;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
:domain-object="ladRow.domainObject"
|
||||
:path-to-table="ladTable.objectPath"
|
||||
:has-units="hasUnits"
|
||||
@rowContextClick="updateViewContext"
|
||||
/>
|
||||
</template>
|
||||
</tbody>
|
||||
@@ -61,7 +62,7 @@ export default {
|
||||
components: {
|
||||
LadRow
|
||||
},
|
||||
inject: ['openmct', 'objectPath'],
|
||||
inject: ['openmct', 'objectPath', 'currentView'],
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
@@ -72,7 +73,8 @@ export default {
|
||||
return {
|
||||
ladTableObjects: [],
|
||||
ladTelemetryObjects: {},
|
||||
compositions: []
|
||||
compositions: [],
|
||||
viewContext: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -166,6 +168,12 @@ export default {
|
||||
|
||||
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
|
||||
};
|
||||
},
|
||||
updateViewContext(rowContext) {
|
||||
this.viewContext.row = rowContext;
|
||||
},
|
||||
getViewContext() {
|
||||
return this.viewContext;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,7 +215,8 @@ export default {
|
||||
},
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
telemetry: {
|
||||
type: Array,
|
||||
|
||||
@@ -321,7 +321,7 @@ export default {
|
||||
if (item) {
|
||||
const type = this.openmct.types.get(item.type);
|
||||
if (type && type.definition) {
|
||||
creatable = (type.definition.creatable === true);
|
||||
creatable = (type.definition.creatable !== undefined && (type.definition.creatable === 'true' || type.definition.creatable === true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,71 +20,78 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./components/AlphanumericFormatView.vue',
|
||||
'vue'
|
||||
], function (AlphanumericFormatView, Vue) {
|
||||
import AlphanumericFormat from './components/AlphanumericFormat.vue';
|
||||
|
||||
function AlphanumericFormatViewProvider(openmct, options) {
|
||||
function isTelemetryObject(selectionPath) {
|
||||
let selectedObject = selectionPath[0].context.item;
|
||||
let parentObject = selectionPath[1].context.item;
|
||||
let selectedLayoutItem = selectionPath[0].context.layoutItem;
|
||||
import Vue from 'vue';
|
||||
|
||||
return parentObject
|
||||
&& parentObject.type === 'layout'
|
||||
&& selectedObject
|
||||
&& selectedLayoutItem
|
||||
&& selectedLayoutItem.type === 'telemetry-view'
|
||||
&& openmct.telemetry.isTelemetryObject(selectedObject)
|
||||
&& !options.showAsView.includes(selectedObject.type);
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'alphanumeric-format',
|
||||
name: 'Alphanumeric Format',
|
||||
canView: function (selection) {
|
||||
if (selection.length === 0 || selection[0].length === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selection.every(isTelemetryObject);
|
||||
},
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
AlphanumericFormatView: AlphanumericFormatView.default
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
objectPath
|
||||
},
|
||||
template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>'
|
||||
});
|
||||
},
|
||||
getViewContext() {
|
||||
if (component) {
|
||||
return component.$refs.alphanumericFormatView.getViewContext();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
class AlphanumericFormatView {
|
||||
constructor(openmct, domainObject, objectPath) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = objectPath;
|
||||
this.component = undefined;
|
||||
}
|
||||
|
||||
return AlphanumericFormatViewProvider;
|
||||
});
|
||||
show(element) {
|
||||
this.component = new Vue({
|
||||
el: element,
|
||||
name: 'AlphanumericFormat',
|
||||
components: {
|
||||
AlphanumericFormat
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
objectPath: this.objectPath,
|
||||
currentView: this
|
||||
},
|
||||
template: '<alphanumeric-format ref="alphanumericFormat"></alphanumeric-format>'
|
||||
});
|
||||
}
|
||||
|
||||
getViewContext() {
|
||||
if (this.component) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return this.component.$refs.alphanumericFormat.getViewContext();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.component.$destroy();
|
||||
this.component = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export default function AlphanumericFormatViewProvider(openmct, options) {
|
||||
function isTelemetryObject(selectionPath) {
|
||||
let selectedObject = selectionPath[0].context.item;
|
||||
let parentObject = selectionPath[1].context.item;
|
||||
let selectedLayoutItem = selectionPath[0].context.layoutItem;
|
||||
|
||||
return parentObject
|
||||
&& parentObject.type === 'layout'
|
||||
&& selectedObject
|
||||
&& selectedLayoutItem
|
||||
&& selectedLayoutItem.type === 'telemetry-view'
|
||||
&& openmct.telemetry.isTelemetryObject(selectedObject)
|
||||
&& !options.showAsView.includes(selectedObject.type);
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'alphanumeric-format',
|
||||
name: 'Alphanumeric Format',
|
||||
canView: function (selection) {
|
||||
if (selection.length === 0 || selection[0].length === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selection.every(isTelemetryObject);
|
||||
},
|
||||
view: function (domainObject, objectPath) {
|
||||
return new AlphanumericFormatView(openmct, domainObject, objectPath);
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class CopyToClipboardAction {
|
||||
|
||||
invoke(objectPath, view = {}) {
|
||||
const viewContext = view.getViewContext && view.getViewContext();
|
||||
const formattedValue = viewContext.formattedValueForCopy();
|
||||
const formattedValue = viewContext.row.formattedValueForCopy();
|
||||
|
||||
clipboard.updateClipboard(formattedValue)
|
||||
.then(() => {
|
||||
@@ -26,9 +26,13 @@ export default class CopyToClipboardAction {
|
||||
}
|
||||
|
||||
appliesTo(objectPath, view = {}) {
|
||||
let viewContext = view.getViewContext && view.getViewContext();
|
||||
const viewContext = view.getViewContext && view.getViewContext();
|
||||
const row = viewContext && viewContext.row;
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return viewContext && viewContext.formattedValueForCopy
|
||||
&& typeof viewContext.formattedValueForCopy === 'function';
|
||||
return row.formattedValueForCopy
|
||||
&& typeof row.formattedValueForCopy === 'function';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
name: 'AlphanumericFormat',
|
||||
inject: ['openmct', 'objectPath'],
|
||||
data() {
|
||||
return {
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
@@ -56,6 +56,7 @@
|
||||
:index="index"
|
||||
:multi-select="selectedLayoutItems.length > 1"
|
||||
:is-editing="isEditing"
|
||||
@contextClick="updateViewContext"
|
||||
@move="move"
|
||||
@endMove="endMove"
|
||||
@endLineResize="endLineResize"
|
||||
@@ -140,7 +141,7 @@ function getItemDefinition(itemType, ...options) {
|
||||
|
||||
export default {
|
||||
components: components,
|
||||
inject: ['openmct', 'options', 'objectPath'],
|
||||
inject: ['openmct', 'objectPath', 'options', 'objectUtils', 'currentView'],
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
@@ -155,7 +156,8 @@ export default {
|
||||
return {
|
||||
initSelectIndex: undefined,
|
||||
selection: [],
|
||||
showGrid: true
|
||||
showGrid: true,
|
||||
viewContext: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -819,6 +821,12 @@ export default {
|
||||
},
|
||||
toggleGrid() {
|
||||
this.showGrid = !this.showGrid;
|
||||
},
|
||||
updateViewContext(viewContext) {
|
||||
this.viewContext.row = viewContext;
|
||||
},
|
||||
getViewContext() {
|
||||
return this.viewContext;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<slot></slot>
|
||||
<div
|
||||
class="c-frame__move-bar"
|
||||
@mousedown="isEditing ? startMove([1,1], [0,0], $event) : null"
|
||||
@mousedown.left="startMove($event)"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -93,7 +93,11 @@ export default {
|
||||
return value - this.initialPosition[index];
|
||||
}.bind(this));
|
||||
},
|
||||
startMove(posFactor, dimFactor, event) {
|
||||
startMove(event, posFactor = [1, 1], dimFactor = [0, 0]) {
|
||||
if (!this.isEditing) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.body.addEventListener('mousemove', this.continueMove);
|
||||
document.body.addEventListener('mouseup', this.endMove);
|
||||
this.dragPosition = {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<div
|
||||
class="c-frame__move-bar"
|
||||
@mousedown="startDrag($event)"
|
||||
@mousedown.left="startDrag($event)"
|
||||
></div>
|
||||
<div
|
||||
v-if="showFrameEdit"
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue';
|
||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
||||
import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js';
|
||||
import { getDefaultNotebook, getNotebookSectionAndPage } from '@/plugins/notebook/utils/notebook-storage.js';
|
||||
|
||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
|
||||
const DEFAULT_POSITION = [1, 1];
|
||||
@@ -102,7 +102,7 @@ export default {
|
||||
LayoutFrame
|
||||
},
|
||||
mixins: [conditionalStylesMixin],
|
||||
inject: ['openmct', 'objectPath'],
|
||||
inject: ['openmct', 'objectPath', 'currentView'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@@ -294,16 +294,6 @@ export default {
|
||||
this.requestHistoricalData(this.domainObject);
|
||||
}
|
||||
},
|
||||
getView() {
|
||||
return {
|
||||
getViewContext: () => {
|
||||
return {
|
||||
viewHistoricalData: true,
|
||||
formattedValueForCopy: this.formattedValueForCopy
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
setObject(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.mutablePromise = undefined;
|
||||
@@ -338,30 +328,41 @@ export default {
|
||||
|
||||
this.$emit('formatChanged', this.item, format);
|
||||
},
|
||||
updateViewContext() {
|
||||
this.$emit('contextClick', {
|
||||
viewHistoricalData: true,
|
||||
formattedValueForCopy: this.formattedValueForCopy
|
||||
});
|
||||
},
|
||||
async getContextMenuActions() {
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
|
||||
const actionCollection = this.openmct.actions.get(this.currentObjectPath, this.getView());
|
||||
const actionsObject = actionCollection.getActionsObject();
|
||||
|
||||
let copyToNotebookAction = actionsObject.copyToNotebook;
|
||||
|
||||
let defaultNotebookName;
|
||||
if (defaultNotebook) {
|
||||
const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
|
||||
copyToNotebookAction.name = `Copy to Notebook ${defaultPath}`;
|
||||
} else {
|
||||
actionsObject.copyToNotebook = undefined;
|
||||
delete actionsObject.copyToNotebook;
|
||||
const domainObject = await this.openmct.objects.get(defaultNotebook.identifier);
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
|
||||
return CONTEXT_MENU_ACTIONS.map(actionKey => {
|
||||
return actionsObject[actionKey];
|
||||
}).filter(action => action !== undefined);
|
||||
return CONTEXT_MENU_ACTIONS
|
||||
.map(actionKey => {
|
||||
const action = this.openmct.actions.getAction(actionKey);
|
||||
if (action.key === 'copyToNotebook') {
|
||||
action.name = defaultNotebookName;
|
||||
}
|
||||
|
||||
return action;
|
||||
})
|
||||
.filter(action => action.name !== undefined);
|
||||
},
|
||||
async showContextMenu(event) {
|
||||
this.updateViewContext();
|
||||
const contextMenuActions = await this.getContextMenuActions();
|
||||
|
||||
this.openmct.menus.showMenu(event.x, event.y, contextMenuActions);
|
||||
const menuItems = this.openmct.menus.actionsToMenuItems(contextMenuActions, this.currentObjectPath, this.currentView);
|
||||
this.openmct.menus.showMenu(event.x, event.y, menuItems);
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
|
||||
@@ -20,13 +20,81 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import Layout from './components/DisplayLayout.vue';
|
||||
import Vue from 'vue';
|
||||
import objectUtils from 'objectUtils';
|
||||
import DisplayLayoutType from './DisplayLayoutType.js';
|
||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
||||
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
|
||||
import CopyToClipboardAction from './actions/CopyToClipboardAction';
|
||||
import DisplayLayout from './components/DisplayLayout.vue';
|
||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
||||
import DisplayLayoutType from './DisplayLayoutType.js';
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
class DisplayLayoutView {
|
||||
constructor(openmct, domainObject, objectPath, options) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = objectPath;
|
||||
this.options = options;
|
||||
|
||||
this.component = undefined;
|
||||
}
|
||||
|
||||
show(container, isEditing) {
|
||||
this.component = new Vue({
|
||||
el: container,
|
||||
components: {
|
||||
DisplayLayout
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
objectPath: this.objectPath,
|
||||
options: this.options,
|
||||
objectUtils,
|
||||
currentView: this
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
domainObject: this.domainObject,
|
||||
isEditing
|
||||
};
|
||||
},
|
||||
template: '<display-layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></display-layout>'
|
||||
});
|
||||
}
|
||||
|
||||
getViewContext() {
|
||||
if (!this.component) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return this.component.$refs.displayLayout.getViewContext();
|
||||
}
|
||||
|
||||
getSelectionContext() {
|
||||
return {
|
||||
item: this.domainObject,
|
||||
supportsMultiSelect: true,
|
||||
addElement: this.component && this.component.$refs.displayLayout.addElement,
|
||||
removeItem: this.component && this.component.$refs.displayLayout.removeItem,
|
||||
orderItem: this.component && this.component.$refs.displayLayout.orderItem,
|
||||
duplicateItem: this.component && this.component.$refs.displayLayout.duplicateItem,
|
||||
switchViewType: this.component && this.component.$refs.displayLayout.switchViewType,
|
||||
mergeMultipleTelemetryViews: this.component && this.component.$refs.displayLayout.mergeMultipleTelemetryViews,
|
||||
mergeMultipleOverlayPlots: this.component && this.component.$refs.displayLayout.mergeMultipleOverlayPlots,
|
||||
toggleGrid: this.component && this.component.$refs.displayLayout.toggleGrid
|
||||
};
|
||||
}
|
||||
|
||||
onEditModeChange(isEditing) {
|
||||
this.component.isEditing = isEditing;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.component.$destroy();
|
||||
this.component = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export default function DisplayLayoutPlugin(options) {
|
||||
return function (openmct) {
|
||||
@@ -41,51 +109,7 @@ export default function DisplayLayoutPlugin(options) {
|
||||
return domainObject.type === 'layout';
|
||||
},
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show(container) {
|
||||
component = new Vue({
|
||||
el: container,
|
||||
components: {
|
||||
Layout
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
objectUtils,
|
||||
options,
|
||||
objectPath
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject: domainObject,
|
||||
isEditing: openmct.editor.isEditing()
|
||||
};
|
||||
},
|
||||
template: '<layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></layout>'
|
||||
});
|
||||
},
|
||||
getSelectionContext() {
|
||||
return {
|
||||
item: domainObject,
|
||||
supportsMultiSelect: true,
|
||||
addElement: component && component.$refs.displayLayout.addElement,
|
||||
removeItem: component && component.$refs.displayLayout.removeItem,
|
||||
orderItem: component && component.$refs.displayLayout.orderItem,
|
||||
duplicateItem: component && component.$refs.displayLayout.duplicateItem,
|
||||
switchViewType: component && component.$refs.displayLayout.switchViewType,
|
||||
mergeMultipleTelemetryViews: component && component.$refs.displayLayout.mergeMultipleTelemetryViews,
|
||||
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots,
|
||||
toggleGrid: component && component.$refs.displayLayout.toggleGrid
|
||||
};
|
||||
},
|
||||
onEditModeChange: function (isEditing) {
|
||||
component.isEditing = isEditing;
|
||||
},
|
||||
destroy() {
|
||||
component.$destroy();
|
||||
}
|
||||
};
|
||||
return new DisplayLayoutView(openmct, domainObject, objectPath, options);
|
||||
},
|
||||
priority() {
|
||||
return 100;
|
||||
|
||||
51
src/plugins/hyperlink/HyperlinkLayout.vue
Normal file
51
src/plugins/hyperlink/HyperlinkLayout.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
|
||||
<a class="c-hyperlink"
|
||||
:class="{
|
||||
'c-hyperlink--button' : isButton
|
||||
}"
|
||||
:target="domainObject.linkTarget"
|
||||
:href="domainObject.url"
|
||||
>
|
||||
<span class="c-hyperlink__label">{{ domainObject.displayText }}</span>
|
||||
</a>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
inject: ['domainObject'],
|
||||
computed: {
|
||||
isButton() {
|
||||
if (this.domainObject.displayFormat === "link") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -19,40 +19,41 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define(function () {
|
||||
|
||||
/**
|
||||
* An object containing key-value pairs, where keys are symbolic of
|
||||
* device attributes, and values are functions that take the
|
||||
* `agentService` 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 platform/commonUI/mobile
|
||||
* @private
|
||||
*/
|
||||
import HyperlinkLayout from './HyperlinkLayout.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function HyperlinkProvider(openmct) {
|
||||
|
||||
return {
|
||||
mobile: function (agentService) {
|
||||
return agentService.isMobile();
|
||||
key: 'hyperlink.view',
|
||||
name: 'Hyperlink',
|
||||
cssClass: 'icon-chain-links',
|
||||
canView(domainObject) {
|
||||
return domainObject.type === 'hyperlink';
|
||||
},
|
||||
phone: function (agentService) {
|
||||
return agentService.isPhone();
|
||||
},
|
||||
tablet: function (agentService) {
|
||||
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();
|
||||
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
HyperlinkLayout
|
||||
},
|
||||
provide: {
|
||||
domainObject
|
||||
},
|
||||
template: '<hyperlink-layout></hyperlink-layout>'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
89
src/plugins/hyperlink/plugin.js
Normal file
89
src/plugins/hyperlink/plugin.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/*****************************************************************************
|
||||
* 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 HyperlinkProvider from './HyperlinkProvider';
|
||||
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
openmct.types.addType('hyperlink', {
|
||||
name: 'Hyperlink',
|
||||
key: 'hyperlink',
|
||||
description: 'A hyperlink to redirect to a different link',
|
||||
creatable: true,
|
||||
cssClass: 'icon-chain-links',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.displayFormat = "link";
|
||||
domainObject.linkTarget = "_self";
|
||||
},
|
||||
form: [
|
||||
{
|
||||
"key": "url",
|
||||
"name": "URL",
|
||||
"control": "textfield",
|
||||
"required": true,
|
||||
"cssClass": "l-input-lg"
|
||||
},
|
||||
{
|
||||
"key": "displayText",
|
||||
"name": "Text to Display",
|
||||
"control": "textfield",
|
||||
"required": true,
|
||||
"cssClass": "l-input-lg"
|
||||
},
|
||||
{
|
||||
"key": "displayFormat",
|
||||
"name": "Display Format",
|
||||
"control": "select",
|
||||
"options": [
|
||||
{
|
||||
"name": "Link",
|
||||
"value": "link"
|
||||
},
|
||||
{
|
||||
"name": "Button",
|
||||
"value": "button"
|
||||
}
|
||||
],
|
||||
"cssClass": "l-inline"
|
||||
},
|
||||
{
|
||||
"key": "linkTarget",
|
||||
"name": "Tab to Open Hyperlink",
|
||||
"control": "select",
|
||||
"options": [
|
||||
{
|
||||
"name": "Open in this tab",
|
||||
"value": "_self"
|
||||
},
|
||||
{
|
||||
"name": "Open in a new tab",
|
||||
"value": "_blank"
|
||||
}
|
||||
],
|
||||
"cssClass": "l-inline"
|
||||
|
||||
}
|
||||
]
|
||||
});
|
||||
openmct.objectViews.addProvider(new HyperlinkProvider(openmct));
|
||||
};
|
||||
}
|
||||
130
src/plugins/hyperlink/pluginSpec.js
Normal file
130
src/plugins/hyperlink/pluginSpec.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2016, 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 HyperlinkPlugin from "./plugin";
|
||||
|
||||
function getView(openmct, domainObj, objectPath) {
|
||||
const applicableViews = openmct.objectViews.get(domainObj, objectPath);
|
||||
const hyperLinkView = applicableViews.find((viewProvider) => viewProvider.key === 'hyperlink.view');
|
||||
|
||||
return hyperLinkView.view(domainObj);
|
||||
}
|
||||
|
||||
function destroyView(view) {
|
||||
return view.destroy();
|
||||
}
|
||||
|
||||
describe("The controller for hyperlinks", function () {
|
||||
let mockDomainObject;
|
||||
let mockObjectPath;
|
||||
let openmct;
|
||||
let element;
|
||||
let child;
|
||||
let view;
|
||||
|
||||
beforeEach((done) => {
|
||||
mockObjectPath = [
|
||||
{
|
||||
name: 'mock hyperlink',
|
||||
type: 'hyperlink',
|
||||
identifier: {
|
||||
key: 'mock-hyperlink',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
mockDomainObject = {
|
||||
displayFormat: "",
|
||||
linkTarget: "",
|
||||
name: "Unnamed HyperLink",
|
||||
type: "hyperlink",
|
||||
location: "f69c21ac-24ef-450c-8e2f-3d527087d285",
|
||||
modified: 1627483839783,
|
||||
url: "123",
|
||||
displayText: "123",
|
||||
persisted: 1627483839783,
|
||||
id: "3d9c243d-dffb-446b-8474-d9931a99d679",
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "3d9c243d-dffb-446b-8474-d9931a99d679"
|
||||
}
|
||||
};
|
||||
|
||||
openmct = createOpenMct();
|
||||
openmct.install(new HyperlinkPlugin());
|
||||
|
||||
element = document.createElement('div');
|
||||
element.style.width = '640px';
|
||||
element.style.height = '480px';
|
||||
child = document.createElement('div');
|
||||
child.style.width = '640px';
|
||||
child.style.height = '480px';
|
||||
element.appendChild(child);
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
destroyView(view);
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
it("knows when it should open a new tab", () => {
|
||||
mockDomainObject.displayFormat = "link";
|
||||
mockDomainObject.linkTarget = "_blank";
|
||||
|
||||
view = getView(openmct, mockDomainObject, mockObjectPath);
|
||||
view.show(child, true);
|
||||
|
||||
expect(element.querySelector('.c-hyperlink').target).toBe('_blank');
|
||||
});
|
||||
it("knows when it should open in the same tab", function () {
|
||||
mockDomainObject.displayFormat = "button";
|
||||
mockDomainObject.linkTarget = "_self";
|
||||
|
||||
view = getView(openmct, mockDomainObject, mockObjectPath);
|
||||
view.show(child, true);
|
||||
|
||||
expect(element.querySelector('.c-hyperlink').target).toBe('_self');
|
||||
});
|
||||
|
||||
it("knows when it is a button", function () {
|
||||
mockDomainObject.displayFormat = "button";
|
||||
|
||||
view = getView(openmct, mockDomainObject, mockObjectPath);
|
||||
view.show(child, true);
|
||||
|
||||
expect(element.querySelector('.c-hyperlink--button')).toBeDefined();
|
||||
});
|
||||
it("knows when it is a link", function () {
|
||||
mockDomainObject.displayFormat = "link";
|
||||
|
||||
view = getView(openmct, mockDomainObject, mockObjectPath);
|
||||
view.show(child, true);
|
||||
|
||||
expect(element.querySelector('.c-hyperlink')).not.toHaveClass('c-hyperlink--button');
|
||||
});
|
||||
});
|
||||
37
src/plugins/imagery/ImageryView.js
Normal file
37
src/plugins/imagery/ImageryView.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import ImageryViewLayout from './components/ImageryViewLayout.vue';
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
export default class ImageryView {
|
||||
constructor(openmct, domainObject, objectPath) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = objectPath;
|
||||
this.component = undefined;
|
||||
}
|
||||
|
||||
show(element) {
|
||||
this.component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
ImageryViewLayout
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
domainObject: this.domainObject,
|
||||
objectPath: this.objectPath,
|
||||
currentView: this
|
||||
},
|
||||
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.component.$destroy();
|
||||
this.component = undefined;
|
||||
}
|
||||
|
||||
_getInstance() {
|
||||
return this.component;
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,7 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import ImageryViewLayout from './components/ImageryViewLayout.vue';
|
||||
import Vue from 'vue';
|
||||
import ImageryView from './ImageryView';
|
||||
|
||||
export default function ImageryViewProvider(openmct) {
|
||||
const type = 'example.imagery';
|
||||
@@ -42,31 +40,8 @@ export default function ImageryViewProvider(openmct) {
|
||||
canView: function (domainObject) {
|
||||
return hasImageTelemetry(domainObject);
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
ImageryViewLayout
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
},
|
||||
_getInstance: function () {
|
||||
return component;
|
||||
}
|
||||
};
|
||||
view: function (domainObject, objectPath) {
|
||||
return new ImageryView(openmct, domainObject, objectPath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<div ref="imageBG"
|
||||
class="c-imagery__main-image__bg"
|
||||
:class="{'paused unnsynced': isPaused,'stale':false }"
|
||||
@click="expand"
|
||||
>
|
||||
<div class="image-wrapper"
|
||||
:style="{
|
||||
@@ -170,8 +171,9 @@
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import Compass from './Compass/Compass.vue';
|
||||
|
||||
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
|
||||
import Compass from './Compass/Compass.vue';
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
const REFRESH_CSS_MS = 500;
|
||||
@@ -195,7 +197,7 @@ export default {
|
||||
components: {
|
||||
Compass
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
|
||||
data() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
|
||||
@@ -468,6 +470,16 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
expand() {
|
||||
const actionCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
|
||||
const visibleActions = actionCollection.getVisibleActions();
|
||||
const viewLargeAction = visibleActions
|
||||
&& visibleActions.find(action => action.key === 'large.view');
|
||||
|
||||
if (viewLargeAction && viewLargeAction.appliesTo(this.objectPath, this.currentView)) {
|
||||
viewLargeAction.onItemClicked();
|
||||
}
|
||||
},
|
||||
async initializeRelatedTelemetry() {
|
||||
this.relatedTelemetry = new RelatedTelemetry(
|
||||
this.openmct,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.c-imagery {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&:focus {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getDefaultNotebook } from '../utils/notebook-storage';
|
||||
import { getDefaultNotebook, getNotebookSectionAndPage } from '../utils/notebook-storage';
|
||||
import { addNotebookEntry } from '../utils/notebook-entries';
|
||||
|
||||
export default class CopyToNotebookAction {
|
||||
@@ -15,26 +15,35 @@ export default class CopyToNotebookAction {
|
||||
|
||||
copyToNotebook(entryText) {
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
|
||||
this.openmct.objects.get(notebookStorage.identifier)
|
||||
.then(domainObject => {
|
||||
addNotebookEntry(this.openmct, domainObject, notebookStorage, null, entryText);
|
||||
|
||||
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
|
||||
const { section, page } = getNotebookSectionAndPage(domainObject, notebookStorage.defaultSectionId, notebookStorage.defaultPageId);
|
||||
if (!section || !page) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultPath = `${domainObject.name} - ${section.name} - ${page.name}`;
|
||||
const msg = `Saved to Notebook ${defaultPath}`;
|
||||
this.openmct.notifications.info(msg);
|
||||
});
|
||||
}
|
||||
|
||||
invoke(objectPath, view = {}) {
|
||||
let viewContext = view.getViewContext && view.getViewContext();
|
||||
invoke(objectPath, view) {
|
||||
const formattedValueForCopy = view.getViewContext().row.formattedValueForCopy;
|
||||
|
||||
this.copyToNotebook(viewContext.formattedValueForCopy());
|
||||
this.copyToNotebook(formattedValueForCopy());
|
||||
}
|
||||
|
||||
appliesTo(objectPath, view = {}) {
|
||||
let viewContext = view.getViewContext && view.getViewContext();
|
||||
const viewContext = view.getViewContext && view.getViewContext();
|
||||
const row = viewContext && viewContext.row;
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
|
||||
return viewContext && viewContext.formattedValueForCopy
|
||||
&& typeof viewContext.formattedValueForCopy === 'function';
|
||||
return row.formattedValueForCopy
|
||||
&& typeof row.formattedValueForCopy === 'function';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,14 +43,16 @@
|
||||
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
|
||||
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
|
||||
:default-page-id="defaultPageId"
|
||||
:selected-page-id="selectedPageId"
|
||||
:selected-page-id="getSelectedPageId()"
|
||||
:default-section-id="defaultSectionId"
|
||||
:selected-section-id="selectedSectionId"
|
||||
:selected-section-id="getSelectedSectionId()"
|
||||
:domain-object="domainObject"
|
||||
:page-title="domainObject.configuration.pageTitle"
|
||||
:section-title="domainObject.configuration.sectionTitle"
|
||||
:sections="sections"
|
||||
:sidebar-covers-entries="sidebarCoversEntries"
|
||||
@defaultPageDeleted="cleanupDefaultNotebook"
|
||||
@defaultSectionDeleted="cleanupDefaultNotebook"
|
||||
@pagesChanged="pagesChanged"
|
||||
@selectPage="selectPage"
|
||||
@sectionsChanged="sectionsChanged"
|
||||
@@ -136,7 +138,7 @@ import NotebookEntry from './NotebookEntry.vue';
|
||||
import Search from '@/ui/components/search.vue';
|
||||
import SearchResults from './SearchResults.vue';
|
||||
import Sidebar from './Sidebar.vue';
|
||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSectionId, setDefaultNotebookPageId } from '../utils/notebook-storage';
|
||||
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
||||
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
|
||||
import objectUtils from 'objectUtils';
|
||||
@@ -164,8 +166,10 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedSectionId: this.getDefaultSectionId(),
|
||||
selectedPageId: this.getDefaultPageId(),
|
||||
defaultPageId: this.getDefaultPageId(),
|
||||
defaultSectionId: this.getDefaultSectionId(),
|
||||
selectedSectionId: this.getSelectedSectionId(),
|
||||
selectedPageId: this.getSelectedPageId(),
|
||||
defaultSort: this.domainObject.configuration.defaultSort,
|
||||
focusEntryId: null,
|
||||
search: '',
|
||||
@@ -176,12 +180,6 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
defaultPageId() {
|
||||
return this.getDefaultPageId();
|
||||
},
|
||||
defaultSectionId() {
|
||||
return this.getDefaultSectionId();
|
||||
},
|
||||
filteredAndSortedEntries() {
|
||||
const filterTime = Date.now();
|
||||
const pageEntries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage) || [];
|
||||
@@ -203,24 +201,38 @@ export default {
|
||||
},
|
||||
selectedPage() {
|
||||
const pages = this.getPages();
|
||||
const selectedPage = pages.find(page => page.id === this.selectedPageId);
|
||||
if (!pages.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const selectedPage = pages.find(page => page.id === this.selectedPageId);
|
||||
if (selectedPage) {
|
||||
return selectedPage;
|
||||
}
|
||||
|
||||
if (!selectedPage && !pages.length) {
|
||||
return undefined;
|
||||
const defaultPage = pages.find(page => page.id === this.defaultPageId);
|
||||
if (defaultPage) {
|
||||
return defaultPage;
|
||||
}
|
||||
|
||||
return pages[0];
|
||||
return this.pages[0];
|
||||
},
|
||||
selectedSection() {
|
||||
if (!this.sections.length) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.sections.find(section => section.id === this.selectedSectionId);
|
||||
const selectedSection = this.sections.find(section => section.id === this.selectedSectionId);
|
||||
if (selectedSection) {
|
||||
return selectedSection;
|
||||
}
|
||||
|
||||
const defaultSection = this.sections.find(section => section.id === this.defaultSectionId);
|
||||
if (defaultSection) {
|
||||
return defaultSection;
|
||||
}
|
||||
|
||||
return this.sections[0];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -301,26 +313,29 @@ export default {
|
||||
this.sectionsChanged({ sections });
|
||||
this.resetSearch();
|
||||
},
|
||||
cleanupDefaultNotebook() {
|
||||
this.defaultPageId = undefined;
|
||||
this.defaultSectionId = undefined;
|
||||
this.removeDefaultClass(this.domainObject);
|
||||
clearDefaultNotebook();
|
||||
},
|
||||
setSectionAndPageFromUrl() {
|
||||
let sectionId = this.getSectionIdFromUrl() || this.selectedSectionId;
|
||||
let pageId = this.getPageIdFromUrl() || this.selectedPageId;
|
||||
let sectionId = this.getSectionIdFromUrl() || this.getDefaultSectionId() || this.getSelectedSectionId();
|
||||
let pageId = this.getPageIdFromUrl() || this.getDefaultPageId() || this.getSelectedPageId();
|
||||
|
||||
this.selectSection(sectionId);
|
||||
this.selectPage(pageId);
|
||||
},
|
||||
createNotebookStorageObject() {
|
||||
const notebookMeta = {
|
||||
name: this.domainObject.name,
|
||||
identifier: this.domainObject.identifier,
|
||||
link: this.getLinktoNotebook()
|
||||
};
|
||||
const page = this.selectedPage;
|
||||
const section = this.selectedSection;
|
||||
|
||||
return {
|
||||
notebookMeta,
|
||||
page,
|
||||
section
|
||||
name: this.domainObject.name,
|
||||
identifier: this.domainObject.identifier,
|
||||
link: this.getLinktoNotebook(),
|
||||
defaultSectionId: section.id,
|
||||
defaultPageId: page.id
|
||||
};
|
||||
},
|
||||
deleteEntry(entryId) {
|
||||
@@ -419,35 +434,21 @@ export default {
|
||||
this.sidebarCoversEntries = sidebarCoversEntries;
|
||||
},
|
||||
getDefaultPageId() {
|
||||
let defaultPageId;
|
||||
|
||||
if (this.isDefaultNotebook()) {
|
||||
defaultPageId = getDefaultNotebook().page.id;
|
||||
} else {
|
||||
const firstSection = this.getSections()[0];
|
||||
defaultPageId = firstSection && firstSection.pages[0].id;
|
||||
}
|
||||
|
||||
return defaultPageId;
|
||||
return this.isDefaultNotebook()
|
||||
? getDefaultNotebook().defaultPageId
|
||||
: undefined;
|
||||
},
|
||||
isDefaultNotebook() {
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.notebookMeta.identifier;
|
||||
const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.identifier;
|
||||
|
||||
return defaultNotebookIdentifier !== null
|
||||
&& this.openmct.objects.areIdsEqual(defaultNotebookIdentifier, this.domainObject.identifier);
|
||||
},
|
||||
getDefaultSectionId() {
|
||||
let defaultSectionId;
|
||||
|
||||
if (this.isDefaultNotebook()) {
|
||||
defaultSectionId = getDefaultNotebook().section.id;
|
||||
} else {
|
||||
const firstSection = this.getSections()[0];
|
||||
defaultSectionId = firstSection && firstSection.id;
|
||||
}
|
||||
|
||||
return defaultSectionId;
|
||||
return this.isDefaultNotebook()
|
||||
? getDefaultNotebook().defaultSectionId
|
||||
: undefined;
|
||||
},
|
||||
getDefaultNotebookObject() {
|
||||
const oldNotebookStorage = getDefaultNotebook();
|
||||
@@ -455,7 +456,7 @@ export default {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.openmct.objects.get(oldNotebookStorage.notebookMeta.identifier);
|
||||
return this.openmct.objects.get(oldNotebookStorage.identifier);
|
||||
},
|
||||
getLinktoNotebook() {
|
||||
const objectPath = this.openmct.router.path;
|
||||
@@ -573,6 +574,22 @@ export default {
|
||||
|
||||
return selectedSection.pages;
|
||||
},
|
||||
getSelectedPageId() {
|
||||
const page = this.selectedPage;
|
||||
if (!page) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return page.id;
|
||||
},
|
||||
getSelectedSectionId() {
|
||||
const section = this.selectedSection;
|
||||
if (!section) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return section.id;
|
||||
},
|
||||
newEntry(embed = null) {
|
||||
this.resetSearch();
|
||||
const notebookStorage = this.createNotebookStorageObject();
|
||||
@@ -616,51 +633,26 @@ export default {
|
||||
},
|
||||
async updateDefaultNotebook(notebookStorage) {
|
||||
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
||||
if (!defaultNotebookObject) {
|
||||
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
|
||||
} else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
|
||||
const isSameNotebook = defaultNotebookObject
|
||||
&& objectUtils.makeKeyString(defaultNotebookObject.identifier) === objectUtils.makeKeyString(notebookStorage.identifier);
|
||||
if (!isSameNotebook) {
|
||||
this.removeDefaultClass(defaultNotebookObject);
|
||||
}
|
||||
|
||||
if (!defaultNotebookObject || !isSameNotebook) {
|
||||
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
|
||||
}
|
||||
|
||||
if (this.defaultSectionId && this.defaultSectionId.length === 0 || this.defaultSectionId !== notebookStorage.section.id) {
|
||||
this.defaultSectionId = notebookStorage.section.id;
|
||||
setDefaultNotebookSection(notebookStorage.section);
|
||||
if (this.defaultSectionId !== notebookStorage.defaultSectionId) {
|
||||
setDefaultNotebookSectionId(notebookStorage.defaultSectionId);
|
||||
this.defaultSectionId = notebookStorage.defaultSectionId;
|
||||
}
|
||||
|
||||
if (this.defaultPageId && this.defaultPageId.length === 0 || this.defaultPageId !== notebookStorage.page.id) {
|
||||
this.defaultPageId = notebookStorage.page.id;
|
||||
setDefaultNotebookPage(notebookStorage.page);
|
||||
if (this.defaultPageId !== notebookStorage.defaultPageId) {
|
||||
setDefaultNotebookPageId(notebookStorage.defaultPageId);
|
||||
this.defaultPageId = notebookStorage.defaultPageId;
|
||||
}
|
||||
},
|
||||
updateDefaultNotebookPage(pages, id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
if (!notebookStorage
|
||||
|| notebookStorage.notebookMeta.identifier.key !== this.domainObject.identifier.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultNotebookPage = notebookStorage.page;
|
||||
const page = pages.find(p => p.id === id);
|
||||
if (!page && defaultNotebookPage.id === id) {
|
||||
this.defaultSectionId = null;
|
||||
this.defaultPageId = null;
|
||||
this.removeDefaultClass(this.domainObject);
|
||||
clearDefaultNotebook();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (id !== defaultNotebookPage.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDefaultNotebookPage(page);
|
||||
},
|
||||
updateDefaultNotebookSection(sections, id) {
|
||||
if (!id) {
|
||||
return;
|
||||
@@ -668,26 +660,26 @@ export default {
|
||||
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
if (!notebookStorage
|
||||
|| notebookStorage.notebookMeta.identifier.key !== this.domainObject.identifier.key) {
|
||||
|| notebookStorage.identifier.key !== this.domainObject.identifier.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultNotebookSection = notebookStorage.section;
|
||||
const section = sections.find(s => s.id === id);
|
||||
if (!section && defaultNotebookSection.id === id) {
|
||||
this.defaultSectionId = null;
|
||||
this.defaultPageId = null;
|
||||
this.removeDefaultClass(this.domainObject);
|
||||
clearDefaultNotebook();
|
||||
const defaultNotebookSectionId = notebookStorage.defaultSectionId;
|
||||
if (defaultNotebookSectionId === id) {
|
||||
const section = sections.find(s => s.id === id);
|
||||
if (!section) {
|
||||
this.removeDefaultClass(this.domainObject);
|
||||
clearDefaultNotebook();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (id !== defaultNotebookSectionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id !== defaultNotebookSection.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDefaultNotebookSection(section);
|
||||
setDefaultNotebookSectionId(defaultNotebookSectionId);
|
||||
},
|
||||
updateEntry(entry) {
|
||||
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||
@@ -715,19 +707,27 @@ export default {
|
||||
sectionId: this.selectedSectionId
|
||||
});
|
||||
},
|
||||
sectionsChanged({ sections, id = null }) {
|
||||
sectionsChanged({ sections, id = undefined }) {
|
||||
mutateObject(this.openmct, this.domainObject, 'configuration.sections', sections);
|
||||
this.updateDefaultNotebookSection(sections, id);
|
||||
},
|
||||
selectPage(pageId) {
|
||||
if (!pageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedPageId = pageId;
|
||||
this.syncUrlWithPageAndSection();
|
||||
},
|
||||
selectSection(sectionId) {
|
||||
if (!sectionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedSectionId = sectionId;
|
||||
|
||||
const defaultPageId = this.selectedSection.pages[0].id;
|
||||
this.selectPage(defaultPageId);
|
||||
const pageId = this.selectedSection.pages[0].id;
|
||||
this.selectPage(pageId);
|
||||
|
||||
this.syncUrlWithPageAndSection();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import PainterroInstance from '../utils/painterroInstance';
|
||||
import SnapshotTemplate from './snapshot-template.html';
|
||||
|
||||
import { updateNotebookImageDomainObject } from '../utils/notebook-image';
|
||||
import ImageExporter from '../../../exporters/ImageExporter';
|
||||
|
||||
import PopupMenu from './PopupMenu.vue';
|
||||
import Vue from 'vue';
|
||||
@@ -71,7 +72,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.addPopupMenuItems();
|
||||
this.exportImageService = this.openmct.$injector.get('exportImageService');
|
||||
this.imageExporter = new ImageExporter(this.openmct);
|
||||
},
|
||||
methods: {
|
||||
addPopupMenuItems() {
|
||||
@@ -234,9 +235,9 @@ export default {
|
||||
let element = this.snapshot.$refs['snapshot-image'];
|
||||
|
||||
if (type === 'png') {
|
||||
this.exportImageService.exportPNG(element, this.embed.name);
|
||||
this.imageExporter.exportPNG(element, this.embed.name);
|
||||
} else {
|
||||
this.exportImageService.exportJPG(element, this.embed.name);
|
||||
this.imageExporter.exportJPG(element, this.embed.name);
|
||||
}
|
||||
},
|
||||
previewEmbed() {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<script>
|
||||
import Snapshot from '../snapshot';
|
||||
import { getDefaultNotebook, validateNotebookStorageObject } from '../utils/notebook-storage';
|
||||
import { getDefaultNotebook, getNotebookSectionAndPage, validateNotebookStorageObject } from '../utils/notebook-storage';
|
||||
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
|
||||
|
||||
export default {
|
||||
@@ -56,11 +56,10 @@ export default {
|
||||
this.setDefaultNotebookStatus();
|
||||
},
|
||||
methods: {
|
||||
async getDefaultNotebookObject() {
|
||||
getDefaultNotebookObject() {
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const defaultNotebookObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
|
||||
|
||||
return defaultNotebookObject;
|
||||
return defaultNotebook && this.openmct.objects.get(defaultNotebook.identifier);
|
||||
},
|
||||
async showMenu(event) {
|
||||
const notebookTypes = [];
|
||||
@@ -70,36 +69,39 @@ export default {
|
||||
|
||||
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
||||
if (defaultNotebookObject) {
|
||||
const name = defaultNotebookObject.name;
|
||||
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const sectionName = defaultNotebook.section.name;
|
||||
const pageName = defaultNotebook.page.name;
|
||||
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
|
||||
const { section, page } = getNotebookSectionAndPage(defaultNotebookObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
|
||||
if (section && page) {
|
||||
const name = defaultNotebookObject.name;
|
||||
const sectionName = section.name;
|
||||
const pageName = page.name;
|
||||
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
|
||||
|
||||
notebookTypes.push({
|
||||
cssClass: 'icon-notebook',
|
||||
name: `Save to Notebook ${defaultPath}`,
|
||||
callBack: () => {
|
||||
return this.snapshot(NOTEBOOK_DEFAULT);
|
||||
}
|
||||
});
|
||||
notebookTypes.push({
|
||||
cssClass: 'icon-notebook',
|
||||
name: `Save to Notebook ${defaultPath}`,
|
||||
onItemClicked: () => {
|
||||
return this.snapshot(NOTEBOOK_DEFAULT, event.target);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
notebookTypes.push({
|
||||
cssClass: 'icon-camera',
|
||||
name: 'Save to Notebook Snapshots',
|
||||
callBack: () => {
|
||||
return this.snapshot(NOTEBOOK_SNAPSHOT);
|
||||
onItemClicked: () => {
|
||||
return this.snapshot(NOTEBOOK_SNAPSHOT, event.target);
|
||||
}
|
||||
});
|
||||
|
||||
this.openmct.menus.showMenu(x, y, notebookTypes);
|
||||
},
|
||||
snapshot(notebookType) {
|
||||
snapshot(notebookType, target) {
|
||||
this.$nextTick(() => {
|
||||
const element = document.querySelector('.c-overlay__contents')
|
||||
|| document.getElementsByClassName('l-shell__main-container')[0];
|
||||
const wrapper = target && target.closest('.js-notebook-snapshot-item-wrapper')
|
||||
|| document;
|
||||
const element = wrapper.querySelector('.js-notebook-snapshot-item');
|
||||
|
||||
const bounds = this.openmct.time.bounds();
|
||||
const link = !this.ignoreLink
|
||||
@@ -119,9 +121,8 @@ export default {
|
||||
},
|
||||
setDefaultNotebookStatus() {
|
||||
let defaultNotebookObject = getDefaultNotebook();
|
||||
|
||||
if (defaultNotebookObject && defaultNotebookObject.notebookMeta) {
|
||||
let notebookIdentifier = defaultNotebookObject.notebookMeta.identifier;
|
||||
if (defaultNotebookObject) {
|
||||
let notebookIdentifier = defaultNotebookObject.identifier;
|
||||
|
||||
this.openmct.status.set(notebookIdentifier, 'notebook-default');
|
||||
}
|
||||
|
||||
@@ -87,22 +87,26 @@ export default {
|
||||
|
||||
const selectedPage = this.pages.find(p => p.isSelected);
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const defaultpage = defaultNotebook && defaultNotebook.page;
|
||||
const defaultPageId = defaultNotebook && defaultNotebook.defaultPageId;
|
||||
const isPageSelected = selectedPage && selectedPage.id === id;
|
||||
const isPageDefault = defaultpage && defaultpage.id === id;
|
||||
const isPageDefault = defaultPageId === id;
|
||||
const pages = this.pages.filter(s => s.id !== id);
|
||||
let selectedPageId;
|
||||
|
||||
if (isPageSelected && defaultpage) {
|
||||
if (isPageSelected && defaultPageId) {
|
||||
pages.forEach(s => {
|
||||
s.isSelected = false;
|
||||
if (defaultpage && defaultpage.id === s.id) {
|
||||
if (defaultPageId === s.id) {
|
||||
selectedPageId = s.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (pages.length && isPageSelected && (!defaultpage || isPageDefault)) {
|
||||
if (isPageDefault) {
|
||||
this.$emit('defaultPageDeleted');
|
||||
}
|
||||
|
||||
if (pages.length && isPageSelected && (!defaultPageId || isPageDefault)) {
|
||||
selectedPageId = pages[0].id;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,21 +75,25 @@ export default {
|
||||
|
||||
const selectedSection = this.sections.find(s => s.id === this.selectedSectionId);
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const defaultSection = defaultNotebook && defaultNotebook.section;
|
||||
const defaultSectionId = defaultNotebook && defaultNotebook.defaultSectionId;
|
||||
const isSectionSelected = selectedSection && selectedSection.id === id;
|
||||
const isSectionDefault = defaultSection && defaultSection.id === id;
|
||||
const isSectionDefault = defaultSectionId === id;
|
||||
const sections = this.sections.filter(s => s.id !== id);
|
||||
|
||||
if (isSectionSelected && defaultSection) {
|
||||
if (isSectionSelected && defaultSectionId) {
|
||||
sections.forEach(s => {
|
||||
s.isSelected = false;
|
||||
if (defaultSection && defaultSection.id === s.id) {
|
||||
if (defaultSectionId === s.id) {
|
||||
s.isSelected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sections.length && isSectionSelected && (!defaultSection || isSectionDefault)) {
|
||||
if (isSectionDefault) {
|
||||
this.$emit('defaultSectionDeleted');
|
||||
}
|
||||
|
||||
if (sections.length && isSectionSelected && (!defaultSectionId || isSectionDefault)) {
|
||||
sections[0].isSelected = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
:domain-object="domainObject"
|
||||
:sections="sections"
|
||||
:section-title="sectionTitle"
|
||||
@defaultSectionDeleted="defaultSectionDeleted"
|
||||
@updateSection="sectionsChanged"
|
||||
@selectSection="selectSection"
|
||||
/>
|
||||
@@ -50,6 +51,7 @@
|
||||
:sections="sections"
|
||||
:sidebar-covers-entries="sidebarCoversEntries"
|
||||
:page-title="pageTitle"
|
||||
@defaultPageDeleted="defaultPageDeleted"
|
||||
@toggleNav="toggleNav"
|
||||
@updatePage="pagesChanged"
|
||||
@selectPage="selectPage"
|
||||
@@ -218,6 +220,12 @@ export default {
|
||||
sectionTitle
|
||||
};
|
||||
},
|
||||
defaultPageDeleted() {
|
||||
this.$emit('defaultPageDeleted');
|
||||
},
|
||||
defaultSectionDeleted() {
|
||||
this.$emit('defaultSectionDeleted');
|
||||
},
|
||||
toggleNav() {
|
||||
this.$emit('toggleNav');
|
||||
},
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries';
|
||||
import { getDefaultNotebook, getDefaultNotebookLink, setDefaultNotebook } from './utils/notebook-storage';
|
||||
import { getDefaultNotebook, getNotebookSectionAndPage, getDefaultNotebookLink, setDefaultNotebook } from './utils/notebook-storage';
|
||||
import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants';
|
||||
import { createNotebookImageDomainObject, DEFAULT_SIZE } from './utils/notebook-image';
|
||||
|
||||
import SnapshotContainer from './snapshot-container';
|
||||
import ImageExporter from '../../exporters/ImageExporter';
|
||||
|
||||
export default class Snapshot {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.snapshotContainer = new SnapshotContainer(openmct);
|
||||
this.imageExporter = new ImageExporter(openmct);
|
||||
|
||||
this.capture = this.capture.bind(this);
|
||||
this._saveSnapShot = this._saveSnapShot.bind(this);
|
||||
}
|
||||
|
||||
capture(snapshotMeta, notebookType, domElement) {
|
||||
const exportImageService = this.openmct.$injector.get('exportImageService');
|
||||
|
||||
const options = {
|
||||
className: 's-status-taking-snapshot',
|
||||
thumbnailSize: DEFAULT_SIZE
|
||||
};
|
||||
exportImageService.exportPNGtoSRC(domElement, options)
|
||||
this.imageExporter.exportPNGtoSRC(domElement, options)
|
||||
.then(function ({blob, thumbnail}) {
|
||||
const reader = new window.FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
@@ -58,20 +58,25 @@ export default class Snapshot {
|
||||
*/
|
||||
_saveToDefaultNoteBook(embed) {
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
|
||||
this.openmct.objects.get(notebookStorage.identifier)
|
||||
.then(async (domainObject) => {
|
||||
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
|
||||
|
||||
let link = notebookStorage.notebookMeta.link;
|
||||
let link = notebookStorage.link;
|
||||
|
||||
// Backwards compatibility fix (old notebook model without link)
|
||||
if (!link) {
|
||||
link = await getDefaultNotebookLink(this.openmct, domainObject);
|
||||
notebookStorage.notebookMeta.link = link;
|
||||
notebookStorage.link = link;
|
||||
setDefaultNotebook(this.openmct, notebookStorage);
|
||||
}
|
||||
|
||||
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
|
||||
const { section, page } = getNotebookSectionAndPage(domainObject, notebookStorage.defaultSectionId, notebookStorage.defaultPageId);
|
||||
if (!section || !page) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultPath = `${domainObject.name} - ${section.name} - ${page.name}`;
|
||||
const msg = `Saved to Notebook ${defaultPath}`;
|
||||
this._showNotification(msg, link);
|
||||
});
|
||||
|
||||
@@ -9,24 +9,24 @@ const TIME_BOUNDS = {
|
||||
};
|
||||
|
||||
export function addEntryIntoPage(notebookStorage, entries, entry) {
|
||||
const defaultSection = notebookStorage.section;
|
||||
const defaultPage = notebookStorage.page;
|
||||
if (!defaultSection || !defaultPage) {
|
||||
const defaultSectionId = notebookStorage.defaultSectionId;
|
||||
const defaultPageId = notebookStorage.defaultPageId;
|
||||
if (!defaultSectionId || !defaultPageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newEntries = JSON.parse(JSON.stringify(entries));
|
||||
let section = newEntries[defaultSection.id];
|
||||
let section = newEntries[defaultSectionId];
|
||||
if (!section) {
|
||||
newEntries[defaultSection.id] = {};
|
||||
newEntries[defaultSectionId] = {};
|
||||
}
|
||||
|
||||
let page = newEntries[defaultSection.id][defaultPage.id];
|
||||
let page = newEntries[defaultSectionId][defaultPageId];
|
||||
if (!page) {
|
||||
newEntries[defaultSection.id][defaultPage.id] = [];
|
||||
newEntries[defaultSectionId][defaultPageId] = [];
|
||||
}
|
||||
|
||||
newEntries[defaultSection.id][defaultPage.id].push(entry);
|
||||
newEntries[defaultSectionId][defaultPageId].push(entry);
|
||||
|
||||
return newEntries;
|
||||
}
|
||||
|
||||
@@ -23,28 +23,13 @@ import * as NotebookEntries from './notebook-entries';
|
||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
|
||||
const notebookStorage = {
|
||||
notebookMeta: {
|
||||
name: 'notebook',
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'test-notebook'
|
||||
}
|
||||
name: 'notebook',
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'test-notebook'
|
||||
},
|
||||
section: {
|
||||
id: '03a79b6a-971c-4e56-9892-ec536332c3f0',
|
||||
isDefault: true,
|
||||
isSelected: true,
|
||||
name: 'section',
|
||||
pages: [],
|
||||
sectionTitle: 'Section'
|
||||
},
|
||||
page: {
|
||||
id: '8b548fd9-2b8a-4b02-93a9-4138e22eba00',
|
||||
isDefault: true,
|
||||
isSelected: true,
|
||||
name: 'page',
|
||||
pageTitle: 'Page'
|
||||
}
|
||||
defaultSectionId: '03a79b6a-971c-4e56-9892-ec536332c3f0',
|
||||
defaultPageId: '8b548fd9-2b8a-4b02-93a9-4138e22eba00'
|
||||
};
|
||||
|
||||
const notebookEntries = {
|
||||
|
||||
@@ -19,18 +19,22 @@ function defaultNotebookObjectChanged(newDomainObject) {
|
||||
clearDefaultNotebook();
|
||||
}
|
||||
|
||||
function observeDefaultNotebookObject(openmct, notebookMeta, domainObject) {
|
||||
function observeDefaultNotebookObject(openmct, notebookStorage, domainObject) {
|
||||
if (currentNotebookObjectIdentifier
|
||||
&& objectUtils.makeKeyString(currentNotebookObjectIdentifier) === objectUtils.makeKeyString(notebookMeta.identifier)) {
|
||||
&& objectUtils.makeKeyString(currentNotebookObjectIdentifier) === objectUtils.makeKeyString(notebookStorage.identifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeListener();
|
||||
|
||||
unlisten = openmct.objects.observe(domainObject, '*', defaultNotebookObjectChanged);
|
||||
}
|
||||
|
||||
function removeListener() {
|
||||
if (unlisten) {
|
||||
unlisten();
|
||||
unlisten = null;
|
||||
}
|
||||
|
||||
unlisten = openmct.objects.observe(domainObject, '*', defaultNotebookObjectChanged);
|
||||
}
|
||||
|
||||
function saveDefaultNotebook(notebookStorage) {
|
||||
@@ -39,6 +43,8 @@ function saveDefaultNotebook(notebookStorage) {
|
||||
|
||||
export function clearDefaultNotebook() {
|
||||
currentNotebookObjectIdentifier = null;
|
||||
removeListener();
|
||||
|
||||
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, null);
|
||||
}
|
||||
|
||||
@@ -48,6 +54,17 @@ export function getDefaultNotebook() {
|
||||
return JSON.parse(notebookStorage);
|
||||
}
|
||||
|
||||
export function getNotebookSectionAndPage(domainObject, sectionId, pageId) {
|
||||
const configuration = domainObject.configuration;
|
||||
const section = configuration && configuration.sections.find(s => s.id === sectionId);
|
||||
const page = section && section.pages.find(p => p.id === pageId);
|
||||
|
||||
return {
|
||||
section,
|
||||
page
|
||||
};
|
||||
}
|
||||
|
||||
export async function getDefaultNotebookLink(openmct, domainObject = null) {
|
||||
if (!domainObject) {
|
||||
return null;
|
||||
@@ -59,9 +76,9 @@ export async function getDefaultNotebookLink(openmct, domainObject = null) {
|
||||
.reverse()
|
||||
.join('/')
|
||||
);
|
||||
const { page, section } = getDefaultNotebook();
|
||||
const { defaultPageId, defaultSectionId } = getDefaultNotebook();
|
||||
|
||||
return `#/browse/${path}?sectionId=${section.id}&pageId=${page.id}`;
|
||||
return `#/browse/${path}?sectionId=${defaultSectionId}&pageId=${defaultPageId}`;
|
||||
}
|
||||
|
||||
export function setDefaultNotebook(openmct, notebookStorage, domainObject) {
|
||||
@@ -69,15 +86,15 @@ export function setDefaultNotebook(openmct, notebookStorage, domainObject) {
|
||||
saveDefaultNotebook(notebookStorage);
|
||||
}
|
||||
|
||||
export function setDefaultNotebookSection(section) {
|
||||
export function setDefaultNotebookSectionId(sectionId) {
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
notebookStorage.section = section;
|
||||
notebookStorage.defaultSectionId = sectionId;
|
||||
saveDefaultNotebook(notebookStorage);
|
||||
}
|
||||
|
||||
export function setDefaultNotebookPage(page) {
|
||||
export function setDefaultNotebookPageId(pageId) {
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
notebookStorage.page = page;
|
||||
notebookStorage.defaultPageId = pageId;
|
||||
saveDefaultNotebook(notebookStorage);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,37 +23,44 @@
|
||||
import * as NotebookStorage from './notebook-storage';
|
||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
|
||||
const notebookSection = {
|
||||
id: 'temp-section',
|
||||
isDefault: false,
|
||||
isSelected: true,
|
||||
name: 'section',
|
||||
pages: [
|
||||
{
|
||||
id: 'temp-page',
|
||||
isDefault: false,
|
||||
isSelected: true,
|
||||
name: 'page',
|
||||
pageTitle: 'Page'
|
||||
}
|
||||
],
|
||||
sectionTitle: 'Section'
|
||||
};
|
||||
|
||||
const domainObject = {
|
||||
name: 'notebook',
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'test-notebook'
|
||||
},
|
||||
configuration: {
|
||||
sections: [
|
||||
notebookSection
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const notebookStorage = {
|
||||
notebookMeta: {
|
||||
name: 'notebook',
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'test-notebook'
|
||||
}
|
||||
name: 'notebook',
|
||||
identifier: {
|
||||
namespace: '',
|
||||
key: 'test-notebook'
|
||||
},
|
||||
section: {
|
||||
id: 'temp-section',
|
||||
isDefault: false,
|
||||
isSelected: true,
|
||||
name: 'section',
|
||||
pages: [],
|
||||
sectionTitle: 'Section'
|
||||
},
|
||||
page: {
|
||||
id: 'temp-page',
|
||||
isDefault: false,
|
||||
isSelected: true,
|
||||
name: 'page',
|
||||
pageTitle: 'Page'
|
||||
}
|
||||
defaultSectionId: 'temp-section',
|
||||
defaultPageId: 'temp-page'
|
||||
};
|
||||
|
||||
let openmct;
|
||||
@@ -104,7 +111,7 @@ describe('Notebook Storage:', () => {
|
||||
expect(JSON.stringify(defaultNotebook)).toBe(JSON.stringify(notebookStorage));
|
||||
});
|
||||
|
||||
it('has correct section on setDefaultNotebookSection', () => {
|
||||
it('has correct section on setDefaultNotebookSectionId', () => {
|
||||
const section = {
|
||||
id: 'new-temp-section',
|
||||
isDefault: true,
|
||||
@@ -115,14 +122,14 @@ describe('Notebook Storage:', () => {
|
||||
};
|
||||
|
||||
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
|
||||
NotebookStorage.setDefaultNotebookSection(section);
|
||||
NotebookStorage.setDefaultNotebookSectionId(section.id);
|
||||
|
||||
const defaultNotebook = NotebookStorage.getDefaultNotebook();
|
||||
const newSection = defaultNotebook.section;
|
||||
expect(JSON.stringify(section)).toBe(JSON.stringify(newSection));
|
||||
const defaultSectionId = defaultNotebook.defaultSectionId;
|
||||
expect(section.id).toBe(defaultSectionId);
|
||||
});
|
||||
|
||||
it('has correct page on setDefaultNotebookPage', () => {
|
||||
it('has correct page on setDefaultNotebookPageId', () => {
|
||||
const page = {
|
||||
id: 'new-temp-page',
|
||||
isDefault: true,
|
||||
@@ -132,10 +139,52 @@ describe('Notebook Storage:', () => {
|
||||
};
|
||||
|
||||
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
|
||||
NotebookStorage.setDefaultNotebookPage(page);
|
||||
NotebookStorage.setDefaultNotebookPageId(page.id);
|
||||
|
||||
const defaultNotebook = NotebookStorage.getDefaultNotebook();
|
||||
const newPage = defaultNotebook.page;
|
||||
expect(JSON.stringify(page)).toBe(JSON.stringify(newPage));
|
||||
const newPageId = defaultNotebook.defaultPageId;
|
||||
expect(page.id).toBe(newPageId);
|
||||
});
|
||||
|
||||
describe('is getNotebookSectionAndPage function searches and returns correct,', () => {
|
||||
let section;
|
||||
let page;
|
||||
|
||||
beforeEach(() => {
|
||||
const sectionId = 'temp-section';
|
||||
const pageId = 'temp-page';
|
||||
|
||||
const sectionAndpage = NotebookStorage.getNotebookSectionAndPage(domainObject, sectionId, pageId);
|
||||
section = sectionAndpage.section;
|
||||
page = sectionAndpage.page;
|
||||
});
|
||||
|
||||
it('id for section from notebook domain object', () => {
|
||||
expect(section.id).toEqual('temp-section');
|
||||
});
|
||||
|
||||
it('name for section from notebook domain object', () => {
|
||||
expect(section.name).toEqual('section');
|
||||
});
|
||||
|
||||
it('sectionTitle for section from notebook domain object', () => {
|
||||
expect(section.sectionTitle).toEqual('Section');
|
||||
});
|
||||
|
||||
it('number of pages for section from notebook domain object', () => {
|
||||
expect(section.pages.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('id for page from notebook domain object', () => {
|
||||
expect(page.id).toEqual('temp-page');
|
||||
});
|
||||
|
||||
it('name for page from notebook domain object', () => {
|
||||
expect(page.name).toEqual('page');
|
||||
});
|
||||
|
||||
it('pageTitle for page from notebook domain object', () => {
|
||||
expect(page.pageTitle).toEqual('Page');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
106
src/plugins/persistence/couch/CouchChangesFeed.js
Normal file
106
src/plugins/persistence/couch/CouchChangesFeed.js
Normal file
@@ -0,0 +1,106 @@
|
||||
(function () {
|
||||
const connections = [];
|
||||
let connected = false;
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
|
||||
self.onconnect = function (e) {
|
||||
let port = e.ports[0];
|
||||
connections.push(port);
|
||||
|
||||
port.postMessage({
|
||||
type: 'connection',
|
||||
connectionId: connections.length
|
||||
});
|
||||
|
||||
port.onmessage = async function (event) {
|
||||
if (event.data.request === 'close') {
|
||||
connections.splice(event.data.connectionId - 1, 1);
|
||||
if (connections.length <= 0) {
|
||||
// abort any outstanding requests if there's nobody listening to it.
|
||||
controller.abort();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.request === 'changes') {
|
||||
if (connected === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
connected = true;
|
||||
|
||||
let url = event.data.url;
|
||||
let body = event.data.body;
|
||||
let error = false;
|
||||
// feed=continuous maintains an indefinitely open connection with a keep-alive of HEARTBEAT milliseconds until this client closes the connection
|
||||
// style=main_only returns only the current winning revision of the document
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": 'application/json'
|
||||
},
|
||||
signal,
|
||||
body
|
||||
});
|
||||
|
||||
let reader;
|
||||
|
||||
if (response.body === undefined) {
|
||||
error = true;
|
||||
} else {
|
||||
reader = response.body.getReader();
|
||||
}
|
||||
|
||||
while (!error) {
|
||||
const {done, value} = await reader.read();
|
||||
//done is true when we lose connection with the provider
|
||||
if (done) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
let chunk = new Uint8Array(value.length);
|
||||
chunk.set(value, 0);
|
||||
const decodedChunk = new TextDecoder("utf-8").decode(chunk).split('\n');
|
||||
if (decodedChunk.length && decodedChunk[decodedChunk.length - 1] === '') {
|
||||
decodedChunk.forEach((doc, index) => {
|
||||
try {
|
||||
if (doc) {
|
||||
const objectChanges = JSON.parse(doc);
|
||||
connections.forEach(function (connection) {
|
||||
connection.postMessage({
|
||||
objectChanges
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (decodeError) {
|
||||
//do nothing;
|
||||
console.log(decodeError);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (error) {
|
||||
port.postMessage({
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
port.start();
|
||||
|
||||
};
|
||||
|
||||
self.onerror = function () {
|
||||
//do nothing
|
||||
console.log('Error on feed');
|
||||
};
|
||||
|
||||
}());
|
||||
@@ -40,6 +40,65 @@ export default class CouchObjectProvider {
|
||||
this.batchIds = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
startSharedWorker() {
|
||||
let provider = this;
|
||||
let sharedWorker;
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const sharedWorkerURL = `${this.openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}couchDBChangesFeed.js`;
|
||||
|
||||
sharedWorker = new SharedWorker(sharedWorkerURL);
|
||||
sharedWorker.port.onmessage = provider.onSharedWorkerMessage.bind(this);
|
||||
sharedWorker.port.onmessageerror = provider.onSharedWorkerMessageError.bind(this);
|
||||
sharedWorker.port.start();
|
||||
|
||||
this.openmct.on('destroy', () => {
|
||||
this.changesFeedSharedWorker.port.postMessage({
|
||||
request: 'close',
|
||||
connectionId: this.changesFeedSharedWorkerConnectionId
|
||||
});
|
||||
this.changesFeedSharedWorker.port.close();
|
||||
});
|
||||
|
||||
return sharedWorker;
|
||||
}
|
||||
|
||||
onSharedWorkerMessageError(event) {
|
||||
console.log('Error', event);
|
||||
}
|
||||
|
||||
onSharedWorkerMessage(event) {
|
||||
if (event.data.type === 'connection') {
|
||||
this.changesFeedSharedWorkerConnectionId = event.data.connectionId;
|
||||
} else {
|
||||
const error = event.data.error;
|
||||
if (error && Object.keys(this.observers).length > 0) {
|
||||
this.observeObjectChanges();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let objectChanges = event.data.objectChanges;
|
||||
objectChanges.identifier = {
|
||||
namespace: this.namespace,
|
||||
key: objectChanges.id
|
||||
};
|
||||
let keyString = this.openmct.objects.makeKeyString(objectChanges.identifier);
|
||||
//TODO: Optimize this so that we don't 'get' the object if it's current revision (from this.objectQueue) is the same as the one we already have.
|
||||
let observersForObject = this.observers[keyString];
|
||||
|
||||
if (observersForObject) {
|
||||
observersForObject.forEach(async (observer) => {
|
||||
const updatedObject = await this.get(objectChanges.identifier);
|
||||
observer(updatedObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//backwards compatibility, options used to be a url. Now it's an object
|
||||
_normalize(options) {
|
||||
if (typeof options === 'string') {
|
||||
@@ -320,7 +379,7 @@ export default class CouchObjectProvider {
|
||||
this.observers[keyString] = this.observers[keyString].filter(observer => observer !== callback);
|
||||
if (this.observers[keyString].length === 0) {
|
||||
delete this.observers[keyString];
|
||||
if (Object.keys(this.observers).length === 0) {
|
||||
if (Object.keys(this.observers).length === 0 && this.isObservingObjectChanges()) {
|
||||
this.stopObservingObjectChanges();
|
||||
}
|
||||
}
|
||||
@@ -334,9 +393,8 @@ export default class CouchObjectProvider {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async observeObjectChanges() {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
observeObjectChanges() {
|
||||
|
||||
let filter = {selector: {}};
|
||||
|
||||
if (this.openmct.objects.SYNCHRONIZED_OBJECT_TYPES.length > 1) {
|
||||
@@ -354,17 +412,6 @@ export default class CouchObjectProvider {
|
||||
};
|
||||
}
|
||||
|
||||
let error = false;
|
||||
|
||||
if (typeof this.stopObservingObjectChanges === 'function') {
|
||||
this.stopObservingObjectChanges();
|
||||
}
|
||||
|
||||
this.stopObservingObjectChanges = () => {
|
||||
controller.abort();
|
||||
delete this.stopObservingObjectChanges;
|
||||
};
|
||||
|
||||
// feed=continuous maintains an indefinitely open connection with a keep-alive of HEARTBEAT milliseconds until this client closes the connection
|
||||
// style=main_only returns only the current winning revision of the document
|
||||
let url = `${this.url}/_changes?feed=continuous&style=main_only&heartbeat=${HEARTBEAT}`;
|
||||
@@ -375,6 +422,52 @@ export default class CouchObjectProvider {
|
||||
body = JSON.stringify(filter);
|
||||
}
|
||||
|
||||
if (typeof SharedWorker === 'undefined') {
|
||||
this.fetchChanges(url, body);
|
||||
} else {
|
||||
this.initiateSharedWorkerFetchChanges(url, body);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initiateSharedWorkerFetchChanges(url, body) {
|
||||
if (!this.changesFeedSharedWorker) {
|
||||
this.changesFeedSharedWorker = this.startSharedWorker();
|
||||
|
||||
if (this.isObservingObjectChanges()) {
|
||||
this.stopObservingObjectChanges();
|
||||
}
|
||||
|
||||
this.stopObservingObjectChanges = () => {
|
||||
delete this.stopObservingObjectChanges;
|
||||
};
|
||||
|
||||
this.changesFeedSharedWorker.port.postMessage({
|
||||
request: 'changes',
|
||||
body,
|
||||
url
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fetchChanges(url, body) {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
|
||||
let error = false;
|
||||
|
||||
if (this.isObservingObjectChanges()) {
|
||||
this.stopObservingObjectChanges();
|
||||
}
|
||||
|
||||
this.stopObservingObjectChanges = () => {
|
||||
controller.abort();
|
||||
delete this.stopObservingObjectChanges;
|
||||
};
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
signal,
|
||||
|
||||
@@ -25,16 +25,16 @@
|
||||
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
|
||||
>
|
||||
<plot-legend :cursor-locked="!!lockHighlightPoint"
|
||||
:series="config.series.models"
|
||||
:series="seriesModels"
|
||||
:highlights="highlights"
|
||||
:legend="config.legend"
|
||||
:legend="legend"
|
||||
@legendHoverChanged="legendHoverChanged"
|
||||
/>
|
||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
||||
<y-axis v-if="config.series.models.length > 0"
|
||||
<y-axis v-if="seriesModels.length > 0"
|
||||
:tick-width="tickWidth"
|
||||
:single-series="config.series.models.length === 1"
|
||||
:series-model="config.series.models[0]"
|
||||
:single-series="seriesModels.length === 1"
|
||||
:series-model="seriesModels[0]"
|
||||
@yKeyChanged="setYAxisKey"
|
||||
@tickWidthChanged="onTickWidthChange"
|
||||
/>
|
||||
@@ -141,8 +141,8 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<x-axis v-if="config.series.models.length > 0 && !options.compact"
|
||||
:series-model="config.series.models[0]"
|
||||
<x-axis v-if="seriesModels.length > 0 && !options.compact"
|
||||
:series-model="seriesModels[0]"
|
||||
/>
|
||||
|
||||
</div>
|
||||
@@ -213,7 +213,8 @@ export default {
|
||||
plotHistory: [],
|
||||
selectedXKeyOption: {},
|
||||
xKeyOptions: [],
|
||||
config: {},
|
||||
seriesModels: [],
|
||||
legend: {},
|
||||
pending: 0,
|
||||
isRealTime: this.openmct.time.clock() !== undefined,
|
||||
loaded: false,
|
||||
@@ -239,18 +240,13 @@ export default {
|
||||
watch: {
|
||||
plotTickWidth(newTickWidth) {
|
||||
this.onTickWidthChange(newTickWidth, true);
|
||||
},
|
||||
gridLines(newGridLines) {
|
||||
this.setGridLinesVisibility(newGridLines);
|
||||
},
|
||||
cursorGuide(newCursorGuide) {
|
||||
this.setCursorGuideVisibility(newCursorGuide);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
|
||||
this.config = this.getConfig();
|
||||
this.legend = this.config.legend;
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
||||
@@ -290,14 +286,18 @@ export default {
|
||||
config = new PlotConfigurationModel({
|
||||
id: configId,
|
||||
domainObject: this.domainObject,
|
||||
openmct: this.openmct
|
||||
openmct: this.openmct,
|
||||
callback: (data) => {
|
||||
this.data = data;
|
||||
}
|
||||
});
|
||||
configStore.add(configId, config);
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
addSeries(series) {
|
||||
addSeries(series, index) {
|
||||
this.$set(this.seriesModels, index, series);
|
||||
this.listenTo(series, 'change:xKey', (xKey) => {
|
||||
this.setDisplayRange(series, xKey);
|
||||
}, this);
|
||||
@@ -323,6 +323,8 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth;
|
||||
|
||||
this.startLoading();
|
||||
const options = {
|
||||
size: this.$parent.$refs.plotWrapper.offsetWidth,
|
||||
@@ -377,11 +379,8 @@ export default {
|
||||
},
|
||||
|
||||
stopLoading() {
|
||||
//TODO: Is Vue.$nextTick ok to replace $scope.$evalAsync?
|
||||
this.$nextTick().then(() => {
|
||||
this.pending -= 1;
|
||||
this.updateLoading();
|
||||
});
|
||||
this.pending -= 1;
|
||||
this.updateLoading();
|
||||
},
|
||||
|
||||
updateLoading() {
|
||||
@@ -507,7 +506,7 @@ export default {
|
||||
},
|
||||
|
||||
initialize() {
|
||||
_.debounce(this.handleWindowResize, 400);
|
||||
this.handleWindowResize = _.debounce(this.handleWindowResize, 500);
|
||||
this.plotContainerResizeObserver = new ResizeObserver(this.handleWindowResize);
|
||||
this.plotContainerResizeObserver.observe(this.$parent.$refs.plotWrapper);
|
||||
|
||||
@@ -623,7 +622,7 @@ export default {
|
||||
this.config.series.models.forEach(series => delete series.closest);
|
||||
} else {
|
||||
this.highlights = this.config.series.models
|
||||
.filter(series => series.data.length > 0)
|
||||
.filter(series => series.getSeriesData().length > 0)
|
||||
.map(series => {
|
||||
series.closest = series.nearestPoint(point);
|
||||
|
||||
@@ -927,16 +926,8 @@ export default {
|
||||
this.userViewportChangeEnd();
|
||||
},
|
||||
|
||||
setCursorGuideVisibility(cursorGuide) {
|
||||
this.cursorGuide = cursorGuide === true;
|
||||
},
|
||||
|
||||
setGridLinesVisibility(gridLines) {
|
||||
this.gridLines = gridLines === true;
|
||||
},
|
||||
|
||||
setYAxisKey(yKey) {
|
||||
this.config.series.models[0].emit('change:yKey', yKey);
|
||||
this.config.series.models[0].set('yKey', yKey);
|
||||
},
|
||||
|
||||
pause() {
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import eventHelpers from "./lib/eventHelpers";
|
||||
import eventHelpers from './lib/eventHelpers';
|
||||
import ImageExporter from '../../exporters/ImageExporter';
|
||||
import MctPlot from './MctPlot.vue';
|
||||
|
||||
export default {
|
||||
@@ -102,8 +103,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
|
||||
this.exportImageService = this.openmct.$injector.get('exportImageService');
|
||||
this.imageExporter = new ImageExporter(this.openmct);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroy();
|
||||
@@ -118,14 +118,12 @@ export default {
|
||||
|
||||
exportJPG() {
|
||||
const plotElement = this.$refs.plotContainer;
|
||||
|
||||
this.exportImageService.exportJPG(plotElement, 'plot.jpg', 'export-plot');
|
||||
this.imageExporter.exportJPG(plotElement, 'plot.jpg', 'export-plot');
|
||||
},
|
||||
|
||||
exportPNG() {
|
||||
const plotElement = this.$refs.plotContainer;
|
||||
|
||||
this.exportImageService.exportPNG(plotElement, 'plot.png', 'export-plot');
|
||||
this.imageExporter.exportPNG(plotElement, 'plot.png', 'export-plot');
|
||||
},
|
||||
|
||||
toggleCursorGuide() {
|
||||
|
||||
@@ -106,8 +106,9 @@ export default {
|
||||
},
|
||||
toggleXKeyOption() {
|
||||
const selectedXKey = this.selectedXKeyOptionKey;
|
||||
const dataForSelectedXKey = this.seriesModel.data
|
||||
? this.seriesModel.data[0][selectedXKey]
|
||||
const seriesData = this.seriesModel.getSeriesData();
|
||||
const dataForSelectedXKey = seriesData
|
||||
? seriesData[0][selectedXKey]
|
||||
: undefined;
|
||||
|
||||
if (dataForSelectedXKey !== undefined) {
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class MCTChartAlarmPointSet {
|
||||
this.listenTo(series, 'reset', this.reset, this);
|
||||
this.listenTo(series, 'destroy', this.destroy, this);
|
||||
|
||||
series.data.forEach(function (point, index) {
|
||||
this.series.getSeriesData().forEach(function (point, index) {
|
||||
this.append(point, index, series);
|
||||
}, this);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class MCTChartSeriesElement {
|
||||
this.listenTo(series, 'remove', this.remove, this);
|
||||
this.listenTo(series, 'reset', this.reset, this);
|
||||
this.listenTo(series, 'destroy', this.destroy, this);
|
||||
series.data.forEach(function (point, index) {
|
||||
this.series.getSeriesData().forEach(function (point, index) {
|
||||
this.append(point, index, series);
|
||||
}, this);
|
||||
}
|
||||
@@ -133,7 +133,7 @@ export default class MCTChartSeriesElement {
|
||||
this.buffer = new Float32Array(20000);
|
||||
this.count = 0;
|
||||
if (this.offset.x) {
|
||||
this.series.data.forEach(function (point, index) {
|
||||
this.series.getSeriesData().forEach(function (point, index) {
|
||||
this.append(point, index, this.series);
|
||||
}, this);
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ export default class PlotConfigurationModel extends Model {
|
||||
updateDomainObject(domainObject) {
|
||||
this.set('domainObject', domainObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all objects and remove all listeners.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import _ from 'lodash';
|
||||
import Model from "./Model";
|
||||
import { MARKER_SHAPES } from '../draw/MarkerShapes';
|
||||
import configStore from "../configuration/configStore";
|
||||
|
||||
/**
|
||||
* Plot series handle interpreting telemetry metadata for a single telemetry
|
||||
@@ -62,7 +63,6 @@ import { MARKER_SHAPES } from '../draw/MarkerShapes';
|
||||
export default class PlotSeries extends Model {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.data = [];
|
||||
|
||||
this.listenTo(this, 'change:xKey', this.onXKeyChange, this);
|
||||
this.listenTo(this, 'change:yKey', this.onYKeyChange, this);
|
||||
@@ -115,6 +115,8 @@ export default class PlotSeries extends Model {
|
||||
this.openmct = options.openmct;
|
||||
this.domainObject = options.domainObject;
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.dataStoreId = `data-${options.collection.plot.id}-${this.keyString}`;
|
||||
this.updateSeriesData([]);
|
||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
|
||||
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
|
||||
this.limits = [];
|
||||
@@ -182,7 +184,8 @@ export default class PlotSeries extends Model {
|
||||
.telemetry
|
||||
.request(this.domainObject, options)
|
||||
.then(function (points) {
|
||||
const newPoints = _(this.data)
|
||||
const data = this.getSeriesData();
|
||||
const newPoints = _(data)
|
||||
.concat(points)
|
||||
.sortBy(this.getXVal)
|
||||
.uniq(true, point => [this.getXVal(point), this.getYVal(point)].join())
|
||||
@@ -236,7 +239,7 @@ export default class PlotSeries extends Model {
|
||||
*/
|
||||
resetStats() {
|
||||
this.unset('stats');
|
||||
this.data.forEach(this.updateStats, this);
|
||||
this.getSeriesData().forEach(this.updateStats, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,7 +247,7 @@ export default class PlotSeries extends Model {
|
||||
* data to series after reset.
|
||||
*/
|
||||
reset(newData) {
|
||||
this.data = [];
|
||||
this.updateSeriesData([]);
|
||||
this.resetStats();
|
||||
this.emit('reset');
|
||||
if (newData) {
|
||||
@@ -258,8 +261,9 @@ export default class PlotSeries extends Model {
|
||||
*/
|
||||
nearestPoint(xValue) {
|
||||
const insertIndex = this.sortedIndex(xValue);
|
||||
const lowPoint = this.data[insertIndex - 1];
|
||||
const highPoint = this.data[insertIndex];
|
||||
const data = this.getSeriesData();
|
||||
const lowPoint = data[insertIndex - 1];
|
||||
const highPoint = data[insertIndex];
|
||||
const indexVal = this.getXVal(xValue);
|
||||
const lowDistance = lowPoint
|
||||
? indexVal - this.getXVal(lowPoint)
|
||||
@@ -292,7 +296,7 @@ export default class PlotSeries extends Model {
|
||||
* @private
|
||||
*/
|
||||
sortedIndex(point) {
|
||||
return _.sortedIndexBy(this.data, point, this.getXVal);
|
||||
return _.sortedIndexBy(this.getSeriesData(), point, this.getXVal);
|
||||
}
|
||||
/**
|
||||
* Update min/max stats for the series.
|
||||
@@ -346,9 +350,10 @@ export default class PlotSeries extends Model {
|
||||
* a point to the end without dupe checking.
|
||||
*/
|
||||
add(point, appendOnly) {
|
||||
let insertIndex = this.data.length;
|
||||
let data = this.getSeriesData();
|
||||
let insertIndex = data.length;
|
||||
const currentYVal = this.getYVal(point);
|
||||
const lastYVal = this.getYVal(this.data[insertIndex - 1]);
|
||||
const lastYVal = this.getYVal(data[insertIndex - 1]);
|
||||
|
||||
if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) {
|
||||
console.warn('[Plot] Invalid Y Values detected');
|
||||
@@ -358,18 +363,19 @@ export default class PlotSeries extends Model {
|
||||
|
||||
if (!appendOnly) {
|
||||
insertIndex = this.sortedIndex(point);
|
||||
if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) {
|
||||
if (this.getXVal(data[insertIndex]) === this.getXVal(point)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getXVal(this.data[insertIndex - 1]) === this.getXVal(point)) {
|
||||
if (this.getXVal(data[insertIndex - 1]) === this.getXVal(point)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateStats(point);
|
||||
point.mctLimitState = this.evaluate(point);
|
||||
this.data.splice(insertIndex, 0, point);
|
||||
data.splice(insertIndex, 0, point);
|
||||
this.updateSeriesData(data);
|
||||
this.emit('add', point, insertIndex, this);
|
||||
}
|
||||
|
||||
@@ -386,8 +392,10 @@ export default class PlotSeries extends Model {
|
||||
* @private
|
||||
*/
|
||||
remove(point) {
|
||||
const index = this.data.indexOf(point);
|
||||
this.data.splice(index, 1);
|
||||
let data = this.getSeriesData();
|
||||
const index = data.indexOf(point);
|
||||
data.splice(index, 1);
|
||||
this.updateSeriesData(data);
|
||||
this.emit('remove', point, index, this);
|
||||
}
|
||||
/**
|
||||
@@ -403,14 +411,16 @@ export default class PlotSeries extends Model {
|
||||
purgeRecordsOutsideRange(range) {
|
||||
const startIndex = this.sortedIndex(range.min);
|
||||
const endIndex = this.sortedIndex(range.max) + 1;
|
||||
const pointsToRemove = startIndex + (this.data.length - endIndex + 1);
|
||||
let data = this.getSeriesData();
|
||||
const pointsToRemove = startIndex + (data.length - endIndex + 1);
|
||||
if (pointsToRemove > 0) {
|
||||
if (pointsToRemove < 1000) {
|
||||
this.data.slice(0, startIndex).forEach(this.remove, this);
|
||||
this.data.slice(endIndex, this.data.length).forEach(this.remove, this);
|
||||
data.slice(0, startIndex).forEach(this.remove, this);
|
||||
data.slice(endIndex, data.length).forEach(this.remove, this);
|
||||
this.updateSeriesData(data);
|
||||
this.resetStats();
|
||||
} else {
|
||||
const newData = this.data.slice(startIndex, endIndex);
|
||||
const newData = this.getSeriesData().slice(startIndex, endIndex);
|
||||
this.reset(newData);
|
||||
}
|
||||
}
|
||||
@@ -441,12 +451,13 @@ export default class PlotSeries extends Model {
|
||||
}
|
||||
}
|
||||
getDisplayRange(xKey) {
|
||||
const unsortedData = this.data;
|
||||
this.data = [];
|
||||
const unsortedData = this.getSeriesData();
|
||||
this.updateSeriesData([]);
|
||||
unsortedData.forEach(point => this.add(point, false));
|
||||
|
||||
const minValue = this.getXVal(this.data[0]);
|
||||
const maxValue = this.getXVal(this.data[this.data.length - 1]);
|
||||
let data = this.getSeriesData();
|
||||
const minValue = this.getXVal(data[0]);
|
||||
const maxValue = this.getXVal(data[data.length - 1]);
|
||||
|
||||
return {
|
||||
min: minValue,
|
||||
@@ -470,4 +481,18 @@ export default class PlotSeries extends Model {
|
||||
|
||||
return this.get('name') + (unit ? ' ' + unit : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the series data with the given value.
|
||||
*/
|
||||
updateSeriesData(data) {
|
||||
configStore.add(this.dataStoreId, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the series data with the given value.
|
||||
*/
|
||||
getSeriesData() {
|
||||
return configStore.get(this.dataStoreId) || [];
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user