mirror of
https://github.com/nasa/openmct.git
synced 2025-06-28 11:51:10 +00:00
Compare commits
43 Commits
html2canva
...
condition-
Author | SHA1 | Date | |
---|---|---|---|
0a4eeb6a32 | |||
4df88ddccf | |||
f2b2953a5d | |||
62de310686 | |||
4b9ff67e49 | |||
d5e32ec494 | |||
38880ba3d1 | |||
a99ce7733c | |||
9f48764210 | |||
a1aaa0dd41 | |||
bee15e98c8 | |||
092bbe547d | |||
6cbe05317c | |||
3b92fcdf6c | |||
6dde54bd25 | |||
359e7377ac | |||
9f4190f781 | |||
f3fc991a74 | |||
2564e75fc9 | |||
f42fe78acf | |||
fe928a1386 | |||
b329ed6ed5 | |||
9b7a0d7e4c | |||
5c15e53abb | |||
f58b3881f2 | |||
071a13b219 | |||
ca66898e51 | |||
94c7b2343a | |||
c397c336ab | |||
eea23f2caf | |||
6665641c02 | |||
c3ebf52dd2 | |||
f8f2e7da9b | |||
240f58b2d0 | |||
7d3baee7b5 | |||
1f5cb7ca42 | |||
4a7ebe326c | |||
10da314a4a | |||
b3ceccd7fb | |||
1bde4c9a0c | |||
4b85360446 | |||
41b860a547 | |||
254b3db966 |
@ -1,36 +1,93 @@
|
|||||||
version: 2
|
version: 2.1
|
||||||
jobs:
|
executors:
|
||||||
build:
|
linux:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:13-browsers
|
- image: cimg/base:stable
|
||||||
environment:
|
orbs:
|
||||||
CHROME_BIN: "/usr/bin/google-chrome"
|
node: circleci/node@4.5.1
|
||||||
steps:
|
browser-tools: circleci/browser-tools@1.1.3
|
||||||
- checkout
|
jobs:
|
||||||
- run:
|
|
||||||
name: Update npm
|
|
||||||
command: 'sudo npm install -g npm@latest'
|
|
||||||
- restore_cache:
|
|
||||||
key: dependency-cache-{{ checksum "package.json" }}
|
|
||||||
- run:
|
|
||||||
name: Installing dependencies (npm install)
|
|
||||||
command: npm install
|
|
||||||
- save_cache:
|
|
||||||
key: dependency-cache-{{ checksum "package.json" }}
|
|
||||||
paths:
|
|
||||||
- node_modules
|
|
||||||
- run:
|
|
||||||
name: npm run test:coverage
|
|
||||||
command: npm run test:coverage
|
|
||||||
- run:
|
|
||||||
name: npm run lint
|
|
||||||
command: npm run lint
|
|
||||||
- store_artifacts:
|
|
||||||
path: dist
|
|
||||||
prefix: dist
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
version: 2
|
|
||||||
test:
|
test:
|
||||||
|
parameters:
|
||||||
|
node-version:
|
||||||
|
type: string
|
||||||
|
browser:
|
||||||
|
type: string
|
||||||
|
always-pass:
|
||||||
|
type: boolean
|
||||||
|
executor: linux
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}
|
||||||
|
- node/install:
|
||||||
|
node-version: << parameters.node-version >>
|
||||||
|
- node/install-packages:
|
||||||
|
override-ci-command: npm install
|
||||||
|
- when: # Just to save time until caching saves the browser bin
|
||||||
|
condition:
|
||||||
|
equal: [ "FirefoxESR", <<parameters.browser>> ]
|
||||||
|
steps:
|
||||||
|
- browser-tools/install-firefox:
|
||||||
|
version: "78.11.0esr" #https://archive.mozilla.org/pub/firefox/releases/
|
||||||
|
- when: # Just to save time until caching saves the browser bin
|
||||||
|
condition:
|
||||||
|
equal: [ "ChromeHeadless", <<parameters.browser>> ]
|
||||||
|
steps:
|
||||||
|
- browser-tools/install-chrome:
|
||||||
|
replace-existing: false
|
||||||
|
- save_cache:
|
||||||
|
key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}
|
||||||
|
paths:
|
||||||
|
- ~/.npm
|
||||||
|
- ~/.cache
|
||||||
|
- node_modules
|
||||||
|
- run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
|
||||||
|
- store_test_results:
|
||||||
|
path: dist/reports/tests/
|
||||||
|
- store_artifacts:
|
||||||
|
path: dist/reports/
|
||||||
|
workflows:
|
||||||
|
matrix-tests:
|
||||||
jobs:
|
jobs:
|
||||||
- build
|
- test:
|
||||||
|
name: node10-chrome
|
||||||
|
node-version: lts/dubnium
|
||||||
|
browser: ChromeHeadless
|
||||||
|
always-pass: false
|
||||||
|
- test:
|
||||||
|
name: node12-firefoxESR-build-only
|
||||||
|
node-version: lts/erbium
|
||||||
|
browser: FirefoxESR
|
||||||
|
always-pass: true
|
||||||
|
- test:
|
||||||
|
name: node14-chrome-build-only
|
||||||
|
node-version: lts/fermium
|
||||||
|
browser: ChromeHeadless
|
||||||
|
always-pass: true
|
||||||
|
nightly:
|
||||||
|
jobs:
|
||||||
|
- test:
|
||||||
|
name: node10-chrome-nightly
|
||||||
|
node-version: lts/dubnium
|
||||||
|
browser: ChromeHeadless
|
||||||
|
always-pass: false
|
||||||
|
- test:
|
||||||
|
name: node12-firefoxESR-nightly
|
||||||
|
node-version: lts/erbium
|
||||||
|
browser: FirefoxESR
|
||||||
|
always-pass: false
|
||||||
|
- test:
|
||||||
|
name: node14-chrome-nightly
|
||||||
|
node-version: lts/fermium
|
||||||
|
browser: ChromeHeadless
|
||||||
|
always-pass: false
|
||||||
|
triggers:
|
||||||
|
- schedule:
|
||||||
|
cron: "0 0 * * *"
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
|
||||||
|
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,11 +1,12 @@
|
|||||||
<!--- This is for filing bugs. If you have a general question, please -->
|
|
||||||
<!--- visit https://github.com/nasa/openmct/discussions -->
|
|
||||||
|
|
||||||
---
|
---
|
||||||
name: Bug Report
|
name: Bug report
|
||||||
about: File a Bug !
|
about: File a Bug !
|
||||||
|
title: ''
|
||||||
|
labels: type:bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--- Focus on user impact in the title. Use the Summary Field to -->
|
<!--- Focus on user impact in the title. Use the Summary Field to -->
|
||||||
<!--- describe the problem technically. -->
|
<!--- describe the problem technically. -->
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ about: File a Bug !
|
|||||||
|
|
||||||
#### Environment
|
#### Environment
|
||||||
* Open MCT Version: <!--- date of build, version, or SHA -->
|
* Open MCT Version: <!--- date of build, version, or SHA -->
|
||||||
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yams? -->
|
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yamcs? -->
|
||||||
* OS:
|
* OS:
|
||||||
* Browser:
|
* Browser:
|
||||||
|
|
||||||
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Discussions
|
||||||
|
url: https://github.com/nasa/openmct/discussions
|
||||||
|
about: Got a question?
|
||||||
|
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement request
|
||||||
|
about: Suggest an enhancement or new improvement for this project
|
||||||
|
title: ''
|
||||||
|
labels: type:enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
4
.github/workflows/lighthouse.yml
vendored
4
.github/workflows/lighthouse.yml
vendored
@ -13,6 +13,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.version }}
|
ref: ${{ github.event.inputs.version }}
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '14'
|
||||||
- run: npm install && npm install -g @lhci/cli #Don't want to include this in our deps
|
- run: npm install && npm install -g @lhci/cli #Don't want to include this in our deps
|
||||||
- run: lhci autorun
|
- run: lhci autorun
|
2
API.md
2
API.md
@ -996,7 +996,7 @@ reveal additional information when the mouse cursor is hovered over it.
|
|||||||
A common use case for indicators is to convey the state of some external system such as a
|
A common use case for indicators is to convey the state of some external system such as a
|
||||||
persistence backend or HTTP server. So long as this system is accessible via HTTP request,
|
persistence backend or HTTP server. So long as this system is accessible via HTTP request,
|
||||||
Open MCT provides a general purpose indicator to show whether the server is available and
|
Open MCT provides a general purpose indicator to show whether the server is available and
|
||||||
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
|
the [documentation](./src/plugins/URLIndicatorPlugin) for details on how to install and configure the
|
||||||
URL Status Indicator.
|
URL Status Indicator.
|
||||||
|
|
||||||
|
@ -423,7 +423,7 @@ which can help with this, however.
|
|||||||
instead of separate approaches for static and substitutable
|
instead of separate approaches for static and substitutable
|
||||||
dependencies.
|
dependencies.
|
||||||
* Removes need to understand Angular's DI mechanism.
|
* Removes need to understand Angular's DI mechanism.
|
||||||
* Improves useability of documentation (`typeService` is an
|
* Improves usability of documentation (`typeService` is an
|
||||||
instance of `CompositeService` and implements `TypeService`
|
instance of `CompositeService` and implements `TypeService`
|
||||||
so you can easily traverse links in the JSDoc.)
|
so you can easily traverse links in the JSDoc.)
|
||||||
* Can be used more easily from Web Workers, allowing services
|
* Can be used more easily from Web Workers, allowing services
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
## Legacy Documentation
|
## Legacy Documentation
|
||||||
|
|
||||||
As we transition to a new API, the following documentation for the old API
|
As we transition to a new API, the following documentation for the old API
|
||||||
(which is supported during the 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
|
* The [Architecture Overview](architecture/) describes the concepts used
|
||||||
throughout Open MCT, and gives a high level overview of the platform's design.
|
throughout Open MCT, and gives a high level overview of the platform's design.
|
||||||
|
@ -63,7 +63,7 @@ define([
|
|||||||
|
|
||||||
StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
||||||
var start = options.start;
|
var start = options.start;
|
||||||
var end = options.end;
|
var end = Math.min(Date.now(), options.end); // no future values
|
||||||
var duration = domainObject.telemetry.duration * 1000;
|
var duration = domainObject.telemetry.duration * 1000;
|
||||||
if (options.strategy === 'latest' || options.size === 1) {
|
if (options.strategy === 'latest' || options.size === 1) {
|
||||||
start = end;
|
start = end;
|
||||||
|
@ -49,6 +49,10 @@ define([
|
|||||||
];
|
];
|
||||||
const IMAGE_DELAY = 20000;
|
const IMAGE_DELAY = 20000;
|
||||||
|
|
||||||
|
function getCompassValues(min, max) {
|
||||||
|
return min + Math.random() * (max - min);
|
||||||
|
}
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, name) {
|
function pointForTimestamp(timestamp, name) {
|
||||||
const url = IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length];
|
const url = IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length];
|
||||||
const urlItems = url.split('/');
|
const urlItems = url.split('/');
|
||||||
@ -59,6 +63,9 @@ define([
|
|||||||
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
url,
|
url,
|
||||||
|
sunOrientation: getCompassValues(0, 360),
|
||||||
|
cameraPan: getCompassValues(0, 360),
|
||||||
|
heading: getCompassValues(0, 360),
|
||||||
imageDownloadName
|
imageDownloadName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Vue from 'Vue';
|
import Vue from 'vue';
|
||||||
import HelloWorld from './HelloWorld.vue';
|
import HelloWorld from './HelloWorld.vue';
|
||||||
|
|
||||||
function SimpleVuePlugin() {
|
function SimpleVuePlugin() {
|
||||||
|
@ -152,7 +152,7 @@
|
|||||||
<h2>How to Use Glyphs</h2>
|
<h2>How to Use Glyphs</h2>
|
||||||
<div class="cols cols1-1">
|
<div class="cols cols1-1">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p>The easiest way to use a glyph is to include its CSS class in an element. The CSS adds a 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>
|
<p>Alternately, you can use the <code>.ui-symbol</code> class in an object that contains encoded HTML entities. This method is only recommended if you cannot use the aforementioned CSS class approach.</p>
|
||||||
</div>
|
</div>
|
||||||
<mct-example><a class="s-button icon-gear" title="Settings"></a>
|
<mct-example><a class="s-button icon-gear" title="Settings"></a>
|
||||||
|
@ -88,6 +88,7 @@
|
|||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.PlanLayout());
|
openmct.install(openmct.plugins.PlanLayout());
|
||||||
openmct.install(openmct.plugins.Timeline());
|
openmct.install(openmct.plugins.Timeline());
|
||||||
|
openmct.install(openmct.plugins.Hyperlink());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
openmct.install(openmct.plugins.AutoflowView({
|
openmct.install(openmct.plugins.AutoflowView({
|
||||||
type: "telemetry.panel"
|
type: "telemetry.panel"
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
/*global module,process*/
|
/*global module,process*/
|
||||||
|
|
||||||
const devMode = process.env.NODE_ENV !== 'production';
|
const devMode = process.env.NODE_ENV !== 'production';
|
||||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'FirefoxHeadless'];
|
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
|
||||||
const coverageEnabled = process.env.COVERAGE === 'true';
|
const coverageEnabled = process.env.COVERAGE === 'true';
|
||||||
const reporters = ['progress', 'html'];
|
const reporters = ['progress', 'html', 'junit'];
|
||||||
|
|
||||||
if (coverageEnabled) {
|
if (coverageEnabled) {
|
||||||
reporters.push('coverage-istanbul');
|
reporters.push('coverage-istanbul');
|
||||||
@ -59,7 +59,8 @@ module.exports = (config) => {
|
|||||||
browsers: browsers,
|
browsers: browsers,
|
||||||
client: {
|
client: {
|
||||||
jasmine: {
|
jasmine: {
|
||||||
random: false
|
random: false,
|
||||||
|
timeoutInterval: 30000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
customLaunchers: {
|
customLaunchers: {
|
||||||
@ -67,6 +68,10 @@ module.exports = (config) => {
|
|||||||
base: 'Chrome',
|
base: 'Chrome',
|
||||||
flags: ['--remote-debugging-port=9222'],
|
flags: ['--remote-debugging-port=9222'],
|
||||||
debug: true
|
debug: true
|
||||||
|
},
|
||||||
|
FirefoxESR: {
|
||||||
|
base: 'FirefoxHeadless',
|
||||||
|
name: 'FirefoxESR'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors: true,
|
colors: true,
|
||||||
@ -78,12 +83,21 @@ module.exports = (config) => {
|
|||||||
preserveDescribeNesting: true,
|
preserveDescribeNesting: true,
|
||||||
foldAll: false
|
foldAll: false
|
||||||
},
|
},
|
||||||
browserConsoleLogOptions: { level: "error", format: "%b %T: %m", terminal: true },
|
junitReporter: {
|
||||||
|
outputDir: "dist/reports/tests",
|
||||||
|
outputFile: "test-results.xml",
|
||||||
|
useBrowserName: false
|
||||||
|
},
|
||||||
|
browserConsoleLogOptions: {
|
||||||
|
level: "error",
|
||||||
|
format: "%b %T: %m",
|
||||||
|
terminal: true
|
||||||
|
},
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
fixWebpackSourcePaths: true,
|
fixWebpackSourcePaths: true,
|
||||||
dir: process.env.CIRCLE_ARTIFACTS ?
|
dir: process.env.CIRCLE_ARTIFACTS
|
||||||
process.env.CIRCLE_ARTIFACTS + '/coverage' :
|
? process.env.CIRCLE_ARTIFACTS + '/coverage'
|
||||||
"dist/reports/coverage",
|
: "dist/reports/coverage",
|
||||||
reports: ['html', 'lcovonly', 'text-summary'],
|
reports: ['html', 'lcovonly', 'text-summary'],
|
||||||
thresholds: {
|
thresholds: {
|
||||||
global: {
|
global: {
|
||||||
|
19
package.json
19
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.7.4",
|
"version": "1.7.7",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -34,20 +34,21 @@
|
|||||||
"git-rev-sync": "^1.4.0",
|
"git-rev-sync": "^1.4.0",
|
||||||
"glob": ">= 3.0.0",
|
"glob": ">= 3.0.0",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
"html2canvas": "^1.0.0-alpha.12",
|
"html2canvas": "^1.0.0-rc.7",
|
||||||
"imports-loader": "^0.8.0",
|
"imports-loader": "^0.8.0",
|
||||||
"istanbul-instrumenter-loader": "^3.0.1",
|
"istanbul-instrumenter-loader": "^3.0.1",
|
||||||
"jasmine-core": "^3.1.0",
|
"jasmine-core": "^3.7.1",
|
||||||
"jsdoc": "^3.3.2",
|
"jsdoc": "^3.3.2",
|
||||||
"karma": "5.1.1",
|
"karma": "6.3.4",
|
||||||
"karma-chrome-launcher": "3.1.0",
|
"karma-chrome-launcher": "3.1.0",
|
||||||
|
"karma-firefox-launcher": "2.1.1",
|
||||||
"karma-cli": "2.0.0",
|
"karma-cli": "2.0.0",
|
||||||
"karma-coverage": "2.0.3",
|
"karma-coverage": "2.0.3",
|
||||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||||
"karma-firefox-launcher": "1.3.0",
|
"karma-junit-reporter": "2.0.1",
|
||||||
"karma-html-reporter": "0.2.7",
|
"karma-html-reporter": "0.2.7",
|
||||||
"karma-jasmine": "3.3.1",
|
"karma-jasmine": "4.0.1",
|
||||||
"karma-sourcemap-loader": "0.3.7",
|
"karma-sourcemap-loader": "0.3.8",
|
||||||
"karma-webpack": "4.0.2",
|
"karma-webpack": "4.0.2",
|
||||||
"location-bar": "^3.0.1",
|
"location-bar": "^3.0.1",
|
||||||
"lodash": "^4.17.12",
|
"lodash": "^4.17.12",
|
||||||
@ -89,6 +90,7 @@
|
|||||||
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
|
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
|
||||||
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
|
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
|
||||||
"test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run",
|
"test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run",
|
||||||
|
"test:coverage:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
|
||||||
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
||||||
"verify": "concurrently 'npm:test' 'npm:lint'",
|
"verify": "concurrently 'npm:test' 'npm:lint'",
|
||||||
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
||||||
@ -100,6 +102,9 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nasa/openmct.git"
|
"url": "https://github.com/nasa/openmct.git"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.10.2 <16.0.0"
|
||||||
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": true
|
"private": true
|
||||||
|
@ -64,7 +64,7 @@ define(
|
|||||||
*
|
*
|
||||||
* @param {DomainObject} domainObject the domain object to navigate to
|
* @param {DomainObject} domainObject the domain object to navigate to
|
||||||
* @param {Boolean} force if true, force navigation to occur.
|
* @param {Boolean} force if true, force navigation to occur.
|
||||||
* @returns {Boolean} true if navigation occured, otherwise false.
|
* @returns {Boolean} true if navigation occurred, otherwise false.
|
||||||
*/
|
*/
|
||||||
NavigationService.prototype.setNavigation = function (domainObject, force) {
|
NavigationService.prototype.setNavigation = function (domainObject, force) {
|
||||||
if (force) {
|
if (force) {
|
||||||
|
@ -21,28 +21,14 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
define([
|
||||||
"./src/MCTDevice",
|
"./src/AgentService"
|
||||||
"./src/AgentService",
|
|
||||||
"./src/DeviceClassifier"
|
|
||||||
], function (
|
], function (
|
||||||
MCTDevice,
|
AgentService
|
||||||
AgentService,
|
|
||||||
DeviceClassifier
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "platform/commonUI/mobile",
|
name: "platform/commonUI/mobile",
|
||||||
definition: {
|
definition: {
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "mctDevice",
|
|
||||||
"implementation": MCTDevice,
|
|
||||||
"depends": [
|
|
||||||
"agentService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"key": "agentService",
|
"key": "agentService",
|
||||||
@ -51,15 +37,6 @@ define([
|
|||||||
"$window"
|
"$window"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"runs": [
|
|
||||||
{
|
|
||||||
"implementation": DeviceClassifier,
|
|
||||||
"depends": [
|
|
||||||
"agentService",
|
|
||||||
"$document"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,122 +20,12 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
/**
|
define(["../../../../src/utils/agent/Agent.js"], function (Agent) {
|
||||||
* Provides features which support variant behavior on mobile devices.
|
function AngularAgentServiceWrapper(window) {
|
||||||
*
|
const AS = Agent.default;
|
||||||
* @namespace platform/commonUI/mobile
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
return new AS(window);
|
||||||
* The query service handles calls for browser and userAgent
|
|
||||||
* info using a comparison between the userAgent and key
|
|
||||||
* device names
|
|
||||||
* @constructor
|
|
||||||
* @param $window Angular-injected instance of the window
|
|
||||||
* @memberof platform/commonUI/mobile
|
|
||||||
*/
|
|
||||||
function AgentService($window) {
|
|
||||||
var userAgent = $window.navigator.userAgent,
|
|
||||||
matches = userAgent.match(/iPad|iPhone|Android/i) || [];
|
|
||||||
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.mobileName = matches[0];
|
|
||||||
this.$window = $window;
|
|
||||||
this.touchEnabled = ($window.ontouchstart !== undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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",
|
"name": "Math.uuid.js",
|
||||||
"version": "1.4.7",
|
"version": "1.4.7",
|
||||||
"description": "Unique identifer generation (code adapted.)",
|
"description": "Unique identifier generation (code adapted.)",
|
||||||
"author": "Robert Kieffer",
|
"author": "Robert Kieffer",
|
||||||
"website": "https://github.com/broofa/node-uuid",
|
"website": "https://github.com/broofa/node-uuid",
|
||||||
"copyright": "Copyright (c) 2010-2012 Robert Kieffer",
|
"copyright": "Copyright (c) 2010-2012 Robert Kieffer",
|
||||||
|
@ -181,7 +181,7 @@ define([
|
|||||||
],
|
],
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
"name": "Stop",
|
"name": "Stop",
|
||||||
"cssClass": "icon-box",
|
"cssClass": "icon-box-round-corners",
|
||||||
"priority": "preferred"
|
"priority": "preferred"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -101,7 +101,7 @@ define(
|
|||||||
name: "Pause"
|
name: "Pause"
|
||||||
});
|
});
|
||||||
mockStop.getMetadata.and.returnValue({
|
mockStop.getMetadata.and.returnValue({
|
||||||
cssClass: "icon-box",
|
cssClass: "icon-box-round-corners",
|
||||||
name: "Stop"
|
name: "Stop"
|
||||||
});
|
});
|
||||||
mockScope.domainObject = mockDomainObject;
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
@ -1,120 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./src/HyperlinkController',
|
|
||||||
'./res/templates/hyperlink.html'
|
|
||||||
], function (
|
|
||||||
HyperlinkController,
|
|
||||||
hyperlinkTemplate
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
name: "platform/features/hyperlink",
|
|
||||||
definition: {
|
|
||||||
"name": "Hyperlink",
|
|
||||||
"description": "Insert a hyperlink to reference a link",
|
|
||||||
"extensions": {
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"key": "hyperlink",
|
|
||||||
"name": "Hyperlink",
|
|
||||||
"cssClass": "icon-chain-links",
|
|
||||||
"description": "A hyperlink to redirect to a different link",
|
|
||||||
"features": ["creation"],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"key": "url",
|
|
||||||
"name": "URL",
|
|
||||||
"control": "textfield",
|
|
||||||
"required": true,
|
|
||||||
"cssClass": "l-input-lg"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"key": "displayText",
|
|
||||||
"name": "Text to Display",
|
|
||||||
"control": "textfield",
|
|
||||||
"required": true,
|
|
||||||
"cssClass": "l-input-lg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "displayFormat",
|
|
||||||
"name": "Display Format",
|
|
||||||
"control": "select",
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "Link",
|
|
||||||
"value": "link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "button",
|
|
||||||
"name": "Button"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cssClass": "l-inline"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "openNewTab",
|
|
||||||
"name": "Tab to Open Hyperlink",
|
|
||||||
"control": "select",
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "Open in this tab",
|
|
||||||
"value": "thisTab"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "newTab",
|
|
||||||
"name": "Open in a new tab"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cssClass": "l-inline"
|
|
||||||
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"model": {
|
|
||||||
"displayFormat": "link",
|
|
||||||
"openNewTab": "thisTab",
|
|
||||||
"removeTitle": true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "hyperlink",
|
|
||||||
"type": "hyperlink",
|
|
||||||
"name": "Hyperlink Display",
|
|
||||||
"template": hyperlinkTemplate,
|
|
||||||
"editable": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "HyperlinkController",
|
|
||||||
"implementation": HyperlinkController,
|
|
||||||
"depends": ["$scope"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,28 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<a class="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
|
|
||||||
ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
|
|
||||||
ng-class="{
|
|
||||||
'c-hyperlink--button u-fills-container' : hyperlink.isButton(),
|
|
||||||
'c-hyperlink--link' : !hyperlink.isButton() }">
|
|
||||||
<span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span>
|
|
||||||
</a>
|
|
@ -1,61 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle adds the Hyperlink object type, which can be used to add hyperlinks as a domain Object type
|
|
||||||
and into display Layouts as either a button or link that can be chosen to open in either the same tab or
|
|
||||||
create a new tab to open the link in
|
|
||||||
* @namespace platform/features/hyperlink
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
function HyperlinkController($scope) {
|
|
||||||
this.$scope = $scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Function to analyze the location in which to open the hyperlink
|
|
||||||
@returns true if the hyperlink is chosen to open in a different tab, false if the same tab
|
|
||||||
**/
|
|
||||||
HyperlinkController.prototype.openNewTab = function () {
|
|
||||||
if (this.$scope.domainObject.getModel().openNewTab === "thisTab") {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**Function to specify the format in which the hyperlink should be created
|
|
||||||
@returns true if the hyperlink is chosen to be created as a button, false if a link
|
|
||||||
**/
|
|
||||||
HyperlinkController.prototype.isButton = function () {
|
|
||||||
if (this.$scope.domainObject.getModel().displayFormat === "link") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return HyperlinkController;
|
|
||||||
}
|
|
||||||
|
|
||||||
);
|
|
@ -1,89 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2009-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../src/HyperlinkController"],
|
|
||||||
function (HyperlinkController) {
|
|
||||||
|
|
||||||
describe("The controller for hyperlinks", function () {
|
|
||||||
var domainObject,
|
|
||||||
controller,
|
|
||||||
scope;
|
|
||||||
beforeEach(function () {
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["domainObject"]
|
|
||||||
);
|
|
||||||
domainObject = jasmine.createSpyObj(
|
|
||||||
"domainObject",
|
|
||||||
["getModel"]
|
|
||||||
);
|
|
||||||
scope.domainObject = domainObject;
|
|
||||||
controller = new HyperlinkController(scope);
|
|
||||||
});
|
|
||||||
it("knows when it should open a new tab", function () {
|
|
||||||
scope.domainObject.getModel.and.returnValue({
|
|
||||||
"displayFormat": "link",
|
|
||||||
"openNewTab": "newTab",
|
|
||||||
"showTitle": false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
controller = new HyperlinkController(scope);
|
|
||||||
expect(controller.openNewTab())
|
|
||||||
.toBe(true);
|
|
||||||
});
|
|
||||||
it("knows when it is a button", function () {
|
|
||||||
scope.domainObject.getModel.and.returnValue({
|
|
||||||
"displayFormat": "button",
|
|
||||||
"openNewTab": "thisTab",
|
|
||||||
"showTitle": false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
controller = new HyperlinkController(scope);
|
|
||||||
expect(controller.isButton())
|
|
||||||
.toEqual(true);
|
|
||||||
});
|
|
||||||
it("knows when it should open in the same tab", function () {
|
|
||||||
scope.domainObject.getModel.and.returnValue({
|
|
||||||
"displayFormat": "link",
|
|
||||||
"openNewTab": "thisTab",
|
|
||||||
"showTitle": false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
controller = new HyperlinkController(scope);
|
|
||||||
expect(controller.openNewTab())
|
|
||||||
.toBe(false);
|
|
||||||
});
|
|
||||||
it("knows when it is a link", function () {
|
|
||||||
scope.domainObject.getModel.and.returnValue({
|
|
||||||
"displayFormat": "link",
|
|
||||||
"openNewTab": "thisTab",
|
|
||||||
"showTitle": false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
controller = new HyperlinkController(scope);
|
|
||||||
expect(controller.openNewTab())
|
|
||||||
.toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,70 +0,0 @@
|
|||||||
This bundle provides the Timeline domain object type, as well
|
|
||||||
as other associated domain object types and relevant views.
|
|
||||||
|
|
||||||
# Implementation notes
|
|
||||||
|
|
||||||
## Model Properties
|
|
||||||
|
|
||||||
The properties below record properties relevant to using and
|
|
||||||
understanding timelines based on their JSON representation.
|
|
||||||
Additional common properties, such as `modified`
|
|
||||||
or `persisted` timestamps, may also be present.
|
|
||||||
|
|
||||||
### Timeline Model
|
|
||||||
|
|
||||||
A timeline's model looks like:
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"type": "timeline",
|
|
||||||
"start": {
|
|
||||||
"timestamp": <number> (milliseconds since epoch),
|
|
||||||
"epoch": <string> (currently, always "SET")
|
|
||||||
},
|
|
||||||
"capacity": <number> (optional; battery capacity in watt-hours)
|
|
||||||
"composition": <string[]> (array of identifiers for contained objects)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The identifiers in a timeline's `composition` field should refer to
|
|
||||||
other Timeline objects, or to Activity objects.
|
|
||||||
|
|
||||||
### Activity Model
|
|
||||||
|
|
||||||
An activity's model looks like:
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"type": "activity",
|
|
||||||
"start": {
|
|
||||||
"timestamp": <number> (milliseconds since epoch),
|
|
||||||
"epoch": <string> (currently, always "SET")
|
|
||||||
},
|
|
||||||
"duration": {
|
|
||||||
"timestamp": <number> (duration of this activity, in milliseconds)
|
|
||||||
"epoch": "SET" (this is ignored)
|
|
||||||
},
|
|
||||||
"relationships": {
|
|
||||||
"modes": <string[]> (array of applicable Activity Mode ids)
|
|
||||||
},
|
|
||||||
"link": <string> (optional; URL linking to associated external resource)
|
|
||||||
"composition": <string[]> (array of identifiers for contained objects)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The identifiers in a timeline's `composition` field should only refer to
|
|
||||||
other Activity objects.
|
|
||||||
|
|
||||||
### Activity Mode Model
|
|
||||||
|
|
||||||
An activity mode's model looks like:
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"type": "mode",
|
|
||||||
"resources": {
|
|
||||||
"comms": <number> (communications utilization, in Kbps)
|
|
||||||
"power": <number> (power utilization, in watts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,10 +0,0 @@
|
|||||||
<div>
|
|
||||||
Timeline, Activity and Activity Mode objects have been deprecated and will no longer be supported.
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Please open an issue in the
|
|
||||||
<a href="https://github.com/nasa/openmct/issues" target="_blank">
|
|
||||||
Open MCT Issue tracker
|
|
||||||
</a>
|
|
||||||
if you have any questions about the timeline plugin.
|
|
||||||
</div>
|
|
@ -47,7 +47,7 @@ define(
|
|||||||
* @param $interval Angular's $interval service
|
* @param $interval Angular's $interval service
|
||||||
* @param {string} space the name of the persistence space being served
|
* @param {string} space the name of the persistence space being served
|
||||||
* @param {string} root the root of the path to ElasticSearch
|
* @param {string} root the root of the path to ElasticSearch
|
||||||
* @param {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) {
|
function ElasticPersistenceProvider($http, $q, space, root, path) {
|
||||||
this.spaces = [space];
|
this.spaces = [space];
|
||||||
|
@ -122,6 +122,7 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.destroy = this.destroy.bind(this);
|
||||||
/**
|
/**
|
||||||
* Tracks current selection state of the application.
|
* Tracks current selection state of the application.
|
||||||
* @private
|
* @private
|
||||||
@ -262,7 +263,7 @@ define([
|
|||||||
// Plugins that are installed by default
|
// Plugins that are installed by default
|
||||||
|
|
||||||
this.install(this.plugins.Plot());
|
this.install(this.plugins.Plot());
|
||||||
this.install(this.plugins.TelemetryTable());
|
this.install(this.plugins.TelemetryTable.default());
|
||||||
this.install(PreviewPlugin.default());
|
this.install(PreviewPlugin.default());
|
||||||
this.install(LegacyIndicatorsPlugin());
|
this.install(LegacyIndicatorsPlugin());
|
||||||
this.install(LicensesPlugin.default());
|
this.install(LicensesPlugin.default());
|
||||||
@ -283,8 +284,10 @@ define([
|
|||||||
this.install(this.plugins.NotificationIndicator());
|
this.install(this.plugins.NotificationIndicator());
|
||||||
this.install(this.plugins.NewFolderAction());
|
this.install(this.plugins.NewFolderAction());
|
||||||
this.install(this.plugins.ViewDatumAction());
|
this.install(this.plugins.ViewDatumAction());
|
||||||
|
this.install(this.plugins.ViewLargeAction());
|
||||||
this.install(this.plugins.ObjectInterceptors());
|
this.install(this.plugins.ObjectInterceptors());
|
||||||
this.install(this.plugins.NonEditableFolder());
|
this.install(this.plugins.NonEditableFolder());
|
||||||
|
this.install(this.plugins.DeviceClassifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
@ -434,6 +437,8 @@ define([
|
|||||||
Browse(this);
|
Browse(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', this.destroy);
|
||||||
|
|
||||||
this.router.start();
|
this.router.start();
|
||||||
this.emit('start');
|
this.emit('start');
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@ -457,6 +462,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
MCT.prototype.destroy = function () {
|
MCT.prototype.destroy = function () {
|
||||||
|
window.removeEventListener('beforeunload', this.destroy);
|
||||||
this.emit('destroy');
|
this.emit('destroy');
|
||||||
this.router.destroy();
|
this.router.destroy();
|
||||||
};
|
};
|
||||||
|
@ -36,8 +36,7 @@ define([
|
|||||||
'./views/installLegacyViews',
|
'./views/installLegacyViews',
|
||||||
'./policies/LegacyCompositionPolicyAdapter',
|
'./policies/LegacyCompositionPolicyAdapter',
|
||||||
'./actions/LegacyActionAdapter',
|
'./actions/LegacyActionAdapter',
|
||||||
'./services/LegacyPersistenceAdapter',
|
'./services/LegacyPersistenceAdapter'
|
||||||
'./services/ExportImageService'
|
|
||||||
], function (
|
], function (
|
||||||
ActionDialogDecorator,
|
ActionDialogDecorator,
|
||||||
AdapterCapability,
|
AdapterCapability,
|
||||||
@ -54,8 +53,7 @@ define([
|
|||||||
installLegacyViews,
|
installLegacyViews,
|
||||||
legacyCompositionPolicyAdapter,
|
legacyCompositionPolicyAdapter,
|
||||||
LegacyActionAdapter,
|
LegacyActionAdapter,
|
||||||
LegacyPersistenceAdapter,
|
LegacyPersistenceAdapter
|
||||||
ExportImageService
|
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
name: 'src/adapter',
|
name: 'src/adapter',
|
||||||
@ -84,13 +82,6 @@ define([
|
|||||||
"identifierService",
|
"identifierService",
|
||||||
"cacheService"
|
"cacheService"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "exportImageService",
|
|
||||||
"implementation": ExportImageService,
|
|
||||||
"depends": [
|
|
||||||
"dialogService"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
components: [
|
components: [
|
||||||
|
@ -173,10 +173,11 @@ define([
|
|||||||
const limitEvaluator = oldObject.getCapability("limit");
|
const limitEvaluator = oldObject.getCapability("limit");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
limits: function () {
|
limits: () => {
|
||||||
return limitEvaluator.limits();
|
return limitEvaluator.limits.then !== undefined
|
||||||
|
? limitEvaluator.limits()
|
||||||
|
: Promise.resolve(limitEvaluator.limits());
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,218 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module defining ExportImageService. Created by hudsonfoo on 09/02/16
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"html2canvas",
|
|
||||||
"saveAs"
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
html2canvas,
|
|
||||||
{ saveAs }
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The export image service will export any HTML node to
|
|
||||||
* JPG, or PNG.
|
|
||||||
* @param {object} dialogService
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ExportImageService(dialogService) {
|
|
||||||
this.dialogService = dialogService;
|
|
||||||
this.exportCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an HTML element into a PNG or JPG Blob.
|
|
||||||
* @private
|
|
||||||
* @param {node} element that will be converted to an image
|
|
||||||
* @param {object} options Image options.
|
|
||||||
* @returns {promise}
|
|
||||||
*/
|
|
||||||
ExportImageService.prototype.renderElement = function (element, {imageType, className, thumbnailSize}) {
|
|
||||||
const self = this;
|
|
||||||
const dialogService = this.dialogService;
|
|
||||||
const dialog = dialogService.showBlockingMessage({
|
|
||||||
title: "Capturing...",
|
|
||||||
hint: "Capturing an image",
|
|
||||||
unknownProgress: true,
|
|
||||||
severity: "info",
|
|
||||||
delay: true
|
|
||||||
});
|
|
||||||
|
|
||||||
let mimeType = "image/png";
|
|
||||||
if (imageType === "jpg") {
|
|
||||||
mimeType = "image/jpeg";
|
|
||||||
}
|
|
||||||
|
|
||||||
let exportId = undefined;
|
|
||||||
let oldId = undefined;
|
|
||||||
if (className) {
|
|
||||||
exportId = 'export-element-' + this.exportCount;
|
|
||||||
this.exportCount++;
|
|
||||||
oldId = element.id;
|
|
||||||
element.id = exportId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html2canvas(element, {
|
|
||||||
onclone: function (document) {
|
|
||||||
if (className) {
|
|
||||||
const clonedElement = document.getElementById(exportId);
|
|
||||||
clonedElement.classList.add(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
element.id = oldId;
|
|
||||||
},
|
|
||||||
removeContainer: true // Set to false to debug what html2canvas renders
|
|
||||||
}).then(function (canvas) {
|
|
||||||
dialog.dismiss();
|
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
if (thumbnailSize) {
|
|
||||||
const thumbnail = self.getThumbnail(canvas, mimeType, thumbnailSize);
|
|
||||||
|
|
||||||
return canvas.toBlob(blob => resolve({
|
|
||||||
blob,
|
|
||||||
thumbnail
|
|
||||||
}), mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return canvas.toBlob(blob => resolve({ blob }), mimeType);
|
|
||||||
});
|
|
||||||
}, function (error) {
|
|
||||||
console.log('error capturing image', error);
|
|
||||||
dialog.dismiss();
|
|
||||||
const errorDialog = dialogService.showBlockingMessage({
|
|
||||||
title: "Error capturing image",
|
|
||||||
severity: "error",
|
|
||||||
hint: "Image was not captured successfully!",
|
|
||||||
options: [{
|
|
||||||
label: "OK",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
ExportImageService.prototype.getThumbnail = function (canvas, mimeType, size) {
|
|
||||||
const thumbnailCanvas = document.createElement('canvas');
|
|
||||||
thumbnailCanvas.setAttribute('width', size.width);
|
|
||||||
thumbnailCanvas.setAttribute('height', size.height);
|
|
||||||
const ctx = thumbnailCanvas.getContext('2d');
|
|
||||||
ctx.globalCompositeOperation = "copy";
|
|
||||||
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, size.width, size.height);
|
|
||||||
|
|
||||||
return thumbnailCanvas.toDataURL(mimeType);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a screenshot of a DOM node and exports to JPG.
|
|
||||||
* @param {node} element to be exported
|
|
||||||
* @param {string} filename the exported image
|
|
||||||
* @param {string} className to be added to element before capturing (optional)
|
|
||||||
* @returns {promise}
|
|
||||||
*/
|
|
||||||
ExportImageService.prototype.exportJPG = function (element, filename, className) {
|
|
||||||
const processedFilename = replaceDotsWithUnderscores(filename);
|
|
||||||
|
|
||||||
return this.renderElement(element, {
|
|
||||||
imageType: 'jpg',
|
|
||||||
className
|
|
||||||
})
|
|
||||||
.then(function (img) {
|
|
||||||
saveAs(img.blob, processedFilename);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a screenshot of a DOM node and exports to PNG.
|
|
||||||
* @param {node} element to be exported
|
|
||||||
* @param {string} filename the exported image
|
|
||||||
* @param {string} className to be added to element before capturing (optional)
|
|
||||||
* @returns {promise}
|
|
||||||
*/
|
|
||||||
ExportImageService.prototype.exportPNG = function (element, filename, className) {
|
|
||||||
const processedFilename = replaceDotsWithUnderscores(filename);
|
|
||||||
|
|
||||||
return this.renderElement(element, {
|
|
||||||
imageType: 'png',
|
|
||||||
className
|
|
||||||
})
|
|
||||||
.then(function (img) {
|
|
||||||
saveAs(img.blob, processedFilename);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a screenshot of a DOM node in PNG format.
|
|
||||||
* @param {node} element to be exported
|
|
||||||
* @param {string} filename the exported image
|
|
||||||
* @returns {promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
ExportImageService.prototype.exportPNGtoSRC = function (element, options) {
|
|
||||||
|
|
||||||
return this.renderElement(element, {
|
|
||||||
imageType: 'png',
|
|
||||||
...options
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function replaceDotsWithUnderscores(filename) {
|
|
||||||
const regex = /\./gi;
|
|
||||||
|
|
||||||
return filename.replace(regex, '_');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* canvas.toBlob() not supported in IE < 10, Opera, and Safari. This polyfill
|
|
||||||
* implements the method in browsers that would not otherwise support it.
|
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
|
|
||||||
*/
|
|
||||||
function polyfillToBlob() {
|
|
||||||
if (!HTMLCanvasElement.prototype.toBlob) {
|
|
||||||
Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", {
|
|
||||||
value: function (callback, mimeType, quality) {
|
|
||||||
const binStr = atob(this.toDataURL(mimeType, quality).split(',')[1]);
|
|
||||||
const len = binStr.length;
|
|
||||||
const arr = new Uint8Array(len);
|
|
||||||
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
arr[i] = binStr.charCodeAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(new Blob([arr], {type: mimeType || "image/png"}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
polyfillToBlob();
|
|
||||||
|
|
||||||
return ExportImageService;
|
|
||||||
}
|
|
||||||
);
|
|
@ -46,8 +46,6 @@ class ActionCollection extends EventEmitter {
|
|||||||
this._observeObjectPath();
|
this._observeObjectPath();
|
||||||
this.openmct.editor.on('isEditing', this._updateActions);
|
this.openmct.editor.on('isEditing', this._updateActions);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._initializeActions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disable(actionKeys) {
|
disable(actionKeys) {
|
||||||
@ -156,19 +154,10 @@ class ActionCollection extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_initializeActions() {
|
|
||||||
Object.keys(this.applicableActions).forEach(key => {
|
|
||||||
this.applicableActions[key].callBack = () => {
|
|
||||||
return this.applicableActions[key].invoke(this.objectPath, this.view);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateActions() {
|
_updateActions() {
|
||||||
let newApplicableActions = this.openmct.actions._applicableActions(this.objectPath, this.view);
|
let newApplicableActions = this.openmct.actions._applicableActions(this.objectPath, this.view);
|
||||||
|
|
||||||
this.applicableActions = this._mergeOldAndNewActions(this.applicableActions, newApplicableActions);
|
this.applicableActions = this._mergeOldAndNewActions(this.applicableActions, newApplicableActions);
|
||||||
this._initializeActions();
|
|
||||||
this._update();
|
this._update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class ActionsAPI extends EventEmitter {
|
|||||||
this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json'];
|
this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json'];
|
||||||
|
|
||||||
this.register = this.register.bind(this);
|
this.register = this.register.bind(this);
|
||||||
this.get = this.get.bind(this);
|
this.getActionsCollection = this.getActionsCollection.bind(this);
|
||||||
this._applicableActions = this._applicableActions.bind(this);
|
this._applicableActions = this._applicableActions.bind(this);
|
||||||
this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this);
|
this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this);
|
||||||
}
|
}
|
||||||
@ -43,12 +43,14 @@ class ActionsAPI extends EventEmitter {
|
|||||||
this._allActions[actionDefinition.key] = actionDefinition;
|
this._allActions[actionDefinition.key] = actionDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(objectPath, view) {
|
getAction(key) {
|
||||||
if (view) {
|
return this._allActions[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
getActionsCollection(objectPath, view) {
|
||||||
|
if (view) {
|
||||||
return this._getCachedActionCollection(objectPath, view) || this._newActionCollection(objectPath, view, true);
|
return this._getCachedActionCollection(objectPath, view) || this._newActionCollection(objectPath, view, true);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
return this._newActionCollection(objectPath, view, true);
|
return this._newActionCollection(objectPath, view, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,25 +59,24 @@ class ActionsAPI extends EventEmitter {
|
|||||||
this._groupOrder = groupArray;
|
this._groupOrder = groupArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
_get(objectPath, view) {
|
|
||||||
let actionCollection = this._newActionCollection(objectPath, view);
|
|
||||||
|
|
||||||
this._actionCollections.set(view, actionCollection);
|
|
||||||
actionCollection.on('destroy', this._updateCachedActionCollections);
|
|
||||||
|
|
||||||
return actionCollection;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getCachedActionCollection(objectPath, view) {
|
_getCachedActionCollection(objectPath, view) {
|
||||||
let cachedActionCollection = this._actionCollections.get(view);
|
return this._actionCollections.get(view);
|
||||||
|
|
||||||
return cachedActionCollection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_newActionCollection(objectPath, view, skipEnvironmentObservers) {
|
_newActionCollection(objectPath, view, skipEnvironmentObservers) {
|
||||||
let applicableActions = this._applicableActions(objectPath, view);
|
let applicableActions = this._applicableActions(objectPath, view);
|
||||||
|
|
||||||
return new ActionCollection(applicableActions, objectPath, view, this._openmct, skipEnvironmentObservers);
|
const actionCollection = new ActionCollection(applicableActions, objectPath, view, this._openmct, skipEnvironmentObservers);
|
||||||
|
if (view) {
|
||||||
|
this._cacheActionCollection(view, actionCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cacheActionCollection(view, actionCollection) {
|
||||||
|
this._actionCollections.set(view, actionCollection);
|
||||||
|
actionCollection.on('destroy', this._updateCachedActionCollections);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateCachedActionCollections(key) {
|
_updateCachedActionCollections(key) {
|
||||||
|
@ -106,7 +106,7 @@ describe('The Actions API', () => {
|
|||||||
it("adds action to ActionsAPI", () => {
|
it("adds action to ActionsAPI", () => {
|
||||||
actionsAPI.register(mockAction);
|
actionsAPI.register(mockAction);
|
||||||
|
|
||||||
let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
|
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
|
||||||
let action = actionCollection.getActionsObject()[mockAction.key];
|
let action = actionCollection.getActionsObject()[mockAction.key];
|
||||||
|
|
||||||
expect(action.key).toEqual(mockAction.key);
|
expect(action.key).toEqual(mockAction.key);
|
||||||
@ -121,21 +121,21 @@ describe('The Actions API', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns an ActionCollection when invoked with an objectPath only", () => {
|
it("returns an ActionCollection when invoked with an objectPath only", () => {
|
||||||
let actionCollection = actionsAPI.get(mockObjectPath);
|
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath);
|
||||||
let instanceOfActionCollection = actionCollection instanceof ActionCollection;
|
let instanceOfActionCollection = actionCollection instanceof ActionCollection;
|
||||||
|
|
||||||
expect(instanceOfActionCollection).toBeTrue();
|
expect(instanceOfActionCollection).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns an ActionCollection when invoked with an objectPath and view", () => {
|
it("returns an ActionCollection when invoked with an objectPath and view", () => {
|
||||||
let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
|
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
|
||||||
let instanceOfActionCollection = actionCollection instanceof ActionCollection;
|
let instanceOfActionCollection = actionCollection instanceof ActionCollection;
|
||||||
|
|
||||||
expect(instanceOfActionCollection).toBeTrue();
|
expect(instanceOfActionCollection).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns relevant actions when invoked with objectPath only", () => {
|
it("returns relevant actions when invoked with objectPath only", () => {
|
||||||
let actionCollection = actionsAPI.get(mockObjectPath);
|
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath);
|
||||||
let action = actionCollection.getActionsObject()[mockObjectPathAction.key];
|
let action = actionCollection.getActionsObject()[mockObjectPathAction.key];
|
||||||
|
|
||||||
expect(action.key).toEqual(mockObjectPathAction.key);
|
expect(action.key).toEqual(mockObjectPathAction.key);
|
||||||
@ -143,7 +143,7 @@ describe('The Actions API', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns relevant actions when invoked with objectPath and view", () => {
|
it("returns relevant actions when invoked with objectPath and view", () => {
|
||||||
let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
|
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
|
||||||
let action = actionCollection.getActionsObject()[mockAction.key];
|
let action = actionCollection.getActionsObject()[mockAction.key];
|
||||||
|
|
||||||
expect(action.key).toEqual(mockAction.key);
|
expect(action.key).toEqual(mockAction.key);
|
||||||
|
@ -37,7 +37,7 @@ import Menu, { MENU_PLACEMENT } from './menu.js';
|
|||||||
* @property {Boolean} isDisabled adds disable class if true
|
* @property {Boolean} isDisabled adds disable class if true
|
||||||
* @property {String} name Menu item text
|
* @property {String} name Menu item text
|
||||||
* @property {String} description Menu item description
|
* @property {String} description Menu item description
|
||||||
* @property {Function} callBack callback function: invoked when item is clicked
|
* @property {Function} onItemClicked callback function: invoked when item is clicked
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,12 +66,27 @@ class MenuAPI {
|
|||||||
* @param {Array.<Action>|Array.<Array.<Action>>} actions collection of actions{@link Action} or collection of groups of actions {@link Action}
|
* @param {Array.<Action>|Array.<Array.<Action>>} actions collection of actions{@link Action} or collection of groups of actions {@link Action}
|
||||||
* @param {MenuOptions} [menuOptions] [Optional] The {@link MenuOptions} options for Menu
|
* @param {MenuOptions} [menuOptions] [Optional] The {@link MenuOptions} options for Menu
|
||||||
*/
|
*/
|
||||||
showMenu(x, y, actions, menuOptions) {
|
showMenu(x, y, items, menuOptions) {
|
||||||
this._createMenuComponent(x, y, actions, menuOptions);
|
this._createMenuComponent(x, y, items, menuOptions);
|
||||||
|
|
||||||
this.menuComponent.showMenu();
|
this.menuComponent.showMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actionsToMenuItems(actions, objectPath, view) {
|
||||||
|
return actions.map(action => {
|
||||||
|
const isActionGroup = Array.isArray(action);
|
||||||
|
if (isActionGroup) {
|
||||||
|
action = this.actionsToMenuItems(action, objectPath, view);
|
||||||
|
} else {
|
||||||
|
action.onItemClicked = () => {
|
||||||
|
action.invoke(objectPath, view);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show popup menu with description of item on hover
|
* Show popup menu with description of item on hover
|
||||||
* @param {number} x x-coordinates for popup
|
* @param {number} x x-coordinates for popup
|
||||||
|
@ -57,7 +57,7 @@ describe ('The Menu API', () => {
|
|||||||
name: 'Test Action 1',
|
name: 'Test Action 1',
|
||||||
cssClass: 'icon-clock',
|
cssClass: 'icon-clock',
|
||||||
description: 'This is a test action',
|
description: 'This is a test action',
|
||||||
callBack: () => {
|
onItemClicked: () => {
|
||||||
result = 'Test Action 1 Invoked';
|
result = 'Test Action 1 Invoked';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -66,7 +66,7 @@ describe ('The Menu API', () => {
|
|||||||
name: 'Test Action 2',
|
name: 'Test Action 2',
|
||||||
cssClass: 'icon-clock',
|
cssClass: 'icon-clock',
|
||||||
description: 'This is a test action',
|
description: 'This is a test action',
|
||||||
callBack: () => {
|
onItemClicked: () => {
|
||||||
result = 'Test Action 2 Invoked';
|
result = 'Test Action 2 Invoked';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
:key="action.name"
|
:key="action.name"
|
||||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||||
:title="action.description"
|
:title="action.description"
|
||||||
@click="action.callBack"
|
@click="action.onItemClicked"
|
||||||
>
|
>
|
||||||
{{ action.name }}
|
{{ action.name }}
|
||||||
</li>
|
</li>
|
||||||
@ -36,7 +36,7 @@
|
|||||||
:key="action.name"
|
:key="action.name"
|
||||||
:class="action.cssClass"
|
:class="action.cssClass"
|
||||||
:title="action.description"
|
:title="action.description"
|
||||||
@click="action.callBack"
|
@click="action.onItemClicked"
|
||||||
>
|
>
|
||||||
{{ action.name }}
|
{{ action.name }}
|
||||||
</li>
|
</li>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
:key="action.name"
|
:key="action.name"
|
||||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||||
:title="action.description"
|
:title="action.description"
|
||||||
@click="action.callBack"
|
@click="action.onItemClicked"
|
||||||
@mouseover="toggleItemDescription(action)"
|
@mouseover="toggleItemDescription(action)"
|
||||||
@mouseleave="toggleItemDescription()"
|
@mouseleave="toggleItemDescription()"
|
||||||
>
|
>
|
||||||
@ -42,7 +42,7 @@
|
|||||||
:key="action.name"
|
:key="action.name"
|
||||||
:class="action.cssClass"
|
:class="action.cssClass"
|
||||||
:title="action.description"
|
:title="action.description"
|
||||||
@click="action.callBack"
|
@click="action.onItemClicked"
|
||||||
@mouseover="toggleItemDescription(action)"
|
@mouseover="toggleItemDescription(action)"
|
||||||
@mouseleave="toggleItemDescription()"
|
@mouseleave="toggleItemDescription()"
|
||||||
>
|
>
|
||||||
|
@ -71,12 +71,12 @@ class Menu extends EventEmitter {
|
|||||||
|
|
||||||
showMenu() {
|
showMenu() {
|
||||||
this.component = new Vue({
|
this.component = new Vue({
|
||||||
provide: {
|
|
||||||
options: this.options
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
MenuComponent
|
MenuComponent
|
||||||
},
|
},
|
||||||
|
provide: {
|
||||||
|
options: this.options
|
||||||
|
},
|
||||||
template: '<menu-component />'
|
template: '<menu-component />'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,12 +85,12 @@ class Menu extends EventEmitter {
|
|||||||
|
|
||||||
showSuperMenu() {
|
showSuperMenu() {
|
||||||
this.component = new Vue({
|
this.component = new Vue({
|
||||||
provide: {
|
|
||||||
options: this.options
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
SuperMenuComponent
|
SuperMenuComponent
|
||||||
},
|
},
|
||||||
|
provide: {
|
||||||
|
options: this.options
|
||||||
|
},
|
||||||
template: '<super-menu-component />'
|
template: '<super-menu-component />'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ import EventEmitter from 'EventEmitter';
|
|||||||
*
|
*
|
||||||
* @typedef {object} NotificationModel
|
* @typedef {object} NotificationModel
|
||||||
* @property {string} message The message to be displayed by the notification
|
* @property {string} message The message to be displayed by the notification
|
||||||
* @property {number | 'unknown'} [progress] The 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'.
|
* with the string literal 'unknown'.
|
||||||
* @property {string} [progressText] A message conveying progress of some ongoing task.
|
* @property {string} [progressText] A message conveying progress of some ongoing task.
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* Present an alert to the user.
|
* Present an alert to the user.
|
||||||
* @param {string} message The message to display to the user.
|
* @param {string} message The message to display to the user.
|
||||||
* @param {Object} [options] object with following properties
|
* @param {Object} [options] object with following properties
|
||||||
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
* autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
|
||||||
* link: {Object} Add a link to notifications for navigation
|
* link: {Object} Add a link to notifications for navigation
|
||||||
* onClick: callback function
|
* onClick: callback function
|
||||||
* cssClass: css class name to add style on link
|
* cssClass: css class name to add style on link
|
||||||
@ -119,7 +119,7 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* Present an error message to the user
|
* Present an error message to the user
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
* @param {Object} [options] object with following properties
|
* @param {Object} [options] object with following properties
|
||||||
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
* autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
|
||||||
* link: {Object} Add a link to notifications for navigation
|
* link: {Object} Add a link to notifications for navigation
|
||||||
* onClick: callback function
|
* onClick: callback function
|
||||||
* cssClass: css class name to add style on link
|
* cssClass: css class name to add style on link
|
||||||
|
@ -60,7 +60,7 @@ class OverlayAPI {
|
|||||||
* A description of option properties that can be passed into the overlay
|
* A description of option properties that can be passed into the overlay
|
||||||
* @typedef options
|
* @typedef options
|
||||||
* @property {object} element DOMElement that is to be inserted/shown on the overlay
|
* @property {object} element DOMElement that is to be inserted/shown on the overlay
|
||||||
* @property {string} size 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 {array} buttons optional button objects with label and callback properties
|
||||||
* @property {function} onDestroy callback to be called when overlay is destroyed
|
* @property {function} onDestroy callback to be called when overlay is destroyed
|
||||||
* @property {boolean} dismissable allow user to dismiss overlay by using esc, and clicking away
|
* @property {boolean} dismissable allow user to dismiss overlay by using esc, and clicking away
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
></button>
|
></button>
|
||||||
<div
|
<div
|
||||||
ref="element"
|
ref="element"
|
||||||
class="c-overlay__contents"
|
class="c-overlay__contents js-notebook-snapshot-item-wrapper"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
const { TelemetryCollection } = require("./TelemetryCollection");
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'../../plugins/displayLayout/CustomStringFormatter',
|
'../../plugins/displayLayout/CustomStringFormatter',
|
||||||
'./TelemetryMetadataManager',
|
'./TelemetryMetadataManager',
|
||||||
@ -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.
|
* Request historical telemetry for a domain object.
|
||||||
* The `options` argument allows you to specify filters
|
* The `options` argument allows you to specify filters
|
||||||
|
File diff suppressed because it is too large
Load Diff
388
src/api/telemetry/TelemetryCollection.js
Normal file
388
src/api/telemetry/TelemetryCollection.js
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
|
||||||
|
const ERRORS = {
|
||||||
|
TIMESYSTEM_KEY: 'All telemetry metadata must have a telemetry value with a key that matches the key of the active time system.',
|
||||||
|
LOADED: 'Telemetry Collection has already been loaded.'
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Class representing a Telemetry Collection. */
|
||||||
|
|
||||||
|
export class TelemetryCollection extends EventEmitter {
|
||||||
|
/**
|
||||||
|
* Creates a Telemetry Collection
|
||||||
|
*
|
||||||
|
* @param {object} openmct - Openm MCT
|
||||||
|
* @param {object} domainObject - Domain Object to user for telemetry collection
|
||||||
|
* @param {object} options - Any options passed in for request/subscribe
|
||||||
|
*/
|
||||||
|
constructor(openmct, domainObject, options) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.loaded = false;
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.boundedTelemetry = [];
|
||||||
|
this.futureBuffer = [];
|
||||||
|
this.parseTime = undefined;
|
||||||
|
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||||
|
this.unsubscribe = undefined;
|
||||||
|
this.historicalProvider = undefined;
|
||||||
|
this.options = options;
|
||||||
|
this.pageState = undefined;
|
||||||
|
this.lastBounds = undefined;
|
||||||
|
this.requestAbort = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will start the requests for historical and realtime data,
|
||||||
|
* as well as setting up initial values and watchers
|
||||||
|
*/
|
||||||
|
load() {
|
||||||
|
if (this.loaded) {
|
||||||
|
this._error(ERRORS.LOADED);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._timeSystem(this.openmct.time.timeSystem());
|
||||||
|
this.lastBounds = this.openmct.time.bounds();
|
||||||
|
|
||||||
|
this._watchBounds();
|
||||||
|
this._watchTimeSystem();
|
||||||
|
|
||||||
|
this._initiateHistoricalRequests();
|
||||||
|
this._initiateSubscriptionTelemetry();
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* can/should be called by the requester of the telemetry collection
|
||||||
|
* to remove any listeners
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
if (this.requestAbort) {
|
||||||
|
this.requestAbort.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._unwatchBounds();
|
||||||
|
this._unwatchTimeSystem();
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.removeAllListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will start the requests for historical and realtime data,
|
||||||
|
* as well as setting up initial values and watchers
|
||||||
|
*/
|
||||||
|
getAll() {
|
||||||
|
return this.boundedTelemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the telemetry collection for historical requests,
|
||||||
|
* this uses the "standardizeRequestOptions" from Telemetry API
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initiateHistoricalRequests() {
|
||||||
|
this.openmct.telemetry.standardizeRequestOptions(this.options);
|
||||||
|
this.historicalProvider = this.openmct.telemetry.
|
||||||
|
findRequestProvider(this.domainObject, this.options);
|
||||||
|
|
||||||
|
this._requestHistoricalTelemetry();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* If a historical provider exists, then historical requests will be made
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _requestHistoricalTelemetry() {
|
||||||
|
if (!this.historicalProvider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let historicalData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.requestAbort = new AbortController();
|
||||||
|
this.options.signal = this.requestAbort.signal;
|
||||||
|
historicalData = await this.historicalProvider.request(this.domainObject, this.options);
|
||||||
|
this.requestAbort = undefined;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error requesting telemetry data...');
|
||||||
|
this.requestAbort = undefined;
|
||||||
|
this._error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._processNewTelemetry(historicalData);
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This uses the built in subscription function from Telemetry API
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initiateSubscriptionTelemetry() {
|
||||||
|
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unsubscribe = this.openmct.telemetry
|
||||||
|
.subscribe(
|
||||||
|
this.domainObject,
|
||||||
|
datum => this._processNewTelemetry(datum),
|
||||||
|
this.options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter any new telemetry (add/page, historical, subscription) based on
|
||||||
|
* time bounds and dupes
|
||||||
|
*
|
||||||
|
* @param {(Object|Object[])} telemetryData - telemetry data object or
|
||||||
|
* array of telemetry data objects
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_processNewTelemetry(telemetryData) {
|
||||||
|
let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData];
|
||||||
|
let parsedValue;
|
||||||
|
let beforeStartOfBounds;
|
||||||
|
let afterEndOfBounds;
|
||||||
|
let added = [];
|
||||||
|
|
||||||
|
for (let datum of data) {
|
||||||
|
parsedValue = this.parseTime(datum);
|
||||||
|
beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
||||||
|
afterEndOfBounds = parsedValue > this.lastBounds.end;
|
||||||
|
|
||||||
|
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
||||||
|
let isDuplicate = false;
|
||||||
|
let startIndex = this._sortedIndex(datum);
|
||||||
|
let endIndex = undefined;
|
||||||
|
|
||||||
|
// dupe check
|
||||||
|
if (startIndex !== this.boundedTelemetry.length) {
|
||||||
|
endIndex = _.sortedLastIndexBy(
|
||||||
|
this.boundedTelemetry,
|
||||||
|
datum,
|
||||||
|
boundedDatum => this.parseTime(boundedDatum)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (endIndex > startIndex) {
|
||||||
|
let potentialDupes = this.boundedTelemetry.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
isDuplicate = potentialDupes.some(_.isEqual.bind(undefined, datum));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDuplicate) {
|
||||||
|
let index = endIndex || startIndex;
|
||||||
|
|
||||||
|
this.boundedTelemetry.splice(index, 0, datum);
|
||||||
|
added.push(datum);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (afterEndOfBounds) {
|
||||||
|
this.futureBuffer.push(datum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added.length) {
|
||||||
|
this.emit('add', added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the correct insertion point for the given telemetry datum.
|
||||||
|
* Leverages lodash's `sortedIndexBy` function which implements a binary search.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_sortedIndex(datum) {
|
||||||
|
if (this.boundedTelemetry.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsedValue = this.parseTime(datum);
|
||||||
|
let lastValue = this.parseTime(this.boundedTelemetry[this.boundedTelemetry.length - 1]);
|
||||||
|
|
||||||
|
if (parsedValue > lastValue || parsedValue === lastValue) {
|
||||||
|
return this.boundedTelemetry.length;
|
||||||
|
} else {
|
||||||
|
return _.sortedIndexBy(
|
||||||
|
this.boundedTelemetry,
|
||||||
|
datum,
|
||||||
|
boundedDatum => this.parseTime(boundedDatum)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when the start time, end time, or both have been updated.
|
||||||
|
* data could be added OR removed here we update the current
|
||||||
|
* bounded telemetry
|
||||||
|
*
|
||||||
|
* @param {TimeConductorBounds} bounds The newly updated bounds
|
||||||
|
* @param {boolean} [tick] `true` if the bounds update was due to
|
||||||
|
* a "tick" event (ie. was an automatic update), false otherwise.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_bounds(bounds, isTick) {
|
||||||
|
let startChanged = this.lastBounds.start !== bounds.start;
|
||||||
|
let endChanged = this.lastBounds.end !== bounds.end;
|
||||||
|
|
||||||
|
this.lastBounds = bounds;
|
||||||
|
|
||||||
|
if (isTick) {
|
||||||
|
// need to check futureBuffer and need to check
|
||||||
|
// if anything has fallen out of bounds
|
||||||
|
let startIndex = 0;
|
||||||
|
let endIndex = 0;
|
||||||
|
|
||||||
|
let discarded = [];
|
||||||
|
let added = [];
|
||||||
|
let testDatum = {};
|
||||||
|
|
||||||
|
if (startChanged) {
|
||||||
|
testDatum[this.timeKey] = bounds.start;
|
||||||
|
// Calculate the new index of the first item within the bounds
|
||||||
|
startIndex = _.sortedIndexBy(
|
||||||
|
this.boundedTelemetry,
|
||||||
|
testDatum,
|
||||||
|
datum => this.parseTime(datum)
|
||||||
|
);
|
||||||
|
discarded = this.boundedTelemetry.splice(0, startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endChanged) {
|
||||||
|
testDatum[this.timeKey] = bounds.end;
|
||||||
|
// Calculate the new index of the last item in bounds
|
||||||
|
endIndex = _.sortedLastIndexBy(
|
||||||
|
this.futureBuffer,
|
||||||
|
testDatum,
|
||||||
|
datum => this.parseTime(datum)
|
||||||
|
);
|
||||||
|
added = this.futureBuffer.splice(0, endIndex);
|
||||||
|
this.boundedTelemetry = [...this.boundedTelemetry, ...added];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discarded.length > 0) {
|
||||||
|
this.emit('remove', discarded);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added.length > 0) {
|
||||||
|
this.emit('add', added);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// user bounds change, reset
|
||||||
|
this._reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whenever the time system is updated need to update related values in
|
||||||
|
* the Telemetry Collection and reset the telemetry collection
|
||||||
|
*
|
||||||
|
* @param {TimeSystem} timeSystem - the value of the currently applied
|
||||||
|
* Time System
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_timeSystem(timeSystem) {
|
||||||
|
let domains = this.metadata.valuesForHints(['domain']);
|
||||||
|
let domain = domains.find((d) => d.key === timeSystem.key);
|
||||||
|
|
||||||
|
if (domain === undefined) {
|
||||||
|
this._error(ERRORS.TIMESYSTEM_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeKey is used to create a dummy datum used for sorting
|
||||||
|
this.timeKey = domain.source; // this defaults to key if no source is set
|
||||||
|
let metadataValue = this.metadata.value(timeSystem.key) || { format: timeSystem.key };
|
||||||
|
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||||
|
|
||||||
|
this.parseTime = (datum) => {
|
||||||
|
return valueFormatter.parse(datum);
|
||||||
|
};
|
||||||
|
|
||||||
|
this._reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the telemetry data of the collection, and re-request
|
||||||
|
* historical telemetry
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @todo handle subscriptions more granually
|
||||||
|
*/
|
||||||
|
_reset() {
|
||||||
|
this.boundedTelemetry = [];
|
||||||
|
this.futureBuffer = [];
|
||||||
|
|
||||||
|
this._requestHistoricalTelemetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds the _bounds callback to the 'bounds' timeAPI listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_watchBounds() {
|
||||||
|
this.openmct.time.on('bounds', this._bounds, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* removes the _bounds callback from the 'bounds' timeAPI listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_unwatchBounds() {
|
||||||
|
this.openmct.time.off('bounds', this._bounds, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds the _timeSystem callback to the 'timeSystem' timeAPI listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_watchTimeSystem() {
|
||||||
|
this.openmct.time.on('timeSystem', this._timeSystem, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* removes the _timeSystem callback from the 'timeSystem' timeAPI listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_unwatchTimeSystem() {
|
||||||
|
this.openmct.time.off('timeSystem', this._timeSystem, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* will throw a new Error, for passed in message
|
||||||
|
* @param {string} message Message describing the error
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_error(message) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
188
src/exporters/ImageExporter.js
Normal file
188
src/exporters/ImageExporter.js
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class defining an image exporter for JPG/PNG output.
|
||||||
|
* Originally created by hudsonfoo on 09/02/16
|
||||||
|
*/
|
||||||
|
|
||||||
|
function replaceDotsWithUnderscores(filename) {
|
||||||
|
const regex = /\./gi;
|
||||||
|
|
||||||
|
return filename.replace(regex, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
import {saveAs} from 'file-saver/FileSaver';
|
||||||
|
import html2canvas from 'html2canvas';
|
||||||
|
import uuid from 'uuid';
|
||||||
|
|
||||||
|
class ImageExporter {
|
||||||
|
constructor(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Converts an HTML element into a PNG or JPG Blob.
|
||||||
|
* @private
|
||||||
|
* @param {node} element that will be converted to an image
|
||||||
|
* @param {object} options Image options.
|
||||||
|
* @returns {promise}
|
||||||
|
*/
|
||||||
|
renderElement(element, { imageType, className, thumbnailSize }) {
|
||||||
|
const self = this;
|
||||||
|
const overlays = this.openmct.overlays;
|
||||||
|
const dialog = overlays.dialog({
|
||||||
|
iconClass: 'info',
|
||||||
|
message: 'Caputuring an image',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: 'Cancel',
|
||||||
|
emphasis: true,
|
||||||
|
callback: function () {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
let mimeType = 'image/png';
|
||||||
|
if (imageType === 'jpg') {
|
||||||
|
mimeType = 'image/jpeg';
|
||||||
|
}
|
||||||
|
|
||||||
|
let exportId = undefined;
|
||||||
|
let oldId = undefined;
|
||||||
|
if (className) {
|
||||||
|
const newUUID = uuid();
|
||||||
|
exportId = `$export-element-${newUUID}`;
|
||||||
|
oldId = element.id;
|
||||||
|
element.id = exportId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html2canvas(element, {
|
||||||
|
useCORS: true,
|
||||||
|
allowTaint: true,
|
||||||
|
logging: false,
|
||||||
|
onclone: function (document) {
|
||||||
|
if (className) {
|
||||||
|
const clonedElement = document.getElementById(exportId);
|
||||||
|
clonedElement.classList.add(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.id = oldId;
|
||||||
|
},
|
||||||
|
removeContainer: true // Set to false to debug what html2canvas renders
|
||||||
|
}).then(function (canvas) {
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
if (thumbnailSize) {
|
||||||
|
const thumbnail = self.getThumbnail(canvas, mimeType, thumbnailSize);
|
||||||
|
|
||||||
|
return canvas.toBlob(blob => resolve({
|
||||||
|
blob,
|
||||||
|
thumbnail
|
||||||
|
}), mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas.toBlob(blob => resolve({ blob }), mimeType);
|
||||||
|
});
|
||||||
|
}, function (error) {
|
||||||
|
console.log('error capturing image', error);
|
||||||
|
dialog.dismiss();
|
||||||
|
const errorDialog = overlays.dialog({
|
||||||
|
iconClass: 'error',
|
||||||
|
message: 'Image was not captured successfully!',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: "OK",
|
||||||
|
emphasis: true,
|
||||||
|
callback: function () {
|
||||||
|
errorDialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getThumbnail(canvas, mimeType, size) {
|
||||||
|
const thumbnailCanvas = document.createElement('canvas');
|
||||||
|
thumbnailCanvas.setAttribute('width', size.width);
|
||||||
|
thumbnailCanvas.setAttribute('height', size.height);
|
||||||
|
const ctx = thumbnailCanvas.getContext('2d');
|
||||||
|
ctx.globalCompositeOperation = "copy";
|
||||||
|
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, size.width, size.height);
|
||||||
|
|
||||||
|
return thumbnailCanvas.toDataURL(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a screenshot of a DOM node and exports to JPG.
|
||||||
|
* @param {node} element to be exported
|
||||||
|
* @param {string} filename the exported image
|
||||||
|
* @param {string} className to be added to element before capturing (optional)
|
||||||
|
* @returns {promise}
|
||||||
|
*/
|
||||||
|
async exportJPG(element, filename, className) {
|
||||||
|
const processedFilename = replaceDotsWithUnderscores(filename);
|
||||||
|
|
||||||
|
const img = await this.renderElement(element, {
|
||||||
|
imageType: 'jpg',
|
||||||
|
className
|
||||||
|
});
|
||||||
|
saveAs(img.blob, processedFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a screenshot of a DOM node and exports to PNG.
|
||||||
|
* @param {node} element to be exported
|
||||||
|
* @param {string} filename the exported image
|
||||||
|
* @param {string} className to be added to element before capturing (optional)
|
||||||
|
* @returns {promise}
|
||||||
|
*/
|
||||||
|
async exportPNG(element, filename, className) {
|
||||||
|
const processedFilename = replaceDotsWithUnderscores(filename);
|
||||||
|
|
||||||
|
const img = await this.renderElement(element, {
|
||||||
|
imageType: 'png',
|
||||||
|
className
|
||||||
|
});
|
||||||
|
saveAs(img.blob, processedFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a screenshot of a DOM node in PNG format.
|
||||||
|
* @param {node} element to be exported
|
||||||
|
* @param {string} filename the exported image
|
||||||
|
* @returns {promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
exportPNGtoSRC(element, options) {
|
||||||
|
return this.renderElement(element, {
|
||||||
|
imageType: 'png',
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageExporter;
|
||||||
|
|
58
src/exporters/ImageExporterSpec.js
Normal file
58
src/exporters/ImageExporterSpec.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import ImageExporter from './ImageExporter';
|
||||||
|
import { createOpenMct, resetApplicationState } from '../utils/testing';
|
||||||
|
|
||||||
|
describe('The Image Exporter', () => {
|
||||||
|
let openmct;
|
||||||
|
let imageExporter;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
openmct = createOpenMct();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("basic instatation", () => {
|
||||||
|
it("can be instatiated", () => {
|
||||||
|
imageExporter = new ImageExporter(openmct);
|
||||||
|
|
||||||
|
expect(imageExporter).not.toEqual(null);
|
||||||
|
});
|
||||||
|
it("can render an element to a blob", async () => {
|
||||||
|
const mockHeadElement = document.createElement("h1");
|
||||||
|
const mockTextNode = document.createTextNode('foo bar');
|
||||||
|
mockHeadElement.appendChild(mockTextNode);
|
||||||
|
document.body.appendChild(mockHeadElement);
|
||||||
|
imageExporter = new ImageExporter(openmct);
|
||||||
|
const returnedBlob = await imageExporter.renderElement(document.body, {
|
||||||
|
imageType: 'png'
|
||||||
|
});
|
||||||
|
expect(returnedBlob).not.toEqual(null);
|
||||||
|
expect(returnedBlob.blob).not.toEqual(null);
|
||||||
|
expect(returnedBlob.blob).toBeInstanceOf(Blob);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -38,8 +38,6 @@ const DEFAULTS = [
|
|||||||
'platform/exporters',
|
'platform/exporters',
|
||||||
'platform/telemetry',
|
'platform/telemetry',
|
||||||
'platform/features/clock',
|
'platform/features/clock',
|
||||||
'platform/features/hyperlink',
|
|
||||||
'platform/features/timeline',
|
|
||||||
'platform/forms',
|
'platform/forms',
|
||||||
'platform/identity',
|
'platform/identity',
|
||||||
'platform/persistence/aggregator',
|
'platform/persistence/aggregator',
|
||||||
@ -82,9 +80,7 @@ define([
|
|||||||
'../platform/exporters/bundle',
|
'../platform/exporters/bundle',
|
||||||
'../platform/features/clock/bundle',
|
'../platform/features/clock/bundle',
|
||||||
'../platform/features/my-items/bundle',
|
'../platform/features/my-items/bundle',
|
||||||
'../platform/features/hyperlink/bundle',
|
|
||||||
'../platform/features/static-markup/bundle',
|
'../platform/features/static-markup/bundle',
|
||||||
'../platform/features/timeline/bundle',
|
|
||||||
'../platform/forms/bundle',
|
'../platform/forms/bundle',
|
||||||
'../platform/framework/bundle',
|
'../platform/framework/bundle',
|
||||||
'../platform/framework/src/load/Bundle',
|
'../platform/framework/src/load/Bundle',
|
||||||
|
32
src/plugins/DeviceClassifier/plugin.js
Normal file
32
src/plugins/DeviceClassifier/plugin.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import Agent from "../../utils/agent/Agent";
|
||||||
|
import DeviceClassifier from "./src/DeviceClassifier";
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (openmct) => {
|
||||||
|
openmct.on("start", () => {
|
||||||
|
const agent = new Agent(window);
|
||||||
|
DeviceClassifier(agent, window.document);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
72
src/plugins/DeviceClassifier/src/DeviceClassifier.js
Normal file
72
src/plugins/DeviceClassifier/src/DeviceClassifier.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs at application startup and adds a subset of the following
|
||||||
|
* CSS classes to the body of the document, depending on device
|
||||||
|
* attributes:
|
||||||
|
*
|
||||||
|
* * `mobile`: Phones or tablets.
|
||||||
|
* * `phone`: Phones specifically.
|
||||||
|
* * `tablet`: Tablets specifically.
|
||||||
|
* * `desktop`: Non-mobile devices.
|
||||||
|
* * `portrait`: Devices in a portrait-style orientation.
|
||||||
|
* * `landscape`: Devices in a landscape-style orientation.
|
||||||
|
* * `touch`: Device supports touch events.
|
||||||
|
*
|
||||||
|
* @param {utils/agent/Agent} agent
|
||||||
|
* the service used to examine the user agent
|
||||||
|
* @param document the HTML DOM document object
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
import DeviceMatchers from "./DeviceMatchers";
|
||||||
|
|
||||||
|
export default (agent, document) => {
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
|
Object.keys(DeviceMatchers).forEach((key, index, array) => {
|
||||||
|
if (DeviceMatchers[key](agent)) {
|
||||||
|
body.classList.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (agent.isMobile()) {
|
||||||
|
const mediaQuery = window.matchMedia("(orientation: landscape)");
|
||||||
|
function eventHandler(event) {
|
||||||
|
console.log("changed");
|
||||||
|
if (event.matches) {
|
||||||
|
body.classList.remove("portrait");
|
||||||
|
body.classList.add("landscape");
|
||||||
|
} else {
|
||||||
|
body.classList.remove("landscape");
|
||||||
|
body.classList.add("portrait");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaQuery.addEventListener) {
|
||||||
|
mediaQuery.addEventListener(`change`, eventHandler);
|
||||||
|
} else {
|
||||||
|
// Deprecated 'MediaQueryList' API, <Safari 14, IE, <Edge 16
|
||||||
|
mediaQuery.addListener(eventHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
105
src/plugins/DeviceClassifier/src/DeviceClassifierSpec.js
Normal file
105
src/plugins/DeviceClassifier/src/DeviceClassifierSpec.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import DeviceClassifier from "./DeviceClassifier";
|
||||||
|
import DeviceMatchers from "./DeviceMatchers";
|
||||||
|
|
||||||
|
const AGENT_METHODS = [
|
||||||
|
"isMobile",
|
||||||
|
"isPhone",
|
||||||
|
"isTablet",
|
||||||
|
"isPortrait",
|
||||||
|
"isLandscape",
|
||||||
|
"isTouch"
|
||||||
|
];
|
||||||
|
const TEST_PERMUTATIONS = [
|
||||||
|
["isMobile", "isPhone", "isTouch", "isPortrait"],
|
||||||
|
["isMobile", "isPhone", "isTouch", "isLandscape"],
|
||||||
|
["isMobile", "isTablet", "isTouch", "isPortrait"],
|
||||||
|
["isMobile", "isTablet", "isTouch", "isLandscape"],
|
||||||
|
["isTouch"],
|
||||||
|
[]
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("DeviceClassifier", function () {
|
||||||
|
let mockAgent;
|
||||||
|
let mockDocument;
|
||||||
|
let mockClassList;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockAgent = jasmine.createSpyObj(
|
||||||
|
"agent",
|
||||||
|
AGENT_METHODS
|
||||||
|
);
|
||||||
|
|
||||||
|
mockClassList = jasmine.createSpyObj("classList", ["add"]);
|
||||||
|
|
||||||
|
mockDocument = jasmine.createSpyObj(
|
||||||
|
"document",
|
||||||
|
{},
|
||||||
|
{ body: { classList: mockClassList } }
|
||||||
|
);
|
||||||
|
|
||||||
|
AGENT_METHODS.forEach(function (m) {
|
||||||
|
mockAgent[m].and.returnValue(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
TEST_PERMUTATIONS.forEach(function (trueMethods) {
|
||||||
|
const summary =
|
||||||
|
trueMethods.length === 0
|
||||||
|
? "device has no detected characteristics"
|
||||||
|
: "device " + trueMethods.join(", ");
|
||||||
|
|
||||||
|
describe("when " + summary, function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
trueMethods.forEach(function (m) {
|
||||||
|
mockAgent[m].and.returnValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-new
|
||||||
|
DeviceClassifier(mockAgent, mockDocument);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds classes for matching, detected characteristics", function () {
|
||||||
|
Object.keys(DeviceMatchers)
|
||||||
|
.filter(function (m) {
|
||||||
|
return DeviceMatchers[m](mockAgent);
|
||||||
|
})
|
||||||
|
.forEach(function (key) {
|
||||||
|
expect(mockDocument.body.classList.add).toHaveBeenCalledWith(key);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not add classes for non-matching characteristics", function () {
|
||||||
|
Object.keys(DeviceMatchers)
|
||||||
|
.filter(function (m) {
|
||||||
|
return !DeviceMatchers[m](mockAgent);
|
||||||
|
})
|
||||||
|
.forEach(function (key) {
|
||||||
|
expect(mockDocument.body.classList.add).not.toHaveBeenCalledWith(
|
||||||
|
key
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
57
src/plugins/DeviceClassifier/src/DeviceMatchers.js
Normal file
57
src/plugins/DeviceClassifier/src/DeviceMatchers.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object containing key-value pairs, where keys are symbolic of
|
||||||
|
* device attributes, and values are functions that take the
|
||||||
|
* `agent` as inputs and return boolean values indicating
|
||||||
|
* whether or not the current device has these attributes.
|
||||||
|
*
|
||||||
|
* For internal use by the mobile support bundle.
|
||||||
|
*
|
||||||
|
* @memberof src/plugins/DeviceClassifier
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mobile: function (agent) {
|
||||||
|
return agent.isMobile();
|
||||||
|
},
|
||||||
|
phone: function (agent) {
|
||||||
|
return agent.isPhone();
|
||||||
|
},
|
||||||
|
tablet: function (agent) {
|
||||||
|
return agent.isTablet();
|
||||||
|
},
|
||||||
|
desktop: function (agent) {
|
||||||
|
return !agent.isMobile();
|
||||||
|
},
|
||||||
|
portrait: function (agent) {
|
||||||
|
return agent.isPortrait();
|
||||||
|
},
|
||||||
|
landscape: function (agent) {
|
||||||
|
return agent.isLandscape();
|
||||||
|
},
|
||||||
|
touch: function (agent) {
|
||||||
|
return agent.isTouch();
|
||||||
|
}
|
||||||
|
};
|
65
src/plugins/DeviceClassifier/src/DeviceMatchersSpec.js
Normal file
65
src/plugins/DeviceClassifier/src/DeviceMatchersSpec.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import DeviceMatchers from "./DeviceMatchers";
|
||||||
|
|
||||||
|
describe("DeviceMatchers", function () {
|
||||||
|
let mockAgent;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockAgent = jasmine.createSpyObj("agent", [
|
||||||
|
"isMobile",
|
||||||
|
"isPhone",
|
||||||
|
"isTablet",
|
||||||
|
"isPortrait",
|
||||||
|
"isLandscape",
|
||||||
|
"isTouch"
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects when a device is a desktop device", function () {
|
||||||
|
mockAgent.isMobile.and.returnValue(false);
|
||||||
|
expect(DeviceMatchers.desktop(mockAgent)).toBe(true);
|
||||||
|
mockAgent.isMobile.and.returnValue(true);
|
||||||
|
expect(DeviceMatchers.desktop(mockAgent)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
function method(deviceType) {
|
||||||
|
return "is" + deviceType[0].toUpperCase() + deviceType.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[
|
||||||
|
"mobile",
|
||||||
|
"phone",
|
||||||
|
"tablet",
|
||||||
|
"landscape",
|
||||||
|
"portrait",
|
||||||
|
"landscape",
|
||||||
|
"touch"
|
||||||
|
].forEach(function (deviceType) {
|
||||||
|
it("detects when a device is a " + deviceType + " device", function () {
|
||||||
|
mockAgent[method(deviceType)].and.returnValue(true);
|
||||||
|
expect(DeviceMatchers[deviceType](mockAgent)).toBe(true);
|
||||||
|
mockAgent[method(deviceType)].and.returnValue(false);
|
||||||
|
expect(DeviceMatchers[deviceType](mockAgent)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -19,8 +19,8 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import LadTableSet from './components/LadTableSet.vue';
|
|
||||||
import Vue from 'vue';
|
import LadTableSetView from './LadTableSetView';
|
||||||
|
|
||||||
export default function LADTableSetViewProvider(openmct) {
|
export default function LADTableSetViewProvider(openmct) {
|
||||||
return {
|
return {
|
||||||
@ -34,32 +34,7 @@ export default function LADTableSetViewProvider(openmct) {
|
|||||||
return domainObject.type === 'LadTableSet';
|
return domainObject.type === 'LadTableSet';
|
||||||
},
|
},
|
||||||
view: function (domainObject, objectPath) {
|
view: function (domainObject, objectPath) {
|
||||||
let component;
|
return new LadTableSetView(openmct, domainObject, objectPath);
|
||||||
|
|
||||||
return {
|
|
||||||
show: function (element) {
|
|
||||||
component = new Vue({
|
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
LadTableSet: LadTableSet
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct,
|
|
||||||
objectPath
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
domainObject
|
|
||||||
};
|
|
||||||
},
|
|
||||||
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy: function (element) {
|
|
||||||
component.$destroy();
|
|
||||||
component = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
priority: function () {
|
priority: function () {
|
||||||
return 1;
|
return 1;
|
||||||
|
45
src/plugins/LADTable/LADTableView.js
Normal file
45
src/plugins/LADTable/LADTableView.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import LadTable from './components/LADTable.vue';
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default class LADTableView {
|
||||||
|
constructor(openmct, domainObject, objectPath) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.objectPath = objectPath;
|
||||||
|
this.component = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
show(element) {
|
||||||
|
this.component = new Vue({
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
LadTable
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: this.openmct,
|
||||||
|
currentView: this
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
domainObject: this.domainObject,
|
||||||
|
objectPath: this.objectPath
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: '<lad-table ref="ladTable" :domain-object="domainObject" :object-path="objectPath"></lad-table>'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewContext() {
|
||||||
|
if (!this.component) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.component.$refs.ladTable.getViewContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(element) {
|
||||||
|
this.component.$destroy();
|
||||||
|
this.component = undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -19,50 +19,30 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import LadTable from './components/LADTable.vue';
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
export default function LADTableViewProvider(openmct) {
|
import LADTableView from './LADTableView';
|
||||||
return {
|
|
||||||
key: 'LadTable',
|
|
||||||
name: 'LAD Table',
|
|
||||||
cssClass: 'icon-tabular-lad',
|
|
||||||
canView: function (domainObject) {
|
|
||||||
return domainObject.type === 'LadTable';
|
|
||||||
},
|
|
||||||
canEdit: function (domainObject) {
|
|
||||||
return domainObject.type === 'LadTable';
|
|
||||||
},
|
|
||||||
view: function (domainObject, objectPath) {
|
|
||||||
let component;
|
|
||||||
|
|
||||||
return {
|
export default class LADTableViewProvider {
|
||||||
show: function (element) {
|
constructor(openmct) {
|
||||||
component = new Vue({
|
this.openmct = openmct;
|
||||||
el: element,
|
this.name = 'LAD Table';
|
||||||
components: {
|
this.key = 'LadTable';
|
||||||
LadTableComponent: LadTable
|
this.cssClass = 'icon-tabular-lad';
|
||||||
},
|
}
|
||||||
provide: {
|
|
||||||
openmct
|
canView(domainObject) {
|
||||||
},
|
return domainObject.type === 'LadTable';
|
||||||
data: () => {
|
}
|
||||||
return {
|
|
||||||
domainObject,
|
canEdit(domainObject) {
|
||||||
objectPath
|
return domainObject.type === 'LadTable';
|
||||||
};
|
}
|
||||||
},
|
|
||||||
template: '<lad-table-component :domain-object="domainObject" :object-path="objectPath"></lad-table-component>'
|
view(domainObject, objectPath) {
|
||||||
});
|
return new LADTableView(this.openmct, domainObject, objectPath);
|
||||||
},
|
}
|
||||||
destroy: function (element) {
|
|
||||||
component.$destroy();
|
priority(domainObject) {
|
||||||
component = undefined;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
},
|
|
||||||
priority: function () {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
45
src/plugins/LADTable/LadTableSetView.js
Normal file
45
src/plugins/LADTable/LadTableSetView.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import LadTableSet from './components/LadTableSet.vue';
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default class LadTableSetView {
|
||||||
|
constructor(openmct, domainObject, objectPath) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.objectPath = objectPath;
|
||||||
|
this.component = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
show(element) {
|
||||||
|
this.component = new Vue({
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
LadTableSet
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: this.openmct,
|
||||||
|
objectPath: this.objectPath,
|
||||||
|
currentView: this
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
domainObject: this.domainObject
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: '<lad-table-set ref="ladTableSet" :domain-object="domainObject"></lad-table-set>'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewContext() {
|
||||||
|
if (!this.component) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.component.$refs.ladTableSet.getViewContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(element) {
|
||||||
|
this.component.$destroy();
|
||||||
|
this.component = undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -50,7 +50,7 @@ const CONTEXT_MENU_ACTIONS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct', 'currentView'],
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -167,25 +167,23 @@ export default {
|
|||||||
this.resetValues();
|
this.resetValues();
|
||||||
this.timestampKey = timeSystem.key;
|
this.timestampKey = timeSystem.key;
|
||||||
},
|
},
|
||||||
getView() {
|
updateViewContext() {
|
||||||
return {
|
this.$emit('rowContextClick', {
|
||||||
getViewContext: () => {
|
viewHistoricalData: true,
|
||||||
return {
|
viewDatumAction: true,
|
||||||
viewHistoricalData: true,
|
getDatum: () => {
|
||||||
viewDatumAction: true,
|
return this.datum;
|
||||||
getDatum: () => {
|
|
||||||
return this.datum;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
},
|
},
|
||||||
showContextMenu(event) {
|
showContextMenu(event) {
|
||||||
let actionCollection = this.openmct.actions.get(this.objectPath, this.getView());
|
this.updateViewContext();
|
||||||
let allActions = actionCollection.getActionsObject();
|
|
||||||
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
|
||||||
|
|
||||||
this.openmct.menus.showMenu(event.x, event.y, applicableActions);
|
const actions = CONTEXT_MENU_ACTIONS.map(key => this.openmct.actions.getAction(key));
|
||||||
|
const menuItems = this.openmct.menus.actionsToMenuItems(actions, this.objectPath, this.currentView);
|
||||||
|
if (menuItems.length) {
|
||||||
|
this.openmct.menus.showMenu(event.x, event.y, menuItems);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resetValues() {
|
resetValues() {
|
||||||
this.value = '---';
|
this.value = '---';
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
:domain-object="ladRow.domainObject"
|
:domain-object="ladRow.domainObject"
|
||||||
:path-to-table="objectPath"
|
:path-to-table="objectPath"
|
||||||
:has-units="hasUnits"
|
:has-units="hasUnits"
|
||||||
|
@rowContextClick="updateViewContext"
|
||||||
/>
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -51,7 +52,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
LadRow
|
LadRow
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct', 'currentView'],
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -64,7 +65,8 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
items: []
|
items: [],
|
||||||
|
viewContext: {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -114,6 +116,12 @@ export default {
|
|||||||
let metadataWithUnits = valueMetadatas.filter(metadatum => metadatum.unit);
|
let metadataWithUnits = valueMetadatas.filter(metadatum => metadatum.unit);
|
||||||
|
|
||||||
return metadataWithUnits.length > 0;
|
return metadataWithUnits.length > 0;
|
||||||
|
},
|
||||||
|
updateViewContext(rowContext) {
|
||||||
|
this.viewContext.row = rowContext;
|
||||||
|
},
|
||||||
|
getViewContext() {
|
||||||
|
return this.viewContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
:domain-object="ladRow.domainObject"
|
:domain-object="ladRow.domainObject"
|
||||||
:path-to-table="ladTable.objectPath"
|
:path-to-table="ladTable.objectPath"
|
||||||
:has-units="hasUnits"
|
:has-units="hasUnits"
|
||||||
|
@rowContextClick="updateViewContext"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -61,7 +62,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
LadRow
|
LadRow
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'objectPath'],
|
inject: ['openmct', 'objectPath', 'currentView'],
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -72,7 +73,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
ladTableObjects: [],
|
ladTableObjects: [],
|
||||||
ladTelemetryObjects: {},
|
ladTelemetryObjects: {},
|
||||||
compositions: []
|
compositions: [],
|
||||||
|
viewContext: {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -166,6 +168,12 @@ export default {
|
|||||||
|
|
||||||
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
|
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
updateViewContext(rowContext) {
|
||||||
|
this.viewContext.row = rowContext;
|
||||||
|
},
|
||||||
|
getViewContext() {
|
||||||
|
return this.viewContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -45,6 +45,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
|
this.updateTimeSettings();
|
||||||
this.openmct.router.on('change:params', this.updateTimeSettings);
|
this.openmct.router.on('change:params', this.updateTimeSettings);
|
||||||
|
|
||||||
TIME_EVENTS.forEach(event => {
|
TIME_EVENTS.forEach(event => {
|
||||||
|
@ -41,7 +41,7 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
this.subscriptions = {};
|
this.subscriptions = {};
|
||||||
this.telemetryObjects = {};
|
this.telemetryObjects = {};
|
||||||
this.testData = {
|
this.testData = {
|
||||||
conditionTestData: [],
|
conditionTestInputs: this.conditionSetDomainObject.configuration.conditionTestData,
|
||||||
applied: false
|
applied: false
|
||||||
};
|
};
|
||||||
this.initialize();
|
this.initialize();
|
||||||
@ -154,8 +154,10 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
|
|
||||||
updateConditionDescription(condition) {
|
updateConditionDescription(condition) {
|
||||||
const found = this.conditionSetDomainObject.configuration.conditionCollection.find(conditionConfiguration => (conditionConfiguration.id === condition.id));
|
const found = this.conditionSetDomainObject.configuration.conditionCollection.find(conditionConfiguration => (conditionConfiguration.id === condition.id));
|
||||||
found.summary = condition.description;
|
if (found.summary !== condition.description) {
|
||||||
this.persistConditions();
|
found.summary = condition.description;
|
||||||
|
this.persistConditions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initCondition(conditionConfiguration, index) {
|
initCondition(conditionConfiguration, index) {
|
||||||
@ -414,8 +416,10 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTestData(testData) {
|
updateTestData(testData) {
|
||||||
this.testData = testData;
|
if (!_.isEqual(testData, this.testData)) {
|
||||||
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
|
this.testData = testData;
|
||||||
|
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
persistConditions() {
|
persistConditions() {
|
||||||
|
@ -215,7 +215,8 @@ export default {
|
|||||||
},
|
},
|
||||||
isEditing: {
|
isEditing: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true
|
required: true,
|
||||||
|
default: false
|
||||||
},
|
},
|
||||||
telemetry: {
|
telemetry: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -321,7 +321,7 @@ export default {
|
|||||||
if (item) {
|
if (item) {
|
||||||
const type = this.openmct.types.get(item.type);
|
const type = this.openmct.types.get(item.type);
|
||||||
if (type && type.definition) {
|
if (type && type.definition) {
|
||||||
creatable = (type.definition.creatable === true);
|
creatable = (type.definition.creatable !== undefined && (type.definition.creatable === 'true' || type.definition.creatable === true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,71 +20,78 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
import AlphanumericFormat from './components/AlphanumericFormat.vue';
|
||||||
'./components/AlphanumericFormatView.vue',
|
|
||||||
'vue'
|
|
||||||
], function (AlphanumericFormatView, Vue) {
|
|
||||||
|
|
||||||
function AlphanumericFormatViewProvider(openmct, options) {
|
import Vue from 'vue';
|
||||||
function isTelemetryObject(selectionPath) {
|
|
||||||
let selectedObject = selectionPath[0].context.item;
|
|
||||||
let parentObject = selectionPath[1].context.item;
|
|
||||||
let selectedLayoutItem = selectionPath[0].context.layoutItem;
|
|
||||||
|
|
||||||
return parentObject
|
class AlphanumericFormatView {
|
||||||
&& parentObject.type === 'layout'
|
constructor(openmct, domainObject, objectPath) {
|
||||||
&& selectedObject
|
this.openmct = openmct;
|
||||||
&& selectedLayoutItem
|
this.domainObject = domainObject;
|
||||||
&& selectedLayoutItem.type === 'telemetry-view'
|
this.objectPath = objectPath;
|
||||||
&& openmct.telemetry.isTelemetryObject(selectedObject)
|
this.component = undefined;
|
||||||
&& !options.showAsView.includes(selectedObject.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: 'alphanumeric-format',
|
|
||||||
name: 'Alphanumeric Format',
|
|
||||||
canView: function (selection) {
|
|
||||||
if (selection.length === 0 || selection[0].length === 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selection.every(isTelemetryObject);
|
|
||||||
},
|
|
||||||
view: function (domainObject, objectPath) {
|
|
||||||
let component;
|
|
||||||
|
|
||||||
return {
|
|
||||||
show: function (element) {
|
|
||||||
component = new Vue({
|
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
AlphanumericFormatView: AlphanumericFormatView.default
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct,
|
|
||||||
objectPath
|
|
||||||
},
|
|
||||||
template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getViewContext() {
|
|
||||||
if (component) {
|
|
||||||
return component.$refs.alphanumericFormatView.getViewContext();
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroy: function () {
|
|
||||||
component.$destroy();
|
|
||||||
component = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
priority: function () {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return AlphanumericFormatViewProvider;
|
show(element) {
|
||||||
});
|
this.component = new Vue({
|
||||||
|
el: element,
|
||||||
|
name: 'AlphanumericFormat',
|
||||||
|
components: {
|
||||||
|
AlphanumericFormat
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: this.openmct,
|
||||||
|
objectPath: this.objectPath,
|
||||||
|
currentView: this
|
||||||
|
},
|
||||||
|
template: '<alphanumeric-format ref="alphanumericFormat"></alphanumeric-format>'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewContext() {
|
||||||
|
if (this.component) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.component.$refs.alphanumericFormat.getViewContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.component.$destroy();
|
||||||
|
this.component = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AlphanumericFormatViewProvider(openmct, options) {
|
||||||
|
function isTelemetryObject(selectionPath) {
|
||||||
|
let selectedObject = selectionPath[0].context.item;
|
||||||
|
let parentObject = selectionPath[1].context.item;
|
||||||
|
let selectedLayoutItem = selectionPath[0].context.layoutItem;
|
||||||
|
|
||||||
|
return parentObject
|
||||||
|
&& parentObject.type === 'layout'
|
||||||
|
&& selectedObject
|
||||||
|
&& selectedLayoutItem
|
||||||
|
&& selectedLayoutItem.type === 'telemetry-view'
|
||||||
|
&& openmct.telemetry.isTelemetryObject(selectedObject)
|
||||||
|
&& !options.showAsView.includes(selectedObject.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'alphanumeric-format',
|
||||||
|
name: 'Alphanumeric Format',
|
||||||
|
canView: function (selection) {
|
||||||
|
if (selection.length === 0 || selection[0].length === 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selection.every(isTelemetryObject);
|
||||||
|
},
|
||||||
|
view: function (domainObject, objectPath) {
|
||||||
|
return new AlphanumericFormatView(openmct, domainObject, objectPath);
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -14,7 +14,7 @@ export default class CopyToClipboardAction {
|
|||||||
|
|
||||||
invoke(objectPath, view = {}) {
|
invoke(objectPath, view = {}) {
|
||||||
const viewContext = view.getViewContext && view.getViewContext();
|
const viewContext = view.getViewContext && view.getViewContext();
|
||||||
const formattedValue = viewContext.formattedValueForCopy();
|
const formattedValue = viewContext.row.formattedValueForCopy();
|
||||||
|
|
||||||
clipboard.updateClipboard(formattedValue)
|
clipboard.updateClipboard(formattedValue)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -26,9 +26,13 @@ export default class CopyToClipboardAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
appliesTo(objectPath, view = {}) {
|
appliesTo(objectPath, view = {}) {
|
||||||
let viewContext = view.getViewContext && view.getViewContext();
|
const viewContext = view.getViewContext && view.getViewContext();
|
||||||
|
const row = viewContext && viewContext.row;
|
||||||
|
if (!row) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return viewContext && viewContext.formattedValueForCopy
|
return row.formattedValueForCopy
|
||||||
&& typeof viewContext.formattedValueForCopy === 'function';
|
&& typeof row.formattedValueForCopy === 'function';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
name: 'AlphanumericFormat',
|
||||||
|
inject: ['openmct', 'objectPath'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isEditing: this.openmct.editor.isEditing(),
|
isEditing: this.openmct.editor.isEditing(),
|
@ -56,6 +56,7 @@
|
|||||||
:index="index"
|
:index="index"
|
||||||
:multi-select="selectedLayoutItems.length > 1"
|
:multi-select="selectedLayoutItems.length > 1"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
|
@contextClick="updateViewContext"
|
||||||
@move="move"
|
@move="move"
|
||||||
@endMove="endMove"
|
@endMove="endMove"
|
||||||
@endLineResize="endLineResize"
|
@endLineResize="endLineResize"
|
||||||
@ -140,7 +141,7 @@ function getItemDefinition(itemType, ...options) {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: components,
|
components: components,
|
||||||
inject: ['openmct', 'options', 'objectPath'],
|
inject: ['openmct', 'objectPath', 'options', 'objectUtils', 'currentView'],
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -155,7 +156,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
initSelectIndex: undefined,
|
initSelectIndex: undefined,
|
||||||
selection: [],
|
selection: [],
|
||||||
showGrid: true
|
showGrid: true,
|
||||||
|
viewContext: {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -819,6 +821,12 @@ export default {
|
|||||||
},
|
},
|
||||||
toggleGrid() {
|
toggleGrid() {
|
||||||
this.showGrid = !this.showGrid;
|
this.showGrid = !this.showGrid;
|
||||||
|
},
|
||||||
|
updateViewContext(viewContext) {
|
||||||
|
this.viewContext.row = viewContext;
|
||||||
|
},
|
||||||
|
getViewContext() {
|
||||||
|
return this.viewContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div
|
<div
|
||||||
class="c-frame__move-bar"
|
class="c-frame__move-bar"
|
||||||
@mousedown="isEditing ? startMove([1,1], [0,0], $event) : null"
|
@mousedown.left="startMove($event)"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -93,7 +93,11 @@ export default {
|
|||||||
return value - this.initialPosition[index];
|
return value - this.initialPosition[index];
|
||||||
}.bind(this));
|
}.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('mousemove', this.continueMove);
|
||||||
document.body.addEventListener('mouseup', this.endMove);
|
document.body.addEventListener('mouseup', this.endMove);
|
||||||
this.dragPosition = {
|
this.dragPosition = {
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="c-frame__move-bar"
|
class="c-frame__move-bar"
|
||||||
@mousedown="startDrag($event)"
|
@mousedown.left="startDrag($event)"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
v-if="showFrameEdit"
|
v-if="showFrameEdit"
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import LayoutFrame from './LayoutFrame.vue';
|
import LayoutFrame from './LayoutFrame.vue';
|
||||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
||||||
import { getDefaultNotebook } 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_TELEMETRY_DIMENSIONS = [10, 5];
|
||||||
const DEFAULT_POSITION = [1, 1];
|
const DEFAULT_POSITION = [1, 1];
|
||||||
@ -102,7 +102,7 @@ export default {
|
|||||||
LayoutFrame
|
LayoutFrame
|
||||||
},
|
},
|
||||||
mixins: [conditionalStylesMixin],
|
mixins: [conditionalStylesMixin],
|
||||||
inject: ['openmct', 'objectPath'],
|
inject: ['openmct', 'objectPath', 'currentView'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -294,16 +294,6 @@ export default {
|
|||||||
this.requestHistoricalData(this.domainObject);
|
this.requestHistoricalData(this.domainObject);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getView() {
|
|
||||||
return {
|
|
||||||
getViewContext: () => {
|
|
||||||
return {
|
|
||||||
viewHistoricalData: true,
|
|
||||||
formattedValueForCopy: this.formattedValueForCopy
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setObject(domainObject) {
|
setObject(domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.mutablePromise = undefined;
|
this.mutablePromise = undefined;
|
||||||
@ -338,30 +328,41 @@ export default {
|
|||||||
|
|
||||||
this.$emit('formatChanged', this.item, format);
|
this.$emit('formatChanged', this.item, format);
|
||||||
},
|
},
|
||||||
|
updateViewContext() {
|
||||||
|
this.$emit('contextClick', {
|
||||||
|
viewHistoricalData: true,
|
||||||
|
formattedValueForCopy: this.formattedValueForCopy
|
||||||
|
});
|
||||||
|
},
|
||||||
async getContextMenuActions() {
|
async getContextMenuActions() {
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
|
|
||||||
const actionCollection = this.openmct.actions.get(this.currentObjectPath, this.getView());
|
|
||||||
const actionsObject = actionCollection.getActionsObject();
|
|
||||||
|
|
||||||
let copyToNotebookAction = actionsObject.copyToNotebook;
|
|
||||||
|
|
||||||
|
let defaultNotebookName;
|
||||||
if (defaultNotebook) {
|
if (defaultNotebook) {
|
||||||
const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
|
const domainObject = await this.openmct.objects.get(defaultNotebook.identifier);
|
||||||
copyToNotebookAction.name = `Copy to Notebook ${defaultPath}`;
|
const { section, page } = getNotebookSectionAndPage(domainObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
|
||||||
} else {
|
if (section && page) {
|
||||||
actionsObject.copyToNotebook = undefined;
|
const defaultPath = domainObject && `${domainObject.name} - ${section.name} - ${page.name}`;
|
||||||
delete actionsObject.copyToNotebook;
|
defaultNotebookName = `Copy to Notebook ${defaultPath}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CONTEXT_MENU_ACTIONS.map(actionKey => {
|
return CONTEXT_MENU_ACTIONS
|
||||||
return actionsObject[actionKey];
|
.map(actionKey => {
|
||||||
}).filter(action => action !== undefined);
|
const action = this.openmct.actions.getAction(actionKey);
|
||||||
|
if (action.key === 'copyToNotebook') {
|
||||||
|
action.name = defaultNotebookName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return action;
|
||||||
|
})
|
||||||
|
.filter(action => action.name !== undefined);
|
||||||
},
|
},
|
||||||
async showContextMenu(event) {
|
async showContextMenu(event) {
|
||||||
|
this.updateViewContext();
|
||||||
const contextMenuActions = await this.getContextMenuActions();
|
const contextMenuActions = await this.getContextMenuActions();
|
||||||
|
const menuItems = this.openmct.menus.actionsToMenuItems(contextMenuActions, this.currentObjectPath, this.currentView);
|
||||||
this.openmct.menus.showMenu(event.x, event.y, contextMenuActions);
|
this.openmct.menus.showMenu(event.x, event.y, menuItems);
|
||||||
},
|
},
|
||||||
setStatus(status) {
|
setStatus(status) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
@ -20,13 +20,81 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Layout from './components/DisplayLayout.vue';
|
|
||||||
import Vue from 'vue';
|
|
||||||
import objectUtils from 'objectUtils';
|
|
||||||
import DisplayLayoutType from './DisplayLayoutType.js';
|
|
||||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
|
||||||
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
|
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
|
||||||
import CopyToClipboardAction from './actions/CopyToClipboardAction';
|
import CopyToClipboardAction from './actions/CopyToClipboardAction';
|
||||||
|
import DisplayLayout from './components/DisplayLayout.vue';
|
||||||
|
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
||||||
|
import DisplayLayoutType from './DisplayLayoutType.js';
|
||||||
|
|
||||||
|
import objectUtils from 'objectUtils';
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
class DisplayLayoutView {
|
||||||
|
constructor(openmct, domainObject, objectPath, options) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.objectPath = objectPath;
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
this.component = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
show(container, isEditing) {
|
||||||
|
this.component = new Vue({
|
||||||
|
el: container,
|
||||||
|
components: {
|
||||||
|
DisplayLayout
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: this.openmct,
|
||||||
|
objectPath: this.objectPath,
|
||||||
|
options: this.options,
|
||||||
|
objectUtils,
|
||||||
|
currentView: this
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
domainObject: this.domainObject,
|
||||||
|
isEditing
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: '<display-layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></display-layout>'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewContext() {
|
||||||
|
if (!this.component) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.component.$refs.displayLayout.getViewContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectionContext() {
|
||||||
|
return {
|
||||||
|
item: this.domainObject,
|
||||||
|
supportsMultiSelect: true,
|
||||||
|
addElement: this.component && this.component.$refs.displayLayout.addElement,
|
||||||
|
removeItem: this.component && this.component.$refs.displayLayout.removeItem,
|
||||||
|
orderItem: this.component && this.component.$refs.displayLayout.orderItem,
|
||||||
|
duplicateItem: this.component && this.component.$refs.displayLayout.duplicateItem,
|
||||||
|
switchViewType: this.component && this.component.$refs.displayLayout.switchViewType,
|
||||||
|
mergeMultipleTelemetryViews: this.component && this.component.$refs.displayLayout.mergeMultipleTelemetryViews,
|
||||||
|
mergeMultipleOverlayPlots: this.component && this.component.$refs.displayLayout.mergeMultipleOverlayPlots,
|
||||||
|
toggleGrid: this.component && this.component.$refs.displayLayout.toggleGrid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditModeChange(isEditing) {
|
||||||
|
this.component.isEditing = isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.component.$destroy();
|
||||||
|
this.component = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function DisplayLayoutPlugin(options) {
|
export default function DisplayLayoutPlugin(options) {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
@ -41,51 +109,7 @@ export default function DisplayLayoutPlugin(options) {
|
|||||||
return domainObject.type === 'layout';
|
return domainObject.type === 'layout';
|
||||||
},
|
},
|
||||||
view: function (domainObject, objectPath) {
|
view: function (domainObject, objectPath) {
|
||||||
let component;
|
return new DisplayLayoutView(openmct, domainObject, objectPath, options);
|
||||||
|
|
||||||
return {
|
|
||||||
show(container) {
|
|
||||||
component = new Vue({
|
|
||||||
el: container,
|
|
||||||
components: {
|
|
||||||
Layout
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct,
|
|
||||||
objectUtils,
|
|
||||||
options,
|
|
||||||
objectPath
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
domainObject: domainObject,
|
|
||||||
isEditing: openmct.editor.isEditing()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
template: '<layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></layout>'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getSelectionContext() {
|
|
||||||
return {
|
|
||||||
item: domainObject,
|
|
||||||
supportsMultiSelect: true,
|
|
||||||
addElement: component && component.$refs.displayLayout.addElement,
|
|
||||||
removeItem: component && component.$refs.displayLayout.removeItem,
|
|
||||||
orderItem: component && component.$refs.displayLayout.orderItem,
|
|
||||||
duplicateItem: component && component.$refs.displayLayout.duplicateItem,
|
|
||||||
switchViewType: component && component.$refs.displayLayout.switchViewType,
|
|
||||||
mergeMultipleTelemetryViews: component && component.$refs.displayLayout.mergeMultipleTelemetryViews,
|
|
||||||
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots,
|
|
||||||
toggleGrid: component && component.$refs.displayLayout.toggleGrid
|
|
||||||
};
|
|
||||||
},
|
|
||||||
onEditModeChange: function (isEditing) {
|
|
||||||
component.isEditing = isEditing;
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
component.$destroy();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
priority() {
|
priority() {
|
||||||
return 100;
|
return 100;
|
||||||
|
51
src/plugins/hyperlink/HyperlinkLayout.vue
Normal file
51
src/plugins/hyperlink/HyperlinkLayout.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<a class="c-hyperlink"
|
||||||
|
:class="{
|
||||||
|
'c-hyperlink--button' : isButton
|
||||||
|
}"
|
||||||
|
:target="domainObject.linkTarget"
|
||||||
|
:href="domainObject.url"
|
||||||
|
>
|
||||||
|
<span class="c-hyperlink__label">{{ domainObject.displayText }}</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['domainObject'],
|
||||||
|
computed: {
|
||||||
|
isButton() {
|
||||||
|
if (this.domainObject.displayFormat === "link") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -19,40 +19,41 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
define(function () {
|
|
||||||
|
|
||||||
/**
|
import HyperlinkLayout from './HyperlinkLayout.vue';
|
||||||
* An object containing key-value pairs, where keys are symbolic of
|
import Vue from 'vue';
|
||||||
* device attributes, and values are functions that take the
|
|
||||||
* `agentService` as inputs and return boolean values indicating
|
export default function HyperlinkProvider(openmct) {
|
||||||
* whether or not the current device has these attributes.
|
|
||||||
*
|
|
||||||
* For internal use by the mobile support bundle.
|
|
||||||
*
|
|
||||||
* @memberof platform/commonUI/mobile
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
return {
|
return {
|
||||||
mobile: function (agentService) {
|
key: 'hyperlink.view',
|
||||||
return agentService.isMobile();
|
name: 'Hyperlink',
|
||||||
|
cssClass: 'icon-chain-links',
|
||||||
|
canView(domainObject) {
|
||||||
|
return domainObject.type === 'hyperlink';
|
||||||
},
|
},
|
||||||
phone: function (agentService) {
|
|
||||||
return agentService.isPhone();
|
view: function (domainObject) {
|
||||||
},
|
let component;
|
||||||
tablet: function (agentService) {
|
|
||||||
return agentService.isTablet();
|
return {
|
||||||
},
|
show: function (element) {
|
||||||
desktop: function (agentService) {
|
component = new Vue({
|
||||||
return !agentService.isMobile();
|
el: element,
|
||||||
},
|
components: {
|
||||||
portrait: function (agentService) {
|
HyperlinkLayout
|
||||||
return agentService.isPortrait();
|
},
|
||||||
},
|
provide: {
|
||||||
landscape: function (agentService) {
|
domainObject
|
||||||
return agentService.isLandscape();
|
},
|
||||||
},
|
template: '<hyperlink-layout></hyperlink-layout>'
|
||||||
touch: function (agentService) {
|
});
|
||||||
return agentService.isTouch();
|
},
|
||||||
|
destroy: function () {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
}
|
89
src/plugins/hyperlink/plugin.js
Normal file
89
src/plugins/hyperlink/plugin.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import HyperlinkProvider from './HyperlinkProvider';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return function install(openmct) {
|
||||||
|
openmct.types.addType('hyperlink', {
|
||||||
|
name: 'Hyperlink',
|
||||||
|
key: 'hyperlink',
|
||||||
|
description: 'A hyperlink to redirect to a different link',
|
||||||
|
creatable: true,
|
||||||
|
cssClass: 'icon-chain-links',
|
||||||
|
initialize: function (domainObject) {
|
||||||
|
domainObject.displayFormat = "link";
|
||||||
|
domainObject.linkTarget = "_self";
|
||||||
|
},
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
"key": "url",
|
||||||
|
"name": "URL",
|
||||||
|
"control": "textfield",
|
||||||
|
"required": true,
|
||||||
|
"cssClass": "l-input-lg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "displayText",
|
||||||
|
"name": "Text to Display",
|
||||||
|
"control": "textfield",
|
||||||
|
"required": true,
|
||||||
|
"cssClass": "l-input-lg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "displayFormat",
|
||||||
|
"name": "Display Format",
|
||||||
|
"control": "select",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "Link",
|
||||||
|
"value": "link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Button",
|
||||||
|
"value": "button"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cssClass": "l-inline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "linkTarget",
|
||||||
|
"name": "Tab to Open Hyperlink",
|
||||||
|
"control": "select",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "Open in this tab",
|
||||||
|
"value": "_self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Open in a new tab",
|
||||||
|
"value": "_blank"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cssClass": "l-inline"
|
||||||
|
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
openmct.objectViews.addProvider(new HyperlinkProvider(openmct));
|
||||||
|
};
|
||||||
|
}
|
130
src/plugins/hyperlink/pluginSpec.js
Normal file
130
src/plugins/hyperlink/pluginSpec.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import { createOpenMct, resetApplicationState } from "utils/testing";
|
||||||
|
import HyperlinkPlugin from "./plugin";
|
||||||
|
|
||||||
|
function getView(openmct, domainObj, objectPath) {
|
||||||
|
const applicableViews = openmct.objectViews.get(domainObj, objectPath);
|
||||||
|
const hyperLinkView = applicableViews.find((viewProvider) => viewProvider.key === 'hyperlink.view');
|
||||||
|
|
||||||
|
return hyperLinkView.view(domainObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyView(view) {
|
||||||
|
return view.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("The controller for hyperlinks", function () {
|
||||||
|
let mockDomainObject;
|
||||||
|
let mockObjectPath;
|
||||||
|
let openmct;
|
||||||
|
let element;
|
||||||
|
let child;
|
||||||
|
let view;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
mockObjectPath = [
|
||||||
|
{
|
||||||
|
name: 'mock hyperlink',
|
||||||
|
type: 'hyperlink',
|
||||||
|
identifier: {
|
||||||
|
key: 'mock-hyperlink',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
mockDomainObject = {
|
||||||
|
displayFormat: "",
|
||||||
|
linkTarget: "",
|
||||||
|
name: "Unnamed HyperLink",
|
||||||
|
type: "hyperlink",
|
||||||
|
location: "f69c21ac-24ef-450c-8e2f-3d527087d285",
|
||||||
|
modified: 1627483839783,
|
||||||
|
url: "123",
|
||||||
|
displayText: "123",
|
||||||
|
persisted: 1627483839783,
|
||||||
|
id: "3d9c243d-dffb-446b-8474-d9931a99d679",
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "3d9c243d-dffb-446b-8474-d9931a99d679"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openmct = createOpenMct();
|
||||||
|
openmct.install(new HyperlinkPlugin());
|
||||||
|
|
||||||
|
element = document.createElement('div');
|
||||||
|
element.style.width = '640px';
|
||||||
|
element.style.height = '480px';
|
||||||
|
child = document.createElement('div');
|
||||||
|
child.style.width = '640px';
|
||||||
|
child.style.height = '480px';
|
||||||
|
element.appendChild(child);
|
||||||
|
|
||||||
|
openmct.on('start', done);
|
||||||
|
openmct.startHeadless();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
destroyView(view);
|
||||||
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
it("knows when it should open a new tab", () => {
|
||||||
|
mockDomainObject.displayFormat = "link";
|
||||||
|
mockDomainObject.linkTarget = "_blank";
|
||||||
|
|
||||||
|
view = getView(openmct, mockDomainObject, mockObjectPath);
|
||||||
|
view.show(child, true);
|
||||||
|
|
||||||
|
expect(element.querySelector('.c-hyperlink').target).toBe('_blank');
|
||||||
|
});
|
||||||
|
it("knows when it should open in the same tab", function () {
|
||||||
|
mockDomainObject.displayFormat = "button";
|
||||||
|
mockDomainObject.linkTarget = "_self";
|
||||||
|
|
||||||
|
view = getView(openmct, mockDomainObject, mockObjectPath);
|
||||||
|
view.show(child, true);
|
||||||
|
|
||||||
|
expect(element.querySelector('.c-hyperlink').target).toBe('_self');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("knows when it is a button", function () {
|
||||||
|
mockDomainObject.displayFormat = "button";
|
||||||
|
|
||||||
|
view = getView(openmct, mockDomainObject, mockObjectPath);
|
||||||
|
view.show(child, true);
|
||||||
|
|
||||||
|
expect(element.querySelector('.c-hyperlink--button')).toBeDefined();
|
||||||
|
});
|
||||||
|
it("knows when it is a link", function () {
|
||||||
|
mockDomainObject.displayFormat = "link";
|
||||||
|
|
||||||
|
view = getView(openmct, mockDomainObject, mockObjectPath);
|
||||||
|
view.show(child, true);
|
||||||
|
|
||||||
|
expect(element.querySelector('.c-hyperlink')).not.toHaveClass('c-hyperlink--button');
|
||||||
|
});
|
||||||
|
});
|
37
src/plugins/imagery/ImageryView.js
Normal file
37
src/plugins/imagery/ImageryView.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import ImageryViewLayout from './components/ImageryViewLayout.vue';
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default class ImageryView {
|
||||||
|
constructor(openmct, domainObject, objectPath) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.objectPath = objectPath;
|
||||||
|
this.component = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
show(element) {
|
||||||
|
this.component = new Vue({
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
ImageryViewLayout
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: this.openmct,
|
||||||
|
domainObject: this.domainObject,
|
||||||
|
objectPath: this.objectPath,
|
||||||
|
currentView: this
|
||||||
|
},
|
||||||
|
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.component.$destroy();
|
||||||
|
this.component = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getInstance() {
|
||||||
|
return this.component;
|
||||||
|
}
|
||||||
|
}
|
@ -19,9 +19,7 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
import ImageryView from './ImageryView';
|
||||||
import ImageryViewLayout from './components/ImageryViewLayout.vue';
|
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
export default function ImageryViewProvider(openmct) {
|
export default function ImageryViewProvider(openmct) {
|
||||||
const type = 'example.imagery';
|
const type = 'example.imagery';
|
||||||
@ -42,31 +40,8 @@ export default function ImageryViewProvider(openmct) {
|
|||||||
canView: function (domainObject) {
|
canView: function (domainObject) {
|
||||||
return hasImageTelemetry(domainObject);
|
return hasImageTelemetry(domainObject);
|
||||||
},
|
},
|
||||||
view: function (domainObject) {
|
view: function (domainObject, objectPath) {
|
||||||
let component;
|
return new ImageryView(openmct, domainObject, objectPath);
|
||||||
|
|
||||||
return {
|
|
||||||
show: function (element) {
|
|
||||||
component = new Vue({
|
|
||||||
el: element,
|
|
||||||
components: {
|
|
||||||
ImageryViewLayout
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct,
|
|
||||||
domainObject
|
|
||||||
},
|
|
||||||
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy: function () {
|
|
||||||
component.$destroy();
|
|
||||||
component = undefined;
|
|
||||||
},
|
|
||||||
_getInstance: function () {
|
|
||||||
return component;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="c-compass"
|
class="c-compass"
|
||||||
:style="`width: ${ sizedImageDimensions.width }px; height: ${ sizedImageDimensions.height }px`"
|
:style="`width: 100%; height: 100%`"
|
||||||
>
|
>
|
||||||
<CompassHUD
|
<CompassHUD
|
||||||
v-if="hasCameraFieldOfView"
|
v-if="hasCameraFieldOfView"
|
||||||
@ -33,13 +33,12 @@
|
|||||||
/>
|
/>
|
||||||
<CompassRose
|
<CompassRose
|
||||||
v-if="hasCameraFieldOfView"
|
v-if="hasCameraFieldOfView"
|
||||||
:heading="heading"
|
|
||||||
:sized-image-width="sizedImageDimensions.width"
|
|
||||||
:sun-heading="sunHeading"
|
|
||||||
:camera-angle-of-view="cameraAngleOfView"
|
:camera-angle-of-view="cameraAngleOfView"
|
||||||
:camera-pan="cameraPan"
|
:camera-pan="cameraPan"
|
||||||
:lock-compass="lockCompass"
|
:compass-rose-sizing-classes="compassRoseSizingClasses"
|
||||||
@toggle-lock-compass="toggleLockCompass"
|
:heading="heading"
|
||||||
|
:sized-image-dimensions="sizedImageDimensions"
|
||||||
|
:sun-heading="sunHeading"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -56,42 +55,20 @@ export default {
|
|||||||
CompassRose
|
CompassRose
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
containerWidth: {
|
compassRoseSizingClasses: {
|
||||||
type: Number,
|
type: String,
|
||||||
required: true
|
|
||||||
},
|
|
||||||
containerHeight: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
naturalAspectRatio: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
lockCompass: {
|
sizedImageDimensions: {
|
||||||
type: Boolean,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
sizedImageDimensions() {
|
|
||||||
let sizedImageDimensions = {};
|
|
||||||
if ((this.containerWidth / this.containerHeight) > this.naturalAspectRatio) {
|
|
||||||
// container is wider than image
|
|
||||||
sizedImageDimensions.width = this.containerHeight * this.naturalAspectRatio;
|
|
||||||
sizedImageDimensions.height = this.containerHeight;
|
|
||||||
} else {
|
|
||||||
// container is taller than image
|
|
||||||
sizedImageDimensions.width = this.containerWidth;
|
|
||||||
sizedImageDimensions.height = this.containerWidth * this.naturalAspectRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sizedImageDimensions;
|
|
||||||
},
|
|
||||||
hasCameraFieldOfView() {
|
hasCameraFieldOfView() {
|
||||||
return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
|
return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
|
||||||
},
|
},
|
||||||
|
@ -21,152 +21,203 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div ref="compassRoseWrapper"
|
||||||
class="w-direction-rose"
|
class="w-direction-rose"
|
||||||
:class="compassRoseSizingClasses"
|
:class="compassRoseSizingClasses"
|
||||||
|
@click="toggleLockCompass"
|
||||||
>
|
>
|
||||||
<div
|
<svg ref="compassRoseSvg"
|
||||||
class="c-direction-rose"
|
class="c-compass-rose-svg"
|
||||||
@click="toggleLockCompass"
|
viewBox="0 0 100 100"
|
||||||
>
|
>
|
||||||
<div
|
<mask id="mask0"
|
||||||
class="c-nsew"
|
mask-type="alpha"
|
||||||
:style="compassRoseStyle"
|
maskUnits="userSpaceOnUse"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
>
|
>
|
||||||
<svg
|
<circle cx="50"
|
||||||
class="c-nsew__minor-ticks"
|
cy="50"
|
||||||
viewBox="0 0 100 100"
|
r="50"
|
||||||
|
fill="black"
|
||||||
|
/>
|
||||||
|
</mask>
|
||||||
|
<g class="c-cr__compass-wrapper">
|
||||||
|
<g class="c-cr__compass-main"
|
||||||
|
mask="url(#mask0)"
|
||||||
>
|
>
|
||||||
<rect
|
<!-- Background and clipped elements -->
|
||||||
class="c-nsew__tick c-tick-ne"
|
<rect class="c-cr__bg"
|
||||||
x="49"
|
width="100"
|
||||||
y="0"
|
height="100"
|
||||||
width="2"
|
fill="black"
|
||||||
height="5"
|
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect class="c-cr__edge"
|
||||||
class="c-nsew__tick c-tick-se"
|
width="100"
|
||||||
x="95"
|
height="100"
|
||||||
y="49"
|
fill="url(#paint0_radial)"
|
||||||
width="5"
|
|
||||||
height="2"
|
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect v-if="hasSunHeading"
|
||||||
class="c-nsew__tick c-tick-sw"
|
class="c-cr__sun"
|
||||||
x="49"
|
width="100"
|
||||||
y="95"
|
height="100"
|
||||||
width="2"
|
fill="url(#paint1_radial)"
|
||||||
height="5"
|
:style="sunHeadingStyle"
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
class="c-nsew__tick c-tick-nw"
|
|
||||||
x="0"
|
|
||||||
y="49"
|
|
||||||
width="5"
|
|
||||||
height="2"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</svg>
|
<!-- Camera FOV -->
|
||||||
|
<mask id="mask2"
|
||||||
|
class="c-cr__cam-fov-l-mask"
|
||||||
|
mask-type="alpha"
|
||||||
|
maskUnits="userSpaceOnUse"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="50"
|
||||||
|
height="100"
|
||||||
|
>
|
||||||
|
<rect width="51"
|
||||||
|
height="100"
|
||||||
|
/>
|
||||||
|
</mask>
|
||||||
|
<mask id="mask1"
|
||||||
|
class="c-cr__cam-fov-r-mask"
|
||||||
|
mask-type="alpha"
|
||||||
|
maskUnits="userSpaceOnUse"
|
||||||
|
x="50"
|
||||||
|
y="0"
|
||||||
|
width="50"
|
||||||
|
height="100"
|
||||||
|
>
|
||||||
|
<rect x="49"
|
||||||
|
width="51"
|
||||||
|
height="100"
|
||||||
|
/>
|
||||||
|
</mask>
|
||||||
|
<g class="c-cr__cam-fov"
|
||||||
|
:style="cameraPanStyle"
|
||||||
|
>
|
||||||
|
<g mask="url(#mask2)">
|
||||||
|
<rect class="c-cr__cam-fov-r"
|
||||||
|
x="49"
|
||||||
|
width="51"
|
||||||
|
height="100"
|
||||||
|
:style="cameraFOVStyleRightHalf"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g mask="url(#mask1)">
|
||||||
|
<rect class="c-cr__cam-fov-l"
|
||||||
|
width="51"
|
||||||
|
height="100"
|
||||||
|
:style="cameraFOVStyleLeftHalf"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
|
||||||
<svg
|
<!-- Spacecraft body -->
|
||||||
class="c-nsew__ticks"
|
<path v-if="hasHeading"
|
||||||
viewBox="0 0 100 100"
|
class="c-cr__spacecraft-body"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M37 49C35.3431 49 34 50.3431 34 52V82C34 83.6569 35.3431 85 37 85H63C64.6569 85 66 83.6569 66 82V52C66 50.3431 64.6569 49 63 49H37ZM50 52L58 60H55V67H45V60H42L50 52Z"
|
||||||
|
:style="headingStyle"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- NSEW and ticks -->
|
||||||
|
<g class="c-cr__nsew"
|
||||||
|
:style="compassRoseStyle"
|
||||||
>
|
>
|
||||||
<polygon
|
<g class="c-cr__ticks-major">
|
||||||
class="c-nsew__tick c-tick-n"
|
<path d="M50 3L43 10H57L50 3Z" />
|
||||||
points="50,0 60,10 40,10"
|
<path d="M4 51V49H10V51H4Z"
|
||||||
|
class="--hide-min"
|
||||||
|
/>
|
||||||
|
<path d="M49 96V90H51V96H49Z"
|
||||||
|
class="--hide-min"
|
||||||
|
/>
|
||||||
|
<path d="M90 49V51H96V49H90Z"
|
||||||
|
class="--hide-min"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g class="c-cr__ticks-minor --hide-small">
|
||||||
|
<path d="M4 51V49H10V51H4Z" />
|
||||||
|
<path d="M90 51V49H96V51H90Z" />
|
||||||
|
<path d="M51 96H49V90H51V96Z" />
|
||||||
|
<path d="M51 10L49 10V4L51 4V10Z" />
|
||||||
|
</g>
|
||||||
|
<g class="c-cr__nsew-text">
|
||||||
|
<path :style="cardinalTextRotateW"
|
||||||
|
class="c-cr__nsew-w --hide-small"
|
||||||
|
d="M56.7418 45.004H54.1378L52.7238 52.312H52.6958L51.2258 45.004H48.7758L47.3058 52.312H47.2778L45.8638 45.004H43.2598L45.9618 55H48.6078L49.9798 48.112H50.0078L51.3798 55H53.9838L56.7418 45.004Z"
|
||||||
|
/>
|
||||||
|
<path :style="cardinalTextRotateE"
|
||||||
|
class="c-cr__nsew-e --hide-small"
|
||||||
|
d="M46.104 55H54.21V52.76H48.708V50.856H53.608V48.84H48.708V47.09H54.07V45.004H46.104V55Z"
|
||||||
|
/>
|
||||||
|
<path :style="cardinalTextRotateS"
|
||||||
|
class="c-cr__nsew-s --hide-small"
|
||||||
|
d="M45.6531 51.64C45.6671 54.202 47.6971 55.21 49.9931 55.21C52.1911 55.21 54.3471 54.398 54.3471 51.864C54.3471 50.058 52.8911 49.386 51.4491 48.98C49.9931 48.574 48.5511 48.434 48.5511 47.664C48.5511 47.006 49.2511 46.81 49.8111 46.81C50.6091 46.81 51.4631 47.104 51.4211 48.014H54.0251C54.0111 45.76 52.0091 44.794 50.0211 44.794C48.1451 44.794 45.9471 45.648 45.9471 47.832C45.9471 49.666 47.4451 50.31 48.8731 50.716C50.3151 51.122 51.7431 51.29 51.7431 52.172C51.7431 52.914 50.9311 53.194 50.1471 53.194C49.0411 53.194 48.3131 52.816 48.2571 51.64H45.6531Z"
|
||||||
|
/>
|
||||||
|
<path :style="cardinalTextRotateN"
|
||||||
|
class="c-cr__nsew-n"
|
||||||
|
d="M42.5935 60H46.7935V49.32H46.8415L52.7935 60H57.3775V42.864H53.1775V53.424H53.1295L47.1775 42.864H42.5935V60Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="paint0_radial"
|
||||||
|
cx="0"
|
||||||
|
cy="0"
|
||||||
|
r="1"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="translate(50 50) rotate(90) scale(50)"
|
||||||
|
>
|
||||||
|
<stop offset="0.751387"
|
||||||
|
stop-opacity="0"
|
||||||
/>
|
/>
|
||||||
<rect
|
<stop offset="1"
|
||||||
class="c-nsew__tick c-tick-e"
|
stop-color="white"
|
||||||
x="95"
|
|
||||||
y="49"
|
|
||||||
width="5"
|
|
||||||
height="2"
|
|
||||||
/>
|
/>
|
||||||
<rect
|
</radialGradient>
|
||||||
class="c-nsew__tick c-tick-w"
|
<radialGradient id="paint1_radial"
|
||||||
x="0"
|
cx="0"
|
||||||
y="49"
|
cy="0"
|
||||||
width="5"
|
r="1"
|
||||||
height="2"
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="translate(50 -7) rotate(-90) scale(18.5)"
|
||||||
|
>
|
||||||
|
<stop offset="0.716377"
|
||||||
|
stop-color="#FFCC00"
|
||||||
/>
|
/>
|
||||||
<rect
|
<stop offset="1"
|
||||||
class="c-nsew__tick c-tick-s"
|
stop-color="#FF9900"
|
||||||
x="49"
|
stop-opacity="0"
|
||||||
y="95"
|
|
||||||
width="2"
|
|
||||||
height="5"
|
|
||||||
/>
|
/>
|
||||||
|
</radialGradient>
|
||||||
<text
|
</defs>
|
||||||
class="c-nsew__label c-label-n"
|
</svg>
|
||||||
text-anchor="middle"
|
|
||||||
:transform="northTextTransform"
|
|
||||||
>N</text>
|
|
||||||
<text
|
|
||||||
class="c-nsew__label c-label-e"
|
|
||||||
text-anchor="middle"
|
|
||||||
:transform="eastTextTransform"
|
|
||||||
>E</text>
|
|
||||||
<text
|
|
||||||
class="c-nsew__label c-label-w"
|
|
||||||
text-anchor="middle"
|
|
||||||
:transform="southTextTransform"
|
|
||||||
>W</text>
|
|
||||||
<text
|
|
||||||
class="c-nsew__label c-label-s"
|
|
||||||
text-anchor="middle"
|
|
||||||
:transform="westTextTransform"
|
|
||||||
>S</text>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="hasHeading"
|
|
||||||
class="c-spacecraft-body"
|
|
||||||
:style="headingStyle"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="hasSunHeading"
|
|
||||||
class="c-sun"
|
|
||||||
:style="sunHeadingStyle"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="c-cam-field"
|
|
||||||
:style="cameraPanStyle"
|
|
||||||
>
|
|
||||||
<div class="cam-field-half cam-field-half-l">
|
|
||||||
<div
|
|
||||||
class="cam-field-area"
|
|
||||||
:style="cameraFOVStyleLeftHalf"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<div class="cam-field-half cam-field-half-r">
|
|
||||||
<div
|
|
||||||
class="cam-field-area"
|
|
||||||
:style="cameraFOVStyleRightHalf"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { rotate } from './utils';
|
import { rotate } from './utils';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
sizedImageWidth: {
|
compassRoseSizingClasses: {
|
||||||
type: Number,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
heading: {
|
heading: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true,
|
||||||
|
default() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
sunHeading: {
|
sunHeading: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -178,58 +229,39 @@ export default {
|
|||||||
},
|
},
|
||||||
cameraPan: {
|
cameraPan: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true,
|
||||||
|
default() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
lockCompass: {
|
sizedImageDimensions: {
|
||||||
type: Boolean,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
lockCompass: true
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
compassRoseSizingClasses() {
|
|
||||||
let compassRoseSizingClasses = '';
|
|
||||||
if (this.sizedImageWidth < 300) {
|
|
||||||
compassRoseSizingClasses = '--rose-small --rose-min';
|
|
||||||
} else if (this.sizedImageWidth < 500) {
|
|
||||||
compassRoseSizingClasses = '--rose-small';
|
|
||||||
} else if (this.sizedImageWidth > 1000) {
|
|
||||||
compassRoseSizingClasses = '--rose-max';
|
|
||||||
}
|
|
||||||
|
|
||||||
return compassRoseSizingClasses;
|
|
||||||
},
|
|
||||||
compassRoseStyle() {
|
compassRoseStyle() {
|
||||||
return { transform: `rotate(${ this.north }deg)` };
|
return { transform: `rotate(${ this.north }deg)` };
|
||||||
},
|
},
|
||||||
north() {
|
north() {
|
||||||
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
||||||
},
|
},
|
||||||
northTextTransform() {
|
cardinalTextRotateN() {
|
||||||
return this.cardinalPointsTextTransform.north;
|
return { transform: `translateY(-27%) rotate(${ -this.north }deg)` };
|
||||||
},
|
},
|
||||||
eastTextTransform() {
|
cardinalTextRotateS() {
|
||||||
return this.cardinalPointsTextTransform.east;
|
return { transform: `translateY(30%) rotate(${ -this.north }deg)` };
|
||||||
},
|
},
|
||||||
southTextTransform() {
|
cardinalTextRotateE() {
|
||||||
return this.cardinalPointsTextTransform.south;
|
return { transform: `translateX(30%) rotate(${ -this.north }deg)` };
|
||||||
},
|
},
|
||||||
westTextTransform() {
|
cardinalTextRotateW() {
|
||||||
return this.cardinalPointsTextTransform.west;
|
return { transform: `translateX(-30%) rotate(${ -this.north }deg)` };
|
||||||
},
|
|
||||||
cardinalPointsTextTransform() {
|
|
||||||
/**
|
|
||||||
* cardinal points text must be rotated
|
|
||||||
* in the opposite direction that north is rotated
|
|
||||||
* to keep text vertically oriented
|
|
||||||
*/
|
|
||||||
const rotation = `rotate(${ -this.north })`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
north: `translate(50,23) ${ rotation }`,
|
|
||||||
east: `translate(82,50) ${ rotation }`,
|
|
||||||
south: `translate(18,50) ${ rotation }`,
|
|
||||||
west: `translate(50,82) ${ rotation }`
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
hasHeading() {
|
hasHeading() {
|
||||||
return this.heading !== undefined;
|
return this.heading !== undefined;
|
||||||
@ -238,7 +270,7 @@ export default {
|
|||||||
const rotation = rotate(this.north, this.heading);
|
const rotation = rotate(this.north, this.heading);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transform: `translateX(-50%) rotate(${ rotation }deg)`
|
transform: `rotate(${ rotation }deg)`
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
hasSunHeading() {
|
hasSunHeading() {
|
||||||
@ -262,20 +294,37 @@ export default {
|
|||||||
// rotated counter-clockwise from camera pan angle
|
// rotated counter-clockwise from camera pan angle
|
||||||
cameraFOVStyleLeftHalf() {
|
cameraFOVStyleLeftHalf() {
|
||||||
return {
|
return {
|
||||||
transform: `translateX(50%) rotate(${ -this.cameraAngleOfView / 2 }deg)`
|
transform: `rotate(${ this.cameraAngleOfView / 2 }deg)`
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// right half of camera field of view
|
// right half of camera field of view
|
||||||
// rotated clockwise from camera pan angle
|
// rotated clockwise from camera pan angle
|
||||||
cameraFOVStyleRightHalf() {
|
cameraFOVStyleRightHalf() {
|
||||||
return {
|
return {
|
||||||
transform: `translateX(-50%) rotate(${ this.cameraAngleOfView / 2 }deg)`
|
transform: `rotate(${ -this.cameraAngleOfView / 2 }deg)`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
sizedImageDimensions() {
|
||||||
|
this.debounceResizeSvg();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.debounceResizeSvg = throttle(this.resizeSvg, 100);
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.debounceResizeSvg();
|
||||||
|
});
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
resizeSvg() {
|
||||||
|
const svg = this.$refs.compassRoseSvg;
|
||||||
|
svg.setAttribute('width', this.$refs.compassRoseWrapper.clientWidth);
|
||||||
|
svg.setAttribute('height', this.$refs.compassRoseWrapper.clientHeight);
|
||||||
|
},
|
||||||
toggleLockCompass() {
|
toggleLockCompass() {
|
||||||
this.$emit('toggle-lock-compass');
|
this.lockCompass = !this.lockCompass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -12,9 +12,8 @@ $elemBg: rgba(black, 0.7);
|
|||||||
.c-compass {
|
.c-compass {
|
||||||
pointer-events: none; // This allows the image element to receive a browser-level context click
|
pointer-events: none; // This allows the image element to receive a browser-level context click
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 0;
|
||||||
top: 50%;
|
top: 0;
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@include userSelectNone;
|
@include userSelectNone;
|
||||||
}
|
}
|
||||||
@ -81,114 +80,55 @@ $elemBg: rgba(black, 0.7);
|
|||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************** COMPASS DIRECTIONS */
|
/***************************** COMPASS SVG */
|
||||||
.c-nsew {
|
.c-compass-rose-svg {
|
||||||
$color: $interfaceKeyColor;
|
$color: $interfaceKeyColor;
|
||||||
$inset: 5%;
|
position: absolute;
|
||||||
$tickHeightPerc: 15%;
|
top: 0; left: 0;
|
||||||
text-shadow: black 0 0 10px;
|
|
||||||
top: $inset;
|
|
||||||
right: $inset;
|
|
||||||
bottom: $inset;
|
|
||||||
left: $inset;
|
|
||||||
z-index: 3;
|
|
||||||
|
|
||||||
&__tick,
|
g, path, rect {
|
||||||
&__label {
|
// In an SVG, rotation occurs about the center of the SVG, not the element
|
||||||
fill: $color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__minor-ticks {
|
|
||||||
opacity: 0.5;
|
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
.c-cr {
|
||||||
dominant-baseline: central;
|
&__bg {
|
||||||
font-size: 1.25em;
|
fill: #000;
|
||||||
font-weight: bold;
|
opacity: 0.8;
|
||||||
}
|
|
||||||
|
|
||||||
.c-label-n {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/***************************** CAMERA FIELD ANGLE */
|
|
||||||
.c-cam-field {
|
|
||||||
$color: white;
|
|
||||||
opacity: 0.3;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 2;
|
|
||||||
|
|
||||||
.cam-field-half {
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
.cam-field-area {
|
|
||||||
background: $color;
|
|
||||||
top: -30%;
|
|
||||||
right: 0;
|
|
||||||
bottom: -30%;
|
|
||||||
left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// clip-paths overlap a bit to avoid a gap between halves
|
&__edge {
|
||||||
&-l {
|
opacity: 0.1;
|
||||||
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
|
||||||
|
|
||||||
.cam-field-area {
|
|
||||||
transform-origin: left center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-r {
|
&__sun {
|
||||||
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
opacity: 0.7;
|
||||||
|
|
||||||
.cam-field-area {
|
|
||||||
transform-origin: right center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/***************************** SPACECRAFT BODY */
|
&__cam-fov-l,
|
||||||
.c-spacecraft-body {
|
&__cam-fov-r {
|
||||||
$color: $interfaceKeyColor;
|
// Cam FOV indication
|
||||||
$s: 30%;
|
opacity: 0.2;
|
||||||
background: $color;
|
fill: #fff;
|
||||||
border-radius: 3px;
|
}
|
||||||
height: $s;
|
|
||||||
width: $s;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
opacity: 0.4;
|
|
||||||
transform-origin: center top;
|
|
||||||
transform: translateX(-50%); // center by default, overridden by CompassRose.vue / headingStyle()
|
|
||||||
|
|
||||||
&:before {
|
&__nsew-text,
|
||||||
// Direction arrow
|
&__spacecraft-body,
|
||||||
$color: rgba(black, 0.5);
|
&__ticks-major,
|
||||||
$arwPointerY: 60%;
|
&__ticks-minor {
|
||||||
$arwBodyOffset: 25%;
|
fill: $color;
|
||||||
background: $color;
|
}
|
||||||
content: '';
|
|
||||||
display: block;
|
&__ticks-minor {
|
||||||
position: absolute;
|
opacity: 0.5;
|
||||||
top: 10%;
|
transform: rotate(45deg);
|
||||||
right: 20%;
|
}
|
||||||
bottom: 50%;
|
|
||||||
left: 20%;
|
&__spacecraft-body {
|
||||||
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
opacity: 0.3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,32 +136,28 @@ $elemBg: rgba(black, 0.7);
|
|||||||
.w-direction-rose {
|
.w-direction-rose {
|
||||||
$s: 10%;
|
$s: 10%;
|
||||||
$m: 2%;
|
$m: 2%;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: all;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: $m;
|
bottom: $m;
|
||||||
left: $m;
|
left: $m;
|
||||||
width: $s;
|
width: $s;
|
||||||
padding-top: $s;
|
padding-top: $s;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
&.--rose-min {
|
&.--rose-min {
|
||||||
$s: 30px;
|
$s: 30px;
|
||||||
width: $s;
|
width: $s;
|
||||||
padding-top: $s;
|
padding-top: $s;
|
||||||
|
.--hide-min {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.--rose-small {
|
&.--rose-small {
|
||||||
.c-nsew__minor-ticks,
|
.--hide-small {
|
||||||
.c-tick-w,
|
|
||||||
.c-tick-s,
|
|
||||||
.c-tick-e,
|
|
||||||
.c-label-w,
|
|
||||||
.c-label-s,
|
|
||||||
.c-label-e {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-label-n {
|
|
||||||
font-size: 2.5em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.--rose-max {
|
&.--rose-max {
|
||||||
@ -230,44 +166,3 @@ $elemBg: rgba(black, 0.7);
|
|||||||
padding-top: $s;
|
padding-top: $s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-direction-rose {
|
|
||||||
$c2: rgba(white, 0.1);
|
|
||||||
background: $elemBg;
|
|
||||||
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
|
|
||||||
transform-origin: 0 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
clip-path: circle(50% at 50% 50%);
|
|
||||||
border-radius: 100%;
|
|
||||||
pointer-events: all;
|
|
||||||
|
|
||||||
svg, div {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sun
|
|
||||||
.c-sun {
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
$s: 35%;
|
|
||||||
@include sun();
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0.7;
|
|
||||||
top: 0;
|
|
||||||
left: 50%;
|
|
||||||
height: $s;
|
|
||||||
width: $s;
|
|
||||||
transform: translate(-50%, -60%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -55,28 +55,34 @@
|
|||||||
></a>
|
></a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-imagery__main-image__bg"
|
<div ref="imageBG"
|
||||||
|
class="c-imagery__main-image__bg"
|
||||||
:class="{'paused unnsynced': isPaused,'stale':false }"
|
:class="{'paused unnsynced': isPaused,'stale':false }"
|
||||||
|
@click="expand"
|
||||||
>
|
>
|
||||||
<img
|
<div class="image-wrapper"
|
||||||
ref="focusedImage"
|
:style="{
|
||||||
class="c-imagery__main-image__image js-imageryView-image"
|
'width': `${sizedImageDimensions.width}px`,
|
||||||
:src="imageUrl"
|
'height': `${sizedImageDimensions.height}px`
|
||||||
:style="{
|
}"
|
||||||
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
|
|
||||||
}"
|
|
||||||
:data-openmct-image-timestamp="time"
|
|
||||||
:data-openmct-object-keystring="keyString"
|
|
||||||
>
|
>
|
||||||
<Compass
|
<img ref="focusedImage"
|
||||||
v-if="shouldDisplayCompass"
|
class="c-imagery__main-image__image js-imageryView-image"
|
||||||
:container-width="imageContainerWidth"
|
:src="imageUrl"
|
||||||
:container-height="imageContainerHeight"
|
:style="{
|
||||||
:natural-aspect-ratio="focusedImageNaturalAspectRatio"
|
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
|
||||||
:image="focusedImage"
|
}"
|
||||||
:lock-compass="lockCompass"
|
:data-openmct-image-timestamp="time"
|
||||||
@toggle-lock-compass="toggleLockCompass"
|
:data-openmct-object-keystring="keyString"
|
||||||
/>
|
>
|
||||||
|
<Compass
|
||||||
|
v-if="shouldDisplayCompass"
|
||||||
|
:compass-rose-sizing-classes="compassRoseSizingClasses"
|
||||||
|
:image="focusedImage"
|
||||||
|
:natural-aspect-ratio="focusedImageNaturalAspectRatio"
|
||||||
|
:sized-image-dimensions="sizedImageDimensions"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
||||||
<button class="c-nav c-nav--prev"
|
<button class="c-nav c-nav--prev"
|
||||||
@ -165,8 +171,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Compass from './Compass/Compass.vue';
|
|
||||||
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
|
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
|
||||||
|
import Compass from './Compass/Compass.vue';
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
const REFRESH_CSS_MS = 500;
|
const REFRESH_CSS_MS = 500;
|
||||||
@ -190,7 +197,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
Compass
|
Compass
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
|
||||||
data() {
|
data() {
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
|
||||||
@ -224,6 +231,18 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
compassRoseSizingClasses() {
|
||||||
|
let compassRoseSizingClasses = '';
|
||||||
|
if (this.sizedImageDimensions.width < 300) {
|
||||||
|
compassRoseSizingClasses = '--rose-small --rose-min';
|
||||||
|
} else if (this.sizedImageDimensions.width < 500) {
|
||||||
|
compassRoseSizingClasses = '--rose-small';
|
||||||
|
} else if (this.sizedImageDimensions.width > 1000) {
|
||||||
|
compassRoseSizingClasses = '--rose-max';
|
||||||
|
}
|
||||||
|
|
||||||
|
return compassRoseSizingClasses;
|
||||||
|
},
|
||||||
time() {
|
time() {
|
||||||
return this.formatTime(this.focusedImage);
|
return this.formatTime(this.focusedImage);
|
||||||
},
|
},
|
||||||
@ -347,6 +366,20 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return isFresh;
|
return isFresh;
|
||||||
|
},
|
||||||
|
sizedImageDimensions() {
|
||||||
|
let sizedImageDimensions = {};
|
||||||
|
if ((this.imageContainerWidth / this.imageContainerHeight) > this.focusedImageNaturalAspectRatio) {
|
||||||
|
// container is wider than image
|
||||||
|
sizedImageDimensions.width = this.imageContainerHeight * this.focusedImageNaturalAspectRatio;
|
||||||
|
sizedImageDimensions.height = this.imageContainerHeight;
|
||||||
|
} else {
|
||||||
|
// container is taller than image
|
||||||
|
sizedImageDimensions.width = this.imageContainerWidth;
|
||||||
|
sizedImageDimensions.height = this.imageContainerWidth * this.focusedImageNaturalAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizedImageDimensions;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -395,7 +428,7 @@ export default {
|
|||||||
_.debounce(this.resizeImageContainer, 400);
|
_.debounce(this.resizeImageContainer, 400);
|
||||||
|
|
||||||
this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer);
|
this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer);
|
||||||
this.imageContainerResizeObserver.observe(this.$refs.focusedImage);
|
this.imageContainerResizeObserver.observe(this.$refs.imageBG);
|
||||||
|
|
||||||
// For adjusting scroll bar size and position when resizing thumbs wrapper
|
// For adjusting scroll bar size and position when resizing thumbs wrapper
|
||||||
this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
|
this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
|
||||||
@ -437,6 +470,16 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
expand() {
|
||||||
|
const actionCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
|
||||||
|
const visibleActions = actionCollection.getVisibleActions();
|
||||||
|
const viewLargeAction = visibleActions
|
||||||
|
&& visibleActions.find(action => action.key === 'large.view');
|
||||||
|
|
||||||
|
if (viewLargeAction && viewLargeAction.appliesTo(this.objectPath, this.currentView)) {
|
||||||
|
viewLargeAction.onItemClicked();
|
||||||
|
}
|
||||||
|
},
|
||||||
async initializeRelatedTelemetry() {
|
async initializeRelatedTelemetry() {
|
||||||
this.relatedTelemetry = new RelatedTelemetry(
|
this.relatedTelemetry = new RelatedTelemetry(
|
||||||
this.openmct,
|
this.openmct,
|
||||||
@ -833,12 +876,12 @@ export default {
|
|||||||
}, { once: true });
|
}, { once: true });
|
||||||
},
|
},
|
||||||
resizeImageContainer() {
|
resizeImageContainer() {
|
||||||
if (this.$refs.focusedImage.clientWidth !== this.imageContainerWidth) {
|
if (this.$refs.imageBG.clientWidth !== this.imageContainerWidth) {
|
||||||
this.imageContainerWidth = this.$refs.focusedImage.clientWidth;
|
this.imageContainerWidth = this.$refs.imageBG.clientWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.$refs.focusedImage.clientHeight !== this.imageContainerHeight) {
|
if (this.$refs.imageBG.clientHeight !== this.imageContainerHeight) {
|
||||||
this.imageContainerHeight = this.$refs.focusedImage.clientHeight;
|
this.imageContainerHeight = this.$refs.imageBG.clientHeight;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleThumbWindowResizeStart() {
|
handleThumbWindowResizeStart() {
|
||||||
@ -858,9 +901,6 @@ export default {
|
|||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.resizingWindow = false;
|
this.resizingWindow = false;
|
||||||
});
|
});
|
||||||
},
|
|
||||||
toggleLockCompass() {
|
|
||||||
this.lockCompass = !this.lockCompass;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.c-imagery {
|
.c-imagery {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 auto;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
@ -22,6 +22,9 @@
|
|||||||
&__bg {
|
&__bg {
|
||||||
background-color: $colorPlotBg;
|
background-color: $colorPlotBg;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
|
||||||
@ -33,7 +36,6 @@
|
|||||||
&__image {
|
&__image {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
object-fit: contain;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,6 +194,10 @@
|
|||||||
margin-right: $interiorMarginSm;
|
margin-right: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.s-status-taking-snapshot & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__lc {
|
&__lc {
|
||||||
@ -273,6 +279,10 @@
|
|||||||
content: $glyph-icon-play;
|
content: $glyph-icon-play;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.s-status-taking-snapshot & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-imagery__prev-next-buttons {
|
.c-imagery__prev-next-buttons {
|
||||||
@ -287,6 +297,10 @@
|
|||||||
.c-nav {
|
.c-nav {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.s-status-taking-snapshot & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-nav {
|
.c-nav {
|
||||||
|
@ -280,7 +280,7 @@ describe("The Imagery View Layout", () => {
|
|||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show the clicked thumbnail as the main image", (done) => {
|
xit("should show the clicked thumbnail as the main image", (done) => {
|
||||||
const target = imageTelemetry[5].url;
|
const target = imageTelemetry[5].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
@ -317,7 +317,7 @@ describe("The Imagery View Layout", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate via arrow keys", (done) => {
|
xit("should navigate via arrow keys", (done) => {
|
||||||
let keyOpts = {
|
let keyOpts = {
|
||||||
element: parent.querySelector('.c-imagery'),
|
element: parent.querySelector('.c-imagery'),
|
||||||
key: 'ArrowLeft',
|
key: 'ArrowLeft',
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getDefaultNotebook } from '../utils/notebook-storage';
|
import { getDefaultNotebook, getNotebookSectionAndPage } from '../utils/notebook-storage';
|
||||||
import { addNotebookEntry } from '../utils/notebook-entries';
|
import { addNotebookEntry } from '../utils/notebook-entries';
|
||||||
|
|
||||||
export default class CopyToNotebookAction {
|
export default class CopyToNotebookAction {
|
||||||
@ -15,26 +15,35 @@ export default class CopyToNotebookAction {
|
|||||||
|
|
||||||
copyToNotebook(entryText) {
|
copyToNotebook(entryText) {
|
||||||
const notebookStorage = getDefaultNotebook();
|
const notebookStorage = getDefaultNotebook();
|
||||||
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
|
this.openmct.objects.get(notebookStorage.identifier)
|
||||||
.then(domainObject => {
|
.then(domainObject => {
|
||||||
addNotebookEntry(this.openmct, domainObject, notebookStorage, null, entryText);
|
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}`;
|
const msg = `Saved to Notebook ${defaultPath}`;
|
||||||
this.openmct.notifications.info(msg);
|
this.openmct.notifications.info(msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke(objectPath, view = {}) {
|
invoke(objectPath, view) {
|
||||||
let viewContext = view.getViewContext && view.getViewContext();
|
const formattedValueForCopy = view.getViewContext().row.formattedValueForCopy;
|
||||||
|
|
||||||
this.copyToNotebook(viewContext.formattedValueForCopy());
|
this.copyToNotebook(formattedValueForCopy());
|
||||||
}
|
}
|
||||||
|
|
||||||
appliesTo(objectPath, view = {}) {
|
appliesTo(objectPath, view = {}) {
|
||||||
let viewContext = view.getViewContext && view.getViewContext();
|
const viewContext = view.getViewContext && view.getViewContext();
|
||||||
|
const row = viewContext && viewContext.row;
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return viewContext && viewContext.formattedValueForCopy
|
return row.formattedValueForCopy
|
||||||
&& typeof viewContext.formattedValueForCopy === 'function';
|
&& typeof row.formattedValueForCopy === 'function';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,14 +43,16 @@
|
|||||||
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
|
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
|
||||||
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
|
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
|
||||||
:default-page-id="defaultPageId"
|
:default-page-id="defaultPageId"
|
||||||
:selected-page-id="selectedPageId"
|
:selected-page-id="getSelectedPageId()"
|
||||||
:default-section-id="defaultSectionId"
|
:default-section-id="defaultSectionId"
|
||||||
:selected-section-id="selectedSectionId"
|
:selected-section-id="getSelectedSectionId()"
|
||||||
:domain-object="domainObject"
|
:domain-object="domainObject"
|
||||||
:page-title="domainObject.configuration.pageTitle"
|
:page-title="domainObject.configuration.pageTitle"
|
||||||
:section-title="domainObject.configuration.sectionTitle"
|
:section-title="domainObject.configuration.sectionTitle"
|
||||||
:sections="sections"
|
:sections="sections"
|
||||||
:sidebar-covers-entries="sidebarCoversEntries"
|
:sidebar-covers-entries="sidebarCoversEntries"
|
||||||
|
@defaultPageDeleted="cleanupDefaultNotebook"
|
||||||
|
@defaultSectionDeleted="cleanupDefaultNotebook"
|
||||||
@pagesChanged="pagesChanged"
|
@pagesChanged="pagesChanged"
|
||||||
@selectPage="selectPage"
|
@selectPage="selectPage"
|
||||||
@sectionsChanged="sectionsChanged"
|
@sectionsChanged="sectionsChanged"
|
||||||
@ -136,7 +138,7 @@ import NotebookEntry from './NotebookEntry.vue';
|
|||||||
import Search from '@/ui/components/search.vue';
|
import Search from '@/ui/components/search.vue';
|
||||||
import SearchResults from './SearchResults.vue';
|
import SearchResults from './SearchResults.vue';
|
||||||
import Sidebar from './Sidebar.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 { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
||||||
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
|
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
|
||||||
import objectUtils from 'objectUtils';
|
import objectUtils from 'objectUtils';
|
||||||
@ -164,8 +166,10 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedSectionId: this.getDefaultSectionId(),
|
defaultPageId: this.getDefaultPageId(),
|
||||||
selectedPageId: this.getDefaultPageId(),
|
defaultSectionId: this.getDefaultSectionId(),
|
||||||
|
selectedSectionId: this.getSelectedSectionId(),
|
||||||
|
selectedPageId: this.getSelectedPageId(),
|
||||||
defaultSort: this.domainObject.configuration.defaultSort,
|
defaultSort: this.domainObject.configuration.defaultSort,
|
||||||
focusEntryId: null,
|
focusEntryId: null,
|
||||||
search: '',
|
search: '',
|
||||||
@ -176,12 +180,6 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
defaultPageId() {
|
|
||||||
return this.getDefaultPageId();
|
|
||||||
},
|
|
||||||
defaultSectionId() {
|
|
||||||
return this.getDefaultSectionId();
|
|
||||||
},
|
|
||||||
filteredAndSortedEntries() {
|
filteredAndSortedEntries() {
|
||||||
const filterTime = Date.now();
|
const filterTime = Date.now();
|
||||||
const pageEntries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage) || [];
|
const pageEntries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage) || [];
|
||||||
@ -203,24 +201,38 @@ export default {
|
|||||||
},
|
},
|
||||||
selectedPage() {
|
selectedPage() {
|
||||||
const pages = this.getPages();
|
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) {
|
if (selectedPage) {
|
||||||
return selectedPage;
|
return selectedPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedPage && !pages.length) {
|
const defaultPage = pages.find(page => page.id === this.defaultPageId);
|
||||||
return undefined;
|
if (defaultPage) {
|
||||||
|
return defaultPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages[0];
|
return this.pages[0];
|
||||||
},
|
},
|
||||||
selectedSection() {
|
selectedSection() {
|
||||||
if (!this.sections.length) {
|
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: {
|
watch: {
|
||||||
@ -301,26 +313,29 @@ export default {
|
|||||||
this.sectionsChanged({ sections });
|
this.sectionsChanged({ sections });
|
||||||
this.resetSearch();
|
this.resetSearch();
|
||||||
},
|
},
|
||||||
|
cleanupDefaultNotebook() {
|
||||||
|
this.defaultPageId = undefined;
|
||||||
|
this.defaultSectionId = undefined;
|
||||||
|
this.removeDefaultClass(this.domainObject);
|
||||||
|
clearDefaultNotebook();
|
||||||
|
},
|
||||||
setSectionAndPageFromUrl() {
|
setSectionAndPageFromUrl() {
|
||||||
let sectionId = this.getSectionIdFromUrl() || this.selectedSectionId;
|
let sectionId = this.getSectionIdFromUrl() || this.getDefaultSectionId() || this.getSelectedSectionId();
|
||||||
let pageId = this.getPageIdFromUrl() || this.selectedPageId;
|
let pageId = this.getPageIdFromUrl() || this.getDefaultPageId() || this.getSelectedPageId();
|
||||||
|
|
||||||
this.selectSection(sectionId);
|
this.selectSection(sectionId);
|
||||||
this.selectPage(pageId);
|
this.selectPage(pageId);
|
||||||
},
|
},
|
||||||
createNotebookStorageObject() {
|
createNotebookStorageObject() {
|
||||||
const notebookMeta = {
|
|
||||||
name: this.domainObject.name,
|
|
||||||
identifier: this.domainObject.identifier,
|
|
||||||
link: this.getLinktoNotebook()
|
|
||||||
};
|
|
||||||
const page = this.selectedPage;
|
const page = this.selectedPage;
|
||||||
const section = this.selectedSection;
|
const section = this.selectedSection;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notebookMeta,
|
name: this.domainObject.name,
|
||||||
page,
|
identifier: this.domainObject.identifier,
|
||||||
section
|
link: this.getLinktoNotebook(),
|
||||||
|
defaultSectionId: section.id,
|
||||||
|
defaultPageId: page.id
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
deleteEntry(entryId) {
|
deleteEntry(entryId) {
|
||||||
@ -419,35 +434,21 @@ export default {
|
|||||||
this.sidebarCoversEntries = sidebarCoversEntries;
|
this.sidebarCoversEntries = sidebarCoversEntries;
|
||||||
},
|
},
|
||||||
getDefaultPageId() {
|
getDefaultPageId() {
|
||||||
let defaultPageId;
|
return this.isDefaultNotebook()
|
||||||
|
? getDefaultNotebook().defaultPageId
|
||||||
if (this.isDefaultNotebook()) {
|
: undefined;
|
||||||
defaultPageId = getDefaultNotebook().page.id;
|
|
||||||
} else {
|
|
||||||
const firstSection = this.getSections()[0];
|
|
||||||
defaultPageId = firstSection && firstSection.pages[0].id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultPageId;
|
|
||||||
},
|
},
|
||||||
isDefaultNotebook() {
|
isDefaultNotebook() {
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.notebookMeta.identifier;
|
const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.identifier;
|
||||||
|
|
||||||
return defaultNotebookIdentifier !== null
|
return defaultNotebookIdentifier !== null
|
||||||
&& this.openmct.objects.areIdsEqual(defaultNotebookIdentifier, this.domainObject.identifier);
|
&& this.openmct.objects.areIdsEqual(defaultNotebookIdentifier, this.domainObject.identifier);
|
||||||
},
|
},
|
||||||
getDefaultSectionId() {
|
getDefaultSectionId() {
|
||||||
let defaultSectionId;
|
return this.isDefaultNotebook()
|
||||||
|
? getDefaultNotebook().defaultSectionId
|
||||||
if (this.isDefaultNotebook()) {
|
: undefined;
|
||||||
defaultSectionId = getDefaultNotebook().section.id;
|
|
||||||
} else {
|
|
||||||
const firstSection = this.getSections()[0];
|
|
||||||
defaultSectionId = firstSection && firstSection.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultSectionId;
|
|
||||||
},
|
},
|
||||||
getDefaultNotebookObject() {
|
getDefaultNotebookObject() {
|
||||||
const oldNotebookStorage = getDefaultNotebook();
|
const oldNotebookStorage = getDefaultNotebook();
|
||||||
@ -455,7 +456,7 @@ export default {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.openmct.objects.get(oldNotebookStorage.notebookMeta.identifier);
|
return this.openmct.objects.get(oldNotebookStorage.identifier);
|
||||||
},
|
},
|
||||||
getLinktoNotebook() {
|
getLinktoNotebook() {
|
||||||
const objectPath = this.openmct.router.path;
|
const objectPath = this.openmct.router.path;
|
||||||
@ -573,6 +574,22 @@ export default {
|
|||||||
|
|
||||||
return selectedSection.pages;
|
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) {
|
newEntry(embed = null) {
|
||||||
this.resetSearch();
|
this.resetSearch();
|
||||||
const notebookStorage = this.createNotebookStorageObject();
|
const notebookStorage = this.createNotebookStorageObject();
|
||||||
@ -616,51 +633,26 @@ export default {
|
|||||||
},
|
},
|
||||||
async updateDefaultNotebook(notebookStorage) {
|
async updateDefaultNotebook(notebookStorage) {
|
||||||
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
||||||
if (!defaultNotebookObject) {
|
const isSameNotebook = defaultNotebookObject
|
||||||
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
|
&& objectUtils.makeKeyString(defaultNotebookObject.identifier) === objectUtils.makeKeyString(notebookStorage.identifier);
|
||||||
} else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
|
if (!isSameNotebook) {
|
||||||
this.removeDefaultClass(defaultNotebookObject);
|
this.removeDefaultClass(defaultNotebookObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defaultNotebookObject || !isSameNotebook) {
|
||||||
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
|
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.defaultSectionId && this.defaultSectionId.length === 0 || this.defaultSectionId !== notebookStorage.section.id) {
|
if (this.defaultSectionId !== notebookStorage.defaultSectionId) {
|
||||||
this.defaultSectionId = notebookStorage.section.id;
|
setDefaultNotebookSectionId(notebookStorage.defaultSectionId);
|
||||||
setDefaultNotebookSection(notebookStorage.section);
|
this.defaultSectionId = notebookStorage.defaultSectionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.defaultPageId && this.defaultPageId.length === 0 || this.defaultPageId !== notebookStorage.page.id) {
|
if (this.defaultPageId !== notebookStorage.defaultPageId) {
|
||||||
this.defaultPageId = notebookStorage.page.id;
|
setDefaultNotebookPageId(notebookStorage.defaultPageId);
|
||||||
setDefaultNotebookPage(notebookStorage.page);
|
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) {
|
updateDefaultNotebookSection(sections, id) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
@ -668,26 +660,26 @@ export default {
|
|||||||
|
|
||||||
const notebookStorage = getDefaultNotebook();
|
const notebookStorage = getDefaultNotebook();
|
||||||
if (!notebookStorage
|
if (!notebookStorage
|
||||||
|| notebookStorage.notebookMeta.identifier.key !== this.domainObject.identifier.key) {
|
|| notebookStorage.identifier.key !== this.domainObject.identifier.key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultNotebookSection = notebookStorage.section;
|
const defaultNotebookSectionId = notebookStorage.defaultSectionId;
|
||||||
const section = sections.find(s => s.id === id);
|
if (defaultNotebookSectionId === id) {
|
||||||
if (!section && defaultNotebookSection.id === id) {
|
const section = sections.find(s => s.id === id);
|
||||||
this.defaultSectionId = null;
|
if (!section) {
|
||||||
this.defaultPageId = null;
|
this.removeDefaultClass(this.domainObject);
|
||||||
this.removeDefaultClass(this.domainObject);
|
clearDefaultNotebook();
|
||||||
clearDefaultNotebook();
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id !== defaultNotebookSectionId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id !== defaultNotebookSection.id) {
|
setDefaultNotebookSectionId(defaultNotebookSectionId);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDefaultNotebookSection(section);
|
|
||||||
},
|
},
|
||||||
updateEntry(entry) {
|
updateEntry(entry) {
|
||||||
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
|
||||||
@ -715,19 +707,27 @@ export default {
|
|||||||
sectionId: this.selectedSectionId
|
sectionId: this.selectedSectionId
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
sectionsChanged({ sections, id = null }) {
|
sectionsChanged({ sections, id = undefined }) {
|
||||||
mutateObject(this.openmct, this.domainObject, 'configuration.sections', sections);
|
mutateObject(this.openmct, this.domainObject, 'configuration.sections', sections);
|
||||||
this.updateDefaultNotebookSection(sections, id);
|
this.updateDefaultNotebookSection(sections, id);
|
||||||
},
|
},
|
||||||
selectPage(pageId) {
|
selectPage(pageId) {
|
||||||
|
if (!pageId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedPageId = pageId;
|
this.selectedPageId = pageId;
|
||||||
this.syncUrlWithPageAndSection();
|
this.syncUrlWithPageAndSection();
|
||||||
},
|
},
|
||||||
selectSection(sectionId) {
|
selectSection(sectionId) {
|
||||||
|
if (!sectionId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedSectionId = sectionId;
|
this.selectedSectionId = sectionId;
|
||||||
|
|
||||||
const defaultPageId = this.selectedSection.pages[0].id;
|
const pageId = this.selectedSection.pages[0].id;
|
||||||
this.selectPage(defaultPageId);
|
this.selectPage(pageId);
|
||||||
|
|
||||||
this.syncUrlWithPageAndSection();
|
this.syncUrlWithPageAndSection();
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import PainterroInstance from '../utils/painterroInstance';
|
|||||||
import SnapshotTemplate from './snapshot-template.html';
|
import SnapshotTemplate from './snapshot-template.html';
|
||||||
|
|
||||||
import { updateNotebookImageDomainObject } from '../utils/notebook-image';
|
import { updateNotebookImageDomainObject } from '../utils/notebook-image';
|
||||||
|
import ImageExporter from '../../../exporters/ImageExporter';
|
||||||
|
|
||||||
import PopupMenu from './PopupMenu.vue';
|
import PopupMenu from './PopupMenu.vue';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
@ -71,7 +72,7 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.addPopupMenuItems();
|
this.addPopupMenuItems();
|
||||||
this.exportImageService = this.openmct.$injector.get('exportImageService');
|
this.imageExporter = new ImageExporter(this.openmct);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addPopupMenuItems() {
|
addPopupMenuItems() {
|
||||||
@ -101,7 +102,6 @@ export default {
|
|||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Cancel',
|
label: 'Cancel',
|
||||||
emphasis: true,
|
|
||||||
callback: () => {
|
callback: () => {
|
||||||
painterroInstance.dismiss();
|
painterroInstance.dismiss();
|
||||||
annotateOverlay.dismiss();
|
annotateOverlay.dismiss();
|
||||||
@ -109,6 +109,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Save',
|
label: 'Save',
|
||||||
|
emphasis: true,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
painterroInstance.save((snapshotObject) => {
|
painterroInstance.save((snapshotObject) => {
|
||||||
annotateOverlay.dismiss();
|
annotateOverlay.dismiss();
|
||||||
@ -234,9 +235,9 @@ export default {
|
|||||||
let element = this.snapshot.$refs['snapshot-image'];
|
let element = this.snapshot.$refs['snapshot-image'];
|
||||||
|
|
||||||
if (type === 'png') {
|
if (type === 'png') {
|
||||||
this.exportImageService.exportPNG(element, this.embed.name);
|
this.imageExporter.exportPNG(element, this.embed.name);
|
||||||
} else {
|
} else {
|
||||||
this.exportImageService.exportJPG(element, this.embed.name);
|
this.imageExporter.exportJPG(element, this.embed.name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
previewEmbed() {
|
previewEmbed() {
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Snapshot from '../snapshot';
|
import Snapshot from '../snapshot';
|
||||||
import { getDefaultNotebook, validateNotebookStorageObject } from '../utils/notebook-storage';
|
import { getDefaultNotebook, getNotebookSectionAndPage, validateNotebookStorageObject } from '../utils/notebook-storage';
|
||||||
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
|
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -56,11 +56,10 @@ export default {
|
|||||||
this.setDefaultNotebookStatus();
|
this.setDefaultNotebookStatus();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getDefaultNotebookObject() {
|
getDefaultNotebookObject() {
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
const defaultNotebookObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
|
|
||||||
|
|
||||||
return defaultNotebookObject;
|
return defaultNotebook && this.openmct.objects.get(defaultNotebook.identifier);
|
||||||
},
|
},
|
||||||
async showMenu(event) {
|
async showMenu(event) {
|
||||||
const notebookTypes = [];
|
const notebookTypes = [];
|
||||||
@ -70,36 +69,39 @@ export default {
|
|||||||
|
|
||||||
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
const defaultNotebookObject = await this.getDefaultNotebookObject();
|
||||||
if (defaultNotebookObject) {
|
if (defaultNotebookObject) {
|
||||||
const name = defaultNotebookObject.name;
|
|
||||||
|
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
const sectionName = defaultNotebook.section.name;
|
const { section, page } = getNotebookSectionAndPage(defaultNotebookObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
|
||||||
const pageName = defaultNotebook.page.name;
|
if (section && page) {
|
||||||
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
|
const name = defaultNotebookObject.name;
|
||||||
|
const sectionName = section.name;
|
||||||
|
const pageName = page.name;
|
||||||
|
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
|
||||||
|
|
||||||
notebookTypes.push({
|
notebookTypes.push({
|
||||||
cssClass: 'icon-notebook',
|
cssClass: 'icon-notebook',
|
||||||
name: `Save to Notebook ${defaultPath}`,
|
name: `Save to Notebook ${defaultPath}`,
|
||||||
callBack: () => {
|
onItemClicked: () => {
|
||||||
return this.snapshot(NOTEBOOK_DEFAULT);
|
return this.snapshot(NOTEBOOK_DEFAULT, event.target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notebookTypes.push({
|
notebookTypes.push({
|
||||||
cssClass: 'icon-camera',
|
cssClass: 'icon-camera',
|
||||||
name: 'Save to Notebook Snapshots',
|
name: 'Save to Notebook Snapshots',
|
||||||
callBack: () => {
|
onItemClicked: () => {
|
||||||
return this.snapshot(NOTEBOOK_SNAPSHOT);
|
return this.snapshot(NOTEBOOK_SNAPSHOT, event.target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.openmct.menus.showMenu(x, y, notebookTypes);
|
this.openmct.menus.showMenu(x, y, notebookTypes);
|
||||||
},
|
},
|
||||||
snapshot(notebookType) {
|
snapshot(notebookType, target) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const element = document.querySelector('.c-overlay__contents')
|
const wrapper = target && target.closest('.js-notebook-snapshot-item-wrapper')
|
||||||
|| document.getElementsByClassName('l-shell__main-container')[0];
|
|| document;
|
||||||
|
const element = wrapper.querySelector('.js-notebook-snapshot-item');
|
||||||
|
|
||||||
const bounds = this.openmct.time.bounds();
|
const bounds = this.openmct.time.bounds();
|
||||||
const link = !this.ignoreLink
|
const link = !this.ignoreLink
|
||||||
@ -119,9 +121,8 @@ export default {
|
|||||||
},
|
},
|
||||||
setDefaultNotebookStatus() {
|
setDefaultNotebookStatus() {
|
||||||
let defaultNotebookObject = getDefaultNotebook();
|
let defaultNotebookObject = getDefaultNotebook();
|
||||||
|
if (defaultNotebookObject) {
|
||||||
if (defaultNotebookObject && defaultNotebookObject.notebookMeta) {
|
let notebookIdentifier = defaultNotebookObject.identifier;
|
||||||
let notebookIdentifier = defaultNotebookObject.notebookMeta.identifier;
|
|
||||||
|
|
||||||
this.openmct.status.set(notebookIdentifier, 'notebook-default');
|
this.openmct.status.set(notebookIdentifier, 'notebook-default');
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
<div class="c-object-label__type-icon icon-camera"></div>
|
<div class="c-object-label__type-icon icon-camera"></div>
|
||||||
<div class="c-object-label__name">
|
<div class="c-object-label__name">
|
||||||
Notebook Snapshots
|
Notebook Snapshots
|
||||||
<span v-if="snapshots.length"
|
</div>
|
||||||
class="l-browse-bar__object-details"
|
<div v-if="snapshots.length"
|
||||||
> {{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
|
class="l-browse-bar__object-details"
|
||||||
</span>
|
>{{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PopupMenu v-if="snapshots.length > 0"
|
<PopupMenu v-if="snapshots.length > 0"
|
||||||
|
@ -87,22 +87,26 @@ export default {
|
|||||||
|
|
||||||
const selectedPage = this.pages.find(p => p.isSelected);
|
const selectedPage = this.pages.find(p => p.isSelected);
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
const defaultpage = defaultNotebook && defaultNotebook.page;
|
const defaultPageId = defaultNotebook && defaultNotebook.defaultPageId;
|
||||||
const isPageSelected = selectedPage && selectedPage.id === id;
|
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);
|
const pages = this.pages.filter(s => s.id !== id);
|
||||||
let selectedPageId;
|
let selectedPageId;
|
||||||
|
|
||||||
if (isPageSelected && defaultpage) {
|
if (isPageSelected && defaultPageId) {
|
||||||
pages.forEach(s => {
|
pages.forEach(s => {
|
||||||
s.isSelected = false;
|
s.isSelected = false;
|
||||||
if (defaultpage && defaultpage.id === s.id) {
|
if (defaultPageId === s.id) {
|
||||||
selectedPageId = 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;
|
selectedPageId = pages[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,21 +75,25 @@ export default {
|
|||||||
|
|
||||||
const selectedSection = this.sections.find(s => s.id === this.selectedSectionId);
|
const selectedSection = this.sections.find(s => s.id === this.selectedSectionId);
|
||||||
const defaultNotebook = getDefaultNotebook();
|
const defaultNotebook = getDefaultNotebook();
|
||||||
const defaultSection = defaultNotebook && defaultNotebook.section;
|
const defaultSectionId = defaultNotebook && defaultNotebook.defaultSectionId;
|
||||||
const isSectionSelected = selectedSection && selectedSection.id === id;
|
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);
|
const sections = this.sections.filter(s => s.id !== id);
|
||||||
|
|
||||||
if (isSectionSelected && defaultSection) {
|
if (isSectionSelected && defaultSectionId) {
|
||||||
sections.forEach(s => {
|
sections.forEach(s => {
|
||||||
s.isSelected = false;
|
s.isSelected = false;
|
||||||
if (defaultSection && defaultSection.id === s.id) {
|
if (defaultSectionId === s.id) {
|
||||||
s.isSelected = true;
|
s.isSelected = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sections.length && isSectionSelected && (!defaultSection || isSectionDefault)) {
|
if (isSectionDefault) {
|
||||||
|
this.$emit('defaultSectionDeleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sections.length && isSectionSelected && (!defaultSectionId || isSectionDefault)) {
|
||||||
sections[0].isSelected = true;
|
sections[0].isSelected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user