mirror of
https://github.com/nasa/openmct.git
synced 2025-07-03 13:35:04 +00:00
Compare commits
33 Commits
plot-perfo
...
fix-resize
Author | SHA1 | Date | |
---|---|---|---|
74538dd00c | |||
63f9cd449f | |||
54220f547b | |||
93d967c2b3 | |||
1226459c6f | |||
d7c9c9cb98 | |||
2131ef2397 | |||
48c22369a1 | |||
6506077f4d | |||
b1b4266ff3 | |||
42b0148f93 | |||
9461ad8edd | |||
40055ba955 | |||
9cb85ad176 | |||
f2b2953a5d | |||
62de310686 | |||
4b9ff67e49 | |||
d5e32ec494 | |||
38880ba3d1 | |||
a99ce7733c | |||
9f48764210 | |||
a1aaa0dd41 | |||
bee15e98c8 | |||
092bbe547d | |||
6cbe05317c | |||
3b92fcdf6c | |||
6dde54bd25 | |||
359e7377ac | |||
9f4190f781 | |||
f3fc991a74 | |||
2564e75fc9 | |||
f42fe78acf | |||
fe928a1386 |
@ -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
|
||||
|
||||
|
||||
|
33
.github/workflows/codeql-analysis.yml
vendored
Normal file
33
.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '28 21 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: javascript
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
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;
|
||||
|
@ -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>
|
||||
|
@ -195,6 +195,7 @@
|
||||
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
|
||||
{indicator: true}
|
||||
));
|
||||
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
|
||||
openmct.start();
|
||||
</script>
|
||||
</html>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "1.7.6-SNAPSHOT",
|
||||
"version": "1.7.8-SNAPSHOT",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
|
@ -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",
|
||||
|
@ -21,32 +21,24 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"moment-timezone",
|
||||
"./src/indicators/ClockIndicator",
|
||||
"./src/services/TickerService",
|
||||
"./src/services/TimerService",
|
||||
"./src/controllers/ClockController",
|
||||
"./src/controllers/TimerController",
|
||||
"./src/controllers/RefreshingController",
|
||||
"./src/actions/StartTimerAction",
|
||||
"./src/actions/RestartTimerAction",
|
||||
"./src/actions/StopTimerAction",
|
||||
"./src/actions/PauseTimerAction",
|
||||
"./res/templates/clock.html",
|
||||
"./res/templates/timer.html"
|
||||
], function (
|
||||
MomentTimezone,
|
||||
ClockIndicator,
|
||||
TickerService,
|
||||
TimerService,
|
||||
ClockController,
|
||||
TimerController,
|
||||
RefreshingController,
|
||||
StartTimerAction,
|
||||
RestartTimerAction,
|
||||
StopTimerAction,
|
||||
PauseTimerAction,
|
||||
clockTemplate,
|
||||
timerTemplate
|
||||
) {
|
||||
return {
|
||||
@ -73,16 +65,6 @@ define([
|
||||
"value": "YYYY/MM/DD HH:mm:ss"
|
||||
}
|
||||
],
|
||||
"indicators": [
|
||||
{
|
||||
"implementation": ClockIndicator,
|
||||
"depends": [
|
||||
"tickerService",
|
||||
"CLOCK_INDICATOR_FORMAT"
|
||||
],
|
||||
"priority": "preferred"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "tickerService",
|
||||
@ -99,14 +81,6 @@ define([
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "ClockController",
|
||||
"implementation": ClockController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"tickerService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "TimerController",
|
||||
"implementation": TimerController,
|
||||
@ -126,12 +100,6 @@ define([
|
||||
}
|
||||
],
|
||||
"views": [
|
||||
{
|
||||
"key": "clock",
|
||||
"type": "clock",
|
||||
"editable": false,
|
||||
"template": clockTemplate
|
||||
},
|
||||
{
|
||||
"key": "timer",
|
||||
"type": "timer",
|
||||
@ -186,70 +154,6 @@ define([
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"key": "clock",
|
||||
"name": "Clock",
|
||||
"cssClass": "icon-clock",
|
||||
"description": "A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.",
|
||||
"priority": 101,
|
||||
"features": [
|
||||
"creation"
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"key": "clockFormat",
|
||||
"name": "Display Format",
|
||||
"control": "composite",
|
||||
"items": [
|
||||
{
|
||||
"control": "select",
|
||||
"options": [
|
||||
{
|
||||
"value": "YYYY/MM/DD hh:mm:ss",
|
||||
"name": "YYYY/MM/DD hh:mm:ss"
|
||||
},
|
||||
{
|
||||
"value": "YYYY/DDD hh:mm:ss",
|
||||
"name": "YYYY/DDD hh:mm:ss"
|
||||
},
|
||||
{
|
||||
"value": "hh:mm:ss",
|
||||
"name": "hh:mm:ss"
|
||||
}
|
||||
],
|
||||
"cssClass": "l-inline"
|
||||
},
|
||||
{
|
||||
"control": "select",
|
||||
"options": [
|
||||
{
|
||||
"value": "clock12",
|
||||
"name": "12hr"
|
||||
},
|
||||
{
|
||||
"value": "clock24",
|
||||
"name": "24hr"
|
||||
}
|
||||
],
|
||||
"cssClass": "l-inline"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "timezone",
|
||||
"name": "Timezone",
|
||||
"control": "autocomplete",
|
||||
"options": MomentTimezone.tz.names()
|
||||
}
|
||||
],
|
||||
"model": {
|
||||
"clockFormat": [
|
||||
"YYYY/MM/DD hh:mm:ss",
|
||||
"clock12"
|
||||
],
|
||||
"timezone": "UTC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "timer",
|
||||
"name": "Timer",
|
||||
|
@ -1,32 +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.
|
||||
-->
|
||||
<div class="c-clock l-time-display u-style-receiver js-style-receiver" ng-controller="ClockController as clock">
|
||||
<div class="c-clock__timezone">
|
||||
{{clock.zone()}}
|
||||
</div>
|
||||
<div class="c-clock__value">
|
||||
{{clock.text()}}
|
||||
</div>
|
||||
<div class="c-clock__ampm">
|
||||
{{clock.ampm()}}
|
||||
</div>
|
||||
</div>
|
@ -1,110 +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([
|
||||
'moment',
|
||||
'moment-timezone'
|
||||
],
|
||||
function (
|
||||
moment,
|
||||
momentTimezone
|
||||
) {
|
||||
|
||||
/**
|
||||
* Controller for views of a Clock domain object.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/features/clock
|
||||
* @param {angular.Scope} $scope the Angular scope
|
||||
* @param {platform/features/clock.TickerService} tickerService
|
||||
* a service used to align behavior with clock ticks
|
||||
*/
|
||||
function ClockController($scope, tickerService) {
|
||||
var lastTimestamp,
|
||||
unlisten,
|
||||
timeFormat,
|
||||
zoneName,
|
||||
self = this;
|
||||
|
||||
function update() {
|
||||
var m = zoneName
|
||||
? moment.utc(lastTimestamp).tz(zoneName) : moment.utc(lastTimestamp);
|
||||
self.zoneAbbr = m.zoneAbbr();
|
||||
self.textValue = timeFormat && m.format(timeFormat);
|
||||
self.ampmValue = m.format("A"); // Just the AM or PM part
|
||||
}
|
||||
|
||||
function tick(timestamp) {
|
||||
lastTimestamp = timestamp;
|
||||
update();
|
||||
}
|
||||
|
||||
function updateModel(model) {
|
||||
var baseFormat;
|
||||
if (model !== undefined) {
|
||||
baseFormat = model.clockFormat[0];
|
||||
|
||||
self.use24 = model.clockFormat[1] === 'clock24';
|
||||
timeFormat = self.use24
|
||||
? baseFormat.replace('hh', "HH") : baseFormat;
|
||||
// If wrong timezone is provided, the UTC will be used
|
||||
zoneName = momentTimezone.tz.names().includes(model.timezone)
|
||||
? model.timezone : "UTC";
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// Pull in the model (clockFormat and timezone) from the domain object model
|
||||
$scope.$watch('model', updateModel);
|
||||
|
||||
// Listen for clock ticks ... and stop listening on destroy
|
||||
unlisten = tickerService.listen(tick);
|
||||
$scope.$on('$destroy', unlisten);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clock's time zone, as displayable text.
|
||||
* @returns {string}
|
||||
*/
|
||||
ClockController.prototype.zone = function () {
|
||||
return this.zoneAbbr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current time, as displayable text.
|
||||
* @returns {string}
|
||||
*/
|
||||
ClockController.prototype.text = function () {
|
||||
return this.textValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text to display to qualify a time as AM or PM.
|
||||
* @returns {string}
|
||||
*/
|
||||
ClockController.prototype.ampm = function () {
|
||||
return this.use24 ? '' : this.ampmValue;
|
||||
};
|
||||
|
||||
return ClockController;
|
||||
}
|
||||
);
|
@ -1,65 +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(
|
||||
['moment'],
|
||||
function (moment) {
|
||||
|
||||
/**
|
||||
* Indicator that displays the current UTC time in the status area.
|
||||
* @implements {Indicator}
|
||||
* @memberof platform/features/clock
|
||||
* @param {platform/features/clock.TickerService} tickerService
|
||||
* a service used to align behavior with clock ticks
|
||||
* @param {string} indicatorFormat format string for timestamps
|
||||
* shown in this indicator
|
||||
*/
|
||||
function ClockIndicator(tickerService, indicatorFormat) {
|
||||
var self = this;
|
||||
|
||||
this.text = "";
|
||||
|
||||
tickerService.listen(function (timestamp) {
|
||||
self.text = moment.utc(timestamp)
|
||||
.format(indicatorFormat) + " UTC";
|
||||
});
|
||||
}
|
||||
|
||||
ClockIndicator.prototype.getGlyphClass = function () {
|
||||
return "";
|
||||
};
|
||||
|
||||
ClockIndicator.prototype.getCssClass = function () {
|
||||
return "t-indicator-clock icon-clock no-minify c-indicator--not-clickable";
|
||||
};
|
||||
|
||||
ClockIndicator.prototype.getText = function () {
|
||||
return this.text;
|
||||
};
|
||||
|
||||
ClockIndicator.prototype.getDescription = function () {
|
||||
return "";
|
||||
};
|
||||
|
||||
return ClockIndicator;
|
||||
}
|
||||
);
|
@ -1,107 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2009-2017, 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/controllers/ClockController"],
|
||||
function (ClockController) {
|
||||
|
||||
// Wed, 03 Jun 2015 17:56:14 GMT
|
||||
var TEST_TIMESTAMP = 1433354174000;
|
||||
|
||||
describe("A clock view's controller", function () {
|
||||
var mockScope,
|
||||
mockTicker,
|
||||
mockUnticker,
|
||||
controller;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
|
||||
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
|
||||
mockUnticker = jasmine.createSpy('unticker');
|
||||
|
||||
mockTicker.listen.and.returnValue(mockUnticker);
|
||||
|
||||
controller = new ClockController(mockScope, mockTicker);
|
||||
});
|
||||
|
||||
it("watches for model (clockFormat and timezone) from the domain object model", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"model",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("subscribes to clock ticks", function () {
|
||||
expect(mockTicker.listen)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("unsubscribes to ticks when destroyed", function () {
|
||||
// Make sure $destroy is being listened for...
|
||||
expect(mockScope.$on.calls.mostRecent().args[0]).toEqual('$destroy');
|
||||
expect(mockUnticker).not.toHaveBeenCalled();
|
||||
|
||||
// ...and makes sure that its listener unsubscribes from ticker
|
||||
mockScope.$on.calls.mostRecent().args[1]();
|
||||
expect(mockUnticker).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("formats using the format string from the model", function () {
|
||||
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
|
||||
mockScope.$watch.calls.mostRecent().args[1]({
|
||||
"clockFormat": [
|
||||
"YYYY-DDD hh:mm:ss",
|
||||
"clock24"
|
||||
],
|
||||
"timezone": "Canada/Eastern"
|
||||
});
|
||||
|
||||
expect(controller.zone()).toEqual("EDT");
|
||||
expect(controller.text()).toEqual("2015-154 13:56:14");
|
||||
expect(controller.ampm()).toEqual("");
|
||||
});
|
||||
|
||||
it("formats 12-hour time", function () {
|
||||
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
|
||||
mockScope.$watch.calls.mostRecent().args[1]({
|
||||
"clockFormat": [
|
||||
"YYYY-DDD hh:mm:ss",
|
||||
"clock12"
|
||||
],
|
||||
"timezone": ""
|
||||
});
|
||||
|
||||
expect(controller.zone()).toEqual("UTC");
|
||||
expect(controller.text()).toEqual("2015-154 05:56:14");
|
||||
expect(controller.ampm()).toEqual("PM");
|
||||
});
|
||||
|
||||
it("does not throw exceptions when model is undefined", function () {
|
||||
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
|
||||
expect(function () {
|
||||
mockScope.$watch.calls.mostRecent().args[1](undefined);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -1,58 +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/indicators/ClockIndicator"],
|
||||
function (ClockIndicator) {
|
||||
|
||||
// Wed, 03 Jun 2015 17:56:14 GMT
|
||||
var TEST_TIMESTAMP = 1433354174000,
|
||||
TEST_FORMAT = "YYYY-DDD HH:mm:ss";
|
||||
|
||||
describe("The clock indicator", function () {
|
||||
var mockTicker,
|
||||
mockUnticker,
|
||||
indicator;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
|
||||
mockUnticker = jasmine.createSpy('unticker');
|
||||
|
||||
mockTicker.listen.and.returnValue(mockUnticker);
|
||||
|
||||
indicator = new ClockIndicator(mockTicker, TEST_FORMAT);
|
||||
});
|
||||
|
||||
it("displays the current time", function () {
|
||||
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
|
||||
expect(indicator.getText()).toEqual("2015-154 17:56:14 UTC");
|
||||
});
|
||||
|
||||
it("implements the Indicator interface", function () {
|
||||
expect(indicator.getCssClass()).toEqual(jasmine.any(String));
|
||||
expect(indicator.getText()).toEqual(jasmine.any(String));
|
||||
expect(indicator.getDescription()).toEqual(jasmine.any(String));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -30,8 +30,8 @@ define([
|
||||
|
||||
return function ImportExportPlugin() {
|
||||
return function (openmct) {
|
||||
ExportAsJSONAction.appliesTo = function (context) {
|
||||
return openmct.$injector.get('policyService')
|
||||
ExportAsJSONAction.prototype.appliesTo = function (context) {
|
||||
return this.openmct.$injector.get('policyService')
|
||||
.allow("creation", context.domainObject.getCapability("type")
|
||||
);
|
||||
};
|
||||
|
@ -29,7 +29,7 @@ define(
|
||||
],
|
||||
function (ExportAsJSONAction, domainObjectFactory, MCT, AdapterCapability) {
|
||||
|
||||
xdescribe("The export JSON action", function () {
|
||||
describe("The export JSON action", function () {
|
||||
|
||||
var context,
|
||||
action,
|
||||
@ -102,7 +102,7 @@ define(
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
|
||||
it("doesn't export non-creatable objects in tree", function () {
|
||||
xit("doesn't export non-creatable objects in tree", function () {
|
||||
var nonCreatableType = {
|
||||
hasFeature:
|
||||
function (feature) {
|
||||
@ -149,7 +149,7 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
it("can export self-containing objects", function () {
|
||||
xit("can export self-containing objects", function () {
|
||||
var parent = domainObjectFactory({
|
||||
name: 'parent',
|
||||
model: {
|
||||
@ -191,7 +191,7 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
it("exports links to external objects as new objects", function () {
|
||||
xit("exports links to external objects as new objects", function () {
|
||||
var parent = domainObjectFactory({
|
||||
name: 'parent',
|
||||
model: {
|
||||
|
@ -27,7 +27,7 @@ define(
|
||||
],
|
||||
function (ImportAsJSONAction, domainObjectFactory) {
|
||||
|
||||
xdescribe("The import JSON action", function () {
|
||||
describe("The import JSON action", function () {
|
||||
|
||||
var context = {};
|
||||
var action,
|
||||
@ -146,7 +146,7 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
it("can import self-containing objects", function () {
|
||||
xit("can import self-containing objects", function () {
|
||||
var compDomainObject = domainObjectFactory({
|
||||
name: 'compObject',
|
||||
model: { name: 'compObject'},
|
||||
@ -198,7 +198,7 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
it("assigns new ids to each imported object", function () {
|
||||
xit("assigns new ids to each imported object", function () {
|
||||
dialogService.getUserInput.and.returnValue(Promise.resolve(
|
||||
{
|
||||
selectFile: {
|
||||
|
@ -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];
|
||||
|
@ -287,6 +287,7 @@ define([
|
||||
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);
|
||||
|
@ -60,9 +60,7 @@ class ActionsAPI extends EventEmitter {
|
||||
}
|
||||
|
||||
_getCachedActionCollection(objectPath, view) {
|
||||
let cachedActionCollection = this._actionCollections.get(view);
|
||||
|
||||
return cachedActionCollection;
|
||||
return this._actionCollections.get(view);
|
||||
}
|
||||
|
||||
_newActionCollection(objectPath, view, skipEnvironmentObservers) {
|
||||
|
@ -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
|
||||
|
@ -358,6 +358,20 @@ ObjectAPI.prototype.applyGetInterceptors = function (identifier, domainObject) {
|
||||
return domainObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return relative url path from a given object path
|
||||
* eg: #/browse/mine/cb56f6bf-c900-43b7-b923-2e3b64b412db/6e89e858-77ce-46e4-a1ad-749240286497/....
|
||||
* @param {Array} objectPath
|
||||
* @returns {string} relative url for object
|
||||
*/
|
||||
ObjectAPI.prototype.getRelativePath = function (objectPath) {
|
||||
return objectPath
|
||||
.map(p => this.makeKeyString(p.identifier))
|
||||
.reverse()
|
||||
.join('/')
|
||||
;
|
||||
};
|
||||
|
||||
/**
|
||||
* Modify a domain object.
|
||||
* @param {module:openmct.DomainObject} object the object to mutate
|
||||
|
@ -10,28 +10,37 @@ const cssClasses = {
|
||||
};
|
||||
|
||||
class Overlay extends EventEmitter {
|
||||
constructor(options) {
|
||||
constructor({
|
||||
buttons,
|
||||
autoHide = true,
|
||||
dismissable = true,
|
||||
element,
|
||||
onDestroy,
|
||||
size
|
||||
} = {}) {
|
||||
super();
|
||||
|
||||
this.dismissable = options.dismissable !== false;
|
||||
this.container = document.createElement('div');
|
||||
this.container.classList.add('l-overlay-wrapper', cssClasses[options.size]);
|
||||
this.container.classList.add('l-overlay-wrapper', cssClasses[size]);
|
||||
|
||||
this.autoHide = autoHide;
|
||||
this.dismissable = dismissable !== false;
|
||||
|
||||
this.component = new Vue({
|
||||
provide: {
|
||||
dismiss: this.dismiss.bind(this),
|
||||
element: options.element,
|
||||
buttons: options.buttons,
|
||||
dismissable: this.dismissable
|
||||
},
|
||||
components: {
|
||||
OverlayComponent: OverlayComponent
|
||||
},
|
||||
provide: {
|
||||
dismiss: this.dismiss.bind(this),
|
||||
element,
|
||||
buttons,
|
||||
dismissable: this.dismissable
|
||||
},
|
||||
template: '<overlay-component></overlay-component>'
|
||||
});
|
||||
|
||||
if (options.onDestroy) {
|
||||
this.once('destroy', options.onDestroy);
|
||||
if (onDestroy) {
|
||||
this.once('destroy', onDestroy);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,10 @@ class OverlayAPI {
|
||||
*/
|
||||
showOverlay(overlay) {
|
||||
if (this.activeOverlays.length) {
|
||||
this.activeOverlays[this.activeOverlays.length - 1].container.classList.add('invisible');
|
||||
const previousOverlay = this.activeOverlays[this.activeOverlays.length - 1];
|
||||
if (previousOverlay.autoHide) {
|
||||
previousOverlay.container.classList.add('invisible');
|
||||
}
|
||||
}
|
||||
|
||||
this.activeOverlays.push(overlay);
|
||||
@ -60,7 +63,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
390
src/api/telemetry/TelemetryCollection.js
Normal file
390
src/api/telemetry/TelemetryCollection.js
Normal file
@ -0,0 +1,390 @@
|
||||
/*****************************************************************************
|
||||
* 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.emit('clear');
|
||||
|
||||
this._requestHistoricalTelemetry();
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the _bounds callback to the 'bounds' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_watchBounds() {
|
||||
this.openmct.time.on('bounds', this._bounds, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes the _bounds callback from the 'bounds' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_unwatchBounds() {
|
||||
this.openmct.time.off('bounds', this._bounds, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the _timeSystem callback to the 'timeSystem' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_watchTimeSystem() {
|
||||
this.openmct.time.on('timeSystem', this._timeSystem, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes the _timeSystem callback from the 'timeSystem' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_unwatchTimeSystem() {
|
||||
this.openmct.time.off('timeSystem', this._timeSystem, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* will throw a new Error, for passed in message
|
||||
* @param {string} message Message describing the error
|
||||
* @private
|
||||
*/
|
||||
_error(message) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
@ -78,6 +78,9 @@ class ImageExporter {
|
||||
}
|
||||
|
||||
return html2canvas(element, {
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
logging: false,
|
||||
onclone: function (document) {
|
||||
if (className) {
|
||||
const clonedElement = document.getElementById(exportId);
|
||||
@ -87,7 +90,7 @@ class ImageExporter {
|
||||
element.id = oldId;
|
||||
},
|
||||
removeContainer: true // Set to false to debug what html2canvas renders
|
||||
}).then(function (canvas) {
|
||||
}).then(canvas => {
|
||||
dialog.dismiss();
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
@ -102,9 +105,10 @@ class ImageExporter {
|
||||
|
||||
return canvas.toBlob(blob => resolve({ blob }), mimeType);
|
||||
});
|
||||
}, function (error) {
|
||||
console.log('error capturing image', error);
|
||||
}).catch(error => {
|
||||
dialog.dismiss();
|
||||
|
||||
console.error('error capturing image', error);
|
||||
const errorDialog = overlays.dialog({
|
||||
iconClass: 'error',
|
||||
message: 'Image was not captured successfully!',
|
||||
|
@ -19,23 +19,14 @@
|
||||
* 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 class ClearDataAction {
|
||||
constructor(openmct, appliesToObjects) {
|
||||
this.name = 'Clear Data for Object';
|
||||
this.key = 'clear-data-action';
|
||||
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
|
||||
this.cssClass = 'icon-clear-data';
|
||||
|
||||
this._openmct = openmct;
|
||||
this._appliesToObjects = appliesToObjects;
|
||||
}
|
||||
invoke(objectPath) {
|
||||
this._openmct.objectViews.emit('clearData', objectPath[0]);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
let contextualDomainObject = objectPath[0];
|
||||
|
||||
return this._appliesToObjects.filter(type => contextualDomainObject.type === type).length;
|
||||
}
|
||||
}
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
78
src/plugins/clearData/ClearDataAction.js
Normal file
78
src/plugins/clearData/ClearDataAction.js
Normal file
@ -0,0 +1,78 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
function inSelectionPath(openmct, domainObject) {
|
||||
const domainObjectIdentifier = domainObject.identifier;
|
||||
|
||||
return openmct.selection.get().some(selectionPath => {
|
||||
return selectionPath.some(objectInPath => {
|
||||
const objectInPathIdentifier = objectInPath.context.item.identifier;
|
||||
|
||||
return openmct.objects.areIdsEqual(objectInPathIdentifier, domainObjectIdentifier);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default class ClearDataAction {
|
||||
constructor(openmct, appliesToObjects) {
|
||||
this.name = 'Clear Data for Object';
|
||||
this.key = 'clear-data-action';
|
||||
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
|
||||
this.cssClass = 'icon-clear-data';
|
||||
|
||||
this._openmct = openmct;
|
||||
this._appliesToObjects = appliesToObjects;
|
||||
}
|
||||
invoke(objectPath) {
|
||||
let domainObject = null;
|
||||
if (objectPath) {
|
||||
domainObject = objectPath[0];
|
||||
}
|
||||
|
||||
this._openmct.objectViews.emit('clearData', domainObject);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
if (!objectPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contextualDomainObject = objectPath[0];
|
||||
// first check to see if this action applies to this sort of object at all
|
||||
const appliesToThisObject = this._appliesToObjects.some(type => {
|
||||
return contextualDomainObject.type === type;
|
||||
});
|
||||
if (!appliesToThisObject) {
|
||||
// we've selected something not applicable
|
||||
return false;
|
||||
}
|
||||
|
||||
const objectInSelectionPath = inSelectionPath(this._openmct, contextualDomainObject);
|
||||
if (objectInSelectionPath) {
|
||||
return true;
|
||||
} else {
|
||||
// if this it doesn't match up, check to see if we're in a composition (i.e., layout)
|
||||
const routerPath = this._openmct.router.path[0];
|
||||
|
||||
return routerPath.type === 'layout';
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@
|
||||
|
||||
define([
|
||||
'./components/globalClearIndicator.vue',
|
||||
'./clearDataAction',
|
||||
'./ClearDataAction',
|
||||
'vue'
|
||||
], function (
|
||||
GlobaClearIndicator,
|
||||
|
140
src/plugins/clearData/test/ClearDataActionSpec.js
Normal file
140
src/plugins/clearData/test/ClearDataActionSpec.js
Normal file
@ -0,0 +1,140 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import ClearDataActionPlugin from '../plugin.js';
|
||||
import ClearDataAction from '../ClearDataAction.js';
|
||||
|
||||
describe('When the Clear Data Plugin is installed,', () => {
|
||||
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
|
||||
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
|
||||
const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
|
||||
const goodMockSelectionPath = [[{
|
||||
context: {
|
||||
item: {
|
||||
identifier: {
|
||||
key: 'apple',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}]];
|
||||
|
||||
const openmct = {
|
||||
objectViews: mockObjectViews,
|
||||
indicators: mockIndicatorProvider,
|
||||
actions: mockActionsProvider,
|
||||
install: function (plugin) {
|
||||
plugin(this);
|
||||
},
|
||||
selection: {
|
||||
get: function () {
|
||||
return goodMockSelectionPath;
|
||||
}
|
||||
},
|
||||
objects: {
|
||||
areIdsEqual: function (obj1, obj2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mockObjectPath = [
|
||||
{
|
||||
name: 'mockObject1',
|
||||
type: 'apple'
|
||||
},
|
||||
{
|
||||
name: 'mockObject2',
|
||||
type: 'banana'
|
||||
}
|
||||
];
|
||||
|
||||
it('Global Clear Indicator is installed', () => {
|
||||
openmct.install(ClearDataActionPlugin([]));
|
||||
|
||||
expect(mockIndicatorProvider.add).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Clear Data context menu action is installed', () => {
|
||||
openmct.install(ClearDataActionPlugin([]));
|
||||
|
||||
expect(mockActionsProvider.register).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clear data action emits a clearData event when invoked', () => {
|
||||
const action = new ClearDataAction(openmct);
|
||||
|
||||
action.invoke(mockObjectPath);
|
||||
|
||||
expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
|
||||
});
|
||||
|
||||
it('clears data on applicable objects', () => {
|
||||
let action = new ClearDataAction(openmct, ['apple']);
|
||||
|
||||
const actionApplies = action.appliesTo(mockObjectPath);
|
||||
|
||||
expect(actionApplies).toBe(true);
|
||||
});
|
||||
|
||||
it('does not clear data on inapplicable objects', () => {
|
||||
let action = new ClearDataAction(openmct, ['pineapple']);
|
||||
|
||||
const actionApplies = action.appliesTo(mockObjectPath);
|
||||
|
||||
expect(actionApplies).toBe(false);
|
||||
});
|
||||
|
||||
it('does not clear data if not in the selection path and not a layout', () => {
|
||||
openmct.objects = {
|
||||
areIdsEqual: function (obj1, obj2) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
openmct.router = {
|
||||
path: [{type: 'not-a-layout'}]
|
||||
};
|
||||
|
||||
let action = new ClearDataAction(openmct, ['apple']);
|
||||
|
||||
const actionApplies = action.appliesTo(mockObjectPath);
|
||||
|
||||
expect(actionApplies).toBe(false);
|
||||
});
|
||||
|
||||
it('does clear data if not in the selection path and is a layout', () => {
|
||||
openmct.objects = {
|
||||
areIdsEqual: function (obj1, obj2) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
openmct.router = {
|
||||
path: [{type: 'layout'}]
|
||||
};
|
||||
|
||||
let action = new ClearDataAction(openmct, ['apple']);
|
||||
|
||||
const actionApplies = action.appliesTo(mockObjectPath);
|
||||
|
||||
expect(actionApplies).toBe(true);
|
||||
});
|
||||
});
|
@ -1,64 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import ClearDataActionPlugin from '../plugin.js';
|
||||
import ClearDataAction from '../clearDataAction.js';
|
||||
|
||||
describe('When the Clear Data Plugin is installed,', function () {
|
||||
const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
|
||||
const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
|
||||
const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
|
||||
|
||||
const openmct = {
|
||||
objectViews: mockObjectViews,
|
||||
indicators: mockIndicatorProvider,
|
||||
actions: mockActionsProvider,
|
||||
install: function (plugin) {
|
||||
plugin(this);
|
||||
}
|
||||
};
|
||||
|
||||
const mockObjectPath = [
|
||||
{name: 'mockObject1'},
|
||||
{name: 'mockObject2'}
|
||||
];
|
||||
|
||||
it('Global Clear Indicator is installed', function () {
|
||||
openmct.install(ClearDataActionPlugin([]));
|
||||
|
||||
expect(mockIndicatorProvider.add).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Clear Data context menu action is installed', function () {
|
||||
openmct.install(ClearDataActionPlugin([]));
|
||||
|
||||
expect(mockActionsProvider.register).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clear data action emits a clearData event when invoked', function () {
|
||||
let action = new ClearDataAction(openmct);
|
||||
|
||||
action.invoke(mockObjectPath);
|
||||
|
||||
expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
|
||||
});
|
||||
});
|
@ -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 Clock from './components/Clock.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function ClockViewProvider(openmct) {
|
||||
return {
|
||||
mobile: function (agentService) {
|
||||
return agentService.isMobile();
|
||||
key: 'clock.view',
|
||||
name: 'Clock',
|
||||
cssClass: 'icon-clock',
|
||||
canView(domainObject) {
|
||||
return domainObject.type === 'clock';
|
||||
},
|
||||
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: {
|
||||
Clock
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
template: '<clock />'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
99
src/plugins/clock/components/Clock.vue
Normal file
99
src/plugins/clock/components/Clock.vue
Normal file
@ -0,0 +1,99 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="l-angular-ov-wrapper">
|
||||
<div class="u-contents">
|
||||
<div class="c-clock l-time-display u-style-receiver js-style-receiver">
|
||||
<div class="c-clock__timezone">
|
||||
{{ timeZoneAbbr }}
|
||||
</div>
|
||||
<div class="c-clock__value">
|
||||
{{ timeTextValue }}
|
||||
</div>
|
||||
<div class="c-clock__ampm">
|
||||
{{ timeAmPm }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import momentTimezone from 'moment-timezone';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
lastTimestamp: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
configuration() {
|
||||
return this.domainObject.configuration;
|
||||
},
|
||||
baseFormat() {
|
||||
return this.configuration.baseFormat;
|
||||
},
|
||||
use24() {
|
||||
return this.configuration.use24 === 'clock24';
|
||||
},
|
||||
timezone() {
|
||||
return this.configuration.timezone;
|
||||
},
|
||||
timeFormat() {
|
||||
return this.use24 ? this.baseFormat.replace('hh', "HH") : this.baseFormat;
|
||||
},
|
||||
zoneName() {
|
||||
return momentTimezone.tz.names().includes(this.timezone) ? this.timezone : "UTC";
|
||||
},
|
||||
momentTime() {
|
||||
return this.zoneName ? moment.utc(this.lastTimestamp).tz(this.zoneName) : moment.utc(this.lastTimestamp);
|
||||
},
|
||||
timeZoneAbbr() {
|
||||
return this.momentTime.zoneAbbr();
|
||||
},
|
||||
timeTextValue() {
|
||||
return this.timeFormat && this.momentTime.format(this.timeFormat);
|
||||
},
|
||||
timeAmPm() {
|
||||
return this.use24 ? '' : this.momentTime.format("A");
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const TickerService = this.openmct.$injector.get('tickerService');
|
||||
this.unlisten = TickerService.listen(this.tick);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
tick(timestamp) {
|
||||
this.lastTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
64
src/plugins/clock/components/ClockIndicator.vue
Normal file
64
src/plugins/clock/components/ClockIndicator.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="c-indicator t-indicator-clock icon-clock no-minify c-indicator--not-clickable">
|
||||
<span class="label c-indicator__label">
|
||||
{{ timeTextValue }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
indicatorFormat: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timeTextValue: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.on('start', () => {
|
||||
const TickerService = this.openmct.$injector.get('tickerService');
|
||||
this.unlisten = TickerService.listen(this.tick);
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
tick(timestamp) {
|
||||
this.timeTextValue = `${moment.utc(timestamp).format(this.indicatorFormat)} UTC`;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
154
src/plugins/clock/plugin.js
Normal file
154
src/plugins/clock/plugin.js
Normal file
@ -0,0 +1,154 @@
|
||||
|
||||
/*****************************************************************************
|
||||
* 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 ClockViewProvider from './ClockViewProvider';
|
||||
import ClockIndicator from './components/ClockIndicator.vue';
|
||||
|
||||
import momentTimezone from 'moment-timezone';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function ClockPlugin(options) {
|
||||
return function install(openmct) {
|
||||
const CLOCK_INDICATOR_FORMAT = 'YYYY/MM/DD HH:mm:ss';
|
||||
openmct.types.addType('clock', {
|
||||
name: 'Clock',
|
||||
description: 'A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.',
|
||||
creatable: true,
|
||||
cssClass: 'icon-clock',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.configuration = {
|
||||
baseFormat: 'YYYY/MM/DD hh:mm:ss',
|
||||
use24: 'clock12',
|
||||
timezone: 'UTC'
|
||||
};
|
||||
},
|
||||
"form": [
|
||||
{
|
||||
"key": "displayFormat",
|
||||
"name": "Display Format",
|
||||
control: 'select',
|
||||
options: [
|
||||
{
|
||||
value: 'YYYY/MM/DD hh:mm:ss',
|
||||
name: 'YYYY/MM/DD hh:mm:ss'
|
||||
},
|
||||
{
|
||||
value: 'YYYY/DDD hh:mm:ss',
|
||||
name: 'YYYY/DDD hh:mm:ss'
|
||||
},
|
||||
{
|
||||
value: 'hh:mm:ss',
|
||||
name: 'hh:mm:ss'
|
||||
}
|
||||
],
|
||||
cssClass: 'l-inline',
|
||||
property: [
|
||||
'configuration',
|
||||
'baseFormat'
|
||||
]
|
||||
},
|
||||
{
|
||||
control: 'select',
|
||||
options: [
|
||||
{
|
||||
value: 'clock12',
|
||||
name: '12hr'
|
||||
},
|
||||
{
|
||||
value: 'clock24',
|
||||
name: '24hr'
|
||||
}
|
||||
],
|
||||
cssClass: 'l-inline',
|
||||
property: [
|
||||
'configuration',
|
||||
'use24'
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "timezone",
|
||||
"name": "Timezone",
|
||||
"control": "autocomplete",
|
||||
"options": momentTimezone.tz.names(),
|
||||
property: [
|
||||
'configuration',
|
||||
'timezone'
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
openmct.objectViews.addProvider(new ClockViewProvider(openmct));
|
||||
|
||||
if (options && options.enableClockIndicator === true) {
|
||||
const clockIndicator = new Vue ({
|
||||
components: {
|
||||
ClockIndicator
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
indicatorFormat: CLOCK_INDICATOR_FORMAT
|
||||
};
|
||||
},
|
||||
template: '<ClockIndicator :indicator-format="indicatorFormat" />'
|
||||
});
|
||||
const indicator = {
|
||||
element: clockIndicator.$mount().$el,
|
||||
key: 'clock-indicator'
|
||||
};
|
||||
|
||||
openmct.indicators.add(indicator);
|
||||
}
|
||||
|
||||
openmct.objects.addGetInterceptor({
|
||||
appliesTo: (identifier, domainObject) => {
|
||||
return domainObject && domainObject.type === 'clock';
|
||||
},
|
||||
invoke: (identifier, domainObject) => {
|
||||
if (domainObject.configuration) {
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
if (domainObject.clockFormat
|
||||
&& domainObject.timezone) {
|
||||
const baseFormat = domainObject.clockFormat[0];
|
||||
const use24 = domainObject.clockFormat[1];
|
||||
const timezone = domainObject.timezone;
|
||||
|
||||
domainObject.configuration = {
|
||||
baseFormat,
|
||||
use24,
|
||||
timezone
|
||||
};
|
||||
|
||||
openmct.objects.mutate(domainObject, 'configuration', domainObject.configuration);
|
||||
}
|
||||
|
||||
return domainObject;
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
231
src/plugins/clock/pluginSpec.js
Normal file
231
src/plugins/clock/pluginSpec.js
Normal file
@ -0,0 +1,231 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
import clockPlugin from './plugin';
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
describe("Clock plugin:", () => {
|
||||
let openmct;
|
||||
let clockDefinition;
|
||||
let element;
|
||||
let child;
|
||||
let appHolder;
|
||||
|
||||
let clockDomainObject;
|
||||
|
||||
function setupClock(enableClockIndicator) {
|
||||
return new Promise((resolve, reject) => {
|
||||
clockDomainObject = {
|
||||
identifier: {
|
||||
key: 'clock',
|
||||
namespace: 'test-namespace'
|
||||
},
|
||||
type: 'clock'
|
||||
};
|
||||
|
||||
appHolder = document.createElement('div');
|
||||
appHolder.style.width = '640px';
|
||||
appHolder.style.height = '480px';
|
||||
document.body.appendChild(appHolder);
|
||||
|
||||
openmct = createOpenMct();
|
||||
|
||||
element = document.createElement('div');
|
||||
child = document.createElement('div');
|
||||
element.appendChild(child);
|
||||
|
||||
openmct.install(clockPlugin({ enableClockIndicator }));
|
||||
|
||||
clockDefinition = openmct.types.get('clock').definition;
|
||||
clockDefinition.initialize(clockDomainObject);
|
||||
|
||||
openmct.on('start', resolve);
|
||||
openmct.start(appHolder);
|
||||
});
|
||||
}
|
||||
|
||||
describe("Clock view:", () => {
|
||||
let clockViewProvider;
|
||||
let clockView;
|
||||
let clockViewObject;
|
||||
let mutableClockObject;
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupClock(true);
|
||||
|
||||
clockViewObject = {
|
||||
...clockDomainObject,
|
||||
id: "test-object",
|
||||
name: 'Clock',
|
||||
configuration: {
|
||||
baseFormat: 'YYYY/MM/DD hh:mm:ss',
|
||||
use24: 'clock12',
|
||||
timezone: 'UTC'
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve(clockViewObject));
|
||||
spyOn(openmct.objects, 'save').and.returnValue(Promise.resolve(true));
|
||||
|
||||
const applicableViews = openmct.objectViews.get(clockViewObject, [clockViewObject]);
|
||||
clockViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'clock.view');
|
||||
|
||||
mutableClockObject = await openmct.objects.getMutable(clockViewObject.identifier);
|
||||
|
||||
clockView = clockViewProvider.view(mutableClockObject);
|
||||
clockView.show(child);
|
||||
|
||||
await Vue.nextTick();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clockView.destroy();
|
||||
openmct.objects.destroyMutable(mutableClockObject);
|
||||
if (appHolder) {
|
||||
appHolder.remove();
|
||||
}
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("has name as Clock", () => {
|
||||
expect(clockDefinition.name).toEqual('Clock');
|
||||
});
|
||||
|
||||
it("is creatable", () => {
|
||||
expect(clockDefinition.creatable).toEqual(true);
|
||||
});
|
||||
|
||||
it("provides clock view", () => {
|
||||
expect(clockViewProvider).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders clock element", () => {
|
||||
const clockElement = element.querySelectorAll('.c-clock');
|
||||
expect(clockElement.length).toBe(1);
|
||||
});
|
||||
|
||||
it("renders major elements", () => {
|
||||
const clockElement = element.querySelector('.c-clock');
|
||||
const timezone = clockElement.querySelector('.c-clock__timezone');
|
||||
const time = clockElement.querySelector('.c-clock__value');
|
||||
const amPm = clockElement.querySelector('.c-clock__ampm');
|
||||
const hasMajorElements = Boolean(timezone && time && amPm);
|
||||
|
||||
expect(hasMajorElements).toBe(true);
|
||||
});
|
||||
|
||||
it("renders time in UTC", () => {
|
||||
const clockElement = element.querySelector('.c-clock');
|
||||
const timezone = clockElement.querySelector('.c-clock__timezone').textContent.trim();
|
||||
|
||||
expect(timezone).toBe('UTC');
|
||||
});
|
||||
|
||||
it("updates the 24 hour option in the configuration", (done) => {
|
||||
expect(clockDomainObject.configuration.use24).toBe('clock12');
|
||||
const new24Option = 'clock24';
|
||||
|
||||
openmct.objects.observe(clockViewObject, 'configuration', (changedDomainObject) => {
|
||||
expect(changedDomainObject.use24).toBe(new24Option);
|
||||
done();
|
||||
});
|
||||
|
||||
openmct.objects.mutate(clockViewObject, 'configuration.use24', new24Option);
|
||||
});
|
||||
|
||||
it("updates the timezone option in the configuration", (done) => {
|
||||
expect(clockDomainObject.configuration.timezone).toBe('UTC');
|
||||
const newZone = 'CST6CDT';
|
||||
|
||||
openmct.objects.observe(clockViewObject, 'configuration', (changedDomainObject) => {
|
||||
expect(changedDomainObject.timezone).toBe(newZone);
|
||||
done();
|
||||
});
|
||||
|
||||
openmct.objects.mutate(clockViewObject, 'configuration.timezone', newZone);
|
||||
});
|
||||
|
||||
it("updates the time format option in the configuration", (done) => {
|
||||
expect(clockDomainObject.configuration.baseFormat).toBe('YYYY/MM/DD hh:mm:ss');
|
||||
const newFormat = 'hh:mm:ss';
|
||||
|
||||
openmct.objects.observe(clockViewObject, 'configuration', (changedDomainObject) => {
|
||||
expect(changedDomainObject.baseFormat).toBe(newFormat);
|
||||
done();
|
||||
});
|
||||
|
||||
openmct.objects.mutate(clockViewObject, 'configuration.baseFormat', newFormat);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Clock Indicator view:", () => {
|
||||
let clockIndicator;
|
||||
|
||||
afterEach(() => {
|
||||
if (clockIndicator) {
|
||||
clockIndicator.remove();
|
||||
}
|
||||
|
||||
clockIndicator = undefined;
|
||||
if (appHolder) {
|
||||
appHolder.remove();
|
||||
}
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("doesn't exist", async () => {
|
||||
await setupClock(false);
|
||||
|
||||
clockIndicator = openmct.indicators.indicatorObjects
|
||||
.find(indicator => indicator.key === 'clock-indicator');
|
||||
|
||||
const clockIndicatorMissing = clockIndicator === null || clockIndicator === undefined;
|
||||
expect(clockIndicatorMissing).toBe(true);
|
||||
});
|
||||
|
||||
it("exists", async () => {
|
||||
await setupClock(true);
|
||||
|
||||
clockIndicator = openmct.indicators.indicatorObjects
|
||||
.find(indicator => indicator.key === 'clock-indicator').element;
|
||||
|
||||
const hasClockIndicator = clockIndicator !== null && clockIndicator !== undefined;
|
||||
expect(hasClockIndicator).toBe(true);
|
||||
});
|
||||
|
||||
it("contains text", async () => {
|
||||
await setupClock(true);
|
||||
|
||||
clockIndicator = openmct.indicators.indicatorObjects
|
||||
.find(indicator => indicator.key === 'clock-indicator').element;
|
||||
|
||||
const clockIndicatorText = clockIndicator.textContent.trim();
|
||||
const textIncludesUTC = clockIndicatorText.includes('UTC');
|
||||
|
||||
expect(textIncludesUTC).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
@ -273,10 +273,7 @@ export default {
|
||||
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
|
||||
(objectPath) => {
|
||||
this.objectPath = objectPath;
|
||||
this.navigateToPath = '#/browse/' + this.objectPath
|
||||
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
|
||||
.reverse()
|
||||
.join('/');
|
||||
this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
@ -141,6 +141,7 @@ const NON_STYLEABLE_CONTAINER_TYPES = [
|
||||
const NON_STYLEABLE_LAYOUT_ITEM_TYPES = [
|
||||
'line-view',
|
||||
'box-view',
|
||||
'ellipse-view',
|
||||
'image-view'
|
||||
];
|
||||
|
||||
@ -296,10 +297,7 @@ export default {
|
||||
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
|
||||
(objectPath) => {
|
||||
this.objectPath = objectPath;
|
||||
this.navigateToPath = '#/browse/' + this.objectPath
|
||||
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
|
||||
.reverse()
|
||||
.join('/');
|
||||
this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath);
|
||||
}
|
||||
);
|
||||
},
|
||||
@ -321,7 +319,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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +230,7 @@ describe('the plugin', function () {
|
||||
};
|
||||
const staticStyle = {
|
||||
"style": {
|
||||
"backgroundColor": "#717171",
|
||||
"backgroundColor": "#666666",
|
||||
"border": "1px solid #00ffff"
|
||||
}
|
||||
};
|
||||
@ -238,7 +238,7 @@ describe('the plugin', function () {
|
||||
"conditionId": "39584410-cbf9-499e-96dc-76f27e69885d",
|
||||
"style": {
|
||||
"isStyleInvisible": "",
|
||||
"backgroundColor": "#717171",
|
||||
"backgroundColor": "#666666",
|
||||
"border": "1px solid #ffff00"
|
||||
}
|
||||
};
|
||||
@ -250,7 +250,7 @@ describe('the plugin', function () {
|
||||
"configuration": {
|
||||
"items": [
|
||||
{
|
||||
"fill": "#717171",
|
||||
"fill": "#666666",
|
||||
"stroke": "",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
@ -259,12 +259,22 @@ describe('the plugin', function () {
|
||||
"type": "box-view",
|
||||
"id": "89b88746-d325-487b-aec4-11b79afff9e8"
|
||||
},
|
||||
{
|
||||
"fill": "#666666",
|
||||
"stroke": "",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"width": 10,
|
||||
"height": 5,
|
||||
"type": "ellipse-view",
|
||||
"id": "19b88746-d325-487b-aec4-11b79afff9z8"
|
||||
},
|
||||
{
|
||||
"x": 18,
|
||||
"y": 9,
|
||||
"x2": 23,
|
||||
"y2": 4,
|
||||
"stroke": "#717171",
|
||||
"stroke": "#666666",
|
||||
"type": "line-view",
|
||||
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
||||
},
|
||||
@ -299,12 +309,12 @@ describe('the plugin', function () {
|
||||
"y": 9,
|
||||
"x2": 23,
|
||||
"y2": 4,
|
||||
"stroke": "#717171",
|
||||
"stroke": "#666666",
|
||||
"type": "line-view",
|
||||
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
||||
};
|
||||
boxLayoutItem = {
|
||||
"fill": "#717171",
|
||||
"fill": "#666666",
|
||||
"stroke": "",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
|
@ -29,9 +29,10 @@ const styleProps = {
|
||||
noneValue: NONE_VALUE,
|
||||
applicableForType: type => {
|
||||
return !type ? true : (type === 'text-view'
|
||||
|| type === 'telemetry-view'
|
||||
|| type === 'box-view'
|
||||
|| type === 'subobject-view');
|
||||
|| type === 'telemetry-view'
|
||||
|| type === 'box-view'
|
||||
|| type === 'ellipse-view'
|
||||
|| type === 'subobject-view');
|
||||
}
|
||||
},
|
||||
border: {
|
||||
@ -41,6 +42,7 @@ const styleProps = {
|
||||
return !type ? true : (type === 'text-view'
|
||||
|| type === 'telemetry-view'
|
||||
|| type === 'box-view'
|
||||
|| type === 'ellipse-view'
|
||||
|| type === 'image-view'
|
||||
|| type === 'line-view'
|
||||
|| type === 'subobject-view');
|
||||
|
@ -149,6 +149,7 @@ define(['lodash'], function (_) {
|
||||
return type === 'text-view'
|
||||
|| type === 'telemetry-view'
|
||||
|| type === 'box-view'
|
||||
|| type === 'ellipse-view'
|
||||
|| type === 'image-view'
|
||||
|| type === 'line-view'
|
||||
|| type === 'subobject-view';
|
||||
@ -180,6 +181,10 @@ define(['lodash'], function (_) {
|
||||
"name": "Box",
|
||||
"class": "icon-box-round-corners"
|
||||
},
|
||||
{
|
||||
"name": "Ellipse",
|
||||
"class": "icon-circle"
|
||||
},
|
||||
{
|
||||
"name": "Line",
|
||||
"class": "icon-line-horz"
|
||||
@ -745,7 +750,7 @@ define(['lodash'], function (_) {
|
||||
if (toolbar.remove.length === 0) {
|
||||
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
|
||||
}
|
||||
} else if (layoutItem.type === 'box-view') {
|
||||
} else if (layoutItem.type === 'box-view' || layoutItem.type === 'ellipse-view') {
|
||||
if (toolbar.position.length === 0) {
|
||||
toolbar.position = [
|
||||
getStackOrder(selectedParent, selectionPath),
|
||||
|
@ -43,7 +43,7 @@ import conditionalStylesMixin from '../mixins/objectStyles-mixin';
|
||||
export default {
|
||||
makeDefinition() {
|
||||
return {
|
||||
fill: '#717171',
|
||||
fill: '#666666',
|
||||
stroke: '',
|
||||
x: 1,
|
||||
y: 1,
|
||||
|
@ -76,6 +76,7 @@ import uuid from 'uuid';
|
||||
import SubobjectView from './SubobjectView.vue';
|
||||
import TelemetryView from './TelemetryView.vue';
|
||||
import BoxView from './BoxView.vue';
|
||||
import EllipseView from './EllipseView.vue';
|
||||
import TextView from './TextView.vue';
|
||||
import LineView from './LineView.vue';
|
||||
import ImageView from './ImageView.vue';
|
||||
@ -112,6 +113,7 @@ const ITEM_TYPE_VIEW_MAP = {
|
||||
'subobject-view': SubobjectView,
|
||||
'telemetry-view': TelemetryView,
|
||||
'box-view': BoxView,
|
||||
'ellipse-view': EllipseView,
|
||||
'line-view': LineView,
|
||||
'text-view': TextView,
|
||||
'image-view': ImageView
|
||||
|
@ -28,19 +28,19 @@
|
||||
>
|
||||
<div
|
||||
class="c-frame-edit__handle c-frame-edit__handle--nw"
|
||||
@mousedown="startResize([1,1], [-1,-1], $event)"
|
||||
@mousedown.left="startResize([1,1], [-1,-1], $event)"
|
||||
></div>
|
||||
<div
|
||||
class="c-frame-edit__handle c-frame-edit__handle--ne"
|
||||
@mousedown="startResize([0,1], [1,-1], $event)"
|
||||
@mousedown.left="startResize([0,1], [1,-1], $event)"
|
||||
></div>
|
||||
<div
|
||||
class="c-frame-edit__handle c-frame-edit__handle--sw"
|
||||
@mousedown="startResize([1,0], [-1,1], $event)"
|
||||
@mousedown.left="startResize([1,0], [-1,1], $event)"
|
||||
></div>
|
||||
<div
|
||||
class="c-frame-edit__handle c-frame-edit__handle--se"
|
||||
@mousedown="startResize([0,0], [1,1], $event)"
|
||||
@mousedown.left="startResize([0,0], [1,1], $event)"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
122
src/plugins/displayLayout/components/EllipseView.vue
Normal file
122
src/plugins/displayLayout/components/EllipseView.vue
Normal file
@ -0,0 +1,122 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<layout-frame
|
||||
:item="item"
|
||||
:grid-size="gridSize"
|
||||
:is-editing="isEditing"
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')"
|
||||
>
|
||||
<div
|
||||
class="c-ellipse-view u-style-receiver js-style-receiver"
|
||||
:class="[styleClass]"
|
||||
:style="style"
|
||||
></div>
|
||||
</layout-frame>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue';
|
||||
import conditionalStylesMixin from '../mixins/objectStyles-mixin';
|
||||
|
||||
export default {
|
||||
makeDefinition() {
|
||||
return {
|
||||
fill: '#666666',
|
||||
stroke: '',
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 10,
|
||||
height: 10
|
||||
};
|
||||
},
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
mixins: [conditionalStylesMixin],
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
gridSize: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator: (arr) => arr && arr.length === 2
|
||||
&& arr.every(el => typeof el === 'number')
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
initSelect: Boolean,
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
if (this.itemStyle) {
|
||||
return this.itemStyle;
|
||||
} else {
|
||||
return {
|
||||
backgroundColor: this.item.fill,
|
||||
border: this.item.stroke ? '1px solid ' + this.item.stroke : ''
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
index(newIndex) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.index = newIndex;
|
||||
},
|
||||
item(newItem) {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.layoutItem = newItem;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context, this.initSelect);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -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"
|
||||
@ -96,7 +96,7 @@ export default {
|
||||
y: 10,
|
||||
x2: 10,
|
||||
y2: 5,
|
||||
stroke: '#717171'
|
||||
stroke: '#666666'
|
||||
};
|
||||
},
|
||||
mixins: [conditionalStylesMixin],
|
||||
|
@ -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];
|
||||
@ -336,12 +336,15 @@ export default {
|
||||
},
|
||||
async getContextMenuActions() {
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
|
||||
|
||||
let defaultNotebookName;
|
||||
if (defaultNotebook) {
|
||||
const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
|
||||
defaultNotebookName = `Copy to Notebook ${defaultPath}`;
|
||||
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
|
||||
|
@ -1,4 +1,5 @@
|
||||
.c-box-view {
|
||||
.c-box-view,
|
||||
.c-ellipse-view {
|
||||
border-width: $drawingObjBorderW !important;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
@ -8,6 +9,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.c-ellipse-view {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.c-line-view {
|
||||
&.c-frame {
|
||||
box-shadow: none !important;
|
||||
|
@ -186,7 +186,7 @@ describe('the plugin', function () {
|
||||
'configuration': {
|
||||
'items': [
|
||||
{
|
||||
'fill': '#717171',
|
||||
'fill': '#666666',
|
||||
'stroke': '',
|
||||
'x': 1,
|
||||
'y': 1,
|
||||
@ -195,12 +195,22 @@ describe('the plugin', function () {
|
||||
'type': 'box-view',
|
||||
'id': '89b88746-d325-487b-aec4-11b79afff9e8'
|
||||
},
|
||||
{
|
||||
'fill': '#666666',
|
||||
'stroke': '',
|
||||
'x': 1,
|
||||
'y': 1,
|
||||
'width': 10,
|
||||
'height': 10,
|
||||
'type': 'ellipse-view',
|
||||
'id': '19b88746-d325-487b-aec4-11b79afff9z8'
|
||||
},
|
||||
{
|
||||
'x': 18,
|
||||
'y': 9,
|
||||
'x2': 23,
|
||||
'y2': 4,
|
||||
'stroke': '#717171',
|
||||
'stroke': '#666666',
|
||||
'type': 'line-view',
|
||||
'id': '57d49a28-7863-43bd-9593-6570758916f0'
|
||||
},
|
||||
@ -341,7 +351,7 @@ describe('the plugin', function () {
|
||||
it('provides controls including separators', () => {
|
||||
const displayLayoutToolbar = openmct.toolbars.get(selection);
|
||||
|
||||
expect(displayLayoutToolbar.length).toBe(9);
|
||||
expect(displayLayoutToolbar.length).toBe(7);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -33,10 +33,6 @@ export default function HyperlinkProvider(openmct) {
|
||||
return domainObject.type === 'hyperlink';
|
||||
},
|
||||
|
||||
canEdit(domainObject) {
|
||||
return domainObject.type === 'hyperlink';
|
||||
},
|
||||
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
|
@ -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,11 +15,16 @@ 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);
|
||||
});
|
||||
|
@ -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,8 +138,9 @@ 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 { saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from '../utils/notebook-image';
|
||||
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
|
||||
import objectUtils from 'objectUtils';
|
||||
|
||||
@ -164,8 +167,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 +181,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 +202,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 +314,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) {
|
||||
@ -370,9 +386,13 @@ export default {
|
||||
const snapshotId = event.dataTransfer.getData('openmct/snapshot/id');
|
||||
if (snapshotId.length) {
|
||||
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
|
||||
this.newEntry(snapshot);
|
||||
this.newEntry(snapshot.embedObject);
|
||||
this.snapshotContainer.removeSnapshot(snapshotId);
|
||||
|
||||
const namespace = this.domainObject.identifier.namespace;
|
||||
const notebookImageDomainObject = updateNamespaceOfDomainObject(snapshot.notebookImageDomainObject, namespace);
|
||||
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -419,43 +439,26 @@ 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();
|
||||
if (!oldNotebookStorage) {
|
||||
return null;
|
||||
}
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
|
||||
return this.openmct.objects.get(oldNotebookStorage.notebookMeta.identifier);
|
||||
return defaultNotebook && this.openmct.objects.get(defaultNotebook.identifier);
|
||||
},
|
||||
getLinktoNotebook() {
|
||||
const objectPath = this.openmct.router.path;
|
||||
@ -573,6 +576,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 +635,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 +662,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 +709,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();
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ export default {
|
||||
components: {
|
||||
PopupMenu
|
||||
},
|
||||
inject: ['openmct'],
|
||||
inject: ['openmct', 'snapshotContainer'],
|
||||
props: {
|
||||
embed: {
|
||||
type: Object,
|
||||
@ -48,6 +48,12 @@ export default {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
isSnapshotContainer: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
removeActionString: {
|
||||
type: String,
|
||||
default() {
|
||||
@ -135,6 +141,14 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isSnapshotContainer) {
|
||||
const snapshot = this.snapshotContainer.getSnapshot(this.embed.id);
|
||||
const fullSizeImageURL = snapshot.notebookImageDomainObject.configuration.fullSizeImageURL;
|
||||
painterroInstance.show(fullSizeImageURL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.openmct.objects.get(fullSizeImageObjectIdentifier)
|
||||
.then(object => {
|
||||
painterroInstance.show(object.configuration.fullSizeImageURL);
|
||||
@ -190,6 +204,14 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isSnapshotContainer) {
|
||||
const snapshot = this.snapshotContainer.getSnapshot(this.embed.id);
|
||||
const fullSizeImageURL = snapshot.notebookImageDomainObject.configuration.fullSizeImageURL;
|
||||
this.openSnapshotOverlay(fullSizeImageURL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.openmct.objects.get(fullSizeImageObjectIdentifier)
|
||||
.then(object => {
|
||||
this.openSnapshotOverlay(object.configuration.fullSizeImageURL);
|
||||
@ -259,8 +281,20 @@ export default {
|
||||
updateSnapshot(snapshotObject) {
|
||||
this.embed.snapshot.thumbnailImage = snapshotObject.thumbnailImage;
|
||||
|
||||
updateNotebookImageDomainObject(this.openmct, this.embed.snapshot.fullSizeImageObjectIdentifier, snapshotObject.fullSizeImage);
|
||||
this.updateNotebookImageDomainObjectSnapshot(snapshotObject);
|
||||
this.updateEmbed(this.embed);
|
||||
},
|
||||
updateNotebookImageDomainObjectSnapshot(snapshotObject) {
|
||||
if (this.isSnapshotContainer) {
|
||||
const snapshot = this.snapshotContainer.getSnapshot(this.embed.id);
|
||||
|
||||
snapshot.embedObject.snapshot.thumbnailImage = snapshotObject.thumbnailImage;
|
||||
snapshot.notebookImageDomainObject.configuration.fullSizeImageURL = snapshotObject.fullSizeImage.src;
|
||||
|
||||
this.snapshotContainer.updateSnapshot(snapshot);
|
||||
} else {
|
||||
updateNotebookImageDomainObject(this.openmct, this.embed.snapshot.fullSizeImageObjectIdentifier, snapshotObject.fullSizeImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -102,9 +102,11 @@
|
||||
|
||||
<script>
|
||||
import NotebookEmbed from './NotebookEmbed.vue';
|
||||
import { createNewEmbed } from '../utils/notebook-entries';
|
||||
import Moment from 'moment';
|
||||
import TextHighlight from '../../../utils/textHighlight/TextHighlight.vue';
|
||||
import { createNewEmbed } from '../utils/notebook-entries';
|
||||
import { saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from '../utils/notebook-image';
|
||||
|
||||
import Moment from 'moment';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -210,8 +212,12 @@ export default {
|
||||
const snapshotId = $event.dataTransfer.getData('openmct/snapshot/id');
|
||||
if (snapshotId.length) {
|
||||
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
|
||||
this.entry.embeds.push(snapshot.embedObject);
|
||||
this.snapshotContainer.removeSnapshot(snapshotId);
|
||||
this.entry.embeds.push(snapshot);
|
||||
|
||||
const namespace = this.domainObject.identifier.namespace;
|
||||
const notebookImageDomainObject = updateNamespaceOfDomainObject(snapshot.notebookImageDomainObject, namespace);
|
||||
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
|
||||
} else {
|
||||
const data = $event.dataTransfer.getData('openmct/domain-object-path');
|
||||
const objectPath = JSON.parse(data);
|
||||
|
@ -19,17 +19,24 @@
|
||||
import Snapshot from '../snapshot';
|
||||
import { getDefaultNotebook, validateNotebookStorageObject } from '../utils/notebook-storage';
|
||||
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
|
||||
import { getMenuItems } from '../utils/notebook-snapshot-menu';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
currentView: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
domainObject: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
ignoreLink: {
|
||||
isPreview: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
@ -50,65 +57,53 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
validateNotebookStorageObject();
|
||||
this.getDefaultNotebookObject();
|
||||
|
||||
this.notebookSnapshot = new Snapshot(this.openmct);
|
||||
this.setDefaultNotebookStatus();
|
||||
},
|
||||
methods: {
|
||||
async getDefaultNotebookObject() {
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const defaultNotebookObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
|
||||
getPreviewObjectLink() {
|
||||
const relativePath = this.openmct.objects.getRelativePath(this.objectPath);
|
||||
const urlParams = this.openmct.router.getParams();
|
||||
urlParams.view = this.currentView.key;
|
||||
|
||||
return defaultNotebookObject;
|
||||
const urlParamsString = Object.entries(urlParams)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('&');
|
||||
|
||||
return `#/browse/${relativePath}?${urlParamsString}`;
|
||||
},
|
||||
async showMenu(event) {
|
||||
const notebookTypes = [];
|
||||
const menuItemOptions = {
|
||||
default: {
|
||||
cssClass: 'icon-notebook',
|
||||
name: `Save to Notebook`,
|
||||
onItemClicked: () => this.snapshot(NOTEBOOK_DEFAULT, event.target)
|
||||
},
|
||||
snapshot: {
|
||||
cssClass: 'icon-camera',
|
||||
name: 'Save to Notebook Snapshots',
|
||||
onItemClicked: () => this.snapshot(NOTEBOOK_SNAPSHOT, event.target)
|
||||
}
|
||||
};
|
||||
|
||||
const notebookTypes = await getMenuItems(this.openmct, menuItemOptions);
|
||||
const elementBoundingClientRect = this.$el.getBoundingClientRect();
|
||||
const x = elementBoundingClientRect.x;
|
||||
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
|
||||
|
||||
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}`;
|
||||
|
||||
notebookTypes.push({
|
||||
cssClass: 'icon-notebook',
|
||||
name: `Save to Notebook ${defaultPath}`,
|
||||
onItemClicked: () => {
|
||||
return this.snapshot(NOTEBOOK_DEFAULT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
notebookTypes.push({
|
||||
cssClass: 'icon-camera',
|
||||
name: 'Save to Notebook Snapshots',
|
||||
onItemClicked: () => {
|
||||
return this.snapshot(NOTEBOOK_SNAPSHOT);
|
||||
}
|
||||
});
|
||||
|
||||
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 bounds = this.openmct.time.bounds();
|
||||
const link = !this.ignoreLink
|
||||
? window.location.hash
|
||||
: null;
|
||||
|
||||
const wrapper = target && target.closest('.js-notebook-snapshot-item-wrapper')
|
||||
|| document;
|
||||
const element = wrapper.querySelector('.js-notebook-snapshot-item');
|
||||
const objectPath = this.objectPath || this.openmct.router.path;
|
||||
const link = this.isPreview
|
||||
? this.getPreviewObjectLink()
|
||||
: window.location.hash;
|
||||
const snapshotMeta = {
|
||||
bounds,
|
||||
bounds: this.openmct.time.bounds(),
|
||||
link,
|
||||
objectPath,
|
||||
openmct: this.openmct
|
||||
@ -119,9 +114,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');
|
||||
}
|
||||
|
@ -27,15 +27,15 @@
|
||||
</div><!-- closes l-browse-bar -->
|
||||
<div class="c-snapshots">
|
||||
<span v-for="snapshot in snapshots"
|
||||
:key="snapshot.id"
|
||||
:key="snapshot.embedObject.id"
|
||||
draggable="true"
|
||||
@dragstart="startEmbedDrag(snapshot, $event)"
|
||||
>
|
||||
<NotebookEmbed ref="notebookEmbed"
|
||||
:key="snapshot.id"
|
||||
:embed="snapshot"
|
||||
:key="snapshot.embedObject.id"
|
||||
:embed="snapshot.embedObject"
|
||||
:is-snapshot-container="true"
|
||||
:remove-action-string="'Delete Snapshot'"
|
||||
@updateEmbed="updateSnapshot"
|
||||
@removeEmbed="removeSnapshot"
|
||||
/>
|
||||
</span>
|
||||
@ -119,11 +119,8 @@ export default {
|
||||
this.snapshots = this.snapshotContainer.getSnapshots();
|
||||
},
|
||||
startEmbedDrag(snapshot, event) {
|
||||
event.dataTransfer.setData('text/plain', snapshot.id);
|
||||
event.dataTransfer.setData('openmct/snapshot/id', snapshot.id);
|
||||
},
|
||||
updateSnapshot(snapshot) {
|
||||
this.snapshotContainer.updateSnapshot(snapshot);
|
||||
event.dataTransfer.setData('text/plain', snapshot.embedObject.id);
|
||||
event.dataTransfer.setData('openmct/snapshot/id', snapshot.embedObject.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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');
|
||||
},
|
||||
|
@ -18,13 +18,18 @@ export default class SnapshotContainer extends EventEmitter {
|
||||
return SnapshotContainer.instance;
|
||||
}
|
||||
|
||||
addSnapshot(embedObject) {
|
||||
addSnapshot(notebookImageDomainObject, embedObject) {
|
||||
const snapshots = this.getSnapshots();
|
||||
if (snapshots.length >= NOTEBOOK_SNAPSHOT_MAX_COUNT) {
|
||||
snapshots.pop();
|
||||
}
|
||||
|
||||
snapshots.unshift(embedObject);
|
||||
const snapshotObject = {
|
||||
notebookImageDomainObject,
|
||||
embedObject
|
||||
};
|
||||
|
||||
snapshots.unshift(snapshotObject);
|
||||
|
||||
return this.saveSnapshots(snapshots);
|
||||
}
|
||||
@ -32,7 +37,7 @@ export default class SnapshotContainer extends EventEmitter {
|
||||
getSnapshot(id) {
|
||||
const snapshots = this.getSnapshots();
|
||||
|
||||
return snapshots.find(s => s.id === id);
|
||||
return snapshots.find(s => s.embedObject.id === id);
|
||||
}
|
||||
|
||||
getSnapshots() {
|
||||
@ -47,7 +52,7 @@ export default class SnapshotContainer extends EventEmitter {
|
||||
}
|
||||
|
||||
const snapshots = this.getSnapshots();
|
||||
const filteredsnapshots = snapshots.filter(snapshot => snapshot.id !== id);
|
||||
const filteredsnapshots = snapshots.filter(snapshot => snapshot.embedObject.id !== id);
|
||||
|
||||
return this.saveSnapshots(filteredsnapshots);
|
||||
}
|
||||
@ -73,7 +78,7 @@ export default class SnapshotContainer extends EventEmitter {
|
||||
updateSnapshot(snapshot) {
|
||||
const snapshots = this.getSnapshots();
|
||||
const updatedSnapshots = snapshots.map(s => {
|
||||
return s.id === snapshot.id
|
||||
return s.embedObject.id === snapshot.embedObject.id
|
||||
? snapshot
|
||||
: s;
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
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 { createNotebookImageDomainObject, saveNotebookImageDomainObject, updateNamespaceOfDomainObject, DEFAULT_SIZE } from './utils/notebook-image';
|
||||
|
||||
import SnapshotContainer from './snapshot-container';
|
||||
import ImageExporter from '../../exporters/ImageExporter';
|
||||
@ -35,43 +35,47 @@ export default class Snapshot {
|
||||
* @private
|
||||
*/
|
||||
_saveSnapShot(notebookType, fullSizeImageURL, thumbnailImageURL, snapshotMeta) {
|
||||
createNotebookImageDomainObject(this.openmct, fullSizeImageURL)
|
||||
.then(object => {
|
||||
const thumbnailImage = { src: thumbnailImageURL || '' };
|
||||
const snapshot = {
|
||||
fullSizeImageObjectIdentifier: object.identifier,
|
||||
thumbnailImage
|
||||
};
|
||||
const embed = createNewEmbed(snapshotMeta, snapshot);
|
||||
if (notebookType === NOTEBOOK_DEFAULT) {
|
||||
this._saveToDefaultNoteBook(embed);
|
||||
const object = createNotebookImageDomainObject(fullSizeImageURL);
|
||||
const thumbnailImage = { src: thumbnailImageURL || '' };
|
||||
const snapshot = {
|
||||
fullSizeImageObjectIdentifier: object.identifier,
|
||||
thumbnailImage
|
||||
};
|
||||
const embed = createNewEmbed(snapshotMeta, snapshot);
|
||||
if (notebookType === NOTEBOOK_DEFAULT) {
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._saveToNotebookSnapshots(embed);
|
||||
});
|
||||
this._saveToDefaultNoteBook(notebookStorage, embed);
|
||||
const notebookImageDomainObject = updateNamespaceOfDomainObject(object, notebookStorage.identifier.namespace);
|
||||
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
|
||||
} else {
|
||||
this._saveToNotebookSnapshots(object, embed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_saveToDefaultNoteBook(embed) {
|
||||
const notebookStorage = getDefaultNotebook();
|
||||
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
|
||||
_saveToDefaultNoteBook(notebookStorage, embed) {
|
||||
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);
|
||||
});
|
||||
@ -80,19 +84,22 @@ export default class Snapshot {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_saveToNotebookSnapshots(embed) {
|
||||
this.snapshotContainer.addSnapshot(embed);
|
||||
_saveToNotebookSnapshots(notebookImageDomainObject, embed) {
|
||||
this.snapshotContainer.addSnapshot(notebookImageDomainObject, embed);
|
||||
}
|
||||
|
||||
_showNotification(msg, url) {
|
||||
const options = {
|
||||
autoDismissTimeout: 30000,
|
||||
link: {
|
||||
autoDismissTimeout: 30000
|
||||
};
|
||||
|
||||
if (!this.openmct.editor.isEditing()) {
|
||||
options.link = {
|
||||
cssClass: '',
|
||||
text: 'click to view',
|
||||
onClick: this._navigateToNotebook(url)
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
this.openmct.notifications.info(msg, options);
|
||||
}
|
||||
@ -103,7 +110,8 @@ export default class Snapshot {
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.location.href = window.location.origin + url;
|
||||
const path = window.location.href.split('#');
|
||||
window.location.href = path[0] + url;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -5,14 +5,14 @@ export const DEFAULT_SIZE = {
|
||||
height: 30
|
||||
};
|
||||
|
||||
export function createNotebookImageDomainObject(openmct, fullSizeImageURL) {
|
||||
export function createNotebookImageDomainObject(fullSizeImageURL) {
|
||||
const identifier = {
|
||||
key: uuid(),
|
||||
namespace: ''
|
||||
};
|
||||
const viewType = 'notebookSnapshotImage';
|
||||
|
||||
const object = {
|
||||
return {
|
||||
name: 'Notebook Snapshot Image',
|
||||
type: viewType,
|
||||
identifier,
|
||||
@ -20,21 +20,6 @@ export function createNotebookImageDomainObject(openmct, fullSizeImageURL) {
|
||||
fullSizeImageURL
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
openmct.objects.save(object)
|
||||
.then(result => {
|
||||
if (result) {
|
||||
resolve(object);
|
||||
}
|
||||
|
||||
reject();
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function getThumbnailURLFromCanvas(canvas, size = DEFAULT_SIZE) {
|
||||
@ -67,6 +52,23 @@ export function getThumbnailURLFromimageUrl(imageUrl, size = DEFAULT_SIZE) {
|
||||
});
|
||||
}
|
||||
|
||||
export function saveNotebookImageDomainObject(openmct, object) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openmct.objects.save(object)
|
||||
.then(result => {
|
||||
if (result) {
|
||||
resolve(object);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function updateNotebookImageDomainObject(openmct, identifier, fullSizeImage) {
|
||||
openmct.objects.get(identifier)
|
||||
.then(domainObject => {
|
||||
@ -76,3 +78,9 @@ export function updateNotebookImageDomainObject(openmct, identifier, fullSizeIma
|
||||
openmct.objects.mutate(domainObject, 'configuration', configuration);
|
||||
});
|
||||
}
|
||||
|
||||
export function updateNamespaceOfDomainObject(object, namespace) {
|
||||
object.identifier.namespace = namespace;
|
||||
|
||||
return object;
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { createNotebookImageDomainObject, getThumbnailURLFromimageUrl } from './notebook-image';
|
||||
import { createNotebookImageDomainObject, getThumbnailURLFromimageUrl, saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from './notebook-image';
|
||||
import { mutateObject } from './notebook-entries';
|
||||
|
||||
const IMAGE_MIGRATION_VER = "v1";
|
||||
|
||||
export function notebookImageMigration(openmct, domainObject) {
|
||||
const configuration = domainObject.configuration;
|
||||
const notebookEntries = configuration.entries;
|
||||
|
||||
const imageMigrationVer = configuration.imageMigrationVer;
|
||||
if (imageMigrationVer && imageMigrationVer === 'v1') {
|
||||
if (imageMigrationVer && imageMigrationVer === IMAGE_MIGRATION_VER) {
|
||||
return;
|
||||
}
|
||||
|
||||
configuration.imageMigrationVer = 'v1';
|
||||
configuration.imageMigrationVer = IMAGE_MIGRATION_VER;
|
||||
|
||||
// to avoid muliple notebookImageMigration calls updating images.
|
||||
mutateObject(openmct, domainObject, 'configuration', configuration);
|
||||
@ -27,14 +29,16 @@ export function notebookImageMigration(openmct, domainObject) {
|
||||
const fullSizeImageURL = snapshot.src;
|
||||
if (fullSizeImageURL) {
|
||||
const thumbnailImageURL = await getThumbnailURLFromimageUrl(fullSizeImageURL);
|
||||
const notebookImageDomainObject = await createNotebookImageDomainObject(openmct, fullSizeImageURL);
|
||||
|
||||
const object = createNotebookImageDomainObject(fullSizeImageURL);
|
||||
const notebookImageDomainObject = updateNamespaceOfDomainObject(object, domainObject.identifier.namespace);
|
||||
embed.snapshot = {
|
||||
fullSizeImageObjectIdentifier: notebookImageDomainObject.identifier,
|
||||
thumbnailImage: { src: thumbnailImageURL || '' }
|
||||
};
|
||||
|
||||
mutateObject(openmct, domainObject, 'configuration.entries', notebookEntries);
|
||||
|
||||
saveNotebookImageDomainObject(openmct, notebookImageDomainObject);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
31
src/plugins/notebook/utils/notebook-snapshot-menu.js
Normal file
31
src/plugins/notebook/utils/notebook-snapshot-menu.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { getDefaultNotebook, getNotebookSectionAndPage } from './notebook-storage';
|
||||
|
||||
export async function getMenuItems(openmct, menuItemOptions) {
|
||||
const notebookTypes = [];
|
||||
|
||||
const defaultNotebook = getDefaultNotebook();
|
||||
const defaultNotebookObject = defaultNotebook && await openmct.objects.get(defaultNotebook.identifier);
|
||||
if (defaultNotebookObject) {
|
||||
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: menuItemOptions.default.cssClass,
|
||||
name: `${menuItemOptions.default.name} ${defaultPath}`,
|
||||
onItemClicked: menuItemOptions.default.onItemClicked
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
notebookTypes.push({
|
||||
cssClass: menuItemOptions.snapshot.cssClass,
|
||||
name: menuItemOptions.snapshot.name,
|
||||
onItemClicked: menuItemOptions.snapshot.onItemClicked
|
||||
});
|
||||
|
||||
return notebookTypes;
|
||||
}
|
@ -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,20 +54,27 @@ 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;
|
||||
}
|
||||
|
||||
const path = await openmct.objects.getOriginalPath(domainObject.identifier)
|
||||
.then(objectPath => objectPath
|
||||
.map(o => o && openmct.objects.makeKeyString(o.identifier))
|
||||
.reverse()
|
||||
.join('/')
|
||||
);
|
||||
const { page, section } = getDefaultNotebook();
|
||||
.then(openmct.objects.getRelativePath);
|
||||
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 +82,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);
|
||||
}
|
||||
|
||||
@ -86,10 +99,13 @@ export function validateNotebookStorageObject() {
|
||||
|
||||
let valid = false;
|
||||
if (notebookStorage) {
|
||||
Object.entries(notebookStorage).forEach(([key, value]) => {
|
||||
const oldInvalidKeys = ['notebookMeta', 'page', 'section'];
|
||||
valid = Object.entries(notebookStorage).every(([key, value]) => {
|
||||
const validKey = key !== undefined && key !== null;
|
||||
const validValue = value !== undefined && value !== null;
|
||||
valid = validKey && validValue;
|
||||
const hasOldInvalidKeys = oldInvalidKeys.includes(key);
|
||||
|
||||
return validKey && validValue && !hasOldInvalidKeys;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -47,6 +47,7 @@ export default class CouchObjectProvider {
|
||||
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);
|
||||
@ -378,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();
|
||||
}
|
||||
}
|
||||
@ -436,7 +437,7 @@ export default class CouchObjectProvider {
|
||||
if (!this.changesFeedSharedWorker) {
|
||||
this.changesFeedSharedWorker = this.startSharedWorker();
|
||||
|
||||
if (typeof this.stopObservingObjectChanges === 'function') {
|
||||
if (this.isObservingObjectChanges()) {
|
||||
this.stopObservingObjectChanges();
|
||||
}
|
||||
|
||||
@ -458,7 +459,7 @@ export default class CouchObjectProvider {
|
||||
|
||||
let error = false;
|
||||
|
||||
if (typeof this.stopObservingObjectChanges === 'function') {
|
||||
if (this.isObservingObjectChanges()) {
|
||||
this.stopObservingObjectChanges();
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
@ -394,12 +393,31 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
clearData() {
|
||||
clearSeries() {
|
||||
this.config.series.forEach(function (series) {
|
||||
series.reset();
|
||||
});
|
||||
},
|
||||
|
||||
compositionPathContainsId(domainObjectToClear) {
|
||||
return domainObjectToClear.composition.some((compositionIdentifier) => {
|
||||
return this.openmct.objects.areIdsEqual(compositionIdentifier, this.domainObject.identifier);
|
||||
});
|
||||
},
|
||||
|
||||
clearData(domainObjectToClear) {
|
||||
// If we don't have an object to clear (global), or the IDs are equal, just clear the data.
|
||||
// If we have an object to clear, but the IDs don't match, we need to check the composition
|
||||
// of the object we've been asked to clear to see if it contains the id we're looking for.
|
||||
// This happens with stacked plots for example.
|
||||
// If we find the ID, clear the plot.
|
||||
if (!domainObjectToClear
|
||||
|| this.openmct.objects.areIdsEqual(domainObjectToClear.identifier, this.domainObject.identifier)
|
||||
|| this.compositionPathContainsId(domainObjectToClear)) {
|
||||
this.clearSeries();
|
||||
}
|
||||
},
|
||||
|
||||
setDisplayRange(series, xKey) {
|
||||
if (this.config.series.length !== 1) {
|
||||
return;
|
||||
@ -507,7 +525,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 +641,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 +945,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() {
|
||||
@ -1019,7 +1029,8 @@ export default {
|
||||
this.$emit('statusUpdated', status);
|
||||
},
|
||||
handleWindowResize() {
|
||||
if (this.offsetWidth !== this.$parent.$refs.plotWrapper.offsetWidth) {
|
||||
if (this.$parent.$refs.plotWrapper
|
||||
&& (this.offsetWidth !== this.$parent.$refs.plotWrapper.offsetWidth)) {
|
||||
this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth;
|
||||
this.config.series.models.forEach(this.loadSeriesData, this);
|
||||
}
|
||||
|
@ -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) || [];
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,10 @@ function ConfigStore() {
|
||||
|
||||
ConfigStore.prototype.deleteStore = function (id) {
|
||||
if (this.store[id]) {
|
||||
this.store[id].destroy();
|
||||
if (this.store[id].destroy) {
|
||||
this.store[id].destroy();
|
||||
}
|
||||
|
||||
delete this.store[id];
|
||||
}
|
||||
};
|
||||
|
@ -176,7 +176,9 @@ DrawWebGL.prototype.doDraw = function (drawType, buf, color, points, shape) {
|
||||
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
|
||||
this.gl.uniform4fv(this.uColor, color);
|
||||
this.gl.uniform1i(this.uMarkerShape, shapeCode);
|
||||
this.gl.drawArrays(drawType, 0, points);
|
||||
if (points !== 0) {
|
||||
this.gl.drawArrays(drawType, 0, points);
|
||||
}
|
||||
};
|
||||
|
||||
DrawWebGL.prototype.clear = function () {
|
||||
|
@ -715,14 +715,15 @@ describe("the plugin", function () {
|
||||
});
|
||||
|
||||
it("Adds a new point to the plot", (done) => {
|
||||
let originalLength = config.series.models[0].data.length;
|
||||
let originalLength = config.series.models[0].getSeriesData().length;
|
||||
config.series.models[0].add({
|
||||
utc: 2,
|
||||
'some-key': 1,
|
||||
'some-other-key': 2
|
||||
});
|
||||
Vue.nextTick(() => {
|
||||
expect(config.series.models[0].data.length).toEqual(originalLength + 1);
|
||||
const seriesData = config.series.models[0].getSeriesData();
|
||||
expect(seriesData.length).toEqual(originalLength + 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -68,7 +68,9 @@ define([
|
||||
'./performanceIndicator/plugin',
|
||||
'./CouchDBSearchFolder/plugin',
|
||||
'./timeline/plugin',
|
||||
'./hyperlink/plugin'
|
||||
'./hyperlink/plugin',
|
||||
'./clock/plugin',
|
||||
'./DeviceClassifier/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@ -117,7 +119,9 @@ define([
|
||||
PerformanceIndicator,
|
||||
CouchDBSearchFolder,
|
||||
Timeline,
|
||||
Hyperlink
|
||||
Hyperlink,
|
||||
Clock,
|
||||
DeviceClassifier
|
||||
) {
|
||||
const bundleMap = {
|
||||
LocalStorage: 'platform/persistence/local',
|
||||
@ -221,6 +225,8 @@ define([
|
||||
plugins.CouchDBSearchFolder = CouchDBSearchFolder.default;
|
||||
plugins.Timeline = Timeline.default;
|
||||
plugins.Hyperlink = Hyperlink.default;
|
||||
plugins.Clock = Clock.default;
|
||||
plugins.DeviceClassifier = DeviceClassifier.default;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user