Compare commits

...

68 Commits

Author SHA1 Message Date
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
92cba55f88 Merge remote-tracking branch 'origin/6708-displays-with-a-large-number-of-plots-take-a-long-time-to-load-due-to-annotation-requests' into image-tagging-plus-map-tagging-plus-batching-requests 2023-06-13 11:28:10 -07:00
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
def37263ea Merge branch 'map-annotations-prototype' into image-tagging-plus-map-tagging-plus-batching-requests 2023-06-13 11:16:09 -07:00
Scott Bell
a131fd185a forgot to remove superfluous data 2023-06-13 12:08:12 +02:00
Scott Bell
929b5fb8f0 Merge branch 'master' into 6557-annotations-image-tagging-prototype 2023-06-13 12:04:23 +02:00
Scott Bell
01cc501f6a replace comment 2023-06-13 12:03:59 +02:00
Scott Bell
16dd43d1c8 add todo for search and removed mutation listener 2023-06-13 12:02:46 +02:00
Scott Bell
6878f06b03 revert couchdb change 2023-06-12 16:03:59 +02:00
Scott Bell
f61eb0a2e9 add test 2023-06-12 16:01:22 +02:00
Scott Bell
a5e3317f8e Merge branch 'master' into 6557-annotations-image-tagging-prototype 2023-06-12 15:28:33 +02:00
Scott Bell
7b0d1d9c8c Merge remote-tracking branch 'origin/master' into 6708-displays-with-a-large-number-of-plots-take-a-long-time-to-load-due-to-annotation-requests 2023-06-12 14:24:00 +02:00
Scott Bell
d32913c20f remove debug statement 2023-06-08 17:07:53 +02:00
Scott Bell
15e30f52bc batching requests 2023-06-08 16:59:34 +02:00
Scott Bell
076c1425a8 batching, but query is messed up 2023-06-08 16:45:00 +02:00
Scott Bell
5be9a5d04f Merge branch 'master' into 6557-annotations-image-tagging-prototype 2023-06-06 12:58:02 +02:00
Scott Bell
7de80778e3 Merge branch 'master' into map-annotations-prototype 2023-06-06 12:56:25 +02:00
Jesse Mazzella
bfa82abc25 test: add unit tests for target comparators 2023-06-05 14:54:28 -07:00
Jesse Mazzella
9a0923801b feat: comparators for annotation target equality
- Adds `addTargetComparator()` to the Annotation API, allowing plugins to define additional comparators for certain annotation types.
- Update usage of `_.isEqual()` for targets to use the `areAnnotationTargetsEqual()` method, which uses any additional comparators before falling back to a deep equality check.
- Handle aborted `getAnnotations()` calls gracefully in the AnnotationInspectorView
2023-06-05 14:24:23 -07:00
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
11295a8042 fix: navigate before selection for geospatial results 2023-06-02 12:17:20 -07:00
Scott Bell
ed7e85c8a0 Merge branch 'master' into map-annotations-prototype 2023-06-02 20:55:37 +02:00
Scott Bell
ec90d4d92a Merge branch 'master' into map-annotations-prototype 2023-06-02 10:35:58 +02:00
Scott Bell
cfe0c7f68e Merge branch 'master' into 6557-annotations-image-tagging-prototype 2023-06-02 09:51:09 +02:00
Jesse Mazzella
12b7c0e805 docs: add comment 2023-06-01 11:31:18 -07:00
Scott Bell
39c4e581ac Merge branch 'master' into 6557-annotations-image-tagging-prototype 2023-06-01 20:17:00 +02:00
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
8d8dd34853 fix: remove redundant MAP annotation type 2023-06-01 09:37:02 -07:00
Scott Bell
c530c2998a Merge branch 'master' into map-annotations-prototype 2023-06-01 15:47:35 +02:00
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
37dd237055 fix: handle AbortErrors gracefully 2023-05-31 13:23:46 -07:00
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
c7542d0052 fix(#5646): abort pending annotations requests on nav away from notebooks or plots 2023-05-31 13:04:59 -07:00
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
f4007d3dfc fix: have loadAnnotationForTargetObject take an abortSignal 2023-05-25 19:05:11 -07:00
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
b51654efbb fix: handle MAP annotations in search results 2023-05-25 19:02:28 -07:00
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
3f2c63c9d7 feat: add MAP annotationType 2023-05-25 19:01:14 -07:00
Scott Bell
e35d7a085b Merge branch 'master' into 6557-annotations-image-tagging-prototype 2023-05-25 12:21:32 +02:00
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC]
5152583c3b feat: getAnnotations can take an abortSignal 2023-05-24 11:27:30 -07:00
Scott Bell
fbb37ac382 Merge branch 'master' into 6557-annotations-image-tagging-prototype 2023-05-24 11:36:12 +02:00
Scott Bell
71c1fdc298 Merge branch 'master' into 6557-annotations-image-tagging-prototype 2023-05-22 17:10:20 +02:00
Scott Bell
efb55ecafc resolve conflicts 2023-05-19 16:16:34 +02:00
Scott Bell
609dcb0460 refactor: format with prettier 2023-05-19 16:10:03 +02:00
Scott Bell
3bff7f9f32 pause when annotating 2023-05-19 16:08:41 +02:00
Scott Bell
09df2f64f2 fix rectangle blurriness on retina screens 2023-05-19 16:08:41 +02:00
Scott Bell
6b2adcb7b7 add basic tagging test 2023-05-19 16:08:41 +02:00
Scott Bell
2f310a3432 fix drag test 2023-05-19 16:08:41 +02:00
Scott Bell
02855d2c9c get rid of force and use upper div 2023-05-19 16:08:41 +02:00
Scott Bell
6dd6c87ceb fix tests 2023-05-19 16:08:41 +02:00
Scott Bell
8945f27eed git rid of carat 2023-05-19 16:08:41 +02:00
Scott Bell
5ddf8a8ff4 move flatbush to dev 2023-05-19 16:08:41 +02:00
Scott Bell
3a77efb010 separate annotation guard 2023-05-19 16:08:41 +02:00
Scott Bell
bd356653db remove some debug code 2023-05-19 16:08:41 +02:00
Scott Bell
d1e8b3835d generate keystring on mount 2023-05-19 16:08:41 +02:00
Scott Bell
051a0adbb7 added notes on how search selection will work 2023-05-19 16:08:41 +02:00
Scott Bell
87d695a454 remove kdbush and generalize annotation selection 2023-05-19 16:08:41 +02:00
Scott Bell
85482902be works on load 2023-05-19 16:08:41 +02:00
Scott Bell
8015aceaa7 added indicator 2023-05-19 16:08:41 +02:00
Scott Bell
a381673f21 add icon for annotations 2023-05-19 16:08:41 +02:00
Scott Bell
7a808622ae need to add image indicator 2023-05-19 16:08:41 +02:00
Scott Bell
5fb78a9604 rough prototype done 2023-05-19 16:08:41 +02:00
Scott Bell
629e884c9b annotation selection much better 2023-05-19 16:08:41 +02:00
Scott Bell
a8949d39bf still need to clean up selection code 2023-05-19 16:08:41 +02:00
Scott Bell
91ad130f8b switch plots to use flatbush 2023-05-19 16:08:41 +02:00
Scott Bell
72d8779736 clicking on annotations finds them 2023-05-19 16:08:41 +02:00
Scott Bell
e34093eda7 remove flickering 2023-05-19 16:08:41 +02:00
Scott Bell
501fdf902b can draw annotations, now need to select existing annotations 2023-05-19 16:08:41 +02:00
Scott Bell
cb32dd94f8 loading annotations from db 2023-05-19 16:08:41 +02:00
Scott Bell
2d868cdb58 rudimentary annotation 2023-05-19 16:08:41 +02:00
Scott Bell
424a2b30ac get math right for drawing the rectangle 2023-05-19 16:08:41 +02:00
Scott Bell
693b8804ba dragging 2023-05-19 16:08:41 +02:00
Scott Bell
9018dcd319 fix pointer events 2023-05-19 16:08:40 +02:00
Scott Bell
0aaa7998f5 add canvas click events 2023-05-19 16:08:40 +02:00
Scott Bell
d426ae86b8 separate into canvas 2023-05-19 16:08:40 +02:00
Scott Bell
002d8d11e8 drawing a bunch of garbage on the image 2023-05-19 16:08:40 +02:00
17 changed files with 856 additions and 75 deletions

View File

@@ -67,7 +67,6 @@ const config = {
MCT: path.join(projectRootDir, 'src/MCT'),
testUtils: path.join(projectRootDir, 'src/utils/testUtils.js'),
objectUtils: path.join(projectRootDir, 'src/api/objects/object-utils.js'),
kdbush: path.join(projectRootDir, 'node_modules/kdbush/kdbush.min.js'),
utils: path.join(projectRootDir, 'src/utils')
}
},

View File

@@ -23,5 +23,5 @@ module.exports = merge(common, {
__OPENMCT_ROOT_RELATIVE__: '""'
})
],
devtool: 'source-map'
devtool: 'eval-source-map'
});

View File

@@ -205,6 +205,71 @@ test.describe('Display Layout', () => {
expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
});
test('When multiple plots are contained in a layout, we only ask for annotations once @couchdb', async ({
page
}) => {
// Create another Sine Wave Generator
const anotherSineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator'
});
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: 'Test Display Layout'
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
let layoutGridHolder = page.locator('.l-layout__grid-holder');
// eslint-disable-next-line playwright/no-force-option
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder, { force: true });
await page.getByText('View type').click();
await page.getByText('Overlay Plot').click();
const anotherSineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(anotherSineWaveObject.name)
});
layoutGridHolder = page.locator('.l-layout__grid-holder');
// eslint-disable-next-line playwright/no-force-option
await anotherSineWaveGeneratorTreeItem.dragTo(layoutGridHolder, { force: true });
await page.getByText('View type').click();
await page.getByText('Overlay Plot').click();
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Time to inspect some network traffic
let networkRequests = [];
page.on('request', (request) => {
const searchRequest = request.url().endsWith('_find');
const fetchRequest = request.resourceType() === 'fetch';
if (searchRequest && fetchRequest) {
networkRequests.push(request);
}
});
await page.reload();
// wait for annotations requests to be batched and requested
await page.waitForLoadState('networkidle');
// Network requests for the composite telemetry with multiple items should be:
// 1. a single batched request for annotations
expect(networkRequests.length).toBe(1);
});
});
/**

View File

@@ -30,6 +30,7 @@ const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const backgroundImageSelector = '.c-imagery__main-image__background-image';
const panHotkey = process.platform === 'linux' ? ['Shift', 'Alt'] : ['Alt'];
const tagHotkey = ['Shift', 'Alt'];
const expectedAltText = process.platform === 'linux' ? 'Shift+Alt drag to pan' : 'Alt drag to pan';
const thumbnailUrlParamsRegexp = /\?w=100&h=100/;
@@ -44,7 +45,7 @@ test.describe('Example Imagery Object', () => {
// Verify that the created object is focused
await expect(page.locator('.l-browse-bar__object-name')).toContainText(exampleImagery.name);
await page.locator(backgroundImageSelector).hover({ trial: true });
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
});
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
@@ -72,11 +73,11 @@ test.describe('Example Imagery Object', () => {
test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
const deltaYStep = 100; //equivalent to 1x zoom
await page.locator(backgroundImageSelector).hover({ trial: true });
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
// zoom in
await page.mouse.wheel(0, deltaYStep * 2);
await page.locator(backgroundImageSelector).hover({ trial: true });
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
@@ -131,6 +132,36 @@ test.describe('Example Imagery Object', () => {
expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y);
});
test('Can use alt+shift+drag to create a tag', async ({ page }) => {
const canvas = page.locator('canvas');
await canvas.hover({ trial: true });
const canvasBoundingBox = await canvas.boundingBox();
const canvasCenterX = canvasBoundingBox.x + canvasBoundingBox.width / 2;
const canvasCenterY = canvasBoundingBox.y + canvasBoundingBox.height / 2;
await Promise.all(tagHotkey.map((x) => page.keyboard.down(x)));
await page.mouse.down();
// steps not working for me here
await page.mouse.move(canvasCenterX - 20, canvasCenterY - 20);
await page.mouse.move(canvasCenterX - 100, canvasCenterY - 100);
await page.mouse.up();
await Promise.all(tagHotkey.map((x) => page.keyboard.up(x)));
//Wait for canvas to stablize.
await canvas.hover({ trial: true });
// add some tags
await page.getByText('Annotations').click();
await page.getByRole('button', { name: /Add Tag/ }).click();
await page.getByPlaceholder('Type to select tag').click();
await page.getByText('Driving').click();
await page.getByRole('button', { name: /Add Tag/ }).click();
await page.getByPlaceholder('Type to select tag').click();
await page.getByText('Science').click();
});
test('Can use + - buttons to zoom on the image @unstable', async ({ page }) => {
await buttonZoomOnImageAndAssert(page);
});
@@ -692,7 +723,6 @@ async function panZoomAndAssertImageProperties(page) {
async function mouseZoomOnImageAndAssert(page, factor = 2) {
// Zoom in
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
await page.locator(backgroundImageSelector).hover({ trial: true });
const deltaYStep = 100; // equivalent to 1x zoom
await page.mouse.wheel(0, deltaYStep * factor);
const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
@@ -703,7 +733,7 @@ async function mouseZoomOnImageAndAssert(page, factor = 2) {
await page.mouse.move(imageCenterX, imageCenterY);
// Wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({ trial: true });
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
const imageMouseZoomed = await page.locator(backgroundImageSelector).boundingBox();
if (factor > 0) {

View File

@@ -30,6 +30,7 @@
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
"file-saver": "2.0.5",
"flatbush": "4.1.0",
"git-rev-sync": "3.0.2",
"html2canvas": "1.4.1",
"imports-loader": "4.0.1",
@@ -44,7 +45,6 @@
"karma-sourcemap-loader": "0.4.0",
"karma-spec-reporter": "0.0.36",
"karma-webpack": "5.0.0",
"kdbush": "3.0.0",
"location-bar": "3.0.1",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.7.6",

View File

@@ -76,6 +76,9 @@ const ANNOTATION_LAST_CREATED = 'annotationLastCreated';
* @constructor
*/
export default class AnnotationAPI extends EventEmitter {
/** @type {Map<ANNOTATION_TYPES, Array<(a, b) => boolean >>} */
#targetComparatorMap;
/**
* @param {OpenMCT} openmct
*/
@@ -84,6 +87,7 @@ export default class AnnotationAPI extends EventEmitter {
this.openmct = openmct;
this.availableTags = {};
this.namespaceToSaveAnnotations = '';
this.#targetComparatorMap = new Map();
this.ANNOTATION_TYPES = ANNOTATION_TYPES;
this.ANNOTATION_TYPE = ANNOTATION_TYPE;
@@ -246,15 +250,16 @@ export default class AnnotationAPI extends EventEmitter {
/**
* @method getAnnotations
* @param {Identifier} domainObjectIdentifier - The domain object identifier to use to search for annotations. For example, a telemetry object identifier.
* @param {AbortSignal} abortSignal - An abort signal to cancel the search
* @returns {DomainObject[]} Returns an array of annotations that match the search query
*/
async getAnnotations(domainObjectIdentifier) {
async getAnnotations(domainObjectIdentifier, abortSignal = null) {
const keyStringQuery = this.openmct.objects.makeKeyString(domainObjectIdentifier);
const searchResults = (
await Promise.all(
this.openmct.objects.search(
keyStringQuery,
null,
abortSignal,
this.openmct.objects.SEARCH_TYPES.ANNOTATIONS
)
)
@@ -384,7 +389,8 @@ export default class AnnotationAPI extends EventEmitter {
const combinedResults = [];
results.forEach((currentAnnotation) => {
const existingAnnotation = combinedResults.find((annotationToFind) => {
return _.isEqual(currentAnnotation.targets, annotationToFind.targets);
const { annotationType, targets } = currentAnnotation;
return this.areAnnotationTargetsEqual(annotationType, targets, annotationToFind.targets);
});
if (!existingAnnotation) {
combinedResults.push(currentAnnotation);
@@ -460,4 +466,35 @@ export default class AnnotationAPI extends EventEmitter {
return breakApartSeparateTargets;
}
/**
* Adds a comparator function for a given annotation type.
* The comparator functions will be used to determine if two annotations
* have the same target.
* @param {ANNOTATION_TYPES} annotationType
* @param {(t1, t2) => boolean} comparator
*/
addTargetComparator(annotationType, comparator) {
const comparatorList = this.#targetComparatorMap.get(annotationType) ?? [];
comparatorList.push(comparator);
this.#targetComparatorMap.set(annotationType, comparatorList);
}
/**
* Compare two sets of targets to see if they are equal. First checks if
* any targets comparators evaluate to true, then falls back to a deep
* equality check.
* @param {ANNOTATION_TYPES} annotationType
* @param {*} targets
* @param {*} otherTargets
* @returns true if the targets are equal, false otherwise
*/
areAnnotationTargetsEqual(annotationType, targets, otherTargets) {
const targetComparatorList = this.#targetComparatorMap.get(annotationType);
return (
(targetComparatorList?.length &&
targetComparatorList.some((targetComparator) => targetComparator(targets, otherTargets))) ||
_.isEqual(targets, otherTargets)
);
}
}

View File

@@ -265,4 +265,52 @@ describe('The Annotation API', () => {
expect(results.length).toEqual(0);
});
});
describe('Target Comparators', () => {
let targets;
let otherTargets;
let comparator;
beforeEach(() => {
targets = {
fooTarget: {
foo: 42
}
};
otherTargets = {
fooTarget: {
bar: 42
}
};
comparator = (t1, t2) => t1.fooTarget.foo === t2.fooTarget.bar;
});
it('can add a comparator function', () => {
const notebookAnnotationType = openmct.annotation.ANNOTATION_TYPES.NOTEBOOK;
expect(
openmct.annotation.areAnnotationTargetsEqual(notebookAnnotationType, targets, otherTargets)
).toBeFalse(); // without a comparator, these should NOT be equal
// Register a comparator function for the notebook annotation type
openmct.annotation.addTargetComparator(notebookAnnotationType, comparator);
expect(
openmct.annotation.areAnnotationTargetsEqual(notebookAnnotationType, targets, otherTargets)
).toBeTrue(); // the comparator should make these equal
});
it('falls back to deep equality check if no comparator functions', () => {
const annotationTypeWithoutComparator = openmct.annotation.ANNOTATION_TYPES.GEOSPATIAL;
const areEqual = openmct.annotation.areAnnotationTargetsEqual(
annotationTypeWithoutComparator,
targets,
targets
);
const areNotEqual = openmct.annotation.areAnnotationTargetsEqual(
annotationTypeWithoutComparator,
targets,
otherTargets
);
expect(areEqual).toBeTrue();
expect(areNotEqual).toBeFalse();
});
});
});

View File

@@ -0,0 +1,378 @@
<!--
Open MCT, Copyright (c) 2014-2023, 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>
<canvas
ref="canvas"
class="c-image-canvas"
style="width: 100%; height: 100%"
@mousedown="clearSelectedAnnotations"
@mousemove="trackAnnotationDrag"
@click="selectOrCreateAnnotation"
></canvas>
</template>
<script>
import Flatbush from 'flatbush';
const EXISTING_ANNOTATION_STROKE_STYLE = '#D79078';
const EXISTING_ANNOTATION_FILL_STYLE = 'rgba(202, 202, 142, 0.2)';
const SELECTED_ANNOTATION_STROKE_COLOR = '#BD8ECC';
const SELECTED_ANNOTATION_FILL_STYLE = 'rgba(199, 87, 231, 0.2)';
export default {
inject: ['openmct', 'domainObject', 'objectPath'],
props: {
image: {
type: Object,
required: true
},
imageryAnnotations: {
type: Array,
default() {
return [];
}
}
},
data() {
return {
dragging: false,
mouseDown: false,
newAnnotationRectangle: {},
keyString: null,
context: null,
canvas: null,
annotationsIndex: null,
selectedAnnotations: [],
indexToAnnotationMap: {}
};
},
watch: {
imageryAnnotations() {
this.buildAnnotationIndex();
}
},
mounted() {
this.canvas = this.$refs.canvas;
this.context = this.canvas.getContext('2d');
// adjust canvas size for retina displays
const scale = window.devicePixelRatio;
this.canvas.width = Math.floor(this.canvas.width * scale);
this.canvas.height = Math.floor(this.canvas.height * scale);
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.openmct.selection.on('change', this.updateSelection);
this.buildAnnotationIndex();
},
beforeDestroy() {
this.openmct.selection.off('change', this.updateSelection);
document.body.removeEventListener('click', this.cancelSelection);
},
methods: {
buildAnnotationIndex() {
if (this.imageryAnnotations.length) {
// create a flatbush index for the annotations
this.annotationsIndex = new Flatbush(this.imageryAnnotations.length);
this.imageryAnnotations.forEach((annotation) => {
const annotationRectangle = annotation.targets[this.keyString].rectangle;
const indexNumber = this.annotationsIndex.add(
annotationRectangle.x,
annotationRectangle.y,
annotationRectangle.x + annotationRectangle.width,
annotationRectangle.y + annotationRectangle.height
);
this.indexToAnnotationMap[indexNumber] = annotation;
});
this.annotationsIndex.finish();
this.drawAnnotations();
}
},
onAnnotationChange(annotations) {
this.selectedAnnotations = annotations;
this.$emit('annotationsChanged', annotations);
},
updateSelection(selection) {
const selectionContext = selection?.[0]?.[0]?.context?.item;
const selectionType = selection?.[0]?.[0]?.context?.type;
const validSelectionTypes = ['clicked-on-image-selection'];
if (!validSelectionTypes.includes(selectionType)) {
// wrong type of selection
return;
}
if (
selectionContext &&
this.openmct.objects.areIdsEqual(selectionContext.identifier, this.domainObject.identifier)
) {
return;
}
const incomingSelectedAnnotations = selection?.[0]?.[0]?.context?.annotations;
this.prepareExistingAnnotationSelection(incomingSelectedAnnotations);
},
prepareExistingAnnotationSelection(annotations) {
const targetDomainObjects = {};
targetDomainObjects[this.keyString] = this.domainObject;
const targetDetails = {};
annotations.forEach((annotation) => {
Object.entries(annotation.targets).forEach(([key, value]) => {
targetDetails[key] = value;
});
});
this.selectedAnnotations = annotations;
this.drawAnnotations();
return {
targetDomainObjects,
targetDetails
};
},
clearSelectedAnnotations() {
if (!this.openmct.annotation.getAvailableTags().length) {
// don't bother with new annotations if there are no tags
return;
}
this.mouseDown = true;
this.selectedAnnotations = [];
},
drawRectInCanvas(rectangle, fillStyle, strokeStyle) {
this.context.beginPath();
this.context.lineWidth = 1;
this.context.fillStyle = fillStyle;
this.context.strokeStyle = strokeStyle;
this.context.rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
this.context.fill();
this.context.stroke();
},
trackAnnotationDrag(event) {
if (this.mouseDown && !this.dragging && event.shiftKey && event.altKey) {
this.startAnnotationDrag(event);
} else if (this.dragging) {
const boundingRect = this.canvas.getBoundingClientRect();
const scaleX = this.canvas.width / boundingRect.width;
const scaleY = this.canvas.height / boundingRect.height;
this.newAnnotationRectangle = {
x: this.newAnnotationRectangle.x,
y: this.newAnnotationRectangle.y,
width: (event.clientX - boundingRect.left) * scaleX - this.newAnnotationRectangle.x,
height: (event.clientY - boundingRect.top) * scaleY - this.newAnnotationRectangle.y
};
this.drawAnnotations();
this.drawRectInCanvas(
this.newAnnotationRectangle,
SELECTED_ANNOTATION_FILL_STYLE,
SELECTED_ANNOTATION_STROKE_COLOR
);
}
},
clearCanvas() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
selectImageView() {
// should show ImageView itself if we have no annotations to display
const selection = this.createPathSelection();
this.openmct.selection.select(selection, true);
},
createSelection(annotation) {
const selection = this.createPathSelection();
selection[0].context = annotation;
return selection;
},
selectImageAnnotations({ targetDetails, targetDomainObjects, annotations }) {
const annotationContext = {
type: 'clicked-on-image-selection',
targetDetails,
targetDomainObjects,
annotations,
annotationType: this.openmct.annotation.ANNOTATION_TYPES.PIXEL_SPATIAL,
onAnnotationChange: this.onAnnotationChange
};
const selection = this.createPathSelection();
if (
selection.length &&
this.openmct.objects.areIdsEqual(
selection[0].context.item.identifier,
this.domainObject.identifier
)
) {
selection[0].context = {
...selection[0].context,
...annotationContext
};
} else {
selection.unshift({
element: this.$el,
context: {
item: this.domainObject,
...annotationContext
}
});
}
this.openmct.selection.select(selection, true);
document.body.addEventListener('click', this.cancelSelection);
},
cancelSelection(event) {
if (this.$refs.canvas) {
const clickedInsideCanvas = this.$refs.canvas.contains(event.target);
const clickedInsideInspector = event.target.closest('.js-inspector') !== null;
const clickedOption = event.target.closest('.js-autocomplete-options') !== null;
if (!clickedInsideCanvas && !clickedInsideInspector && !clickedOption) {
this.newAnnotationRectangle = {};
this.selectedAnnotations = [];
this.drawAnnotations();
}
}
},
createNewAnnotation() {
this.dragging = false;
this.selectedAnnotations = [];
const targetDomainObjects = {};
targetDomainObjects[this.keyString] = this.domainObject;
const targetDetails = {};
targetDetails[this.keyString] = {
rectangle: {
x: this.newAnnotationRectangle.x,
y: this.newAnnotationRectangle.y,
width: this.newAnnotationRectangle.width,
height: this.newAnnotationRectangle.height
},
time: this.image.time
};
this.selectImageAnnotations({
targetDetails,
targetDomainObjects,
annotations: []
});
},
attemptToSelectExistingAnnotation(event) {
this.dragging = false;
// use flatbush to find annotations that are close to the click
const boundingRect = this.canvas.getBoundingClientRect();
const scaleX = this.canvas.width / boundingRect.width;
const scaleY = this.canvas.height / boundingRect.height;
const x = (event.clientX - boundingRect.left) * scaleX;
const y = (event.clientY - boundingRect.top) * scaleY;
if (this.annotationsIndex) {
let nearbyAnnotations = [];
const resultIndicies = this.annotationsIndex.search(x, y, x, y);
resultIndicies.forEach((resultIndex) => {
const foundAnnotation = this.indexToAnnotationMap[resultIndex];
nearbyAnnotations.push(foundAnnotation);
});
//show annotations if some were found
const { targetDomainObjects, targetDetails } =
this.prepareExistingAnnotationSelection(nearbyAnnotations);
this.selectImageAnnotations({
targetDetails,
targetDomainObjects,
annotations: nearbyAnnotations
});
} else {
// nothing selected
this.drawAnnotations();
}
},
selectOrCreateAnnotation(event) {
event.stopPropagation();
this.mouseDown = false;
if (
!this.dragging ||
(!this.newAnnotationRectangle.width && !this.newAnnotationRectangle.height)
) {
this.newAnnotationRectangle = {};
this.attemptToSelectExistingAnnotation(event);
} else {
this.createNewAnnotation();
}
},
createPathSelection() {
let selection = [];
selection.unshift({
element: this.$el,
context: {
item: this.domainObject
}
});
this.objectPath.forEach((pathObject, index) => {
selection.push({
element: this.openmct.layout.$refs.browseObject.$el,
context: {
item: pathObject
}
});
});
return selection;
},
startAnnotationDrag(event) {
this.$emit('annotationMarqueed');
this.newAnnotationRectangle = {};
const boundingRect = this.canvas.getBoundingClientRect();
const scaleX = this.canvas.width / boundingRect.width;
const scaleY = this.canvas.height / boundingRect.height;
this.newAnnotationRectangle = {
x: (event.clientX - boundingRect.left) * scaleX,
y: (event.clientY - boundingRect.top) * scaleY
};
this.dragging = true;
},
isSelectedAnnotation(annotation) {
const someSelectedAnnotationExists = this.selectedAnnotations.some((selectedAnnotation) => {
return this.openmct.objects.areIdsEqual(
selectedAnnotation.identifier,
annotation.identifier
);
});
return someSelectedAnnotationExists;
},
drawAnnotations() {
this.clearCanvas();
this.imageryAnnotations.forEach((annotation) => {
if (this.isSelectedAnnotation(annotation)) {
this.drawRectInCanvas(
annotation.targets[this.keyString].rectangle,
SELECTED_ANNOTATION_FILL_STYLE,
SELECTED_ANNOTATION_STROKE_COLOR
);
} else {
this.drawRectInCanvas(
annotation.targets[this.keyString].rectangle,
EXISTING_ANNOTATION_FILL_STYLE,
EXISTING_ANNOTATION_STROKE_STYLE
);
}
});
}
}
};
</script>

View File

@@ -19,7 +19,7 @@ $elemBg: rgba(black, 0.7);
position: absolute;
left: 0;
top: 0;
z-index: 2;
z-index: 3;
@include userSelectNone;
}

View File

@@ -38,6 +38,11 @@
fetchpriority="low"
@load="imageLoadCompleted"
/>
<i
v-show="showAnnotationIndicator"
class="c-thumb__annotation-indicator icon-status-poll-edit"
>
</i>
</a>
<div v-if="viewableArea" class="c-thumb__viewable-area" :style="viewableAreaStyle"></div>
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
@@ -66,6 +71,12 @@ export default {
type: Boolean,
required: true
},
imageryAnnotations: {
type: Array,
default() {
return [];
}
},
viewableArea: {
type: Object,
default: function () {
@@ -125,6 +136,9 @@ export default {
width: `${width}px`,
height: `${height}px`
};
},
showAnnotationIndicator() {
return this.imageryAnnotations?.length > 0;
}
},
methods: {

View File

@@ -88,6 +88,13 @@
:image="focusedImage"
:sized-image-dimensions="sizedImageDimensions"
/>
<AnnotationsCanvas
v-if="shouldDisplayAnnotations"
:image="focusedImage"
:imagery-annotations="imageryAnnotations[focusedImage.time]"
@annotationMarqueed="handlePauseButton(true)"
@annotationsChanged="loadAnnotations"
/>
</div>
</div>
@@ -173,6 +180,7 @@
:key="`${image.thumbnailUrl || image.url}-${image.time}-${index}`"
:image="image"
:active="focusedImageIndex === index"
:imagery-annotations="imageryAnnotations[image.time]"
:selected="focusedImageIndex === index && isPaused"
:real-time="!isFixed"
:viewable-area="focusedImageIndex === index ? viewableArea : null"
@@ -200,6 +208,7 @@ import Compass from './Compass/Compass.vue';
import ImageControls from './ImageControls.vue';
import ImageThumbnail from './ImageThumbnail.vue';
import imageryData from '../../imagery/mixins/imageryData';
import AnnotationsCanvas from './AnnotationsCanvas.vue';
const REFRESH_CSS_MS = 500;
const DURATION_TRACK_MS = 1000;
@@ -232,7 +241,8 @@ export default {
components: {
Compass,
ImageControls,
ImageThumbnail
ImageThumbnail,
AnnotationsCanvas
},
mixins: [imageryData],
inject: ['openmct', 'domainObject', 'objectPath', 'currentView', 'imageFreshnessOptions'],
@@ -295,7 +305,8 @@ export default {
animateZoom: true,
imagePanned: false,
forceShowThumbnails: false,
animateThumbScroll: false
animateThumbScroll: false,
imageryAnnotations: {}
};
},
computed: {
@@ -425,6 +436,19 @@ export default {
return result;
},
shouldDisplayAnnotations() {
const imageHeightAndWidth = this.sizedImageHeight !== 0 && this.sizedImageWidth !== 0;
const display =
this.focusedImage !== undefined &&
this.focusedImageNaturalAspectRatio !== undefined &&
this.imageContainerWidth !== undefined &&
this.imageContainerHeight !== undefined &&
imageHeightAndWidth &&
this.zoomFactor === 1 &&
this.imagePanned !== true;
return display;
},
shouldDisplayCompass() {
const imageHeightAndWidth = this.sizedImageHeight !== 0 && this.sizedImageWidth !== 0;
const display =
@@ -689,6 +713,9 @@ export default {
this.listenTo(this.focusedImageWrapper, 'wheel', this.wheelZoom, this);
this.loadVisibleLayers();
this.loadAnnotations();
this.openmct.selection.on('change', this.updateSelection);
},
beforeDestroy() {
this.persistVisibleLayers();
@@ -716,6 +743,15 @@ export default {
}
this.stopListening(this.focusedImageWrapper, 'wheel', this.wheelZoom, this);
Object.keys(this.imageryAnnotations).forEach((time) => {
const imageAnnotationsForTime = this.imageryAnnotations[time];
imageAnnotationsForTime.forEach((imageAnnotation) => {
this.openmct.objects.destroyMutable(imageAnnotation);
});
});
this.openmct.selection.off('change', this.updateSelection);
},
methods: {
calculateViewHeight() {
@@ -743,6 +779,24 @@ export default {
this.timeContext.off('clock', this.trackDuration);
}
},
updateSelection(selection) {
const selectionType = selection?.[0]?.[0]?.context?.type;
const validSelectionTypes = ['annotation-search-result'];
if (!validSelectionTypes.includes(selectionType)) {
// wrong type of selection
return;
}
const incomingSelectedAnnotation = selection?.[0]?.[0]?.context?.annotations?.[0];
console.debug(`📲 incoming search selections`, incomingSelectedAnnotation);
// TODO in https://github.com/nasa/openmct/issues/6731
// For incoming search results, we should:
// 1. set the the time bounds to match the search result
// 2. search the imageHistory for the image that matches the time of the search result
// 3. using the index from the above, "click" on the image to select it
// 4. pass to the annotation canvas layer the selected annotation
},
expand() {
// check for modifier keys so it doesnt interfere with the layout
if (this.cursorStates.modifierKeyPressed) {
@@ -832,6 +886,40 @@ export default {
});
}
},
async loadAnnotations(existingAnnotations) {
if (!this.openmct.annotation.getAvailableTags().length) {
// don't bother loading annotations if there are no tags
return;
}
let foundAnnotations = existingAnnotations;
if (!foundAnnotations) {
// attempt to load
foundAnnotations = await this.openmct.annotation.getAnnotations(
this.domainObject.identifier
);
}
foundAnnotations.forEach((foundAnnotation) => {
const targetId = Object.keys(foundAnnotation.targets)[0];
const timeForAnnotation = foundAnnotation.targets[targetId].time;
if (!this.imageryAnnotations[timeForAnnotation]) {
this.$set(this.imageryAnnotations, timeForAnnotation, []);
}
const annotationExtant = this.imageryAnnotations[timeForAnnotation].some(
(existingAnnotation) => {
return this.openmct.objects.areIdsEqual(
existingAnnotation.identifier,
foundAnnotation.identifier
);
}
);
if (!annotationExtant) {
const annotationArray = this.imageryAnnotations[timeForAnnotation];
const mutableAnnotation = this.openmct.objects.toMutable(foundAnnotation);
annotationArray.push(mutableAnnotation);
}
});
},
persistVisibleLayers() {
if (
this.domainObject.configuration &&
@@ -979,7 +1067,9 @@ export default {
}
await Vue.nextTick();
this.$refs.thumbsWrapper.scrollLeft = scrollWidth;
if (this.$refs.thumbsWrapper) {
this.$refs.thumbsWrapper.scrollLeft = scrollWidth;
}
},
scrollHandler() {
if (this.isPaused) {

View File

@@ -293,6 +293,13 @@
width: 100%;
}
&__annotation-indicator {
color: $colorClickIconButton;
position: absolute;
top: 6px;
right: 8px;
}
&__timestamp {
flex: 0 0 auto;
padding: 2px 3px;
@@ -540,3 +547,11 @@
align-self: flex-end;
}
}
.c-image-canvas {
pointer-events: auto; // This allows the image element to receive a browser-level context click
position: absolute;
left: 0;
top: 0;
z-index: 2;
}

View File

@@ -41,7 +41,6 @@
<script>
import TagEditor from './tags/TagEditor.vue';
import _ from 'lodash';
export default {
components: {
@@ -123,6 +122,7 @@ export default {
}
},
async mounted() {
this.abortController = null;
this.openmct.annotation.on('targetDomainObjectAnnotated', this.loadAnnotationForTargetObject);
this.openmct.selection.on('change', this.updateSelection);
await this.updateSelection(this.openmct.selection.get());
@@ -190,20 +190,34 @@ export default {
}
},
async loadAnnotationForTargetObject(target) {
const targetID = this.openmct.objects.makeKeyString(target.identifier);
const allAnnotationsForTarget = await this.openmct.annotation.getAnnotations(
target.identifier
);
const filteredAnnotationsForSelection = allAnnotationsForTarget.filter((annotation) => {
const matchingTargetID = Object.keys(annotation.targets).filter((loadedTargetID) => {
return targetID === loadedTargetID;
});
const fetchedTargetDetails = annotation.targets[matchingTargetID];
const selectedTargetDetails = this.targetDetails[matchingTargetID];
// If the user changes targets while annotations are loading,
// abort the previous request.
if (this.abortController !== null) {
this.abortController.abort();
}
return _.isEqual(fetchedTargetDetails, selectedTargetDetails);
});
this.loadNewAnnotations(filteredAnnotationsForSelection);
this.abortController = new AbortController();
try {
const allAnnotationsForTarget = await this.openmct.annotation.getAnnotations(
target.identifier,
this.abortController.signal
);
const filteredAnnotationsForSelection = allAnnotationsForTarget.filter((annotation) =>
this.openmct.annotation.areAnnotationTargetsEqual(
this.annotationType,
this.targetDetails,
annotation.targets
)
);
this.loadNewAnnotations(filteredAnnotationsForSelection);
} catch (err) {
if (err.name !== 'AbortError') {
throw err;
}
} finally {
this.abortController = null;
}
}
}
};

View File

@@ -306,13 +306,22 @@ export default {
this.getSearchResults = debounce(this.getSearchResults, 500);
this.syncUrlWithPageAndSection = debounce(this.syncUrlWithPageAndSection, 100);
},
async mounted() {
await this.loadAnnotations();
async created() {
this.transaction = null;
this.abortController = new AbortController();
try {
await this.loadAnnotations();
} catch (err) {
if (err.name !== 'AbortError') {
throw err;
}
}
},
mounted() {
this.formatSidebar();
this.setSectionAndPageFromUrl();
this.openmct.selection.on('change', this.updateSelection);
this.transaction = null;
window.addEventListener('orientationchange', this.formatSidebar);
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
@@ -324,6 +333,7 @@ export default {
);
},
beforeDestroy() {
this.abortController.abort();
if (this.unlisten) {
this.unlisten();
}
@@ -387,8 +397,10 @@ export default {
this.lastLocalAnnotationCreation = this.domainObject.annotationLastCreated ?? 0;
const foundAnnotations = await this.openmct.annotation.getAnnotations(
this.domainObject.identifier
this.domainObject.identifier,
this.abortController.signal
);
foundAnnotations.forEach((foundAnnotation) => {
const targetId = Object.keys(foundAnnotation.targets)[0];
const entryId = foundAnnotation.targets[targetId].entryId;
@@ -425,7 +437,11 @@ export default {
: [...filteredPageEntriesByTime].reverse();
if (this.lastLocalAnnotationCreation < this.domainObject.annotationLastCreated) {
this.loadAnnotations();
this.loadAnnotations().catch((err) => {
if (err.name !== 'AbortError') {
throw err;
}
});
}
},
changeSelectedSection({ sectionId, pageId }) {

View File

@@ -27,7 +27,13 @@
// If the above namespace is ever resolved, we can fold this search provider
// back into the object provider.
const BATCH_ANNOTATION_DEBOUNCE_MS = 100;
class CouchSearchProvider {
#bulkPromise;
#batchIds;
#lastAbortSignal;
constructor(couchObjectProvider) {
this.couchObjectProvider = couchObjectProvider;
this.searchTypes = couchObjectProvider.openmct.objects.SEARCH_TYPES;
@@ -36,6 +42,8 @@ class CouchSearchProvider {
this.searchTypes.ANNOTATIONS,
this.searchTypes.TAGS
];
this.#batchIds = [];
this.#bulkPromise = null;
}
supportsSearchType(searchType) {
@@ -68,28 +76,77 @@ class CouchSearchProvider {
return this.couchObjectProvider.getObjectsByFilter(filter, abortSignal);
}
searchForAnnotations(keyString, abortSignal) {
async #deferBatchAnnotationSearch() {
// We until the next event loop cycle to "collect" all of the get
// requests triggered in this iteration of the event loop
await this.#waitForDebounce();
const batchIdsToSearch = [...this.#batchIds];
this.#clearBatch();
return this.#bulkAnnotationSearch(batchIdsToSearch);
}
#clearBatch() {
this.#batchIds = [];
this.#bulkPromise = undefined;
}
#waitForDebounce() {
let timeoutID;
clearTimeout(timeoutID);
return new Promise((resolve) => {
timeoutID = setTimeout(() => {
resolve();
}, BATCH_ANNOTATION_DEBOUNCE_MS);
});
}
#bulkAnnotationSearch(batchIdsToSearch) {
const filter = {
selector: {
$and: [
{
model: {
targets: {}
}
},
{
'model.type': {
$eq: 'annotation'
}
},
{
$or: []
}
]
}
};
filter.selector.$and[0].model.targets[keyString] = {
$exists: true
};
let lastAbortSignal = null;
// TODO: should remove duplicates from batchIds
batchIdsToSearch.forEach(({ keyString, abortSignal }) => {
const modelFilter = {
model: {
targets: {}
}
};
modelFilter.model.targets[keyString] = {
$exists: true
};
return this.couchObjectProvider.getObjectsByFilter(filter, abortSignal);
filter.selector.$and[1].$or.push(modelFilter);
lastAbortSignal = abortSignal;
});
return this.couchObjectProvider.getObjectsByFilter(filter, lastAbortSignal);
}
async searchForAnnotations(keyString, abortSignal) {
this.#batchIds.push({ keyString, abortSignal });
if (!this.#bulkPromise) {
this.#bulkPromise = this.#deferBatchAnnotationSearch();
}
const returnedData = await this.#bulkPromise;
// only return data that matches the keystring
const filteredByKeyString = returnedData.filter((foundAnnotation) => {
return foundAnnotation.targets[keyString];
});
return filteredByKeyString;
}
searchForTags(tagsArray, abortSignal) {

View File

@@ -183,7 +183,7 @@ import MctTicks from './MctTicks.vue';
import MctChart from './chart/MctChart.vue';
import XAxis from './axis/XAxis.vue';
import YAxis from './axis/YAxis.vue';
import KDBush from 'kdbush';
import Flatbush from 'flatbush';
import _ from 'lodash';
const OFFSET_THRESHOLD = 10;
@@ -339,6 +339,9 @@ export default {
this.cursorGuide = newCursorGuide;
}
},
created() {
this.abortController = new AbortController();
},
mounted() {
this.yAxisIdVisibility = {};
this.offsetWidth = 0;
@@ -398,6 +401,7 @@ export default {
this.loaded = true;
},
beforeDestroy() {
this.abortController.abort();
this.openmct.selection.off('change', this.updateSelection);
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
@@ -410,8 +414,8 @@ export default {
// on clicking on a search result we highlight the annotation and zoom - we know it's an annotation result when isAnnotationSearchResult === true
// We shouldn't zoom when we're selecting existing annotations to view them or creating new annotations.
const selectionType = selection?.[0]?.[0]?.context?.type;
const validSelectionTypes = ['clicked-on-plot-selection', 'plot-annotation-search-result'];
const isAnnotationSearchResult = selectionType === 'plot-annotation-search-result';
const validSelectionTypes = ['clicked-on-plot-selection', 'annotation-search-result'];
const isAnnotationSearchResult = selectionType === 'annotation-search-result';
if (!validSelectionTypes.includes(selectionType)) {
// wrong type of selection
@@ -621,7 +625,8 @@ export default {
await Promise.all(
this.seriesModels.map(async (seriesModel) => {
const seriesAnnotations = await this.openmct.annotation.getAnnotations(
seriesModel.model.identifier
seriesModel.model.identifier,
this.abortController.signal
);
rawAnnotationsForPlot.push(...seriesAnnotations);
})
@@ -1393,6 +1398,24 @@ export default {
return annotationsByPoints.flat();
},
searchWithFlatbush(seriesData, seriesModel, boundingBox) {
const flatbush = new Flatbush(seriesData.length);
seriesData.forEach((point) => {
const x = seriesModel.getXVal(point);
const y = seriesModel.getYVal(point);
flatbush.add(x, y, x, y);
});
flatbush.finish();
const rangeResults = flatbush.search(
boundingBox.minX,
boundingBox.minY,
boundingBox.maxX,
boundingBox.maxY
);
return rangeResults;
},
getPointsInBox(boundingBoxPerYAxis, rawAnnotation) {
// load series models in KD-Trees
const seriesKDTrees = [];
@@ -1408,22 +1431,8 @@ export default {
const seriesData = seriesModel.getSeriesData();
if (seriesData && seriesData.length) {
const kdTree = new KDBush(
seriesData,
(point) => {
return seriesModel.getXVal(point);
},
(point) => {
return seriesModel.getYVal(point);
}
);
const searchResults = [];
const rangeResults = kdTree.range(
boundingBox.minX,
boundingBox.minY,
boundingBox.maxX,
boundingBox.maxY
);
const rangeResults = this.searchWithFlatbush(seriesData, seriesModel, boundingBox);
rangeResults.forEach((id) => {
const seriesDatum = seriesData[id];
if (seriesDatum) {
@@ -1524,7 +1533,11 @@ export default {
this.endMarquee();
}
this.loadAnnotations();
this.loadAnnotations().catch((err) => {
if (err.name !== 'AbortError') {
throw err;
}
});
},
zoom(zoomDirection, zoomFactor) {

View File

@@ -98,6 +98,13 @@ export default {
}
return 'Could not find any matching Notebook entries';
} else if (
this.result.annotationType === this.openmct.annotation.ANNOTATION_TYPES.GEOSPATIAL
) {
const targetID = Object.keys(this.result.targets)[0];
const { layerName, name } = this.result.targets[targetID];
return layerName ? `${layerName} - ${name}` : name;
} else {
return this.result.targetModels[0].name;
}
@@ -115,11 +122,11 @@ export default {
mounted() {
this.previewAction = new PreviewAction(this.openmct);
this.previewAction.on('isVisible', this.togglePreviewState);
this.clickedPlotAnnotation = this.clickedPlotAnnotation.bind(this);
this.fireAnnotationSelection = this.fireAnnotationSelection.bind(this);
},
destroyed() {
this.previewAction.off('isVisible', this.togglePreviewState);
this.openmct.selection.off('change', this.clickedPlotAnnotation);
this.openmct.selection.off('change', this.fireAnnotationSelection);
},
methods: {
clickedResult(event) {
@@ -132,17 +139,15 @@ export default {
if (!this.openmct.router.isNavigatedObject(objectPath)) {
// if we're not on the correct page, navigate to the object,
// then wait for the selection event to fire before issuing a new selection
if (
this.result.annotationType === this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL
) {
this.openmct.selection.on('change', this.clickedPlotAnnotation);
if (this.result.annotationType) {
this.openmct.selection.on('change', this.fireAnnotationSelection);
}
this.openmct.router.navigate(resultUrl);
} else {
// if this is the navigated object, then we are already on the correct page
// and just need to issue the selection event
this.clickedPlotAnnotation();
this.fireAnnotationSelection();
}
}
},
@@ -151,8 +156,8 @@ export default {
this.previewAction.invoke(objectPath);
}
},
clickedPlotAnnotation() {
this.openmct.selection.off('change', this.clickedPlotAnnotation);
fireAnnotationSelection() {
this.openmct.selection.off('change', this.fireAnnotationSelection);
const targetDetails = {};
const targetDomainObjects = {};
@@ -168,11 +173,11 @@ export default {
element: this.$el,
context: {
item: this.result.targetModels[0],
type: 'plot-annotation-search-result',
type: 'annotation-search-result',
targetDetails,
targetDomainObjects,
annotations: [this.result],
annotationType: this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL,
annotationType: this.result.annotationType,
onAnnotationChange: () => {}
}
}