Compare commits

...

38 Commits

Author SHA1 Message Date
38c1a981e8 Clock element as a standalone component
- Was intended to replace clock indicator;
- WIP!
2019-06-25 10:10:58 -07:00
50944cfed0 Status bar migration to top of layout, WIP
- Added tracking style to indicator-template;
- Moved click action to button in label of globalClearIndicator;
- Removed unnecessary markup in Indicators.vue;
- Commented out __head collapse button for now in Layout.vue;
2019-06-07 19:04:30 -07:00
27b236ffe4 Status bar migration to top of layout, WIP
- Refine styles and markup for Indicators;
- Better separation of styles for clickable and non-clickable
Indicators;
2019-06-07 18:30:08 -07:00
b7e75bcc22 Status bar migration to top of layout, WIP
- Refine and remove legacy styles for Indicators;
- Significant cleanup in Indicator markup;
- Remove unnecessary wrapper component StatusBar.vue;
- Move collapse-button styles to a more general location in _controls
.scss;
- New hasMenu mixin to allow easier application of disclosure control
styling;
2019-06-07 16:07:50 -07:00
210a23dfe2 styling 2019-06-06 15:29:19 -07:00
84bfb9e8cc global clear works on plots 2019-06-06 15:06:03 -07:00
fd749253d9 first proto of global clear, working on tables 2019-06-06 14:38:41 -07:00
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
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
d0e08f1d9a Fix typos that prevent building in linux 2019-05-24 11:24:43 -07:00
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
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
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
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
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
2c11eb90d4 Add additional check for presence of configuration attribute (#2393) 2019-04-29 19:18:27 -07:00
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
1b83631e43 Remove deprecation warnings (#2391) 2019-04-28 12:30:30 -07:00
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
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
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
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
844280eaa5 Memory leak fixes (#2387)
* Clean up listeners

* Fix uses of 'destroy' instead of 'destroyed'
2019-04-26 10:34:24 -07:00
d2e2d55caf Bring across fixes for #1468 and #2277 into TCR (#2386) 2019-04-24 16:01:45 -07:00
f01d4071a1 Merge pull request #2385 from nasa/misc-fixes-2
Misc Fixes 2
2019-04-24 16:00:28 -07:00
06524ce967 Misc Fixes 2
- Hide nav-to-parent button when editing
2019-04-18 14:47:32 -07:00
1ec529f360 Misc Fixes 2
- Remove explicit height;
2019-04-17 00:20:51 -07:00
cf6458c69d Misc Fixes 2
- Better approach to title in layout frames;
- Removed unneeded !important attribs;
2019-04-16 23:31:59 -07:00
3316500774 Misc Fixes 2
- Restore erroneous delete;
2019-04-16 22:03:30 -07:00
0f780587c0 Misc Fixes 2
- Fix alignment issue in c-so-view__headers;
2019-04-16 21:59:41 -07:00
ea69508e22 Misc Fixes 2
- Fix table resizing issue in Flex Layouts;
2019-04-16 14:45:08 -07:00
4274d8cc0b Misc Fixes 1 (#2382)
- Fix color issue for mobile menu icon;
- Fixed Chrome 73 overflow issue in main folder view;
- Better fixes for Chrome 73 overflow bug;
- Code cleanup;
2019-04-16 10:42:17 -07:00
1a2048332f Request latest data from alphanumerics (#2377) 2019-04-11 11:00:15 -07:00
38a875395f Markup changes to support VISTA About Dialog (#2375) 2019-04-11 10:19:25 -07:00
f601ab03e7 Add unsynced status class to legacy views (#2374) 2019-04-11 10:18:23 -07:00
ee1d92d4a9 Implements selection and reorder in stacked plots (#2371)
* working selection in stacked plots

* reorder in stacked plots works

* tabs code cleanup
2019-04-10 16:03:18 -07:00
548286bacd fixed filter field issue, and prevent elements pool from updating when selection has not changed (#2372) 2019-04-10 15:56:08 -07:00
9c9006d415 conditionally enable notebook button in preview (#2373) 2019-04-10 15:45:02 -07:00
87 changed files with 1166 additions and 725 deletions

View File

@ -1,6 +1,6 @@
<span class="h-indicator" ng-controller="DialogLaunchController">
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class="ls-indicator icon-box-with-arrow s-status-available"><span class="label">
<div class="c-indicator c-indicator--clickable icon-box-with-arrow s-status-available"><span class="label">
<a ng-click="launchProgress(true)">Known</a>
<a ng-click="launchProgress(false)">Unknown</a>
<a ng-click="launchError()">Error</a>

View File

@ -1,6 +1,6 @@
<span class="h-indicator" ng-controller="NotificationLaunchController">
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class="ls-indicator icon-bell s-status-available"><span class="label">
<div class="c-indicator c-indicator--clickable icon-bell s-status-available"><span class="label">
<a ng-click="newInfo()">Success</a>
<a ng-click="newError()">Error</a>
<a ng-click="newAlert()">Alert</a>

View File

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

View File

@ -9,17 +9,17 @@
ng-model="ngModel"
ng-show="ngModel.progressPerc !== undefined"></mct-include>
</div>
<div class="c-overlay__button-bar">
<button ng-repeat="dialogOption in ngModel.options"
</div>
<div class="c-overlay__button-bar">
<button ng-repeat="dialogOption in ngModel.options"
class="c-button"
ng-click="dialogOption.callback()">
{{dialogOption.label}}
</button>
<button class="c-button c-button--major"
{{dialogOption.label}}
</button>
<button class="c-button c-button--major"
ng-if="ngModel.primaryOption"
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</button>
</div>
{{ngModel.primaryOption.label}}
</button>
</div>
</div>

View File

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

View File

@ -77,14 +77,19 @@ define([], function () {
return promiseFn().then(nextFn);
};
}
if (!this.isScheduled(id)) {
this.clearTransactionFns[id] =
this.transactionService.addToTransaction(
chain(onCommit, release),
chain(onCancel, release)
);
/**
* Clear any existing persistence calls for object with given ID. This ensures only the most recent persistence
* call is executed. This should prevent stale objects being persisted and overwriting fresh ones.
*/
if (this.isScheduled(id)) {
this.clearTransactionsFor(id);
}
this.clearTransactionFns[id] =
this.transactionService.addToTransaction(
chain(onCommit, release),
chain(onCancel, release)
);
};
/**

View File

@ -93,24 +93,33 @@ define(
expect(mockOnCancel).toHaveBeenCalled();
});
it("ignores subsequent calls for the same object", function () {
manager.addToTransaction(
testId,
jasmine.createSpy(),
jasmine.createSpy()
);
expect(mockTransactionService.addToTransaction.calls.count())
.toEqual(1);
});
describe("Adds callbacks to transaction", function () {
beforeEach(function () {
spyOn(manager, 'clearTransactionsFor');
manager.clearTransactionsFor.and.callThrough();
});
it("accepts subsequent calls for other objects", function () {
manager.addToTransaction(
'other-id',
jasmine.createSpy(),
jasmine.createSpy()
);
expect(mockTransactionService.addToTransaction.calls.count())
.toEqual(2);
it("and clears pending calls if same object", function () {
manager.addToTransaction(
testId,
jasmine.createSpy(),
jasmine.createSpy()
);
expect(manager.clearTransactionsFor).toHaveBeenCalledWith(testId);
});
it("and does not clear pending calls if different object", function () {
manager.addToTransaction(
'other-id',
jasmine.createSpy(),
jasmine.createSpy()
);
expect(manager.clearTransactionsFor).not.toHaveBeenCalled();
});
afterEach(function () {
expect(mockTransactionService.addToTransaction.calls.count()).toEqual(2);
});
});
it("does not remove callbacks from the transaction", function () {

View File

@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class="ls-indicator {{ngModel.getCssClass()}}"
<div class="c-indicator {{ngModel.getCssClass()}}"
title="{{ngModel.getDescription()}}"
ng-show="ngModel.getText().length > 0">
<span class="label">{{ngModel.getText()}}</span>

View File

@ -54,6 +54,7 @@ define(
if (isDestroyed) {
return;
}
var removeSelectable = openmct.selection.selectable(
element[0],
scope.$eval(attrs.mctSelectable),

View File

@ -1,5 +1,5 @@
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div ng-show="notifications.length > 0" class="ls-indicator s-status-{{highest.severity}} icon-bell"
<div ng-show="notifications.length > 0" class="c-indicator c-indicator--clickable s-status-{{highest.severity}} icon-bell"
ng-controller="NotificationIndicatorController">
<span class="label">
<a ng-click="showNotificationsList()">

View File

@ -43,23 +43,10 @@ define([], function () {
var mutationTopic = topic('mutation');
mutationTopic.listen(function (domainObject) {
var persistence = domainObject.getCapability('persistence');
var wasActive = transactionService.isActive();
cacheService.put(domainObject.getId(), domainObject.getModel());
if (hasChanged(domainObject)) {
if (!wasActive) {
transactionService.startTransaction();
}
transactionService.addToTransaction(
persistence.persist.bind(persistence),
persistence.refresh.bind(persistence)
);
if (!wasActive) {
transactionService.commit();
}
persistence.persist();
}
});
}

View File

@ -24,22 +24,27 @@ define(
["../../src/runs/TransactingMutationListener"],
function (TransactingMutationListener) {
xdescribe("TransactingMutationListener", function () {
describe("TransactingMutationListener", function () {
var mockTopic,
mockMutationTopic,
mockCacheService,
mockTransactionService,
mockDomainObject,
mockModel,
mockPersistence;
beforeEach(function () {
mockTopic = jasmine.createSpy('topic');
mockMutationTopic =
jasmine.createSpyObj('mutation', ['listen']);
mockCacheService =
jasmine.createSpyObj('cacheService', [
'put'
]);
mockTransactionService =
jasmine.createSpyObj('transactionService', [
'isActive',
'startTransaction',
'addToTransaction',
'commit'
]);
mockDomainObject = jasmine.createSpyObj(
@ -52,18 +57,24 @@ define(
);
mockTopic.and.callFake(function (t) {
return (t === 'mutation') && mockMutationTopic;
expect(t).toBe('mutation');
return mockMutationTopic;
});
mockDomainObject.getId.and.returnValue('mockId');
mockDomainObject.getCapability.and.callFake(function (c) {
return (c === 'persistence') && mockPersistence;
expect(c).toBe('persistence');
return mockPersistence;
});
mockModel = {};
mockDomainObject.getModel.and.returnValue(mockModel);
mockPersistence.persisted.and.returnValue(true);
return new TransactingMutationListener(
mockTopic,
mockTransactionService
mockTransactionService,
mockCacheService
);
});
@ -72,48 +83,27 @@ define(
.toHaveBeenCalledWith(jasmine.any(Function));
});
[false, true].forEach(function (isActive) {
var verb = isActive ? "is" : "isn't";
it("calls persist if the model has changed", function () {
mockModel.persisted = Date.now();
function onlyWhenInactive(expectation) {
return isActive ? expectation.not : expectation;
}
//Mark the model dirty by setting the mutated date later than the last persisted date.
mockModel.modified = mockModel.persisted + 1;
describe("when a transaction " + verb + " active", function () {
var innerVerb = isActive ? "does" : "doesn't";
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
beforeEach(function () {
mockTransactionService.isActive.and.returnValue(isActive);
});
expect(mockPersistence.persist).toHaveBeenCalled();
});
describe("and mutation occurs", function () {
beforeEach(function () {
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
});
it("does not call persist if the model has not changed", function () {
mockModel.persisted = Date.now();
mockModel.modified = mockModel.persisted;
it(innerVerb + " start a new transaction", function () {
onlyWhenInactive(
expect(mockTransactionService.startTransaction)
).toHaveBeenCalled();
});
mockMutationTopic.listen.calls.mostRecent()
.args[0](mockDomainObject);
it("adds to the active transaction", function () {
expect(mockTransactionService.addToTransaction)
.toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Function)
);
});
it(innerVerb + " immediately commit", function () {
onlyWhenInactive(
expect(mockTransactionService.commit)
).toHaveBeenCalled();
});
});
});
expect(mockPersistence.persist).not.toHaveBeenCalled();
});
});
}

View File

@ -24,7 +24,6 @@ define([
"./src/actions/MoveAction",
"./src/actions/CopyAction",
"./src/actions/LinkAction",
"./src/actions/GoToOriginalAction",
"./src/actions/SetPrimaryLocationAction",
"./src/services/LocatingCreationDecorator",
"./src/services/LocatingObjectDecorator",
@ -41,7 +40,6 @@ define([
MoveAction,
CopyAction,
LinkAction,
GoToOriginalAction,
SetPrimaryLocationAction,
LocatingCreationDecorator,
LocatingObjectDecorator,
@ -104,14 +102,6 @@ define([
"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",
"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

@ -49,7 +49,7 @@ define(
};
ClockIndicator.prototype.getCssClass = function () {
return "t-indicator-clock icon-clock no-collapse float-right";
return "t-indicator-clock icon-clock no-collapse c-indicator--not-clickable";
};
ClockIndicator.prototype.getText = function () {

View File

@ -80,15 +80,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

@ -100,7 +100,7 @@ define(
}
CouchIndicator.prototype.getCssClass = function () {
return "icon-database " + this.state.statusClass;
return "c-indicator--clickable icon-database " + this.state.statusClass;
};
CouchIndicator.prototype.getGlyphClass = function () {

View File

@ -84,7 +84,7 @@ define(
}
ElasticIndicator.prototype.getCssClass = function () {
return "icon-database";
return "c-indicator--clickable icon-database";
};
ElasticIndicator.prototype.getGlyphClass = function () {
return this.state.glyphClass;

View File

@ -41,7 +41,7 @@ define(
}
LocalStorageIndicator.prototype.getCssClass = function () {
return "icon-database s-status-caution";
return "c-indicator--clickable icon-database s-status-caution";
};
LocalStorageIndicator.prototype.getGlyphClass = function () {
return 'caution';

View File

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

View File

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

View File

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

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

@ -1,3 +1,3 @@
<div class="ls-indicator" title="">
<div class="c-indicator c-indicator--clickable c-indicator--simple" title="">
<span class="label indicator-text"></span>
</div>

View File

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

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

@ -269,33 +269,63 @@
_.cloneDeep(this.selectedLayoutItems).forEach(selectedItem => {
if (selectedItem.type === 'line-view') {
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 {
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 => {
if (this.initialPositions[item.id]) {
let startingPosition = this.initialPositions[item.id];
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];
}
this.updateItemPosition(item, gridDelta);
}
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() {
this.mutate('configuration.items', this.layoutItems);
this.initialPositions = undefined;
this.startingMinX = undefined;
this.startingMinY = undefined;
this.startingMinX2 = undefined;
this.startingMinY2 = undefined;
},
mutate(path, value) {
this.openmct.objects.mutate(this.internalDomainObject, path, value);

View File

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

View File

@ -22,6 +22,7 @@
<template>
<layout-frame :item="item"
:grid-size="gridSize"
:title="domainObject && domainObject.name"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<object-frame v-if="domainObject"

View File

@ -176,7 +176,8 @@
let options = {
start: bounds.start,
end: bounds.end,
size: 1
size: 1,
strategy: 'latest'
};
this.openmct.telemetry.request(this.domainObject, options)
.then(data => {

View File

@ -42,7 +42,10 @@
<!-- Checkbox list, NOT editing -->
<template v-if="filter.possibleValues && !isEditing">
<span>{{persistedFilters[filter.comparator].join(', ')}}</span>
<span
v-if="persistedFilters[filter.comparator]">
{{persistedFilters[filter.comparator].join(', ')}}
</span>
</template>
</div>
</li>

View File

@ -69,16 +69,17 @@ export default {
}
} else {
if (!this.updatedFilters[key]) {
this.updatedFilters[key] = {};
this.$set(this.updatedFilters, key, {});
}
this.updatedFilters[key][comparator] = [value ? valueName : undefined];
this.$set(this.updatedFilters[key], comparator, [value ? valueName : undefined]);
}
this.$emit('updateFilters', this.keyString, this.updatedFilters);
},
updateTextFilter(key, comparator, value) {
if (!this.updatedFilters[key]) {
this.updatedFilters[key] = {};
this.$set(this.updatedFilters, key, {});
this.$set(this.updatedFilters[key], comparator, '');
}
this.updatedFilters[key][comparator] = value;
this.$emit('updateFilters', this.keyString, this.updatedFilters);

View File

@ -113,7 +113,8 @@
&__container-holder {
display: flex;
flex: 1 1 100%; // Must needs to be 100% to work
flex: 1 1 100%; // Must be 100% to work
overflow: auto;
// Columns by default
flex-direction: row;

View File

@ -0,0 +1,18 @@
<template>
<div class="c-indicator c-indicator--clickable icon-session">
<span class="label">
<button @click="globalClearEmit">Clear All Data</button>
</span>
</div>
</template>
<script>
export default {
inject: ['openmct'],
methods: {
globalClearEmit() {
this.openmct.notifications.emit('clear');
}
}
}
</script>

View File

@ -0,0 +1,48 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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/globalClearIndicator.vue',
'vue'
], function (
GlobaClearIndicator,
Vue
) {
return function plugin() {
return function install(openmct) {
let component = new Vue ({
provide: {
openmct
},
components: {
GlobalClearIndicator: GlobaClearIndicator.default
},
template: '<GlobalClearIndicator></GlobalClearIndicator>'
}),
indicator = {
element: component.$mount().$el
};
openmct.indicators.add(indicator);
};
};
});

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 => {
let panel = panels[key];
let domainObject = childObjects[key];
let identifier = undefined;
if (isTelemetry(domainObject)) {
items.push({
width: panel.dimensions[0],
height: panel.dimensions[1],
x: panel.position[0],
y: panel.position[1],
identifier: domainObject.identifier,
id: uuid(),
type: 'telemetry-view',
displayMode: 'all',
value: openmct.telemetry.getMetadata(domainObject).getDefaultDisplayValue(),
stroke: "transparent",
fill: "",
color: "",
size: "13px"
});
} else {
items.push({
width: panel.dimensions[0],
height: panel.dimensions[1],
x: panel.position[0],
y: panel.position[1],
identifier: domainObject.identifier,
id: uuid(),
type: 'subobject-view',
hasFrame: panel.hasFrame
// If object is a telemetry point, convert it to a plot and
// replace the object in migratedObject composition with the plot.
identifier = {
key: uuid(),
namespace: migratedObject.identifier.namespace
};
let plotObject = {
identifier: identifier,
location: domainObject.location,
name: domainObject.name,
type: "telemetry.plot.overlay"
};
let plotType = openmct.types.get('telemetry.plot.overlay');
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;
}
});
}
items.push({
width: panel.dimensions[0],
height: panel.dimensions[1],
x: panel.position[0],
y: panel.position[1],
identifier: identifier || domainObject.identifier,
id: uuid(),
type: 'subobject-view',
hasFrame: panel.hasFrame
});
});
migratedObject.configuration.items = items;
@ -167,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 = {};
@ -186,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];
@ -224,6 +238,7 @@ define([
{
check(domainObject) {
return domainObject.type === 'table' &&
domainObject.configuration &&
domainObject.configuration.table;
},
migrate(domainObject) {

View File

@ -43,12 +43,16 @@
<div class="l-view-section">
<div class="c-loading--overlay loading"
ng-show="!!currentRequest.pending"></div>
<div class="gl-plot child-frame"
<div class="gl-plot child-frame u-inspectable"
ng-repeat="telemetryObject in telemetryObjects"
ng-class="{
's-status-timeconductor-unsynced': telemetryObject
.getCapability('status')
.get('timeconductor-unsynced')
}"
mct-selectable="{
item: telemetryObject.useCapability('adapter'),
oldItem: telemetryObject
}">
<mct-overlay-plot domain-object="telemetryObject"></mct-overlay-plot>
</div>

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

@ -377,6 +377,19 @@ define([
delete this.unsubscribe;
}
this.fetch();
},
/**
* Clears the plot series, unsubscribes and resubscribes
* @public
*/
refresh: function () {
this.reset();
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
}
this.fetch();
}
});

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

@ -80,6 +80,10 @@ define([
'configuration.filters',
this.updateFiltersAndResubscribe.bind(this)
);
this.refresh = this.refresh.bind(this);
this.openmct.notifications.on('clear', this.refresh);
}
eventHelpers.extend(PlotController.prototype);
@ -148,7 +152,6 @@ define([
});
configStore.add(configId, config);
}
configStore.track(configId);
return config;
};
@ -157,7 +160,8 @@ define([
};
PlotController.prototype.destroy = function () {
configStore.untrack(this.config.id);
configStore.deleteStore(this.config.id);
this.stopListening();
if (this.checkForSize) {
clearInterval(this.checkForSize);
@ -166,6 +170,8 @@ define([
if (this.filterObserver) {
this.filterObserver();
}
this.openmct.notifications.off('clear', this.refresh);
};
PlotController.prototype.loadMoreData = function (range, purge) {
@ -263,6 +269,12 @@ define([
});
};
PlotController.prototype.refresh = function (updatedFilters) {
this.config.series.forEach(function (series) {
series.refresh();
});
};
/**
* Export view as JPG.
*/

View File

@ -79,6 +79,15 @@ define([
$scope.$broadcast('plot:tickWidth', _.max(tickWidthMap));
}
}
function compositionReorder(reorderPlan) {
let oldComposition = telemetryObjects.slice();
reorderPlan.forEach((reorder) => {
telemetryObjects[reorder.newIndex] = oldComposition[reorder.oldIndex];
});
}
thisRequest.pending += 1;
openmct.objects.get(domainObject.getId())
.then(function (obj) {
@ -89,10 +98,12 @@ define([
composition = openmct.composition.get(obj);
composition.on('add', addChild);
composition.on('remove', removeChild);
composition.on('reorder', compositionReorder);
composition.load();
unlisten = function () {
composition.off('add', addChild);
composition.off('remove', removeChild);
composition.off('reorder', compositionReorder);
};
});
}

View File

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

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

@ -7,7 +7,8 @@
}">
<div class="c-drop-hint"
@drop="onDrop"
ref="dropHint">
@dragenter="dragenter"
@dragleave="dragleave">
</div>
<div class="c-tabs-view__empty-message"
v-if="!tabsList.length > 0">Drag objects here to add them to this view.</div>
@ -197,13 +198,6 @@ export default {
document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend);
let dropHint = this.$refs.dropHint;
if (dropHint) {
dropHint.addEventListener('dragenter', this.dragenter);
dropHint.addEventListener('dragleave', this.dragleave);
}
},
destroyed() {
this.composition.off('add', this.addItem);
@ -212,12 +206,6 @@ export default {
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);
},
beforeDestroy() {
let dropHint = this.$refs.dropHint;
dropHint.removeEventListener('dragenter', this.dragenter);
dropHint.removeEventListener('dragleave', this.dragleave);
}
}
</script>

View File

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

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

@ -78,7 +78,7 @@
<!-- 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-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'}">
<tbody>
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
@ -284,7 +284,7 @@ import _ from 'lodash';
const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
const AUTO_SCROLL_TRIGGER_HEIGHT = 100;
const RESIZE_HOT_ZONE = 10;
const MOVE_TRIGGER_WAIT = 500;
const VERTICAL_SCROLL_WIDTH = 30;
@ -364,42 +364,48 @@ export default {
},
methods: {
updateVisibleRows() {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
let start = 0;
let end = VISIBLE_ROW_COUNT;
let filteredRows = this.table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length;
let start = 0;
let end = VISIBLE_ROW_COUNT;
let filteredRows = this.table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength;
} else {
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
let totalVisible = lastVisible - firstVisible;
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength;
} else {
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
let totalVisible = lastVisible - firstVisible;
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
} else if (end >= filteredRowsLength) {
end = filteredRowsLength;
start = end - VISIBLE_ROW_COUNT + 1;
}
}
this.rowOffset = start;
this.visibleRows = filteredRows.slice(start, end);
if (start < 0) {
start = 0;
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
} else if (end >= filteredRowsLength) {
end = filteredRowsLength;
start = end - VISIBLE_ROW_COUNT + 1;
}
this.updatingView = false;
});
}
this.rowOffset = start;
this.visibleRows = filteredRows.slice(start, end);
},
calculateFirstVisibleRow() {
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
let scrollTop = this.scrollable.scrollTop;
return Math.floor(scrollTop / this.rowHeight);
},
calculateLastVisibleRow() {
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.floor(bottomScroll / this.rowHeight);
let scrollBottom = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.ceil(scrollBottom / this.rowHeight);
},
updateHeaders() {
this.headers = this.table.configuration.getVisibleHeaders();
@ -443,71 +449,70 @@ export default {
}
this.table.sortBy(this.sortOptions);
},
scroll() {
if (!this.processingScroll) {
this.processingScroll = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
this.synchronizeScrollX();
scroll () {
this.updateVisibleRows();
this.synchronizeScrollX();
if (this.shouldSnapToBottom()) {
this.autoScroll = true;
} else {
// If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = false;
}
this.processingScroll = false;
});
if (this.shouldSnapToBottom()) {
this.autoScroll = true;
} else {
// If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = false;
}
},
shouldSnapToBottom() {
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
},
scrollToBottom() {
this.scrollable.scrollTop = this.scrollable.scrollHeight;
this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
},
synchronizeScrollX() {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
},
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) {
rowsAdded (rows) {
this.setHeight();
let sizingRow;
if (Array.isArray(rows)) {
sizingRow = rows[0];
} else {
sizingRow = rows;
}
if (!this.sizingRows[sizingRow.objectKeyString]) {
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
this.$nextTick().then(this.calculateColumnWidths);
}
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
if (this.autoScroll) {
this.$nextTick().then(this.scrollToBottom);
}
this.updatingView = false;
});
if (this.autoScroll) {
this.scrollToBottom();
}
this.updateVisibleRows();
},
rowsRemoved(rows) {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
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() {
const headerKeys = Object.keys(this.headers);
@ -595,7 +600,11 @@ export default {
this.calculateTableSize();
// 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.
this.scrollable.scrollTop = scrollTop;
if (this.autoScroll) {
this.scrollToBottom();
} else {
this.scrollable.scrollTop = scrollTop;
}
width = el.clientWidth;
height = el.clientHeight;
}
@ -608,6 +617,9 @@ export default {
this.filterChanged = _.debounce(this.filterChanged, 500);
},
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-removed', this.removeObject);
@ -621,6 +633,7 @@ export default {
//Default sort
this.sortOptions = this.table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');

View File

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

View File

@ -49,6 +49,21 @@ button {
}
}
&[class*='__collapse-button'] {
box-shadow: none;
background: $splitterBtnColorBg;
color: $splitterBtnColorFg;
border-radius: $smallCr;
font-size: 6px;
line-height: 90%;
padding: 3px 15px;
@include hover() {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
}
&.is-active {
background: $colorBtnActiveBg;
color: $colorBtnActiveFg;
@ -70,13 +85,7 @@ button {
@include cClickIconButton();
&--menu {
&:after {
content: $glyph-icon-arrow-down;
font-family: symbolsfont;
font-size: 0.7em;
margin-left: floor($interiorMarginSm * 0.8);
opacity: 0.5;
}
@include hasMenu();
}
}
@ -126,7 +135,7 @@ button {
/******************************************************** DISCLOSURE CONTROLS */
/********* Disclosure Button */
// Provides a downward arrow icon that when clicked displays a context menu
// Provides a downward arrow icon that when clicked displays additional options and/or info.
// Always placed AFTER an element
.c-disclosure-button {
@include cClickIcon();
@ -393,6 +402,11 @@ select {
color: $colorMenuIc;
font-size: 1em;
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

@ -51,6 +51,22 @@ mct-plot {
}
}
.is-editing {
.gl-plot.child-frame {
&:hover {
background: rgba($editUIColorBg, 0.1);
box-shadow: inset rgba($editUIColorBg, 0.8) 0 0 0 1px;
}
&[s-selected] {
border: 1px solid $editUIColorFg !important;
color: $editUIColorFg !important;
box-shadow: $editFrameSelectedShdw;
z-index: 2;
}
}
}
.gl-plot {
color: $colorPlotFg;
display: flex;
@ -548,3 +564,10 @@ mct-plot {
top: 0; bottom: 0;
}
}
.s-status-timeconductor-unsynced {
.t-object-alert.t-alert-unsynced {
@extend .icon-alert-triangle;
color: $colorPausedBg;
}
}

View File

@ -782,6 +782,10 @@ mct-indicators mct-include {
display: contents;
}
/*
// MOVED TO INDICATORS.VUE
.ls-indicator {
$bg: rgba(white, 0.2) !important;
$hbg: $colorStatusBarBg;
@ -859,7 +863,7 @@ mct-indicators mct-include {
border-radius: $br;
font-size: .6rem;
left: 0;
bottom: 140%;
top: 140%;
opacity: 0;
padding: $interiorMarginSm $interiorMargin;
position: absolute;
@ -873,8 +877,8 @@ mct-indicators mct-include {
content: '';
display: block;
position: absolute;
top: 100%;
@include triangle('down', $size: 4px, $ratio: 1, $color: $hbg);
bottom: 100%;
@include triangle('up', $size: 4px, $ratio: 1, $color: $hbg);
}
}
@ -894,7 +898,8 @@ mct-indicators mct-include {
}
}
/* Mobile */
// Hide the clock indicator when we're phone portrait
body.phone.portrait {
.ls-indicator.t-indicator-clock {
@ -902,6 +907,8 @@ body.phone.portrait {
}
}
*/
/************************************************* DATETIME UI */
@mixin complexFieldHolder($myW) {
width: $myW + $interiorMargin;

View File

@ -420,19 +420,9 @@
}
}
@mixin cClickIconButton() {
// A clickable element that just includes the icon
// Background is displayed on hover
// Padding is included to facilitate a bigger hit area
// Make the icon bigger relative to its container
@include cControl();
background: none;
box-shadow: none;
cursor: pointer;
@mixin cClickIconButtonLayout() {
$pLR: 4px;
$pTB: 4px;
transition: $transOut;
border-radius: $controlCr;
padding: $pTB $pLR;
&:before,
@ -441,6 +431,20 @@
// Needed for c-togglebutton.
font-size: 1.25em;
}
}
@mixin cClickIconButton() {
// A clickable element that just includes the icon
// Background is displayed on hover
// Padding is included to facilitate a bigger hit area
// Make the icon bigger relative to its container
@include cControl();
@include cClickIconButtonLayout();
background: none;
box-shadow: none;
cursor: pointer;
transition: $transOut;
border-radius: $controlCr;
@include hover() {
transition: $transIn;
@ -477,6 +481,16 @@
}
}
@mixin hasMenu() {
&:after {
content: $glyph-icon-arrow-down;
font-family: symbolsfont;
font-size: 0.7em;
margin-left: floor($interiorMarginSm * 0.8);
opacity: 0.5;
}
}
@mixin cSelect($bg, $fg, $arwClr, $shdw) {
$svgArwClr: str-slice(inspect($arwClr), 2, str-length(inspect($arwClr))); // Remove initial # in color value
background: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3e%3cpath fill='%23#{$svgArwClr}' d='M5 5l5-5H0z'/%3e%3c/svg%3e"), $bg;

View File

@ -127,7 +127,7 @@ tr {
.s-status-icon-ok:before { content: $glyph-icon-check; }
/*************************************************** INDICATOR COLORING */
.ls-indicator {
.c-indicator {
&.s-status-info {
@include indicatorStatusColors($colorInfo);
}

View File

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

View File

@ -25,8 +25,8 @@
// Do not include anything that renders to CSS!
@import "constants";
@import "constants-mobile.scss";
//@import "constants-espresso"; // TEMP
@import "constants-snow"; // TEMP
@import "constants-espresso"; // TEMP
//@import "constants-snow"; // TEMP
//@import "constants-maelstrom";
@import "mixins";
@import "animations";

View File

@ -0,0 +1,22 @@
<template>
<div class="c-indicator no-collapse icon-clock c-current-datetime">
<span class="label">{{ currentDateTime }} UTC</span>
</div>
</template>
<script>
import moment from 'moment';
export default {
data() {
return {
currentDateTime: moment.utc().format('LTS')
}
},
mounted: function() {
setInterval(()=>{
this.currentDateTime = moment.utc().format('YYYY/MM/DD HH:mm:ss')
},100)
}
}
</script>

View File

@ -26,8 +26,8 @@
'has-complex-content': complexContent
}">
<div class="c-so-view__header">
<div class="c-so-view__header__name"
:class="cssClass">
<div class="c-so-view__header__icon" :class="cssClass"></div>
<div class="c-so-view__header__name">
{{ domainObject && domainObject.name }}
</div>
<context-menu-drop-down
@ -63,12 +63,16 @@
align-items: center;
margin-bottom: $interiorMargin;
&__icon {
flex: 0 0 auto;
margin-right: $interiorMarginSm;
opacity: 0.5;
}
&__name {
@include headerFont(1em);
display: flex;
&:before {
margin-right: $interiorMarginSm;
}
@include ellipsize();
flex: 0 1 auto;
}
}
@ -88,19 +92,6 @@
}
}
&__name {
@include ellipsize();
@include headerFont(1.2em);
flex: 0 1 auto;
&:before {
// Object type icon
flex: 0 0 auto;
margin-right: $interiorMarginSm;
opacity: 0.5;
}
}
&__local-controls {
position: absolute;
top: $interiorMargin; right: $interiorMargin;

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

@ -67,6 +67,7 @@
}
</style>
<script>
import _ from 'lodash';
import Search from '../components/search.vue';
import ObjectLabel from '../components/ObjectLabel.vue';
@ -82,7 +83,8 @@ export default {
isEditing: this.openmct.editor.isEditing(),
parentObject: undefined,
currentSearch: '',
isDragging: false
isDragging: false,
selection: []
}
},
mounted() {
@ -99,6 +101,10 @@ export default {
this.showSelection(this.openmct.selection.get());
},
showSelection(selection) {
if (_.isEqual(this.selection, selection)) {
return;
}
this.selection = selection;
this.elements = [];
this.elementsCache = {};
this.listeners = [];

View File

@ -1,23 +1,24 @@
<template>
<div class="c-about c-about--splash">
<div v-if="branding.aboutHtml" class="s-text l-content" v-html="branding.aboutHtml"></div>
<div v-else class="c-about__image c-splash-image"></div>
<div class="c-about__image c-splash-image"></div>
<div class="c-about__text s-text">
<h1 class="l-title s-title">Open MCT</h1>
<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 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 <a target="_blank" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>.</p>
<p>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.</p>
<p>Open MCT includes source code licensed under additional open source licenses. See the Open Source Licenses file included with this distribution or <a @click="showLicenses">click here for third party licensing information</a>.</p>
<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>
<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 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 <a target="_blank" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>.</p>
<p>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.</p>
<p>Open MCT includes source code licensed under additional open source licenses. See the Open Source Licenses file included with this distribution or <a @click="showLicenses">click here for third party licensing information</a>.</p>
</div>
<h2>Version Information</h2>
<ul class="t-info l-info s-info">
<li>Version: {{buildInfo.version || 'Unknown'}}</li>
<li>Build Date: {{buildInfo.buildDate || 'Unknown'}}</li>
<li>Revision: {{buildInfo.revision || 'Unknown'}}</li>
<li>Branch: {{buildInfo.branch || 'Unknown'}}</li>
</ul>
</div>
<h2>Version Information</h2>
<ul class="t-info l-info s-info">
<li>Version: {{buildInfo.version || 'Unknown'}}</li>
<li>Build Date: {{buildInfo.buildDate || 'Unknown'}}</li>
<li>Revision: {{buildInfo.revision || 'Unknown'}}</li>
<li>Branch: {{buildInfo.branch || 'Unknown'}}</li>
</ul>
</div>
</div>
</template>

View File

@ -289,10 +289,14 @@ const PLACEHOLDER_OBJECT = {};
&__nav-to-parent-button {
// This is an icon-button
$p: $interiorMarginLg;
$p: $interiorMargin;
margin-right: $interiorMargin;
padding-left: $p;
padding-right: $p;
.is-editing & {
display: none;
}
}
&__object-name--w {

View File

@ -4,7 +4,9 @@
}">
<div class="l-shell__head">
<CreateButton class="l-shell__create-button"></CreateButton>
<div class="l-shell__controls">
<indicators class="l-shell__head-section l-shell__indicators"></indicators>
<notification-banner></notification-banner> <!-- TODO: MAKE SURE THIS PLACEMENT WORKS PROPERLY -->
<div class="l-shell__head-section l-shell__controls">
<button class="c-icon-button c-icon-button--major icon-new-window" title="Open in a new browser tab"
@click="openInNewTab"
target="_blank">
@ -15,6 +17,8 @@
</button>
</div>
<app-logo></app-logo>
<!--button class="l-shell__head__collapse-button c-button"
@click="toggleShellHead"></button-->
</div>
<multipane class="l-shell__main"
type="horizontal">
@ -44,9 +48,6 @@
<Inspector :isEditing="isEditing" ref="inspector"></Inspector>
</pane>
</multipane>
<div class="l-shell__status">
<StatusBar></StatusBar>
</div>
</div>
</template>
@ -61,12 +62,13 @@
flex-flow: column nowrap;
overflow: hidden;
&__status {
/* &__status {
min-width: 95%;
margin-right: 1%;
background: $colorStatusBarBg;
color: $colorStatusBarFg;
height: 24px;
padding: $interiorMarginSm;
}
}*/
&__pane-tree {
width: 40%;
@ -75,6 +77,7 @@
// For mobile, collapse button becomes menu icon
body.mobile & {
@include cClickIconButton();
color: $colorKey !important;
position: absolute;
right: -2 * nth($shellPanePad, 2); // Needs to be -1 * when pane is collapsed
top: 0;
@ -163,11 +166,25 @@
align-items: center;
background: $colorHeadBg;
justify-content: space-between;
padding: $interiorMargin;
padding: $interiorMargin $interiorMargin + 2;
> [class*="__"] + [class*="__"] {
margin-left: $interiorMargin;
}
[class*='__collapse-button']:before {
content: $glyph-icon-arrow-down;
font-size: 1.1em;
}
&-section {
// Subdivides elements across the head
@include ellipsize();
border-right: 1px solid $colorInteriorBorder;
display: flex;
flex: 0 1 auto;
padding-right: $interiorMargin;
}
}
&__create-button,
@ -175,17 +192,18 @@
flex: 0 0 auto;
}
&__controls {
flex: 1 1 100%;
display: flex;
justify-content: flex-end;
margin-right: 2.5%;
&__create-button { margin-right: $interiorMarginLg; }
&__indicators {
flex: 1 1 auto;
[class*='indicator-clock'] { order: 99; }
}
/******************************* MAIN AREA */
&__main-container {
// Wrapper for main views
flex: 1 1 auto !important;
height: 0; // Chrome 73 overflow bug fix
overflow: auto;
}
@ -265,9 +283,10 @@
import multipane from './multipane.vue';
import pane from './pane.vue';
import BrowseBar from './BrowseBar.vue';
import StatusBar from './status-bar/StatusBar.vue';
import Toolbar from '../toolbar/Toolbar.vue';
import AppLogo from './AppLogo.vue';
import Indicators from './status-bar/Indicators.vue';
import NotificationBanner from './status-bar/NotificationBanner.vue';
var enterFullScreen = () => {
var docElm = document.documentElement;
@ -308,9 +327,10 @@
multipane,
pane,
BrowseBar,
StatusBar,
Toolbar,
AppLogo
AppLogo,
Indicators,
NotificationBanner
},
mounted() {
this.openmct.editor.on('isEditing', (isEditing)=>{
@ -355,6 +375,9 @@
}
this.hasToolbar = structure.length > 0;
},
toggleShellHead() {
return true;
}
}
}

View File

@ -52,13 +52,12 @@
&__tree {
flex: 1 1 auto;
height: 100%;
height: 0; // Chrome 73 overflow bug fix
}
}
.c-tree {
@include userSelectNone();
height: 100%; // Chrome 73 overflow bug fix
overflow-x: hidden;
overflow-y: auto;
padding-right: $interiorMargin;

View File

@ -148,21 +148,6 @@
font-size: floor(12px * .9);
}
&__collapse-button {
box-shadow: none;
background: $splitterBtnColorBg;
color: $splitterBtnColorFg;
border-radius: $smallCr;
font-size: 6px;
line-height: 90%;
padding: 3px 15px;
@include hover() {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
}
&__label {
// Name of the pane
@include ellipsize();
@ -194,8 +179,8 @@
/********************************* STYLES FOR DESKTOP COLLAPSED PANES, ALL ORIENTATIONS */
$d: nth($splitterBtnD, 1);
flex-basis: $d;
min-width: $d !important;
min-height: $d !important;
min-width: $d;
min-height: $d;
> .l-pane__handle {
display: none;

View File

@ -17,10 +17,132 @@
at runtime from the About dialog for additional information.
-->
<template>
<span id='status' class='status-holder'></span>
</template>
<style lang="scss">
@import "~styles/sass-base";
.l-indicators {
display: flex;
align-items: center;
}
.c-indicator {
@include cClickIconButtonLayout();
&--clickable {
@include cClickIconButton();
@include hasMenu();
}
$bg: rgba(white, 0.2) !important;
$hbg: $colorStatusBarBg;
$hshdw: rgba(white, 0.4) 0 0 3px;
$br: $controlCr;
$hoverYOffset: -35px;
/*background: transparent !important;*/
border-radius: $br;
/*display: inline-block;*/
/*position: relative;*/
//padding: 1px $interiorMarginSm; // Use padding instead of margin to keep hover chatter to a minimum
text-transform: uppercase;
&:before {
/*display: inline-block;*/
}
.label {
// Hover bubbles that appear when hovering on an Indicator
display: inline-block;
a,
button,
s-button,
.c-button {
// Make <a> in label look like buttons
transition: $transIn;
background: transparent;
border: 1px solid rgba($colorStatusBarFg, 0.5);
border-radius: $br;
box-sizing: border-box;
color: inherit;
font-size: inherit;
height: auto;
line-height: normal;
padding: 0 2px;
&:hover {
background: $bg;
color: #fff;
}
}
[class*='icon-'] {
// If any elements within label include the class 'icon-*' then deal with their :before's
&:before {
font-size: 0.8em;
margin-right: $interiorMarginSm;
}
}
}
&.no-collapse {
display: flex;
flex-flow: row nowrap;
align-items: center;
> *,
&:before {
flex: 1 1 auto;
}
&:before {
margin-right: $interiorMarginSm;
}
}
&:not(.no-collapse) {
&:before {
margin-right: 0 !important;
}
.label {
transition: all 250ms ease-in 100ms;
background: $hbg;
border-radius: $br;
font-size: .6rem;
left: 0;
top: 140%;
opacity: 0;
padding: $interiorMarginSm $interiorMargin;
position: absolute;
transform-origin: 10px 100%;
transform: scale(0.0);
white-space: nowrap;
z-index: 50;
&:before {
// Infobubble-style arrow element
content: '';
display: block;
position: absolute;
bottom: 100%;
@include triangle('up', $size: 4px, $ratio: 1, $color: $hbg);
}
}
}
&.float-right {
float: right;
}
}
/* Mobile */
// Hide the clock indicator when we're phone portrait
body.phone.portrait {
.c-indicator.t-indicator-clock {
display: none;
}
}
</style>
<script>
@ -31,10 +153,11 @@
this.openmct.indicators.indicatorObjects.forEach((indicator) => {
// So that we can consistently position indicator elements,
// guarantee that they are wrapped in an element we control
var wrapperNode = document.createElement('span');
wrapperNode.className = 'l-indicator';
wrapperNode.appendChild(indicator.element);
this.$el.appendChild(wrapperNode);
// CH: fuck that...
// var wrapperNode = document.createElement('span');
// wrapperNode.className = 'u-contents';
// wrapperNode.appendChild(indicator.element);
this.$el.appendChild(indicator.element);
});
}
}

View File

@ -1,42 +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.
-->
<template>
<span class="c-status">
<indicators></indicators>
<notification-banner></notification-banner>
</span>
</template>
<style lang="scss">
.c-status {
width: 100%;
}
</style>
<script>
import Indicators from './Indicators.vue';
import NotificationBanner from './NotificationBanner.vue';
export default {
components: {
Indicators,
NotificationBanner
}
}
</script>

View File

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

View File

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

View File

@ -33,7 +33,8 @@
</div>
<div class="l-browse-bar__end">
<div class="l-browse-bar__actions">
<button class="l-browse-bar__actions__edit c-button icon-notebook"
<button v-if="notebookEnabled"
class="l-browse-bar__actions__edit c-button icon-notebook"
title="New Notebook entry"
@click="snapshot">
</button>
@ -101,16 +102,21 @@
return {
domainObject: domainObject,
type: type
type: type,
notebookEnabled: false
};
},
mounted() {
let viewProvider = this.openmct.objectViews.get(this.domainObject)[0];
this.view = viewProvider.view(this.domainObject);
this.view.show(this.$refs.objectView, false);
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
if (this.openmct.types.get('notebook')) {
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
this.notebookEnabled = true;
}
},
destroy() {
destroyed() {
this.view.destroy();
}
}