Compare commits
	
		
			19 Commits
		
	
	
		
			telemetry-
			...
			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