Compare commits

...

19 Commits

Author SHA1 Message Date
6b719259e3 WIP: shelving for now, but may be useful in future 2021-01-04 11:59:08 -08:00
9fc31809f6 Merge branch 'search-index-type' into tree-search-item
Merg'n search index type to use in tree
2020-12-23 13:19:33 -08:00
ccd4bbd279 Merge branch 'master' into search-index-type
Merg'n master
2020-12-23 13:18:50 -08:00
72848849dd WIP working with imagery for testing 2020-12-14 14:14:47 -08:00
66130ba542 Merge branch 'master' into telemetry-collection
Merg'n master
2020-12-11 10:49:12 -08:00
895bdc164f WIP 2020-12-09 11:57:11 -08:00
c9728144a5 Merge branch 'master' into telemetry-collection
Merg'n master
2020-12-07 12:12:21 -08:00
76fec7f3bc WIP 2020-12-01 12:04:14 -08:00
1b4717065a WIP 2020-11-30 14:35:02 -08:00
e24542c1a6 Merge branch 'master' into telemetry-collection
Merg'n master
2020-11-24 11:22:04 -08:00
b08f3106ed WIP 2020-11-24 10:34:08 -08:00
700bc7616d WIP 2020-11-20 11:13:41 -08:00
3436e976cf Merge branch 'master' into search-index-type 2020-11-13 17:03:45 -08:00
b8e232831e Merge branch 'master' into search-index-type 2020-10-15 09:33:58 -07:00
f6bc49fc82 using type from model, instead of passing in separately 2020-10-15 09:33:09 -07:00
7018c217c4 Merge branch 'master' into search-index-type 2020-10-02 15:46:53 -07:00
18c230c0f7 reverting 2020-10-02 15:27:22 -07:00
b8c2f3f49a Reverting some files 2020-10-02 15:22:19 -07:00
e5e27ea498 WIP 2020-10-02 10:26:29 -07:00
13 changed files with 832 additions and 61 deletions

View File

@ -32,7 +32,8 @@
function indexItem(id, model) {
indexedItems.push({
id: id,
name: model.name.toLowerCase()
name: model.name.toLowerCase(),
type: model.type
});
}

View File

@ -125,15 +125,17 @@ define([
* @param topic the topicService.
*/
GenericSearchProvider.prototype.indexOnMutation = function (topic) {
var mutationTopic = topic('mutation'),
provider = this;
let mutationTopic = topic('mutation');
mutationTopic.listen(function (mutatedObject) {
var editor = mutatedObject.getCapability('editor');
mutationTopic.listen(mutatedObject => {
let editor = mutatedObject.getCapability('editor');
if (!editor || !editor.inEditContext()) {
provider.index(
let mutatedObjectModel = mutatedObject.getModel();
this.index(
mutatedObject.getId(),
mutatedObject.getModel()
mutatedObjectModel,
mutatedObjectModel.type
);
}
});
@ -178,14 +180,15 @@ define([
* @param id a model id
* @param model a model
*/
GenericSearchProvider.prototype.index = function (id, model) {
GenericSearchProvider.prototype.index = function (id, model, type) {
var provider = this;
if (id !== 'ROOT') {
this.worker.postMessage({
request: 'index',
model: model,
id: id
id: id,
type: type
});
}
@ -223,7 +226,7 @@ define([
.then(function (objects) {
delete provider.pendingIndex[idToIndex];
if (objects[idToIndex]) {
provider.index(idToIndex, objects[idToIndex].model);
provider.index(idToIndex, objects[idToIndex].model, objects[idToIndex].model.type);
}
}, function () {
provider
@ -262,6 +265,7 @@ define([
return {
id: hit.item.id,
model: hit.item.model,
type: hit.item.type,
score: hit.matchCount
};
});
@ -273,7 +277,9 @@ define([
modelResults.hits = event.data.results.map(function (hit) {
return {
id: hit.id
id: hit.id,
name: hit.name,
type: hit.type
};
});
}

View File

@ -41,7 +41,8 @@
indexedItems.push({
id: id,
vector: vector,
model: model
model: model,
type: model.type
});
}

View File

@ -85,7 +85,8 @@ define([
SearchAggregator.prototype.query = function (
inputText,
maxResults,
filter
filter,
indexOnly
) {
var aggregator = this,
@ -120,10 +121,24 @@ define([
modelResults = aggregator.applyFilter(modelResults, filter);
modelResults = aggregator.removeDuplicates(modelResults);
if (indexOnly) {
return Promise.resolve(modelResults);
}
return aggregator.asObjectResults(modelResults);
});
};
SearchAggregator.prototype.queryLite = function (
inputText,
maxResults,
filter
) {
const INDEX_ONLY = true;
return this.query(inputText, maxResults, filter, INDEX_ONLY);
};
/**
* Order model results by score descending and return them.
*/

View File

@ -20,6 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { TelemetryCollection } = require("./TelemetryCollection");
define([
'../../plugins/displayLayout/CustomStringFormatter',
'./TelemetryMetadataManager',
@ -273,6 +275,48 @@ define([
}
};
/**
* Request telemetry collection for a domain object.
* The `options` argument allows you to specify filters
* (start, end, etc.), sort order, and strategies for retrieving
* telemetry (aggregation, latest available, etc.).
*
* @method request
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
* @param {module:openmct.DomainObject} domainObject the object
* which has associated telemetry
* @param {module:openmct.TelemetryAPI~TelemetryRequest} options
* options for this historical request
* @returns {Promise.<object[]>} a promise for an array of
* telemetry data
*/
TelemetryAPI.prototype.requestTelemetryCollection = function (domainObject) {
if (arguments.length === 1) {
arguments.length = 2;
arguments[1] = {};
}
// historical setup
this.standardizeRequestOptions(arguments[1]);
const historicalProvider = this.findRequestProvider(domainObject, arguments);
// subscription setup
const subscriptionProvider = this.findSubscriptionProvider(domainObject);
// check for no providers
if (!historicalProvider && !subscriptionProvider) {
return Promise.reject('No providers found');
}
let telemetryCollectionOptions = {
historicalProvider,
subscriptionProvider,
arguments: arguments
};
return Promise.resolve(new TelemetryCollection(this.openmct, domainObject, telemetryCollectionOptions));
};
/**
* Request historical telemetry for a domain object.
* The `options` argument allows you to specify filters

View File

@ -0,0 +1,315 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
// import _ from 'lodash';
import TelemetrySubscriptionService from './TelemetrySubscriptionService';
function bindUs() {
return [
'trackHistoricalTelemetry',
'trackSubscriptionTelemetry',
'addPage',
'processNewTelemetry',
'hasMorePages',
'nextPage',
'bounds',
'timeSystem',
'on',
'off',
'emit',
'subscribeToBounds',
'unsubscribeFromBounds',
'subscribeToTimeSystem',
'unsubscribeFromTimeSystem',
'destroy'
];
}
export class TelemetryCollection {
constructor(openmct, domainObject, options) {
bindUs().forEach(method => this[method] = this[method].bind(this));
this.openmct = openmct;
this.domainObject = domainObject;
this.boundedTelemetry = [];
this.futureBuffer = [];
this.parseTime = undefined;
this.timeSystem(openmct.time.timeSystem());
this.lastBounds = openmct.time.bounds();
this.historicalProvider = options.historicalProvider;
this.subscriptionProvider = options.subscriptionProvider;
this.arguments = options.arguments;
this.listeners = {
add: [],
remove: []
};
this.trackHistoricalTelemetry();
this.trackSubscriptionTelemetry();
this.subscribeToBounds();
this.subscribeToTimeSystem();
}
// should we wait to track history until an 'add' listener is added?
async trackHistoricalTelemetry() {
if (!this.historicalProvider) {
return;
}
// remove for reset
if (this.boundedTelemetry.length !== 0) {
this.emit('remove', this.boundedTelemetry);
this.boundedTelemetry = [];
}
let historicalData = await this.historicalProvider.request.apply(this.domainObject, this.arguments).catch((rejected) => {
this.openmct.notifications.error('Error requesting telemetry data, see console for details');
console.error(rejected);
return Promise.reject(rejected);
});
// make sure it wasn't rejected
if (Array.isArray(historicalData)) {
// reset on requests, should only happen on initial load,
// bounds manually changed and time system changes
this.boundedTelemetry = historicalData;
this.emit('add', [...this.boundedTelemetry]);
}
}
trackSubscriptionTelemetry() {
if (!this.subscriptionProvider) {
return;
}
this.subscriptionService = new TelemetrySubscriptionService(this.openmct);
this.unsubscribe = this.subscriptionService.subscribe(
this.domainObject,
this.processNewTelemetry,
this.subscriptionProvider,
this.arguments
);
}
// utilized by telemetry provider to add more data
addPage(telemetryData) {
this.processNewTelemetry(telemetryData);
}
// used to sort any new telemetry (add/page, historical, subscription)
processNewTelemetry(telemetryData) {
let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData];
let parsedValue;
let beforeStartOfBounds;
let afterEndOfBounds;
let added = [];
for (let datum of data) {
parsedValue = this.parseTime(datum);
beforeStartOfBounds = parsedValue < this.lastBounds.start;
afterEndOfBounds = parsedValue > this.lastBounds.end;
if (!afterEndOfBounds && !beforeStartOfBounds) {
if (!this.boundedTelemetry.includes(datum)) {
this.boundedTelemetry.push(datum);
added.push(datum);
}
} else if (afterEndOfBounds) {
this.futureBuffer.push(datum);
}
}
if (added.length) {
this.emit('add', added);
}
}
// returns a boolean if there is more telemetry within the time bounds
// if the provider supports it
hasMorePages() {
return this.historicalProvider
&& this.historicalProvider.supportsPaging()
&& this.historicalProvider.hasMorePages(this);
}
// will return the next "page" of telemetry if the provider supports it
nextPage() {
if (!this.historicalProvider || !this.historicalProvider.supportsPaging()) {
throw new Error('Provider does not support paging');
}
this.historicalProvider.nextPage(this.arguments, this);
}
// when user changes bounds, or when bounds increment from a tick
bounds(bounds, isTick) {
this.lastBounds = bounds;
if (isTick) {
// need to check futureBuffer and need to check
// if anything has fallen out of bounds
} else {
// TODO: also reset right?
// need to reset and request history again
// no need to mess with subscription
}
let startChanged = this.lastBounds.start !== bounds.start;
let endChanged = this.lastBounds.end !== bounds.end;
let startIndex = 0;
let endIndex = 0;
let discarded = [];
let added = [];
let testValue = {
datum: {}
};
this.lastBounds = bounds;
if (startChanged) {
testValue.datum[this.sortOptions.key] = bounds.start;
// Calculate the new index of the first item within the bounds
startIndex = this.sortedIndex(this.rows, testValue);
discarded = this.rows.splice(0, startIndex);
}
if (endChanged) {
testValue.datum[this.sortOptions.key] = bounds.end;
// Calculate the new index of the last item in bounds
endIndex = this.sortedLastIndex(this.futureBuffer.rows, testValue);
added = this.futureBuffer.rows.splice(0, endIndex);
added.forEach((datum) => this.rows.push(datum));
}
if (discarded.length > 0) {
/**
* A `discarded` event is emitted when telemetry data fall out of
* bounds due to a bounds change event
* @type {object[]} discarded the telemetry data
* discarded as a result of the bounds change
*/
this.emit('remove', discarded);
}
if (added.length > 0) {
/**
* An `added` event is emitted when a bounds change results in
* received telemetry falling within the new bounds.
* @type {object[]} added the telemetry data that is now within bounds
*/
this.emit('add', added);
}
}
timeSystem(timeSystem) {
let timeKey = timeSystem.key;
let formatter = this.openmct.telemetry.getValueFormatter({
key: timeKey,
source: timeKey,
format: timeKey
});
this.parseTime = formatter.parse;
// TODO: Reset right?
}
on(event, callback, context) {
if (!this.listeners[event]) {
throw new Error('Event not supported by Telemetry Collections: ' + event);
}
if (this.listeners[event].includes(callback)) {
throw new Error('Tried to add a listener that is already registered');
}
this.listeners[event].push({
callback: callback,
context: context
});
}
// Unregister TelemetryCollection events.
off(event, callback) {
if (!this.listeners[event]) {
throw new Error('Event not supported by Telemetry Collections: ' + event);
}
if (!this.listeners[event].includes(callback)) {
throw new Error('Tried to remove a listener that does not exist');
}
this.listeners[event].remove(callback);
}
emit(event, payload) {
if (!this.listeners[event].length) {
return;
}
payload = [...payload];
this.listeners[event].forEach((listener) => {
if (listener.context) {
listener.callback.apply(listener.context, payload);
} else {
listener.callback(payload);
}
});
}
subscribeToBounds() {
this.openmct.time.on('bounds', this.bounds);
}
unsubscribeFromBounds() {
this.openmct.time.off('bounds', this.bounds);
}
subscribeToTimeSystem() {
this.openmct.time.on('timeSystem', this.timeSystem);
}
unsubscribeFromTimeSystem() {
this.openmct.time.off('bounds', this.timeSystem);
}
destroy() {
this.unsubscribeFromBounds();
this.unsubscribeFromTimeSystem();
if (this.unsubscribe) {
this.unsubscribe();
}
}
}

View File

@ -0,0 +1,72 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import objectUtils from 'objectUtils';
class TelemetrySubscriptionService {
constructor(openmct) {
if (!TelemetrySubscriptionService.instance) {
this.openmct = openmct;
this.subscriptionCache = {};
TelemetrySubscriptionService.instance = this;
}
return TelemetrySubscriptionService.instance; // eslint-disable-line no-constructor-return
}
subscribe(domainObject, callback, provider, options) {
const keyString = objectUtils.makeKeyString(domainObject.identifier);
let subscriber = this.subscriptionCache[keyString];
if (!subscriber) {
subscriber = this.subscriptionCache[keyString] = {
callbacks: [callback]
};
subscriber.unsubscribe = provider
.subscribe(domainObject, function (value) {
subscriber.callbacks.forEach(function (cb) {
cb(value);
});
}, options);
} else {
subscriber.callbacks.push(callback);
}
return () => {
subscriber.callbacks = subscriber.callbacks.filter((cb) => {
return cb !== callback;
});
if (subscriber.callbacks.length === 0) {
subscriber.unsubscribe();
delete this.subscriptionCache[keyString];
}
};
}
}
function instance(openmct) {
return new TelemetrySubscriptionService(openmct);
}
export default instance;

View File

@ -137,7 +137,8 @@ export default {
refreshCSS: false,
keyString: undefined,
focusedImageIndex: undefined,
numericDuration: undefined
numericDuration: undefined,
telemetryCollection: undefined
};
},
computed: {
@ -222,6 +223,7 @@ export default {
// kickoff
this.subscribe();
this.requestHistory();
this.requestTelemetry();
},
updated() {
this.scrollToRight();
@ -353,6 +355,15 @@ export default {
this.requestHistory();
}
},
async requestTelemetry() {
this.telemetryCollection = await this.openmct.telemetry.requestTelemetryCollection(this.domainObject);
this.telemetryCollection.on('add', (data) => {
console.log('added data', data);
});
this.telemetryCollection.on('remove', (data) => {
console.log('removed data', data);
});
},
async requestHistory() {
let bounds = this.openmct.time.bounds();
this.requestCount++;

View File

@ -42,6 +42,15 @@ export default {
navigateToPath: {
type: String,
default: undefined
},
liteObject: {
type: Boolean,
default: false
},
beforeInteraction: {
type: Function,
required: false,
default: undefined
}
},
data() {
@ -52,7 +61,8 @@ export default {
},
computed: {
typeClass() {
let type = this.openmct.types.get(this.observedObject.type);
let domainObjectType = this.liteObject ? this.domainObject.type : this.observedObject.type;
let type = this.openmct.types.get(domainObjectType);
if (!type) {
return 'icon-object-unknown';
}
@ -64,13 +74,15 @@ export default {
}
},
mounted() {
if (this.observedObject) {
// if it's a liteObject nothing to observe
if (this.observedObject && !this.liteObject) {
let removeListener = this.openmct.objects.observe(this.observedObject, '*', (newObject) => {
this.observedObject = newObject;
});
this.$once('hook:destroyed', removeListener);
}
// liteObjects do have identifiers, so statuses can be observed
this.removeStatusListener = this.openmct.status.observe(this.observedObject.identifier, this.setStatus);
this.status = this.openmct.status.get(this.observedObject.identifier);
this.previewAction = new PreviewAction(this.openmct);
@ -79,35 +91,74 @@ export default {
this.removeStatusListener();
},
methods: {
navigateOrPreview(event) {
async navigateOrPreview(event) {
// skip if editing or is a lite object with an interaction function
if (this.openmct.editor.isEditing() || !(this.liteObject && this.beforeInteraction)) {
return;
}
event.preventDefault();
if (this.openmct.editor.isEditing()) {
event.preventDefault();
this.preview();
} else if (this.liteObject && this.beforeInteraction) {
let fullObjectInfo = await this.getFullObjectInfo();
// need to update when new route functions are merged (back button PR)
window.location.href = '#/browse/' + fullObjectInfo.navigationPath;
}
},
preview() {
if (this.previewAction.appliesTo(this.objectPath)) {
this.previewAction.invoke(this.objectPath);
async preview() {
let objectPath = this.objectPath;
if (this.liteObject && this.beforeInteraction) {
let fullObjectInfo = await this.getFullObjectInfo();
objectPath = fullObjectInfo.objectPath;
}
if (this.previewAction.appliesTo(objectPath)) {
this.previewAction.invoke(objectPath);
}
},
dragStart(event) {
const LITE_DOMAIN_OBJECT_TYPE = "openmct/domain-object-lite";
let navigatedObject = this.openmct.router.path[0];
let serializedPath = JSON.stringify(this.objectPath);
let keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
let serializedPath = JSON.stringify(this.objectPath);
/*
* Cannot inspect data transfer objects on dragover/dragenter so impossible to determine composability at
* that point. If dragged object can be composed by navigated object, then indicate with presence of
* 'composable-domain-object' in data transfer
*/
if (this.openmct.composition.checkPolicy(navigatedObject, this.observedObject)) {
event.dataTransfer.setData("openmct/composable-domain-object", JSON.stringify(this.domainObject));
if (this.liteObject) {
event.dataTransfer.setData(LITE_DOMAIN_OBJECT_TYPE, JSON.stringify(this.domainObject.identifier));
} else {
/*
* Cannot inspect data transfer objects on dragover/dragenter so impossible to determine composability at
* that point. If dragged object can be composed by navigated object, then indicate with presence of
* 'composable-domain-object' in data transfer
*/
if (this.openmct.composition.checkPolicy(navigatedObject, this.observedObject)) {
event.dataTransfer.setData("openmct/composable-domain-object", JSON.stringify(this.domainObject));
}
// serialize domain object anyway, because some views can drag-and-drop objects without composition
// (eg. notabook.)
event.dataTransfer.setData("openmct/domain-object-path", serializedPath);
event.dataTransfer.setData(`openmct/domain-object/${keyString}`, this.domainObject);
}
},
async getFullObjectInfo() {
let fullObjectInfo = await this.beforeInteraction();
let objectPath = fullObjectInfo.objectPath;
let navigationPath = objectPath
.reverse()
.map(object =>
this.openmct.objects.makeKeyString(object.identifier)
).join('/');
// serialize domain object anyway, because some views can drag-and-drop objects without composition
// (eg. notabook.)
event.dataTransfer.setData("openmct/domain-object-path", serializedPath);
event.dataTransfer.setData(`openmct/domain-object/${keyString}`, this.domainObject);
fullObjectInfo.objectPath = objectPath;
fullObjectInfo.navigationPath = navigationPath;
return fullObjectInfo;
},
setStatus(status) {
this.status = status;

View File

@ -98,7 +98,11 @@
:style="scrollableStyles()"
@scroll="scrollItems"
>
<div :style="{ height: childrenHeight + 'px' }">
<!-- Regular Tree Items -->
<div
v-if="!activeSearch"
:style="{ height: childrenHeight + 'px' }"
>
<tree-item
v-for="(treeItem, index) in visibleItems"
:key="treeItem.id"
@ -108,7 +112,32 @@
:item-index="index"
:item-height="itemHeight"
:virtual-scroll="true"
:show-down="activeSearch ? false : true"
:show-down="true"
@expanded="beginNavigationRequest('handleExpanded', treeItem)"
/>
<div
v-if="showNoItems"
:style="indicatorLeftOffset"
class="c-tree__item c-tree__item--empty"
>
No items
</div>
</div>
<!-- Search Result Items (Index Only) -->
<div
v-if="activeSearch"
:style="{ height: childrenHeight + 'px' }"
>
<tree-item-lite
v-for="(treeItem, index) in visibleItems"
:key="treeItem.id"
:node="treeItem"
:left-offset="itemLeftOffset"
:item-offset="itemOffset"
:item-index="index"
:item-height="itemHeight"
:virtual-scroll="true"
:show-down="false"
@expanded="beginNavigationRequest('handleExpanded', treeItem)"
/>
<div
@ -131,6 +160,7 @@
<script>
import _ from 'lodash';
import treeItem from './tree-item.vue';
import treeItemLite from './tree-item-lite.vue';
import search from '../components/search.vue';
import objectUtils from 'objectUtils';
import uuid from 'uuid';
@ -145,7 +175,8 @@ export default {
name: 'MctTree',
components: {
search,
treeItem
treeItem,
treeItemLite
},
props: {
syncTreeNavigation: {
@ -596,6 +627,21 @@ export default {
navigationPath
};
},
buildTreeItemLite(indexResult) {
let liteObject = {
identifier: objectUtils.parseKeyString(indexResult.id),
name: indexResult.name,
type: indexResult.type
};
let navigationPath = '';
return {
id: indexResult.id,
object: liteObject,
objectPath: [],
navigationPath
};
},
// domainObject: the item we're building the path for (will be used in url and links)
// objects: array of domainObjects representing path to domainobject passed in
buildNavigationPath(domainObject, objects) {
@ -693,30 +739,37 @@ export default {
}
},
async getSearchResults() {
let results = await this.searchService.query(this.searchValue);
let results = await this.searchService.queryLite(this.searchValue);
this.searchResultItems = [];
// build out tree-item-lite results
for (let i = 0; i < results.hits.length; i++) {
let result = results.hits[i];
let newStyleObject = objectUtils.toNewFormat(result.object.getModel(), result.object.getId());
let objectPath = await this.openmct.objects.getOriginalPath(newStyleObject.identifier);
// removing the item itself, as the path we pass to buildTreeItem is a parent path
objectPath.shift();
// if root, remove, we're not using in object path for tree
let lastObject = objectPath.length ? objectPath[objectPath.length - 1] : false;
if (lastObject && lastObject.type === 'root') {
objectPath.pop();
}
// we reverse the objectPath in the tree, so have to do it here first,
// since this one is already in the correct direction
let resultObject = this.buildTreeItem(newStyleObject, objectPath.reverse());
let resultObject = this.buildTreeItemLite(result);
this.searchResultItems.push(resultObject);
}
// for (let i = 0; i < results.hits.length; i++) {
// let result = results.hits[i];
// let newStyleObject = objectUtils.toNewFormat(result.object.getModel(), result.object.getId());
// let objectPath = await this.openmct.objects.getOriginalPath(newStyleObject.identifier);
// // removing the item itself, as the path we pass to buildTreeItem is a parent path
// objectPath.shift();
// // if root, remove, we're not using in object path for tree
// let lastObject = objectPath.length ? objectPath[objectPath.length - 1] : false;
// if (lastObject && lastObject.type === 'root') {
// objectPath.pop();
// }
// // we reverse the objectPath in the tree, so have to do it here first,
// // since this one is already in the correct direction
// let resultObject = this.buildTreeItem(newStyleObject, objectPath.reverse());
// this.searchResultItems.push(resultObject);
// }
this.searchLoading = false;
},
searchTree(value) {

View File

@ -0,0 +1,108 @@
<template>
<div
ref="me"
:style="{
'top': virtualScroll ? itemTop : 'auto',
'position': virtualScroll ? 'absolute' : 'relative'
}"
class="c-tree__item-h"
>
<div
class="c-tree__item"
>
<view-control
v-model="expanded"
class="c-tree__item__view-control"
:control-class="'c-nav__up'"
:enabled="showUp"
/>
<object-label
:domain-object="node.object"
:object-path="node.objectPath"
:navigate-to-path="''"
:style="{ paddingLeft: leftOffset }"
:lite-object="true"
:before-interaction="onInteraction"
/>
<view-control
v-model="expanded"
class="c-tree__item__view-control"
:control-class="'c-nav__down'"
:enabled="showDown"
/>
</div>
</div>
</template>
<script>
import viewControl from '../components/viewControl.vue';
import ObjectLabel from '../components/ObjectLabel.vue';
export default {
name: 'TreeItem',
inject: ['openmct'],
components: {
viewControl,
ObjectLabel
},
props: {
node: {
type: Object,
required: true
},
leftOffset: {
type: String,
default: '0px'
},
showUp: {
type: Boolean,
default: false
},
showDown: {
type: Boolean,
default: true
},
itemIndex: {
type: Number,
required: false,
default: undefined
},
itemOffset: {
type: Number,
required: false,
default: undefined
},
itemHeight: {
type: Number,
required: false,
default: 0
},
virtualScroll: {
type: Boolean,
default: false
}
},
data() {
return {
expanded: false
};
},
computed: {
itemTop() {
return (this.itemOffset + this.itemIndex) * this.itemHeight + 'px';
}
},
methods: {
async onInteraction() {
let domainObject = await this.openmct.objects.get(this.node.object.identifier);
let objectPath = await this.openmct.objects.getOriginalPath(this.node.object.identifier);
objectPath.pop();
return {
domainObject,
objectPath
};
}
}
};
</script>

View File

@ -13,25 +13,35 @@ export default {
this.$el.addEventListener('contextmenu', this.showContextMenu);
function updateObject(oldObject, newObject) {
if (oldObject.name == 'drag n drop sg') console.log({ oldObject, newObject});
Object.assign(oldObject, newObject);
}
this.objectPath.forEach(object => {
if (object) {
this.$once('hook:destroyed',
this.openmct.objects.observe(object, '*', updateObject.bind(this, object)));
}
});
if (!this.liteObject) {
this.objectPath.forEach(object => {
if (object) {
this.$once('hook:destroyed',
this.openmct.objects.observe(object, '*', updateObject.bind(this, object)));
}
});
}
},
destroyed() {
this.$el.removeEventListener('contextMenu', this.showContextMenu);
},
methods: {
showContextMenu(event) {
async showContextMenu(event) {
event.preventDefault();
event.stopPropagation();
let actionsCollection = this.openmct.actions.get(this.objectPath);
let objectPath = this.objectPath;
if (this.liteObject && this.beforeInteraction) {
let fullObjectInfo = await this.beforeInteraction();
objectPath = fullObjectInfo.objectPath;
}
let actionsCollection = this.openmct.actions.get(objectPath);
let actions = actionsCollection.getVisibleActions();
let sortedActions = this.openmct.actions._groupAndSortActions(actions);

View File

@ -0,0 +1,84 @@
import uuid from 'uuid';
const ENHANCED_DATA_TRANSFER_TYPE = "openmct/enhanced-data-transfer-id/";
const _enhancedEventData = {};
const _storedPromises = {};
const EnhancedDataTransfer = {
start: (event) => {
const eventId = uuid();
event.dataTransfer.setData(ENHANCED_DATA_TRANSFER_TYPE + eventId, eventId);
_enhancedEventData[eventId] = {};
let eventResolve;
let eventReject;
const eventPromise = new Promise((resolve, reject) => {
eventResolve = resolve;
eventReject = reject;
});
_storedPromises[eventId] = eventPromise;
return {
setData: EnhancedDataTransfer._setData(eventId),
finish: () => eventResolve(),
fail: () => eventReject(),
eventId
};
},
load: async (event) => {
const eventId = event.dataTransfer.types
.filter(type => type.startsWith(ENHANCED_DATA_TRANSFER_TYPE))
.map(type => type.substring(ENHANCED_DATA_TRANSFER_TYPE.length))[0];
if (!eventId) {
throw new Error('Event is not an enhanced data transfer event');
}
if (_storedPromises[eventId] !== 'complete') {
try {
await _storedPromises[eventId];
_storedPromises[eventId] = 'complete';
} catch (err) {
delete _storedPromises[eventId];
console.warn(err);
return err;
}
}
return {
getData: EnhancedDataTransfer._getData(eventId),
allData: EnhancedDataTransfer._allData(eventId),
types: () => Object.keys(_enhancedEventData[eventId]),
delete: EnhancedDataTransfer._delete(eventId)
};
},
_setData: (eventId) => {
return (key, value) => {
_enhancedEventData[eventId][key] = value;
};
},
_getData: (eventId) => {
return key => _enhancedEventData[eventId][key];
},
_allData: (eventId) => {
return () => _enhancedEventData[eventId];
},
_delete: (eventId) => {
return () => {
delete _enhancedEventData[eventId];
delete _storedPromises[eventId];
};
},
isEnhancedDataTransfer: (event) => {
return event.dataTransfer.types
.filter(type => type.startsWith(ENHANCED_DATA_TRANSFER_TYPE)).length > 0;
}
};
Object.freeze(EnhancedDataTransfer);
export default EnhancedDataTransfer;