Compare commits

..

18 Commits

Author SHA1 Message Date
Joshi
fd4dcc8513 Add check for destroy method on store.
Change failing test to use new data getter.
2021-08-10 09:55:22 -07:00
Joshi
9ebd18318b Remve console log 2021-08-10 09:55:22 -07:00
Joshi
4a89b81f4f Logging for beforeunload events 2021-08-10 09:55:22 -07:00
Joshi
98e1abd7b1 Don't draw points if the count is 0 2021-08-10 09:55:22 -07:00
Joshi
56c25762ac Fix resize handler for plots 2021-08-10 09:55:22 -07:00
Joshi
5c8e726b87 Fix data store id 2021-08-10 09:55:22 -07:00
Joshi
d80f4a1f7d Revert commented out code 2021-08-10 09:55:22 -07:00
Joshi
3fe4c7a954 Revert eslint changes 2021-08-10 09:55:22 -07:00
Joshi
676ef60128 Revert eslint changes 2021-08-10 09:55:22 -07:00
Joshi
5a90d28450 Separate plot series data from the configuration (like it should be!) 2021-08-10 09:55:22 -07:00
Joshi
2bb6822e6b Draft 2021-08-10 09:55:22 -07:00
Joshi
383b4c0d8d Fix no mutating props violation for Browsebar and StyleEditor 2021-08-10 09:55:22 -07:00
Joshi
404ab720ad Enable no mutating props vue lint configuration. Fix error for plots 2021-08-10 09:55:22 -07:00
Khalid Adil
259ab53060 Refactor clock object and clock indicator to remove AngularJS dependency (#4094)
* To enable clock indicator, pass in the following configuration { enableClockIndicator: true }.
2021-08-09 14:29:45 -07:00
Nikhil
1db7ac55b4 [Imagery] Click on image to get a large view #3582 (#4085)
fixed issue where large imagery view opens only once.
2021-08-04 15:44:50 -07:00
Shefali Joshi
82b3383834 Set the yKey value on the series when it's changed (#4083) 2021-08-04 13:56:37 -07:00
Shefali Joshi
ac240d524c Add check for stop observing before calling it (#4080) 2021-08-04 10:40:16 -07:00
Henry Hsu
1b034f6125 remove can edit from hyperlink (#4076) 2021-08-03 16:01:29 -07:00
58 changed files with 1874 additions and 2547 deletions

View File

@@ -63,7 +63,7 @@ define([
StateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = Math.min(Date.now(), options.end); // no future values
var end = options.end;
var duration = domainObject.telemetry.duration * 1000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;

View File

@@ -195,6 +195,7 @@
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
{indicator: true}
));
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
openmct.start();
</script>
</html>

View File

@@ -21,32 +21,24 @@
*****************************************************************************/
define([
"moment-timezone",
"./src/indicators/ClockIndicator",
"./src/services/TickerService",
"./src/services/TimerService",
"./src/controllers/ClockController",
"./src/controllers/TimerController",
"./src/controllers/RefreshingController",
"./src/actions/StartTimerAction",
"./src/actions/RestartTimerAction",
"./src/actions/StopTimerAction",
"./src/actions/PauseTimerAction",
"./res/templates/clock.html",
"./res/templates/timer.html"
], function (
MomentTimezone,
ClockIndicator,
TickerService,
TimerService,
ClockController,
TimerController,
RefreshingController,
StartTimerAction,
RestartTimerAction,
StopTimerAction,
PauseTimerAction,
clockTemplate,
timerTemplate
) {
return {
@@ -73,16 +65,6 @@ define([
"value": "YYYY/MM/DD HH:mm:ss"
}
],
"indicators": [
{
"implementation": ClockIndicator,
"depends": [
"tickerService",
"CLOCK_INDICATOR_FORMAT"
],
"priority": "preferred"
}
],
"services": [
{
"key": "tickerService",
@@ -99,14 +81,6 @@ define([
}
],
"controllers": [
{
"key": "ClockController",
"implementation": ClockController,
"depends": [
"$scope",
"tickerService"
]
},
{
"key": "TimerController",
"implementation": TimerController,
@@ -126,12 +100,6 @@ define([
}
],
"views": [
{
"key": "clock",
"type": "clock",
"editable": false,
"template": clockTemplate
},
{
"key": "timer",
"type": "timer",
@@ -186,70 +154,6 @@ define([
}
],
"types": [
{
"key": "clock",
"name": "Clock",
"cssClass": "icon-clock",
"description": "A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.",
"priority": 101,
"features": [
"creation"
],
"properties": [
{
"key": "clockFormat",
"name": "Display Format",
"control": "composite",
"items": [
{
"control": "select",
"options": [
{
"value": "YYYY/MM/DD hh:mm:ss",
"name": "YYYY/MM/DD hh:mm:ss"
},
{
"value": "YYYY/DDD hh:mm:ss",
"name": "YYYY/DDD hh:mm:ss"
},
{
"value": "hh:mm:ss",
"name": "hh:mm:ss"
}
],
"cssClass": "l-inline"
},
{
"control": "select",
"options": [
{
"value": "clock12",
"name": "12hr"
},
{
"value": "clock24",
"name": "24hr"
}
],
"cssClass": "l-inline"
}
]
},
{
"key": "timezone",
"name": "Timezone",
"control": "autocomplete",
"options": MomentTimezone.tz.names()
}
],
"model": {
"clockFormat": [
"YYYY/MM/DD hh:mm:ss",
"clock12"
],
"timezone": "UTC"
}
},
{
"key": "timer",
"name": "Timer",

View File

@@ -1,32 +0,0 @@
<!--
Open MCT, Copyright (c) 2009-2016, 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.
-->
<div class="c-clock l-time-display u-style-receiver js-style-receiver" ng-controller="ClockController as clock">
<div class="c-clock__timezone">
{{clock.zone()}}
</div>
<div class="c-clock__value">
{{clock.text()}}
</div>
<div class="c-clock__ampm">
{{clock.ampm()}}
</div>
</div>

View File

@@ -1,110 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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.
*****************************************************************************/
define([
'moment',
'moment-timezone'
],
function (
moment,
momentTimezone
) {
/**
* Controller for views of a Clock domain object.
*
* @constructor
* @memberof platform/features/clock
* @param {angular.Scope} $scope the Angular scope
* @param {platform/features/clock.TickerService} tickerService
* a service used to align behavior with clock ticks
*/
function ClockController($scope, tickerService) {
var lastTimestamp,
unlisten,
timeFormat,
zoneName,
self = this;
function update() {
var m = zoneName
? moment.utc(lastTimestamp).tz(zoneName) : moment.utc(lastTimestamp);
self.zoneAbbr = m.zoneAbbr();
self.textValue = timeFormat && m.format(timeFormat);
self.ampmValue = m.format("A"); // Just the AM or PM part
}
function tick(timestamp) {
lastTimestamp = timestamp;
update();
}
function updateModel(model) {
var baseFormat;
if (model !== undefined) {
baseFormat = model.clockFormat[0];
self.use24 = model.clockFormat[1] === 'clock24';
timeFormat = self.use24
? baseFormat.replace('hh', "HH") : baseFormat;
// If wrong timezone is provided, the UTC will be used
zoneName = momentTimezone.tz.names().includes(model.timezone)
? model.timezone : "UTC";
update();
}
}
// Pull in the model (clockFormat and timezone) from the domain object model
$scope.$watch('model', updateModel);
// Listen for clock ticks ... and stop listening on destroy
unlisten = tickerService.listen(tick);
$scope.$on('$destroy', unlisten);
}
/**
* Get the clock's time zone, as displayable text.
* @returns {string}
*/
ClockController.prototype.zone = function () {
return this.zoneAbbr;
};
/**
* Get the current time, as displayable text.
* @returns {string}
*/
ClockController.prototype.text = function () {
return this.textValue;
};
/**
* Get the text to display to qualify a time as AM or PM.
* @returns {string}
*/
ClockController.prototype.ampm = function () {
return this.use24 ? '' : this.ampmValue;
};
return ClockController;
}
);

View File

@@ -1,65 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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.
*****************************************************************************/
define(
['moment'],
function (moment) {
/**
* Indicator that displays the current UTC time in the status area.
* @implements {Indicator}
* @memberof platform/features/clock
* @param {platform/features/clock.TickerService} tickerService
* a service used to align behavior with clock ticks
* @param {string} indicatorFormat format string for timestamps
* shown in this indicator
*/
function ClockIndicator(tickerService, indicatorFormat) {
var self = this;
this.text = "";
tickerService.listen(function (timestamp) {
self.text = moment.utc(timestamp)
.format(indicatorFormat) + " UTC";
});
}
ClockIndicator.prototype.getGlyphClass = function () {
return "";
};
ClockIndicator.prototype.getCssClass = function () {
return "t-indicator-clock icon-clock no-minify c-indicator--not-clickable";
};
ClockIndicator.prototype.getText = function () {
return this.text;
};
ClockIndicator.prototype.getDescription = function () {
return "";
};
return ClockIndicator;
}
);

View File

@@ -1,107 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2017, 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.
*****************************************************************************/
define(
["../../src/controllers/ClockController"],
function (ClockController) {
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000;
describe("A clock view's controller", function () {
var mockScope,
mockTicker,
mockUnticker,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.and.returnValue(mockUnticker);
controller = new ClockController(mockScope, mockTicker);
});
it("watches for model (clockFormat and timezone) from the domain object model", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"model",
jasmine.any(Function)
);
});
it("subscribes to clock ticks", function () {
expect(mockTicker.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("unsubscribes to ticks when destroyed", function () {
// Make sure $destroy is being listened for...
expect(mockScope.$on.calls.mostRecent().args[0]).toEqual('$destroy');
expect(mockUnticker).not.toHaveBeenCalled();
// ...and makes sure that its listener unsubscribes from ticker
mockScope.$on.calls.mostRecent().args[1]();
expect(mockUnticker).toHaveBeenCalled();
});
it("formats using the format string from the model", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
mockScope.$watch.calls.mostRecent().args[1]({
"clockFormat": [
"YYYY-DDD hh:mm:ss",
"clock24"
],
"timezone": "Canada/Eastern"
});
expect(controller.zone()).toEqual("EDT");
expect(controller.text()).toEqual("2015-154 13:56:14");
expect(controller.ampm()).toEqual("");
});
it("formats 12-hour time", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
mockScope.$watch.calls.mostRecent().args[1]({
"clockFormat": [
"YYYY-DDD hh:mm:ss",
"clock12"
],
"timezone": ""
});
expect(controller.zone()).toEqual("UTC");
expect(controller.text()).toEqual("2015-154 05:56:14");
expect(controller.ampm()).toEqual("PM");
});
it("does not throw exceptions when model is undefined", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
expect(function () {
mockScope.$watch.calls.mostRecent().args[1](undefined);
}).not.toThrow();
});
});
}
);

View File

@@ -1,58 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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.
*****************************************************************************/
define(
["../../src/indicators/ClockIndicator"],
function (ClockIndicator) {
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000,
TEST_FORMAT = "YYYY-DDD HH:mm:ss";
describe("The clock indicator", function () {
var mockTicker,
mockUnticker,
indicator;
beforeEach(function () {
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.and.returnValue(mockUnticker);
indicator = new ClockIndicator(mockTicker, TEST_FORMAT);
});
it("displays the current time", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
expect(indicator.getText()).toEqual("2015-154 17:56:14 UTC");
});
it("implements the Indicator interface", function () {
expect(indicator.getCssClass()).toEqual(jasmine.any(String));
expect(indicator.getText()).toEqual(jasmine.any(String));
expect(indicator.getDescription()).toEqual(jasmine.any(String));
});
});
}
);

View File

@@ -20,8 +20,6 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { TelemetryCollection } = require("./TelemetryCollection");
define([
'../../plugins/displayLayout/CustomStringFormatter',
'./TelemetryMetadataManager',
@@ -275,28 +273,6 @@ 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 requestTelemetryCollection
* @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 telemetry collection request
* @returns {TelemetryCollection} a TelemetryCollection instance
*/
TelemetryAPI.prototype.requestTelemetryCollection = function (domainObject, options = {}) {
return new TelemetryCollection(
this.openmct,
domainObject,
options
);
};
/**
* Request historical telemetry for a domain object.
* The `options` argument allows you to specify filters

File diff suppressed because it is too large Load Diff

View File

@@ -1,366 +0,0 @@
/*****************************************************************************
* 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 EventEmitter from 'EventEmitter';
/** Class representing a Telemetry Collection. */
export class TelemetryCollection extends EventEmitter {
/**
* Creates a Telemetry Collection
*
* @param {object} openmct - Openm MCT
* @param {object} domainObject - Domain Object to user for telemetry collection
* @param {object} options - Any options passed in for request/subscribe
*/
constructor(openmct, domainObject, options) {
super();
this.loaded = false;
this.openmct = openmct;
this.domainObject = domainObject;
this.boundedTelemetry = [];
this.futureBuffer = [];
this.parseTime = undefined;
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
this.unsubscribe = undefined;
this.historicalProvider = undefined;
this.options = options;
this.pageState = undefined;
this.lastBounds = undefined;
this.requestAbort = undefined;
}
/**
* This will start the requests for historical and realtime data,
* as well as setting up initial values and watchers
*/
load() {
if (this.loaded) {
throw new Error('Telemetry Collection has already been loaded.');
}
this._timeSystem(this.openmct.time.timeSystem());
this.lastBounds = this.openmct.time.bounds();
this._watchBounds();
this._watchTimeSystem();
this._initiateHistoricalRequests();
this._initiateSubscriptionTelemetry();
this.loaded = true;
}
/**
* can/should be called by the requester of the telemetry collection
* to remove any listeners
*/
destroy() {
if (this.requestAbort) {
this.requestAbort.abort();
}
this._unwatchBounds();
this._unwatchTimeSystem();
if (this.unsubscribe) {
this.unsubscribe();
}
this.removeAllListeners();
}
/**
* This will start the requests for historical and realtime data,
* as well as setting up initial values and watchers
*/
getAll() {
return this.boundedTelemetry;
}
/**
* Sets up the telemetry collection for historical requests,
* this uses the "standardizeRequestOptions" from Telemetry API
* @private
*/
_initiateHistoricalRequests() {
this.openmct.telemetry.standardizeRequestOptions(this.options);
this.historicalProvider = this.openmct.telemetry.
findRequestProvider(this.domainObject, this.options);
this._requestHistoricalTelemetry();
}
/**
* If a historical provider exists, then historical requests will be made
* @private
*/
async _requestHistoricalTelemetry() {
if (!this.historicalProvider) {
return;
}
let historicalData;
try {
this.requestAbort = new AbortController();
this.options.abortSignal = this.requestAbort.signal;
historicalData = await this.historicalProvider.request(this.domainObject, this.options);
this.requestAbort = undefined;
} catch (error) {
console.error('Error requesting telemetry data...');
this.requestAbort = undefined;
throw new Error(error);
}
this._processNewTelemetry(historicalData);
}
/**
* This uses the built in subscription function from Telemetry API
* @private
*/
_initiateSubscriptionTelemetry() {
if (this.unsubscribe) {
this.unsubscribe();
}
this.unsubscribe = this.openmct.telemetry
.subscribe(
this.domainObject,
datum => this._processNewTelemetry(datum),
this.options
);
}
/**
* Filter any new telemetry (add/page, historical, subscription) based on
* time bounds and dupes
*
* @param {(Object|Object[])} telemetryData - telemetry data object or
* array of telemetry data objects
* @private
*/
_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) {
let isDuplicate = false;
let startIndex = this._sortedIndex(datum);
let endIndex = undefined;
// dupe check
if (startIndex !== this.boundedTelemetry.length) {
endIndex = _.sortedLastIndexBy(
this.boundedTelemetry,
datum,
boundedDatum => this.parseTime(boundedDatum)
);
if (endIndex > startIndex) {
let potentialDupes = this.boundedTelemetry.slice(startIndex, endIndex);
isDuplicate = potentialDupes.some(_.isEqual(undefined, datum));
}
}
if (!isDuplicate) {
let index = endIndex || startIndex;
this.boundedTelemetry.splice(index, 0, datum);
added.push(datum);
}
} else if (afterEndOfBounds) {
this.futureBuffer.push(datum);
}
}
if (added.length) {
this.emit('add', added);
}
}
/**
* Finds the correct insertion point for the given telemetry datum.
* Leverages lodash's `sortedIndexBy` function which implements a binary search.
* @private
*/
_sortedIndex(datum) {
if (this.boundedTelemetry.length === 0) {
return 0;
}
let parsedValue = this.parseTime(datum);
let lastValue = this.parseTime(this.boundedTelemetry[this.boundedTelemetry.length - 1]);
if (parsedValue > lastValue || parsedValue === lastValue) {
return this.boundedTelemetry.length;
} else {
return _.sortedIndexBy(
this.boundedTelemetry,
datum,
boundedDatum => this.parseTime(boundedDatum)
);
}
}
/**
* when the start time, end time, or both have been updated.
* data could be added OR removed here we update the current
* bounded telemetry
*
* @param {TimeConductorBounds} bounds The newly updated bounds
* @param {boolean} [tick] `true` if the bounds update was due to
* a "tick" event (ie. was an automatic update), false otherwise.
* @private
*/
_bounds(bounds, isTick) {
let startChanged = this.lastBounds.start !== bounds.start;
let endChanged = this.lastBounds.end !== bounds.end;
this.lastBounds = bounds;
if (isTick) {
// need to check futureBuffer and need to check
// if anything has fallen out of bounds
let startIndex = 0;
let endIndex = 0;
let discarded = [];
let added = [];
let testDatum = {};
if (startChanged) {
testDatum[this.timeKey] = bounds.start;
// Calculate the new index of the first item within the bounds
startIndex = _.sortedIndexBy(
this.boundedTelemetry,
testDatum,
datum => this.parseTime(datum)
);
discarded = this.boundedTelemetry.splice(0, startIndex);
}
if (endChanged) {
testDatum[this.timeKey] = bounds.end;
// Calculate the new index of the last item in bounds
endIndex = _.sortedLastIndexBy(
this.futureBuffer,
testDatum,
datum => this.parseTime(datum)
);
added = this.futureBuffer.splice(0, endIndex);
this.boundedTelemetry = [...this.boundedTelemetry, ...added];
}
if (discarded.length > 0) {
this.emit('remove', discarded);
}
if (added.length > 0) {
this.emit('add', added);
}
} else {
// user bounds change, reset
this._reset();
}
}
/**
* whenever the time system is updated need to update related values in
* the Telemetry Collection and reset the telemetry collection
*
* @param {TimeSystem} timeSystem - the value of the currently applied
* Time System
* @private
*/
_timeSystem(timeSystem) {
this.timeKey = timeSystem.key;
let metadataValue = this.metadata.value(this.timeKey) || { format: this.timeKey };
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
this.parseTime = (datum) => {
return valueFormatter.parse(datum);
};
this._reset();
}
/**
* Reset the telemetry data of the collection, and re-request
* historical telemetry
* @private
*
* @todo handle subscriptions more granually
*/
_reset() {
this.boundedTelemetry = [];
this.futureBuffer = [];
this._requestHistoricalTelemetry();
}
/**
* adds the _bounds callback to the 'bounds' timeAPI listener
* @private
*/
_watchBounds() {
this.openmct.time.on('bounds', this._bounds, this);
}
/**
* removes the _bounds callback from the 'bounds' timeAPI listener
* @private
*/
_unwatchBounds() {
this.openmct.time.off('bounds', this._bounds, this);
}
/**
* adds the _timeSystem callback to the 'timeSystem' timeAPI listener
* @private
*/
_watchTimeSystem() {
this.openmct.time.on('timeSystem', this._timeSystem, this);
}
/**
* removes the _timeSystem callback from the 'timeSystem' timeAPI listener
* @private
*/
_unwatchTimeSystem() {
this.openmct.time.off('timeSystem', this._timeSystem, this);
}
}

View File

@@ -78,9 +78,6 @@ class ImageExporter {
}
return html2canvas(element, {
useCORS: true,
allowTaint: true,
logging: false,
onclone: function (document) {
if (className) {
const clonedElement = document.getElementById(exportId);

View File

@@ -20,31 +20,40 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<li class="c-inspect-properties__row">
<div class="c-inspect-properties__label">
{{ detail.name }}
</div>
<div class="c-inspect-properties__value">
{{ formattedTime }}
</div>
</li>
</template>
import Clock from './components/Clock.vue';
import Vue from 'vue';
<script>
import Moment from 'moment';
export default function ClockViewProvider(openmct) {
return {
key: 'clock.view',
name: 'Clock',
cssClass: 'icon-clock',
canView(domainObject) {
return domainObject.type === 'clock';
},
export default {
props: {
detail: {
type: Object,
required: true
view: function (domainObject) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
Clock
},
provide: {
openmct,
domainObject
},
template: '<clock></clock>'
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
};
}
},
computed: {
formattedTime() {
return Moment.utc(this.detail.value).format('YYYY-MM-DD[\n]HH:mm:ss') + ' UTC';
}
}
};
</script>
};
}

View File

@@ -0,0 +1,99 @@
<!--
Open MCT, Copyright (c) 2014-2021, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div class="l-angular-ov-wrapper">
<div class="u-contents">
<div class="c-clock l-time-display u-style-receiver js-style-receiver">
<div class="c-clock__timezone">
{{ timeZoneAbbr }}
</div>
<div class="c-clock__value">
{{ timeTextValue }}
</div>
<div class="c-clock__ampm">
{{ timeAmPm }}
</div>
</div>
</div>
</div>
</template>
<script>
import moment from 'moment';
import momentTimezone from 'moment-timezone';
export default {
inject: ['openmct', 'domainObject'],
data() {
return {
lastTimestamp: null
};
},
computed: {
configuration() {
return this.domainObject.configuration;
},
baseFormat() {
return this.configuration.baseFormat;
},
use24() {
return this.configuration.use24 === 'clock24';
},
timezone() {
return this.configuration.timezone;
},
timeFormat() {
return this.use24 ? this.baseFormat.replace('hh', "HH") : this.baseFormat;
},
zoneName() {
return momentTimezone.tz.names().includes(this.timezone) ? this.timezone : "UTC";
},
momentTime() {
return this.zoneName ? moment.utc(this.lastTimestamp).tz(this.zoneName) : moment.utc(this.lastTimestamp);
},
timeZoneAbbr() {
return this.momentTime.zoneAbbr();
},
timeTextValue() {
return this.timeFormat && this.momentTime.format(this.timeFormat);
},
timeAmPm() {
return this.use24 ? '' : this.momentTime.format("A");
}
},
mounted() {
const TickerService = this.openmct.$injector.get('tickerService');
this.unlisten = TickerService.listen(this.tick);
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
},
methods: {
tick(timestamp) {
this.lastTimestamp = timestamp;
}
}
};
</script>

View File

@@ -0,0 +1,64 @@
<!--
Open MCT, Copyright (c) 2014-2021, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div class="c-indicator t-indicator-clock icon-clock no-minify c-indicator--not-clickable">
<span class="label c-indicator__label">
{{ timeTextValue }}
</span>
</div>
</template>
<script>
import moment from 'moment';
export default {
inject: ['openmct'],
props: {
indicatorFormat: {
type: String,
required: true
}
},
data() {
return {
timeTextValue: null
};
},
mounted() {
this.openmct.on('start', () => {
const TickerService = this.openmct.$injector.get('tickerService');
this.unlisten = TickerService.listen(this.tick);
});
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
},
methods: {
tick(timestamp) {
this.timeTextValue = `${moment.utc(timestamp).format(this.indicatorFormat)} UTC`;
}
}
};
</script>

154
src/plugins/clock/plugin.js Normal file
View File

@@ -0,0 +1,154 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 ClockViewProvider from './ClockViewProvider';
import ClockIndicator from './components/ClockIndicator.vue';
import momentTimezone from 'moment-timezone';
import Vue from 'vue';
export default function ClockPlugin(options) {
return function install(openmct) {
const CLOCK_INDICATOR_FORMAT = 'YYYY/MM/DD HH:mm:ss';
openmct.types.addType('clock', {
name: 'Clock',
description: 'A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.',
creatable: true,
cssClass: 'icon-clock',
initialize: function (domainObject) {
domainObject.configuration = {
baseFormat: 'YYYY/MM/DD hh:mm:ss',
use24: 'clock12',
timezone: 'UTC'
};
},
"form": [
{
"key": "displayFormat",
"name": "Display Format",
control: 'select',
options: [
{
value: 'YYYY/MM/DD hh:mm:ss',
name: 'YYYY/MM/DD hh:mm:ss'
},
{
value: 'YYYY/DDD hh:mm:ss',
name: 'YYYY/DDD hh:mm:ss'
},
{
value: 'hh:mm:ss',
name: 'hh:mm:ss'
}
],
cssClass: 'l-inline',
property: [
'configuration',
'baseFormat'
]
},
{
control: 'select',
options: [
{
value: 'clock12',
name: '12hr'
},
{
value: 'clock24',
name: '24hr'
}
],
cssClass: 'l-inline',
property: [
'configuration',
'use24'
]
},
{
"key": "timezone",
"name": "Timezone",
"control": "autocomplete",
"options": momentTimezone.tz.names(),
property: [
'configuration',
'timezone'
]
}
]
});
openmct.objectViews.addProvider(new ClockViewProvider(openmct));
if (options && options.enableClockIndicator) {
const clockIndicator = new Vue ({
components: {
ClockIndicator
},
provide: {
openmct
},
data() {
return {
indicatorFormat: CLOCK_INDICATOR_FORMAT
};
},
template: '<ClockIndicator :indicator-format="indicatorFormat"></ClockIndicator>'
});
const indicator = {
element: clockIndicator.$mount().$el,
key: 'clock-indicator'
};
openmct.indicators.add(indicator);
}
openmct.objects.addGetInterceptor({
appliesTo: (identifier, domainObject) => {
return domainObject && domainObject.type === 'clock';
},
invoke: (identifier, domainObject) => {
if (domainObject.configuration) {
return domainObject;
}
if (domainObject.clockFormat
&& domainObject.timezone) {
const baseFormat = domainObject.clockFormat[0];
const use24 = domainObject.clockFormat[1];
const timezone = domainObject.timezone;
domainObject.configuration = {
baseFormat,
use24,
timezone
};
openmct.objects.mutate(domainObject, 'configuration', domainObject.configuration);
}
return domainObject;
}
});
};
}

View File

@@ -72,7 +72,7 @@
<script>
import LayoutFrame from './LayoutFrame.vue';
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
import { getDefaultNotebook, getNotebookSectionAndPage } from '@/plugins/notebook/utils/notebook-storage.js';
import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js';
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
const DEFAULT_POSITION = [1, 1];
@@ -336,15 +336,12 @@ export default {
},
async getContextMenuActions() {
const defaultNotebook = getDefaultNotebook();
const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
let defaultNotebookName;
if (defaultNotebook) {
const domainObject = await this.openmct.objects.get(defaultNotebook.identifier);
const { section, page } = getNotebookSectionAndPage(domainObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
if (section && page) {
const defaultPath = domainObject && `${domainObject.name} - ${section.name} - ${page.name}`;
defaultNotebookName = `Copy to Notebook ${defaultPath}`;
}
const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
defaultNotebookName = `Copy to Notebook ${defaultPath}`;
}
return CONTEXT_MENU_ACTIONS

View File

@@ -1,4 +1,4 @@
import { getDefaultNotebook, getNotebookSectionAndPage } from '../utils/notebook-storage';
import { getDefaultNotebook } from '../utils/notebook-storage';
import { addNotebookEntry } from '../utils/notebook-entries';
export default class CopyToNotebookAction {
@@ -15,16 +15,11 @@ export default class CopyToNotebookAction {
copyToNotebook(entryText) {
const notebookStorage = getDefaultNotebook();
this.openmct.objects.get(notebookStorage.identifier)
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
.then(domainObject => {
addNotebookEntry(this.openmct, domainObject, notebookStorage, null, entryText);
const { section, page } = getNotebookSectionAndPage(domainObject, notebookStorage.defaultSectionId, notebookStorage.defaultPageId);
if (!section || !page) {
return;
}
const defaultPath = `${domainObject.name} - ${section.name} - ${page.name}`;
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
const msg = `Saved to Notebook ${defaultPath}`;
this.openmct.notifications.info(msg);
});

View File

@@ -43,16 +43,14 @@
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
:default-page-id="defaultPageId"
:selected-page-id="getSelectedPageId()"
:selected-page-id="selectedPageId"
:default-section-id="defaultSectionId"
:selected-section-id="getSelectedSectionId()"
:selected-section-id="selectedSectionId"
:domain-object="domainObject"
:page-title="domainObject.configuration.pageTitle"
:section-title="domainObject.configuration.sectionTitle"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
@defaultPageDeleted="cleanupDefaultNotebook"
@defaultSectionDeleted="cleanupDefaultNotebook"
@pagesChanged="pagesChanged"
@selectPage="selectPage"
@sectionsChanged="sectionsChanged"
@@ -138,7 +136,7 @@ import NotebookEntry from './NotebookEntry.vue';
import Search from '@/ui/components/search.vue';
import SearchResults from './SearchResults.vue';
import Sidebar from './Sidebar.vue';
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSectionId, setDefaultNotebookPageId } from '../utils/notebook-storage';
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
import objectUtils from 'objectUtils';
@@ -166,10 +164,8 @@ export default {
},
data() {
return {
defaultPageId: this.getDefaultPageId(),
defaultSectionId: this.getDefaultSectionId(),
selectedSectionId: this.getSelectedSectionId(),
selectedPageId: this.getSelectedPageId(),
selectedSectionId: this.getDefaultSectionId(),
selectedPageId: this.getDefaultPageId(),
defaultSort: this.domainObject.configuration.defaultSort,
focusEntryId: null,
search: '',
@@ -180,6 +176,12 @@ export default {
};
},
computed: {
defaultPageId() {
return this.getDefaultPageId();
},
defaultSectionId() {
return this.getDefaultSectionId();
},
filteredAndSortedEntries() {
const filterTime = Date.now();
const pageEntries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage) || [];
@@ -201,38 +203,24 @@ export default {
},
selectedPage() {
const pages = this.getPages();
if (!pages.length) {
return undefined;
}
const selectedPage = pages.find(page => page.id === this.selectedPageId);
if (selectedPage) {
return selectedPage;
}
const defaultPage = pages.find(page => page.id === this.defaultPageId);
if (defaultPage) {
return defaultPage;
}
return this.pages[0];
},
selectedSection() {
if (!this.sections.length) {
if (!selectedPage && !pages.length) {
return undefined;
}
const selectedSection = this.sections.find(section => section.id === this.selectedSectionId);
if (selectedSection) {
return selectedSection;
return pages[0];
},
selectedSection() {
if (!this.sections.length) {
return null;
}
const defaultSection = this.sections.find(section => section.id === this.defaultSectionId);
if (defaultSection) {
return defaultSection;
}
return this.sections[0];
return this.sections.find(section => section.id === this.selectedSectionId);
}
},
watch: {
@@ -313,29 +301,26 @@ export default {
this.sectionsChanged({ sections });
this.resetSearch();
},
cleanupDefaultNotebook() {
this.defaultPageId = undefined;
this.defaultSectionId = undefined;
this.removeDefaultClass(this.domainObject);
clearDefaultNotebook();
},
setSectionAndPageFromUrl() {
let sectionId = this.getSectionIdFromUrl() || this.getDefaultSectionId() || this.getSelectedSectionId();
let pageId = this.getPageIdFromUrl() || this.getDefaultPageId() || this.getSelectedPageId();
let sectionId = this.getSectionIdFromUrl() || this.selectedSectionId;
let pageId = this.getPageIdFromUrl() || this.selectedPageId;
this.selectSection(sectionId);
this.selectPage(pageId);
},
createNotebookStorageObject() {
const notebookMeta = {
name: this.domainObject.name,
identifier: this.domainObject.identifier,
link: this.getLinktoNotebook()
};
const page = this.selectedPage;
const section = this.selectedSection;
return {
name: this.domainObject.name,
identifier: this.domainObject.identifier,
link: this.getLinktoNotebook(),
defaultSectionId: section.id,
defaultPageId: page.id
notebookMeta,
page,
section
};
},
deleteEntry(entryId) {
@@ -434,21 +419,35 @@ export default {
this.sidebarCoversEntries = sidebarCoversEntries;
},
getDefaultPageId() {
return this.isDefaultNotebook()
? getDefaultNotebook().defaultPageId
: undefined;
let defaultPageId;
if (this.isDefaultNotebook()) {
defaultPageId = getDefaultNotebook().page.id;
} else {
const firstSection = this.getSections()[0];
defaultPageId = firstSection && firstSection.pages[0].id;
}
return defaultPageId;
},
isDefaultNotebook() {
const defaultNotebook = getDefaultNotebook();
const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.identifier;
const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.notebookMeta.identifier;
return defaultNotebookIdentifier !== null
&& this.openmct.objects.areIdsEqual(defaultNotebookIdentifier, this.domainObject.identifier);
},
getDefaultSectionId() {
return this.isDefaultNotebook()
? getDefaultNotebook().defaultSectionId
: undefined;
let defaultSectionId;
if (this.isDefaultNotebook()) {
defaultSectionId = getDefaultNotebook().section.id;
} else {
const firstSection = this.getSections()[0];
defaultSectionId = firstSection && firstSection.id;
}
return defaultSectionId;
},
getDefaultNotebookObject() {
const oldNotebookStorage = getDefaultNotebook();
@@ -456,7 +455,7 @@ export default {
return null;
}
return this.openmct.objects.get(oldNotebookStorage.identifier);
return this.openmct.objects.get(oldNotebookStorage.notebookMeta.identifier);
},
getLinktoNotebook() {
const objectPath = this.openmct.router.path;
@@ -574,22 +573,6 @@ export default {
return selectedSection.pages;
},
getSelectedPageId() {
const page = this.selectedPage;
if (!page) {
return undefined;
}
return page.id;
},
getSelectedSectionId() {
const section = this.selectedSection;
if (!section) {
return undefined;
}
return section.id;
},
newEntry(embed = null) {
this.resetSearch();
const notebookStorage = this.createNotebookStorageObject();
@@ -633,26 +616,51 @@ export default {
},
async updateDefaultNotebook(notebookStorage) {
const defaultNotebookObject = await this.getDefaultNotebookObject();
const isSameNotebook = defaultNotebookObject
&& objectUtils.makeKeyString(defaultNotebookObject.identifier) === objectUtils.makeKeyString(notebookStorage.identifier);
if (!isSameNotebook) {
if (!defaultNotebookObject) {
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
} else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
this.removeDefaultClass(defaultNotebookObject);
}
if (!defaultNotebookObject || !isSameNotebook) {
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
}
if (this.defaultSectionId !== notebookStorage.defaultSectionId) {
setDefaultNotebookSectionId(notebookStorage.defaultSectionId);
this.defaultSectionId = notebookStorage.defaultSectionId;
if (this.defaultSectionId && this.defaultSectionId.length === 0 || this.defaultSectionId !== notebookStorage.section.id) {
this.defaultSectionId = notebookStorage.section.id;
setDefaultNotebookSection(notebookStorage.section);
}
if (this.defaultPageId !== notebookStorage.defaultPageId) {
setDefaultNotebookPageId(notebookStorage.defaultPageId);
this.defaultPageId = notebookStorage.defaultPageId;
if (this.defaultPageId && this.defaultPageId.length === 0 || this.defaultPageId !== notebookStorage.page.id) {
this.defaultPageId = notebookStorage.page.id;
setDefaultNotebookPage(notebookStorage.page);
}
},
updateDefaultNotebookPage(pages, id) {
if (!id) {
return;
}
const notebookStorage = getDefaultNotebook();
if (!notebookStorage
|| notebookStorage.notebookMeta.identifier.key !== this.domainObject.identifier.key) {
return;
}
const defaultNotebookPage = notebookStorage.page;
const page = pages.find(p => p.id === id);
if (!page && defaultNotebookPage.id === id) {
this.defaultSectionId = null;
this.defaultPageId = null;
this.removeDefaultClass(this.domainObject);
clearDefaultNotebook();
return;
}
if (id !== defaultNotebookPage.id) {
return;
}
setDefaultNotebookPage(page);
},
updateDefaultNotebookSection(sections, id) {
if (!id) {
return;
@@ -660,26 +668,26 @@ export default {
const notebookStorage = getDefaultNotebook();
if (!notebookStorage
|| notebookStorage.identifier.key !== this.domainObject.identifier.key) {
|| notebookStorage.notebookMeta.identifier.key !== this.domainObject.identifier.key) {
return;
}
const defaultNotebookSectionId = notebookStorage.defaultSectionId;
if (defaultNotebookSectionId === id) {
const section = sections.find(s => s.id === id);
if (!section) {
this.removeDefaultClass(this.domainObject);
clearDefaultNotebook();
const defaultNotebookSection = notebookStorage.section;
const section = sections.find(s => s.id === id);
if (!section && defaultNotebookSection.id === id) {
this.defaultSectionId = null;
this.defaultPageId = null;
this.removeDefaultClass(this.domainObject);
clearDefaultNotebook();
return;
}
}
if (id !== defaultNotebookSectionId) {
return;
}
setDefaultNotebookSectionId(defaultNotebookSectionId);
if (id !== defaultNotebookSection.id) {
return;
}
setDefaultNotebookSection(section);
},
updateEntry(entry) {
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
@@ -707,27 +715,19 @@ export default {
sectionId: this.selectedSectionId
});
},
sectionsChanged({ sections, id = undefined }) {
sectionsChanged({ sections, id = null }) {
mutateObject(this.openmct, this.domainObject, 'configuration.sections', sections);
this.updateDefaultNotebookSection(sections, id);
},
selectPage(pageId) {
if (!pageId) {
return;
}
this.selectedPageId = pageId;
this.syncUrlWithPageAndSection();
},
selectSection(sectionId) {
if (!sectionId) {
return;
}
this.selectedSectionId = sectionId;
const pageId = this.selectedSection.pages[0].id;
this.selectPage(pageId);
const defaultPageId = this.selectedSection.pages[0].id;
this.selectPage(defaultPageId);
this.syncUrlWithPageAndSection();
}

View File

@@ -17,7 +17,7 @@
<script>
import Snapshot from '../snapshot';
import { getDefaultNotebook, getNotebookSectionAndPage, validateNotebookStorageObject } from '../utils/notebook-storage';
import { getDefaultNotebook, validateNotebookStorageObject } from '../utils/notebook-storage';
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
export default {
@@ -56,10 +56,11 @@ export default {
this.setDefaultNotebookStatus();
},
methods: {
getDefaultNotebookObject() {
async getDefaultNotebookObject() {
const defaultNotebook = getDefaultNotebook();
const defaultNotebookObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
return defaultNotebook && this.openmct.objects.get(defaultNotebook.identifier);
return defaultNotebookObject;
},
async showMenu(event) {
const notebookTypes = [];
@@ -69,22 +70,20 @@ export default {
const defaultNotebookObject = await this.getDefaultNotebookObject();
if (defaultNotebookObject) {
const defaultNotebook = getDefaultNotebook();
const { section, page } = getNotebookSectionAndPage(defaultNotebookObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
if (section && page) {
const name = defaultNotebookObject.name;
const sectionName = section.name;
const pageName = page.name;
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
const name = defaultNotebookObject.name;
notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
onItemClicked: () => {
return this.snapshot(NOTEBOOK_DEFAULT);
}
});
}
const defaultNotebook = getDefaultNotebook();
const sectionName = defaultNotebook.section.name;
const pageName = defaultNotebook.page.name;
const defaultPath = `${name} - ${sectionName} - ${pageName}`;
notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
onItemClicked: () => {
return this.snapshot(NOTEBOOK_DEFAULT);
}
});
}
notebookTypes.push({
@@ -120,8 +119,9 @@ export default {
},
setDefaultNotebookStatus() {
let defaultNotebookObject = getDefaultNotebook();
if (defaultNotebookObject) {
let notebookIdentifier = defaultNotebookObject.identifier;
if (defaultNotebookObject && defaultNotebookObject.notebookMeta) {
let notebookIdentifier = defaultNotebookObject.notebookMeta.identifier;
this.openmct.status.set(notebookIdentifier, 'notebook-default');
}

View File

@@ -87,26 +87,22 @@ export default {
const selectedPage = this.pages.find(p => p.isSelected);
const defaultNotebook = getDefaultNotebook();
const defaultPageId = defaultNotebook && defaultNotebook.defaultPageId;
const defaultpage = defaultNotebook && defaultNotebook.page;
const isPageSelected = selectedPage && selectedPage.id === id;
const isPageDefault = defaultPageId === id;
const isPageDefault = defaultpage && defaultpage.id === id;
const pages = this.pages.filter(s => s.id !== id);
let selectedPageId;
if (isPageSelected && defaultPageId) {
if (isPageSelected && defaultpage) {
pages.forEach(s => {
s.isSelected = false;
if (defaultPageId === s.id) {
if (defaultpage && defaultpage.id === s.id) {
selectedPageId = s.id;
}
});
}
if (isPageDefault) {
this.$emit('defaultPageDeleted');
}
if (pages.length && isPageSelected && (!defaultPageId || isPageDefault)) {
if (pages.length && isPageSelected && (!defaultpage || isPageDefault)) {
selectedPageId = pages[0].id;
}

View File

@@ -75,25 +75,21 @@ export default {
const selectedSection = this.sections.find(s => s.id === this.selectedSectionId);
const defaultNotebook = getDefaultNotebook();
const defaultSectionId = defaultNotebook && defaultNotebook.defaultSectionId;
const defaultSection = defaultNotebook && defaultNotebook.section;
const isSectionSelected = selectedSection && selectedSection.id === id;
const isSectionDefault = defaultSectionId === id;
const isSectionDefault = defaultSection && defaultSection.id === id;
const sections = this.sections.filter(s => s.id !== id);
if (isSectionSelected && defaultSectionId) {
if (isSectionSelected && defaultSection) {
sections.forEach(s => {
s.isSelected = false;
if (defaultSectionId === s.id) {
if (defaultSection && defaultSection.id === s.id) {
s.isSelected = true;
}
});
}
if (isSectionDefault) {
this.$emit('defaultSectionDeleted');
}
if (sections.length && isSectionSelected && (!defaultSectionId || isSectionDefault)) {
if (sections.length && isSectionSelected && (!defaultSection || isSectionDefault)) {
sections[0].isSelected = true;
}

View File

@@ -19,7 +19,6 @@
:domain-object="domainObject"
:sections="sections"
:section-title="sectionTitle"
@defaultSectionDeleted="defaultSectionDeleted"
@updateSection="sectionsChanged"
@selectSection="selectSection"
/>
@@ -51,7 +50,6 @@
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
:page-title="pageTitle"
@defaultPageDeleted="defaultPageDeleted"
@toggleNav="toggleNav"
@updatePage="pagesChanged"
@selectPage="selectPage"
@@ -220,12 +218,6 @@ export default {
sectionTitle
};
},
defaultPageDeleted() {
this.$emit('defaultPageDeleted');
},
defaultSectionDeleted() {
this.$emit('defaultSectionDeleted');
},
toggleNav() {
this.$emit('toggleNav');
},

View File

@@ -1,5 +1,5 @@
import { addNotebookEntry, createNewEmbed } from './utils/notebook-entries';
import { getDefaultNotebook, getNotebookSectionAndPage, getDefaultNotebookLink, setDefaultNotebook } from './utils/notebook-storage';
import { getDefaultNotebook, getDefaultNotebookLink, setDefaultNotebook } from './utils/notebook-storage';
import { NOTEBOOK_DEFAULT } from '@/plugins/notebook/notebook-constants';
import { createNotebookImageDomainObject, DEFAULT_SIZE } from './utils/notebook-image';
@@ -58,25 +58,20 @@ export default class Snapshot {
*/
_saveToDefaultNoteBook(embed) {
const notebookStorage = getDefaultNotebook();
this.openmct.objects.get(notebookStorage.identifier)
this.openmct.objects.get(notebookStorage.notebookMeta.identifier)
.then(async (domainObject) => {
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
let link = notebookStorage.link;
let link = notebookStorage.notebookMeta.link;
// Backwards compatibility fix (old notebook model without link)
if (!link) {
link = await getDefaultNotebookLink(this.openmct, domainObject);
notebookStorage.link = link;
notebookStorage.notebookMeta.link = link;
setDefaultNotebook(this.openmct, notebookStorage);
}
const { section, page } = getNotebookSectionAndPage(domainObject, notebookStorage.defaultSectionId, notebookStorage.defaultPageId);
if (!section || !page) {
return;
}
const defaultPath = `${domainObject.name} - ${section.name} - ${page.name}`;
const defaultPath = `${domainObject.name} - ${notebookStorage.section.name} - ${notebookStorage.page.name}`;
const msg = `Saved to Notebook ${defaultPath}`;
this._showNotification(msg, link);
});

View File

@@ -9,24 +9,24 @@ const TIME_BOUNDS = {
};
export function addEntryIntoPage(notebookStorage, entries, entry) {
const defaultSectionId = notebookStorage.defaultSectionId;
const defaultPageId = notebookStorage.defaultPageId;
if (!defaultSectionId || !defaultPageId) {
const defaultSection = notebookStorage.section;
const defaultPage = notebookStorage.page;
if (!defaultSection || !defaultPage) {
return;
}
const newEntries = JSON.parse(JSON.stringify(entries));
let section = newEntries[defaultSectionId];
let section = newEntries[defaultSection.id];
if (!section) {
newEntries[defaultSectionId] = {};
newEntries[defaultSection.id] = {};
}
let page = newEntries[defaultSectionId][defaultPageId];
let page = newEntries[defaultSection.id][defaultPage.id];
if (!page) {
newEntries[defaultSectionId][defaultPageId] = [];
newEntries[defaultSection.id][defaultPage.id] = [];
}
newEntries[defaultSectionId][defaultPageId].push(entry);
newEntries[defaultSection.id][defaultPage.id].push(entry);
return newEntries;
}

View File

@@ -23,13 +23,28 @@ import * as NotebookEntries from './notebook-entries';
import { createOpenMct, resetApplicationState } from 'utils/testing';
const notebookStorage = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
notebookMeta: {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
}
},
defaultSectionId: '03a79b6a-971c-4e56-9892-ec536332c3f0',
defaultPageId: '8b548fd9-2b8a-4b02-93a9-4138e22eba00'
section: {
id: '03a79b6a-971c-4e56-9892-ec536332c3f0',
isDefault: true,
isSelected: true,
name: 'section',
pages: [],
sectionTitle: 'Section'
},
page: {
id: '8b548fd9-2b8a-4b02-93a9-4138e22eba00',
isDefault: true,
isSelected: true,
name: 'page',
pageTitle: 'Page'
}
};
const notebookEntries = {

View File

@@ -19,22 +19,18 @@ function defaultNotebookObjectChanged(newDomainObject) {
clearDefaultNotebook();
}
function observeDefaultNotebookObject(openmct, notebookStorage, domainObject) {
function observeDefaultNotebookObject(openmct, notebookMeta, domainObject) {
if (currentNotebookObjectIdentifier
&& objectUtils.makeKeyString(currentNotebookObjectIdentifier) === objectUtils.makeKeyString(notebookStorage.identifier)) {
&& objectUtils.makeKeyString(currentNotebookObjectIdentifier) === objectUtils.makeKeyString(notebookMeta.identifier)) {
return;
}
removeListener();
unlisten = openmct.objects.observe(domainObject, '*', defaultNotebookObjectChanged);
}
function removeListener() {
if (unlisten) {
unlisten();
unlisten = null;
}
unlisten = openmct.objects.observe(domainObject, '*', defaultNotebookObjectChanged);
}
function saveDefaultNotebook(notebookStorage) {
@@ -43,8 +39,6 @@ function saveDefaultNotebook(notebookStorage) {
export function clearDefaultNotebook() {
currentNotebookObjectIdentifier = null;
removeListener();
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, null);
}
@@ -54,17 +48,6 @@ export function getDefaultNotebook() {
return JSON.parse(notebookStorage);
}
export function getNotebookSectionAndPage(domainObject, sectionId, pageId) {
const configuration = domainObject.configuration;
const section = configuration && configuration.sections.find(s => s.id === sectionId);
const page = section && section.pages.find(p => p.id === pageId);
return {
section,
page
};
}
export async function getDefaultNotebookLink(openmct, domainObject = null) {
if (!domainObject) {
return null;
@@ -76,9 +59,9 @@ export async function getDefaultNotebookLink(openmct, domainObject = null) {
.reverse()
.join('/')
);
const { defaultPageId, defaultSectionId } = getDefaultNotebook();
const { page, section } = getDefaultNotebook();
return `#/browse/${path}?sectionId=${defaultSectionId}&pageId=${defaultPageId}`;
return `#/browse/${path}?sectionId=${section.id}&pageId=${page.id}`;
}
export function setDefaultNotebook(openmct, notebookStorage, domainObject) {
@@ -86,15 +69,15 @@ export function setDefaultNotebook(openmct, notebookStorage, domainObject) {
saveDefaultNotebook(notebookStorage);
}
export function setDefaultNotebookSectionId(sectionId) {
export function setDefaultNotebookSection(section) {
const notebookStorage = getDefaultNotebook();
notebookStorage.defaultSectionId = sectionId;
notebookStorage.section = section;
saveDefaultNotebook(notebookStorage);
}
export function setDefaultNotebookPageId(pageId) {
export function setDefaultNotebookPage(page) {
const notebookStorage = getDefaultNotebook();
notebookStorage.defaultPageId = pageId;
notebookStorage.page = page;
saveDefaultNotebook(notebookStorage);
}

View File

@@ -23,44 +23,37 @@
import * as NotebookStorage from './notebook-storage';
import { createOpenMct, resetApplicationState } from 'utils/testing';
const notebookSection = {
id: 'temp-section',
isDefault: false,
isSelected: true,
name: 'section',
pages: [
{
id: 'temp-page',
isDefault: false,
isSelected: true,
name: 'page',
pageTitle: 'Page'
}
],
sectionTitle: 'Section'
};
const domainObject = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
},
configuration: {
sections: [
notebookSection
]
}
};
const notebookStorage = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
notebookMeta: {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
}
},
defaultSectionId: 'temp-section',
defaultPageId: 'temp-page'
section: {
id: 'temp-section',
isDefault: false,
isSelected: true,
name: 'section',
pages: [],
sectionTitle: 'Section'
},
page: {
id: 'temp-page',
isDefault: false,
isSelected: true,
name: 'page',
pageTitle: 'Page'
}
};
let openmct;
@@ -111,7 +104,7 @@ describe('Notebook Storage:', () => {
expect(JSON.stringify(defaultNotebook)).toBe(JSON.stringify(notebookStorage));
});
it('has correct section on setDefaultNotebookSectionId', () => {
it('has correct section on setDefaultNotebookSection', () => {
const section = {
id: 'new-temp-section',
isDefault: true,
@@ -122,14 +115,14 @@ describe('Notebook Storage:', () => {
};
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
NotebookStorage.setDefaultNotebookSectionId(section.id);
NotebookStorage.setDefaultNotebookSection(section);
const defaultNotebook = NotebookStorage.getDefaultNotebook();
const defaultSectionId = defaultNotebook.defaultSectionId;
expect(section.id).toBe(defaultSectionId);
const newSection = defaultNotebook.section;
expect(JSON.stringify(section)).toBe(JSON.stringify(newSection));
});
it('has correct page on setDefaultNotebookPageId', () => {
it('has correct page on setDefaultNotebookPage', () => {
const page = {
id: 'new-temp-page',
isDefault: true,
@@ -139,52 +132,10 @@ describe('Notebook Storage:', () => {
};
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
NotebookStorage.setDefaultNotebookPageId(page.id);
NotebookStorage.setDefaultNotebookPage(page);
const defaultNotebook = NotebookStorage.getDefaultNotebook();
const newPageId = defaultNotebook.defaultPageId;
expect(page.id).toBe(newPageId);
});
describe('is getNotebookSectionAndPage function searches and returns correct,', () => {
let section;
let page;
beforeEach(() => {
const sectionId = 'temp-section';
const pageId = 'temp-page';
const sectionAndpage = NotebookStorage.getNotebookSectionAndPage(domainObject, sectionId, pageId);
section = sectionAndpage.section;
page = sectionAndpage.page;
});
it('id for section from notebook domain object', () => {
expect(section.id).toEqual('temp-section');
});
it('name for section from notebook domain object', () => {
expect(section.name).toEqual('section');
});
it('sectionTitle for section from notebook domain object', () => {
expect(section.sectionTitle).toEqual('Section');
});
it('number of pages for section from notebook domain object', () => {
expect(section.pages.length).toEqual(1);
});
it('id for page from notebook domain object', () => {
expect(page.id).toEqual('temp-page');
});
it('name for page from notebook domain object', () => {
expect(page.name).toEqual('page');
});
it('pageTitle for page from notebook domain object', () => {
expect(page.pageTitle).toEqual('Page');
});
const newPage = defaultNotebook.page;
expect(JSON.stringify(page)).toBe(JSON.stringify(newPage));
});
});

View File

@@ -47,7 +47,6 @@ export default class CouchObjectProvider {
let provider = this;
let sharedWorker;
// eslint-disable-next-line no-undef
const sharedWorkerURL = `${this.openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}couchDBChangesFeed.js`;
sharedWorker = new SharedWorker(sharedWorkerURL);

View File

@@ -25,16 +25,16 @@
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
>
<plot-legend :cursor-locked="!!lockHighlightPoint"
:series="config.series.models"
:series="seriesModels"
:highlights="highlights"
:legend="config.legend"
:legend="legend"
@legendHoverChanged="legendHoverChanged"
/>
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
<y-axis v-if="config.series.models.length > 0"
<y-axis v-if="seriesModels.length > 0"
:tick-width="tickWidth"
:single-series="config.series.models.length === 1"
:series-model="config.series.models[0]"
:single-series="seriesModels.length === 1"
:series-model="seriesModels[0]"
@yKeyChanged="setYAxisKey"
@tickWidthChanged="onTickWidthChange"
/>
@@ -141,8 +141,8 @@
>
</div>
</div>
<x-axis v-if="config.series.models.length > 0 && !options.compact"
:series-model="config.series.models[0]"
<x-axis v-if="seriesModels.length > 0 && !options.compact"
:series-model="seriesModels[0]"
/>
</div>
@@ -213,7 +213,8 @@ export default {
plotHistory: [],
selectedXKeyOption: {},
xKeyOptions: [],
config: {},
seriesModels: [],
legend: {},
pending: 0,
isRealTime: this.openmct.time.clock() !== undefined,
loaded: false,
@@ -239,18 +240,13 @@ export default {
watch: {
plotTickWidth(newTickWidth) {
this.onTickWidthChange(newTickWidth, true);
},
gridLines(newGridLines) {
this.setGridLinesVisibility(newGridLines);
},
cursorGuide(newCursorGuide) {
this.setCursorGuideVisibility(newCursorGuide);
}
},
mounted() {
eventHelpers.extend(this);
this.config = this.getConfig();
this.legend = this.config.legend;
this.listenTo(this.config.series, 'add', this.addSeries, this);
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
@@ -290,14 +286,18 @@ export default {
config = new PlotConfigurationModel({
id: configId,
domainObject: this.domainObject,
openmct: this.openmct
openmct: this.openmct,
callback: (data) => {
this.data = data;
}
});
configStore.add(configId, config);
}
return config;
},
addSeries(series) {
addSeries(series, index) {
this.$set(this.seriesModels, index, series);
this.listenTo(series, 'change:xKey', (xKey) => {
this.setDisplayRange(series, xKey);
}, this);
@@ -377,11 +377,8 @@ export default {
},
stopLoading() {
//TODO: Is Vue.$nextTick ok to replace $scope.$evalAsync?
this.$nextTick().then(() => {
this.pending -= 1;
this.updateLoading();
});
this.pending -= 1;
this.updateLoading();
},
updateLoading() {
@@ -507,7 +504,7 @@ export default {
},
initialize() {
_.debounce(this.handleWindowResize, 400);
this.handleWindowResize = _.debounce(this.handleWindowResize, 500);
this.plotContainerResizeObserver = new ResizeObserver(this.handleWindowResize);
this.plotContainerResizeObserver.observe(this.$parent.$refs.plotWrapper);
@@ -623,7 +620,7 @@ export default {
this.config.series.models.forEach(series => delete series.closest);
} else {
this.highlights = this.config.series.models
.filter(series => series.data.length > 0)
.filter(series => series.getSeriesData().length > 0)
.map(series => {
series.closest = series.nearestPoint(point);
@@ -927,14 +924,6 @@ export default {
this.userViewportChangeEnd();
},
setCursorGuideVisibility(cursorGuide) {
this.cursorGuide = cursorGuide === true;
},
setGridLinesVisibility(gridLines) {
this.gridLines = gridLines === true;
},
setYAxisKey(yKey) {
this.config.series.models[0].set('yKey', yKey);
},

View File

@@ -106,8 +106,9 @@ export default {
},
toggleXKeyOption() {
const selectedXKey = this.selectedXKeyOptionKey;
const dataForSelectedXKey = this.seriesModel.data
? this.seriesModel.data[0][selectedXKey]
const seriesData = this.seriesModel.getSeriesData();
const dataForSelectedXKey = seriesData
? seriesData[0][selectedXKey]
: undefined;
if (dataForSelectedXKey !== undefined) {

View File

@@ -36,7 +36,7 @@ export default class MCTChartAlarmPointSet {
this.listenTo(series, 'reset', this.reset, this);
this.listenTo(series, 'destroy', this.destroy, this);
series.data.forEach(function (point, index) {
this.series.getSeriesData().forEach(function (point, index) {
this.append(point, index, series);
}, this);
}

View File

@@ -36,7 +36,7 @@ export default class MCTChartSeriesElement {
this.listenTo(series, 'remove', this.remove, this);
this.listenTo(series, 'reset', this.reset, this);
this.listenTo(series, 'destroy', this.destroy, this);
series.data.forEach(function (point, index) {
this.series.getSeriesData().forEach(function (point, index) {
this.append(point, index, series);
}, this);
}
@@ -133,7 +133,7 @@ export default class MCTChartSeriesElement {
this.buffer = new Float32Array(20000);
this.count = 0;
if (this.offset.x) {
this.series.data.forEach(function (point, index) {
this.series.getSeriesData().forEach(function (point, index) {
this.append(point, index, this.series);
}, this);
}

View File

@@ -107,6 +107,7 @@ export default class PlotConfigurationModel extends Model {
updateDomainObject(domainObject) {
this.set('domainObject', domainObject);
}
/**
* Clean up all objects and remove all listeners.
*/

View File

@@ -22,6 +22,7 @@
import _ from 'lodash';
import Model from "./Model";
import { MARKER_SHAPES } from '../draw/MarkerShapes';
import configStore from "../configuration/configStore";
/**
* Plot series handle interpreting telemetry metadata for a single telemetry
@@ -62,7 +63,6 @@ import { MARKER_SHAPES } from '../draw/MarkerShapes';
export default class PlotSeries extends Model {
constructor(options) {
super(options);
this.data = [];
this.listenTo(this, 'change:xKey', this.onXKeyChange, this);
this.listenTo(this, 'change:yKey', this.onYKeyChange, this);
@@ -115,6 +115,8 @@ export default class PlotSeries extends Model {
this.openmct = options.openmct;
this.domainObject = options.domainObject;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.dataStoreId = `data-${options.collection.plot.id}-${this.keyString}`;
this.updateSeriesData([]);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
this.limits = [];
@@ -182,7 +184,8 @@ export default class PlotSeries extends Model {
.telemetry
.request(this.domainObject, options)
.then(function (points) {
const newPoints = _(this.data)
const data = this.getSeriesData();
const newPoints = _(data)
.concat(points)
.sortBy(this.getXVal)
.uniq(true, point => [this.getXVal(point), this.getYVal(point)].join())
@@ -236,7 +239,7 @@ export default class PlotSeries extends Model {
*/
resetStats() {
this.unset('stats');
this.data.forEach(this.updateStats, this);
this.getSeriesData().forEach(this.updateStats, this);
}
/**
@@ -244,7 +247,7 @@ export default class PlotSeries extends Model {
* data to series after reset.
*/
reset(newData) {
this.data = [];
this.updateSeriesData([]);
this.resetStats();
this.emit('reset');
if (newData) {
@@ -258,8 +261,9 @@ export default class PlotSeries extends Model {
*/
nearestPoint(xValue) {
const insertIndex = this.sortedIndex(xValue);
const lowPoint = this.data[insertIndex - 1];
const highPoint = this.data[insertIndex];
const data = this.getSeriesData();
const lowPoint = data[insertIndex - 1];
const highPoint = data[insertIndex];
const indexVal = this.getXVal(xValue);
const lowDistance = lowPoint
? indexVal - this.getXVal(lowPoint)
@@ -292,7 +296,7 @@ export default class PlotSeries extends Model {
* @private
*/
sortedIndex(point) {
return _.sortedIndexBy(this.data, point, this.getXVal);
return _.sortedIndexBy(this.getSeriesData(), point, this.getXVal);
}
/**
* Update min/max stats for the series.
@@ -346,9 +350,10 @@ export default class PlotSeries extends Model {
* a point to the end without dupe checking.
*/
add(point, appendOnly) {
let insertIndex = this.data.length;
let data = this.getSeriesData();
let insertIndex = data.length;
const currentYVal = this.getYVal(point);
const lastYVal = this.getYVal(this.data[insertIndex - 1]);
const lastYVal = this.getYVal(data[insertIndex - 1]);
if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) {
console.warn('[Plot] Invalid Y Values detected');
@@ -358,18 +363,19 @@ export default class PlotSeries extends Model {
if (!appendOnly) {
insertIndex = this.sortedIndex(point);
if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) {
if (this.getXVal(data[insertIndex]) === this.getXVal(point)) {
return;
}
if (this.getXVal(this.data[insertIndex - 1]) === this.getXVal(point)) {
if (this.getXVal(data[insertIndex - 1]) === this.getXVal(point)) {
return;
}
}
this.updateStats(point);
point.mctLimitState = this.evaluate(point);
this.data.splice(insertIndex, 0, point);
data.splice(insertIndex, 0, point);
this.updateSeriesData(data);
this.emit('add', point, insertIndex, this);
}
@@ -386,8 +392,10 @@ export default class PlotSeries extends Model {
* @private
*/
remove(point) {
const index = this.data.indexOf(point);
this.data.splice(index, 1);
let data = this.getSeriesData();
const index = data.indexOf(point);
data.splice(index, 1);
this.updateSeriesData(data);
this.emit('remove', point, index, this);
}
/**
@@ -403,14 +411,16 @@ export default class PlotSeries extends Model {
purgeRecordsOutsideRange(range) {
const startIndex = this.sortedIndex(range.min);
const endIndex = this.sortedIndex(range.max) + 1;
const pointsToRemove = startIndex + (this.data.length - endIndex + 1);
let data = this.getSeriesData();
const pointsToRemove = startIndex + (data.length - endIndex + 1);
if (pointsToRemove > 0) {
if (pointsToRemove < 1000) {
this.data.slice(0, startIndex).forEach(this.remove, this);
this.data.slice(endIndex, this.data.length).forEach(this.remove, this);
data.slice(0, startIndex).forEach(this.remove, this);
data.slice(endIndex, data.length).forEach(this.remove, this);
this.updateSeriesData(data);
this.resetStats();
} else {
const newData = this.data.slice(startIndex, endIndex);
const newData = this.getSeriesData().slice(startIndex, endIndex);
this.reset(newData);
}
}
@@ -441,12 +451,13 @@ export default class PlotSeries extends Model {
}
}
getDisplayRange(xKey) {
const unsortedData = this.data;
this.data = [];
const unsortedData = this.getSeriesData();
this.updateSeriesData([]);
unsortedData.forEach(point => this.add(point, false));
const minValue = this.getXVal(this.data[0]);
const maxValue = this.getXVal(this.data[this.data.length - 1]);
let data = this.getSeriesData();
const minValue = this.getXVal(data[0]);
const maxValue = this.getXVal(data[data.length - 1]);
return {
min: minValue,
@@ -470,4 +481,18 @@ export default class PlotSeries extends Model {
return this.get('name') + (unit ? ' ' + unit : '');
}
/**
* Update the series data with the given value.
*/
updateSeriesData(data) {
configStore.add(this.dataStoreId, data);
}
/**
* Update the series data with the given value.
*/
getSeriesData() {
return configStore.get(this.dataStoreId) || [];
}
}

View File

@@ -25,7 +25,10 @@ function ConfigStore() {
ConfigStore.prototype.deleteStore = function (id) {
if (this.store[id]) {
this.store[id].destroy();
if (this.store[id].destroy) {
this.store[id].destroy();
}
delete this.store[id];
}
};

View File

@@ -176,7 +176,9 @@ DrawWebGL.prototype.doDraw = function (drawType, buf, color, points, shape) {
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
this.gl.uniform4fv(this.uColor, color);
this.gl.uniform1i(this.uMarkerShape, shapeCode);
this.gl.drawArrays(drawType, 0, points);
if (points !== 0) {
this.gl.drawArrays(drawType, 0, points);
}
};
DrawWebGL.prototype.clear = function () {

View File

@@ -715,14 +715,15 @@ describe("the plugin", function () {
});
it("Adds a new point to the plot", (done) => {
let originalLength = config.series.models[0].data.length;
let originalLength = config.series.models[0].getSeriesData().length;
config.series.models[0].add({
utc: 2,
'some-key': 1,
'some-other-key': 2
});
Vue.nextTick(() => {
expect(config.series.models[0].data.length).toEqual(originalLength + 1);
const seriesData = config.series.models[0].getSeriesData();
expect(seriesData.length).toEqual(originalLength + 1);
done();
});
});

View File

@@ -68,7 +68,8 @@ define([
'./performanceIndicator/plugin',
'./CouchDBSearchFolder/plugin',
'./timeline/plugin',
'./hyperlink/plugin'
'./hyperlink/plugin',
'./clock/plugin'
], function (
_,
UTCTimeSystem,
@@ -117,7 +118,8 @@ define([
PerformanceIndicator,
CouchDBSearchFolder,
Timeline,
Hyperlink
Hyperlink,
Clock
) {
const bundleMap = {
LocalStorage: 'platform/persistence/local',
@@ -221,6 +223,7 @@ define([
plugins.CouchDBSearchFolder = CouchDBSearchFolder.default;
plugins.Timeline = Timeline.default;
plugins.Hyperlink = Hyperlink.default;
plugins.Clock = Clock.default;
return plugins;
});

View File

@@ -23,18 +23,20 @@
define([
'EventEmitter',
'lodash',
'./collections/TableRowCollection',
'./TelemetryTableRow',
'./collections/BoundedTableRowCollection',
'./collections/FilteredTableRowCollection',
'./TelemetryTableNameColumn',
'./TelemetryTableRow',
'./TelemetryTableColumn',
'./TelemetryTableUnitColumn',
'./TelemetryTableConfiguration'
], function (
EventEmitter,
_,
TableRowCollection,
TelemetryTableRow,
BoundedTableRowCollection,
FilteredTableRowCollection,
TelemetryTableNameColumn,
TelemetryTableRow,
TelemetryTableColumn,
TelemetryTableUnitColumn,
TelemetryTableConfiguration
@@ -46,23 +48,20 @@ define([
this.domainObject = domainObject;
this.openmct = openmct;
this.rowCount = 100;
this.subscriptions = {};
this.tableComposition = undefined;
this.telemetryObjects = [];
this.datumCache = [];
this.outstandingRequests = 0;
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
this.paused = false;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.telemetryObjects = {};
this.telemetryCollections = {};
this.delayedActions = [];
this.outstandingRequests = 0;
this.addTelemetryObject = this.addTelemetryObject.bind(this);
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
this.removeTelemetryCollection = this.removeTelemetryCollection.bind(this);
this.resetRowsFromAllData = this.resetRowsFromAllData.bind(this);
this.isTelemetryObject = this.isTelemetryObject.bind(this);
this.refreshData = this.refreshData.bind(this);
this.requestDataFor = this.requestDataFor.bind(this);
this.updateFilters = this.updateFilters.bind(this);
this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this);
@@ -103,7 +102,8 @@ define([
}
createTableRowCollections() {
this.tableRows = new TableRowCollection();
this.boundedRows = new BoundedTableRowCollection(this.openmct);
this.filteredRows = new FilteredTableRowCollection(this.boundedRows);
//Fetch any persisted default sort
let sortOptions = this.configuration.getConfiguration().sortOptions;
@@ -113,14 +113,11 @@ define([
key: this.openmct.time.timeSystem().key,
direction: 'asc'
};
this.tableRows.sortBy(sortOptions);
this.tableRows.on('resetRowsFromAllData', this.resetRowsFromAllData);
this.filteredRows.sortBy(sortOptions);
}
loadComposition() {
this.tableComposition = this.openmct.composition.get(this.domainObject);
if (this.tableComposition !== undefined) {
this.tableComposition.load().then((composition) => {
@@ -135,64 +132,66 @@ define([
addTelemetryObject(telemetryObject) {
this.addColumnsForObject(telemetryObject, true);
const keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
this.incrementOutstandingRequests();
const telemetryProcessor = this.getTelemetryProcessor(keyString, columnMap, limitEvaluator);
const telemetryRemover = this.getTelemetryRemover();
this.removeTelemetryCollection(keyString);
this.telemetryCollections[keyString] = this.openmct.telemetry
.requestTelemetryCollection(telemetryObject, requestOptions);
this.telemetryCollections[keyString].on('remove', telemetryRemover);
this.telemetryCollections[keyString].on('add', telemetryProcessor);
this.telemetryCollections[keyString].load();
this.decrementOutstandingRequests();
this.telemetryObjects[keyString] = {
telemetryObject,
keyString,
requestOptions,
columnMap,
limitEvaluator
};
this.requestDataFor(telemetryObject);
this.subscribeTo(telemetryObject);
this.telemetryObjects.push(telemetryObject);
this.emit('object-added', telemetryObject);
}
getTelemetryProcessor(keyString, columnMap, limitEvaluator) {
return (telemetry) => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects[keyString]) {
return;
}
updateFilters(updatedFilters) {
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
let telemetryRows = telemetry.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
if (this.paused) {
this.delayedActions.push(this.tableRows.addRows.bind(this, telemetryRows, 'add'));
} else {
this.tableRows.addRows(telemetryRows, 'add');
}
};
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
this.filters = deepCopiedFilters;
this.clearAndResubscribe();
} else {
this.filters = deepCopiedFilters;
}
}
getTelemetryRemover() {
return (telemetry) => {
if (this.paused) {
this.delayedActions.push(this.tableRows.removeRowsByData.bind(this, telemetry));
} else {
this.tableRows.removeRowsByData(telemetry);
}
};
clearAndResubscribe() {
this.filteredRows.clear();
this.boundedRows.clear();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.telemetryObjects.forEach(this.requestDataFor.bind(this));
this.telemetryObjects.forEach(this.subscribeTo.bind(this));
}
removeTelemetryObject(objectIdentifier) {
this.configuration.removeColumnsForObject(objectIdentifier, true);
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
this.boundedRows.removeAllRowsForObject(keyString);
this.unsubscribe(keyString);
this.telemetryObjects = this.telemetryObjects.filter((object) => !_.eq(objectIdentifier, object.identifier));
this.emit('object-removed', objectIdentifier);
}
requestDataFor(telemetryObject) {
this.incrementOutstandingRequests();
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
return this.openmct.telemetry.request(telemetryObject, requestOptions)
.then(telemetryData => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects.includes(telemetryObject)) {
return;
}
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
this.processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator);
}).finally(() => {
this.decrementOutstandingRequests();
});
}
processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) {
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
this.boundedRows.add(telemetryRows);
}
/**
@@ -217,72 +216,35 @@ define([
}
}
// will pull all necessary information for all existing bounded telemetry
// and pass to table row collection to reset without making any new requests
// triggered by filtering
resetRowsFromAllData() {
let allRows = [];
Object.keys(this.telemetryCollections).forEach(keyString => {
let { columnMap, limitEvaluator } = this.telemetryObjects[keyString];
this.telemetryCollections[keyString].getAll().forEach(datum => {
allRows.push(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
});
});
this.tableRows.addRows(allRows, 'filter');
}
updateFilters(updatedFilters) {
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
this.filters = deepCopiedFilters;
this.tableRows.clear();
this.clearAndResubscribe();
} else {
this.filters = deepCopiedFilters;
}
}
clearAndResubscribe() {
let objectKeys = Object.keys(this.telemetryObjects);
this.tableRows.clear();
objectKeys.forEach((keyString) => {
this.addTelemetryObject(this.telemetryObjects[keyString].telemetryObject);
});
}
removeTelemetryObject(objectIdentifier) {
const keyString = this.openmct.objects.makeKeyString(objectIdentifier);
this.configuration.removeColumnsForObject(objectIdentifier, true);
this.tableRows.removeRowsByObject(keyString);
this.removeTelemetryCollection(keyString);
delete this.telemetryObjects[keyString];
this.emit('object-removed', objectIdentifier);
}
refreshData(bounds, isTick) {
if (!isTick && this.tableRows.outstandingRequests === 0) {
this.tableRows.clear();
this.tableRows.sortBy({
key: this.openmct.time.timeSystem().key,
direction: 'asc'
});
this.tableRows.resubscribe();
if (!isTick && this.outstandingRequests === 0) {
this.filteredRows.clear();
this.boundedRows.clear();
this.boundedRows.sortByTimeSystem(this.openmct.time.timeSystem());
this.telemetryObjects.forEach(this.requestDataFor);
}
}
clearData() {
this.tableRows.clear();
this.filteredRows.clear();
this.boundedRows.clear();
this.emit('refresh');
}
getColumnMapForObject(objectKeyString) {
let columns = this.configuration.getColumns();
if (columns[objectKeyString]) {
return columns[objectKeyString].reduce((map, column) => {
map[column.getKey()] = column;
return map;
}, {});
}
return {};
}
addColumnsForObject(telemetryObject) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
@@ -302,18 +264,54 @@ define([
});
}
getColumnMapForObject(objectKeyString) {
let columns = this.configuration.getColumns();
createColumn(metadatum) {
return new TelemetryTableColumn(this.openmct, metadatum);
}
if (columns[objectKeyString]) {
return columns[objectKeyString].reduce((map, column) => {
map[column.getKey()] = column;
createUnitColumn(metadatum) {
return new TelemetryTableUnitColumn(this.openmct, metadatum);
}
return map;
}, {});
}
subscribeTo(telemetryObject) {
let subscribeOptions = this.buildOptionsFromConfiguration(telemetryObject);
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
return {};
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects.includes(telemetryObject)) {
return;
}
if (this.paused) {
let realtimeDatum = {
datum,
columnMap,
keyString,
limitEvaluator
};
this.datumCache.push(realtimeDatum);
} else {
this.processRealtimeDatum(datum, columnMap, keyString, limitEvaluator);
}
}, subscribeOptions);
}
processDatumCache() {
this.datumCache.forEach(cachedDatum => {
this.processRealtimeDatum(cachedDatum.datum, cachedDatum.columnMap, cachedDatum.keyString, cachedDatum.limitEvaluator);
});
this.datumCache = [];
}
processRealtimeDatum(datum, columnMap, keyString, limitEvaluator) {
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
}
isTelemetryObject(domainObject) {
return Object.prototype.hasOwnProperty.call(domainObject, 'telemetry');
}
buildOptionsFromConfiguration(telemetryObject) {
@@ -325,20 +323,13 @@ define([
return {filters} || {};
}
createColumn(metadatum) {
return new TelemetryTableColumn(this.openmct, metadatum);
}
createUnitColumn(metadatum) {
return new TelemetryTableUnitColumn(this.openmct, metadatum);
}
isTelemetryObject(domainObject) {
return Object.prototype.hasOwnProperty.call(domainObject, 'telemetry');
unsubscribe(keyString) {
this.subscriptions[keyString]();
delete this.subscriptions[keyString];
}
sortBy(sortOptions) {
this.tableRows.sortBy(sortOptions);
this.filteredRows.sortBy(sortOptions);
if (this.openmct.editor.isEditing()) {
let configuration = this.configuration.getConfiguration();
@@ -347,36 +338,21 @@ define([
}
}
runDelayedActions() {
this.delayedActions.forEach(action => action());
this.delayedActions = [];
}
removeTelemetryCollection(keyString) {
if (this.telemetryCollections[keyString]) {
this.telemetryCollections[keyString].destroy();
this.telemetryCollections[keyString] = undefined;
delete this.telemetryCollections[keyString];
}
}
pause() {
this.paused = true;
this.boundedRows.unsubscribeFromBounds();
}
unpause() {
this.paused = false;
this.runDelayedActions();
this.processDatumCache();
this.boundedRows.subscribeToBounds();
}
destroy() {
this.tableRows.destroy();
this.tableRows.off('resetRowsFromAllData', this.resetRowsFromAllData);
let keystrings = Object.keys(this.telemetryCollections);
keystrings.forEach(this.removeTelemetryCollection);
this.boundedRows.destroy();
this.filteredRows.destroy();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.off('timeSystem', this.refreshData);

View File

@@ -0,0 +1,166 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
define(
[
'lodash',
'./SortedTableRowCollection'
],
function (
_,
SortedTableRowCollection
) {
class BoundedTableRowCollection extends SortedTableRowCollection {
constructor(openmct) {
super();
this.futureBuffer = new SortedTableRowCollection();
this.openmct = openmct;
this.sortByTimeSystem = this.sortByTimeSystem.bind(this);
this.bounds = this.bounds.bind(this);
this.sortByTimeSystem(openmct.time.timeSystem());
this.lastBounds = openmct.time.bounds();
this.subscribeToBounds();
}
addOne(item) {
let parsedValue = this.getValueForSortColumn(item);
// Insert into either in-bounds array, or the future buffer.
// Data in the future buffer will be re-evaluated for possible
// insertion on next bounds change
let beforeStartOfBounds = parsedValue < this.lastBounds.start;
let afterEndOfBounds = parsedValue > this.lastBounds.end;
if (!afterEndOfBounds && !beforeStartOfBounds) {
return super.addOne(item);
} else if (afterEndOfBounds) {
this.futureBuffer.addOne(item);
}
return false;
}
sortByTimeSystem(timeSystem) {
this.sortBy({
key: timeSystem.key,
direction: 'asc'
});
let formatter = this.openmct.telemetry.getValueFormatter({
key: timeSystem.key,
source: timeSystem.key,
format: timeSystem.timeFormat
});
this.parseTime = formatter.parse.bind(formatter);
this.futureBuffer.sortBy({
key: timeSystem.key,
direction: 'asc'
});
}
/**
* This function is optimized for ticking - it assumes that start and end
* bounds will only increase and as such this cannot be used for decreasing
* bounds changes.
*
* An implication of this is that data will not be discarded that exceeds
* the given end bounds. For arbitrary bounds changes, it's assumed that
* a telemetry requery is performed anyway, and the collection is cleared
* and repopulated.
*
* @fires TelemetryCollection#added
* @fires TelemetryCollection#discarded
* @param bounds
*/
bounds(bounds) {
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 && 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 && 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);
}
}
getValueForSortColumn(row) {
return this.parseTime(row.datum[this.sortOptions.key]);
}
unsubscribeFromBounds() {
this.openmct.time.off('bounds', this.bounds);
}
subscribeToBounds() {
this.openmct.time.on('bounds', this.bounds);
}
destroy() {
this.unsubscribeFromBounds();
}
}
return BoundedTableRowCollection;
});

View File

@@ -0,0 +1,136 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
define(
[
'./SortedTableRowCollection'
],
function (
SortedTableRowCollection
) {
class FilteredTableRowCollection extends SortedTableRowCollection {
constructor(masterCollection) {
super();
this.masterCollection = masterCollection;
this.columnFilters = {};
//Synchronize with master collection
this.masterCollection.on('add', this.add);
this.masterCollection.on('remove', this.remove);
//Default to master collection's sort options
this.sortOptions = masterCollection.sortBy();
}
setColumnFilter(columnKey, filter) {
filter = filter.trim().toLowerCase();
let rowsToFilter = this.getRowsToFilter(columnKey, filter);
if (filter.length === 0) {
delete this.columnFilters[columnKey];
} else {
this.columnFilters[columnKey] = filter;
}
this.rows = rowsToFilter.filter(this.matchesFilters, this);
this.emit('filter');
}
setColumnRegexFilter(columnKey, filter) {
filter = filter.trim();
let rowsToFilter = this.masterCollection.getRows();
this.columnFilters[columnKey] = new RegExp(filter);
this.rows = rowsToFilter.filter(this.matchesFilters, this);
this.emit('filter');
}
/**
* @private
*/
getRowsToFilter(columnKey, filter) {
if (this.isSubsetOfCurrentFilter(columnKey, filter)) {
return this.getRows();
} else {
return this.masterCollection.getRows();
}
}
/**
* @private
*/
isSubsetOfCurrentFilter(columnKey, filter) {
if (this.columnFilters[columnKey] instanceof RegExp) {
return false;
}
return this.columnFilters[columnKey]
&& filter.startsWith(this.columnFilters[columnKey])
// startsWith check will otherwise fail when filter cleared
// because anyString.startsWith('') === true
&& filter !== '';
}
addOne(row) {
return this.matchesFilters(row) && super.addOne(row);
}
/**
* @private
*/
matchesFilters(row) {
let doesMatchFilters = true;
Object.keys(this.columnFilters).forEach((key) => {
if (!doesMatchFilters || !this.rowHasColumn(row, key)) {
return false;
}
let formattedValue = row.getFormattedValue(key);
if (formattedValue === undefined) {
return false;
}
if (this.columnFilters[key] instanceof RegExp) {
doesMatchFilters = this.columnFilters[key].test(formattedValue);
} else {
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
}
});
return doesMatchFilters;
}
rowHasColumn(row, key) {
return Object.prototype.hasOwnProperty.call(row.columns, key);
}
destroy() {
this.masterCollection.off('add', this.add);
this.masterCollection.off('remove', this.remove);
}
}
return FilteredTableRowCollection;
});

View File

@@ -36,72 +36,85 @@ define(
/**
* @constructor
*/
class TableRowCollection extends EventEmitter {
class SortedTableRowCollection extends EventEmitter {
constructor() {
super();
this.dupeCheck = false;
this.rows = [];
this.columnFilters = {};
this.addRows = this.addRows.bind(this);
this.removeRowsByObject = this.removeRowsByObject.bind(this);
this.removeRowsByData = this.removeRowsByData.bind(this);
this.clear = this.clear.bind(this);
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);
}
removeRowsByObject(keyString) {
let removed = [];
/**
* Add a datum or array of data to this telemetry collection
* @fires TelemetryCollection#added
* @param {object | object[]} rows
*/
add(rows) {
if (Array.isArray(rows)) {
this.dupeCheck = false;
this.rows = this.rows.filter((row) => {
if (row.objectKeyString === keyString) {
removed.push(row);
return false;
} else {
return true;
let rowsAdded = rows.filter(this.addOne, this);
if (rowsAdded.length > 0) {
this.emit('add', rowsAdded);
}
});
this.emit('remove', removed);
this.dupeCheck = true;
} else {
let wasAdded = this.addOne(rows);
if (wasAdded) {
this.emit('add', rows);
}
}
}
addRows(rows, type = 'add') {
/**
* @private
*/
addOne(row) {
if (this.sortOptions === undefined) {
throw 'Please specify sort options';
}
let isFilterTriggeredReset = type === 'filter';
let anyActiveFilters = Object.keys(this.columnFilters).length > 0;
let rowsToAdd = !anyActiveFilters ? rows : rows.filter(this.matchesFilters, this);
let isDuplicate = false;
// if type is filter, then it's a reset of all rows,
// need to wipe current rows
if (isFilterTriggeredReset) {
this.rows = [];
// Going to check for duplicates. Bound the search problem to
// items around the given time. Use sortedIndex because it
// employs a binary search which is O(log n). Can use binary search
// because the array is guaranteed ordered due to sorted insertion.
let startIx = this.sortedIndex(this.rows, row);
let endIx = undefined;
if (this.dupeCheck && startIx !== this.rows.length) {
endIx = this.sortedLastIndex(this.rows, row);
// Create an array of potential dupes, based on having the
// same time stamp
let potentialDupes = this.rows.slice(startIx, endIx + 1);
// Search potential dupes for exact dupe
isDuplicate = potentialDupes.some(_.isEqual.bind(undefined, row));
}
for (let row of rowsToAdd) {
let index = this.sortedIndex(this.rows, row);
this.rows.splice(index, 0, row);
if (!isDuplicate) {
this.rows.splice(endIx || startIx, 0, row);
return true;
}
// we emit filter no matter what to trigger
// an update of visible rows
if (rowsToAdd.length > 0 || isFilterTriggeredReset) {
this.emit(type, rowsToAdd);
}
return false;
}
sortedLastIndex(rows, testRow) {
return this.sortedIndex(rows, testRow, _.sortedLastIndex);
}
/**
* Finds the correct insertion point for the given row.
* Leverages lodash's `sortedIndex` function which implements a binary search.
* @private
*/
sortedIndex(rows, testRow, lodashFunction = _.sortedIndexBy) {
sortedIndex(rows, testRow, lodashFunction) {
if (this.rows.length === 0) {
return 0;
}
@@ -110,6 +123,8 @@ define(
const firstValue = this.getValueForSortColumn(this.rows[0]);
const lastValue = this.getValueForSortColumn(this.rows[this.rows.length - 1]);
lodashFunction = lodashFunction || _.sortedIndexBy;
if (this.sortOptions.direction === 'asc') {
if (testRowValue > lastValue) {
return this.rows.length;
@@ -147,22 +162,6 @@ define(
}
}
removeRowsByData(data) {
let removed = [];
this.rows = this.rows.filter((row) => {
if (data.includes(row.fullDatum)) {
removed.push(row);
return false;
} else {
return true;
}
});
this.emit('remove', removed);
}
/**
* Sorts the telemetry collection based on the provided sort field
* specifier. Subsequent inserts are sorted to maintain specified sport
@@ -206,7 +205,6 @@ define(
if (arguments.length > 0) {
this.sortOptions = sortOptions;
this.rows = _.orderBy(this.rows, (row) => row.getParsedValue(sortOptions.key), sortOptions.direction);
this.emit('sort');
}
@@ -214,114 +212,44 @@ define(
return Object.assign({}, this.sortOptions);
}
setColumnFilter(columnKey, filter) {
filter = filter.trim().toLowerCase();
let wasBlank = this.columnFilters[columnKey] === undefined;
let isSubset = this.isSubsetOfCurrentFilter(columnKey, filter);
removeAllRowsForObject(objectKeyString) {
let removed = [];
this.rows = this.rows.filter(row => {
if (row.objectKeyString === objectKeyString) {
removed.push(row);
if (filter.length === 0) {
delete this.columnFilters[columnKey];
} else {
this.columnFilters[columnKey] = filter;
}
if (isSubset || wasBlank) {
this.rows = this.rows.filter(this.matchesFilters, this);
this.emit('filter');
} else {
this.emit('resetRowsFromAllData');
}
}
setColumnRegexFilter(columnKey, filter) {
filter = filter.trim();
this.columnFilters[columnKey] = new RegExp(filter);
this.emit('resetRowsFromAllData');
}
getColumnMapForObject(objectKeyString) {
let columns = this.configuration.getColumns();
if (columns[objectKeyString]) {
return columns[objectKeyString].reduce((map, column) => {
map[column.getKey()] = column;
return map;
}, {});
}
return {};
}
// /**
// * @private
// */
isSubsetOfCurrentFilter(columnKey, filter) {
if (this.columnFilters[columnKey] instanceof RegExp) {
return false;
}
return this.columnFilters[columnKey]
&& filter.startsWith(this.columnFilters[columnKey])
// startsWith check will otherwise fail when filter cleared
// because anyString.startsWith('') === true
&& filter !== '';
}
/**
* @private
*/
matchesFilters(row) {
let doesMatchFilters = true;
Object.keys(this.columnFilters).forEach((key) => {
if (!doesMatchFilters || !this.rowHasColumn(row, key)) {
return false;
}
let formattedValue = row.getFormattedValue(key);
if (formattedValue === undefined) {
return false;
}
if (this.columnFilters[key] instanceof RegExp) {
doesMatchFilters = this.columnFilters[key].test(formattedValue);
} else {
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
}
return true;
});
return doesMatchFilters;
}
rowHasColumn(row, key) {
return Object.prototype.hasOwnProperty.call(row.columns, key);
}
getRows() {
return this.rows;
}
getRowsLength() {
return this.rows.length;
this.emit('remove', removed);
}
getValueForSortColumn(row) {
return row.getParsedValue(this.sortOptions.key);
}
remove(removedRows) {
this.rows = this.rows.filter(row => {
return removedRows.indexOf(row) === -1;
});
this.emit('remove', removedRows);
}
getRows() {
return this.rows;
}
clear() {
let removedRows = this.rows;
this.rows = [];
this.emit('remove', removedRows);
}
destroy() {
this.removeAllListeners();
}
}
return TableRowCollection;
return SortedTableRowCollection;
});

View File

@@ -466,21 +466,22 @@ export default {
this.table.on('object-added', this.addObject);
this.table.on('object-removed', this.removeObject);
this.table.on('outstanding-requests', this.outstandingRequests);
this.table.on('refresh', this.clearRowsAndRerender);
this.table.on('historical-rows-processed', this.checkForMarkedRows);
this.table.on('outstanding-requests', this.outstandingRequests);
this.table.tableRows.on('add', this.rowsAdded);
this.table.tableRows.on('remove', this.rowsRemoved);
this.table.tableRows.on('sort', this.updateVisibleRows);
this.table.tableRows.on('filter', this.updateVisibleRows);
this.table.filteredRows.on('add', this.rowsAdded);
this.table.filteredRows.on('remove', this.rowsRemoved);
this.table.filteredRows.on('sort', this.updateVisibleRows);
this.table.filteredRows.on('filter', this.updateVisibleRows);
//Default sort
this.sortOptions = this.table.tableRows.sortBy();
this.sortOptions = this.table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
this.table.configuration.on('change', this.updateConfiguration);
this.calculateTableSize();
@@ -492,14 +493,13 @@ export default {
destroyed() {
this.table.off('object-added', this.addObject);
this.table.off('object-removed', this.removeObject);
this.table.off('historical-rows-processed', this.checkForMarkedRows);
this.table.off('refresh', this.clearRowsAndRerender);
this.table.off('outstanding-requests', this.outstandingRequests);
this.table.off('refresh', this.clearRowsAndRerender);
this.table.tableRows.off('add', this.rowsAdded);
this.table.tableRows.off('remove', this.rowsRemoved);
this.table.tableRows.off('sort', this.updateVisibleRows);
this.table.tableRows.off('filter', this.updateVisibleRows);
this.table.filteredRows.off('add', this.rowsAdded);
this.table.filteredRows.off('remove', this.rowsRemoved);
this.table.filteredRows.off('sort', this.updateVisibleRows);
this.table.filteredRows.off('filter', this.updateVisibleRows);
this.table.configuration.off('change', this.updateConfiguration);
@@ -517,13 +517,13 @@ export default {
let start = 0;
let end = VISIBLE_ROW_COUNT;
let tableRows = this.table.tableRows.getRows();
let tableRowsLength = tableRows.length;
let filteredRows = this.table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length;
this.totalNumberOfRows = tableRowsLength;
this.totalNumberOfRows = filteredRowsLength;
if (tableRowsLength < VISIBLE_ROW_COUNT) {
end = tableRowsLength;
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength;
} else {
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
@@ -535,15 +535,15 @@ export default {
if (start < 0) {
start = 0;
end = Math.min(VISIBLE_ROW_COUNT, tableRowsLength);
} else if (end >= tableRowsLength) {
end = tableRowsLength;
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
} else if (end >= filteredRowsLength) {
end = filteredRowsLength;
start = end - VISIBLE_ROW_COUNT + 1;
}
}
this.rowOffset = start;
this.visibleRows = tableRows.slice(start, end);
this.visibleRows = filteredRows.slice(start, end);
this.updatingView = false;
});
@@ -630,19 +630,19 @@ export default {
filterChanged(columnKey) {
if (this.enableRegexSearch[columnKey]) {
if (this.isCompleteRegex(this.filters[columnKey])) {
this.table.tableRows.setColumnRegexFilter(columnKey, this.filters[columnKey].slice(1, -1));
this.table.filteredRows.setColumnRegexFilter(columnKey, this.filters[columnKey].slice(1, -1));
} else {
return;
}
} else {
this.table.tableRows.setColumnFilter(columnKey, this.filters[columnKey]);
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
}
this.setHeight();
},
clearFilter(columnKey) {
this.filters[columnKey] = '';
this.table.tableRows.setColumnFilter(columnKey, '');
this.table.filteredRows.setColumnFilter(columnKey, '');
this.setHeight();
},
rowsAdded(rows) {
@@ -674,8 +674,8 @@ export default {
* Calculates height based on total number of rows, and sets table height.
*/
setHeight() {
let tableRowsLength = this.table.tableRows.getRowsLength();
this.totalHeight = this.rowHeight * tableRowsLength - 1;
let filteredRowsLength = this.table.filteredRows.getRows().length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
// Set element height directly to avoid having to wait for Vue to update DOM
// which causes subsequent scroll to use an out of date height.
this.contentTable.style.height = this.totalHeight + 'px';
@@ -689,13 +689,13 @@ export default {
});
},
exportAllDataAsCSV() {
const justTheData = this.table.tableRows.getRows()
const justTheData = this.table.filteredRows.getRows()
.map(row => row.getFormattedDatum(this.headers));
this.exportAsCSV(justTheData);
},
exportMarkedDataAsCSV() {
const data = this.table.tableRows.getRows()
const data = this.table.filteredRows.getRows()
.filter(row => row.marked === true)
.map(row => row.getFormattedDatum(this.headers));
@@ -900,7 +900,7 @@ export default {
let lastRowToBeMarked = this.visibleRows[rowIndex];
let allRows = this.table.tableRows.getRows();
let allRows = this.table.filteredRows.getRows();
let firstRowIndex = allRows.indexOf(this.markedRows[0]);
let lastRowIndex = allRows.indexOf(lastRowToBeMarked);
@@ -923,17 +923,17 @@ export default {
},
checkForMarkedRows() {
this.isShowingMarkedRowsOnly = false;
this.markedRows = this.table.tableRows.getRows().filter(row => row.marked);
this.markedRows = this.table.filteredRows.getRows().filter(row => row.marked);
},
showRows(rows) {
this.table.tableRows.rows = rows;
this.table.emit('filter');
this.table.filteredRows.rows = rows;
this.table.filteredRows.emit('filter');
},
toggleMarkedRows(flag) {
if (flag) {
this.isShowingMarkedRowsOnly = true;
this.userScroll = this.scrollable.scrollTop;
this.allRows = this.table.tableRows.getRows();
this.allRows = this.table.filteredRows.getRows();
this.showRows(this.markedRows);
this.setHeight();

View File

@@ -48,8 +48,6 @@ describe("the plugin", () => {
let tablePlugin;
let element;
let child;
let historicalProvider;
let originalRouterPath;
let unlistenConfigMutation;
beforeEach((done) => {
@@ -60,12 +58,7 @@ describe("the plugin", () => {
tablePlugin = new TablePlugin();
openmct.install(tablePlugin);
historicalProvider = {
request: () => {
return Promise.resolve([]);
}
};
spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider);
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
element = document.createElement('div');
child = document.createElement('div');
@@ -85,8 +78,6 @@ describe("the plugin", () => {
callBack();
});
originalRouterPath = openmct.router.path;
openmct.on('start', done);
openmct.startHeadless();
});
@@ -199,12 +190,11 @@ describe("the plugin", () => {
let telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve;
});
historicalProvider.request = () => {
openmct.telemetry.request.and.callFake(() => {
telemetryPromiseResolve(testTelemetry);
return telemetryPromise;
};
});
openmct.router.path = [testTelemetryObject];
@@ -218,10 +208,6 @@ describe("the plugin", () => {
return telemetryPromise.then(() => Vue.nextTick());
});
afterEach(() => {
openmct.router.path = originalRouterPath;
});
it("Renders a row for every telemetry datum returned", () => {
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
expect(rows.length).toBe(3);
@@ -270,14 +256,14 @@ describe("the plugin", () => {
});
it("Supports filtering telemetry by regular text search", () => {
tableInstance.tableRows.setColumnFilter("some-key", "1");
tableInstance.filteredRows.setColumnFilter("some-key", "1");
return Vue.nextTick().then(() => {
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
expect(filteredRowElements.length).toEqual(1);
tableInstance.tableRows.setColumnFilter("some-key", "");
tableInstance.filteredRows.setColumnFilter("some-key", "");
return Vue.nextTick().then(() => {
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
@@ -288,14 +274,14 @@ describe("the plugin", () => {
});
it("Supports filtering using Regex", () => {
tableInstance.tableRows.setColumnRegexFilter("some-key", "^some-value$");
tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value$");
return Vue.nextTick().then(() => {
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
expect(filteredRowElements.length).toEqual(0);
tableInstance.tableRows.setColumnRegexFilter("some-key", "^some-value");
tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value");
return Vue.nextTick().then(() => {
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');

View File

@@ -1,25 +1,3 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<li
draggable="true"

View File

@@ -1,25 +1,3 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-elements-pool">
<Search

View File

@@ -1,25 +1,3 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-inspector">
<object-name />
@@ -41,9 +19,7 @@
type="vertical"
>
<pane class="c-inspector__properties">
<Properties
v-if="!activity"
/>
<properties />
<location />
<inspector-views />
</pane>
@@ -81,7 +57,7 @@ import multipane from '../layout/multipane.vue';
import pane from '../layout/pane.vue';
import ElementsPool from './ElementsPool.vue';
import Location from './Location.vue';
import Properties from './details/Properties.vue';
import Properties from './Properties.vue';
import ObjectName from './ObjectName.vue';
import InspectorViews from './InspectorViews.vue';
import _ from "lodash";
@@ -122,8 +98,7 @@ export default {
key: '__styles',
name: 'Styles'
}],
currentTabbedView: {},
activity: undefined
currentTabbedView: {}
};
},
mounted() {
@@ -136,12 +111,9 @@ export default {
methods: {
updateInspectorViews(selection) {
this.refreshComposition(selection);
if (this.openmct.types.get('conditionSet')) {
this.refreshTabs(selection);
}
this.setActivity(selection);
},
refreshComposition(selection) {
if (selection.length > 0 && selection[0].length > 0) {
@@ -178,12 +150,6 @@ export default {
},
isCurrent(view) {
return _.isEqual(this.currentTabbedView, view);
},
setActivity(selection) {
this.activity = selection
&& selection.length
&& selection[0].length
&& selection[0][0].activity;
}
}
};

View File

@@ -1,25 +1,3 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
export const mockTelemetryTableSelection = [
[{
context: {

View File

@@ -1,29 +1,10 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div></div>
</template>
<style>
</style>
<script>
export default {
inject: ['openmct'],

View File

@@ -1,25 +1,3 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-inspect-properties c-inspect-properties--location">
<div

View File

@@ -1,25 +1,3 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-inspector__header">
<div v-if="!multiSelect"

View File

@@ -0,0 +1,171 @@
<template>
<div v-if="!activity"
class="c-inspector__properties c-inspect-properties"
>
<div class="c-inspect-properties__header">
Details
</div>
<ul
v-if="!multiSelect && !singleSelectNonObject"
class="c-inspect-properties__section"
>
<li class="c-inspect-properties__row">
<div class="c-inspect-properties__label">
Title
</div>
<div class="c-inspect-properties__value">
{{ item.name }}
</div>
</li>
<li class="c-inspect-properties__row">
<div class="c-inspect-properties__label">
Type
</div>
<div class="c-inspect-properties__value">
{{ typeName }}
</div>
</li>
<li
v-if="item.created"
class="c-inspect-properties__row"
>
<div class="c-inspect-properties__label">
Created
</div>
<div class="c-inspect-properties__value">
{{ formatTime(item.created) }}
</div>
</li>
<li
v-if="item.modified"
class="c-inspect-properties__row"
>
<div class="c-inspect-properties__label">
Modified
</div>
<div class="c-inspect-properties__value">
{{ formatTime(item.modified) }}
</div>
</li>
<li
v-for="prop in typeProperties"
:key="prop.name"
class="c-inspect-properties__row"
>
<div class="c-inspect-properties__label">
{{ prop.name }}
</div>
<div class="c-inspect-properties__value">
{{ prop.value }}
</div>
</li>
</ul>
<div
v-if="multiSelect"
class="c-inspect-properties__row--span-all"
>
No properties to display for multiple items
</div>
<div
v-if="singleSelectNonObject"
class="c-inspect-properties__row--span-all"
>
No properties to display for this item
</div>
</div>
</template>
<script>
import Moment from "moment";
export default {
inject: ['openmct'],
data() {
return {
domainObject: {},
activity: undefined,
multiSelect: false
};
},
computed: {
item() {
return this.domainObject || {};
},
type() {
return this.openmct.types.get(this.item.type);
},
typeName() {
if (!this.type) {
return `Unknown: ${this.item.type}`;
}
return this.type.definition.name;
},
typeProperties() {
if (!this.type) {
return [];
}
let definition = this.type.definition;
if (!definition.form || definition.form.length === 0) {
return [];
}
return definition.form
.map((field) => {
let path = field.property;
if (typeof path === 'string') {
path = [path];
}
return {
name: field.name,
path
};
})
.filter(field => Array.isArray(field.path))
.map((field) => {
return {
name: field.name,
value: field.path.reduce((object, key) => {
return object[key];
}, this.item)
};
});
},
singleSelectNonObject() {
return !this.item.identifier && !this.multiSelect;
}
},
mounted() {
this.openmct.selection.on('change', this.updateSelection);
this.updateSelection(this.openmct.selection.get());
},
beforeDestroy() {
this.openmct.selection.off('change', this.updateSelection);
},
methods: {
updateSelection(selection) {
if (selection.length === 0 || selection[0].length === 0) {
this.domainObject = {};
return;
}
if (selection.length > 1) {
this.multiSelect = true;
this.domainObject = {};
return;
} else {
this.multiSelect = false;
this.domainObject = selection[0][0].context.item;
this.activity = selection[0][0].context.activity;
}
},
formatTime(unixTime) {
return Moment.utc(unixTime).format('YYYY-MM-DD[\n]HH:mm:ss') + ' UTC';
}
}
};
</script>

View File

@@ -1,43 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<li class="c-inspect-properties__row">
<div class="c-inspect-properties__label">
{{ detail.name }}
</div>
<div class="c-inspect-properties__value">
{{ detail.value }}
</div>
</li>
</template>
<script>
export default {
props: {
detail: {
type: Object,
required: true
}
}
};
</script>

View File

@@ -1,199 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-inspector__properties c-inspect-properties">
<div class="c-inspect-properties__header">
Details
</div>
<ul
v-if="hasDetails"
class="c-inspect-properties__section"
>
<Component
:is="getComponent(detail)"
v-for="detail in details"
:key="detail.name"
:detail="detail"
/>
</ul>
<div
v-else
class="c-inspect-properties__row--span-all"
>
{{ noDetailsMessage }}
</div>
</div>
</template>
<script>
import DetailText from './DetailText.vue';
import DetailTime from './DetailTime.vue';
export default {
components: {
DetailText,
DetailTime
},
inject: ['openmct'],
data() {
return {
selection: undefined
};
},
computed: {
details() {
return this.customDetails ? this.customDetails : this.domainObjectDetails;
},
customDetails() {
if (this.context === undefined) {
return;
}
return this.context.details;
},
domainObject() {
if (this.context === undefined) {
return;
}
return this.context.item;
},
type() {
if (this.domainObject === undefined) {
return;
}
return this.openmct.types.get(this.domainObject.type);
},
domainObjectDetails() {
if (this.domainObject === undefined) {
return;
}
const title = this.domainObject.name;
const typeName = this.type ? this.type.definition.name : `Unknown: ${this.domainObject.type}`;
const timestampLabel = this.domainObject.modified ? 'Modified' : 'Created';
const timestamp = this.domainObject.modified ? this.domainObject.modified : this.domainObject.created;
const details = [
{
name: 'Title',
value: title
},
{
name: 'Type',
value: typeName
}
];
if (timestamp !== undefined) {
details.push(
{
name: timestampLabel,
value: timestamp,
component: 'time'
}
);
}
return [...details, ...this.typeProperties];
},
context() {
if (
!this.selection
|| !this.selection.length
|| !this.selection[0].length
) {
return;
}
return this.selection[0][0].context;
},
hasDetails() {
return Boolean(
this.details
&& this.details.length
&& !this.multiSelection
);
},
multiSelection() {
return this.selection && this.selection.length > 1;
},
noDetailsMessage() {
return this.multiSelection
? 'No properties to display for multiple items'
: 'No properties to display for this item';
},
typeProperties() {
if (!this.type) {
return [];
}
let definition = this.type.definition;
if (!definition.form || definition.form.length === 0) {
return [];
}
return definition.form
.map((field) => {
let path = field.property;
if (typeof path === 'string') {
path = [path];
}
return {
name: field.name,
path
};
})
.filter(field => Array.isArray(field.path))
.map((field) => {
return {
name: field.name,
value: field.path.reduce((object, key) => {
return object[key];
}, this.domainObject)
};
});
}
},
mounted() {
this.openmct.selection.on('change', this.updateSelection);
this.updateSelection(this.openmct.selection.get());
},
beforeDestroy() {
this.openmct.selection.off('change', this.updateSelection);
},
methods: {
getComponent(detail) {
const component = detail.component ? detail.component : 'text';
return `detail-${component}`;
},
updateSelection(selection) {
this.selection = selection;
}
}
};
</script>

View File

@@ -233,6 +233,7 @@ export default {
},
mounted: function () {
document.addEventListener('click', this.closeViewAndSaveMenu);
this.promptUserbeforeNavigatingAway = this.promptUserbeforeNavigatingAway.bind(this);
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
this.openmct.editor.on('isEditing', (isEditing) => {
@@ -253,7 +254,7 @@ export default {
}
document.removeEventListener('click', this.closeViewAndSaveMenu);
window.removeEventListener('click', this.promptUserbeforeNavigatingAway);
window.removeEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
},
methods: {
toggleSaveMenu() {

View File

@@ -53,7 +53,6 @@
class="l-shell__pane-tree"
handle="after"
label="Browse"
hide-param="hideTree"
collapsable
@start-resizing="onStartResizing"
@end-resizing="onEndResizing"
@@ -105,7 +104,6 @@
class="l-shell__pane-inspector l-pane--holds-multipane"
handle="before"
label="Inspect"
hide-param="hideInspector"
collapsable
@start-resizing="onStartResizing"
@end-resizing="onEndResizing"

View File

@@ -41,6 +41,10 @@
<script>
const COLLAPSE_THRESHOLD_PX = 40;
const HIDE_TREE_PARAM = 'hideTree';
const HIDE_INSPECTOR_PARAM = 'hideInspector';
const PANE_INSPECTOR = 'Inspect';
const PANE_TREE = 'Browse';
export default {
inject: ['openmct'],
@@ -59,10 +63,6 @@ export default {
label: {
type: String,
default: ''
},
hideParam: {
type: String,
default: ''
}
},
data() {
@@ -78,27 +78,39 @@ export default {
async mounted() {
await this.$nextTick();
// Hide tree and/or inspector pane if specified in URL
if (this.collapsable) {
this.handleHideUrl();
}
this.handleHideUrl();
this.openmct.router.on('change:params', this.handleHideUrl);
},
beforeDestroy() {
this.openmct.router.off('change:params', this.handleHideUrl);
},
methods: {
toggleCollapse: function (e) {
let target = this.label === PANE_TREE ? HIDE_TREE_PARAM : HIDE_INSPECTOR_PARAM;
this.collapsed = !this.collapsed;
if (this.collapsed) {
this.handleExpand();
this.removeHideParam(this.hideParam);
} else {
this.handleCollapse();
this.addHideParam(this.hideParam);
this.addHideParam(target);
} else {
this.handleExpand();
this.removeHideParam(target);
}
},
handleHideUrl: function () {
const hideParam = this.openmct.router.getSearchParam(this.hideParam);
if (!this.collapsable) {
return;
}
if (hideParam === 'true') {
let hideTreeParam = this.openmct.router.getSearchParam(HIDE_TREE_PARAM);
let hideInspectorParam = this.openmct.router.getSearchParam(HIDE_INSPECTOR_PARAM);
let hideTree = hideTreeParam === 'true' && this.label === PANE_TREE;
let hideInspector = hideInspectorParam === 'true' && this.label === PANE_INSPECTOR;
if (hideTree || hideInspector) {
this.collapsed = true;
this.handleCollapse();
} else {
this.collapsed = false;
this.handleExpand();
}
},
addHideParam: function (target) {
@@ -110,13 +122,11 @@ export default {
handleCollapse: function () {
this.currentSize = (this.dragCollapse === true) ? this.initial : this.$el.style[this.styleProp];
this.$el.style[this.styleProp] = '';
this.collapsed = true;
},
handleExpand: function () {
this.$el.style[this.styleProp] = this.currentSize;
delete this.currentSize;
delete this.dragCollapse;
this.collapsed = false;
},
trackSize: function () {
if (!this.dragCollapse === true) {