Compare commits

..

2 Commits

Author SHA1 Message Date
David Tsay
5da1c9c0d7 Compass rose fix (#6318)
* won't mount if cameraAngleOfView undefined

* fix non-gimbling camera azimuth

correct pan to azimuth

* reorganize shared props passing

* enable hud for non-gimbling cameras

* fix unit tests

* rotate function needs to work with numbers

* avoid -0

* fix: don't delete imagery size, update related telemetry on focusedImage change

* refactor: remove unused prop

* fix: ensure thumbnail key is unique

* fix: watch `focusedImage`, not `focusedImageIndex`

- Corrects a false assumption that if the `focusedImageIndex` changes, the `focusedImage` has changed. This was causing us to mistakenly reset a lot of display props that control whether or not the Compass shows.

- For example, if an image falls out of bounds, the `focusedImageIndex` will change as the old image is removed from the array.

---------

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2023-02-13 19:28:00 +00:00
dependabot[bot]
4fa9a9697b chore(deps-dev): bump eslint from 8.32.0 to 8.34.0 (#6325)
Bumps [eslint](https://github.com/eslint/eslint) from 8.32.0 to 8.34.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.32.0...v8.34.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-13 18:52:09 +00:00
10 changed files with 137 additions and 127 deletions

View File

@@ -275,7 +275,7 @@ function pointForTimestamp(timestamp, name, imageSamples, delay) {
local: Math.floor(timestamp / delay) * delay,
url,
sunOrientation: getCompassValues(0, 360),
cameraPan: getCompassValues(0, 360),
cameraAzimuth: getCompassValues(0, 360),
heading: getCompassValues(0, 360),
transformations: navCamTransformations,
imageDownloadName

View File

@@ -20,7 +20,7 @@
"d3-axis": "3.0.0",
"d3-scale": "3.3.0",
"d3-selection": "3.0.0",
"eslint": "8.32.0",
"eslint": "8.34.0",
"eslint-plugin-compat": "4.1.1",
"eslint-plugin-playwright": "0.12.0",
"eslint-plugin-vue": "9.9.0",

View File

@@ -32,18 +32,14 @@ class IndependentTimeContext extends TimeContext {
this.openmct = openmct;
this.unlisteners = [];
this.globalTimeContext = globalTimeContext;
// We always start with the global time context.
// This upstream context will be undefined when an independent time context is added later.
this.upstreamTimeContext = this.globalTimeContext;
this.upstreamTimeContext = undefined;
this.objectPath = objectPath;
this.refreshContext = this.refreshContext.bind(this);
this.resetContext = this.resetContext.bind(this);
this.removeIndependentContext = this.removeIndependentContext.bind(this);
this.refreshContext();
this.globalTimeContext.on('refreshContext', this.refreshContext);
this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext);
}
bounds(newBounds) {
@@ -206,16 +202,10 @@ class IndependentTimeContext extends TimeContext {
}
getUpstreamContext() {
// If a view has an independent context, don't return an upstream context
// Be aware that when a new independent time context is created, we assign the global context as default
if (this.hasOwnContext()) {
return undefined;
}
let timeContext = this.globalTimeContext;
this.objectPath.some((item, index) => {
const key = this.openmct.objects.makeKeyString(item.identifier);
// we're only interested in parents, not self, so index > 0
//first index is the view object itself
const itemContext = this.globalTimeContext.independentContexts.get(key);
if (index > 0 && itemContext && itemContext.hasOwnContext()) {
//upstream time context
@@ -229,43 +219,6 @@ class IndependentTimeContext extends TimeContext {
return timeContext;
}
/**
* Set the time context of a view to follow any upstream time contexts as necessary (defaulting to the global context)
* This needs to be separate from refreshContext
*/
removeIndependentContext(viewKey) {
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
if (viewKey && key === viewKey) {
//this is necessary as the upstream context gets reassigned after this
this.stopFollowingTimeContext();
let timeContext = this.globalTimeContext;
this.objectPath.some((item, index) => {
const objectKey = this.openmct.objects.makeKeyString(item.identifier);
// we're only interested in any parents, not self, so index > 0
const itemContext = this.globalTimeContext.independentContexts.get(objectKey);
if (index > 0 && itemContext && itemContext.hasOwnContext()) {
//upstream time context
timeContext = itemContext;
return true;
}
return false;
});
this.upstreamTimeContext = timeContext;
this.followTimeContext();
// Emit bounds so that views that are changing context get the upstream bounds
this.emit('bounds', this.bounds());
// now that the view's context is set, tell others to check theirs in case they were following this view's context.
this.globalTimeContext.emit('refreshContext', viewKey);
}
}
}
export default IndependentTimeContext;

View File

@@ -149,7 +149,7 @@ class TimeAPI extends GlobalTimeContext {
return () => {
//follow any upstream time context
this.emit('removeOwnContext', key);
this.emit('refreshContext');
};
}

View File

@@ -26,18 +26,23 @@
:style="`width: 100%; height: 100%`"
>
<CompassHUD
v-if="showCompassHUD"
:sun-heading="sunHeading"
:camera-angle-of-view="cameraAngleOfView"
:camera-pan="cameraPan"
:heading="heading"
:camera-azimuth="cameraAzimuth"
:transformations="transformations"
:has-gimble="hasGimble"
:normalized-camera-azimuth="normalizedCameraAzimuth"
:sun-heading="sunHeading"
/>
<CompassRose
v-if="showCompassRose"
:camera-pan="cameraPan"
:camera-angle-of-view="cameraAngleOfView"
:heading="heading"
:sized-image-dimensions="sizedImageDimensions"
:sun-heading="sunHeading"
:camera-azimuth="cameraAzimuth"
:transformations="transformations"
:has-gimble="hasGimble"
:normalized-camera-azimuth="normalizedCameraAzimuth"
:sun-heading="sunHeading"
:sized-image-dimensions="sizedImageDimensions"
/>
</div>
</template>
@@ -45,6 +50,7 @@
<script>
import CompassHUD from './CompassHUD.vue';
import CompassRose from './CompassRose.vue';
import { rotate } from './utils';
export default {
components: {
@@ -62,11 +68,14 @@ export default {
}
},
computed: {
showCompassHUD() {
return this.hasCameraPan && this.cameraAngleOfView > 0;
hasGimble() {
return this.cameraAzimuth !== undefined;
},
showCompassRose() {
return (this.hasCameraPan || this.hasHeading) && this.cameraAngleOfView > 0;
// compass ordinal orientation of camera
normalizedCameraAzimuth() {
return this.hasGimble
? rotate(this.cameraAzimuth)
: rotate(this.heading, -this.transformations.rotation || 0);
},
// horizontal rotation from north in degrees
heading() {
@@ -80,14 +89,11 @@ export default {
return this.image.sunOrientation;
},
// horizontal rotation from north in degrees
cameraPan() {
cameraAzimuth() {
return this.image.cameraPan;
},
hasCameraPan() {
return this.cameraPan !== undefined;
},
cameraAngleOfView() {
return this.transformations?.cameraAngleOfView;
return this.transformations.cameraAngleOfView;
},
transformations() {
return this.image.transformations;

View File

@@ -94,17 +94,33 @@ const COMPASS_POINTS = [
export default {
props: {
cameraAngleOfView: {
type: Number,
required: true
},
heading: {
type: Number,
required: true
},
cameraAzimuth: {
type: Number,
default: undefined
},
transformations: {
type: Object,
required: true
},
hasGimble: {
type: Boolean,
required: true
},
normalizedCameraAzimuth: {
type: Number,
required: true
},
sunHeading: {
type: Number,
default: undefined
},
cameraAngleOfView: {
type: Number,
default: undefined
},
cameraPan: {
type: Number,
required: true
}
},
computed: {
@@ -130,10 +146,13 @@ export default {
left: `${ percentage * 100 }%`
};
},
cameraRotation() {
return this.transformations?.rotation;
},
visibleRange() {
return [
rotate(this.cameraPan, -this.cameraAngleOfView / 2),
rotate(this.cameraPan, this.cameraAngleOfView / 2)
rotate(this.normalizedCameraAzimuth, -this.cameraAngleOfView / 2),
rotate(this.normalizedCameraAzimuth, this.cameraAngleOfView / 2)
];
}
}

View File

@@ -75,7 +75,6 @@
:style="sunHeadingStyle"
/>
<!-- Camera FOV -->
<mask
id="mask2"
class="c-cr__cam-fov-l-mask"
@@ -117,10 +116,10 @@
class="cr-vrover"
:style="camAngleAndPositionStyle"
>
<!-- Equipment body. Rotates relative to the camera pan value for cams that gimbal. -->
<!-- Equipment body. Rotates relative to the camera pan value for cameras that gimble. -->
<path
class="cr-vrover__body"
:style="camGimbalAngleStyle"
:style="gimbledCameraPanStyle"
x
fill-rule="evenodd"
clip-rule="evenodd"
@@ -128,6 +127,7 @@
/>
</g>
<!-- Camera FOV -->
<g
class="c-cr__cam-fov"
>
@@ -160,7 +160,7 @@
<!-- NSEW and ticks -->
<g
class="c-cr__nsew"
:style="compassRoseStyle"
:style="compassDialStyle"
>
<g class="c-cr__ticks-major">
<path d="M50 3L43 10H57L50 3Z" />
@@ -259,23 +259,32 @@ import { throttle } from 'lodash';
export default {
props: {
cameraAngleOfView: {
type: Number,
required: true
},
heading: {
type: Number,
required: true,
default() {
return 0;
}
required: true
},
sunHeading: {
type: Number,
default: undefined
},
cameraPan: {
cameraAzimuth: {
type: Number,
default: undefined
},
transformations: {
type: Object,
required: true
},
hasGimble: {
type: Boolean,
required: true
},
normalizedCameraAzimuth: {
type: Number,
required: true
},
sunHeading: {
type: Number,
default: undefined
},
sizedImageDimensions: {
@@ -289,18 +298,6 @@ export default {
};
},
computed: {
cameraHeading() {
return this.cameraPan ?? this.heading;
},
cameraAngleOfView() {
const cameraAngleOfView = this.transformations?.cameraAngleOfView;
if (!cameraAngleOfView) {
console.warn('No Camera Angle of View provided');
}
return cameraAngleOfView;
},
camAngleAndPositionStyle() {
const translateX = this.transformations?.translateX;
const translateY = this.transformations?.translateY;
@@ -309,18 +306,22 @@ export default {
return { transform: `translate(${translateX}%, ${translateY}%) rotate(${rotation}deg) scale(${scale})` };
},
camGimbalAngleStyle() {
const rotation = rotate(this.heading);
gimbledCameraPanStyle() {
if (!this.hasGimble) {
return;
}
const gimbledCameraPan = rotate(this.normalizedCameraAzimuth, -this.heading);
return {
transform: `rotate(${ rotation }deg)`
transform: `rotate(${ -gimbledCameraPan }deg)`
};
},
compassRoseStyle() {
compassDialStyle() {
return { transform: `rotate(${ this.north }deg)` };
},
north() {
return this.lockCompass ? rotate(-this.cameraHeading) : 0;
return this.lockCompass ? rotate(-this.normalizedCameraAzimuth) : 0;
},
cardinalTextRotateN() {
return { transform: `translateY(-27%) rotate(${ -this.north }deg)` };
@@ -348,7 +349,7 @@ export default {
};
},
cameraHeadingStyle() {
const rotation = rotate(this.north, this.cameraHeading);
const rotation = rotate(this.north, this.normalizedCameraAzimuth);
return {
transform: `rotate(${ rotation }deg)`

View File

@@ -35,8 +35,15 @@ describe("The Compass component", () => {
roll: 90,
pitch: 90,
cameraTilt: 100,
cameraPan: 90,
sunAngle: 30
cameraAzimuth: 90,
sunAngle: 30,
transformations: {
translateX: 0,
translateY: 18,
rotation: 0,
scale: 0.3,
cameraAngleOfView: 70
}
};
let propsData = {
naturalAspectRatio: 0.9,
@@ -44,8 +51,7 @@ describe("The Compass component", () => {
sizedImageDimensions: {
width: 100,
height: 100
},
compassRoseSizingClasses: '--rose-small --rose-min'
}
};
app = new Vue({
@@ -54,7 +60,6 @@ describe("The Compass component", () => {
return propsData;
},
template: `<Compass
:compass-rose-sizing-classes="compassRoseSizingClasses"
:image="image"
:natural-aspect-ratio="naturalAspectRatio"
:sized-image-dimensions="sizedImageDimensions"
@@ -67,7 +72,7 @@ describe("The Compass component", () => {
app.$destroy();
});
describe("when a heading value exists on the image", () => {
describe("when a heading value and cameraAngleOfView exists on the image", () => {
it("should display a compass rose", () => {
let compassRoseElement = instance.$el.querySelector(COMPASS_ROSE_CLASS

View File

@@ -94,7 +94,6 @@
<Compass
v-if="shouldDisplayCompass"
:image="focusedImage"
:natural-aspect-ratio="focusedImageNaturalAspectRatio"
:sized-image-dimensions="sizedImageDimensions"
/>
</div>
@@ -171,7 +170,7 @@
>
<ImageThumbnail
v-for="(image, index) in imageHistory"
:key="`${image.thumbnailUrl || image.url}${image.time}`"
:key="`${image.thumbnailUrl || image.url}-${image.time}-${index}`"
:image="image"
:active="focusedImageIndex === index"
:selected="focusedImageIndex === index && isPaused"
@@ -430,9 +429,12 @@ export default {
&& imageHeightAndWidth
&& this.zoomFactor === 1
&& this.imagePanned !== true;
const hasCameraConfigurations = this.focusedImage?.transformations !== undefined;
const hasHeading = this.focusedImage?.heading !== undefined;
const hasCameraAngleOfView = this.focusedImage?.transformations?.cameraAngleOfView > 0;
return display && hasCameraConfigurations;
return display
&& hasCameraAngleOfView
&& hasHeading;
},
isSpacecraftPositionFresh() {
let isFresh = undefined;
@@ -582,11 +584,34 @@ export default {
},
deep: true
},
focusedImageIndex() {
this.trackDuration();
this.resetAgeCSS();
this.updateRelatedTelemetryForFocusedImage();
this.getImageNaturalDimensions();
focusedImage: {
handler(newImage, oldImage) {
const newTime = newImage?.time;
const oldTime = oldImage?.time;
const newUrl = newImage?.url;
const oldUrl = oldImage?.url;
// Skip if it's all falsy
if (!newTime && !oldTime && !newUrl && !oldUrl) {
return;
}
// Skip if it's the same image
if (newTime === oldTime && newUrl === oldUrl) {
return;
}
// Update image duration and reset age CSS
this.trackDuration();
this.resetAgeCSS();
// Reset image dimensions and calculate new dimensions
// on new image load
this.getImageNaturalDimensions();
// Get the related telemetry for the new image
this.updateRelatedTelemetryForFocusedImage();
}
},
bounds() {
this.scrollHandler();
@@ -771,6 +796,10 @@ export default {
this.layers = layersMetadata;
if (this.domainObject.configuration) {
const persistedLayers = this.domainObject.configuration.layers;
if (!persistedLayers) {
return;
}
layersMetadata.forEach((layer) => {
const persistedLayer = persistedLayers.find(object => object.name === layer.name);
if (persistedLayer) {

View File

@@ -153,9 +153,6 @@ export default {
return;
}
// forcibly reset the imageContainer size to prevent an aspect ratio distortion
delete this.imageContainerWidth;
delete this.imageContainerHeight;
this.bounds = bounds; // setting bounds for ImageryView watcher
},
timeSystemChange() {