Compare commits

...

17 Commits

Author SHA1 Message Date
Pegah Sarram
9fef060501 Implement an inspector view provider to display a component that allows setting printf format for alphanumeric items in a display layout. 2019-06-05 11:13:24 -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
Andrew Henry
90e9c79e19 Table rendering performance tweaks (#2392)
* Table rendering performance tweaks

Throttled add, remove, and scroll

* Scroll to bottom after resize, if auto-scroll enabled
2019-04-28 17:43:06 -07:00
Andrew Henry
1b83631e43 Remove deprecation warnings (#2391) 2019-04-28 12:30:30 -07:00
Pegah Sarram
547d4e82db [Display Layout] Disallow moving objects beyond top or left edges of the edit area (#2390)
* Disallow moving objects beyond top or left edges of the edit area.

* Disallow line also to move beyond top or left edges of the edit area.
2019-04-28 12:30:10 -07:00
Deep Tailor
3377ad5e0d Reimplemented Go To Original Action (#2383)
* complete working go to original action, todo (css class for action)

* Fix layout when a menu item does not have an icon

* Removed superfluous keystring conversion
2019-04-28 12:29:16 -07:00
Charles Hacskaylo
1c0df60f05 Misc Fixes 3 (#2389)
* Misc Fixes 3

- Fix Chrome 73 bug in overlay __contents-main element;
- Fixed messages by including erroneously missing _legacy-messages.scss
file;
- Better layout for messages in notification overlay list;

* Misc Fixes 3

- Fix about screen for better compatibility with VISTA;
- Better logo sizing in splash element;
2019-04-26 14:43:13 -07:00
Pegah Sarram
138067dca9 [Migration] convert telemetry points to overlay plot (#2388)
* Replace telemetry point objects with overlay plot when migrating display layouts.

* Persist plot object
2019-04-26 11:19:07 -07:00
Andrew Henry
844280eaa5 Memory leak fixes (#2387)
* Clean up listeners

* Fix uses of 'destroy' instead of 'destroyed'
2019-04-26 10:34:24 -07:00
55 changed files with 832 additions and 499 deletions

View File

@@ -86,6 +86,7 @@
openmct.install(openmct.plugins.LADTable()); openmct.install(openmct.plugins.LADTable());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay'])); openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration()); openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.GoToOriginalAction());
openmct.start(); openmct.start();
</script> </script>
</html> </html>

View File

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

View File

@@ -9,6 +9,7 @@
ng-model="ngModel" ng-model="ngModel"
ng-show="ngModel.progressPerc !== undefined"></mct-include> ng-show="ngModel.progressPerc !== undefined"></mct-include>
</div> </div>
</div>
<div class="c-overlay__button-bar"> <div class="c-overlay__button-bar">
<button ng-repeat="dialogOption in ngModel.options" <button ng-repeat="dialogOption in ngModel.options"
class="c-button" class="c-button"
@@ -21,5 +22,4 @@
{{ngModel.primaryOption.label}} {{ngModel.primaryOption.label}}
</button> </button>
</div> </div>
</div>
</div> </div>

View File

@@ -64,7 +64,6 @@ define(
* @returns boolean * @returns boolean
*/ */
EditorCapability.prototype.inEditContext = function () { EditorCapability.prototype.inEditContext = function () {
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing(); return this.openmct.editor.isEditing();
}; };
@@ -74,7 +73,6 @@ define(
* @returns {*} * @returns {*}
*/ */
EditorCapability.prototype.isEditContextRoot = function () { EditorCapability.prototype.isEditContextRoot = function () {
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
return this.openmct.editor.isEditing(); return this.openmct.editor.isEditing();
}; };

View File

@@ -24,7 +24,6 @@ define([
"./src/actions/MoveAction", "./src/actions/MoveAction",
"./src/actions/CopyAction", "./src/actions/CopyAction",
"./src/actions/LinkAction", "./src/actions/LinkAction",
"./src/actions/GoToOriginalAction",
"./src/actions/SetPrimaryLocationAction", "./src/actions/SetPrimaryLocationAction",
"./src/services/LocatingCreationDecorator", "./src/services/LocatingCreationDecorator",
"./src/services/LocatingObjectDecorator", "./src/services/LocatingObjectDecorator",
@@ -41,7 +40,6 @@ define([
MoveAction, MoveAction,
CopyAction, CopyAction,
LinkAction, LinkAction,
GoToOriginalAction,
SetPrimaryLocationAction, SetPrimaryLocationAction,
LocatingCreationDecorator, LocatingCreationDecorator,
LocatingObjectDecorator, LocatingObjectDecorator,
@@ -104,14 +102,6 @@ define([
"linkService" "linkService"
] ]
}, },
{
"key": "follow",
"name": "Go To Original",
"description": "Go to the original, un-linked instance of this object.",
"cssClass": "",
"category": "contextual",
"implementation": GoToOriginalAction
},
{ {
"key": "locate", "key": "locate",
"name": "Set Primary Location", "name": "Set Primary Location",

View File

@@ -1,60 +0,0 @@
/*****************************************************************************
* 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(
function () {
/**
* Implements the "Go To Original" action, which follows a link back
* to an original instance of an object.
*
* @implements {Action}
* @constructor
* @private
* @memberof platform/entanglement
* @param {ActionContext} context the context in which the action
* will be performed
*/
function GoToOriginalAction(context) {
this.domainObject = context.domainObject;
}
GoToOriginalAction.prototype.perform = function () {
return this.domainObject.getCapability("location").getOriginal()
.then(function (originalObject) {
var actionCapability =
originalObject.getCapability("action");
return actionCapability &&
actionCapability.perform("navigate");
});
};
GoToOriginalAction.appliesTo = function (context) {
var domainObject = context.domainObject;
return domainObject && domainObject.hasCapability("location") &&
domainObject.getCapability("location").isLink();
};
return GoToOriginalAction;
}
);

View File

@@ -1,93 +0,0 @@
/*****************************************************************************
* 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(
[
'../../src/actions/GoToOriginalAction',
'../DomainObjectFactory',
'../ControlledPromise'
],
function (GoToOriginalAction, domainObjectFactory, ControlledPromise) {
describe("The 'go to original' action", function () {
var testContext,
originalDomainObject,
mockLocationCapability,
mockOriginalActionCapability,
originalPromise,
action;
beforeEach(function () {
mockLocationCapability = jasmine.createSpyObj(
'location',
['isLink', 'isOriginal', 'getOriginal']
);
mockOriginalActionCapability = jasmine.createSpyObj(
'action',
['perform', 'getActions']
);
originalPromise = new ControlledPromise();
mockLocationCapability.getOriginal.and.returnValue(originalPromise);
mockLocationCapability.isLink.and.returnValue(true);
mockLocationCapability.isOriginal.and.callFake(function () {
return !mockLocationCapability.isLink();
});
testContext = {
domainObject: domainObjectFactory({
capabilities: {
location: mockLocationCapability
}
})
};
originalDomainObject = domainObjectFactory({
capabilities: {
action: mockOriginalActionCapability
}
});
action = new GoToOriginalAction(testContext);
});
it("is applicable to links", function () {
expect(GoToOriginalAction.appliesTo(testContext))
.toBeTruthy();
});
it("is not applicable to originals", function () {
mockLocationCapability.isLink.and.returnValue(false);
expect(GoToOriginalAction.appliesTo(testContext))
.toBeFalsy();
});
it("navigates to original objects when performed", function () {
expect(mockOriginalActionCapability.perform)
.not.toHaveBeenCalled();
action.perform();
originalPromise.resolve(originalDomainObject);
expect(mockOriginalActionCapability.perform)
.toHaveBeenCalledWith('navigate');
});
});
}
);

View File

@@ -80,15 +80,17 @@ define(['zepto'], function ($) {
var newObj; var newObj;
seen.push(parent.getId()); 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; return;
} }
newObj = this.instantiate(tree[childId], childId); newObj = this.instantiate(tree[keystring], keystring);
parent.getCapability("composition").add(newObj);
newObj.getCapability("location") newObj.getCapability("location")
.setPrimaryLocation(tree[childId].location); .setPrimaryLocation(tree[keystring].location);
this.deepInstantiate(newObj, tree, seen); this.deepInstantiate(newObj, tree, seen);
}, this); }, this);
} }

View File

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

View File

@@ -45,22 +45,27 @@ define([
view: function (domainObject) { view: function (domainObject) {
let $rootScope = openmct.$injector.get('$rootScope'); let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker'); let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new(); let scope = $rootScope.$new(true);
let legacyObject = convertToLegacyObject(domainObject); let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false; let isDestroyed = false;
let unlistenToStatus; let unlistenToStatus;
let element;
scope.domainObject = legacyObject; scope.domainObject = legacyObject;
scope.model = legacyObject.getModel(); scope.model = legacyObject.getModel();
let child;
let parent;
return { return {
show: function (container) { show: function (container) {
parent = container;
child = document.createElement('div');
parent.appendChild(child);
let statusCapability = legacyObject.getCapability('status'); let statusCapability = legacyObject.getCapability('status');
unlistenToStatus = statusCapability.listen((newStatus) => { unlistenToStatus = statusCapability.listen((newStatus) => {
container.classList.remove('s-status-timeconductor-unsynced'); child.classList.remove('s-status-timeconductor-unsynced');
if (newStatus.includes('timeconductor-unsynced')) { if (newStatus.includes('timeconductor-unsynced')) {
container.classList.add('s-status-timeconductor-unsynced'); child.classList.add('s-status-timeconductor-unsynced');
} }
}); });
@@ -84,12 +89,13 @@ define([
uses.forEach(function (key, i) { uses.forEach(function (key, i) {
scope[key] = results[i]; scope[key] = results[i];
}); });
element = openmct.$angular.element(child);
templateLinker.link( templateLinker.link(
scope, scope,
openmct.$angular.element(container), element,
legacyView legacyView
); );
container.classList.add('u-contents'); child.classList.add('u-contents');
} }
if (promises.length) { if (promises.length) {
@@ -103,7 +109,11 @@ define([
} }
}, },
destroy: function () { destroy: function () {
element.off();
element.remove();
scope.$destroy(); scope.$destroy();
element = null;
scope = null;
unlistenToStatus(); unlistenToStatus();
} }
} }

View File

@@ -41,15 +41,18 @@ define([
let domainObject = selection[0][0].context.item; let domainObject = selection[0][0].context.item;
let $rootScope = openmct.$injector.get('$rootScope'); let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker'); let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new(); let scope = $rootScope.$new(true);
let legacyObject = convertToLegacyObject(domainObject); let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false; let isDestroyed = false;
let element;
scope.domainObject = legacyObject; scope.domainObject = legacyObject;
scope.model = legacyObject.getModel(); scope.model = legacyObject.getModel();
return { return {
show: function (container) { show: function (container) {
let child = document.createElement('div');
container.appendChild(child);
// TODO: implement "gestures" support ? // TODO: implement "gestures" support ?
let uses = representation.uses || []; let uses = representation.uses || [];
let promises = []; let promises = [];
@@ -70,9 +73,10 @@ define([
uses.forEach(function (key, i) { uses.forEach(function (key, i) {
scope[key] = results[i]; scope[key] = results[i];
}); });
element = openmct.$angular.element(child)
templateLinker.link( templateLinker.link(
scope, scope,
openmct.$angular.element(container), element,
representation representation
); );
container.style.height = '100%'; container.style.height = '100%';
@@ -89,7 +93,11 @@ define([
} }
}, },
destroy: function () { destroy: function () {
element.off();
element.remove();
scope.$destroy(); scope.$destroy();
element = null;
scope = null;
} }
} }
} }

View File

@@ -22,8 +22,20 @@ define([
publicAPI = {}; publicAPI = {};
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [ publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
'get', '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', [ publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [
'on' 'on'
]); ]);
@@ -119,49 +131,16 @@ define([
expect(newComposition[2].key).toEqual('a'); expect(newComposition[2].key).toEqual('a');
}) })
}); });
it('supports adding an object to composition', function () {
// TODO: Implement add/removal in new default provider. let addListener = jasmine.createSpy('addListener');
xit('synchronizes changes between instances', function () { let mockChildObject = {
var otherComposition = compositionAPI.get(domainObject); identifier: {key: 'mock-key', namespace: ''}
var addListener = jasmine.createSpy('addListener'); };
var removeListener = jasmine.createSpy('removeListener');
var otherAddListener = jasmine.createSpy('otherAddListener');
var otherRemoveListener = jasmine.createSpy('otherRemoveListener');
composition.on('add', addListener); composition.on('add', addListener);
composition.on('remove', removeListener); composition.add(mockChildObject);
otherComposition.on('add', otherAddListener);
otherComposition.on('remove', otherRemoveListener);
return Promise.all([composition.load(), otherComposition.load()]) expect(domainObject.composition.length).toBe(4);
.then(function () { expect(domainObject.composition[3]).toEqual(mockChildObject.identifier);
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();
});
}); });
}); });
@@ -184,7 +163,9 @@ define([
key: 'thing' key: 'thing'
} }
]); ]);
} },
add: jasmine.createSpy('add'),
remove: jasmine.createSpy('remove')
}; };
domainObject = { domainObject = {
identifier: { 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 () { describe('dynamic custom composition', function () {

View File

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

View File

@@ -48,24 +48,11 @@ define([
this.listeningTo = {}; this.listeningTo = {};
this.onMutation = this.onMutation.bind(this); this.onMutation = this.onMutation.bind(this);
this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this);
this.cannotContainItself = this.cannotContainItself.bind(this); this.cannotContainItself = this.cannotContainItself.bind(this);
compositionAPI.addPolicy(this.cannotContainDuplicates);
compositionAPI.addPolicy(this.cannotContainItself); 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 * @private
*/ */
@@ -199,9 +186,18 @@ define([
* @memberof module:openmct.CompositionProvider# * @memberof module:openmct.CompositionProvider#
* @method add * @method add
*/ */
DefaultCompositionProvider.prototype.add = function (domainObject, child) { DefaultCompositionProvider.prototype.add = function (parent, childId) {
throw new Error('Default Provider does not implement adding.'); if (!this.includes(parent, childId)) {
// TODO: this needs to be synchronized via mutation 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) { DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {

View File

@@ -92,6 +92,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 1 auto; flex: 1 1 auto;
height: 0; // Chrome 73 overflow bug fix
overflow: auto; overflow: auto;
padding-right: $interiorMargin; // fend off scroll bar padding-right: $interiorMargin; // fend off scroll bar
} }

View File

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

View File

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

View File

@@ -52,7 +52,7 @@
<script> <script>
import lodash from 'lodash'; import lodash from 'lodash';
import LadRow from './LadRow.vue'; import LadRow from './LADRow.vue';
export default { export default {
inject: ['openmct', 'domainObject'], 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,80 @@
/*****************************************************************************
* 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" v-if="!multiSelect">
<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">
</div>
</li>
</ul>
<div class="c-properties__row--span-all" v-if="multiSelect">No format to display for multiple items</div>
</div>
</template>
<script>
export default {
inject: ['openmct'],
data() {
let selectionPath = this.openmct.selection.get()[0];
return {
isEditing: this.openmct.editor.isEditing(),
telemetryFormat: selectionPath[0].context.layoutItem.format,
multiSelect: false
}
},
methods: {
toggleEdit(isEditing) {
this.isEditing = isEditing;
},
formatTelemetry(event) {
let selectionPath = this.openmct.selection.get()[0];
let newFormat = event.currentTarget.value;
selectionPath[0].context.updateTelemetryFormat(newFormat);
this.telemetryFormat = newFormat;
},
handleSelection(selection) {
if (selection.length === 0 || selection[0].length === 0) {
return;
}
this.multiSelect = selection.length > 1 ? true : false;
}
},
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" :multiSelect="selectedLayoutItems.length > 1"
@move="move" @move="move"
@endMove="endMove" @endMove="endMove"
@endLineResize='endLineResize'> @endLineResize='endLineResize'
@formatChanged='updateTelemetryFormat'>
</component> </component>
<edit-marquee v-if='showMarquee' <edit-marquee v-if='showMarquee'
:gridSize="gridSize" :gridSize="gridSize"
@@ -269,33 +270,63 @@
_.cloneDeep(this.selectedLayoutItems).forEach(selectedItem => { _.cloneDeep(this.selectedLayoutItems).forEach(selectedItem => {
if (selectedItem.type === 'line-view') { if (selectedItem.type === 'line-view') {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y, selectedItem.x2, selectedItem.y2]; this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y, selectedItem.x2, selectedItem.y2];
this.startingMinX2 = this.startingMinX2 !== undefined ? Math.min(this.startingMinX2, selectedItem.x2) : selectedItem.x2;
this.startingMinY2 = this.startingMinY2 !== undefined ? Math.min(this.startingMinY2, selectedItem.y2) : selectedItem.y2;
} else { } else {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y]; this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y];
} }
this.startingMinX = this.startingMinX !== undefined ? Math.min(this.startingMinX, selectedItem.x) : selectedItem.x;
this.startingMinY = this.startingMinY !== undefined ? Math.min(this.startingMinY, selectedItem.y) : selectedItem.y;
}); });
} }
let layoutItems = this.layoutItems.map(item => { let layoutItems = this.layoutItems.map(item => {
if (this.initialPositions[item.id]) { if (this.initialPositions[item.id]) {
let startingPosition = this.initialPositions[item.id]; this.updateItemPosition(item, gridDelta);
let [startingX, startingY, startingX2, startingY2] = startingPosition;
item.x = startingX + gridDelta[0];
item.y = startingY + gridDelta[1];
if (item.x2) {
item.x2 = startingX2 + gridDelta[0];
}
if (item.y2) {
item.y2 = startingY2 + gridDelta[1];
}
} }
return item; return item;
}); });
}, },
updateItemPosition(item, gridDelta) {
let startingPosition = this.initialPositions[item.id];
let [startingX, startingY, startingX2, startingY2] = startingPosition;
if (this.startingMinX + gridDelta[0] >= 0) {
if (item.x2 !== undefined) {
if (this.startingMinX2 + gridDelta[0] >= 0) {
item.x = startingX + gridDelta[0];
}
} else {
item.x = startingX + gridDelta[0];
}
}
if (this.startingMinY + gridDelta[1] >= 0) {
if (item.y2 !== undefined) {
if (this.startingMinY2 + gridDelta[1] >= 0) {
item.y = startingY + gridDelta[1];
}
} else {
item.y = startingY + gridDelta[1];
}
}
if (item.x2 !== undefined && this.startingMinX2 + gridDelta[0] >= 0 && this.startingMinX + gridDelta[0] >= 0) {
item.x2 = startingX2 + gridDelta[0];
}
if (item.y2 !== undefined && this.startingMinY2 + gridDelta[1] >= 0 && this.startingMinY + gridDelta[1] >= 0) {
item.y2 = startingY2 + gridDelta[1];
}
},
endMove() { endMove() {
this.mutate('configuration.items', this.layoutItems); this.mutate('configuration.items', this.layoutItems);
this.initialPositions = undefined; this.initialPositions = undefined;
this.startingMinX = undefined;
this.startingMinY = undefined;
this.startingMinX2 = undefined;
this.startingMinY2 = undefined;
}, },
mutate(path, value) { mutate(path, value) {
this.openmct.objects.mutate(this.internalDomainObject, path, value); this.openmct.objects.mutate(this.internalDomainObject, path, value);
@@ -527,6 +558,11 @@
this.layoutItems.splice(itemIndex, 1); this.layoutItems.splice(itemIndex, 1);
this.layoutItems.splice(newIndex, 0, items[itemIndex]); this.layoutItems.splice(newIndex, 0, items[itemIndex]);
} }
},
updateTelemetryFormat(item, format) {
let index = this.layoutItems.indexOf(item);
item.format = format;
this.mutate(`configuration.items[${index}]`, item);
} }
}, },
mounted() { mounted() {

View File

@@ -116,7 +116,7 @@
let height = Number.NEGATIVE_INFINITY; let height = Number.NEGATIVE_INFINITY;
this.selectedLayoutItems.forEach(item => { this.selectedLayoutItems.forEach(item => {
if (item.x2) { if (item.x2 !== undefined) {
let lineWidth = Math.abs(item.x - item.x2); let lineWidth = Math.abs(item.x - item.x2);
let lineMinX = Math.min(item.x, item.x2); let lineMinX = Math.min(item.x, item.x2);
x = Math.min(lineMinX, x); x = Math.min(lineMinX, x);
@@ -126,7 +126,7 @@
width = Math.max(item.width + item.x, width); width = Math.max(item.width + item.x, width);
} }
if (item.y2) { if (item.y2 !== undefined) {
let lineHeight = Math.abs(item.y - item.y2); let lineHeight = Math.abs(item.y - item.y2);
let lineMinY = Math.min(item.y, item.y2); let lineMinY = Math.min(item.y, item.y2);
y = Math.min(lineMinY, y); y = Math.min(lineMinY, y);

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
/*****************************************************************************
* 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.
*****************************************************************************/
export default class GoToOriginalAction {
constructor(openmct) {
this.name = 'Go To Original';
this.description = 'Go to the original unlinked instance of this object';
this._openmct = openmct;
}
invoke(objectPath) {
this._openmct.objects.getOriginalPath(objectPath[0].identifier)
.then((originalPath) => {
let url = '#/browse/' + originalPath
.map(function (o) {
return o && this._openmct.objects.makeKeyString(o.identifier);
}.bind(this))
.reverse()
.slice(1)
.join('/');
window.location.href = url;
});
}
appliesTo(objectPath) {
let parentKeystring = objectPath[1] && this._openmct.objects.makeKeyString(objectPath[1].identifier);
if (!parentKeystring) {
return false;
}
return (parentKeystring !== objectPath[0].location);
}
}

View File

@@ -0,0 +1,28 @@
/*****************************************************************************
* 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.
*****************************************************************************/
import GoToOriginalAction from './goToOriginalAction';
export default function () {
return function (openmct) {
openmct.contextMenu.registerAction(new GoToOriginalAction(openmct));
};
}

View File

@@ -64,35 +64,45 @@ define([
Object.keys(panels).forEach(key => { Object.keys(panels).forEach(key => {
let panel = panels[key]; let panel = panels[key];
let domainObject = childObjects[key]; let domainObject = childObjects[key];
let identifier = undefined;
if (isTelemetry(domainObject)) { if (isTelemetry(domainObject)) {
items.push({ // If object is a telemetry point, convert it to a plot and
width: panel.dimensions[0], // replace the object in migratedObject composition with the plot.
height: panel.dimensions[1], identifier = {
x: panel.position[0], key: uuid(),
y: panel.position[1], namespace: migratedObject.identifier.namespace
identifier: domainObject.identifier, };
id: uuid(), let plotObject = {
type: 'telemetry-view', identifier: identifier,
displayMode: 'all', location: domainObject.location,
value: openmct.telemetry.getMetadata(domainObject).getDefaultDisplayValue(), name: domainObject.name,
stroke: "transparent", type: "telemetry.plot.overlay"
fill: "", };
color: "", let plotType = openmct.types.get('telemetry.plot.overlay');
size: "13px" plotType.definition.initialize(plotObject);
plotObject.composition.push(domainObject.identifier);
openmct.objects.mutate(plotObject, 'persisted', Date.now());
let keyString = openmct.objects.makeKeyString(domainObject.identifier);
let clonedComposition = Object.assign([], migratedObject.composition);
clonedComposition.forEach((identifier, index) => {
if (openmct.objects.makeKeyString(identifier) === keyString) {
migratedObject.composition[index] = plotObject.identifier;
}
}); });
} else { }
items.push({ items.push({
width: panel.dimensions[0], width: panel.dimensions[0],
height: panel.dimensions[1], height: panel.dimensions[1],
x: panel.position[0], x: panel.position[0],
y: panel.position[1], y: panel.position[1],
identifier: domainObject.identifier, identifier: identifier || domainObject.identifier,
id: uuid(), id: uuid(),
type: 'subobject-view', type: 'subobject-view',
hasFrame: panel.hasFrame hasFrame: panel.hasFrame
}); });
}
}); });
migratedObject.configuration.items = items; migratedObject.configuration.items = items;
@@ -167,7 +177,9 @@ define([
return [ return [
{ {
check(domainObject) { check(domainObject) {
return domainObject.type === 'layout' && domainObject.configuration.layout; return domainObject.type === 'layout' &&
domainObject.configuration &&
domainObject.configuration.layout;
}, },
migrate(domainObject) { migrate(domainObject) {
let childObjects = {}; let childObjects = {};
@@ -186,7 +198,9 @@ define([
}, },
{ {
check(domainObject) { check(domainObject) {
return domainObject.type === 'telemetry.fixed' && domainObject.configuration['fixed-display']; return domainObject.type === 'telemetry.fixed' &&
domainObject.configuration &&
domainObject.configuration['fixed-display'];
}, },
migrate(domainObject) { migrate(domainObject) {
const DEFAULT_GRID_SIZE = [64, 16]; const DEFAULT_GRID_SIZE = [64, 16];
@@ -224,6 +238,7 @@ define([
{ {
check(domainObject) { check(domainObject) {
return domainObject.type === 'table' && return domainObject.type === 'table' &&
domainObject.configuration &&
domainObject.configuration.table; domainObject.configuration.table;
}, },
migrate(domainObject) { migrate(domainObject) {

View File

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

View File

@@ -100,20 +100,34 @@ define([
removeTelemetryObject: function (identifier) { removeTelemetryObject: function (identifier) {
var plotObject = this.plot.get('domainObject'); var plotObject = this.plot.get('domainObject');
if (plotObject.type === 'telemetry.plot.overlay') { 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); return _.isEqual(identifier, s.identifier);
}); });
this.remove(this.at(index));
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 // Because this is triggered by a composition change, we have
// to defer mutation of our plot object, otherwise we might // to defer mutation of our plot object, otherwise we might
// mutate an outdated version of the plotObject. // mutate an outdated version of the plotObject.
setTimeout(function () { setTimeout(function () {
var newPlotObject = this.plot.get('domainObject'); var newPlotObject = this.plot.get('domainObject');
var cSeries = newPlotObject.configuration.series.slice(); var cSeries = newPlotObject.configuration.series.slice();
cSeries.splice(index, 1); cSeries.splice(persistedIndex, 1);
this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries); this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries);
}.bind(this)); }.bind(this));
} }
}
}, },
onSeriesAdd: function (series) { onSeriesAdd: function (series) {
var seriesColor = series.get('color'); var seriesColor = series.get('color');

View File

@@ -25,23 +25,11 @@ define([
function ConfigStore() { function ConfigStore() {
this.store = {}; this.store = {};
this.tracking = {};
} }
ConfigStore.prototype.track = function (id) { ConfigStore.prototype.deleteStore = 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(); this.store[id].destroy();
delete this.store[id]; delete this.store[id];
}
}; };
ConfigStore.prototype.add = function (id, config) { ConfigStore.prototype.add = function (id, config) {

View File

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

View File

@@ -282,11 +282,19 @@ define([
}; };
MCTPlotController.prototype.zoom = function (zoomDirection, zoomFactor) { 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.freeze();
this.trackHistory(); this.trackHistory();
var currentXaxis = this.$scope.xAxis.get('displayRange'),
currentYaxis = this.$scope.yAxis.get('displayRange'), var xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor,
xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor,
yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor; yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor;
if (zoomDirection === 'in') { if (zoomDirection === 'in') {
@@ -322,12 +330,19 @@ define([
return; 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(); this.freeze();
window.clearTimeout(this.stillZooming); window.clearTimeout(this.stillZooming);
let xDisplayRange = this.$scope.xAxis.get('displayRange'), let xAxisDist = (xDisplayRange.max - xDisplayRange.min),
yDisplayRange = this.$scope.yAxis.get('displayRange'),
xAxisDist = (xDisplayRange.max - xDisplayRange.min),
yAxisDist = (yDisplayRange.max - yDisplayRange.min), yAxisDist = (yDisplayRange.max - yDisplayRange.min),
xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x, xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x,
xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min, xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min,

View File

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

View File

@@ -23,6 +23,7 @@
define([ define([
'lodash', 'lodash',
'./utcTimeSystem/plugin', './utcTimeSystem/plugin',
'./localTimeSystem/plugin',
'../../example/generator/plugin', '../../example/generator/plugin',
'./autoflow/AutoflowTabularPlugin', './autoflow/AutoflowTabularPlugin',
'./timeConductor/plugin', './timeConductor/plugin',
@@ -41,10 +42,12 @@ define([
'./tabs/plugin', './tabs/plugin',
'./LADTable/plugin', './LADTable/plugin',
'./filters/plugin', './filters/plugin',
'./objectMigration/plugin' './objectMigration/plugin',
'./goToOriginalAction/plugin'
], function ( ], function (
_, _,
UTCTimeSystem, UTCTimeSystem,
LocalTimeSystem,
GeneratorPlugin, GeneratorPlugin,
AutoflowPlugin, AutoflowPlugin,
TimeConductorPlugin, TimeConductorPlugin,
@@ -63,7 +66,8 @@ define([
Tabs, Tabs,
LADTable, LADTable,
Filters, Filters,
ObjectMigration ObjectMigration,
GoToOriginalAction
) { ) {
var bundleMap = { var bundleMap = {
LocalStorage: 'platform/persistence/local', LocalStorage: 'platform/persistence/local',
@@ -79,6 +83,7 @@ define([
}); });
plugins.UTCTimeSystem = UTCTimeSystem; plugins.UTCTimeSystem = UTCTimeSystem;
plugins.LocalTimeSystem = LocalTimeSystem;
plugins.ImportExport = ImportExport; plugins.ImportExport = ImportExport;
@@ -160,6 +165,7 @@ define([
plugins.LADTable = LADTable; plugins.LADTable = LADTable;
plugins.Filters = Filters; plugins.Filters = Filters;
plugins.ObjectMigration = ObjectMigration.default; plugins.ObjectMigration = ObjectMigration.default;
plugins.GoToOriginalAction = GoToOriginalAction.default;
return plugins; return plugins;
}); });

View File

@@ -70,17 +70,15 @@ define([
*/ */
function onValueInput(event) { function onValueInput(event) {
var elem = event.target, 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); inputIndex = self.valueInputs.indexOf(elem);
if (elem.tagName.toUpperCase() === 'INPUT') {
self.eventEmitter.emit('change', { self.eventEmitter.emit('change', {
value: value, value: value,
property: 'values[' + inputIndex + ']', property: 'values[' + inputIndex + ']',
index: self.index index: self.index
}); });
} }
}
this.listenTo(this.deleteButton, 'click', this.remove, this); this.listenTo(this.deleteButton, 'click', this.remove, this);
this.listenTo(this.duplicateButton, 'click', this.duplicate, this); this.listenTo(this.duplicateButton, 'click', this.duplicate, this);
@@ -108,8 +106,7 @@ define([
Object.values(this.selects).forEach(function (select) { Object.values(this.selects).forEach(function (select) {
$('.t-configuration', self.domElement).append(select.getDOM()); $('.t-configuration', self.domElement).append(select.getDOM());
}); });
this.listenTo($('.t-value-inputs', this.domElement), 'input', onValueInput);
this.listenTo($(this.domElement), 'input', onValueInput);
} }
Condition.prototype.getDOM = function (container) { Condition.prototype.getDOM = function (container) {
@@ -167,7 +164,9 @@ define([
/** /**
* When an operation is selected, create the appropriate value inputs * 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 * @param {string} operation The key of currently selected operation
*/ */
Condition.prototype.generateValueInputs = function (operation) { Condition.prototype.generateValueInputs = function (operation) {
@@ -176,24 +175,48 @@ define([
inputCount, inputCount,
inputType, inputType,
newInput, newInput,
index = 0; index = 0,
emitChange = false;
inputArea.html(''); inputArea.html('');
this.valueInputs = []; this.valueInputs = [];
this.config.values = [];
if (evaluator.getInputCount(operation)) { if (evaluator.getInputCount(operation)) {
inputCount = evaluator.getInputCount(operation); inputCount = evaluator.getInputCount(operation);
inputType = evaluator.getInputType(operation); inputType = evaluator.getInputType(operation);
while (index < inputCount) { while (index < inputCount) {
if (!this.config.values[index]) { if (inputType === 'select') {
this.config.values[index] = (inputType === 'number' ? 0 : ''); 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)); this.valueInputs.push(newInput.get(0));
inputArea.append(newInput); inputArea.append(newInput);
index += 1; 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; return Condition;

View File

@@ -24,7 +24,8 @@ define([], function () {
*/ */
this.inputTypes = { this.inputTypes = {
number: 'number', number: 'number',
string: 'text' string: 'text',
enum: 'select'
}; };
/** /**
@@ -34,7 +35,8 @@ define([], function () {
*/ */
this.inputValidators = { this.inputValidators = {
number: this.validateNumberInput, number: this.validateNumberInput,
string: this.validateStringInput string: this.validateStringInput,
enum: this.validateNumberInput
}; };
/** /**
@@ -201,7 +203,7 @@ define([], function () {
return typeof input[0] === 'undefined'; return typeof input[0] === 'undefined';
}, },
text: 'is undefined', text: 'is undefined',
appliesTo: ['string', 'number'], appliesTo: ['string', 'number', 'enum'],
inputCount: 0, inputCount: 0,
getDescription: function () { getDescription: function () {
return ' is undefined'; return ' is undefined';
@@ -212,11 +214,33 @@ define([], function () {
return typeof input[0] !== 'undefined'; return typeof input[0] !== 'undefined';
}, },
text: 'is defined', text: 'is defined',
appliesTo: ['string', 'number'], appliesTo: ['string', 'number', 'enum'],
inputCount: 0, inputCount: 0,
getDescription: function () { getDescription: function () {
return ' is defined'; 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; validator;
if (cache[object] && typeof cache[object][key] !== 'undefined') { 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; op = this.operations[operation] && this.operations[operation].operation;
input = telemetryValue && telemetryValue.concat(values); input = telemetryValue && telemetryValue.concat(values);
validator = op && this.inputValidators[this.operations[operation].appliesTo[0]]; validator = op && this.inputValidators[this.operations[operation].appliesTo[0]];
if (op && input && validator) { 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); return (this.validateNumberInput(input) || this.validateStringInput(input)) && op(input);
} else { } else {
return validator(input) && op(input); 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} key The key of the operation
* @param {string} type The value type to query * @param {string} type The value type to query
* @returns {boolean} True if the condition applies, false otherwise * @returns {boolean} True if the condition applies, false otherwise

View File

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

View File

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

View File

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

View File

@@ -174,7 +174,7 @@ define([
return typeof input[0] === 'undefined'; return typeof input[0] === 'undefined';
}, },
text: 'is undefined', text: 'is undefined',
appliesTo: ['string', 'number'], appliesTo: ['string', 'number', 'enum'],
inputCount: 0, inputCount: 0,
getDescription: function () { getDescription: function () {
return ' is undefined'; return ' is undefined';
@@ -185,11 +185,33 @@ define([
return typeof input[0] !== 'undefined'; return typeof input[0] !== 'undefined';
}, },
text: 'is defined', text: 'is defined',
appliesTo: ['string', 'number'], appliesTo: ['string', 'number', 'enum'],
inputCount: 0, inputCount: 0,
getDescription: function () { getDescription: function () {
return ' is defined'; 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

@@ -239,7 +239,7 @@ define([
this.filteredRows.destroy(); this.filteredRows.destroy();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this); Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.openmct.time.off('bounds', this.refreshData); this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.on('timeSystem', this.refreshData); this.openmct.time.off('timeSystem', this.refreshData);
if (this.filterObserver) { if (this.filterObserver) {
this.filterObserver(); this.filterObserver();
} }

View File

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

View File

@@ -116,10 +116,9 @@ define(
return 0; return 0;
} }
const sortOptionsKey = this.sortOptions.key; const testRowValue = this.getValueForSortColumn(testRow);
const testRowValue = testRow.datum[sortOptionsKey]; const firstValue = this.getValueForSortColumn(this.rows[0]);
const firstValue = this.rows[0].datum[sortOptionsKey]; const lastValue = this.getValueForSortColumn(this.rows[this.rows.length - 1]);
const lastValue = this.rows[this.rows.length - 1].datum[sortOptionsKey];
lodashFunction = lodashFunction || _.sortedIndex; lodashFunction = lodashFunction || _.sortedIndex;
@@ -133,7 +132,7 @@ define(
return 0; return 0;
} else { } else {
return lodashFunction(rows, testRow, (thisRow) => { return lodashFunction(rows, testRow, (thisRow) => {
return thisRow.datum[sortOptionsKey]; return this.getValueForSortColumn(thisRow);
}); });
} }
} else { } else {
@@ -147,7 +146,7 @@ define(
} else { } else {
// Use a custom comparison function to support descending sort. // Use a custom comparison function to support descending sort.
return lodashFunction(rows, testRow, (thisRow) => { return lodashFunction(rows, testRow, (thisRow) => {
const thisRowValue = thisRow.datum[sortOptionsKey]; const thisRowValue = this.getValueForSortColumn(thisRow);
if (testRowValue === thisRowValue) { if (testRowValue === thisRowValue) {
return EQUAL; return EQUAL;
} else if (testRowValue < thisRowValue) { } else if (testRowValue < thisRowValue) {
@@ -218,25 +217,32 @@ define(
} }
return true; return true;
}); });
this.emit('remove', removed); this.emit('remove', removed);
} }
getValueForSortColumn(row) {
return row.datum[this.sortOptions.key];
}
remove(removedRows) { remove(removedRows) {
this.rows = this.rows.filter(row => { this.rows = this.rows.filter(row => {
return removedRows.indexOf(row) === -1; return removedRows.indexOf(row) === -1;
}); });
this.emit('remove', removedRows); this.emit('remove', removedRows);
} }
getRows () { getRows() {
return this.rows; return this.rows;
} }
clear() { clear() {
let removedRows = this.rows; let removedRows = this.rows;
this.rows = []; this.rows = [];
this.emit('remove', removedRows); this.emit('remove', removedRows);
} }
} }
return SortedTableRowCollection; return SortedTableRowCollection;
}); });

View File

@@ -78,7 +78,7 @@
<!-- Content table --> <!-- Content table -->
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}"> <div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}">
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div> <div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div>
<table class="c-table__body c-telemetry-table__body" <table class="c-table__body c-telemetry-table__body js-telemetry-table__content"
:style="{ height: totalHeight + 'px'}"> :style="{ height: totalHeight + 'px'}">
<tbody> <tbody>
<telemetry-table-row v-for="(row, rowIndex) in visibleRows" <telemetry-table-row v-for="(row, rowIndex) in visibleRows"
@@ -284,7 +284,7 @@ import _ from 'lodash';
const VISIBLE_ROW_COUNT = 100; const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17; const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200; const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 20; const AUTO_SCROLL_TRIGGER_HEIGHT = 100;
const RESIZE_HOT_ZONE = 10; const RESIZE_HOT_ZONE = 10;
const MOVE_TRIGGER_WAIT = 500; const MOVE_TRIGGER_WAIT = 500;
const VERTICAL_SCROLL_WIDTH = 30; const VERTICAL_SCROLL_WIDTH = 30;
@@ -364,14 +364,15 @@ export default {
}, },
methods: { methods: {
updateVisibleRows() { updateVisibleRows() {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
let start = 0; let start = 0;
let end = VISIBLE_ROW_COUNT; let end = VISIBLE_ROW_COUNT;
let filteredRows = this.table.filteredRows.getRows(); let filteredRows = this.table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length; let filteredRowsLength = filteredRows.length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
if (filteredRowsLength < VISIBLE_ROW_COUNT) { if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength; end = filteredRowsLength;
} else { } else {
@@ -393,13 +394,18 @@ export default {
} }
this.rowOffset = start; this.rowOffset = start;
this.visibleRows = filteredRows.slice(start, end); this.visibleRows = filteredRows.slice(start, end);
this.updatingView = false;
});
}
}, },
calculateFirstVisibleRow() { calculateFirstVisibleRow() {
return Math.floor(this.scrollable.scrollTop / this.rowHeight); let scrollTop = this.scrollable.scrollTop;
return Math.floor(scrollTop / this.rowHeight);
}, },
calculateLastVisibleRow() { calculateLastVisibleRow() {
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight; let scrollBottom = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.floor(bottomScroll / this.rowHeight); return Math.ceil(scrollBottom / this.rowHeight);
}, },
updateHeaders() { updateHeaders() {
this.headers = this.table.configuration.getVisibleHeaders(); this.headers = this.table.configuration.getVisibleHeaders();
@@ -443,10 +449,7 @@ export default {
} }
this.table.sortBy(this.sortOptions); this.table.sortBy(this.sortOptions);
}, },
scroll() { scroll () {
if (!this.processingScroll) {
this.processingScroll = true;
requestAnimationFrame(()=> {
this.updateVisibleRows(); this.updateVisibleRows();
this.synchronizeScrollX(); this.synchronizeScrollX();
@@ -457,57 +460,59 @@ export default {
// Auto-scroll will be re-enabled if user scrolls to bottom again. // Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = false; this.autoScroll = false;
} }
this.processingScroll = false;
});
}
}, },
shouldSnapToBottom() { shouldSnapToBottom() {
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT); return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
}, },
scrollToBottom() { scrollToBottom() {
this.scrollable.scrollTop = this.scrollable.scrollHeight; this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
}, },
synchronizeScrollX() { synchronizeScrollX() {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft; this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
}, },
filterChanged(columnKey) { filterChanged(columnKey) {
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]); this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
this.setHeight();
}, },
clearFilter(columnKey) { clearFilter(columnKey) {
this.filters[columnKey] = ''; this.filters[columnKey] = '';
this.table.filteredRows.setColumnFilter(columnKey, ''); this.table.filteredRows.setColumnFilter(columnKey, '');
this.setHeight();
}, },
rowsAdded(rows) { rowsAdded (rows) {
this.setHeight();
let sizingRow; let sizingRow;
if (Array.isArray(rows)) { if (Array.isArray(rows)) {
sizingRow = rows[0]; sizingRow = rows[0];
} else { } else {
sizingRow = rows; sizingRow = rows;
} }
if (!this.sizingRows[sizingRow.objectKeyString]) { if (!this.sizingRows[sizingRow.objectKeyString]) {
this.sizingRows[sizingRow.objectKeyString] = sizingRow; this.sizingRows[sizingRow.objectKeyString] = sizingRow;
this.$nextTick().then(this.calculateColumnWidths); this.$nextTick().then(this.calculateColumnWidths);
} }
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
if (this.autoScroll) { if (this.autoScroll) {
this.$nextTick().then(this.scrollToBottom); this.scrollToBottom();
} }
this.updatingView = false;
});
}
},
rowsRemoved(rows) {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows(); this.updateVisibleRows();
this.updatingView = false; },
}); rowsRemoved (rows) {
} this.setHeight();
this.updateVisibleRows();
},
/**
* Calculates height based on total number of rows, and sets table height.
*/
setHeight() {
let filteredRowsLength = this.table.filteredRows.getRows().length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
// Set element height directly to avoid having to wait for Vue to update DOM
// which causes subsequent scroll to use an out of date height.
this.contentTable.style.height = this.totalHeight + 'px';
}, },
exportAsCSV() { exportAsCSV() {
const headerKeys = Object.keys(this.headers); const headerKeys = Object.keys(this.headers);
@@ -595,7 +600,11 @@ export default {
this.calculateTableSize(); this.calculateTableSize();
// On some resize events scrollTop is reset to 0. Possibly due to a transition we're using? // On some resize events scrollTop is reset to 0. Possibly due to a transition we're using?
// Need to preserve scroll position in this case. // Need to preserve scroll position in this case.
if (this.autoScroll) {
this.scrollToBottom();
} else {
this.scrollable.scrollTop = scrollTop; this.scrollable.scrollTop = scrollTop;
}
width = el.clientWidth; width = el.clientWidth;
height = el.clientHeight; height = el.clientHeight;
} }
@@ -608,6 +617,9 @@ export default {
this.filterChanged = _.debounce(this.filterChanged, 500); this.filterChanged = _.debounce(this.filterChanged, 500);
}, },
mounted() { mounted() {
this.rowsAdded = _.throttle(this.rowsAdded, 200);
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
this.scroll = _.throttle(this.scroll, 100);
this.table.on('object-added', this.addObject); this.table.on('object-added', this.addObject);
this.table.on('object-removed', this.removeObject); this.table.on('object-removed', this.removeObject);
@@ -621,6 +633,7 @@ export default {
//Default sort //Default sort
this.sortOptions = this.table.filteredRows.sortBy(); this.sortOptions = this.table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w'); this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing'); this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w'); this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');

View File

@@ -54,10 +54,11 @@
&:after { &:after {
// App logo // App logo
top: 0; $d: 25%;
right: 15%; top: $d;
bottom: 0; right: $d;
left: 15%; bottom: $d;
left: $d;
} }
} }
@@ -75,12 +76,20 @@
&__image, &__image,
&__text { &__text {
height: 50%; flex: 1 1 auto;
flex: 1 1 0; }
&__image {
height: 35%;
} }
&__text { &__text {
height: 65%;
overflow: auto; overflow: auto;
> * + * {
border-top: 1px solid $colorInteriorBorder;
margin-top: 1em;
}
} }
&--licenses { &--licenses {
@@ -107,7 +116,7 @@
h1, h2, h3 { h1, h2, h3 {
font-weight: normal; font-weight: normal;
margin-bottom: 1em; margin-bottom: .25em;
} }
h1 { h1 {

View File

@@ -393,6 +393,11 @@ select {
color: $colorMenuIc; color: $colorMenuIc;
font-size: 1em; font-size: 1em;
margin-right: $interiorMargin; margin-right: $interiorMargin;
min-width: 1em;
}
&:not([class]):before {
content: ''; // Add this element so that menu items without an icon still indent properly
} }
} }
} }

View File

@@ -33,3 +33,4 @@
@import "table"; @import "table";
@import "legacy"; @import "legacy";
@import "legacy-plots"; @import "legacy-plots";
@import "legacy-messages";

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
<template> <template>
<div class="c-about c-about--splash"> <div class="c-about c-about--splash">
<div v-if="!branding.aboutHtml" class="c-about__image c-splash-image"></div> <div class="c-about__image c-splash-image"></div>
<div class="c-about__text s-text"> <div class="c-about__text s-text">
<div v-if="branding.aboutHtml" class="s-text l-content" v-html="branding.aboutHtml"></div> <div class="c-about__text__element" v-if="branding.aboutHtml" v-html="branding.aboutHtml"></div>
<div class="c-about__text__element">
<h1 class="l-title s-title">Open MCT</h1> <h1 class="l-title s-title">Open MCT</h1>
<div class="l-description s-description"> <div class="l-description s-description">
<p>Open MCT, Copyright &copy; 2014-2019, United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved.</p> <p>Open MCT, Copyright &copy; 2014-2019, United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved.</p>
@@ -19,6 +19,7 @@
<li>Branch: {{buildInfo.branch || 'Unknown'}}</li> <li>Branch: {{buildInfo.branch || 'Unknown'}}</li>
</ul> </ul>
</div> </div>
</div>
</div> </div>
</template> </template>
<script> <script>

View File

@@ -70,17 +70,18 @@
this.domainObject = newObject; this.domainObject = newObject;
}); });
this.$once('hook:destroyed', removeListener); this.$once('hook:destroyed', removeListener);
if (this.openmct.composition.get(this.node.object)) { if (this.openmct.composition.get(this.node.object)) {
this.hasChildren = true; this.hasChildren = true;
} }
this.openmct.router.on('change:path', this.highlightIfNavigated); this.openmct.router.on('change:path', this.highlightIfNavigated);
}, },
destroy() { destroyed() {
this.openmct.router.off('change:path', this.highlightIfNavigated);
if (this.composition) { if (this.composition) {
this.composition.off('add', this.addChild); this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild); this.composition.off('remove', this.removeChild);
this.openmct.router.off('change:path', this.highlightIfNavigated);
delete this.composition; delete this.composition;
} }
}, },

View File

@@ -18,7 +18,7 @@ export default {
this.objectPath.forEach(object => { this.objectPath.forEach(object => {
if (object) { if (object) {
this.$once('hook:destroy', this.$once('hook:destroyed',
this.openmct.objects.observe(object, '*', updateObject.bind(this, object))) this.openmct.objects.observe(object, '*', updateObject.bind(this, object)))
} }
}); });

View File

@@ -116,7 +116,7 @@
this.notebookEnabled = true; this.notebookEnabled = true;
} }
}, },
destroy() { destroyed() {
this.view.destroy(); this.view.destroy();
} }
} }

View File

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