mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
19 Commits
remove-ang
...
tree-searc
Author | SHA1 | Date | |
---|---|---|---|
6b719259e3 | |||
9fc31809f6 | |||
ccd4bbd279 | |||
72848849dd | |||
66130ba542 | |||
895bdc164f | |||
c9728144a5 | |||
76fec7f3bc | |||
1b4717065a | |||
e24542c1a6 | |||
b08f3106ed | |||
700bc7616d | |||
3436e976cf | |||
b8e232831e | |||
f6bc49fc82 | |||
7018c217c4 | |||
18c230c0f7 | |||
b8c2f3f49a | |||
e5e27ea498 |
@ -32,7 +32,8 @@
|
||||
function indexItem(id, model) {
|
||||
indexedItems.push({
|
||||
id: id,
|
||||
name: model.name.toLowerCase()
|
||||
name: model.name.toLowerCase(),
|
||||
type: model.type
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -41,7 +41,8 @@
|
||||
indexedItems.push({
|
||||
id: id,
|
||||
vector: vector,
|
||||
model: model
|
||||
model: model,
|
||||
type: model.type
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
|
315
src/api/telemetry/TelemetryCollection.js
Normal file
315
src/api/telemetry/TelemetryCollection.js
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
72
src/api/telemetry/TelemetrySubscriptionService.js
Normal file
72
src/api/telemetry/TelemetrySubscriptionService.js
Normal 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;
|
@ -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++;
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
108
src/ui/layout/tree-item-lite.vue
Normal file
108
src/ui/layout/tree-item-lite.vue
Normal 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>
|
@ -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);
|
||||
|
||||
|
84
src/utils/EnhancedDataTransfer.js
Normal file
84
src/utils/EnhancedDataTransfer.js
Normal 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;
|
Reference in New Issue
Block a user