Compare commits

..

2 Commits

Author SHA1 Message Date
2243fbb40b change scroll to auto 2020-12-07 15:51:38 -08:00
4a3dce9253 fixes scroll issue and removed redundant calls to backend 2020-12-07 15:37:55 -08:00
44 changed files with 132 additions and 941 deletions

View File

@ -182,7 +182,7 @@ The following guidelines are provided for anyone contributing source code to the
1. Avoid the use of "magic" values.
eg.
```JavaScript
const UNAUTHORIZED = 401;
Const UNAUTHORIZED = 401
if (responseCode === UNAUTHORIZED)
```
is preferable to

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "1.5.0-SNAPSHOT",
"version": "1.4.1-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {},
"devDependencies": {

View File

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

View File

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

View File

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

View File

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

View File

@ -31,7 +31,6 @@ describe ('The Menu API', () => {
let x;
let y;
let result;
let onDestroy;
beforeEach(() => {
openmct = createOpenMct();
@ -74,9 +73,7 @@ describe ('The Menu API', () => {
let vueComponent;
beforeEach(() => {
onDestroy = jasmine.createSpy('onDestroy');
menuAPI.showMenu(x, y, actionsArray, onDestroy);
menuAPI.showMenu(x, y, actionsArray);
vueComponent = menuAPI.menuComponent.component;
menuComponent = document.querySelector(".c-menu");
@ -123,12 +120,6 @@ 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,8 +75,7 @@ export default class Condition extends EventEmitter {
return;
}
// 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)) {
if (this.isTelemetryUsed(datum.id)) {
this.criteria.forEach(criterion => {
if (this.isAnyOrAllTelemetry(criterion)) {
@ -94,12 +93,6 @@ 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;
@ -257,17 +250,10 @@ export default class Condition extends EventEmitter {
}
getTriggerDescription() {
if (this.trigger) {
return {
conjunction: TRIGGER_CONJUNCTION[this.trigger],
prefix: `${TRIGGER_LABEL[this.trigger]}: `
};
} else {
return {
conjunction: '',
prefix: ''
};
}
return {
conjunction: TRIGGER_CONJUNCTION[this.trigger],
prefix: `${TRIGGER_LABEL[this.trigger]}: `
};
}
requestLADConditionResult() {

View File

@ -79,17 +79,6 @@ 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() {
@ -347,17 +336,14 @@ 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,7 +86,6 @@ export default class StyleRuleManager extends EventEmitter {
updateObjectStyleConfig(styleConfiguration) {
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
this.initialize(styleConfiguration || {});
this.applyStaticStyle();
this.destroy();
} else {
let isNewConditionSet = !this.conditionSetIdentifier
@ -159,6 +158,7 @@ export default class StyleRuleManager extends EventEmitter {
}
destroy() {
this.applyStaticStyle();
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;

View File

@ -22,7 +22,6 @@
import { createOpenMct, resetApplicationState } from "utils/testing";
import ConditionPlugin from "./plugin";
import stylesManager from '@/ui/inspector/styles/StylesManager';
import StylesView from "./components/inspector/StylesView.vue";
import Vue from 'vue';
import {getApplicableStylesForItem} from "./utils/styleUtils";
@ -403,8 +402,7 @@ describe('the plugin', function () {
component = new Vue({
provide: {
openmct: openmct,
selection: selection,
stylesManager
selection: selection
},
el: viewContainer,
components: {

View File

@ -233,9 +233,8 @@ export default {
formattedValueForCopy() {
const timeFormatterKey = this.openmct.time.timeSystem().key;
const timeFormatter = this.formats[timeFormatterKey];
const unit = this.unit ? ` ${this.unit}` : '';
return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue}${unit}`;
return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue} ${this.unit}`;
},
requestHistoricalData() {
let bounds = this.openmct.time.bounds();

View File

@ -79,9 +79,8 @@ export default class DuplicateTask {
*/
async buildDuplicationPlan() {
let domainObjectClone = await this.duplicateObject(this.domainObject);
if (domainObjectClone !== this.domainObject) {
domainObjectClone.location = this.getKeyString(this.parent);
domainObjectClone.location = this.getId(this.parent);
}
this.firstClone = domainObjectClone;
@ -160,6 +159,36 @@ export default class DuplicateTask {
return originalObject;
}
/**
* Update identifiers in a cloned object model (or part of
* a cloned object model) to reflect new identifiers after
* duplicating.
* @private
*/
rewriteIdentifiers(obj, idMap) {
function lookupValue(value) {
return (typeof value === 'string' && idMap[value]) || value;
}
if (Array.isArray(obj)) {
obj.forEach((value, index) => {
obj[index] = lookupValue(value);
this.rewriteIdentifiers(obj[index], idMap);
});
} else if (obj && typeof obj === 'object') {
Object.keys(obj).forEach((key) => {
let value = obj[key];
obj[key] = lookupValue(value);
if (idMap[key]) {
delete obj[key];
obj[idMap[key]] = value;
}
this.rewriteIdentifiers(value, idMap);
});
}
}
/**
* Given an array of objects composed by a parent, clone them, then
* add them to the parent.
@ -167,67 +196,31 @@ export default class DuplicateTask {
* @returns {*}
*/
async duplicateComposees(clonedParent, composees = []) {
let idMappings = [];
let idMap = {};
let allComposeesDuplicated = composees.reduce(async (previousPromise, nextComposee) => {
await previousPromise;
let clonedComposee = await this.duplicateObject(nextComposee);
if (clonedComposee) {
idMappings.push({
newId: clonedComposee.identifier,
oldId: nextComposee.identifier
});
this.composeChild(clonedComposee, clonedParent, clonedComposee !== nextComposee);
}
idMap[this.getId(nextComposee)] = this.getId(clonedComposee);
this.composeChild(clonedComposee, clonedParent, clonedComposee !== nextComposee);
return;
}, Promise.resolve());
await allComposeesDuplicated;
clonedParent = this.rewriteIdentifiers(clonedParent, idMappings);
this.rewriteIdentifiers(clonedParent, idMap);
this.clones.push(clonedParent);
return clonedParent;
}
/**
* Update identifiers in a cloned object model (or part of
* a cloned object model) to reflect new identifiers after
* duplicating.
* @private
*/
rewriteIdentifiers(clonedParent, childIdMappings) {
for (let { newId, oldId } of childIdMappings) {
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
// regex replace keystrings
clonedParent = JSON.stringify(clonedParent).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
// parse reviver to replace identifiers
clonedParent = JSON.parse(clonedParent, (key, value) => {
if (Object.prototype.hasOwnProperty.call(value, 'key')
&& Object.prototype.hasOwnProperty.call(value, 'namespace')
&& value.key === oldId.key
&& value.namespace === oldId.namespace) {
return newId;
} else {
return value;
}
});
}
return clonedParent;
}
composeChild(child, parent, setLocation) {
parent.composition.push(child.identifier);
let childKeyString = this.openmct.objects.makeKeyString(child.identifier);
parent.composition.push(childKeyString);
//If a location is not specified, set it.
if (setLocation && child.location === undefined) {
let parentKeyString = this.getKeyString(parent);
let parentKeyString = this.getId(parent);
child.location = parentKeyString;
}
}
@ -263,7 +256,7 @@ export default class DuplicateTask {
return clone;
}
getKeyString(domainObject) {
getId(domainObject) {
return this.openmct.objects.makeKeyString(domainObject.identifier);
}

View File

@ -1,9 +1,7 @@
<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

@ -36,7 +36,7 @@
<div class="c-imagery__main-image__bg"
:class="{'paused unnsynced': isPaused,'stale':false }"
>
<div class="c-imagery__main-image__image js-imageryView-image"
<div class="c-imagery__main-image__image"
:style="{
'background-image': imageUrl ? `url(${imageUrl})` : 'none',
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`

View File

@ -1,306 +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 ImageryPlugin from './plugin.js';
import Vue from 'vue';
import {
createOpenMct,
resetApplicationState,
simulateKeyEvent
} from 'utils/testing';
const ONE_MINUTE = 1000 * 60;
const TEN_MINUTES = ONE_MINUTE * 10;
const MAIN_IMAGE_CLASS = '.js-imageryView-image';
const NEW_IMAGE_CLASS = '.c-imagery__age.c-imagery--new';
const REFRESH_CSS_MS = 500;
function getImageInfo(doc) {
let imageElement = doc.querySelectorAll(MAIN_IMAGE_CLASS)[0];
let timestamp = imageElement.dataset.openmctImageTimestamp;
let identifier = imageElement.dataset.openmctObjectKeystring;
let url = imageElement.style.backgroundImage;
return {
timestamp,
identifier,
url
};
}
function isNew(doc) {
let newIcon = doc.querySelectorAll(NEW_IMAGE_CLASS);
return newIcon.length !== 0;
}
function generateTelemetry(start, count) {
let telemetry = [];
for (let i = 1, l = count + 1; i < l; i++) {
let stringRep = i + 'minute';
let logo = 'images/logo-openmct.svg';
telemetry.push({
"name": stringRep + " Imagery",
"utc": start + (i * ONE_MINUTE),
"url": location.host + '/' + logo + '?time=' + stringRep,
"timeId": stringRep
});
}
return telemetry;
}
describe("The Imagery View Layout", () => {
const imageryKey = 'example.imagery';
const START = Date.now();
const COUNT = 10;
let openmct;
let imageryPlugin;
let parent;
let child;
let timeFormat = 'utc';
let bounds = {
start: START - TEN_MINUTES,
end: START
};
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
let imageryObject = {
identifier: {
namespace: "",
key: "imageryId"
},
name: "Example Imagery",
type: "example.imagery",
location: "parentId",
modified: 0,
persisted: 0,
telemetry: {
values: [
{
"name": "Image",
"key": "url",
"format": "image",
"hints": {
"image": 1,
"priority": 3
},
"source": "url"
},
{
"name": "Name",
"key": "name",
"source": "name",
"hints": {
"priority": 0
}
},
{
"name": "Time",
"key": "utc",
"format": "utc",
"hints": {
"domain": 2,
"priority": 1
},
"source": "utc"
},
{
"name": "Local Time",
"key": "local",
"format": "local-format",
"hints": {
"domain": 1,
"priority": 2
},
"source": "local"
}
]
}
};
// this setups up the app
beforeEach((done) => {
const appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
parent = document.createElement('div');
child = document.createElement('div');
parent.appendChild(child);
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
imageryPlugin = new ImageryPlugin();
openmct.install(imageryPlugin);
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
openmct.time.timeSystem(timeFormat, {
start: 0,
end: 4
});
openmct.on('start', done);
openmct.startHeadless(appHolder);
});
afterEach(() => {
return resetApplicationState(openmct);
});
it("should provide an imagery view only for imagery producing objects", () => {
let applicableViews = openmct.objectViews.get(imageryObject);
let imageryView = applicableViews.find(
viewProvider => viewProvider.key === imageryKey
);
expect(imageryView).toBeDefined();
});
describe("imagery view", () => {
let applicableViews;
let imageryViewProvider;
let imageryView;
beforeEach(async (done) => {
let telemetryRequestResolve;
let telemetryRequestPromise = new Promise((resolve) => {
telemetryRequestResolve = resolve;
});
openmct.telemetry.request.and.callFake(() => {
telemetryRequestResolve(imageTelemetry);
return telemetryRequestPromise;
});
openmct.time.clock('local', {
start: bounds.start,
end: bounds.end + 100
});
applicableViews = openmct.objectViews.get(imageryObject);
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
imageryView = imageryViewProvider.view(imageryObject);
imageryView.show(child);
await telemetryRequestPromise;
await Vue.nextTick();
return done();
});
it("on mount should show the the most recent image", () => {
const imageInfo = getImageInfo(parent);
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
});
it("should show the clicked thumbnail as the main image", async () => {
const target = imageTelemetry[5].url;
parent.querySelectorAll(`img[src='${target}']`)[0].click();
await Vue.nextTick();
const imageInfo = getImageInfo(parent);
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
});
it("should show that an image is new", async (done) => {
await Vue.nextTick();
// used in code, need to wait to the 500ms here too
setTimeout(() => {
const imageIsNew = isNew(parent);
expect(imageIsNew).toBeTrue();
done();
}, REFRESH_CSS_MS);
});
it("should show that an image is not new", async (done) => {
const target = imageTelemetry[2].url;
parent.querySelectorAll(`img[src='${target}']`)[0].click();
await Vue.nextTick();
// used in code, need to wait to the 500ms here too
setTimeout(() => {
const imageIsNew = isNew(parent);
expect(imageIsNew).toBeFalse();
done();
}, REFRESH_CSS_MS);
});
it("should navigate via arrow keys", async () => {
let keyOpts = {
element: parent.querySelector('.c-imagery'),
key: 'ArrowLeft',
keyCode: 37,
type: 'keyup'
};
simulateKeyEvent(keyOpts);
await Vue.nextTick();
const imageInfo = getImageInfo(parent);
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
});
it("should navigate via numerous arrow keys", async () => {
let element = parent.querySelector('.c-imagery');
let type = 'keyup';
let leftKeyOpts = {
element,
type,
key: 'ArrowLeft',
keyCode: 37
};
let rightKeyOpts = {
element,
type,
key: 'ArrowRight',
keyCode: 39
};
// left thrice
simulateKeyEvent(leftKeyOpts);
simulateKeyEvent(leftKeyOpts);
simulateKeyEvent(leftKeyOpts);
// right once
simulateKeyEvent(rightKeyOpts);
await Vue.nextTick();
const imageInfo = getImageInfo(parent);
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
});
});
});

View File

@ -1,114 +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 {
createOpenMct,
resetApplicationState
} from 'utils/testing';
describe("The local time", () => {
const LOCAL_FORMAT_KEY = 'local-format';
const LOCAL_SYSTEM_KEY = 'local';
const JUNK = "junk";
const TIMESTAMP = -14256000000;
const DATESTRING = '1969-07-20 12:00:00.000 am';
let openmct;
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(openmct.plugins.LocalTimeSystem());
openmct.on('start', done);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
describe("system", function () {
let localTimeSystem;
beforeEach(() => {
localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, {
start: 0,
end: 4
});
});
it("is installed", () => {
let timeSystems = openmct.time.getAllTimeSystems();
let local = timeSystems.find(ts => ts.key === LOCAL_SYSTEM_KEY);
expect(local).not.toEqual(-1);
});
it("can be set to be the main time system", () => {
expect(openmct.time.timeSystem().key).toBe(LOCAL_SYSTEM_KEY);
});
it("uses the local-format time format", () => {
expect(localTimeSystem.timeFormat).toBe(LOCAL_FORMAT_KEY);
});
it("is UTC based", () => {
expect(localTimeSystem.isUTCBased).toBe(true);
});
it("defines expected metadata", () => {
expect(localTimeSystem.key).toBe(LOCAL_SYSTEM_KEY);
expect(localTimeSystem.name).toBeDefined();
expect(localTimeSystem.cssClass).toBeDefined();
expect(localTimeSystem.durationFormat).toBeDefined();
});
});
describe("formatter can be obtained from the telemetry API and", () => {
let localTimeFormatter;
let dateString;
let timeStamp;
beforeEach(() => {
localTimeFormatter = openmct.telemetry.getFormatter(LOCAL_FORMAT_KEY);
dateString = localTimeFormatter.format(TIMESTAMP);
timeStamp = localTimeFormatter.parse(DATESTRING);
});
it("will format a timestamp in local time format", () => {
expect(localTimeFormatter.format(TIMESTAMP)).toBe(dateString);
});
it("will parse an local time Date String into milliseconds", () => {
expect(localTimeFormatter.parse(DATESTRING)).toBe(timeStamp);
});
it("will validate correctly", () => {
expect(localTimeFormatter.validate(DATESTRING)).toBe(true);
expect(localTimeFormatter.validate(JUNK)).toBe(false);
});
});
});

View File

@ -225,12 +225,14 @@ 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
@ -440,10 +442,10 @@ export default {
async updateDefaultNotebook(notebookStorage) {
const defaultNotebookObject = await this.getDefaultNotebookObject();
if (!defaultNotebookObject) {
setDefaultNotebook(this.openmct, notebookStorage, this.internalDomainObject);
setDefaultNotebook(this.openmct, notebookStorage);
} else if (objectUtils.makeKeyString(defaultNotebookObject.identifier) !== objectUtils.makeKeyString(notebookStorage.notebookMeta.identifier)) {
this.removeDefaultClass(defaultNotebookObject);
setDefaultNotebook(this.openmct, notebookStorage, this.internalDomainObject);
setDefaultNotebook(this.openmct, notebookStorage);
}
if (this.defaultSectionId && this.defaultSectionId.length === 0 || this.defaultSectionId !== notebookStorage.section.id) {

View File

@ -143,9 +143,7 @@ export default {
this.openmct.notifications.alert(message);
}
const relativeHash = hash.slice(hash.indexOf('#'));
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
window.location.hash = url.hash;
window.location.hash = hash;
},
formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat);

View File

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

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, createMouseEvent, resetApplicationState } from 'utils/testing';
import { createOpenMct, resetApplicationState } from 'utils/testing';
import NotebookPlugin from './plugin';
import Vue from 'vue';
@ -133,89 +133,4 @@ describe("Notebook plugin:", () => {
expect(hasMajorElements).toBe(true);
});
});
describe("Notebook Snapshots view:", () => {
let snapshotIndicator;
let drawerElement;
function clickSnapshotIndicator() {
const indicator = element.querySelector('.icon-camera');
const button = indicator.querySelector('button');
const clickEvent = createMouseEvent('click');
button.dispatchEvent(clickEvent);
}
beforeAll(() => {
snapshotIndicator = openmct.indicators.indicatorObjects
.find(indicator => indicator.key === 'notebook-snapshot-indicator').element;
element.append(snapshotIndicator);
return Vue.nextTick();
});
afterAll(() => {
snapshotIndicator.remove();
snapshotIndicator = undefined;
if (drawerElement) {
drawerElement.remove();
drawerElement = undefined;
}
});
beforeEach(() => {
drawerElement = document.querySelector('.l-shell__drawer');
});
afterEach(() => {
if (drawerElement) {
drawerElement.classList.remove('is-expanded');
}
});
it("has Snapshots indicator", () => {
const hasSnapshotIndicator = snapshotIndicator !== null && snapshotIndicator !== undefined;
expect(hasSnapshotIndicator).toBe(true);
});
it("snapshots container has class isExpanded", () => {
let classes = drawerElement.classList;
const isExpandedBefore = classes.contains('is-expanded');
clickSnapshotIndicator();
classes = drawerElement.classList;
const isExpandedAfterFirstClick = classes.contains('is-expanded');
expect(isExpandedBefore).toBeFalse();
expect(isExpandedAfterFirstClick).toBeTrue();
});
it("snapshots container does not have class isExpanded", () => {
let classes = drawerElement.classList;
const isExpandedBefore = classes.contains('is-expanded');
clickSnapshotIndicator();
classes = drawerElement.classList;
const isExpandedAfterFirstClick = classes.contains('is-expanded');
clickSnapshotIndicator();
classes = drawerElement.classList;
const isExpandedAfterSecondClick = classes.contains('is-expanded');
expect(isExpandedBefore).toBeFalse();
expect(isExpandedAfterFirstClick).toBeTrue();
expect(isExpandedAfterSecondClick).toBeFalse();
});
it("show notebook snapshots container text", () => {
clickSnapshotIndicator();
const notebookSnapshots = drawerElement.querySelector('.l-browse-bar__object-name');
const snapshotsText = notebookSnapshots.textContent.trim();
expect(snapshotsText).toBe('Notebook Snapshots');
});
});
});

View File

@ -7,14 +7,15 @@ export default class Snapshot {
constructor(openmct) {
this.openmct = openmct;
this.snapshotContainer = new SnapshotContainer(openmct);
this.exportImageService = openmct.$injector.get('exportImageService');
this.dialogService = openmct.$injector.get('dialogService');
this.capture = this.capture.bind(this);
this._saveSnapShot = this._saveSnapShot.bind(this);
}
capture(snapshotMeta, notebookType, domElement) {
const exportImageService = this.openmct.$injector.get('exportImageService');
exportImageService.exportPNGtoSRC(domElement, 's-status-taking-snapshot')
this.exportImageService.exportPNGtoSRC(domElement, 's-status-taking-snapshot')
.then(function (blob) {
const reader = new window.FileReader();
reader.readAsDataURL(blob);

View File

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

View File

@ -23,15 +23,14 @@
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: {
@ -83,7 +82,7 @@ describe('Notebook Storage:', () => {
});
it('has correct notebookstorage on setDefaultNotebook', () => {
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
NotebookStorage.setDefaultNotebook(openmct, notebookStorage);
const defaultNotebook = NotebookStorage.getDefaultNotebook();
expect(JSON.stringify(defaultNotebook)).toBe(JSON.stringify(notebookStorage));
@ -99,7 +98,7 @@ describe('Notebook Storage:', () => {
sectionTitle: 'Section'
};
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
NotebookStorage.setDefaultNotebook(openmct, notebookStorage);
NotebookStorage.setDefaultNotebookSection(section);
const defaultNotebook = NotebookStorage.getDefaultNotebook();
@ -116,7 +115,7 @@ describe('Notebook Storage:', () => {
pageTitle: 'Page'
};
NotebookStorage.setDefaultNotebook(openmct, notebookStorage, domainObject);
NotebookStorage.setDefaultNotebook(openmct, notebookStorage);
NotebookStorage.setDefaultNotebookPage(page);
const defaultNotebook = NotebookStorage.getDefaultNotebook();

View File

@ -1,9 +0,0 @@
# 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

@ -1,62 +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.
*****************************************************************************/
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

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

View File

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

View File

@ -98,10 +98,6 @@ describe('the plugin', function () {
beforeEach((done) => {
planDomainObject = {
identifier: {
key: 'test-object',
namespace: ''
},
type: 'plan',
id: "test-object",
selectFile: {

View File

@ -1,29 +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.
*****************************************************************************/
import TimerViewProvider from './TimerViewProvider';
export default function TimerPlugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new TimerViewProvider(openmct));
};
}

View File

@ -482,7 +482,7 @@ select {
transition: $transIn;
white-space: nowrap;
@include hover {
&:hover {
background: $colorMenuHovBg;
color: $colorMenuHovFg;
&:before {
@ -500,10 +500,6 @@ select {
&:not([class*='icon']):before {
content: ''; // Enable :before so that menu items without an icon still indent properly
}
.menus-no-icon & {
&:before { display: none; }
}
}
}
@ -735,6 +731,7 @@ select {
.c-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
> * {
// First level items

View File

@ -552,8 +552,7 @@
}
}
&[class*='--menus-left'],
&[class*='menus-to-left'] {
&[class*='--menus-left'] {
.c-menu {
left: auto; right: 0;
}

View File

@ -3,13 +3,11 @@
<toolbar-select-menu
:options="fontSizeMenuOptions"
@change="setFontSize"
class="menus-to-left menus-no-icon"
/>
<div class="c-toolbar__separator"></div>
<toolbar-select-menu
:options="fontMenuOptions"
@change="setFont"
class="menus-to-left menus-no-icon"
/>
</div>
</template>

View File

@ -20,7 +20,7 @@
</div>
<span
class="l-browse-bar__object-name c-object-label__name c-input-inline"
:contenteditable="type.creatable"
contenteditable
@blur="updateName"
@keydown.enter.prevent
@keyup.enter.prevent="updateNameOnEnterKeyPress"

View File

@ -279,12 +279,10 @@
}
&__toolbar {
// Toolbar in the main shell, used by Display Layouts
$p: $interiorMargin;
background: $editUIBaseColor;
border-radius: $basicCr;
height: $p + 24px; // Need to standardize the height
justify-content: space-between;
padding: $p;
}
}

View File

@ -95,10 +95,6 @@
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 && initialLoad"
:show-up="index < focusedAncestors.length - 1"
:show-down="false"
:left-offset="index * 10 + 'px'"
@resetTree="beginNavigationRequest('handleReset', ancestor)"
@ -156,7 +156,6 @@ export default {
data() {
return {
root: undefined,
initialLoad: false,
isLoading: false,
mainTreeHeight: undefined,
searchLoading: false,
@ -500,11 +499,6 @@ 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

@ -11,8 +11,7 @@
class="c-tree__item"
:class="{
'is-alias': isAlias,
'is-navigated-object': navigated,
'is-context-clicked': contextClickActive
'is-navigated-object': navigated
}"
>
<view-control
@ -27,7 +26,6 @@
:object-path="node.objectPath"
:navigate-to-path="navigationPath"
:style="{ paddingLeft: leftOffset }"
@context-click-active="setContextClickActive"
/>
<view-control
v-model="expanded"
@ -93,8 +91,7 @@ export default {
return {
hasComposition: false,
navigated: this.isNavigated(),
expanded: false,
contextClickActive: false
expanded: false
};
},
computed: {
@ -145,9 +142,6 @@ export default {
},
resetTreeHere() {
this.$emit('resetTree', this.node);
},
setContextClickActive(active) {
this.contextClickActive = active;
}
}
};

View File

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

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.definition.cssClass"
:class="type.cssClass"
></div>
<span class="l-browse-bar__object-name c-object-label__name">
{{ domainObject.name }}

View File

@ -90,28 +90,6 @@ export function resetApplicationState(openmct) {
return promise;
}
// required: key
// optional: element, keyCode, type
export function simulateKeyEvent(opts) {
if (!opts.key) {
console.warn('simulateKeyEvent needs a key');
return;
}
const el = opts.element || document;
const key = opts.key;
const keyCode = opts.keyCode || key;
const type = opts.type || 'keydown';
const event = new Event(type);
event.keyCode = keyCode;
event.key = key;
el.dispatchEvent(event);
}
function clearBuiltinSpy(funcDefinition) {
funcDefinition.object[funcDefinition.functionName] = funcDefinition.nativeFunction;
}