Compare commits

...

16 Commits

Author SHA1 Message Date
Jamie Vigliotta
d20df25893 merging in main branch changes 2021-01-20 10:41:26 -08:00
Jamie Vigliotta
c236444a05 added options for request 2021-01-19 19:51:55 -08:00
Jamie Vigliotta
39c1eb1d5b added revised functionality for getting related telemetry data with examples and local testing settings as well 2021-01-19 19:49:22 -08:00
Jamie Vigliotta
0500b3967e wip 2021-01-19 15:00:50 -08:00
Jamie Vigliotta
4633436cbd added get imageMetadataValue method for getting necessary values for new image enhancements, has temporary local testing code as well for dev 2021-01-13 15:16:03 -08:00
Jamie V
3e9b567fce [Navigation Tree] Whole tree item clickable (#3638)
* passing click and context click on tree item down to object label, making the whole tree item interactive

* removed unnecessary code

* WIP: removing propagation prop from view control and just stopping all propagation

* capturing click on tree item and then calling the click handler on objectLabel, this prevents multiple events and handles all clicks in the tree-item

* removing unnecessary ref

* ignoring clicks for view control so it can handle them itself

* made view control class a constant

* replaced class-based checks with ref-based checks

* removing old leftover code
2021-01-11 16:25:29 -08:00
Charles Hacskaylo
6f51de85db Fix for changed approach to 'type' object (#3636) 2021-01-08 15:27:00 -08:00
Jamie V
f202ae19cb [Search] Index domain object type and provide query lite (index only) option (#3416)
* WIP

* Reverting some files

* reverting

* using type from model, instead of passing in separately

* reverting to remove query lite from search aggregator, as it is no longer necessary

* removing erroneous properties from legacy search results

* removed unnecessary parameters for type since it is included in the model and we have acces too that

* missed one

* removed empty space

* removed unneccessary code

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2021-01-08 14:16:01 -08:00
Deep Tailor
668bd75025 Revert "[Navigation Tree] Whole tree item clickable (#3632)" (#3635)
This reverts commit e6e07cf959.
2021-01-08 12:57:39 -08:00
Jamie V
e6e07cf959 [Navigation Tree] Whole tree item clickable (#3632)
* passing click and context click on tree item down to object label, making the whole tree item interactive

* removed unnecessary code
2021-01-08 11:55:43 -08:00
Jamie V
2f8431905f [Navigation Tree] Fixes initial load navigation request breaks if children haven't loaded in yet (#3630)
* changed logic so people cant navigate up tree til initial load has happened, thus ensuring ancestors have been populated for the first time
2021-01-08 09:38:09 -08:00
Nikhil
23aba14dfe [Notebook] Do not persist notebook objects in notebook Local Storage (#3295)
* [Notebook] Do not persist domain objects on notebooks or snapshots #3291
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2021-01-06 15:30:57 -08:00
Nikhil
b0fa955914 [openmct-YAMCS] TelemetryTableRow refactor createNormalizedDatum to include extra datum #3613 (#3614) 2021-01-06 11:38:45 -08:00
Deep Tailor
98207a3e0d Context click should add a class to tree item (#3619)
* add ability to pass onDestroy callback to menu api show

* fix lint issues

* remove fdescribe

* rename event and variables to contextClickActive

* rename variable to active

* Sanding and polishing CSS related to context-click visual feedback

- Changed CSS `border` approach to `box-shadow` to avoid jumping;
- Removed unwired code and CSS styles for Folder grid and list views;

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: charlesh88 <charlesh88@gmail.com>
2021-01-04 12:57:18 -08:00
Shefali Joshi
26b81345f2 Various Conditionals bug fixes (#3611)
* Don't apply styles on destroy as destroy should not have any side effects

* Don't show undefined if trigger for condition is not available yet

* Force recalculation of condition result if telemetry is removed

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2021-01-04 10:02:43 -08:00
Andrew Henry
ac2034b243 Adds a simple indicator to display FPS (#3624)
* Adds a simple indicator to display FPS

* ratchet up test coverage threshold
2020-12-28 19:57:36 -08:00
30 changed files with 597 additions and 97 deletions

View File

@@ -86,7 +86,7 @@ module.exports = (config) => {
reports: ['html', 'lcovonly', 'text-summary'],
thresholds: {
global: {
lines: 65
lines: 66
}
}
},

View File

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

View File

@@ -125,13 +125,12 @@ define([
* @param topic the topicService.
*/
GenericSearchProvider.prototype.indexOnMutation = function (topic) {
var mutationTopic = topic('mutation'),
provider = this;
let mutationTopic = topic('mutation');
mutationTopic.listen(function (mutatedObject) {
var editor = mutatedObject.getCapability('editor');
mutationTopic.listen(mutatedObject => {
let editor = mutatedObject.getCapability('editor');
if (!editor || !editor.inEditContext()) {
provider.index(
this.index(
mutatedObject.getId(),
mutatedObject.getModel()
);
@@ -262,6 +261,7 @@ define([
return {
id: hit.item.id,
model: hit.item.model,
type: hit.item.type,
score: hit.matchCount
};
});

View File

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

View File

@@ -38,7 +38,7 @@ class MenuAPI {
this._showObjectMenu = this._showObjectMenu.bind(this);
}
showMenu(x, y, actions) {
showMenu(x, y, actions, onDestroy) {
if (this.menuComponent) {
this.menuComponent.dismiss();
}
@@ -46,7 +46,8 @@ class MenuAPI {
let options = {
x,
y,
actions
actions,
onDestroy
};
this.menuComponent = new Menu(options);

View File

@@ -31,6 +31,7 @@ describe ('The Menu API', () => {
let x;
let y;
let result;
let onDestroy;
beforeEach(() => {
openmct = createOpenMct();
@@ -73,7 +74,9 @@ describe ('The Menu API', () => {
let vueComponent;
beforeEach(() => {
menuAPI.showMenu(x, y, actionsArray);
onDestroy = jasmine.createSpy('onDestroy');
menuAPI.showMenu(x, y, actionsArray, onDestroy);
vueComponent = menuAPI.menuComponent.component;
menuComponent = document.querySelector(".c-menu");
@@ -120,6 +123,12 @@ describe ('The Menu API', () => {
expect(vueComponent.$destroy).toHaveBeenCalled();
});
it("invokes the onDestroy callback if passed in", () => {
document.body.click();
expect(onDestroy).toHaveBeenCalled();
});
});
});
});

View File

@@ -75,7 +75,8 @@ export default class Condition extends EventEmitter {
return;
}
if (this.isTelemetryUsed(datum.id)) {
// if all the criteria in this condition have no telemetry, we want to force the condition result to evaluate
if (this.hasNoTelemetry() || this.isTelemetryUsed(datum.id)) {
this.criteria.forEach(criterion => {
if (this.isAnyOrAllTelemetry(criterion)) {
@@ -93,6 +94,12 @@ export default class Condition extends EventEmitter {
return (criterion.telemetry && (criterion.telemetry === 'all' || criterion.telemetry === 'any'));
}
hasNoTelemetry() {
return this.criteria.every((criterion) => {
return !this.isAnyOrAllTelemetry(criterion) && criterion.telemetry === '';
});
}
isTelemetryUsed(id) {
return this.criteria.some(criterion => {
return this.isAnyOrAllTelemetry(criterion) || criterion.telemetryObjectIdAsString === id;
@@ -250,10 +257,17 @@ export default class Condition extends EventEmitter {
}
getTriggerDescription() {
return {
conjunction: TRIGGER_CONJUNCTION[this.trigger],
prefix: `${TRIGGER_LABEL[this.trigger]}: `
};
if (this.trigger) {
return {
conjunction: TRIGGER_CONJUNCTION[this.trigger],
prefix: `${TRIGGER_LABEL[this.trigger]}: `
};
} else {
return {
conjunction: '',
prefix: ''
};
}
}
requestLADConditionResult() {

View File

@@ -79,6 +79,17 @@ export default class ConditionManager extends EventEmitter {
delete this.subscriptions[id];
delete this.telemetryObjects[id];
this.removeConditionTelemetryObjects();
//force re-computation of condition set result as we might be in a state where
// there is no telemetry datum coming in for a while or at all.
let latestTimestamp = getLatestTimestamp(
{},
{},
this.timeSystems,
this.openmct.time.timeSystem()
);
this.updateConditionResults({id: id});
this.updateCurrentCondition(latestTimestamp);
}
initialize() {
@@ -336,14 +347,17 @@ export default class ConditionManager extends EventEmitter {
let timestamp = {};
timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
this.updateConditionResults(normalizedDatum);
this.updateCurrentCondition(timestamp);
}
updateConditionResults(normalizedDatum) {
//We want to stop when the first condition evaluates to true.
this.conditions.some((condition) => {
condition.updateResult(normalizedDatum);
return condition.result === true;
});
this.updateCurrentCondition(timestamp);
}
updateCurrentCondition(timestamp) {

View File

@@ -86,6 +86,7 @@ export default class StyleRuleManager extends EventEmitter {
updateObjectStyleConfig(styleConfiguration) {
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
this.initialize(styleConfiguration || {});
this.applyStaticStyle();
this.destroy();
} else {
let isNewConditionSet = !this.conditionSetIdentifier
@@ -158,7 +159,6 @@ export default class StyleRuleManager extends EventEmitter {
}
destroy() {
this.applyStaticStyle();
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;

View File

@@ -31,7 +31,6 @@
v-model="expanded"
class="c-tree__item__view-control"
:enabled="hasChildren"
:propagate="false"
/>
<div class="c-tree__item__label c-object-label">
<div

View File

@@ -1,7 +1,9 @@
<template>
<tr
class="c-list-item"
:class="{ 'is-alias': item.isAlias === true }"
:class="{
'is-alias': item.isAlias === true
}"
@click="navigate"
>
<td class="c-list-item__name">

View File

@@ -1,7 +1,7 @@
import ImageryViewLayout from './components/ImageryViewLayout.vue';
import Vue from 'vue';
export default function ImageryViewProvider(openmct) {
export default function ImageryViewProvider(openmct, configuration) {
const type = 'example.imagery';
function hasImageTelemetry(domainObject) {
@@ -32,7 +32,8 @@ export default function ImageryViewProvider(openmct) {
},
provide: {
openmct,
domainObject
domainObject,
configuration
},
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
});

View File

@@ -61,11 +61,25 @@
<div class="c-imagery__control-bar">
<div class="c-imagery__time">
<div class="c-imagery__timestamp u-style-receiver js-style-receiver">{{ time }}</div>
<!-- Imagery Age Freshness -->
<div
v-if="canTrackDuration"
:class="{'c-imagery--new': isImageNew && !refreshCSS}"
class="c-imagery__age icon-timer"
>{{ formattedDuration }}</div>
<!-- Rover Position Freshness -->
<div>isFresh {{ roverPositionIsFresh }}</div>
<div
v-if="roverPositionIsFresh !== undefined"
:class="{'c-imagery--new': roverPositionIsFresh}"
class="c-imagery__age icon-timer"
>{{ roverPositionIsFresh ? 'ROVER' : '' }}</div>
<!-- Rover Camera Position Freshness -->
<div
v-if="cameraPositionIsFresh !== undefined"
:class="{'c-imagery--new': cameraPositionIsFresh && !refreshCSS}"
class="c-imagery__age icon-timer"
>{{ formattedDuration }}</div>
</div>
<div class="h-local-controls">
<button
@@ -116,7 +130,7 @@ const ARROW_RIGHT = 39;
const ARROW_LEFT = 37;
export default {
inject: ['openmct', 'domainObject'],
inject: ['openmct', 'domainObject', 'configuration'],
data() {
let timeSystem = this.openmct.time.timeSystem();
@@ -137,7 +151,14 @@ export default {
refreshCSS: false,
keyString: undefined,
focusedImageIndex: undefined,
numericDuration: undefined
numericDuration: undefined,
metadataEndpoints: {},
roverPositionIsFresh: undefined,
cameraPositionIsFresh: undefined,
roverPositionState: {},
cameraPositionState: {},
canTrackRoverPositionFreshness: undefined,
canTrackCameraPositionFreshness: undefined
};
},
computed: {
@@ -201,9 +222,11 @@ export default {
focusedImageIndex() {
this.trackDuration();
this.resetAgeCSS();
this.determineRoverFreshness();
this.determineCameraPositionFreshness();
}
},
mounted() {
async mounted() {
// listen
this.openmct.time.on('bounds', this.boundsChange);
this.openmct.time.on('timeSystem', this.timeSystemChange);
@@ -212,16 +235,34 @@ export default {
// set
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.imageHints = this.metadata.valuesForHints(['image'])[0];
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.metadata.valuesForHints(['image'])[0]);
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
// DELETE WHEN DONE
this.temporaryForImageEnhancements();
// initialize
this.timeKey = this.timeSystem.key;
this.timeFormatter = this.getFormatter(this.timeKey);
this.roverPositionKeys = ['Rover Heading', 'Rover Roll', 'Rover Pitch', 'Rover Yaw'];
this.cameraPositionKeys = ['Camera Tilt', 'Camera Pan'];
// kickoff
this.subscribe();
this.requestHistory();
await this.initializeRelatedTelemetry();
// DELETE
// examples for requesting historical data for a relatedTelemetry key
// and for subscribing to a relatedTelemetry key
// example for 'Rover Heading'
// ***********************************************************************
// let results = await this.relatedTelemetry['Rover Heading'].request();
// this.relatedTelemetry['Rover Heading'].subscribe((data) => {
// console.log('subscribe test', data);
// });
// ***********************************************************************
},
updated() {
this.scrollToRight();
@@ -236,8 +277,148 @@ export default {
this.openmct.time.off('bounds', this.boundsChange);
this.openmct.time.off('timeSystem', this.timeSystemChange);
this.openmct.time.off('clock', this.clockChange);
// unsubscribe from related telemetry
if (this.hasRelatedTelemetry) {
for (let key of this.relatedTelemetry.keys) {
if (this.relatedTelemetry[key].unsubscribe) {
this.relatedTelemetry[key].unsubscribe();
}
}
}
},
methods: {
// for local dev, to be DELETED
temporaryForImageEnhancements() {
let roverKeys = ['Rover Heading', 'Rover Roll', 'Rover Yaw', 'Rover Pitch'];
let cameraKeys = ['Camera Pan', 'Camera Tilt'];
this.searchService = this.openmct.$injector.get('searchService');
this.temporaryDev = true;
// mock related telemetry metadata
this.imageHints.relatedTelemetry = {};
// populate temp keys in imageHints for local testing
[...roverKeys, ...cameraKeys].forEach(key => {
this.imageHints.relatedTelemetry[key] = {
dev: true,
realtime: key,
historical: key,
devInit: async () => {
const searchResults = await this.searchService.query(key);
const endpoint = searchResults.hits[0].id;
const domainObject = await this.openmct.objects.get(endpoint);
return domainObject;
}
};
});
},
async initializeRelatedTelemetry() {
if (this.imageHints.relatedTelemetry === undefined) {
this.hasRelatedTelemetry = false;
return;
}
// DELETE
if (this.temporaryDev) {
let searchIndexBuildDelay = new Promise((resolve, reject) => {
setTimeout(resolve, 3000);
});
await searchIndexBuildDelay;
}
let keys = Object.keys(this.imageHints.relatedTelemetry);
this.hasRelatedTelemetry = true;
this.relatedTelemetry = {
keys,
...this.imageHints.relatedTelemetry
};
// grab historical and subscribe to realtime
for (let key of keys) {
let historicalId = this.relatedTelemetry[key].historical;
let realtimeId = this.relatedTelemetry[key].realtime;
let sameId = false;
if (historicalId && realtimeId && historicalId === realtimeId) {
// DELETE temp
if (this.relatedTelemetry[key].dev) {
this.relatedTelemetry[key].historicalDomainObject = await this.relatedTelemetry[key].devInit();
delete this.relatedTelemetry[key].dev;
delete this.relatedTelemetry[key].devInit;
} else {
this.relatedTelemetry[key].historicalDomainObject = await this.openmct.objects.get(historicalId);
}
this.relatedTelemetry[key].realtimeDomainObject = this.relatedTelemetry[key].historicalDomainObject;
sameId = true;
}
if (historicalId) {
// check for on-telemetry data
if (historicalId[0] === '.') {
this.relatedTelemetry[key].valueOnTelemetry = true;
}
if (!sameId) {
this.relatedTelemetry[key].historicalDomainObject = await this.openmct.objects.get(historicalId);
}
this.relatedTelemetry[key].request = async (options) => {
let results = await this.openmct.telemetry
.request(this.relatedTelemetry[key].historicalDomainObject, options);
return results;
};
}
if (realtimeId) {
// set up listeners
this.relatedTelemetry[key].listeners = [];
this.relatedTelemetry[key].subscribe = (callback) => {
if (!this.relatedTelemetry[key].listeners.includes(callback)) {
this.relatedTelemetry[key].listeners.push(callback);
return () => {
this.relatedTelemetry[key].listeners.remove(callback);
};
} else {
return () => {};
}
};
if (!sameId) {
this.relatedTelemetry[key].realtimeDomainObject = await this.openmct.objects.get(realtimeId);
}
this.relatedTelemetry[key].unsubscribe = this.openmct.telemetry.subscribe(
this.relatedTelemetry[key].realtimeDomainObject,
datum => {
this.relatedTelemetry[key].latest = datum;
this.relatedTelemetry[key].listeners.forEach(callback => {
callback(datum);
});
}
);
}
}
},
async requestRelatedTelemetry(key) {
if (this.relatedTelemetry[key] && this.relatedTelemetry[key].historicalDomainObject) {
let data = await this.openmct.telemetry.request(this.relatedTelemetry[key].historicalDomainObject);
return data;
}
},
focusElement() {
this.$el.focus();
},
@@ -405,6 +586,83 @@ export default {
return valueFormatter;
},
roverPositionTrackingApplicable() {
let canTrack = false; // possibly false first, depending on if we need all or one
for (const key of this.roverPositionKeys) {
// do we need ALL to be available or will one work?
if (this.metadata.value(key)) {
canTrack = true;
}
}
// Force true, will remove
if (this.temporaryDev) {
return true;
}
return canTrack;
},
async determineRoverFreshness() {
if (this.canTrackRoverPositionFreshness === false) {
return;
}
let noChanges = true;
// set up state tracking
for (const key of this.roverPositionKeys) {
try {
let currentState = await this.getImageMetadataValue(key, this.focusedImage[this.timeKey]);
currentState = currentState.toFixed(1);
if (currentState !== this.roverPositionState[key]) {
this.roverPositionState[key] = currentState;
this.roverPositionIsFresh = false;
noChanges = false;
}
} catch (err) {
this.roverPositionIsFresh = undefined;
}
}
if (noChanges) {
this.roverPositionIsFresh = true;
}
console.log('roverPosition is fresh', this.roverPositionIsFresh);
},
trackRoverPosition() {
this.cancelRoverTrackingInterval = window.setInterval(this.determineRoverFreshness, this.roverTrackingInterval);
},
cameraPositionTrackingApplicable() {
let canTrack = false; // possibly false first, depending on if we need all or one
for (const key of this.cameraPositionKeys) {
// do we need ALL to be available or will one work?
if (this.metadata.value(key)) {
canTrack = true;
}
}
// will remove
if (this.temporaryDev) {
return true;
}
return canTrack;
},
determineCameraPositionFreshness() {
if (this.canTrackCameraPositionFreshness === false) {
return;
}
this.cameraPositionIsFresh = undefined;
},
trackCameraPosition() {
this.cancelCameraTrackingInterval = window.setInterval(this.determineCameraFreshness, this.cameraTrackingInterval);
},
trackDuration() {
if (this.canTrackDuration) {
this.stopDurationTracking();

View File

@@ -1,8 +1,8 @@
import ImageryViewProvider from './ImageryViewProvider';
export default function () {
export default function (configuration) {
return function install(openmct) {
openmct.objectViews.addProvider(new ImageryViewProvider(openmct));
openmct.objectViews.addProvider(new ImageryViewProvider(openmct, configuration));
};
}

View File

@@ -225,14 +225,12 @@ export default {
},
createNotebookStorageObject() {
const notebookMeta = {
name: this.internalDomainObject.name,
identifier: this.internalDomainObject.identifier
};
const page = this.getSelectedPage();
const section = this.getSelectedSection();
return {
domainObject: this.internalDomainObject,
notebookMeta,
section,
page
@@ -442,10 +440,10 @@ export default {
async updateDefaultNotebook(notebookStorage) {
const defaultNotebookObject = await this.getDefaultNotebookObject();
if (!defaultNotebookObject) {
setDefaultNotebook(this.openmct, notebookStorage);
setDefaultNotebook(this.openmct, notebookStorage, this.internalDomainObject);
} else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
this.removeDefaultClass(defaultNotebookObject);
setDefaultNotebook(this.openmct, notebookStorage);
setDefaultNotebook(this.openmct, notebookStorage, this.internalDomainObject);
}
if (this.defaultSectionId && this.defaultSectionId.length === 0 || this.defaultSectionId !== notebookStorage.section.id) {

View File

@@ -44,38 +44,46 @@ export default {
},
data() {
return {
notebookSnapshot: null,
notebookSnapshot: undefined,
notebookTypes: []
};
},
mounted() {
validateNotebookStorageObject();
this.getDefaultNotebookObject();
this.notebookSnapshot = new Snapshot(this.openmct);
this.setDefaultNotebookStatus();
},
methods: {
showMenu(event) {
const notebookTypes = [];
async getDefaultNotebookObject() {
const defaultNotebook = getDefaultNotebook();
const defaultNotebookObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
return defaultNotebookObject;
},
async showMenu(event) {
const notebookTypes = [];
const elementBoundingClientRect = this.$el.getBoundingClientRect();
const x = elementBoundingClientRect.x;
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
if (defaultNotebook) {
const domainObject = defaultNotebook.domainObject;
const defaultNotebookObject = await this.getDefaultNotebookObject();
if (defaultNotebookObject) {
const name = defaultNotebookObject.name;
if (domainObject.location) {
const defaultPath = `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
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}`,
callBack: () => {
return this.snapshot(NOTEBOOK_DEFAULT);
}
});
}
notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
callBack: () => {
return this.snapshot(NOTEBOOK_DEFAULT);
}
});
}
notebookTypes.push({

View File

@@ -23,13 +23,6 @@ import * as NotebookEntries from './notebook-entries';
import { createOpenMct, resetApplicationState } from 'utils/testing';
const notebookStorage = {
domainObject: {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
}
},
notebookMeta: {
name: 'notebook',
identifier: {

View File

@@ -1,13 +1,12 @@
import objectUtils from 'objectUtils';
const NOTEBOOK_LOCAL_STORAGE = 'notebook-storage';
let currentNotebookObject = null;
let currentNotebookObjectIdentifier = null;
let unlisten = null;
function defaultNotebookObjectChanged(newDomainObject) {
if (newDomainObject.location !== null) {
currentNotebookObject = newDomainObject;
const notebookStorage = getDefaultNotebook();
notebookStorage.domainObject = newDomainObject;
saveDefaultNotebook(notebookStorage);
currentNotebookObjectIdentifier = newDomainObject.identifier;
return;
}
@@ -20,10 +19,9 @@ function defaultNotebookObjectChanged(newDomainObject) {
clearDefaultNotebook();
}
function observeDefaultNotebookObject(openmct, notebookStorage) {
const domainObject = notebookStorage.domainObject;
if (currentNotebookObject
&& currentNotebookObject.identifier.key === domainObject.identifier.key) {
function observeDefaultNotebookObject(openmct, notebookMeta, domainObject) {
if (currentNotebookObjectIdentifier
&& objectUtils.makeKeyString(currentNotebookObjectIdentifier) === objectUtils.makeKeyString(notebookMeta.identifier)) {
return;
}
@@ -32,7 +30,7 @@ function observeDefaultNotebookObject(openmct, notebookStorage) {
unlisten = null;
}
unlisten = openmct.objects.observe(notebookStorage.domainObject, '*', defaultNotebookObjectChanged);
unlisten = openmct.objects.observe(domainObject, '*', defaultNotebookObjectChanged);
}
function saveDefaultNotebook(notebookStorage) {
@@ -40,7 +38,7 @@ function saveDefaultNotebook(notebookStorage) {
}
export function clearDefaultNotebook() {
currentNotebookObject = null;
currentNotebookObjectIdentifier = null;
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, null);
}
@@ -50,8 +48,8 @@ export function getDefaultNotebook() {
return JSON.parse(notebookStorage);
}
export function setDefaultNotebook(openmct, notebookStorage) {
observeDefaultNotebookObject(openmct, notebookStorage);
export function setDefaultNotebook(openmct, notebookStorage, domainObject) {
observeDefaultNotebookObject(openmct, notebookStorage.notebookMeta, domainObject);
saveDefaultNotebook(notebookStorage);
}

View File

@@ -23,14 +23,15 @@
import * as NotebookStorage from './notebook-storage';
import { createOpenMct, resetApplicationState } from 'utils/testing';
const domainObject = {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
}
};
const notebookStorage = {
domainObject: {
name: 'notebook',
identifier: {
namespace: '',
key: 'test-notebook'
}
},
notebookMeta: {
name: 'notebook',
identifier: {
@@ -82,7 +83,7 @@ describe('Notebook Storage:', () => {
});
it('has correct notebookstorage on setDefaultNotebook', () => {
NotebookStorage.setDefaultNotebook(openmct, notebookStorage);
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
const defaultNotebook = NotebookStorage.getDefaultNotebook();
expect(JSON.stringify(defaultNotebook)).toBe(JSON.stringify(notebookStorage));
@@ -98,7 +99,7 @@ describe('Notebook Storage:', () => {
sectionTitle: 'Section'
};
NotebookStorage.setDefaultNotebook(openmct, notebookStorage);
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
NotebookStorage.setDefaultNotebookSection(section);
const defaultNotebook = NotebookStorage.getDefaultNotebook();
@@ -115,7 +116,7 @@ describe('Notebook Storage:', () => {
pageTitle: 'Page'
};
NotebookStorage.setDefaultNotebook(openmct, notebookStorage);
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
NotebookStorage.setDefaultNotebookPage(page);
const defaultNotebook = NotebookStorage.getDefaultNotebook();

View File

@@ -0,0 +1,9 @@
# URL Indicator
Adds an indicator which shows the number of frames that the browser is able to render per second. This is a useful proxy for the maximum number of updates that any telemetry display can perform per second. This may be useful during performance testing, but probably should not be enabled by default.
This indicator adds adds about 3% points to CPU usage in the Chrome task manager.
## Installation
```js
openmct.install(openmct.plugins.PerformanceIndicator());
```

View File

@@ -0,0 +1,62 @@
/*****************************************************************************
* 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.
*****************************************************************************/
export default function PerformanceIndicator() {
return function install(openmct) {
let frames = 0;
let lastCalculated = performance.now();
const indicator = openmct.indicators.simpleIndicator();
indicator.text('~ fps');
indicator.statusClass('s-status-info');
openmct.indicators.add(indicator);
let rafHandle = requestAnimationFrame(incremementFrames);
openmct.on('destroy', () => {
cancelAnimationFrame(rafHandle);
});
function incremementFrames() {
let now = performance.now();
if ((now - lastCalculated) < 1000) {
frames++;
} else {
updateFPS(frames);
lastCalculated = now;
frames = 1;
}
rafHandle = requestAnimationFrame(incremementFrames);
}
function updateFPS(fps) {
indicator.text(`${fps} fps`);
if (fps >= 40) {
indicator.statusClass('s-status-on');
} else if (fps < 40 && fps >= 20) {
indicator.statusClass('s-status-warning');
} else {
indicator.statusClass('s-status-error');
}
}
};
}

View File

@@ -0,0 +1,87 @@
/*****************************************************************************
* 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 PerformancePlugin from './plugin.js';
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
describe('the plugin', () => {
let openmct;
let element;
let child;
let performanceIndicator;
let countFramesPromise;
beforeEach((done) => {
openmct = createOpenMct(false);
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct.install(new PerformancePlugin());
countFramesPromise = countFrames();
openmct.on('start', done);
performanceIndicator = openmct.indicators.indicatorObjects.find((indicator) => {
return indicator.text && indicator.text() === '~ fps';
});
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('installs the performance indicator', () => {
expect(performanceIndicator).toBeDefined();
});
it('correctly calculates fps', () => {
return countFramesPromise.then((frames) => {
expect(performanceIndicator.text()).toEqual(`${frames} fps`);
});
});
function countFrames() {
let startTime = performance.now();
let frames = 0;
return new Promise((resolve) => {
requestAnimationFrame(function incrementCount() {
let now = performance.now();
if ((now - startTime) < 1000) {
frames++;
requestAnimationFrame(incrementCount);
} else {
resolve(frames);
}
});
});
}
});

View File

@@ -60,7 +60,8 @@ define([
'./defaultRootName/plugin',
'./timeline/plugin',
'./viewDatumAction/plugin',
'./interceptors/plugin'
'./interceptors/plugin',
'./performanceIndicator/plugin'
], function (
_,
UTCTimeSystem,
@@ -101,7 +102,8 @@ define([
DefaultRootName,
Timeline,
ViewDatumAction,
ObjectInterceptors
ObjectInterceptors,
PerformanceIndicator
) {
const bundleMap = {
LocalStorage: 'platform/persistence/local',
@@ -197,6 +199,7 @@ define([
plugins.Timeline = Timeline.default;
plugins.ViewDatumAction = ViewDatumAction.default;
plugins.ObjectInterceptors = ObjectInterceptors.default;
plugins.PerformanceIndicator = PerformanceIndicator.default;
return plugins;
});

View File

@@ -100,11 +100,13 @@ define([], function () {
* @param {*} metadataValues
*/
function createNormalizedDatum(datum, columns) {
return Object.values(columns).reduce((normalizedDatum, column) => {
normalizedDatum[column.getKey()] = column.getRawValue(datum);
const normalizedDatum = JSON.parse(JSON.stringify(datum));
return normalizedDatum;
}, {});
Object.values(columns).forEach(column => {
normalizedDatum[column.getKey()] = column.getRawValue(datum);
});
return normalizedDatum;
}
return TelemetryTableRow;

View File

@@ -22,10 +22,6 @@ export default {
type: Boolean,
default: false
},
propagate: {
type: Boolean,
default: true
},
controlClass: {
type: String,
default: 'c-disclosure-triangle'
@@ -33,10 +29,8 @@ export default {
},
methods: {
handleClick(event) {
event.stopPropagation();
this.$emit('input', !this.value);
if (!this.propagate) {
event.stopPropagation();
}
}
}
};

View File

@@ -95,6 +95,10 @@
color: $colorItemTreeSelectedFg;
}
}
&.is-context-clicked {
box-shadow: inset $colorItemTreeSelectedBg 0 0 0 1px;
}
}
}

View File

@@ -65,7 +65,7 @@
v-for="(ancestor, index) in focusedAncestors"
:key="ancestor.id"
:node="ancestor"
:show-up="index < focusedAncestors.length - 1"
:show-up="index < focusedAncestors.length - 1 && initialLoad"
:show-down="false"
:left-offset="index * 10 + 'px'"
@resetTree="beginNavigationRequest('handleReset', ancestor)"
@@ -156,6 +156,7 @@ export default {
data() {
return {
root: undefined,
initialLoad: false,
isLoading: false,
mainTreeHeight: undefined,
searchLoading: false,
@@ -499,6 +500,11 @@ export default {
this.ancestors = ancestors;
this.childItems = children;
// track when FIRST full load of tree happens
if (!this.initialLoad) {
this.initialLoad = true;
}
// any new items added or removed handled here
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);

View File

@@ -1,6 +1,5 @@
<template>
<div
ref="me"
:style="{
'top': virtualScroll ? itemTop : 'auto',
'position': virtualScroll ? 'absolute' : 'relative'
@@ -11,10 +10,14 @@
class="c-tree__item"
:class="{
'is-alias': isAlias,
'is-navigated-object': navigated
'is-navigated-object': navigated,
'is-context-clicked': contextClickActive
}"
@click.capture="handleClick"
@contextmenu.capture="handleContextMenu"
>
<view-control
ref="navUp"
v-model="expanded"
class="c-tree__item__view-control"
:control-class="'c-nav__up'"
@@ -22,12 +25,15 @@
@input="resetTreeHere"
/>
<object-label
ref="objectLabel"
:domain-object="node.object"
:object-path="node.objectPath"
:navigate-to-path="navigationPath"
:style="{ paddingLeft: leftOffset }"
@context-click-active="setContextClickActive"
/>
<view-control
ref="navDown"
v-model="expanded"
class="c-tree__item__view-control"
:control-class="'c-nav__down'"
@@ -91,7 +97,8 @@ export default {
return {
hasComposition: false,
navigated: this.isNavigated(),
expanded: false
expanded: false,
contextClickActive: false
};
},
computed: {
@@ -134,6 +141,20 @@ export default {
this.openmct.router.off('change:path', this.highlightIfNavigated);
},
methods: {
handleClick(event) {
// skip for navigation, let viewControl handle click
if ([this.$refs.navUp.$el, this.$refs.navDown.$el].includes(event.target)) {
return;
}
event.stopPropagation();
this.$refs.objectLabel.$el.click();
},
handleContextMenu(event) {
event.stopPropagation();
this.$refs.objectLabel.showContextMenu(event);
},
isNavigated() {
return this.navigationPath === this.openmct.router.currentLocation.path;
},
@@ -142,6 +163,9 @@ export default {
},
resetTreeHere() {
this.$emit('resetTree', this.node);
},
setContextClickActive(active) {
this.contextClickActive = active;
}
}
};

View File

@@ -8,6 +8,11 @@ export default {
}
}
},
data() {
return {
contextClickActive: false
};
},
mounted() {
//TODO: touch support
this.$el.addEventListener('contextmenu', this.showContextMenu);
@@ -35,7 +40,13 @@ export default {
let actions = actionsCollection.getVisibleActions();
let sortedActions = this.openmct.actions._groupAndSortActions(actions);
this.openmct.menus.showMenu(event.clientX, event.clientY, sortedActions);
this.openmct.menus.showMenu(event.clientX, event.clientY, sortedActions, this.onContextMenuDestroyed);
this.contextClickActive = true;
this.$emit('context-click-active', true);
},
onContextMenuDestroyed() {
this.contextClickActive = false;
this.$emit('context-click-active', false);
}
}
};

View File

@@ -5,7 +5,7 @@
class="l-browse-bar__object-name--w c-object-label"
>
<div class="c-object-label__type-icon"
:class="type.cssClass"
:class="type.definition.cssClass"
></div>
<span class="l-browse-bar__object-name c-object-label__name">
{{ domainObject.name }}