Compare commits

...

12 Commits

Author SHA1 Message Date
charlesh88
3918bb2d4b Status styling
- Primarily needed by VISTA Data Products table UI;
- Adds new styling for inline links with icons;
- Adds new status colors in theme files;
2019-06-24 15:15:18 -07:00
Pegah Sarram
884aec8ea0 Alpha-numeric printf format (#2416)
* Implement an inspector view provider to display a component that allows setting printf format for alphanumeric items in a display layout.

* Display 'Mixed' in format input if items' formats in selection are different.

* Use lodash function to find index.

* Simplify code.

* Put the logic to disallow viewing the inspector view for multi-select in the inspector view provider as apposed to the inspector view component.
2019-06-14 13:33:15 -07:00
Deep Tailor
216f447578 Show error message when user tries to import an invalid object into another object (#2417)
* check composition policy before importing into parent

* use alert icon and improve message

* add a but in message

* change alert message to a more generic sentence:

* add a period
2019-06-10 15:17:43 -07:00
Deep Tailor
c38d810658 Fix import export (#2407)
* working import/export, need to check with objects that have name-spaces

* use keystrings instead of key
2019-05-24 12:04:40 -07:00
Andrew Henry
f5c48b7bf6 Fix regression in adding to display layouts (#2408)
* Removed policy preventing duplicate composition, and implemented no-op in composition provider instead

* Change order of edit on drop event listener

* Add mutation listener to CompositionCollection even if nothing listening to collection

* Updated test specs

* Address review comments

* Fix regression

* Removed redundant composition creation
2019-05-24 11:55:16 -07:00
Andrew Henry
d0e08f1d9a Fix typos that prevent building in linux 2019-05-24 11:24:43 -07:00
Pegah Sarram
72ea7b80fd [Summary Widget] support enum fields (#2406)
* Display a drop down menu if the selected key is of type enum.

* Create normalized dataum when persisting telemerty datum using  metadatum source as key.:

* * Clear config values before creating new inputs.
* Emit ‘change' event with the value of the first option after creating the select element.
* If a value is a number, pass it as a number when emitting ‘change’. Similarly, if the cashed telemetry value is a number, convert it to number before applying the operation and validation.

* Update description.

* Update description in operations.js also.
2019-05-24 09:18:46 -07:00
Andrew Henry
35d0c02bc5 Discard old telemetry values in tables when date is formatted as a string (#2400)
* Parse date values before comparison in BoundedTableRowCollection

* Reset table size when filter changes
2019-05-23 14:42:37 -07:00
Deep Tailor
abd7506b45 Plots issues for 4.1.1 (#2397)
* working fix

* prevent wheel zoom when nothing is plotted

* fix bug where chart was not getting rid of plot history

* override remove from series collection to keep changes contained

* don't untrack twice from plot options controller

* make plot controller the life cycle controller for config, destroy when the plot is destroyed. Remove tracking system. Add comments to zoom logic, and simplify remove and keep it in series collection

* add comments to removeTelemetryObject
2019-05-23 09:43:45 -07:00
Andrew Henry
526b4aa07e Remove duplicate policy (#2399)
* Removed policy preventing duplicate composition, and implemented no-op in composition provider instead

* Change order of edit on drop event listener

* Add mutation listener to CompositionCollection even if nothing listening to collection

* Updated test specs

* Address review comments
2019-05-20 19:14:12 -07:00
Pegah Sarram
b5e23963d4 [Summary Widget] Use installed time system's name... (#2398)
* Added LocalTimeSystem to standard plugins object.

* Use each installed time system's name instead of naming them all 'UTC'.
2019-05-16 10:24:38 -07:00
Andrew Henry
2c11eb90d4 Add additional check for presence of configuration attribute (#2393) 2019-04-29 19:18:27 -07:00
42 changed files with 572 additions and 194 deletions

View File

@@ -55,7 +55,7 @@
"node-bourbon": "^4.2.3",
"node-sass": "^4.9.2",
"painterro": "^0.2.65",
"printj": "^1.1.0",
"printj": "^1.2.1",
"raw-loader": "^0.5.1",
"request": "^2.69.0",
"split": "^1.0.0",

View File

@@ -64,12 +64,30 @@ define(['zepto'], function ($) {
var tree = this.generateNewIdentifiers(objTree);
var rootId = tree.rootId;
var rootObj = this.instantiate(tree.openmct[rootId], rootId);
var newStyleParent = parent.useCapability('adapter');
var newStyleRootObj = rootObj.useCapability('adapter');
// Instantiate all objects in tree with their newly genereated ids,
// adding each to its rightful parent's composition
rootObj.getCapability("location").setPrimaryLocation(parent.getId());
this.deepInstantiate(rootObj, tree.openmct, []);
parent.getCapability("composition").add(rootObj);
if (this.openmct.composition.checkPolicy(newStyleParent, newStyleRootObj)) {
// Instantiate all objects in tree with their newly generated ids,
// adding each to its rightful parent's composition
rootObj.getCapability("location").setPrimaryLocation(parent.getId());
this.deepInstantiate(rootObj, tree.openmct, []);
parent.getCapability("composition").add(rootObj);
} else {
var dialog = this.openmct.overlays.dialog({
iconClass: 'alert',
message: "We're sorry, but you cannot import that object type into this object.",
buttons: [
{
label: "Ok",
emphasis: true,
callback: function () {
dialog.dismiss();
}
}
]
});
}
};
ImportAsJSONAction.prototype.deepInstantiate = function (parent, tree, seen) {
@@ -80,15 +98,17 @@ define(['zepto'], function ($) {
var newObj;
seen.push(parent.getId());
parentModel.composition.forEach(function (childId, index) {
if (!tree[childId] || seen.includes(childId)) {
parentModel.composition.forEach(function (childId) {
let keystring = this.openmct.objects.makeKeyString(childId);
if (!tree[keystring] || seen.includes(keystring)) {
return;
}
newObj = this.instantiate(tree[childId], childId);
parent.getCapability("composition").add(newObj);
newObj = this.instantiate(tree[keystring], keystring);
newObj.getCapability("location")
.setPrimaryLocation(tree[childId].location);
.setPrimaryLocation(tree[keystring].location);
this.deepInstantiate(newObj, tree, seen);
}, this);
}

View File

@@ -36,7 +36,7 @@ define([
'./runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews',
'./policies/legacyCompositionPolicyAdapter',
'./policies/LegacyCompositionPolicyAdapter',
'./actions/LegacyActionAdapter'
], function (
legacyRegistry,

View File

@@ -25,7 +25,7 @@ define([
cssClass: representation.cssClass,
description: representation.description,
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 0) {
if (selection.length !== 1 || selection[0].length === 0) {
return false;
}

View File

@@ -22,8 +22,20 @@ define([
publicAPI = {};
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
'get',
'mutate'
'mutate',
'observe',
'areIdsEqual'
]);
publicAPI.objects.areIdsEqual.and.callFake(function (id1, id2) {
return id1.namespace === id2.namespace && id1.key === id2.key;
});
publicAPI.composition = jasmine.createSpyObj('CompositionAPI', [
'checkPolicy'
]);
publicAPI.composition.checkPolicy.and.returnValue(true);
publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [
'on'
]);
@@ -91,7 +103,7 @@ define([
beforeEach(function () {
listener = jasmine.createSpy('reorderListener');
composition.on('reorder', listener);
return composition.load();
});
it('', function () {
@@ -119,49 +131,16 @@ define([
expect(newComposition[2].key).toEqual('a');
})
});
// TODO: Implement add/removal in new default provider.
xit('synchronizes changes between instances', function () {
var otherComposition = compositionAPI.get(domainObject);
var addListener = jasmine.createSpy('addListener');
var removeListener = jasmine.createSpy('removeListener');
var otherAddListener = jasmine.createSpy('otherAddListener');
var otherRemoveListener = jasmine.createSpy('otherRemoveListener');
it('supports adding an object to composition', function () {
let addListener = jasmine.createSpy('addListener');
let mockChildObject = {
identifier: {key: 'mock-key', namespace: ''}
};
composition.on('add', addListener);
composition.on('remove', removeListener);
otherComposition.on('add', otherAddListener);
otherComposition.on('remove', otherRemoveListener);
composition.add(mockChildObject);
return Promise.all([composition.load(), otherComposition.load()])
.then(function () {
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
expect(removeListener).not.toHaveBeenCalled();
expect(otherRemoveListener).not.toHaveBeenCalled();
var object = addListener.calls.mostRecent().args[0];
composition.remove(object);
expect(removeListener).toHaveBeenCalled();
expect(otherRemoveListener).toHaveBeenCalled();
addListener.reset();
otherAddListener.reset();
composition.add(object);
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
removeListener.reset();
otherRemoveListener.reset();
otherComposition.remove(object);
expect(removeListener).toHaveBeenCalled();
expect(otherRemoveListener).toHaveBeenCalled();
addListener.reset();
otherAddListener.reset();
otherComposition.add(object);
expect(addListener).toHaveBeenCalled();
expect(otherAddListener).toHaveBeenCalled();
});
expect(domainObject.composition.length).toBe(4);
expect(domainObject.composition[3]).toEqual(mockChildObject.identifier);
});
});
@@ -184,7 +163,9 @@ define([
key: 'thing'
}
]);
}
},
add: jasmine.createSpy('add'),
remove: jasmine.createSpy('remove')
};
domainObject = {
identifier: {
@@ -214,6 +195,25 @@ define([
});
});
});
describe('Calling add or remove', function () {
let mockChildObject;
beforeEach(function () {
mockChildObject = {
identifier: {key: 'mock-key', namespace: ''}
};
composition.add(mockChildObject);
});
it('calls add on the provider', function () {
expect(customProvider.add).toHaveBeenCalledWith(domainObject, mockChildObject.identifier);
});
it('calls remove on the provider', function () {
composition.remove(mockChildObject);
expect(customProvider.remove).toHaveBeenCalledWith(domainObject, mockChildObject.identifier);
});
});
});
describe('dynamic custom composition', function () {

View File

@@ -75,9 +75,7 @@ define([
throw new Error('Event not supported by composition: ' + event);
}
if (!this.mutationListener) {
this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => {
this.domainObject = newDomainObject;
})
this._synchronize();
}
if (this.provider.on && this.provider.off) {
if (event === 'add') {
@@ -134,10 +132,8 @@ define([
this.listeners[event].splice(index, 1);
if (this.listeners[event].length === 0) {
if (this.mutationListener) {
this.mutationListener();
delete this.mutationListener;
}
this._destroy();
// Remove provider listener if this is the last callback to
// be removed.
if (this.provider.off && this.provider.on) {
@@ -181,6 +177,9 @@ define([
*/
CompositionCollection.prototype.add = function (child, skipMutate) {
if (!skipMutate) {
if (!this.publicAPI.composition.checkPolicy(this.domainObject, child)) {
throw `Object of type ${child.type} cannot be added to object of type ${this.domainObject.type}`;
}
this.provider.add(this.domainObject, child.identifier);
} else {
this.emit('add', child);
@@ -272,6 +271,19 @@ define([
this.remove(child, true);
};
CompositionCollection.prototype._synchronize = function () {
this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => {
this.domainObject = JSON.parse(JSON.stringify(newDomainObject));
});
};
CompositionCollection.prototype._destroy = function () {
if (this.mutationListener) {
this.mutationListener();
delete this.mutationListener;
}
};
/**
* Emit events.
* @private

View File

@@ -48,24 +48,11 @@ define([
this.listeningTo = {};
this.onMutation = this.onMutation.bind(this);
this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this);
this.cannotContainItself = this.cannotContainItself.bind(this);
compositionAPI.addPolicy(this.cannotContainDuplicates);
compositionAPI.addPolicy(this.cannotContainItself);
}
/**
* @private
*/
DefaultCompositionProvider.prototype.cannotContainDuplicates = function (parent, child) {
return this.appliesTo(parent) &&
parent.composition.findIndex((composeeId) => {
return composeeId.namespace === child.identifier.namespace &&
composeeId.key === child.identifier.key;
}) === -1;
}
/**
* @private
*/
@@ -199,9 +186,18 @@ define([
* @memberof module:openmct.CompositionProvider#
* @method add
*/
DefaultCompositionProvider.prototype.add = function (domainObject, child) {
throw new Error('Default Provider does not implement adding.');
// TODO: this needs to be synchronized via mutation
DefaultCompositionProvider.prototype.add = function (parent, childId) {
if (!this.includes(parent, childId)) {
parent.composition.push(childId);
this.publicAPI.objects.mutate(parent, 'composition', parent.composition);
}
};
/**
* @private
*/
DefaultCompositionProvider.prototype.includes = function (parent, childId) {
return parent.composition.findIndex(composee =>
this.publicAPI.objects.areIdsEqual(composee, childId)) !== -1;
};
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
define([
'./components/LadTable.vue',
'./components/LADTable.vue',
'vue'
], function (
LadTableComponent,

View File

@@ -41,7 +41,7 @@
<script>
import lodash from 'lodash';
import LadRow from './LadRow.vue';
import LadRow from './LADRow.vue';
export default {
inject: ['openmct', 'domainObject'],

View File

@@ -52,7 +52,7 @@
<script>
import lodash from 'lodash';
import LadRow from './LadRow.vue';
import LadRow from './LADRow.vue';
export default {
inject: ['openmct', 'domainObject'],

View File

@@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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([
'./components/AlphanumericFormatView.vue',
'vue'
], function (AlphanumericFormatView, Vue) {
function AlphanumericFormatViewProvider(openmct, options) {
function isTelemetryObject(selectionPath) {
let selectedObject = selectionPath[0].context.item;
let parentObject = selectionPath[1].context.item;
return parentObject &&
parentObject.type === 'layout' &&
selectedObject &&
openmct.telemetry.isTelemetryObject(selectedObject) &&
!options.showAsView.includes(selectedObject.type)
}
return {
key: 'alphanumeric-format',
name: 'Alphanumeric Format',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 1) {
return false;
}
return selection.every(isTelemetryObject);
},
view: function (selection) {
let component;
return {
show: function (element) {
component = new Vue({
provide: {
openmct
},
components: {
AlphanumericFormatView: AlphanumericFormatView.default
},
template: '<alphanumeric-format-view></alphanumeric-format-view>',
el: element
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
}
},
priority: function () {
return 1;
}
}
}
return AlphanumericFormatViewProvider;
});

View File

@@ -0,0 +1,90 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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-properties" v-if="isEditing">
<div class="c-properties__header">Alphanumeric Format</div>
<ul class="c-properties__section">
<li class="c-properties__row">
<div class="c-properties__label" title="Printf formatting for the selected telemetry">
<label for="telemetryPrintfFormat">Format</label>
</div>
<div class="c-properties__value">
<input id="telemetryPrintfFormat"
type="text"
@change="formatTelemetry"
:value="telemetryFormat"
:placeholder="nonMixedFormat ? '' : 'Mixed'"
>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
inject: ['openmct'],
data() {
let selectionPath = this.openmct.selection.get()[0];
return {
isEditing: this.openmct.editor.isEditing(),
telemetryFormat: undefined,
nonMixedFormat: false
}
},
methods: {
toggleEdit(isEditing) {
this.isEditing = isEditing;
},
formatTelemetry(event) {
let newFormat = event.currentTarget.value;
this.openmct.selection.get().forEach(selectionPath => {
selectionPath[0].context.updateTelemetryFormat(newFormat);
});
this.telemetryFormat = newFormat;
},
handleSelection(selection) {
if (selection.length === 0 || selection[0].length < 2) {
return;
}
let format = selection[0][0].context.layoutItem.format;
this.nonMixedFormat = selection.every(selectionPath => {
return selectionPath[0].context.layoutItem.format === format;
});
this.telemetryFormat = this.nonMixedFormat ? format : '';
}
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleEdit);
this.openmct.selection.on('change', this.handleSelection);
this.handleSelection(this.openmct.selection.get());
},
destroyed() {
this.openmct.editor.off('isEditing', this.toggleEdit);
this.openmct.selection.off('change', this.handleSelection);
}
}
</script>

View File

@@ -48,7 +48,8 @@
:multiSelect="selectedLayoutItems.length > 1"
@move="move"
@endMove="endMove"
@endLineResize='endLineResize'>
@endLineResize='endLineResize'
@formatChanged='updateTelemetryFormat'>
</component>
<edit-marquee v-if='showMarquee'
:gridSize="gridSize"
@@ -557,6 +558,11 @@
this.layoutItems.splice(itemIndex, 1);
this.layoutItems.splice(newIndex, 0, items[itemIndex]);
}
},
updateTelemetryFormat(item, format) {
let index = _.findIndex(this.layoutItems, item);
item.format = format;
this.mutate(`configuration.items[${index}]`, item);
}
},
mounted() {

View File

@@ -79,6 +79,7 @@
<script>
import LayoutFrame from './LayoutFrame.vue'
import printj from 'printj'
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
DEFAULT_POSITION = [1, 1];
@@ -143,6 +144,10 @@
return;
}
if (this.item.format) {
return printj.sprintf(this.item.format, this.datum[this.valueMetadata.key]);
}
return this.valueFormatter && this.valueFormatter.format(this.datum);
},
telemetryClass() {
@@ -168,6 +173,9 @@
}
this.context.index = newIndex;
},
item(newItem) {
this.context.layoutItem = newItem;
}
},
methods: {
@@ -219,10 +227,14 @@
this.context = {
item: domainObject,
layoutItem: this.item,
index: this.index
index: this.index,
updateTelemetryFormat: this.updateTelemetryFormat
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
updateTelemetryFormat(format) {
this.$emit('formatChanged', this.item, format);
}
},
mounted() {

View File

@@ -25,6 +25,8 @@ import Vue from 'vue'
import objectUtils from '../../api/objects/object-utils.js'
import DisplayLayoutType from './DisplayLayoutType.js'
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'
import AlphaNumericFormatViewProvider from './AlphaNumericFormatViewProvider.js'
export default function DisplayLayoutPlugin(options) {
return function (openmct) {
openmct.objectViews.addProvider({
@@ -76,7 +78,8 @@ export default function DisplayLayoutPlugin(options) {
}
});
openmct.types.addType('layout', DisplayLayoutType());
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct));
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct, options));
openmct.inspectorViews.addProvider(new AlphaNumericFormatViewProvider(openmct, options));
openmct.composition.addPolicy((parent, child) => {
if (parent.type === 'layout' && child.type === 'folder') {
return false;

View File

@@ -177,7 +177,9 @@ define([
return [
{
check(domainObject) {
return domainObject.type === 'layout' && domainObject.configuration.layout;
return domainObject.type === 'layout' &&
domainObject.configuration &&
domainObject.configuration.layout;
},
migrate(domainObject) {
let childObjects = {};
@@ -196,7 +198,9 @@ define([
},
{
check(domainObject) {
return domainObject.type === 'telemetry.fixed' && domainObject.configuration['fixed-display'];
return domainObject.type === 'telemetry.fixed' &&
domainObject.configuration &&
domainObject.configuration['fixed-display'];
},
migrate(domainObject) {
const DEFAULT_GRID_SIZE = [64, 16];
@@ -234,6 +238,7 @@ define([
{
check(domainObject) {
return domainObject.type === 'table' &&
domainObject.configuration &&
domainObject.configuration.table;
},
migrate(domainObject) {

View File

@@ -115,11 +115,13 @@ define([
Collection.prototype.remove = function (model) {
var index = this.indexOf(model);
if (index === -1) {
throw new Error('model not found in collection.');
}
this.models.splice(index, 1);
this.emit('remove', model, index);
this.models.splice(index, 1);
};
Collection.prototype.destroy = function (model) {

View File

@@ -100,19 +100,33 @@ define([
removeTelemetryObject: function (identifier) {
var plotObject = this.plot.get('domainObject');
if (plotObject.type === 'telemetry.plot.overlay') {
var index = _.findIndex(plotObject.configuration.series, function (s) {
var persistedIndex = _.findIndex(plotObject.configuration.series, function (s) {
return _.isEqual(identifier, s.identifier);
});
this.remove(this.at(index));
// Because this is triggered by a composition change, we have
// to defer mutation of our plot object, otherwise we might
// mutate an outdated version of the plotObject.
setTimeout(function () {
var newPlotObject = this.plot.get('domainObject');
var cSeries = newPlotObject.configuration.series.slice();
cSeries.splice(index, 1);
this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries);
}.bind(this));
var configIndex = _.findIndex(this.models, function (m) {
return _.isEqual(m.domainObject.identifier, identifier);
});
/*
when cancelling out of edit mode, the config store and domain object are out of sync
thus it is necesarry to check both and remove the models that are no longer in composition
*/
if (persistedIndex === -1) {
this.remove(this.at(configIndex));
} else {
this.remove(this.at(persistedIndex));
// Because this is triggered by a composition change, we have
// to defer mutation of our plot object, otherwise we might
// mutate an outdated version of the plotObject.
setTimeout(function () {
var newPlotObject = this.plot.get('domainObject');
var cSeries = newPlotObject.configuration.series.slice();
cSeries.splice(persistedIndex, 1);
this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries);
}.bind(this));
}
}
},
onSeriesAdd: function (series) {

View File

@@ -25,23 +25,11 @@ define([
function ConfigStore() {
this.store = {};
this.tracking = {};
}
ConfigStore.prototype.track = function (id) {
if (!this.tracking[id]) {
this.tracking[id] = 0;
}
this.tracking[id] += 1;
};
ConfigStore.prototype.untrack = function (id) {
this.tracking[id] -= 1;
if (this.tracking[id] <= 0) {
delete this.tracking[id];
this.store[id].destroy();
delete this.store[id];
}
ConfigStore.prototype.deleteStore = function (id) {
this.store[id].destroy();
delete this.store[id];
};
ConfigStore.prototype.add = function (id, config) {

View File

@@ -49,7 +49,6 @@ define([
};
PlotOptionsController.prototype.destroy = function () {
configStore.untrack(this.configId);
this.stopListening();
this.unlisten();
};
@@ -60,7 +59,7 @@ define([
this.$timeout(this.setUpScope.bind(this));
return;
}
configStore.track(this.configId);
this.config = this.$scope.config = config;
this.$scope.plotSeries = [];

View File

@@ -282,11 +282,19 @@ define([
};
MCTPlotController.prototype.zoom = function (zoomDirection, zoomFactor) {
var currentXaxis = this.$scope.xAxis.get('displayRange'),
currentYaxis = this.$scope.yAxis.get('displayRange');
// when there is no plot data, the ranges can be undefined
// in which case we should not perform zoom
if (!currentXaxis || !currentYaxis) {
return;
}
this.freeze();
this.trackHistory();
var currentXaxis = this.$scope.xAxis.get('displayRange'),
currentYaxis = this.$scope.yAxis.get('displayRange'),
xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor,
var xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor,
yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor;
if (zoomDirection === 'in') {
@@ -322,12 +330,19 @@ define([
return;
}
let xDisplayRange = this.$scope.xAxis.get('displayRange'),
yDisplayRange = this.$scope.yAxis.get('displayRange');
// when there is no plot data, the ranges can be undefined
// in which case we should not perform zoom
if (!xDisplayRange || !yDisplayRange) {
return;
}
this.freeze();
window.clearTimeout(this.stillZooming);
let xDisplayRange = this.$scope.xAxis.get('displayRange'),
yDisplayRange = this.$scope.yAxis.get('displayRange'),
xAxisDist = (xDisplayRange.max - xDisplayRange.min),
let xAxisDist = (xDisplayRange.max - xDisplayRange.min),
yAxisDist = (yDisplayRange.max - yDisplayRange.min),
xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x,
xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min,

View File

@@ -148,7 +148,6 @@ define([
});
configStore.add(configId, config);
}
configStore.track(configId);
return config;
};
@@ -157,7 +156,8 @@ define([
};
PlotController.prototype.destroy = function () {
configStore.untrack(this.config.id);
configStore.deleteStore(this.config.id);
this.stopListening();
if (this.checkForSize) {
clearInterval(this.checkForSize);

View File

@@ -23,6 +23,7 @@
define([
'lodash',
'./utcTimeSystem/plugin',
'./localTimeSystem/plugin',
'../../example/generator/plugin',
'./autoflow/AutoflowTabularPlugin',
'./timeConductor/plugin',
@@ -46,6 +47,7 @@ define([
], function (
_,
UTCTimeSystem,
LocalTimeSystem,
GeneratorPlugin,
AutoflowPlugin,
TimeConductorPlugin,
@@ -81,6 +83,7 @@ define([
});
plugins.UTCTimeSystem = UTCTimeSystem;
plugins.LocalTimeSystem = LocalTimeSystem;
plugins.ImportExport = ImportExport;

View File

@@ -70,16 +70,14 @@ define([
*/
function onValueInput(event) {
var elem = event.target,
value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber),
value = isNaN(Number(elem.value)) ? elem.value : Number(elem.value),
inputIndex = self.valueInputs.indexOf(elem);
if (elem.tagName.toUpperCase() === 'INPUT') {
self.eventEmitter.emit('change', {
value: value,
property: 'values[' + inputIndex + ']',
index: self.index
});
}
self.eventEmitter.emit('change', {
value: value,
property: 'values[' + inputIndex + ']',
index: self.index
});
}
this.listenTo(this.deleteButton, 'click', this.remove, this);
@@ -108,8 +106,7 @@ define([
Object.values(this.selects).forEach(function (select) {
$('.t-configuration', self.domElement).append(select.getDOM());
});
this.listenTo($(this.domElement), 'input', onValueInput);
this.listenTo($('.t-value-inputs', this.domElement), 'input', onValueInput);
}
Condition.prototype.getDOM = function (container) {
@@ -167,7 +164,9 @@ define([
/**
* When an operation is selected, create the appropriate value inputs
* and add them to the view
* and add them to the view. If an operation is of type enum, create
* a drop-down menu instead.
*
* @param {string} operation The key of currently selected operation
*/
Condition.prototype.generateValueInputs = function (operation) {
@@ -176,25 +175,49 @@ define([
inputCount,
inputType,
newInput,
index = 0;
index = 0,
emitChange = false;
inputArea.html('');
this.valueInputs = [];
this.config.values = [];
if (evaluator.getInputCount(operation)) {
inputCount = evaluator.getInputCount(operation);
inputType = evaluator.getInputType(operation);
while (index < inputCount) {
if (!this.config.values[index]) {
this.config.values[index] = (inputType === 'number' ? 0 : '');
if (inputType === 'select') {
newInput = $('<select>' + this.generateSelectOptions() + '</select>');
emitChange = true;
} else {
this.config.values[index] = inputType === 'number' ? 0 : '';
newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>');
}
newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>');
this.valueInputs.push(newInput.get(0));
inputArea.append(newInput);
index += 1;
}
if (emitChange) {
this.eventEmitter.emit('change', {
value: Number(newInput[0].options[0].value),
property: 'values[0]',
index: this.index
});
}
}
};
Condition.prototype.generateSelectOptions = function () {
let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object);
let options = '';
telemetryMetadata[this.config.key].enumerations.forEach(enumeration => {
options += '<option value="' + enumeration.value + '">'+ enumeration.string + '</option>';
});
return options;
};
return Condition;
});

View File

@@ -24,7 +24,8 @@ define([], function () {
*/
this.inputTypes = {
number: 'number',
string: 'text'
string: 'text',
enum: 'select'
};
/**
@@ -34,7 +35,8 @@ define([], function () {
*/
this.inputValidators = {
number: this.validateNumberInput,
string: this.validateStringInput
string: this.validateStringInput,
enum: this.validateNumberInput
};
/**
@@ -201,7 +203,7 @@ define([], function () {
return typeof input[0] === 'undefined';
},
text: 'is undefined',
appliesTo: ['string', 'number'],
appliesTo: ['string', 'number', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is undefined';
@@ -212,11 +214,33 @@ define([], function () {
return typeof input[0] !== 'undefined';
},
text: 'is defined',
appliesTo: ['string', 'number'],
appliesTo: ['string', 'number', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is defined';
}
},
enumValueIs: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
},
enumValueIsNot: {
operation: function (input) {
return input[0] !== input[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
}
}
};
}
@@ -310,13 +334,16 @@ define([], function () {
validator;
if (cache[object] && typeof cache[object][key] !== 'undefined') {
telemetryValue = [cache[object][key]];
let value = cache[object][key];
telemetryValue = [isNaN(Number(value)) ? value : Number(value)];
}
op = this.operations[operation] && this.operations[operation].operation;
input = telemetryValue && telemetryValue.concat(values);
validator = op && this.inputValidators[this.operations[operation].appliesTo[0]];
if (op && input && validator) {
if (this.operations[operation].appliesTo.length === 2) {
if (this.operations[operation].appliesTo.length > 1) {
return (this.validateNumberInput(input) || this.validateStringInput(input)) && op(input);
} else {
return validator(input) && op(input);
@@ -372,7 +399,7 @@ define([], function () {
};
/**
* Returns true only of the given operation applies to a given type
* Returns true only if the given operation applies to a given type
* @param {string} key The key of the operation
* @param {string} type The value type to query
* @returns {boolean} True if the condition applies, false otherwise

View File

@@ -130,7 +130,9 @@ define ([
this.telemetryTypesById[objectId] = {};
Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) {
var type;
if (valueMetadata.hints.hasOwnProperty('range')) {
if (valueMetadata.enumerations !== undefined) {
type = 'enum';
} else if (valueMetadata.hints.hasOwnProperty('range')) {
type = 'number';
} else if (valueMetadata.hints.hasOwnProperty('domain')) {
type = 'number';
@@ -163,11 +165,18 @@ define ([
* @param {datum} datum The new data from the telemetry source
* @private
*/
ConditionManager.prototype.handleSubscriptionCallback = function (objId, datum) {
this.subscriptionCache[objId] = datum;
ConditionManager.prototype.handleSubscriptionCallback = function (objId, telemetryDatum) {
this.subscriptionCache[objId] = this.createNormalizedDatum(objId, telemetryDatum);
this.eventEmitter.emit('receiveTelemetry');
};
ConditionManager.prototype.createNormalizedDatum = function (objId, telemetryDatum) {
return Object.values(this.telemetryMetadataById[objId]).reduce((normalizedDatum, metadatum) => {
normalizedDatum[metadatum.key] = telemetryDatum[metadatum.source];
return normalizedDatum;
}, {});
};
/**
* Event handler for an add event in this Summary Widget's composition.
* Sets up subscription handlers and parses its property types.
@@ -236,6 +245,7 @@ define ([
id.namespace === identifier.namespace;
});
delete this.compositionObjs[objectId];
delete this.subscriptionCache[objectId];
this.subscriptions[objectId](); //unsubscribe from telemetry source
delete this.subscriptions[objectId];
this.eventEmitter.emit('remove', identifier);

View File

@@ -110,9 +110,11 @@ define([
type = self.manager.getTelemetryPropertyType(self.config.object, key);
self.operationKeys = operations.filter(function (operation) {
return self.evaluator.operationAppliesTo(operation, type);
});
if (type !== undefined) {
self.operationKeys = operations.filter(function (operation) {
return self.evaluator.operationAppliesTo(operation, type);
});
}
};
OperationSelect.prototype.destroy = function () {

View File

@@ -38,7 +38,7 @@ define([
return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
return {
key: ts.key,
name: 'UTC',
name: ts.name,
format: ts.timeFormat,
hints: {
domain: i
@@ -64,7 +64,7 @@ define([
// Generally safe assumption is that we have one domain per timeSystem.
values: this.getDomains().concat([
{
name: 'state',
name: 'State',
key: 'state',
source: 'ruleIndex',
format: 'enum',

View File

@@ -174,7 +174,7 @@ define([
return typeof input[0] === 'undefined';
},
text: 'is undefined',
appliesTo: ['string', 'number'],
appliesTo: ['string', 'number', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is undefined';
@@ -185,11 +185,33 @@ define([
return typeof input[0] !== 'undefined';
},
text: 'is defined',
appliesTo: ['string', 'number'],
appliesTo: ['string', 'number', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is defined';
}
},
enumValueIs: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
},
enumValueIsNot: {
operation: function (input) {
return input[0] !== input[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
}
}
};

View File

@@ -37,7 +37,7 @@ define([
key: 'table-configuration',
name: 'Telemetry Table Configuration',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 0) {
if (selection.length !== 1 || selection[0].length === 0) {
return false;
}
let object = selection[0][0].context.item;

View File

@@ -31,9 +31,9 @@ define(
) {
class BoundedTableRowCollection extends SortedTableRowCollection {
constructor (openmct) {
constructor(openmct) {
super();
this.futureBuffer = new SortedTableRowCollection();
this.openmct = openmct;
@@ -46,12 +46,13 @@ define(
openmct.time.on('bounds', this.bounds);
}
addOne (item) {
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
// Data in the future buffer will be re-evaluated for possible
// insertion on next bounds change
let beforeStartOfBounds = this.parseTime(item.datum[this.sortOptions.key]) < this.lastBounds.start;
let afterEndOfBounds = this.parseTime(item.datum[this.sortOptions.key]) > this.lastBounds.end;
let beforeStartOfBounds = parsedValue < this.lastBounds.start;
let afterEndOfBounds = parsedValue > this.lastBounds.end;
if (!afterEndOfBounds && !beforeStartOfBounds) {
return super.addOne(item);
@@ -86,13 +87,13 @@ define(
* @fires TelemetryCollection#discarded
* @param bounds
*/
bounds (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 = {
@@ -135,9 +136,13 @@ define(
}
}
getValueForSortColumn(row) {
return this.parseTime(row.datum[this.sortOptions.key]);
}
destroy() {
this.openmct.time.off('bounds', this.bounds);
}
}
return BoundedTableRowCollection;
});
return BoundedTableRowCollection;
});

View File

@@ -60,7 +60,7 @@ define(
if (rowsAdded.length > 0) {
this.emit('add', rowsAdded);
}
this.dupeCheck = true;
this.dupeCheck = true;
} else {
let wasAdded = this.addOne(rows);
if (wasAdded) {
@@ -115,11 +115,10 @@ define(
if (this.rows.length === 0) {
return 0;
}
const sortOptionsKey = this.sortOptions.key;
const testRowValue = testRow.datum[sortOptionsKey];
const firstValue = this.rows[0].datum[sortOptionsKey];
const lastValue = this.rows[this.rows.length - 1].datum[sortOptionsKey];
const testRowValue = this.getValueForSortColumn(testRow);
const firstValue = this.getValueForSortColumn(this.rows[0]);
const lastValue = this.getValueForSortColumn(this.rows[this.rows.length - 1]);
lodashFunction = lodashFunction || _.sortedIndex;
@@ -133,7 +132,7 @@ define(
return 0;
} else {
return lodashFunction(rows, testRow, (thisRow) => {
return thisRow.datum[sortOptionsKey];
return this.getValueForSortColumn(thisRow);
});
}
} else {
@@ -147,7 +146,7 @@ define(
} else {
// Use a custom comparison function to support descending sort.
return lodashFunction(rows, testRow, (thisRow) => {
const thisRowValue = thisRow.datum[sortOptionsKey];
const thisRowValue = this.getValueForSortColumn(thisRow);
if (testRowValue === thisRowValue) {
return EQUAL;
} else if (testRowValue < thisRowValue) {
@@ -206,7 +205,7 @@ define(
this.emit('sort');
}
// Return duplicate to avoid direct modification of underlying object
return Object.assign({}, this.sortOptions);
return Object.assign({}, this.sortOptions);
}
removeAllRowsForObject(objectKeyString) {
@@ -218,25 +217,32 @@ define(
}
return true;
});
this.emit('remove', removed);
}
getValueForSortColumn(row) {
return row.datum[this.sortOptions.key];
}
remove(removedRows) {
this.rows = this.rows.filter(row => {
return removedRows.indexOf(row) === -1;
});
this.emit('remove', removedRows);
}
getRows () {
getRows() {
return this.rows;
}
clear() {
let removedRows = this.rows;
this.rows = [];
this.emit('remove', removedRows);
}
}
return SortedTableRowCollection;
});
return SortedTableRowCollection;
});

View File

@@ -472,10 +472,12 @@ export default {
},
filterChanged(columnKey) {
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
this.setHeight();
},
clearFilter(columnKey) {
this.filters[columnKey] = '';
this.table.filteredRows.setColumnFilter(columnKey, '');
this.setHeight();
},
rowsAdded (rows) {
this.setHeight();

View File

@@ -101,6 +101,8 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de
$colorStatusError: #da0004;
$colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%);
$colorStatusBtnBg: #666; // Where is this used?
$colorStatusPartialBg: #3f5e8b;
$colorStatusCompleteBg: #457638;
$colorAlert: #ff3c00;
$colorAlertFg: #fff;
$colorWarningHi: #990000;

View File

@@ -105,6 +105,8 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de
$colorStatusError: #da0004;
$colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%);
$colorStatusBtnBg: #666; // Where is this used?
$colorStatusPartialBg: #3f5e8b;
$colorStatusCompleteBg: #457638;
$colorAlert: #ff3c00;
$colorAlertFg: #fff;
$colorWarningHi: #990000;

View File

@@ -101,6 +101,8 @@ $colorStatusAlertFilter: invert(89%) sepia(26%) saturate(5035%) hue-rotate(316de
$colorStatusError: #da0004;
$colorStatusErrorFilter: invert(8%) sepia(96%) saturate(4511%) hue-rotate(352deg) brightness(136%) contrast(114%);
$colorStatusBtnBg: #666; // Where is this used?
$colorStatusPartialBg: #c9d6ff;
$colorStatusCompleteBg: #a4e4b4;
$colorAlert: #ff3c00;
$colorAlertFg: #fff;
$colorWarningHi: #990000;

View File

@@ -60,11 +60,17 @@ button {
}
}
/********* Icon Buttons */
/********* Icon Buttons and Links */
.c-click-icon {
@include cClickIcon();
}
.c-click-link {
// A clickable element, typically inline, with an icon and label
@include cControl();
cursor: pointer;
}
.c-icon-button,
.c-click-swatch {
@include cClickIconButton();

View File

@@ -159,3 +159,16 @@ tr {
@include indicatorStatusColors($colorStatusError);
}
}
.s-status {
&--partial {
// Partially completed things, such as a file downloading or process that's running
background-color: $colorStatusPartialBg;
}
&--complete {
// Completed things, such as a file downloaded or process that's finished
background-color: $colorStatusCompleteBg;
}
}

View File

@@ -34,10 +34,10 @@ export default {
this.currentObject = this.object;
this.updateView();
this.$el.addEventListener('dragover', this.onDragOver);
this.$el.addEventListener('drop', this.addObjectToParent);
this.$el.addEventListener('drop', this.editIfEditable, {
capture: true
});
this.$el.addEventListener('drop', this.addObjectToParent);
},
methods: {
clear() {
@@ -57,6 +57,10 @@ export default {
this.removeSelectable();
delete this.removeSelectable;
}
if (this.composition) {
this.composition._destroy();
}
},
invokeEditModeHandler(editMode) {
this.currentView.onEditModeChange(editMode);
@@ -70,6 +74,13 @@ export default {
if (!this.currentObject) {
return;
}
this.composition = this.openmct.composition.get(this.currentObject);
if (this.composition) {
this.composition._synchronize();
this.loadComposition();
}
this.viewContainer = document.createElement('div');
this.viewContainer.classList.add('c-object-view','u-contents');
this.$el.append(this.viewContainer);
@@ -112,6 +123,10 @@ export default {
delete this.removeSelectable;
}
if (this.composition) {
this.composition._destroy();
}
this.currentObject = object;
this.unlisten = this.openmct.objects.observe(this.currentObject, '*', (mutatedObject) => {
this.currentObject = mutatedObject;
@@ -120,6 +135,9 @@ export default {
this.viewKey = viewKey;
this.updateView(immediatelySelect);
},
loadComposition() {
return this.composition.load();
},
getSelectionContext() {
if (this.currentView.getSelectionContext) {
return this.currentView.getSelectionContext();
@@ -133,10 +151,12 @@ export default {
}
},
addObjectToParent(event) {
if (this.hasComposableDomainObject(event)) {
if (this.hasComposableDomainObject(event) && this.composition) {
let composableDomainObject = this.getComposableDomainObject(event);
this.currentObject.composition.push(composableDomainObject.identifier);
this.openmct.objects.mutate(this.currentObject, 'composition', this.currentObject.composition);
this.loadComposition().then(() => {
this.composition.add(composableDomainObject);
});
event.preventDefault();
event.stopPropagation();
}
@@ -155,6 +175,7 @@ export default {
editIfEditable(event) {
let provider = this.getViewProvider();
if (provider &&
provider.canEdit &&
provider.canEdit(this.currentObject) &&
!this.openmct.editor.isEditing()) {
this.openmct.editor.edit();

View File

@@ -25,10 +25,6 @@ import _ from 'lodash';
},
methods: {
updateSelection(selection) {
if (_.isEqual(this.selection, selection)) {
return;
}
this.selection = selection;
if (this.selectedViews) {
@@ -38,10 +34,6 @@ import _ from 'lodash';
this.$el.innerHTML = '';
}
if (selection.length > 1) {
return;
}
this.selectedViews = this.openmct.inspectorViews.get(selection);
this.selectedViews.forEach(selectedView => {
let viewContainer = document.createElement('div');

View File

@@ -35,6 +35,7 @@ const webpackConfig = {
"bourbon": "bourbon.scss",
"vue": path.join(__dirname, "node_modules/vue/dist/vue.js"),
"d3-scale": path.join(__dirname, "node_modules/d3-scale/build/d3-scale.min.js"),
"printj": path.join(__dirname, "node_modules/printj/dist/printj.min.js"),
"styles": path.join(__dirname, "src/styles-new")
}
},