Compare commits
26 Commits
example-im
...
1.7.5-mast
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d61cdc40a | ||
|
|
f8f2e7da9b | ||
|
|
240f58b2d0 | ||
|
|
7d3baee7b5 | ||
|
|
1312c9ef89 | ||
|
|
1f5cb7ca42 | ||
|
|
4a7ebe326c | ||
|
|
10da314a4a | ||
|
|
b3ceccd7fb | ||
|
|
1bde4c9a0c | ||
|
|
4b85360446 | ||
|
|
36fc4929fc | ||
|
|
0f848d9f4c | ||
|
|
41b860a547 | ||
|
|
254b3db966 | ||
|
|
b6cc1924aa | ||
|
|
3e97f32dba | ||
|
|
558b802228 | ||
|
|
de13f67ae5 | ||
|
|
0256cc4830 | ||
|
|
8422add614 | ||
|
|
2114697d6f | ||
|
|
412eaf599e | ||
|
|
0691a35dab | ||
|
|
f57191fd89 | ||
|
|
14066b5c4d |
@@ -1,36 +1,69 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
version: 2.1
|
||||
executors:
|
||||
linux:
|
||||
docker:
|
||||
- image: circleci/node:13-browsers
|
||||
environment:
|
||||
CHROME_BIN: "/usr/bin/google-chrome"
|
||||
steps:
|
||||
- checkout
|
||||
- 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
|
||||
- image: cimg/base:stable
|
||||
orbs:
|
||||
node: circleci/node@4.5.1
|
||||
browser-tools: circleci/browser-tools@1.1.3
|
||||
jobs:
|
||||
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:
|
||||
- build
|
||||
- test:
|
||||
name: node10-chrome
|
||||
node-version: lts/dubnium
|
||||
browser: ChromeHeadless
|
||||
always-pass: false
|
||||
- test:
|
||||
name: node12-firefoxESR
|
||||
node-version: lts/erbium
|
||||
browser: FirefoxESR
|
||||
always-pass: true
|
||||
- test:
|
||||
name: node14-chrome
|
||||
node-version: lts/fermium
|
||||
browser: ChromeHeadless
|
||||
always-pass: true
|
||||
|
||||
|
||||
|
||||
4
.github/workflows/lighthouse.yml
vendored
4
.github/workflows/lighthouse.yml
vendored
@@ -13,6 +13,8 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
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: lhci autorun
|
||||
@@ -23,9 +23,9 @@
|
||||
/*global module,process*/
|
||||
|
||||
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 reporters = ['progress', 'html'];
|
||||
const reporters = ['progress', 'html', 'junit'];
|
||||
|
||||
if (coverageEnabled) {
|
||||
reporters.push('coverage-istanbul');
|
||||
@@ -59,7 +59,8 @@ module.exports = (config) => {
|
||||
browsers: browsers,
|
||||
client: {
|
||||
jasmine: {
|
||||
random: false
|
||||
random: false,
|
||||
timeoutInterval: 30000
|
||||
}
|
||||
},
|
||||
customLaunchers: {
|
||||
@@ -67,6 +68,10 @@ module.exports = (config) => {
|
||||
base: 'Chrome',
|
||||
flags: ['--remote-debugging-port=9222'],
|
||||
debug: true
|
||||
},
|
||||
FirefoxESR: {
|
||||
base: 'FirefoxHeadless',
|
||||
name: 'FirefoxESR'
|
||||
}
|
||||
},
|
||||
colors: true,
|
||||
@@ -78,12 +83,21 @@ module.exports = (config) => {
|
||||
preserveDescribeNesting: true,
|
||||
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: {
|
||||
fixWebpackSourcePaths: true,
|
||||
dir: process.env.CIRCLE_ARTIFACTS ?
|
||||
process.env.CIRCLE_ARTIFACTS + '/coverage' :
|
||||
"dist/reports/coverage",
|
||||
dir: process.env.CIRCLE_ARTIFACTS
|
||||
? process.env.CIRCLE_ARTIFACTS + '/coverage'
|
||||
: "dist/reports/coverage",
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
thresholds: {
|
||||
global: {
|
||||
|
||||
19
package.json
19
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "1.7.4",
|
||||
"version": "1.7.5",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
@@ -34,20 +34,21 @@
|
||||
"git-rev-sync": "^1.4.0",
|
||||
"glob": ">= 3.0.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"html2canvas": "^1.0.0-alpha.12",
|
||||
"html2canvas": "^1.0.0-rc.7",
|
||||
"imports-loader": "^0.8.0",
|
||||
"istanbul-instrumenter-loader": "^3.0.1",
|
||||
"jasmine-core": "^3.1.0",
|
||||
"jasmine-core": "^3.7.1",
|
||||
"jsdoc": "^3.3.2",
|
||||
"karma": "5.1.1",
|
||||
"karma": "6.3.4",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-firefox-launcher": "2.1.0",
|
||||
"karma-cli": "2.0.0",
|
||||
"karma-coverage": "2.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-jasmine": "3.3.1",
|
||||
"karma-sourcemap-loader": "0.3.7",
|
||||
"karma-jasmine": "4.0.1",
|
||||
"karma-sourcemap-loader": "0.3.8",
|
||||
"karma-webpack": "4.0.2",
|
||||
"location-bar": "^3.0.1",
|
||||
"lodash": "^4.17.12",
|
||||
@@ -89,6 +90,7 @@
|
||||
"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: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",
|
||||
"verify": "concurrently 'npm:test' 'npm:lint'",
|
||||
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
||||
@@ -100,6 +102,9 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/nasa/openmct.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.10.2 <16.0.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"private": true
|
||||
|
||||
@@ -181,7 +181,7 @@ define([
|
||||
],
|
||||
"category": "contextual",
|
||||
"name": "Stop",
|
||||
"cssClass": "icon-box",
|
||||
"cssClass": "icon-box-round-corners",
|
||||
"priority": "preferred"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -101,7 +101,7 @@ define(
|
||||
name: "Pause"
|
||||
});
|
||||
mockStop.getMetadata.and.returnValue({
|
||||
cssClass: "icon-box",
|
||||
cssClass: "icon-box-round-corners",
|
||||
name: "Stop"
|
||||
});
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
@@ -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,52 +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([
|
||||
"./res/templates/deprecated-timeline-message.html"
|
||||
], function (
|
||||
deprecatedTimelineMessage
|
||||
) {
|
||||
return {
|
||||
name: 'platform/features/timeline',
|
||||
definition: {
|
||||
extensions: {
|
||||
types: [
|
||||
{
|
||||
key: "timeline",
|
||||
name: "Timeline",
|
||||
description: "Timeline, Activity and Activity Mode objects have been deprecated and will no longer be supported. (07/18/2018)",
|
||||
priority: 502
|
||||
}
|
||||
],
|
||||
views: [
|
||||
{
|
||||
key: "timeline",
|
||||
name: "Timeline",
|
||||
type: "timeline",
|
||||
description: "Timeline, Activity and Activity Mode objects have been deprecated and will no longer be supported. (07/18/2018)",
|
||||
template: deprecatedTimelineMessage
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -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>
|
||||
@@ -173,10 +173,11 @@ define([
|
||||
const limitEvaluator = oldObject.getCapability("limit");
|
||||
|
||||
return {
|
||||
limits: function () {
|
||||
return limitEvaluator.limits();
|
||||
limits: () => {
|
||||
return limitEvaluator.limits.then !== undefined
|
||||
? limitEvaluator.limits()
|
||||
: Promise.resolve(limitEvaluator.limits());
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ const DEFAULTS = [
|
||||
'platform/telemetry',
|
||||
'platform/features/clock',
|
||||
'platform/features/hyperlink',
|
||||
'platform/features/timeline',
|
||||
'platform/forms',
|
||||
'platform/identity',
|
||||
'platform/persistence/aggregator',
|
||||
@@ -84,7 +83,6 @@ define([
|
||||
'../platform/features/my-items/bundle',
|
||||
'../platform/features/hyperlink/bundle',
|
||||
'../platform/features/static-markup/bundle',
|
||||
'../platform/features/timeline/bundle',
|
||||
'../platform/forms/bundle',
|
||||
'../platform/framework/bundle',
|
||||
'../platform/framework/src/load/Bundle',
|
||||
|
||||
@@ -45,6 +45,7 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.updateTimeSettings();
|
||||
this.openmct.router.on('change:params', this.updateTimeSettings);
|
||||
|
||||
TIME_EVENTS.forEach(event => {
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
this.subscriptions = {};
|
||||
this.telemetryObjects = {};
|
||||
this.testData = {
|
||||
conditionTestData: [],
|
||||
conditionTestInputs: this.conditionSetDomainObject.configuration.conditionTestData,
|
||||
applied: false
|
||||
};
|
||||
this.initialize();
|
||||
@@ -154,8 +154,10 @@ export default class ConditionManager extends EventEmitter {
|
||||
|
||||
updateConditionDescription(condition) {
|
||||
const found = this.conditionSetDomainObject.configuration.conditionCollection.find(conditionConfiguration => (conditionConfiguration.id === condition.id));
|
||||
found.summary = condition.description;
|
||||
this.persistConditions();
|
||||
if (found.summary !== condition.description) {
|
||||
found.summary = condition.description;
|
||||
this.persistConditions();
|
||||
}
|
||||
}
|
||||
|
||||
initCondition(conditionConfiguration, index) {
|
||||
@@ -414,8 +416,10 @@ export default class ConditionManager extends EventEmitter {
|
||||
}
|
||||
|
||||
updateTestData(testData) {
|
||||
this.testData = testData;
|
||||
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
|
||||
if (!_.isEqual(testData, this.testData)) {
|
||||
this.testData = testData;
|
||||
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
|
||||
}
|
||||
}
|
||||
|
||||
persistConditions() {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="c-compass"
|
||||
:style="`width: ${ sizedImageDimensions.width }px; height: ${ sizedImageDimensions.height }px`"
|
||||
:style="`width: 100%; height: 100%`"
|
||||
>
|
||||
<CompassHUD
|
||||
v-if="hasCameraFieldOfView"
|
||||
@@ -33,13 +33,12 @@
|
||||
/>
|
||||
<CompassRose
|
||||
v-if="hasCameraFieldOfView"
|
||||
:heading="heading"
|
||||
:sized-image-width="sizedImageDimensions.width"
|
||||
:sun-heading="sunHeading"
|
||||
:camera-angle-of-view="cameraAngleOfView"
|
||||
:camera-pan="cameraPan"
|
||||
:lock-compass="lockCompass"
|
||||
@toggle-lock-compass="toggleLockCompass"
|
||||
:compass-rose-sizing-classes="compassRoseSizingClasses"
|
||||
:heading="heading"
|
||||
:sized-image-dimensions="sizedImageDimensions"
|
||||
:sun-heading="sunHeading"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -56,42 +55,20 @@ export default {
|
||||
CompassRose
|
||||
},
|
||||
props: {
|
||||
containerWidth: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
containerHeight: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
naturalAspectRatio: {
|
||||
type: Number,
|
||||
compassRoseSizingClasses: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
image: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
lockCompass: {
|
||||
type: Boolean,
|
||||
sizedImageDimensions: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
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() {
|
||||
return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
|
||||
},
|
||||
|
||||
@@ -21,152 +21,203 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="w-direction-rose"
|
||||
:class="compassRoseSizingClasses"
|
||||
<div ref="compassRoseWrapper"
|
||||
class="w-direction-rose"
|
||||
:class="compassRoseSizingClasses"
|
||||
@click="toggleLockCompass"
|
||||
>
|
||||
<div
|
||||
class="c-direction-rose"
|
||||
@click="toggleLockCompass"
|
||||
<svg ref="compassRoseSvg"
|
||||
class="c-compass-rose-svg"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<div
|
||||
class="c-nsew"
|
||||
:style="compassRoseStyle"
|
||||
<mask id="mask0"
|
||||
mask-type="alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="100"
|
||||
height="100"
|
||||
>
|
||||
<svg
|
||||
class="c-nsew__minor-ticks"
|
||||
viewBox="0 0 100 100"
|
||||
<circle cx="50"
|
||||
cy="50"
|
||||
r="50"
|
||||
fill="black"
|
||||
/>
|
||||
</mask>
|
||||
<g class="c-cr__compass-wrapper">
|
||||
<g class="c-cr__compass-main"
|
||||
mask="url(#mask0)"
|
||||
>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-ne"
|
||||
x="49"
|
||||
y="0"
|
||||
width="2"
|
||||
height="5"
|
||||
<!-- Background and clipped elements -->
|
||||
<rect class="c-cr__bg"
|
||||
width="100"
|
||||
height="100"
|
||||
fill="black"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-se"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
<rect class="c-cr__edge"
|
||||
width="100"
|
||||
height="100"
|
||||
fill="url(#paint0_radial)"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-sw"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-nw"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
<rect v-if="hasSunHeading"
|
||||
class="c-cr__sun"
|
||||
width="100"
|
||||
height="100"
|
||||
fill="url(#paint1_radial)"
|
||||
:style="sunHeadingStyle"
|
||||
/>
|
||||
|
||||
</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
|
||||
class="c-nsew__ticks"
|
||||
viewBox="0 0 100 100"
|
||||
<!-- Spacecraft body -->
|
||||
<path v-if="hasHeading"
|
||||
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
|
||||
class="c-nsew__tick c-tick-n"
|
||||
points="50,0 60,10 40,10"
|
||||
<g class="c-cr__ticks-major">
|
||||
<path d="M50 3L43 10H57L50 3Z" />
|
||||
<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
|
||||
class="c-nsew__tick c-tick-e"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
<stop offset="1"
|
||||
stop-color="white"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-w"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
</radialGradient>
|
||||
<radialGradient id="paint1_radial"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(50 -7) rotate(-90) scale(18.5)"
|
||||
>
|
||||
<stop offset="0.716377"
|
||||
stop-color="#FFCC00"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-s"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
<stop offset="1"
|
||||
stop-color="#FF9900"
|
||||
stop-opacity="0"
|
||||
/>
|
||||
|
||||
<text
|
||||
class="c-nsew__label c-label-n"
|
||||
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>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { rotate } from './utils';
|
||||
import { throttle } from 'lodash';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
sizedImageWidth: {
|
||||
type: Number,
|
||||
compassRoseSizingClasses: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
heading: {
|
||||
type: Number,
|
||||
required: true
|
||||
required: true,
|
||||
default() {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
sunHeading: {
|
||||
type: Number,
|
||||
@@ -178,58 +229,39 @@ export default {
|
||||
},
|
||||
cameraPan: {
|
||||
type: Number,
|
||||
required: true
|
||||
required: true,
|
||||
default() {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
lockCompass: {
|
||||
type: Boolean,
|
||||
sizedImageDimensions: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lockCompass: true
|
||||
};
|
||||
},
|
||||
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() {
|
||||
return { transform: `rotate(${ this.north }deg)` };
|
||||
},
|
||||
north() {
|
||||
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
||||
},
|
||||
northTextTransform() {
|
||||
return this.cardinalPointsTextTransform.north;
|
||||
cardinalTextRotateN() {
|
||||
return { transform: `translateY(-27%) rotate(${ -this.north }deg)` };
|
||||
},
|
||||
eastTextTransform() {
|
||||
return this.cardinalPointsTextTransform.east;
|
||||
cardinalTextRotateS() {
|
||||
return { transform: `translateY(30%) rotate(${ -this.north }deg)` };
|
||||
},
|
||||
southTextTransform() {
|
||||
return this.cardinalPointsTextTransform.south;
|
||||
cardinalTextRotateE() {
|
||||
return { transform: `translateX(30%) rotate(${ -this.north }deg)` };
|
||||
},
|
||||
westTextTransform() {
|
||||
return this.cardinalPointsTextTransform.west;
|
||||
},
|
||||
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 }`
|
||||
};
|
||||
cardinalTextRotateW() {
|
||||
return { transform: `translateX(-30%) rotate(${ -this.north }deg)` };
|
||||
},
|
||||
hasHeading() {
|
||||
return this.heading !== undefined;
|
||||
@@ -238,7 +270,7 @@ export default {
|
||||
const rotation = rotate(this.north, this.heading);
|
||||
|
||||
return {
|
||||
transform: `translateX(-50%) rotate(${ rotation }deg)`
|
||||
transform: `rotate(${ rotation }deg)`
|
||||
};
|
||||
},
|
||||
hasSunHeading() {
|
||||
@@ -262,20 +294,37 @@ export default {
|
||||
// rotated counter-clockwise from camera pan angle
|
||||
cameraFOVStyleLeftHalf() {
|
||||
return {
|
||||
transform: `translateX(50%) rotate(${ -this.cameraAngleOfView / 2 }deg)`
|
||||
transform: `rotate(${ this.cameraAngleOfView / 2 }deg)`
|
||||
};
|
||||
},
|
||||
// right half of camera field of view
|
||||
// rotated clockwise from camera pan angle
|
||||
cameraFOVStyleRightHalf() {
|
||||
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: {
|
||||
resizeSvg() {
|
||||
const svg = this.$refs.compassRoseSvg;
|
||||
svg.setAttribute('width', this.$refs.compassRoseWrapper.clientWidth);
|
||||
svg.setAttribute('height', this.$refs.compassRoseWrapper.clientHeight);
|
||||
},
|
||||
toggleLockCompass() {
|
||||
this.$emit('toggle-lock-compass');
|
||||
this.lockCompass = !this.lockCompass;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,9 +12,8 @@ $elemBg: rgba(black, 0.7);
|
||||
.c-compass {
|
||||
pointer-events: none; // This allows the image element to receive a browser-level context click
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
@include userSelectNone;
|
||||
}
|
||||
@@ -81,114 +80,55 @@ $elemBg: rgba(black, 0.7);
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/***************************** COMPASS DIRECTIONS */
|
||||
.c-nsew {
|
||||
/***************************** COMPASS SVG */
|
||||
.c-compass-rose-svg {
|
||||
$color: $interfaceKeyColor;
|
||||
$inset: 5%;
|
||||
$tickHeightPerc: 15%;
|
||||
text-shadow: black 0 0 10px;
|
||||
top: $inset;
|
||||
right: $inset;
|
||||
bottom: $inset;
|
||||
left: $inset;
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
|
||||
&__tick,
|
||||
&__label {
|
||||
fill: $color;
|
||||
}
|
||||
|
||||
&__minor-ticks {
|
||||
opacity: 0.5;
|
||||
g, path, rect {
|
||||
// In an SVG, rotation occurs about the center of the SVG, not the element
|
||||
transform-origin: center;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
&__label {
|
||||
dominant-baseline: central;
|
||||
font-size: 1.25em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.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;
|
||||
.c-cr {
|
||||
&__bg {
|
||||
fill: #000;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
// clip-paths overlap a bit to avoid a gap between halves
|
||||
&-l {
|
||||
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
||||
|
||||
.cam-field-area {
|
||||
transform-origin: left center;
|
||||
}
|
||||
&__edge {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
&-r {
|
||||
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
||||
|
||||
.cam-field-area {
|
||||
transform-origin: right center;
|
||||
}
|
||||
&__sun {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***************************** SPACECRAFT BODY */
|
||||
.c-spacecraft-body {
|
||||
$color: $interfaceKeyColor;
|
||||
$s: 30%;
|
||||
background: $color;
|
||||
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()
|
||||
&__cam-fov-l,
|
||||
&__cam-fov-r {
|
||||
// Cam FOV indication
|
||||
opacity: 0.2;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
&:before {
|
||||
// Direction arrow
|
||||
$color: rgba(black, 0.5);
|
||||
$arwPointerY: 60%;
|
||||
$arwBodyOffset: 25%;
|
||||
background: $color;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
right: 20%;
|
||||
bottom: 50%;
|
||||
left: 20%;
|
||||
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
||||
&__nsew-text,
|
||||
&__spacecraft-body,
|
||||
&__ticks-major,
|
||||
&__ticks-minor {
|
||||
fill: $color;
|
||||
}
|
||||
|
||||
&__ticks-minor {
|
||||
opacity: 0.5;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
&__spacecraft-body {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,32 +136,28 @@ $elemBg: rgba(black, 0.7);
|
||||
.w-direction-rose {
|
||||
$s: 10%;
|
||||
$m: 2%;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
bottom: $m;
|
||||
left: $m;
|
||||
width: $s;
|
||||
padding-top: $s;
|
||||
z-index: 2;
|
||||
|
||||
&.--rose-min {
|
||||
$s: 30px;
|
||||
width: $s;
|
||||
padding-top: $s;
|
||||
.--hide-min {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.--rose-small {
|
||||
.c-nsew__minor-ticks,
|
||||
.c-tick-w,
|
||||
.c-tick-s,
|
||||
.c-tick-e,
|
||||
.c-label-w,
|
||||
.c-label-s,
|
||||
.c-label-e {
|
||||
.--hide-small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.c-label-n {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.--rose-max {
|
||||
@@ -230,44 +166,3 @@ $elemBg: rgba(black, 0.7);
|
||||
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,33 @@
|
||||
></a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="c-imagery__main-image__bg"
|
||||
<div ref="imageBG"
|
||||
class="c-imagery__main-image__bg"
|
||||
:class="{'paused unnsynced': isPaused,'stale':false }"
|
||||
>
|
||||
<img
|
||||
ref="focusedImage"
|
||||
class="c-imagery__main-image__image js-imageryView-image"
|
||||
:src="imageUrl"
|
||||
:style="{
|
||||
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
|
||||
}"
|
||||
:data-openmct-image-timestamp="time"
|
||||
:data-openmct-object-keystring="keyString"
|
||||
<div class="image-wrapper"
|
||||
:style="{
|
||||
'width': `${sizedImageDimensions.width}px`,
|
||||
'height': `${sizedImageDimensions.height}px`
|
||||
}"
|
||||
>
|
||||
<Compass
|
||||
v-if="shouldDisplayCompass"
|
||||
:container-width="imageContainerWidth"
|
||||
:container-height="imageContainerHeight"
|
||||
:natural-aspect-ratio="focusedImageNaturalAspectRatio"
|
||||
:image="focusedImage"
|
||||
:lock-compass="lockCompass"
|
||||
@toggle-lock-compass="toggleLockCompass"
|
||||
/>
|
||||
<img ref="focusedImage"
|
||||
class="c-imagery__main-image__image js-imageryView-image"
|
||||
:src="imageUrl"
|
||||
:style="{
|
||||
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
|
||||
}"
|
||||
:data-openmct-image-timestamp="time"
|
||||
: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 class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
||||
<button class="c-nav c-nav--prev"
|
||||
@@ -224,6 +229,18 @@ export default {
|
||||
};
|
||||
},
|
||||
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() {
|
||||
return this.formatTime(this.focusedImage);
|
||||
},
|
||||
@@ -347,6 +364,20 @@ export default {
|
||||
}
|
||||
|
||||
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: {
|
||||
@@ -395,7 +426,7 @@ export default {
|
||||
_.debounce(this.resizeImageContainer, 400);
|
||||
|
||||
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
|
||||
this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
|
||||
@@ -833,12 +864,12 @@ export default {
|
||||
}, { once: true });
|
||||
},
|
||||
resizeImageContainer() {
|
||||
if (this.$refs.focusedImage.clientWidth !== this.imageContainerWidth) {
|
||||
this.imageContainerWidth = this.$refs.focusedImage.clientWidth;
|
||||
if (this.$refs.imageBG.clientWidth !== this.imageContainerWidth) {
|
||||
this.imageContainerWidth = this.$refs.imageBG.clientWidth;
|
||||
}
|
||||
|
||||
if (this.$refs.focusedImage.clientHeight !== this.imageContainerHeight) {
|
||||
this.imageContainerHeight = this.$refs.focusedImage.clientHeight;
|
||||
if (this.$refs.imageBG.clientHeight !== this.imageContainerHeight) {
|
||||
this.imageContainerHeight = this.$refs.imageBG.clientHeight;
|
||||
}
|
||||
},
|
||||
handleThumbWindowResizeStart() {
|
||||
@@ -858,9 +889,6 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.resizingWindow = false;
|
||||
});
|
||||
},
|
||||
toggleLockCompass() {
|
||||
this.lockCompass = !this.lockCompass;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
&__bg {
|
||||
background-color: $colorPlotBg;
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1 1 auto;
|
||||
height: 0;
|
||||
|
||||
@@ -33,7 +36,6 @@
|
||||
&__image {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +194,10 @@
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
.s-status-taking-snapshot & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__lc {
|
||||
@@ -273,6 +279,10 @@
|
||||
content: $glyph-icon-play;
|
||||
}
|
||||
}
|
||||
|
||||
.s-status-taking-snapshot & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.c-imagery__prev-next-buttons {
|
||||
@@ -287,6 +297,10 @@
|
||||
.c-nav {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.s-status-taking-snapshot & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.c-nav {
|
||||
|
||||
@@ -280,7 +280,7 @@ describe("The Imagery View Layout", () => {
|
||||
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;
|
||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||
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 = {
|
||||
element: parent.querySelector('.c-imagery'),
|
||||
key: 'ArrowLeft',
|
||||
|
||||
@@ -101,7 +101,6 @@ export default {
|
||||
buttons: [
|
||||
{
|
||||
label: 'Cancel',
|
||||
emphasis: true,
|
||||
callback: () => {
|
||||
painterroInstance.dismiss();
|
||||
annotateOverlay.dismiss();
|
||||
@@ -109,6 +108,7 @@ export default {
|
||||
},
|
||||
{
|
||||
label: 'Save',
|
||||
emphasis: true,
|
||||
callback: () => {
|
||||
painterroInstance.save((snapshotObject) => {
|
||||
annotateOverlay.dismiss();
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<div class="c-object-label__type-icon icon-camera"></div>
|
||||
<div class="c-object-label__name">
|
||||
Notebook Snapshots
|
||||
<span v-if="snapshots.length"
|
||||
class="l-browse-bar__object-details"
|
||||
> {{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="snapshots.length"
|
||||
class="l-browse-bar__object-details"
|
||||
>{{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
|
||||
</div>
|
||||
</div>
|
||||
<PopupMenu v-if="snapshots.length > 0"
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
<div class="l-browse-bar__start">
|
||||
<div class="l-browse-bar__object-name--w">
|
||||
<span class="c-object-label l-browse-bar__object-name"
|
||||
v-bind:class="cssClass"
|
||||
>
|
||||
<span class="c-object-label__type-icon"
|
||||
v-bind:class="cssClass"
|
||||
></span>
|
||||
<span class="c-object-label__name">{{ name }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -49,10 +49,6 @@ describe('the plugin', function () {
|
||||
child.style.height = '480px';
|
||||
element.appendChild(child);
|
||||
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 1597160002854,
|
||||
end: 1597181232854
|
||||
});
|
||||
openmct.on('start', done);
|
||||
openmct.start(appHolder);
|
||||
});
|
||||
@@ -105,6 +101,11 @@ describe('the plugin', function () {
|
||||
let planView;
|
||||
|
||||
beforeEach(() => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 1597160002854,
|
||||
end: 1597181232854
|
||||
});
|
||||
|
||||
planDomainObject = {
|
||||
identifier: {
|
||||
key: 'test-object',
|
||||
|
||||
@@ -427,9 +427,12 @@ export default {
|
||||
this.skipReloadOnInteraction = false;
|
||||
this.loadMoreData(newRange, true);
|
||||
} else {
|
||||
// If we're not panning or zooming (time conductor and plot x-axis times are not out of sync)
|
||||
// Drop any data that is more than 1x (max-min) before min.
|
||||
// Limit these purges to once a second.
|
||||
if (!this.nextPurge || this.nextPurge < Date.now()) {
|
||||
const isPanningOrZooming = this.isTimeOutOfSync;
|
||||
const purgeRecords = !isPanningOrZooming && (!this.nextPurge || (this.nextPurge < Date.now()));
|
||||
if (purgeRecords) {
|
||||
const keepRange = {
|
||||
min: newRange.min - (newRange.max - newRange.min),
|
||||
max: newRange.max
|
||||
|
||||
@@ -24,19 +24,23 @@ import Plot from './Plot.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function PlotViewProvider(openmct) {
|
||||
function hasTelemetry(domainObject) {
|
||||
function hasNumericTelemetry(domainObject) {
|
||||
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
||||
|
||||
return metadata.values().length > 0 && hasDomainAndRange(metadata);
|
||||
return metadata.values().length > 0 && hasDomainAndNumericRange(metadata);
|
||||
}
|
||||
|
||||
function hasDomainAndRange(metadata) {
|
||||
return (metadata.valuesForHints(['range']).length > 0
|
||||
&& metadata.valuesForHints(['domain']).length > 0);
|
||||
function hasDomainAndNumericRange(metadata) {
|
||||
const rangeValues = metadata.valuesForHints(['range']);
|
||||
const domains = metadata.valuesForHints(['domain']);
|
||||
|
||||
return domains.length > 0
|
||||
&& rangeValues.length > 0
|
||||
&& !rangeValues.every(value => value.format === 'string');
|
||||
}
|
||||
|
||||
function isCompactView(objectPath) {
|
||||
@@ -44,11 +48,11 @@ export default function PlotViewProvider(openmct) {
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'plot-simple',
|
||||
key: 'plot-single',
|
||||
name: 'Plot',
|
||||
cssClass: 'icon-telemetry',
|
||||
canView(domainObject, objectPath) {
|
||||
return hasTelemetry(domainObject, openmct);
|
||||
return hasNumericTelemetry(domainObject);
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
||||
|
||||
@@ -201,15 +201,57 @@ describe("the plugin", function () {
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "yet-another-key",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
||||
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-simple");
|
||||
const plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-single");
|
||||
|
||||
expect(plotView).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not provide a plot view if the telemetry is entirely non numeric", () => {
|
||||
const testTelemetryObject = {
|
||||
id: "test-object",
|
||||
type: "test-object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "other-key",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "yet-another-key",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
||||
const plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-single");
|
||||
|
||||
expect(plotView).toBeUndefined();
|
||||
});
|
||||
|
||||
it("provides an overlay plot view for objects with telemetry", () => {
|
||||
const testTelemetryObject = {
|
||||
id: "test-object",
|
||||
@@ -279,6 +321,10 @@ describe("the plugin", function () {
|
||||
let plotView;
|
||||
|
||||
beforeEach(() => {
|
||||
openmct.time.timeSystem("utc", {
|
||||
start: 0,
|
||||
end: 4
|
||||
});
|
||||
const getFunc = openmct.$injector.get;
|
||||
spyOn(openmct.$injector, "get")
|
||||
.withArgs("exportImageService").and.returnValue({
|
||||
@@ -319,7 +365,7 @@ describe("the plugin", function () {
|
||||
};
|
||||
|
||||
applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
||||
plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === "plot-simple");
|
||||
plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === "plot-single");
|
||||
plotView = plotViewProvider.view(testTelemetryObject, [testTelemetryObject]);
|
||||
plotView.show(child, true);
|
||||
|
||||
@@ -567,7 +613,7 @@ describe("the plugin", function () {
|
||||
expect(legend.length).toBe(6);
|
||||
});
|
||||
|
||||
it("Renders X-axis ticks for the telemetry object", () => {
|
||||
xit("Renders X-axis ticks for the telemetry object", () => {
|
||||
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
|
||||
expect(xAxisElement.length).toBe(1);
|
||||
|
||||
|
||||
@@ -121,6 +121,11 @@ describe("the plugin", () => {
|
||||
let tableInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 0,
|
||||
end: 4
|
||||
});
|
||||
|
||||
testTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<button
|
||||
ref="startOffset"
|
||||
class="c-button c-conductor__delta-button"
|
||||
@click="showTimePopupStart"
|
||||
@click.prevent="showTimePopupStart"
|
||||
>
|
||||
{{ offsets.start }}
|
||||
</button>
|
||||
@@ -133,7 +133,7 @@
|
||||
<button
|
||||
ref="endOffset"
|
||||
class="c-button c-conductor__delta-button"
|
||||
@click="showTimePopupEnd"
|
||||
@click.prevent="showTimePopupEnd"
|
||||
>
|
||||
{{ offsets.end }}
|
||||
</button>
|
||||
|
||||
@@ -237,7 +237,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Prototype
|
||||
[class^='pr-tc-input-menu'] {
|
||||
// Uses ^= here to target both start and end menus
|
||||
background: $colorBodyBg;
|
||||
@@ -247,8 +246,7 @@
|
||||
grid-column-gap: 3px;
|
||||
grid-row-gap: 4px;
|
||||
align-items: start;
|
||||
|
||||
filter: brightness(1.4);
|
||||
filter: $filterMenu;
|
||||
box-shadow: $shdwMenu;
|
||||
padding: $interiorMargin;
|
||||
position: absolute;
|
||||
|
||||
@@ -237,11 +237,12 @@ $shdwSelect: rgba(black, 0.5) 0 0.5px 3px;
|
||||
$controlDisabledOpacity: 0.2;
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
||||
$colorMenuFg: pullForward($colorBodyFg, 30%);
|
||||
$colorMenuIc: pullForward($colorKey, 15%);
|
||||
$colorMenuHovBg: $colorMenuIc;
|
||||
$colorMenuHovFg: pullForward($colorMenuFg, 10%);
|
||||
$colorMenuBg: $colorBodyBg;
|
||||
$colorMenuFg: $colorBodyFg;
|
||||
$colorMenuIc: $colorKey;
|
||||
$filterMenu: brightness(1.4);
|
||||
$colorMenuHovBg: rgba($colorKey, 0.5);
|
||||
$colorMenuHovFg: $colorBodyFgEm;
|
||||
$colorMenuHovIc: $colorMenuHovFg;
|
||||
$colorMenuElementHilite: pullForward($colorMenuBg, 10%);
|
||||
$shdwMenu: rgba(black, 0.5) 0 1px 5px;
|
||||
|
||||
@@ -241,11 +241,12 @@ $shdwSelect: rgba(black, 0.5) 0 0.5px 3px;
|
||||
$controlDisabledOpacity: 0.2;
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
||||
$colorMenuFg: pullForward($colorBodyFg, 30%);
|
||||
$colorMenuIc: pullForward($colorKey, 15%);
|
||||
$colorMenuHovBg: $colorMenuIc;
|
||||
$colorMenuHovFg: pullForward($colorMenuFg, 10%);
|
||||
$colorMenuBg: $colorBodyBg;
|
||||
$colorMenuFg: $colorBodyFg;
|
||||
$colorMenuIc: $colorKey;
|
||||
$filterMenu: brightness(1.4);
|
||||
$colorMenuHovBg: rgba($colorKey, 0.5);
|
||||
$colorMenuHovFg: $colorBodyFgEm;
|
||||
$colorMenuHovIc: $colorMenuHovFg;
|
||||
$colorMenuElementHilite: pullForward($colorMenuBg, 10%);
|
||||
$shdwMenu: rgba(black, 0.5) 0 1px 5px;
|
||||
|
||||
@@ -237,9 +237,10 @@ $shdwSelect: none;
|
||||
$controlDisabledOpacity: 0.3;
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
||||
$colorMenuFg: pullForward($colorMenuBg, 70%);
|
||||
$colorMenuBg: $colorBodyBg;
|
||||
$colorMenuFg: $colorBodyFg;
|
||||
$colorMenuIc: $colorKey;
|
||||
$filterMenu: brightness(0.95);
|
||||
$colorMenuHovBg: $colorMenuIc;
|
||||
$colorMenuHovFg: $colorMenuBg;
|
||||
$colorMenuHovIc: $colorMenuBg;
|
||||
|
||||
@@ -458,6 +458,7 @@ select {
|
||||
@mixin menuOuter() {
|
||||
border-radius: $basicCr;
|
||||
background: $colorMenuBg;
|
||||
filter: $filterMenu;
|
||||
text-shadow: $shdwMenuText;
|
||||
padding: $interiorMarginSm;
|
||||
box-shadow: $shdwMenu;
|
||||
|
||||
@@ -490,7 +490,7 @@
|
||||
}
|
||||
|
||||
#snap-annotation {
|
||||
$m: $interiorMargin;
|
||||
$m: 0; //$interiorMargin;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
@@ -514,21 +514,32 @@
|
||||
// Holds tool buttons, color selectors, etc.
|
||||
$h: 22px;
|
||||
$fs: 0.8rem;
|
||||
$m: $interiorMarginSm;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: $h + ($m * 2) !important;
|
||||
margin-bottom: $interiorMarginLg;
|
||||
order: 1;
|
||||
flex: 0 0 auto;
|
||||
height: auto;
|
||||
background-color: transparent !important;
|
||||
margin-bottom: $interiorMargin;
|
||||
padding: $interiorMarginSm;
|
||||
|
||||
> div > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: $fs;
|
||||
> div {
|
||||
display: contents;
|
||||
|
||||
> * + * { margin-left: $interiorMargin !important; }
|
||||
}
|
||||
|
||||
.ptro-tool-controls {
|
||||
display: flex;
|
||||
margin-left: $interiorMarginLg !important;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin !important;
|
||||
}
|
||||
}
|
||||
|
||||
> div,
|
||||
> div > span,
|
||||
.ptro-icon-btn,
|
||||
.ptro-named-btn,
|
||||
.ptro-color-btn,
|
||||
@@ -538,27 +549,18 @@
|
||||
.tool-controls,
|
||||
.ptro-input {
|
||||
// Lot of resets for crappy CSS in Painterro
|
||||
&:first-child {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
display: inline-block;
|
||||
font-family: inherit;
|
||||
font-size: $fs !important;
|
||||
height: $h !important;
|
||||
margin: 0 0 0 5px;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
width: auto !important;
|
||||
line-height: $h !important;
|
||||
top: auto;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.ptro-tool-ctl-name {
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
color: $colorBodyFg;
|
||||
top: auto;
|
||||
font-family: inherit;
|
||||
padding: 0;
|
||||
@@ -568,13 +570,15 @@
|
||||
width: $h !important;
|
||||
}
|
||||
|
||||
.ptro-check,
|
||||
.ptro-color-control,
|
||||
.ptro-icon-btn,
|
||||
.ptro-named-btn {
|
||||
// Buttons in toolbar. Why the f* they're named like this is a mystery
|
||||
background-color: $colorBtnBg;
|
||||
color: $colorBtnFg;
|
||||
padding: 0 $interiorMargin;
|
||||
// Buttons in toolbar
|
||||
border-radius: $smallCr;
|
||||
box-shadow: rgba($colorBtnFg, 0.3) 0 0 0 1px;
|
||||
color: $colorBtnFg !important;
|
||||
padding: 1px $interiorMargin;
|
||||
|
||||
&:hover {
|
||||
background: $colorBtnBgHov;
|
||||
@@ -588,6 +592,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ptro-color-control,
|
||||
.ptro-icon-btn,
|
||||
.ptro-named-btn {
|
||||
// Buttons in toolbar
|
||||
background-color: $colorBtnBg;
|
||||
}
|
||||
|
||||
.ptro-color-active-control {
|
||||
background: $colorBtnMajorBg !important;
|
||||
color: $colorBtnMajorFg !important;
|
||||
|
||||
@@ -17,15 +17,7 @@
|
||||
<div v-if="singleSelectNonObject"
|
||||
class="c-inspector__selected c-inspector__selected--non-domain-object c-object-label"
|
||||
>
|
||||
<span class="c-object-label__type-icon"
|
||||
:class="typeCssClass"
|
||||
></span>
|
||||
<span v-if="!activity"
|
||||
class="c-object-label__name"
|
||||
>Layout Object</span>
|
||||
<span v-else
|
||||
class="c-object-label__name"
|
||||
>{{ activity.name }}</span>
|
||||
<span class="c-object-label__name">Layout Object</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="multiSelect"
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
|
||||
<multipane
|
||||
class="l-shell__main"
|
||||
:class="[resizingClass]"
|
||||
type="horizontal"
|
||||
>
|
||||
<pane
|
||||
@@ -53,6 +54,8 @@
|
||||
handle="after"
|
||||
label="Browse"
|
||||
collapsable
|
||||
@start-resizing="onStartResizing"
|
||||
@end-resizing="onEndResizing"
|
||||
>
|
||||
<button
|
||||
slot="controls"
|
||||
@@ -102,6 +105,8 @@
|
||||
handle="before"
|
||||
label="Inspect"
|
||||
collapsable
|
||||
@start-resizing="onStartResizing"
|
||||
@end-resizing="onEndResizing"
|
||||
>
|
||||
<Inspector
|
||||
ref="inspector"
|
||||
@@ -157,12 +162,16 @@ export default {
|
||||
actionCollection: undefined,
|
||||
triggerSync: false,
|
||||
triggerReset: false,
|
||||
headExpanded
|
||||
headExpanded,
|
||||
isResizing: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
toolbar() {
|
||||
return this.hasToolbar && this.isEditing;
|
||||
},
|
||||
resizingClass() {
|
||||
return this.isResizing ? 'l-shell__resizing' : '';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -240,6 +249,12 @@ export default {
|
||||
},
|
||||
handleTreeReset() {
|
||||
this.triggerReset = !this.triggerReset;
|
||||
},
|
||||
onStartResizing() {
|
||||
this.isResizing = true;
|
||||
},
|
||||
onEndResizing() {
|
||||
this.isResizing = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -293,6 +293,12 @@
|
||||
justify-content: space-between;
|
||||
padding: $p;
|
||||
}
|
||||
|
||||
&__resizing {
|
||||
iframe {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-editing {
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
&__scrollable {
|
||||
overflow: auto;
|
||||
padding-right: $interiorMargin;
|
||||
}
|
||||
|
||||
&__item--empty {
|
||||
|
||||
@@ -113,7 +113,7 @@ import search from '../components/search.vue';
|
||||
const ITEM_BUFFER = 25;
|
||||
const LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded';
|
||||
const RETURN_ALL_DESCDNDANTS = true;
|
||||
const TREE_ITEM_INDENT_PX = 15;
|
||||
const TREE_ITEM_INDENT_PX = 18;
|
||||
|
||||
export default {
|
||||
name: 'MctTree',
|
||||
|
||||
@@ -41,8 +41,13 @@
|
||||
|
||||
<script>
|
||||
const COLLAPSE_THRESHOLD_PX = 40;
|
||||
const HIDE_TREE_PARAM = 'hideTree';
|
||||
const HIDE_INSPECTOR_PARAM = 'hideInspector';
|
||||
const PANE_INSPECTOR = 'Inspect';
|
||||
const PANE_TREE = 'Browse';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
handle: {
|
||||
type: String,
|
||||
@@ -70,20 +75,59 @@ export default {
|
||||
this.type = this.$parent.type;
|
||||
this.styleProp = (this.type === 'horizontal') ? 'width' : 'height';
|
||||
},
|
||||
async mounted() {
|
||||
await this.$nextTick();
|
||||
// Hide tree and/or inspector pane if specified in URL
|
||||
this.handleHideUrl();
|
||||
this.openmct.router.on('change:params', this.handleHideUrl);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.router.off('change:params', this.handleHideUrl);
|
||||
},
|
||||
methods: {
|
||||
toggleCollapse: function () {
|
||||
toggleCollapse: function (e) {
|
||||
let target = this.label === PANE_TREE ? HIDE_TREE_PARAM : HIDE_INSPECTOR_PARAM;
|
||||
this.collapsed = !this.collapsed;
|
||||
if (this.collapsed) {
|
||||
// Pane is expanded and is being collapsed
|
||||
this.currentSize = (this.dragCollapse === true) ? this.initial : this.$el.style[this.styleProp];
|
||||
this.$el.style[this.styleProp] = '';
|
||||
this.handleCollapse();
|
||||
this.addHideParam(target);
|
||||
} else {
|
||||
// Pane is collapsed and is being expanded
|
||||
this.$el.style[this.styleProp] = this.currentSize;
|
||||
delete this.currentSize;
|
||||
delete this.dragCollapse;
|
||||
this.handleExpand();
|
||||
this.removeHideParam(target);
|
||||
}
|
||||
},
|
||||
handleHideUrl: function () {
|
||||
if (!this.collapsable) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hideTreeParam = this.openmct.router.getSearchParam(HIDE_TREE_PARAM);
|
||||
let hideInspectorParam = this.openmct.router.getSearchParam(HIDE_INSPECTOR_PARAM);
|
||||
let hideTree = hideTreeParam === 'true' && this.label === PANE_TREE;
|
||||
let hideInspector = hideInspectorParam === 'true' && this.label === PANE_INSPECTOR;
|
||||
if (hideTree || hideInspector) {
|
||||
this.collapsed = true;
|
||||
this.handleCollapse();
|
||||
} else {
|
||||
this.collapsed = false;
|
||||
this.handleExpand();
|
||||
}
|
||||
},
|
||||
addHideParam: function (target) {
|
||||
this.openmct.router.setSearchParam(target, 'true');
|
||||
},
|
||||
removeHideParam: function (target) {
|
||||
this.openmct.router.deleteSearchParam(target);
|
||||
},
|
||||
handleCollapse: function () {
|
||||
this.currentSize = (this.dragCollapse === true) ? this.initial : this.$el.style[this.styleProp];
|
||||
this.$el.style[this.styleProp] = '';
|
||||
},
|
||||
handleExpand: function () {
|
||||
this.$el.style[this.styleProp] = this.currentSize;
|
||||
delete this.currentSize;
|
||||
delete this.dragCollapse;
|
||||
},
|
||||
trackSize: function () {
|
||||
if (!this.dragCollapse === true) {
|
||||
if (this.type === 'vertical') {
|
||||
@@ -126,12 +170,14 @@ export default {
|
||||
document.body.addEventListener('mousemove', this.updatePosition);
|
||||
document.body.addEventListener('mouseup', this.end);
|
||||
this.resizing = true;
|
||||
this.$emit('start-resizing');
|
||||
this.trackSize();
|
||||
},
|
||||
end: function (event) {
|
||||
document.body.removeEventListener('mousemove', this.updatePosition);
|
||||
document.body.removeEventListener('mouseup', this.end);
|
||||
this.resizing = false;
|
||||
this.$emit('end-resizing');
|
||||
this.trackSize();
|
||||
}
|
||||
}
|
||||
|
||||
90
src/ui/layout/paneSpec.js
Normal file
90
src/ui/layout/paneSpec.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import {
|
||||
createOpenMct,
|
||||
resetApplicationState
|
||||
} from 'utils/testing';
|
||||
|
||||
describe("the pane", () => {
|
||||
let openmct;
|
||||
let appHolder;
|
||||
let element;
|
||||
let child;
|
||||
let resolveFunction;
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
|
||||
appHolder = document.createElement('div');
|
||||
appHolder.style.width = '640px';
|
||||
appHolder.style.height = '480px';
|
||||
|
||||
openmct = createOpenMct();
|
||||
openmct.install(openmct.plugins.MyItems());
|
||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
|
||||
element = document.createElement('div');
|
||||
child = document.createElement('div');
|
||||
element.appendChild(child);
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.start(appHolder);
|
||||
|
||||
document.body.append(appHolder);
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
it('toggling tree will toggle tree hide params', (done) => {
|
||||
document.querySelector('.l-shell__pane-tree .l-pane__collapse-button').click();
|
||||
expect(openmct.router.getSearchParam('hideTree')).toBe('true');
|
||||
done();
|
||||
});
|
||||
|
||||
it('tree pane collapses when adding hide tree param in URL', () => {
|
||||
openmct.router.setSearchParam('hideTree', 'true');
|
||||
expect(document.querySelector('.l-shell__pane-tree.l-pane--collapsed')).toBeDefined();
|
||||
});
|
||||
|
||||
it('inspector pane collapses when adding hide inspector param in URL', () => {
|
||||
openmct.router.setSearchParam('hideInspector', 'true');
|
||||
expect(document.querySelector('.l-shell__pane-inspector.l-pane--collapsed')).toBeDefined();
|
||||
});
|
||||
|
||||
it('toggle inspector pane will toggle inspector hide param', (done) => {
|
||||
// There's a short delay on addubg the param.
|
||||
resolveFunction = () => {
|
||||
setTimeout(() => {
|
||||
expect(openmct.router.getSearchParam('hideInspector')).toBe('true');
|
||||
done();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
openmct.router.on('change:params', resolveFunction);
|
||||
document.querySelector('.l-shell__pane-inspector .l-pane__collapse-button').click();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -103,10 +103,16 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
if (this.actionCollection) {
|
||||
this.actionCollection.hide(HIDDEN_ACTIONS);
|
||||
this.actionCollection.on('update', this.updateActionItems);
|
||||
this.updateActionItems(this.actionCollection.getActionsObject());
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
if (this.actionCollection) {
|
||||
this.actionCollection.off('update', this.updateActionItems);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setView(view) {
|
||||
this.$emit('setView', view);
|
||||
@@ -116,7 +122,6 @@ export default {
|
||||
delete this.actionCollection;
|
||||
},
|
||||
updateActionItems() {
|
||||
this.actionCollection.hide(HIDDEN_ACTIONS);
|
||||
this.statusBarItems = this.actionCollection.getStatusBarActions();
|
||||
this.menuActionItems = this.actionCollection.getVisibleActions();
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ let resolveFunction;
|
||||
|
||||
let initialHash = '';
|
||||
|
||||
describe('Application router utility functions', () => {
|
||||
xdescribe('Application router utility functions', () => {
|
||||
beforeAll(done => {
|
||||
appHolder = document.createElement('div');
|
||||
appHolder.style.width = '640px';
|
||||
|
||||
Reference in New Issue
Block a user