Compare commits

...

38 Commits

Author SHA1 Message Date
b74507bd1b merge with tcr 2019-07-22 15:10:59 -07:00
7fe4a77c43 Minor table refactoring (#2431)
* Refactor how tables process incoming data

* Fixed build in linux

* Added 'buttons' slot to tables

* Revert theme change
2019-07-22 15:08:42 -07:00
1461a209d9 remove key from v-for in table.vue 2019-07-22 14:20:18 -07:00
bfb89c7ece make reviewer requested changes 2019-07-22 14:17:04 -07:00
f45e1623a1 add isSelectable functionality 2019-07-16 15:28:54 -07:00
09c4ee48b2 Fixed regression errors in markup 2019-07-16 10:20:20 -07:00
e8997917b2 merge with latest tcr and fix conflicts 2019-07-15 11:32:23 -07:00
8578d78c51 Fixes VISTA issue 635 scrolling messages overflow problem (#2428) 2019-07-12 10:52:16 -07:00
362e565a09 Global and Local Clear (#2418)
* first proto of global clear, working on tables

* global clear works on plots

* styling

* 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;

* Status bar migration to top of layout, WIP

- Refine styles and markup for Indicators;
- Better separation of styles for clickable and non-clickable
Indicators;

* 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;

* Status Bar Migration WIP

- Significant progress styling Indicators and their hover bubbles;
- Pull back from clickable Indicators to hover approach;
- Better theme-based constants for Indicator menu-related colors;

* Status Bar Migration WIP

- Significant refactor of label element naming in multiple indicator
markup files;
- Refactor label-related CSS;
- Better class naming: no-collapse > no-minify;
- Refactor example *-launch files to use buttons instead of <a> tags;
- Significant progress on expanding shell head and button styling;

* Status Bar Migration WIP

- Cleanups, sanding on Indicator CSS;
- Added local storage retention for head expanded state;
- Adjust dark theme colors for $colorWarningHi for better legibility;
- Other minor tweaks and fixes;

* Status Bar Migration WIP

- Suppress background in Indicators;
- Restore Snow as default theme;

* add a local clear action, rename plugin

* objectViews extends eventemitter, table view provider provides an onClearData function that is called from ObjectView when clear event is emitted. TODO - support plots

* add support for plots via legacy view provider

* add test for clearDataAction

* remove focus from test file

* install the following plugins by default:
Import Export
Folder View
Tabs View
Flexible Layout
LAD Table
Go To Original Action

* update test to include plugin level tests

* remove focus from unit test
2019-07-11 16:40:26 -07:00
faa1d01499 use command key to mark multiple 2019-07-03 16:35:34 -07:00
a9c245a7db working concurrent select 2019-07-03 16:05:14 -07:00
85dd5ce00c change logic to marking/selecting 2019-07-03 15:19:35 -07:00
ec86ebd692 Refined styling for c-button in an object frame
- More compact, better alignment, font sizing and padding;
2019-07-03 15:14:41 -07:00
6c69694dca Layout improvements for table and control bar elements
- Table markup re-org'd;
- New .c-separator css class;
- Renamed .c-table__control-bar to .c-table-control-bar;
- Added label to Pause button;
- TODO: refine styling for table within frame in Layouts;
2019-07-03 15:03:52 -07:00
0b8bf682a4 working multi select
tables are paused when user selects a row
2019-07-03 11:10:35 -07:00
9517c1f2cd [Filters] various bugs in telemetry table filters (#2425)
* Update the filters object properly when both checkboxes are deselected. Check composition before loading. Modify logic for mixed filters.

* Get compostion from the global context

* Use Set to store keyStrings

* Rename variables for clarity and add comment. Also add keystring to telemetryKeyStrings when an object is added.

* Use size to get the size of the set instead of length. Remove telemetry keystring from the configuration filters when object is removed from the composition and update the indicator label.
2019-07-02 16:17:15 -07:00
e72ba5e8bf Merge branch 'select-table-rows' of https://github.com/nasa/openmct into select-table-rows 2019-07-02 14:01:23 -07:00
3caba5efac working pause 2019-07-02 14:01:20 -07:00
efdd80bd57 Styling for marked table rows
- CSS class applied;
- Export button label modified;
2019-07-02 10:12:02 -07:00
262d35804d [Telemetry Table] Display applied filters (#2421)
* Display a list of filters that are applied to telemetry objects in a telemetry table.

* - Display 'Mixed' if filters have mixed values.
- Use table configuration domain object to get composition.

* Filter indicator styling WIP

- Markup, class names added;
- TODO: 'Mixed' and commas to be added via CSS, icon and bg coloring;

* Filter indicators styling

- CSS, markup;
- Added dynamic labeling and titling for mixed/non-mixed filter states;
- Theme colors defined and added;
- Added new filter icon glyphs for both 16px and 12px fonts;
- Revised/normalized font project and glyph file names;

* Filter indicators styling

- Adding missed Icomoon project file;

* Filter indicators styling

- Reverting mistakenly changed file;

* Filter indicators styling

- Minor fix to theme sass;
- Sync maelstrom theme;

* Fix indentation

* Set label and title to empty string initially.

* Keep the default snow.
2019-06-26 14:25:02 -07:00
e0587bf0e7 Status styling (#2422)
- Primarily needed by VISTA Data Products table UI;
- Adds new styling for inline links with icons;
- Adds new status colors in theme files;
2019-06-25 17:02:23 -07:00
f1494fd285 Vista table sync (#2423)
* Working version of integrated tables

* Fixed bug with multi-composition in tables

* Changes to support tables from VISTA
2019-06-25 13:56:39 -07:00
832c4d9816 support row selection backwards 2019-06-19 15:15:26 -07:00
3a5024d38d enable shift click to select multiple rows 2019-06-19 14:48:21 -07:00
dcd6334036 add a unmark all rows button 2019-06-18 15:44:49 -07:00
728b39164e first pass 2019-06-17 14:48:05 -07:00
884aec8ea0 Alpha-numeric printf format (#2416)
* Implement an inspector view provider to display a component that allows setting printf format for alphanumeric items in a display layout.

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

* Use lodash function to find index.

* Simplify code.

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

* use alert icon and improve message

* add a but in message

* change alert message to a more generic sentence:

* add a period
2019-06-10 15:17:43 -07:00
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
95 changed files with 2146 additions and 884 deletions

View File

@ -99,10 +99,10 @@ define([
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) { GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return _.extend( return _.extend(
{}, {},
domainObject.telemetry, domainObject.telemetry,
METADATA_BY_TYPE[domainObject.type] METADATA_BY_TYPE[domainObject.type]
); );
}; };
return GeneratorMetadataProvider; return GeneratorMetadataProvider;

View File

@ -1,9 +1,9 @@
<span class="h-indicator" ng-controller="DialogLaunchController"> <span class="h-indicator" ng-controller="DialogLaunchController">
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- 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 c-indicator__label">
<a ng-click="launchProgress(true)">Known</a> <button ng-click="launchProgress(true)">Known</button>
<a ng-click="launchProgress(false)">Unknown</a> <button ng-click="launchProgress(false)">Unknown</button>
<a ng-click="launchError()">Error</a> <button ng-click="launchError()">Error</button>
<a ng-click="launchInfo()">Info</a> <button ng-click="launchInfo()">Info</button>
</span></div> </span></div>
</span> </span>

View File

@ -1,9 +1,9 @@
<span class="h-indicator" ng-controller="NotificationLaunchController"> <span class="h-indicator" ng-controller="NotificationLaunchController">
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- 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 c-indicator__label">
<a ng-click="newInfo()">Success</a> <button ng-click="newInfo()">Success</button>
<a ng-click="newError()">Error</a> <button ng-click="newError()">Error</button>
<a ng-click="newAlert()">Alert</a> <button ng-click="newAlert()">Alert</button>
<a ng-click="newProgress()">Progress</a> <button ng-click="newProgress()">Progress</button>
</span></div> </span></div>
</span> </span>

View File

@ -50,7 +50,6 @@
openmct.install(openmct.plugins.Generator()); openmct.install(openmct.plugins.Generator());
openmct.install(openmct.plugins.ExampleImagery()); openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.UTCTimeSystem()); openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.ImportExport());
openmct.install(openmct.plugins.AutoflowView({ openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel" type: "telemetry.panel"
})); }));
@ -80,13 +79,9 @@
})); }));
openmct.install(openmct.plugins.SummaryWidget()); openmct.install(openmct.plugins.SummaryWidget());
openmct.install(openmct.plugins.Notebook()); openmct.install(openmct.plugins.Notebook());
openmct.install(openmct.plugins.FolderView());
openmct.install(openmct.plugins.Tabs());
openmct.install(openmct.plugins.FlexibleLayout());
openmct.install(openmct.plugins.LADTable());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay'])); openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration()); openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.GoToOriginalAction()); openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked']));
openmct.start(); openmct.start();
</script> </script>
</html> </html>

View File

@ -4,6 +4,7 @@
"description": "The Open MCT core platform", "description": "The Open MCT core platform",
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"acorn": "6.2.0",
"angular": "1.4.14", "angular": "1.4.14",
"angular-route": "1.4.14", "angular-route": "1.4.14",
"babel-eslint": "8.2.6", "babel-eslint": "8.2.6",
@ -55,7 +56,7 @@
"node-bourbon": "^4.2.3", "node-bourbon": "^4.2.3",
"node-sass": "^4.9.2", "node-sass": "^4.9.2",
"painterro": "^0.2.65", "painterro": "^0.2.65",
"printj": "^1.1.0", "printj": "^1.2.1",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"request": "^2.69.0", "request": "^2.69.0",
"split": "^1.0.0", "split": "^1.0.0",

View File

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

View File

@ -1,8 +1,8 @@
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> <!-- 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"> ng-controller="NotificationIndicatorController">
<span class="label"> <span class="label c-indicator__label">
<a ng-click="showNotificationsList()"> <button ng-click="showNotificationsList()">
{{notifications.length}} Notification<span ng-show="notifications.length > 1">s</span></a> {{notifications.length}} Notification<span ng-show="notifications.length > 1">s</span></button>
</span><span class="count">{{notifications.length}}</span> </span><span class="c-indicator__count">{{notifications.length}}</span>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -246,12 +246,21 @@ define([
this.branding = BrandingAPI.default; this.branding = BrandingAPI.default;
this.legacyRegistry = defaultRegistry; this.legacyRegistry = defaultRegistry;
// Plugin's that are installed by default
this.install(this.plugins.Plot()); this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable()); this.install(this.plugins.TelemetryTable());
this.install(PreviewPlugin.default()); this.install(PreviewPlugin.default());
this.install(LegacyIndicatorsPlugin()); this.install(LegacyIndicatorsPlugin());
this.install(LicensesPlugin.default()); this.install(LicensesPlugin.default());
this.install(RemoveActionPlugin.default()); this.install(RemoveActionPlugin.default());
this.install(this.plugins.ImportExport());
this.install(this.plugins.FolderView());
this.install(this.plugins.Tabs());
this.install(this.plugins.FlexibleLayout());
this.install(this.plugins.LADTable());
this.install(this.plugins.GoToOriginalAction());
if (typeof BUILD_CONSTANTS !== 'undefined') { if (typeof BUILD_CONSTANTS !== 'undefined') {
this.install(buildInfoPlugin(BUILD_CONSTANTS)); this.install(buildInfoPlugin(BUILD_CONSTANTS));

View File

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

View File

@ -108,6 +108,9 @@ define([
link(); link();
} }
}, },
onClearData() {
scope.$broadcast('clearData');
},
destroy: function () { destroy: function () {
element.off(); element.off();
element.remove(); element.remove();

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@ define(['zepto', './res/indicator-template.html'],
this.openmct = openmct; this.openmct = openmct;
this.element = $(indicatorTemplate)[0]; this.element = $(indicatorTemplate)[0];
this.textElement = this.element.querySelector('.indicator-text'); this.textElement = this.element.querySelector('.js-indicator-text');
//Set defaults //Set defaults
this.text('New Indicator'); this.text('New Indicator');

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> <span class="label js-indicator-text c-indicator__label"></span>
</div> </div>

View File

@ -93,7 +93,7 @@
&.message-severity-error:before { &.message-severity-error:before {
@include legacyMessage(); @include legacyMessage();
content: $glyph-icon-alert-triangle; content: $glyph-icon-alert-triangle;
color: $colorWarningLo; color: $colorWarningHi;
} }
// Messages in a list // Messages in a list

View File

@ -69,6 +69,7 @@
flex: 1 1 auto; flex: 1 1 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden;
} }
&__top-bar { &__top-bar {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,39 @@
/*****************************************************************************
* 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 ClearDataAction {
constructor(openmct, appliesToObjects) {
this.name = 'Clear Data';
this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
this._openmct = openmct;
this._appliesToObjects = appliesToObjects;
}
invoke(objectPath) {
this._openmct.objectViews.emit('clearData', objectPath[0]);
}
appliesTo(objectPath) {
let contextualDomainObject = objectPath[0];
return this._appliesToObjects.filter(type => contextualDomainObject.type === type).length;
}
}

View File

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

View File

@ -0,0 +1,54 @@
/*****************************************************************************
* 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',
'./clearDataAction',
'vue'
], function (
GlobaClearIndicator,
ClearDataAction,
Vue
) {
return function plugin(appliesToObjects) {
appliesToObjects = appliesToObjects || [];
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);
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
};
};
});

View File

@ -0,0 +1,62 @@
/*****************************************************************************
* 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 ClearDataActionPlugin from '../plugin.js';
import ClearDataAction from '../clearDataAction.js';
describe('When the Clear Data Plugin is installed,', function () {
var mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']),
mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']),
mockContextMenuProvider = jasmine.createSpyObj('contextMenu', ['registerAction']),
openmct = {
objectViews: mockObjectViews,
indicators: mockIndicatorProvider,
contextMenu: mockContextMenuProvider,
install: function (plugin) {
plugin(this);
}
},
mockObjectPath = [
{name: 'mockObject1'},
{name: 'mockObject2'}
];
it('Global Clear Indicator is installed', function () {
openmct.install(ClearDataActionPlugin([]));
expect(mockIndicatorProvider.add).toHaveBeenCalled();
});
it('Clear Data context menu action is installed', function () {
openmct.install(ClearDataActionPlugin([]));
expect(mockContextMenuProvider.registerAction).toHaveBeenCalled();
});
it('clear data action emits a clearData event when invoked', function () {
let action = new ClearDataAction(openmct);
action.invoke(mockObjectPath);
expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
});
});

View File

@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./components/AlphanumericFormatView.vue',
'vue'
], function (AlphanumericFormatView, Vue) {
function AlphanumericFormatViewProvider(openmct, options) {
function isTelemetryObject(selectionPath) {
let selectedObject = selectionPath[0].context.item;
let parentObject = selectionPath[1].context.item;
return parentObject &&
parentObject.type === 'layout' &&
selectedObject &&
openmct.telemetry.isTelemetryObject(selectedObject) &&
!options.showAsView.includes(selectedObject.type)
}
return {
key: 'alphanumeric-format',
name: 'Alphanumeric Format',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 1) {
return false;
}
return selection.every(isTelemetryObject);
},
view: function (selection) {
let component;
return {
show: function (element) {
component = new Vue({
provide: {
openmct
},
components: {
AlphanumericFormatView: AlphanumericFormatView.default
},
template: '<alphanumeric-format-view></alphanumeric-format-view>',
el: element
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
}
},
priority: function () {
return 1;
}
}
}
return AlphanumericFormatViewProvider;
});

View File

@ -0,0 +1,90 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-properties" v-if="isEditing">
<div class="c-properties__header">Alphanumeric Format</div>
<ul class="c-properties__section">
<li class="c-properties__row">
<div class="c-properties__label" title="Printf formatting for the selected telemetry">
<label for="telemetryPrintfFormat">Format</label>
</div>
<div class="c-properties__value">
<input id="telemetryPrintfFormat"
type="text"
@change="formatTelemetry"
:value="telemetryFormat"
:placeholder="nonMixedFormat ? '' : 'Mixed'"
>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
inject: ['openmct'],
data() {
let selectionPath = this.openmct.selection.get()[0];
return {
isEditing: this.openmct.editor.isEditing(),
telemetryFormat: undefined,
nonMixedFormat: false
}
},
methods: {
toggleEdit(isEditing) {
this.isEditing = isEditing;
},
formatTelemetry(event) {
let newFormat = event.currentTarget.value;
this.openmct.selection.get().forEach(selectionPath => {
selectionPath[0].context.updateTelemetryFormat(newFormat);
});
this.telemetryFormat = newFormat;
},
handleSelection(selection) {
if (selection.length === 0 || selection[0].length < 2) {
return;
}
let format = selection[0][0].context.layoutItem.format;
this.nonMixedFormat = selection.every(selectionPath => {
return selectionPath[0].context.layoutItem.format === format;
});
this.telemetryFormat = this.nonMixedFormat ? format : '';
}
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleEdit);
this.openmct.selection.on('change', this.handleSelection);
this.handleSelection(this.openmct.selection.get());
},
destroyed() {
this.openmct.editor.off('isEditing', this.toggleEdit);
this.openmct.selection.off('change', this.handleSelection);
}
}
</script>

View File

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

View File

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

View File

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

View File

@ -63,7 +63,13 @@ export default {
if (filterValue && filterValue[comparator]) { if (filterValue && filterValue[comparator]) {
if (value === false) { if (value === false) {
filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName); let filteredValueName = filterValue[comparator].filter(v => v !== valueName);
if (filteredValueName.length === 0) {
delete this.updatedFilters[key];
} else {
filterValue[comparator] = filteredValueName;
}
} else { } else {
filterValue[comparator].push(valueName); filterValue[comparator].push(valueName);
} }
@ -77,6 +83,14 @@ export default {
this.$emit('updateFilters', this.keyString, this.updatedFilters); this.$emit('updateFilters', this.keyString, this.updatedFilters);
}, },
updateTextFilter(key, comparator, value) { updateTextFilter(key, comparator, value) {
if (value.trim() === '') {
if (this.updatedFilters[key]) {
delete this.updatedFilters[key];
this.$emit('updateFilters', this.keyString, this.updatedFilters);
}
return;
}
if (!this.updatedFilters[key]) { if (!this.updatedFilters[key]) {
this.$set(this.updatedFilters, key, {}); this.$set(this.updatedFilters, key, {});
this.$set(this.updatedFilters[key], comparator, ''); this.$set(this.updatedFilters[key], comparator, '');

View File

@ -59,14 +59,18 @@ export default {
removeChildren(identifier) { removeChildren(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier); let keyString = this.openmct.objects.makeKeyString(identifier);
this.$delete(this.children, keyString); this.$delete(this.children, keyString);
this.persistFilters(keyString); delete this.persistedFilters[keyString];
this.mutateConfigurationFilters();
}, },
persistFilters(keyString, userSelects) { persistFilters(keyString, userSelects) {
this.persistedFilters[keyString] = userSelects; this.persistedFilters[keyString] = userSelects;
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters); this.mutateConfigurationFilters();
}, },
updatePersistedFilters(filters) { updatePersistedFilters(filters) {
this.persistedFilters = filters; this.persistedFilters = filters;
},
mutateConfigurationFilters() {
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
} }
}, },
mounted(){ mounted(){

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -63,8 +63,11 @@ define([
$scope.pending = 0; $scope.pending = 0;
this.clearData = this.clearData.bind(this);
this.listenTo($scope, 'user:viewport:change:end', this.onUserViewportChangeEnd, this); this.listenTo($scope, 'user:viewport:change:end', this.onUserViewportChangeEnd, this);
this.listenTo($scope, '$destroy', this.destroy, this); this.listenTo($scope, '$destroy', this.destroy, this);
this.listenTo($scope, 'clearData', this.clearData);
this.config = this.getConfig(this.$scope.domainObject); this.config = this.getConfig(this.$scope.domainObject);
this.listenTo(this.config.series, 'add', this.addSeries, this); this.listenTo(this.config.series, 'add', this.addSeries, this);
@ -74,6 +77,7 @@ define([
this.followTimeConductor(); this.followTimeConductor();
this.newStyleDomainObject = $scope.domainObject.useCapability('adapter'); this.newStyleDomainObject = $scope.domainObject.useCapability('adapter');
this.keyString = this.openmct.objects.makeKeyString(this.newStyleDomainObject.identifier);
this.filterObserver = this.openmct.objects.observe( this.filterObserver = this.openmct.objects.observe(
this.newStyleDomainObject, this.newStyleDomainObject,
@ -148,7 +152,6 @@ define([
}); });
configStore.add(configId, config); configStore.add(configId, config);
} }
configStore.track(configId);
return config; return config;
}; };
@ -157,7 +160,8 @@ define([
}; };
PlotController.prototype.destroy = function () { PlotController.prototype.destroy = function () {
configStore.untrack(this.config.id); configStore.deleteStore(this.config.id);
this.stopListening(); this.stopListening();
if (this.checkForSize) { if (this.checkForSize) {
clearInterval(this.checkForSize); clearInterval(this.checkForSize);
@ -263,6 +267,12 @@ define([
}); });
}; };
PlotController.prototype.clearData = function () {
this.config.series.forEach(function (series) {
series.refresh();
});
};
/** /**
* Export view as JPG. * Export view as JPG.
*/ */

View File

@ -23,6 +23,7 @@
define([ define([
'lodash', 'lodash',
'./utcTimeSystem/plugin', './utcTimeSystem/plugin',
'./localTimeSystem/plugin',
'../../example/generator/plugin', '../../example/generator/plugin',
'./autoflow/AutoflowTabularPlugin', './autoflow/AutoflowTabularPlugin',
'./timeConductor/plugin', './timeConductor/plugin',
@ -42,10 +43,12 @@ define([
'./LADTable/plugin', './LADTable/plugin',
'./filters/plugin', './filters/plugin',
'./objectMigration/plugin', './objectMigration/plugin',
'./goToOriginalAction/plugin' './goToOriginalAction/plugin',
'./clearData/plugin'
], function ( ], function (
_, _,
UTCTimeSystem, UTCTimeSystem,
LocalTimeSystem,
GeneratorPlugin, GeneratorPlugin,
AutoflowPlugin, AutoflowPlugin,
TimeConductorPlugin, TimeConductorPlugin,
@ -65,7 +68,8 @@ define([
LADTable, LADTable,
Filters, Filters,
ObjectMigration, ObjectMigration,
GoToOriginalAction GoToOriginalAction,
ClearData
) { ) {
var bundleMap = { var bundleMap = {
LocalStorage: 'platform/persistence/local', LocalStorage: 'platform/persistence/local',
@ -81,6 +85,7 @@ define([
}); });
plugins.UTCTimeSystem = UTCTimeSystem; plugins.UTCTimeSystem = UTCTimeSystem;
plugins.LocalTimeSystem = LocalTimeSystem;
plugins.ImportExport = ImportExport; plugins.ImportExport = ImportExport;
@ -163,6 +168,7 @@ define([
plugins.Filters = Filters; plugins.Filters = Filters;
plugins.ObjectMigration = ObjectMigration.default; plugins.ObjectMigration = ObjectMigration.default;
plugins.GoToOriginalAction = GoToOriginalAction.default; plugins.GoToOriginalAction = GoToOriginalAction.default;
plugins.ClearData = ClearData;
return plugins; return plugins;
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,6 +26,7 @@ define([
'./collections/BoundedTableRowCollection', './collections/BoundedTableRowCollection',
'./collections/FilteredTableRowCollection', './collections/FilteredTableRowCollection',
'./TelemetryTableRow', './TelemetryTableRow',
'./TelemetryTableColumn',
'./TelemetryTableConfiguration' './TelemetryTableConfiguration'
], function ( ], function (
EventEmitter, EventEmitter,
@ -33,6 +34,7 @@ define([
BoundedTableRowCollection, BoundedTableRowCollection,
FilteredTableRowCollection, FilteredTableRowCollection,
TelemetryTableRow, TelemetryTableRow,
TelemetryTableColumn,
TelemetryTableConfiguration TelemetryTableConfiguration
) { ) {
class TelemetryTable extends EventEmitter { class TelemetryTable extends EventEmitter {
@ -47,6 +49,8 @@ define([
this.telemetryObjects = []; this.telemetryObjects = [];
this.outstandingRequests = 0; this.outstandingRequests = 0;
this.configuration = new TelemetryTableConfiguration(domainObject, openmct); this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
this.paused = false;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.addTelemetryObject = this.addTelemetryObject.bind(this); this.addTelemetryObject = this.addTelemetryObject.bind(this);
this.removeTelemetryObject = this.removeTelemetryObject.bind(this); this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
@ -94,8 +98,6 @@ define([
this.tableComposition.load().then((composition) => { this.tableComposition.load().then((composition) => {
composition = composition.filter(this.isTelemetryObject); composition = composition.filter(this.isTelemetryObject);
this.configuration.addColumnsForAllObjects(composition);
composition.forEach(this.addTelemetryObject); composition.forEach(this.addTelemetryObject);
this.tableComposition.on('add', this.addTelemetryObject); this.tableComposition.on('add', this.addTelemetryObject);
@ -105,7 +107,7 @@ define([
} }
addTelemetryObject(telemetryObject) { addTelemetryObject(telemetryObject) {
this.configuration.addColumnsForObject(telemetryObject, true); this.addColumnsForObject(telemetryObject, true);
this.requestDataFor(telemetryObject); this.requestDataFor(telemetryObject);
this.subscribeTo(telemetryObject); this.subscribeTo(telemetryObject);
this.telemetryObjects.push(telemetryObject); this.telemetryObjects.push(telemetryObject);
@ -144,14 +146,17 @@ define([
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let columnMap = this.getColumnMapForObject(keyString); let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
this.processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator);
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
this.boundedRows.add(telemetryRows);
}).finally(() => { }).finally(() => {
this.decrementOutstandingRequests(); this.decrementOutstandingRequests();
}); });
} }
processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) {
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
this.boundedRows.add(telemetryRows);
}
/** /**
* @private * @private
*/ */
@ -191,6 +196,19 @@ define([
}, {}); }, {});
} }
addColumnsForObject(telemetryObject) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
metadataValues.forEach(metadatum => {
let column = this.createColumn(metadatum);
this.configuration.addSingleColumnForObject(telemetryObject, column);
});
}
createColumn(metadatum) {
return new TelemetryTableColumn(this.openmct, metadatum);
}
subscribeTo(telemetryObject) { subscribeTo(telemetryObject) {
let subscribeOptions = this.buildOptionsFromConfiguration(telemetryObject); let subscribeOptions = this.buildOptionsFromConfiguration(telemetryObject);
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
@ -202,10 +220,17 @@ define([
if (!this.telemetryObjects.includes(telemetryObject)) { if (!this.telemetryObjects.includes(telemetryObject)) {
return; return;
} }
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
if (!this.paused) {
this.processRealtimeDatum(datum, columnMap, keyString, limitEvaluator);
}
}, subscribeOptions); }, subscribeOptions);
} }
processRealtimeDatum(datum, columnMap, keyString, limitEvaluator) {
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
}
isTelemetryObject(domainObject) { isTelemetryObject(domainObject) {
return domainObject.hasOwnProperty('telemetry'); return domainObject.hasOwnProperty('telemetry');
} }
@ -234,12 +259,24 @@ define([
} }
} }
pause() {
this.paused = true;
this.boundedRows.unsubscribeFromBounds();
}
unpause() {
this.paused = false;
this.boundedRows.subscribeToBounds();
this.refreshData();
}
destroy() { destroy() {
this.boundedRows.destroy(); this.boundedRows.destroy();
this.filteredRows.destroy(); this.filteredRows.destroy();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this); Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.openmct.time.off('bounds', this.refreshData); this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.off('timeSystem', this.refreshData); this.openmct.time.off('timeSystem', this.refreshData);
if (this.filterObserver) { if (this.filterObserver) {
this.filterObserver(); this.filterObserver();
} }

View File

@ -21,10 +21,11 @@
*****************************************************************************/ *****************************************************************************/
define(function () { define(function () {
class TelemetryTableColumn { class TelemetryTableColumn {
constructor (openmct, metadatum) { constructor (openmct, metadatum, options = {selectable: false}) {
this.metadatum = metadatum; this.metadatum = metadatum;
this.formatter = openmct.telemetry.getValueFormatter(metadatum); this.formatter = openmct.telemetry.getValueFormatter(metadatum);
this.titleValue = this.metadatum.name; this.titleValue = this.metadatum.name;
this.selectable = options.selectable;
} }
getKey() { getKey() {
@ -55,8 +56,7 @@ define(function () {
return formattedValue; return formattedValue;
} }
} }
}
};
return TelemetryTableColumn; return TelemetryTableColumn;
}); });

View File

@ -22,9 +22,8 @@
define([ define([
'lodash', 'lodash',
'EventEmitter', 'EventEmitter'
'./TelemetryTableColumn' ], function (_, EventEmitter) {
], function (_, EventEmitter, TelemetryTableColumn) {
class TelemetryTableConfiguration extends EventEmitter { class TelemetryTableConfiguration extends EventEmitter {
constructor(domainObject, openmct) { constructor(domainObject, openmct) {
@ -34,7 +33,6 @@ define([
this.openmct = openmct; this.openmct = openmct;
this.columns = {}; this.columns = {};
this.addColumnsForObject = this.addColumnsForObject.bind(this);
this.removeColumnsForObject = this.removeColumnsForObject.bind(this); this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
this.objectMutated = this.objectMutated.bind(this); this.objectMutated = this.objectMutated.bind(this);
//Make copy of configuration, otherwise change detection is impossible if shared instance is being modified. //Make copy of configuration, otherwise change detection is impossible if shared instance is being modified.
@ -48,6 +46,7 @@ define([
configuration.hiddenColumns = configuration.hiddenColumns || {}; configuration.hiddenColumns = configuration.hiddenColumns || {};
configuration.columnWidths = configuration.columnWidths || {}; configuration.columnWidths = configuration.columnWidths || {};
configuration.columnOrder = configuration.columnOrder || []; configuration.columnOrder = configuration.columnOrder || [];
configuration.cellFormat = configuration.cellFormat || {};
configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize; configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
return configuration; return configuration;
@ -65,26 +64,18 @@ define([
//Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible. //Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible.
this.domainObject = object; this.domainObject = object;
//Was it the configuration that changed? //Was it the configuration that changed?
if (!_.eq(object.configuration, this.oldConfiguration)) { if (object.configuration !== undefined && !_.eq(object.configuration, this.oldConfiguration)) {
//Make copy of configuration, otherwise change detection is impossible if shared instance is being modified. //Make copy of configuration, otherwise change detection is impossible if shared instance is being modified.
this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration())); this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration()));
this.emit('change', object.configuration); this.emit('change', object.configuration);
} }
} }
addColumnsForAllObjects(objects) { addSingleColumnForObject(telemetryObject, column, position) {
objects.forEach(object => this.addColumnsForObject(object, false));
}
addColumnsForObject(telemetryObject) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
let objectKeyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); let objectKeyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
this.columns[objectKeyString] = []; this.columns[objectKeyString] = this.columns[objectKeyString] || [];
position = position || this.columns[objectKeyString].length;
metadataValues.forEach(metadatum => { this.columns[objectKeyString].splice(position, 0, column);
let column = new TelemetryTableColumn(this.openmct, metadatum);
this.columns[objectKeyString].push(column);
});
} }
removeColumnsForObject(objectIdentifier) { removeColumnsForObject(objectIdentifier) {

View File

@ -42,12 +42,19 @@ define([], function () {
return column && column.getFormattedValue(this.datum[key]); return column && column.getFormattedValue(this.datum[key]);
} }
getRowLimitClass() { getCellComponentName(key) {
if (!this.rowLimitClass) { let column = this.columns[key];
return column &&
column.getCellComponentName &&
column.getCellComponentName();
}
getRowClass() {
if (!this.rowClass) {
let limitEvaluation = this.limitEvaluator.evaluate(this.datum); let limitEvaluation = this.limitEvaluator.evaluate(this.datum);
this.rowLimitClass = limitEvaluation && limitEvaluation.cssClass; this.rowClass = limitEvaluation && limitEvaluation.cssClass;
} }
return this.rowLimitClass; return this.rowClass;
} }
getCellLimitClasses() { getCellLimitClasses() {

View File

@ -22,12 +22,10 @@
define([ define([
'./components/table.vue', './components/table.vue',
'../../exporters/CSVExporter',
'./TelemetryTable', './TelemetryTable',
'vue' 'vue'
], function ( ], function (
TableComponent, TableComponent,
CSVExporter,
TelemetryTable, TelemetryTable,
Vue Vue
) { ) {
@ -51,7 +49,6 @@ define([
return domainObject.type === 'table'; return domainObject.type === 'table';
}, },
view(domainObject) { view(domainObject) {
let csvExporter = new CSVExporter.default();
let table = new TelemetryTable(domainObject, openmct); let table = new TelemetryTable(domainObject, openmct);
let component; let component;
return { return {
@ -67,16 +64,18 @@ define([
}, },
provide: { provide: {
openmct, openmct,
csvExporter,
table table
}, },
el: element, el: element,
template: '<table-component :isEditing="isEditing"></table-component>' template: '<table-component :isEditing="isEditing" :enableMarking="true"></table-component>'
}); });
}, },
onEditModeChange(isEditing) { onEditModeChange(isEditing) {
component.isEditing = isEditing; component.isEditing = isEditing;
}, },
onClearData() {
table.refreshData();
},
destroy: function (element) { destroy: function (element) {
component.$destroy(); component.$destroy();
component = undefined; component = undefined;

View File

@ -31,9 +31,9 @@ define(
) { ) {
class BoundedTableRowCollection extends SortedTableRowCollection { class BoundedTableRowCollection extends SortedTableRowCollection {
constructor (openmct) { constructor(openmct) {
super(); super();
this.futureBuffer = new SortedTableRowCollection(); this.futureBuffer = new SortedTableRowCollection();
this.openmct = openmct; this.openmct = openmct;
@ -43,15 +43,17 @@ define(
this.sortByTimeSystem(openmct.time.timeSystem()); this.sortByTimeSystem(openmct.time.timeSystem());
this.lastBounds = openmct.time.bounds(); this.lastBounds = openmct.time.bounds();
openmct.time.on('bounds', this.bounds);
this.subscribeToBounds();
} }
addOne (item) { addOne(item) {
let parsedValue = this.getValueForSortColumn(item);
// Insert into either in-bounds array, or the future buffer. // Insert into either in-bounds array, or the future buffer.
// Data in the future buffer will be re-evaluated for possible // Data in the future buffer will be re-evaluated for possible
// insertion on next bounds change // insertion on next bounds change
let beforeStartOfBounds = this.parseTime(item.datum[this.sortOptions.key]) < this.lastBounds.start; let beforeStartOfBounds = parsedValue < this.lastBounds.start;
let afterEndOfBounds = this.parseTime(item.datum[this.sortOptions.key]) > this.lastBounds.end; let afterEndOfBounds = parsedValue > this.lastBounds.end;
if (!afterEndOfBounds && !beforeStartOfBounds) { if (!afterEndOfBounds && !beforeStartOfBounds) {
return super.addOne(item); return super.addOne(item);
@ -86,13 +88,13 @@ define(
* @fires TelemetryCollection#discarded * @fires TelemetryCollection#discarded
* @param bounds * @param bounds
*/ */
bounds (bounds) { bounds(bounds) {
let startChanged = this.lastBounds.start !== bounds.start; let startChanged = this.lastBounds.start !== bounds.start;
let endChanged = this.lastBounds.end !== bounds.end; let endChanged = this.lastBounds.end !== bounds.end;
let startIndex = 0; let startIndex = 0;
let endIndex = 0; let endIndex = 0;
let discarded = []; let discarded = [];
let added = []; let added = [];
let testValue = { let testValue = {
@ -135,9 +137,21 @@ define(
} }
} }
destroy() { getValueForSortColumn(row) {
return this.parseTime(row.datum[this.sortOptions.key]);
}
unsubscribeFromBounds() {
this.openmct.time.off('bounds', this.bounds); this.openmct.time.off('bounds', this.bounds);
} }
subscribeToBounds() {
this.openmct.time.on('bounds', this.bounds);
}
destroy() {
this.unsubscribeFromBounds();
}
} }
return BoundedTableRowCollection; return BoundedTableRowCollection;
}); });

View File

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

View File

@ -0,0 +1,176 @@
<template>
<div v-if="filterNames.length > 0"
:title=title
class="c-filter-indication"
:class="{ 'c-filter-indication--mixed': mixed }">
<span class="c-filter-indication__mixed">{{ label }}</span>
<span v-for="(name, index) in filterNames"
class="c-filter-indication__label">
{{ name }}
</span>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-filter-indication {
@include userSelectNone();
background: $colorFilterBg;
color: $colorFilterFg;
display: flex;
align-items: center;
font-size: 0.9em;
margin-top: $interiorMarginSm;
padding: 2px;
text-transform: uppercase;
&:before {
font-family: symbolsfont-12px;
content: $glyph-icon-filter;
display: block;
font-size: 12px;
margin-right: $interiorMarginSm;
}
&__mixed {
font-weight: bold;
margin-right: $interiorMarginSm;
}
&--mixed {
.c-filter-indication__mixed {
font-style: italic;
}
}
&__label {
+ .c-filter-indication__label {
&:before {
content: ',';
}
}
}
}
</style>
<script>
const FILTER_INDICATOR_LABEL = 'Filters:';
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.';
const FILTER_INDICATOR_TITLE_MIXED = 'A mix of data filter values are being applied to this view.';
export default {
inject: ['openmct', 'table'],
data() {
return {
filterNames: [],
filteredTelemetry: {},
mixed: false,
label: '',
title: ''
}
},
methods: {
isTelemetryObject(domainObject) {
return domainObject.hasOwnProperty('telemetry');
},
setFilterNames() {
let names = [];
this.composition && this.composition.load().then((domainObjects) => {
domainObjects.forEach(telemetryObject => {
let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier);
let filters = this.filteredTelemetry[keyString];
this.telemetryKeyStrings.add(keyString);
if (filters !== undefined) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
Object.keys(filters).forEach(key => {
metadataValues.forEach(metadaum => {
if (key === metadaum.key) {
names.push(metadaum.name);
}
});
});
}
});
this.filterNames = Array.from(new Set(names));
});
},
handleConfigurationChanges(configuration) {
if (!_.eq(this.filteredTelemetry, configuration.filters)) {
this.updateFilters(configuration.filters || {});
}
},
checkFiltersForMixedValues() {
let valueToCompare = this.filteredTelemetry[Object.keys(this.filteredTelemetry)[0]];
let mixed = false;
Object.values(this.filteredTelemetry).forEach(value => {
if (!_.isEqual(valueToCompare, value)) {
mixed = true;
return;
}
});
// If the filtered telemetry is not mixed at this point, check the number of available objects
// with the number of filtered telemetry. If they are not equal, the filters must be mixed.
if (mixed === false && _.size(this.filteredTelemetry) !== this.telemetryKeyStrings.size) {
mixed = true;
}
this.mixed = mixed;
},
setLabels() {
if (this.mixed) {
this.label = FILTER_INDICATOR_LABEL_MIXED;
this.title = FILTER_INDICATOR_TITLE_MIXED;
} else {
this.label = FILTER_INDICATOR_LABEL;
this.title = FILTER_INDICATOR_TITLE;
}
},
updateFilters(filters) {
this.filteredTelemetry = JSON.parse(JSON.stringify(filters));
this.setFilterNames();
this.updateIndicatorLabel();
},
addChildren(child) {
let keyString = this.openmct.objects.makeKeyString(child.identifier);
this.telemetryKeyStrings.add(keyString);
this.updateIndicatorLabel();
},
removeChildren(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
this.telemetryKeyStrings.delete(keyString);
this.updateIndicatorLabel();
},
updateIndicatorLabel() {
this.checkFiltersForMixedValues();
this.setLabels();
}
},
mounted() {
let filters = this.table.configuration.getConfiguration().filters || {};
this.telemetryKeyStrings = new Set();
this.composition = this.openmct.composition.get(this.table.configuration.domainObject);
if (this.composition) {
this.composition.on('add', this.addChildren);
this.composition.on('remove', this.removeChildren);
}
this.table.configuration.on('change', this.handleConfigurationChanges);
this.updateFilters(filters);
},
destroyed() {
this.table.configuration.off('change', this.handleConfigurationChanges);
if (this.composition) {
this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren);
}
}
}
</script>

View File

@ -0,0 +1,44 @@
/*****************************************************************************
* 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>
<td>{{formattedValue}}</td>
</template>
<script>
export default {
inject: ['openmct'],
props: {
row: {
type: Object,
required: true
},
columnKey: {
type: String,
require: true
}
},
computed: {
formattedValue() {
return this.row.getFormattedValue(this.columnKey);
}
}
};
</script>

View File

@ -23,6 +23,8 @@
</style> </style>
<script> <script>
import TelemetryTableColumn from '../TelemetryTableColumn';
export default { export default {
inject: ['tableConfiguration', 'openmct'], inject: ['tableConfiguration', 'openmct'],
data() { data() {
@ -43,7 +45,7 @@ export default {
this.tableConfiguration.updateConfiguration(this.configuration); this.tableConfiguration.updateConfiguration(this.configuration);
}, },
addObject(domainObject) { addObject(domainObject) {
this.tableConfiguration.addColumnsForObject(domainObject, true); this.addColumnsForObject(domainObject, true);
this.updateHeaders(this.tableConfiguration.getAllHeaders()); this.updateHeaders(this.tableConfiguration.getAllHeaders());
}, },
removeObject(objectIdentifier) { removeObject(objectIdentifier) {
@ -56,6 +58,17 @@ export default {
toggleAutosize() { toggleAutosize() {
this.configuration.autosize = !this.configuration.autosize; this.configuration.autosize = !this.configuration.autosize;
this.tableConfiguration.updateConfiguration(this.configuration); this.tableConfiguration.updateConfiguration(this.configuration);
},
addColumnsForAllObjects(objects) {
objects.forEach(object => this.addColumnsForObject(object, false));
},
addColumnsForObject(telemetryObject) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
metadataValues.forEach(metadatum => {
let column = new TelemetryTableColumn(this.openmct, metadatum);
this.tableConfiguration.addSingleColumnForObject(telemetryObject, column);
});
} }
}, },
mounted() { mounted() {
@ -65,7 +78,7 @@ export default {
compositionCollection.load() compositionCollection.load()
.then((composition) => { .then((composition) => {
this.tableConfiguration.addColumnsForAllObjects(composition); this.addColumnsForAllObjects(composition);
this.updateHeaders(this.tableConfiguration.getAllHeaders()); this.updateHeaders(this.tableConfiguration.getAllHeaders());
compositionCollection.on('add', this.addObject); compositionCollection.on('add', this.addObject);

View File

@ -20,26 +20,56 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<tr :style="{ top: rowTop }" :class="rowLimitClass"> <tr :style="{ top: rowTop }"
<td v-for="(title, key) in headers" class="noselect"
:class="[
rowClass,
{'is-selected': marked}
]"
@click="markRow">
<component v-for="(title, key) in headers"
:key="key" :key="key"
:is="componentList[key]"
:columnKey="key"
:style="columnWidths[key] === undefined ? {} : { width: columnWidths[key] + 'px', 'max-width': columnWidths[key] + 'px'}" :style="columnWidths[key] === undefined ? {} : { width: columnWidths[key] + 'px', 'max-width': columnWidths[key] + 'px'}"
:title="formattedRow[key]" :title="formattedRow[key]"
:class="cellLimitClasses[key]">{{formattedRow[key]}}</td> :class="[cellLimitClasses[key], selectableColumns[key] ? 'is-selectable' : '']"
@click="selectCell($event.currentTarget, key)"
:row="row">
</component>
</tr> </tr>
</template> </template>
<style> <style>
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome and Opera */
}
</style> </style>
<script> <script>
import TableCell from './table-cell.vue';
export default { export default {
data: function () { data: function () {
return { return {
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px', rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
formattedRow: this.row.getFormattedDatum(this.headers), formattedRow: this.row.getFormattedDatum(this.headers),
rowLimitClass: this.row.getRowLimitClass(), rowClass: this.row.getRowClass(),
cellLimitClasses: this.row.getCellLimitClasses() cellLimitClasses: this.row.getCellLimitClasses(),
componentList: Object.keys(this.headers).reduce((components, header) => {
components[header] = this.row.getCellComponentName(header) || 'table-cell';
return components
}, {}),
selectableColumns : Object.keys(this.row.columns).reduce((selectable, columnKeys) => {
selectable[columnKeys] = this.row.columns[columnKeys].selectable;
return selectable;
}, {})
} }
}, },
props: { props: {
@ -69,6 +99,11 @@ export default {
type: Number, type: Number,
required: false, required: false,
default: 0 default: 0
},
marked: {
type: Boolean,
required: false,
default: false
} }
}, },
methods: { methods: {
@ -77,8 +112,44 @@ export default {
}, },
formatRow: function (row) { formatRow: function (row) {
this.formattedRow = row.getFormattedDatum(this.headers); this.formattedRow = row.getFormattedDatum(this.headers);
this.rowLimitClass = row.getRowLimitClass(); this.rowClass = row.getRowClass();
this.cellLimitClasses = row.getCellLimitClasses(); this.cellLimitClasses = row.getCellLimitClasses();
},
markRow: function (event) {
let keyCtrlModifier = false;
if (event.ctrlKey || event.metaKey) {
keyCtrlModifier = true;
}
if (event.shiftKey) {
this.$emit('markMultipleConcurrent', this.rowIndex);
} else {
if (this.marked) {
this.$emit('unmark', this.rowIndex, keyCtrlModifier);
} else {
this.$emit('mark', this.rowIndex, keyCtrlModifier);
}
}
},
selectCell(element, columnKey) {
if (this.selectableColumns[columnKey]) {
//TODO: This is a hack. Cannot get parent this way.
this.openmct.selection.select([{
element: element,
context: {
type: 'table-cell',
row: this.row.objectKeyString,
column: columnKey
}
},{
element: this.openmct.layout.$refs.browseObject.$el,
context: {
item: this.openmct.router.path[0]
}
}], false);
event.stopPropagation();
}
} }
}, },
// TODO: use computed properties // TODO: use computed properties
@ -88,6 +159,9 @@ export default {
handler: 'formatRow', handler: 'formatRow',
deep: false deep: false
} }
},
components: {
TableCell
} }
} }
</script> </script>

View File

@ -20,92 +20,134 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar" <div class="c-table-wrapper">
:class="{'loading': loading}"> <div class="c-table-control-bar c-control-bar">
<div class="c-table__control-bar c-control-bar">
<button class="c-button icon-download labeled" <button class="c-button icon-download labeled"
v-on:click="exportAsCSV()" v-if="allowExport"
title="Export This View's Data"> v-on:click="exportAllDataAsCSV()"
<span class="c-button__label">Export As CSV</span> title="Export This View's Data">
<span class="c-button__label">Export Table Data</span>
</button> </button>
<button class="c-button icon-download labeled"
v-if="allowExport"
v-show="markedRows.length"
v-on:click="exportMarkedDataAsCSV()"
title="Export Marked Rows As CSV">
<span class="c-button__label">Export Marked Rows</span>
</button>
<button class="c-button icon-x labeled"
v-show="markedRows.length"
v-on:click="unmarkAllRows()"
title="Unmark All Rows">
<span class="c-button__label">Unmark All Rows</span>
</button>
<div v-if="enableMarking"
class="c-separator">
</div>
<button v-if="enableMarking"
class="c-button icon-pause pause-play labeled"
:class=" paused ? 'icon-play is-paused' : 'icon-pause'"
v-on:click="togglePauseByButton()"
:title="paused ? 'Continue Data Flow' : 'Pause Data Flow'">
<span class="c-button__label">
{{paused ? 'Play' : 'Pause'}}
</span>
</button>
<slot name="buttons"></slot>
</div> </div>
<div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div>
<!-- Headers table --> <div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
<div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}"> :class="{
<table class="c-table__headers c-telemetry-table__headers"> 'loading': loading,
<thead> 'paused' : paused
<tr class="c-telemetry-table__headers__labels"> }">
<table-column-header
v-for="(title, key, headerIndex) in headers" <div :style="{ 'max-width': widthWithScroll, 'min-width': '150px'}"><slot></slot></div>
:key="key"
:headerKey="key" <div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div>
:headerIndex="headerIndex" <!-- Headers table -->
@sort="sortBy(key)" <div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}">
@resizeColumn="resizeColumn" <table class="c-table__headers c-telemetry-table__headers">
@dropTargetOffsetChanged="setDropTargetOffset" <thead>
@dropTargetActive="dropTargetActive" <tr class="c-telemetry-table__headers__labels">
@reorderColumn="reorderColumn" <table-column-header
@resizeColumnEnd="updateConfiguredColumnWidths" v-for="(title, key, headerIndex) in headers"
:columnWidth="columnWidths[key]" :key="key"
:sortOptions="sortOptions" :headerKey="key"
:isEditing="isEditing" :headerIndex="headerIndex"
><span class="c-telemetry-table__headers__label">{{title}}</span> @sort="allowSorting && sortBy(key)"
</table-column-header> @resizeColumn="resizeColumn"
</tr> @dropTargetOffsetChanged="setDropTargetOffset"
<tr class="c-telemetry-table__headers__filter"> @dropTargetActive="dropTargetActive"
<table-column-header @reorderColumn="reorderColumn"
v-for="(title, key, headerIndex) in headers" @resizeColumnEnd="updateConfiguredColumnWidths"
:key="key" :columnWidth="columnWidths[key]"
:headerKey="key" :sortOptions="sortOptions"
:headerIndex="headerIndex" :isEditing="isEditing"
@resizeColumn="resizeColumn" ><span class="c-telemetry-table__headers__label">{{title}}</span>
@dropTargetOffsetChanged="setDropTargetOffset" </table-column-header>
@dropTargetActive="dropTargetActive" </tr>
@reorderColumn="reorderColumn" <tr class="c-telemetry-table__headers__filter">
@resizeColumnEnd="updateConfiguredColumnWidths" <table-column-header
:columnWidth="columnWidths[key]" v-for="(title, key, headerIndex) in headers"
:isEditing="isEditing" :key="key"
> :headerKey="key"
<search class="c-table__search" :headerIndex="headerIndex"
v-model="filters[key]" @resizeColumn="resizeColumn"
v-on:input="filterChanged(key)" @dropTargetOffsetChanged="setDropTargetOffset"
v-on:clear="clearFilter(key)" /> @dropTargetActive="dropTargetActive"
</table-column-header> @reorderColumn="reorderColumn"
</tr> @resizeColumnEnd="updateConfiguredColumnWidths"
</thead> :columnWidth="columnWidths[key]"
:isEditing="isEditing"
>
<search class="c-table__search"
v-model="filters[key]"
v-on:input="filterChanged(key)"
v-on:clear="clearFilter(key)" />
</table-column-header>
</tr>
</thead>
</table>
</div>
<!-- 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 js-telemetry-table__content"
:style="{ height: totalHeight + 'px'}">
<tbody>
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
:headers="headers"
:columnWidths="columnWidths"
:rowIndex="rowIndex"
:rowOffset="rowOffset"
:rowHeight="rowHeight"
:row="row"
:marked="row.marked"
@mark="markRow"
@unmark="unmarkRow"
@markMultipleConcurrent="markMultipleConcurrentRows">
</telemetry-table-row>
</tbody>
</table>
</div>
<!-- Sizing table -->
<table class="c-telemetry-table__sizing js-telemetry-table__sizing" :style="sizingTableWidth">
<tr>
<template v-for="(title, key) in headers">
<th :key="key" :style="{ width: configuredColumnWidths[key] + 'px', 'max-width': configuredColumnWidths[key] + 'px'}">{{title}}</th>
</template>
</tr>
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
:key="objectKeyString"
:headers="headers"
:columnWidths="configuredColumnWidths"
:row="sizingRowData">
</telemetry-table-row>
</table> </table>
<telemetry-filter-indicator></telemetry-filter-indicator>
</div> </div>
<!-- Content table --> </div><!-- closes c-table-wrapper -->
<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"
:style="{ height: totalHeight + 'px'}">
<tbody>
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
:headers="headers"
:columnWidths="columnWidths"
:rowIndex="rowIndex"
:rowOffset="rowOffset"
:rowHeight="rowHeight"
:row="row">
</telemetry-table-row>
</tbody>
</table>
</div>
<!-- Sizing table -->
<table class="c-telemetry-table__sizing js-telemetry-table__sizing" :style="sizingTableWidth">
<tr>
<template v-for="(title, key) in headers">
<th :key="key" :style="{ width: configuredColumnWidths[key] + 'px', 'max-width': configuredColumnWidths[key] + 'px'}">{{title}}</th>
</template>
</tr>
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
:headers="headers"
:columnWidths="configuredColumnWidths"
:row="sizingRowData">
</telemetry-table-row>
</table>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -131,7 +173,7 @@
display: block; display: block;
flex: 1 0 auto; flex: 1 0 auto;
width: 100px; width: 100px;
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default vertical-align: middle; // This is crucial to hiding 4px height injected by browser by default
} }
td { td {
@ -216,6 +258,10 @@
align-items: stretch; align-items: stretch;
position: absolute; position: absolute;
height: 18px; // Needed when a row has empty values in its cells height: 18px; // Needed when a row has empty values in its cells
&.is-selected {
background-color: $colorSelectedBg;
}
} }
td { td {
@ -266,6 +312,10 @@
} }
} }
.paused {
border: 1px solid #ff9900;
}
/******************************* LEGACY */ /******************************* LEGACY */
.s-status-taking-snapshot, .s-status-taking-snapshot,
.overlay.snapshot { .overlay.snapshot {
@ -279,12 +329,14 @@
import TelemetryTableRow from './table-row.vue'; import TelemetryTableRow from './table-row.vue';
import search from '../../../ui/components/search.vue'; import search from '../../../ui/components/search.vue';
import TableColumnHeader from './table-column-header.vue'; import TableColumnHeader from './table-column-header.vue';
import TelemetryFilterIndicator from './TelemetryFilterIndicator.vue';
import CSVExporter from '../../../exporters/CSVExporter.js';
import _ from 'lodash'; import _ from 'lodash';
const VISIBLE_ROW_COUNT = 100; const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17; const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200; const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 20; const AUTO_SCROLL_TRIGGER_HEIGHT = 100;
const RESIZE_HOT_ZONE = 10; const RESIZE_HOT_ZONE = 10;
const MOVE_TRIGGER_WAIT = 500; const MOVE_TRIGGER_WAIT = 500;
const VERTICAL_SCROLL_WIDTH = 30; const VERTICAL_SCROLL_WIDTH = 30;
@ -293,13 +345,30 @@ export default {
components: { components: {
TelemetryTableRow, TelemetryTableRow,
TableColumnHeader, TableColumnHeader,
search search,
TelemetryFilterIndicator
}, },
inject: ['table', 'openmct', 'csvExporter'], inject: ['table', 'openmct'],
props: { props: {
isEditing: { isEditing: {
type: Boolean, type: Boolean,
default: false default: false
},
allowExport: {
type: Boolean,
default: true
},
allowFiltering: {
'type': Boolean,
'default': true
},
allowSorting: {
'type': Boolean,
'default': true
},
enableMarking: {
type: Boolean,
default: false
} }
}, },
data() { data() {
@ -328,7 +397,10 @@ export default {
dropOffsetLeft: undefined, dropOffsetLeft: undefined,
isDropTargetActive: false, isDropTargetActive: false,
isAutosizeEnabled: configuration.autosize, isAutosizeEnabled: configuration.autosize,
scrollW: 0 scrollW: 0,
markCounter: 0,
paused: false,
markedRows: []
} }
}, },
computed: { computed: {
@ -364,42 +436,48 @@ export default {
}, },
methods: { methods: {
updateVisibleRows() { updateVisibleRows() {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
let start = 0; let start = 0;
let end = VISIBLE_ROW_COUNT; let end = VISIBLE_ROW_COUNT;
let filteredRows = this.table.filteredRows.getRows(); let filteredRows = this.table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length; let filteredRowsLength = filteredRows.length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1; if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength;
} else {
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
let totalVisible = lastVisible - firstVisible;
if (filteredRowsLength < VISIBLE_ROW_COUNT) { let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
end = filteredRowsLength; start = firstVisible - Math.floor(numberOffscreen / 2);
} else { end = lastVisible + Math.ceil(numberOffscreen / 2);
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
let totalVisible = lastVisible - firstVisible;
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible; if (start < 0) {
start = firstVisible - Math.floor(numberOffscreen / 2); start = 0;
end = lastVisible + Math.ceil(numberOffscreen / 2); 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) { this.updatingView = false;
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);
}, },
calculateFirstVisibleRow() { calculateFirstVisibleRow() {
return Math.floor(this.scrollable.scrollTop / this.rowHeight); let scrollTop = this.scrollable.scrollTop;
return Math.floor(scrollTop / this.rowHeight);
}, },
calculateLastVisibleRow() { calculateLastVisibleRow() {
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight; let scrollBottom = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.floor(bottomScroll / this.rowHeight); return Math.ceil(scrollBottom / this.rowHeight);
}, },
updateHeaders() { updateHeaders() {
this.headers = this.table.configuration.getVisibleHeaders(); this.headers = this.table.configuration.getVisibleHeaders();
@ -443,81 +521,92 @@ export default {
} }
this.table.sortBy(this.sortOptions); this.table.sortBy(this.sortOptions);
}, },
scroll() { scroll () {
if (!this.processingScroll) { this.updateVisibleRows();
this.processingScroll = true; this.synchronizeScrollX();
requestAnimationFrame(()=> {
this.updateVisibleRows();
this.synchronizeScrollX();
if (this.shouldSnapToBottom()) { if (this.shouldSnapToBottom()) {
this.autoScroll = true; this.autoScroll = true;
} else { } else {
// If user scrolls away from bottom, disable auto-scroll. // If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again. // Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = false; this.autoScroll = false;
}
this.processingScroll = false;
});
} }
}, },
shouldSnapToBottom() { shouldSnapToBottom() {
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT); return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
}, },
scrollToBottom() { scrollToBottom() {
this.scrollable.scrollTop = this.scrollable.scrollHeight; this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
}, },
synchronizeScrollX() { synchronizeScrollX() {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft; this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
}, },
filterChanged(columnKey) { filterChanged(columnKey) {
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]); this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
this.setHeight();
}, },
clearFilter(columnKey) { clearFilter(columnKey) {
this.filters[columnKey] = ''; this.filters[columnKey] = '';
this.table.filteredRows.setColumnFilter(columnKey, ''); this.table.filteredRows.setColumnFilter(columnKey, '');
this.setHeight();
}, },
rowsAdded(rows) { rowsAdded (rows) {
this.setHeight();
let sizingRow; let sizingRow;
if (Array.isArray(rows)) { if (Array.isArray(rows)) {
sizingRow = rows[0]; sizingRow = rows[0];
} else { } else {
sizingRow = rows; sizingRow = rows;
} }
if (!this.sizingRows[sizingRow.objectKeyString]) { if (!this.sizingRows[sizingRow.objectKeyString]) {
this.sizingRows[sizingRow.objectKeyString] = sizingRow; this.sizingRows[sizingRow.objectKeyString] = sizingRow;
this.$nextTick().then(this.calculateColumnWidths); this.$nextTick().then(this.calculateColumnWidths);
} }
if (!this.updatingView) { if (this.autoScroll) {
this.updatingView = true; this.scrollToBottom();
requestAnimationFrame(()=> {
this.updateVisibleRows();
if (this.autoScroll) {
this.$nextTick().then(this.scrollToBottom);
}
this.updatingView = false;
});
} }
this.updateVisibleRows();
}, },
rowsRemoved(rows) { rowsRemoved (rows) {
if (!this.updatingView) { this.setHeight();
this.updatingView = true; this.updateVisibleRows();
requestAnimationFrame(()=> {
this.updateVisibleRows();
this.updatingView = false;
});
}
}, },
exportAsCSV() { /**
* 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(data) {
const headerKeys = Object.keys(this.headers); const headerKeys = Object.keys(this.headers);
const justTheData = this.table.filteredRows.getRows()
.map(row => row.getFormattedDatum(this.headers)); this.csvExporter.export(data, {
this.csvExporter.export(justTheData, {
filename: this.table.domainObject.name + '.csv', filename: this.table.domainObject.name + '.csv',
headers: headerKeys headers: headerKeys
}); });
}, },
exportAllDataAsCSV() {
const justTheData = this.table.filteredRows.getRows()
.map(row => row.getFormattedDatum(this.headers));
this.exportAsCSV(justTheData);
},
exportMarkedDataAsCSV() {
const data = this.table.filteredRows.getRows()
.filter(row => row.marked === true)
.map(row => row.getFormattedDatum(this.headers));
this.exportAsCSV(data);
},
outstandingRequests(loading) { outstandingRequests(loading) {
this.loading = loading; this.loading = loading;
}, },
@ -595,23 +684,133 @@ export default {
this.calculateTableSize(); this.calculateTableSize();
// On some resize events scrollTop is reset to 0. Possibly due to a transition we're using? // On some resize events scrollTop is reset to 0. Possibly due to a transition we're using?
// Need to preserve scroll position in this case. // Need to preserve scroll position in this case.
this.scrollable.scrollTop = scrollTop; if (this.autoScroll) {
this.scrollToBottom();
} else {
this.scrollable.scrollTop = scrollTop;
}
width = el.clientWidth; width = el.clientWidth;
height = el.clientHeight; height = el.clientHeight;
} }
scrollTop = this.scrollable.scrollTop; scrollTop = this.scrollable.scrollTop;
}, RESIZE_POLL_INTERVAL); }, RESIZE_POLL_INTERVAL);
}, },
clearRowsAndRerender() {
this.visibleRows = [];
this.$nextTick().then(this.updateVisibleRows);
},
pause(pausedByButton) {
if (pausedByButton) {
this.pausedByButton = true;
}
this.paused = true;
this.table.pause();
},
unpause(unpausedByButton) {
if (unpausedByButton) {
this.paused = false;
this.table.unpause();
this.markedRows = [];
this.pausedByButton = false;
} else {
if (!this.pausedByButton) {
this.paused = false;
this.table.unpause();
this.markedRows = [];
}
}
},
togglePauseByButton() {
if (this.paused) {
this.unpause(true);
} else {
this.pause(true);
}
},
undoMarkedRows(unpause) {
this.markedRows.forEach(r => r.marked = false);
this.markedRows = [];
},
unmarkRow(rowIndex) {
this.undoMarkedRows();
this.unpause();
},
markRow(rowIndex, keyModifier) {
if (!this.enableMarking) {
return;
}
let insertMethod = 'unshift';
if (this.markedRows.length && !keyModifier) {
this.undoMarkedRows();
insertMethod = 'push';
}
let markedRow = this.visibleRows[rowIndex];
this.$set(markedRow, 'marked', true);
this.pause();
this.markedRows[insertMethod](markedRow);
},
unmarkAllRows(skipUnpause) {
this.markedRows.forEach(row => row.marked = false);
this.markedRows = [];
this.unpause();
},
markMultipleConcurrentRows(rowIndex) {
if (!this.enableMarking) {
return;
}
if (!this.markedRows.length) {
this.markRow(rowIndex);
} else {
if (this.markedRows.length > 1) {
this.markedRows.forEach((r,i) => {
if (i !== 0) {
r.marked = false;
}
});
this.markedRows.splice(1);
}
let lastRowToBeMarked = this.visibleRows[rowIndex];
let allRows = this.table.filteredRows.getRows(),
firstRowIndex = allRows.indexOf(this.markedRows[0]),
lastRowIndex = allRows.indexOf(lastRowToBeMarked);
//supports backward selection
if (lastRowIndex < firstRowIndex) {
let temp = lastRowIndex;
lastRowIndex = firstRowIndex;
firstRowIndex = temp - 1;
}
for (var i = firstRowIndex + 1; i <= lastRowIndex; i++) {
let row = allRows[i];
row.marked = true;
this.markedRows.push(row);
}
}
}
}, },
created() { created() {
this.filterChanged = _.debounce(this.filterChanged, 500); this.filterChanged = _.debounce(this.filterChanged, 500);
}, },
mounted() { mounted() {
this.csvExporter = new CSVExporter();
this.rowsAdded = _.throttle(this.rowsAdded, 200);
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
this.scroll = _.throttle(this.scroll, 100);
this.table.on('object-added', this.addObject); this.table.on('object-added', this.addObject);
this.table.on('object-removed', this.removeObject); this.table.on('object-removed', this.removeObject);
this.table.on('outstanding-requests', this.outstandingRequests); this.table.on('outstanding-requests', this.outstandingRequests);
this.table.on('refresh', this.clearRowsAndRerender);
this.table.filteredRows.on('add', this.rowsAdded); this.table.filteredRows.on('add', this.rowsAdded);
this.table.filteredRows.on('remove', this.rowsRemoved); this.table.filteredRows.on('remove', this.rowsRemoved);
@ -621,6 +820,7 @@ export default {
//Default sort //Default sort
this.sortOptions = this.table.filteredRows.sortBy(); this.sortOptions = this.table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w'); this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing'); this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w'); this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
@ -636,6 +836,7 @@ export default {
this.table.off('object-added', this.addObject); this.table.off('object-added', this.addObject);
this.table.off('object-removed', this.removeObject); this.table.off('object-removed', this.removeObject);
this.table.off('outstanding-requests', this.outstandingRequests); this.table.off('outstanding-requests', this.outstandingRequests);
this.table.off('refresh', this.clearRowsAndRerender);
this.table.filteredRows.off('add', this.rowsAdded); this.table.filteredRows.off('add', this.rowsAdded);
this.table.filteredRows.off('remove', this.rowsRemoved); this.table.filteredRows.off('remove', this.rowsRemoved);

View File

@ -1,6 +0,0 @@
<tr :style="{ top: rowTop }" :class="rowLimitClass">
<td v-for="(title, key, headerIndex) in headers"
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}"
:title="formattedRow[key]"
:class="cellLimitClasses[key]">{{formattedRow[key]}}</td>
</tr>

View File

@ -70,9 +70,6 @@ $colorBodyFgEm: #fff;
$colorGenBg: #222; $colorGenBg: #222;
$colorHeadBg: #262626; $colorHeadBg: #262626;
$colorHeadFg: $colorBodyFg; $colorHeadFg: $colorBodyFg;
$colorStatusBarBg: $colorHeadBg;
$colorStatusBarFg: $colorBodyFg;
$colorStatusBarFgHov: #aaa;
$colorKey: #0099cc; $colorKey: #0099cc;
$colorKeyFg: #fff; $colorKeyFg: #fff;
$colorKeyHov: #26d8ff; $colorKeyHov: #26d8ff;
@ -101,10 +98,12 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de
$colorStatusError: #da0004; $colorStatusError: #da0004;
$colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%); $colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%);
$colorStatusBtnBg: #666; // Where is this used? $colorStatusBtnBg: #666; // Where is this used?
$colorStatusPartialBg: #3f5e8b;
$colorStatusCompleteBg: #457638;
$colorAlert: #ff3c00; $colorAlert: #ff3c00;
$colorAlertFg: #fff; $colorAlertFg: #fff;
$colorWarningHi: #990000; $colorWarningHi: #ff0000;
$colorWarningHiFg: #FF9594; $colorWarningHiFg: #ffdad0;
$colorWarningLo: #ff9900; $colorWarningLo: #ff9900;
$colorWarningLoFg: #523400; $colorWarningLoFg: #523400;
$colorDiagnostic: #a4b442; $colorDiagnostic: #a4b442;
@ -115,6 +114,8 @@ $colorInfo: #2294a2;
$colorInfoFg: #fff; $colorInfoFg: #fff;
$colorOk: #33cc33; $colorOk: #33cc33;
$colorOkFg: #fff; $colorOkFg: #fff;
$colorFilterBg: #44449c;
$colorFilterFg: #8984e9;
// States // States
$colorPausedBg: #ff9900; $colorPausedBg: #ff9900;
@ -277,6 +278,11 @@ $colorIndicatorAvailable: $colorKey;
$colorIndicatorDisabled: #555555; $colorIndicatorDisabled: #555555;
$colorIndicatorOn: $colorOk; $colorIndicatorOn: $colorOk;
$colorIndicatorOff: #777777; $colorIndicatorOff: #777777;
$colorIndicatorBgHov: rgba($colorHeadFg, 0.1);
$colorIndicatorMenuBg: $colorHeadBg;
$colorIndicatorMenuBgShdw: rgba(white, 0.6) 0 0 6px;
$colorIndicatorMenuFg: $colorHeadFg;
$colorIndicatorMenuFgHov: pullForward($colorHeadFg, 10%);
// Staleness // Staleness
$colorTelemFresh: pullForward($colorBodyFg, 20%); $colorTelemFresh: pullForward($colorBodyFg, 20%);

View File

@ -74,9 +74,6 @@ $colorBodyFgEm: #fff;
$colorGenBg: #222; $colorGenBg: #222;
$colorHeadBg: #262626; $colorHeadBg: #262626;
$colorHeadFg: $colorBodyFg; $colorHeadFg: $colorBodyFg;
$colorStatusBarBg: $colorHeadBg;
$colorStatusBarFg: $colorBodyFg;
$colorStatusBarFgHov: #aaa;
$colorKey: #0099cc; $colorKey: #0099cc;
$colorKeyFg: #fff; $colorKeyFg: #fff;
$colorKeyHov: #26d8ff; $colorKeyHov: #26d8ff;
@ -105,10 +102,12 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de
$colorStatusError: #da0004; $colorStatusError: #da0004;
$colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%); $colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%);
$colorStatusBtnBg: #666; // Where is this used? $colorStatusBtnBg: #666; // Where is this used?
$colorStatusPartialBg: #3f5e8b;
$colorStatusCompleteBg: #457638;
$colorAlert: #ff3c00; $colorAlert: #ff3c00;
$colorAlertFg: #fff; $colorAlertFg: #fff;
$colorWarningHi: #990000; $colorWarningHi: #ff0000;
$colorWarningHiFg: #FF9594; $colorWarningHiFg: #ffdad0;
$colorWarningLo: #ff9900; $colorWarningLo: #ff9900;
$colorWarningLoFg: #523400; $colorWarningLoFg: #523400;
$colorDiagnostic: #a4b442; $colorDiagnostic: #a4b442;
@ -119,6 +118,8 @@ $colorInfo: #2294a2;
$colorInfoFg: #fff; $colorInfoFg: #fff;
$colorOk: #33cc33; $colorOk: #33cc33;
$colorOkFg: #fff; $colorOkFg: #fff;
$colorFilterBg: #44449c;
$colorFilterFg: #8984e9;
// States // States
$colorPausedBg: #ff9900; $colorPausedBg: #ff9900;
@ -281,6 +282,11 @@ $colorIndicatorAvailable: $colorKey;
$colorIndicatorDisabled: #555555; $colorIndicatorDisabled: #555555;
$colorIndicatorOn: $colorOk; $colorIndicatorOn: $colorOk;
$colorIndicatorOff: #777777; $colorIndicatorOff: #777777;
$colorIndicatorBgHov: rgba($colorHeadFg, 0.1);
$colorIndicatorMenuBg: $colorHeadBg;
$colorIndicatorMenuBgShdw: rgba(white, 0.6) 0 0 6px;
$colorIndicatorMenuFg: $colorHeadFg;
$colorIndicatorMenuFgHov: pullForward($colorHeadFg, 10%);
// Staleness // Staleness
$colorTelemFresh: pullForward($colorBodyFg, 20%); $colorTelemFresh: pullForward($colorBodyFg, 20%);

View File

@ -70,9 +70,6 @@ $colorBodyFgEm: #333;
$colorGenBg: #fff; $colorGenBg: #fff;
$colorHeadBg: #eee; $colorHeadBg: #eee;
$colorHeadFg: $colorBodyFg; $colorHeadFg: $colorBodyFg;
$colorStatusBarBg: #000;
$colorStatusBarFg: #999;
$colorStatusBarFgHov: #aaa;
$colorKey: #0099cc; $colorKey: #0099cc;
$colorKeyFg: #fff; $colorKeyFg: #fff;
$colorKeyHov: #00c0f6; $colorKeyHov: #00c0f6;
@ -101,6 +98,8 @@ $colorStatusAlertFilter: invert(89%) sepia(26%) saturate(5035%) hue-rotate(316de
$colorStatusError: #da0004; $colorStatusError: #da0004;
$colorStatusErrorFilter: invert(8%) sepia(96%) saturate(4511%) hue-rotate(352deg) brightness(136%) contrast(114%); $colorStatusErrorFilter: invert(8%) sepia(96%) saturate(4511%) hue-rotate(352deg) brightness(136%) contrast(114%);
$colorStatusBtnBg: #666; // Where is this used? $colorStatusBtnBg: #666; // Where is this used?
$colorStatusPartialBg: #c9d6ff;
$colorStatusCompleteBg: #a4e4b4;
$colorAlert: #ff3c00; $colorAlert: #ff3c00;
$colorAlertFg: #fff; $colorAlertFg: #fff;
$colorWarningHi: #990000; $colorWarningHi: #990000;
@ -115,6 +114,8 @@ $colorInfo: #2294a2;
$colorInfoFg: #fff; $colorInfoFg: #fff;
$colorOk: #33cc33; $colorOk: #33cc33;
$colorOkFg: #fff; $colorOkFg: #fff;
$colorFilterBg: #a29fe2;
$colorFilterFg: #fff;
// States // States
$colorPausedBg: #ff9900; $colorPausedBg: #ff9900;
@ -277,6 +278,11 @@ $colorIndicatorAvailable: $colorKey;
$colorIndicatorDisabled: #444; $colorIndicatorDisabled: #444;
$colorIndicatorOn: $colorOk; $colorIndicatorOn: $colorOk;
$colorIndicatorOff: #666; $colorIndicatorOff: #666;
$colorIndicatorBgHov: rgba($colorHeadFg, 0.1);
$colorIndicatorMenuBg: white;
$colorIndicatorMenuBgShdw: rgba(black, 0.6) 0 0 6px;
$colorIndicatorMenuFg: $colorHeadFg;
$colorIndicatorMenuFgHov: pullForward($colorHeadFg, 10%);
// Staleness // Staleness
$colorTelemFresh: pullForward($colorBodyFg, 20%); $colorTelemFresh: pullForward($colorBodyFg, 20%);

View File

@ -141,6 +141,8 @@ $glyph-icon-grid: '\e922';
$glyph-icon-grippy-ew: '\e923'; $glyph-icon-grippy-ew: '\e923';
$glyph-icon-columns: '\e924'; $glyph-icon-columns: '\e924';
$glyph-icon-rows: '\e925'; $glyph-icon-rows: '\e925';
$glyph-icon-filter: '\e926';
$glyph-icon-filter-outline: '\e927';
$glyph-icon-arrows-right-left: '\ea00'; $glyph-icon-arrows-right-left: '\ea00';
$glyph-icon-arrows-up-down: '\ea01'; $glyph-icon-arrows-up-down: '\ea01';
$glyph-icon-bullet: '\ea02'; $glyph-icon-bullet: '\ea02';

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 { &.is-active {
background: $colorBtnActiveBg; background: $colorBtnActiveBg;
color: $colorBtnActiveFg; color: $colorBtnActiveFg;
@ -60,23 +75,23 @@ button {
} }
} }
/********* Icon Buttons */ /********* Icon Buttons and Links */
.c-click-icon { .c-click-icon {
@include cClickIcon(); @include cClickIcon();
} }
.c-click-link {
// A clickable element, typically inline, with an icon and label
@include cControl();
cursor: pointer;
}
.c-icon-button, .c-icon-button,
.c-click-swatch { .c-click-swatch {
@include cClickIconButton(); @include cClickIconButton();
&--menu { &--menu {
&:after { @include hasMenu();
content: $glyph-icon-arrow-down;
font-family: symbolsfont;
font-size: 0.7em;
margin-left: floor($interiorMarginSm * 0.8);
opacity: 0.5;
}
} }
} }
@ -126,7 +141,7 @@ button {
/******************************************************** DISCLOSURE CONTROLS */ /******************************************************** DISCLOSURE CONTROLS */
/********* Disclosure Button */ /********* 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 // Always placed AFTER an element
.c-disclosure-button { .c-disclosure-button {
@include cClickIcon(); @include cClickIcon();
@ -585,15 +600,15 @@ select {
margin-right: $m; margin-right: $m;
} }
.c-separator {
@include cToolbarSeparator();
}
.c-toolbar { .c-toolbar {
> * + * { > * + * {
margin-left: 2px; margin-left: 2px;
} }
&__button {
}
&__separator { &__separator {
@include cToolbarSeparator(); @include cToolbarSeparator();
} }

View File

@ -30,10 +30,10 @@
} }
@font-face { @font-face {
// Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json to generate font files // Use https://icomoon.io/app with icomoon-project-Open-MCT-Symbols-12px.json to generate font files
font-family: 'symbolsfont-12px'; font-family: 'symbolsfont-12px';
src: url('./fonts/openmct-symbols-12px.woff') format('woff'), src: url('./fonts/Open-MCT-Symbols-12px.woff') format('woff'),
url('./fonts/openmct-symbols-12px.ttf') format('truetype'); url('./fonts/Open-MCT-Symbols-12px.ttf') format('truetype');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@ -77,6 +77,8 @@
.icon-grippy-ew { @include glyphBefore($glyph-icon-grippy-ew); } .icon-grippy-ew { @include glyphBefore($glyph-icon-grippy-ew); }
.icon-columns { @include glyphBefore($glyph-icon-columns); } .icon-columns { @include glyphBefore($glyph-icon-columns); }
.icon-rows { @include glyphBefore($glyph-icon-rows); } .icon-rows { @include glyphBefore($glyph-icon-rows); }
.icon-filter { @include glyphBefore($glyph-icon-filter); }
.icon-filter-outline { @include glyphBefore($glyph-icon-filter-outline); }
.icon-arrows-right-left { @include glyphBefore($glyph-icon-arrows-right-left); } .icon-arrows-right-left { @include glyphBefore($glyph-icon-arrows-right-left); }
.icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); } .icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); }
.icon-bullet { @include glyphBefore($glyph-icon-bullet); } .icon-bullet { @include glyphBefore($glyph-icon-bullet); }
@ -164,6 +166,8 @@
/************************** 12 PX CLASSES */ /************************** 12 PX CLASSES */
// TODO: sync with 16px redo as of 10/25/18 // TODO: sync with 16px redo as of 10/25/18
.icon-filter-12px { @include glyphBefore($glyph-icon-filter,'symbolsfont-12px'); }
.icon-filter-outline-12px { @include glyphBefore($glyph-icon-filter-outline,'symbolsfont-12px'); }
.icon-crosshair-12px { @include glyphBefore($glyph-icon-crosshair,'symbolsfont-12px'); } .icon-crosshair-12px { @include glyphBefore($glyph-icon-crosshair,'symbolsfont-12px'); }
.icon-folder-12px { @include glyphBefore($glyph-icon-folder,'symbolsfont-12px'); } .icon-folder-12px { @include glyphBefore($glyph-icon-folder,'symbolsfont-12px'); }
.icon-list-view-12px { @include glyphBefore($glyph-icon-list-view,'symbolsfont-12px'); } .icon-list-view-12px { @include glyphBefore($glyph-icon-list-view,'symbolsfont-12px'); }

View File

@ -782,126 +782,6 @@ mct-indicators mct-include {
display: contents; display: contents;
} }
.ls-indicator {
$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;
bottom: 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;
top: 100%;
@include triangle('down', $size: 4px, $ratio: 1, $color: $hbg);
}
}
@include hover() {
background: $bg;
.label {
opacity: 1;
transform: scale(1.0);
transition: all 100ms ease-out 0s;
}
}
}
&.float-right {
float: right;
}
}
/* Mobile */
// Hide the clock indicator when we're phone portrait
body.phone.portrait {
.ls-indicator.t-indicator-clock {
display: none;
}
}
/************************************************* DATETIME UI */ /************************************************* DATETIME UI */
@mixin complexFieldHolder($myW) { @mixin complexFieldHolder($myW) {
width: $myW + $interiorMargin; width: $myW + $interiorMargin;

View File

@ -420,20 +420,9 @@
} }
} }
@mixin cClickIconButton() { @mixin cClickIconButtonLayout() {
// 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
$pLR: 4px; $pLR: 4px;
$pTB: 4px; $pTB: 4px;
@include cControl();
background: none;
box-shadow: none;
cursor: pointer;
transition: $transOut;
border-radius: $controlCr;
padding: $pTB $pLR; padding: $pTB $pLR;
&:before, &:before,
@ -442,6 +431,20 @@
// Needed for c-togglebutton. // Needed for c-togglebutton.
font-size: 1.25em; 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() { @include hover() {
transition: $transIn; transition: $transIn;
@ -478,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) { @mixin cSelect($bg, $fg, $arwClr, $shdw) {
$svgArwClr: str-slice(inspect($arwClr), 2, str-length(inspect($arwClr))); // Remove initial # in color value $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; 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

@ -61,7 +61,7 @@
} }
@mixin indicatorStatusColors($c) { @mixin indicatorStatusColors($c) {
&:before, .count { &:before, .c-indicator__count {
color: $c; color: $c;
} }
} }
@ -127,7 +127,7 @@ tr {
.s-status-icon-ok:before { content: $glyph-icon-check; } .s-status-icon-ok:before { content: $glyph-icon-check; }
/*************************************************** INDICATOR COLORING */ /*************************************************** INDICATOR COLORING */
.ls-indicator { .c-indicator {
&.s-status-info { &.s-status-info {
@include indicatorStatusColors($colorInfo); @include indicatorStatusColors($colorInfo);
} }
@ -159,3 +159,16 @@ tr {
@include indicatorStatusColors($colorStatusError); @include indicatorStatusColors($colorStatusError);
} }
} }
.s-status {
&--partial {
// Partially completed things, such as a file downloading or process that's running
background-color: $colorStatusPartialBg;
}
&--complete {
// Completed things, such as a file downloaded or process that's finished
background-color: $colorStatusCompleteBg;
}
}

View File

@ -76,23 +76,43 @@ div.c-table {
height: 100%; height: 100%;
} }
.c-table-wrapper {
// Wraps .c-control-bar and .c-table
@include abs();
overflow: hidden;
display: flex;
flex-direction: column;
> .c-table {
height: auto;
flex: 1 1 auto;
}
> * + * {
margin-top: $interiorMarginSm;
}
}
.c-table-control-bar {
display: flex;
flex: 0 0 auto;
> * + * {
margin-left: $interiorMarginSm;
}
}
.c-table { .c-table {
// Can be used by any type of table, scrolling, LAD, etc. // Can be used by any type of table, scrolling, LAD, etc.
$min-w: 50px; $min-w: 50px;
width: 100%; width: 100%;
&__control-bar,
&__headers-w { &__headers-w {
flex: 0 0 auto; flex: 0 0 auto;
} }
/******************************* ELEMENTS */ /******************************* ELEMENTS */
&__control-bar {
margin-bottom: $interiorMarginSm;
}
thead tr, thead tr,
&.c-table__headers { &.c-table__headers {
background: $colorTabHeaderBg; background: $colorTabHeaderBg;

View File

@ -1,19 +1,35 @@
{ {
"metadata": { "metadata": {
"name": "openmct-symbols-12px", "name": "Open MCT Symbols 12px",
"lastOpened": 0, "lastOpened": 0,
"created": 1527031065005 "created": 1561483556329
}, },
"iconSets": [ "iconSets": [
{ {
"selection": [ "selection": [
{
"order": 12,
"id": 10,
"name": "icon12-filter",
"prevSize": 12,
"code": 59686,
"tempChar": ""
},
{
"order": 14,
"id": 11,
"name": "icon12-filter-outline",
"prevSize": 12,
"code": 59687,
"tempChar": ""
},
{ {
"order": 9, "order": 9,
"id": 6, "id": 6,
"name": "icon12-crosshair", "name": "icon12-crosshair",
"prevSize": 12, "prevSize": 12,
"code": 59696, "code": 59696,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 11, "order": 11,
@ -21,7 +37,7 @@
"name": "icon12-grippy", "name": "icon12-grippy",
"prevSize": 12, "prevSize": 12,
"code": 59697, "code": 59697,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 10, "order": 10,
@ -29,7 +45,7 @@
"name": "icon12-list-view", "name": "icon12-list-view",
"prevSize": 12, "prevSize": 12,
"code": 921666, "code": 921666,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 6, "order": 6,
@ -37,14 +53,14 @@
"prevSize": 12, "prevSize": 12,
"code": 921865, "code": 921865,
"name": "icon12-folder", "name": "icon12-folder",
"tempChar": "" "tempChar": ""
} }
], ],
"id": 0, "id": 0,
"metadata": { "metadata": {
"name": "openmct-symbols-12px", "name": "Open MCT Symbols 12px",
"importSize": { "importSize": {
"width": 279, "width": 384,
"height": 384 "height": 384
}, },
"designer": "Charles Hacskaylo" "designer": "Charles Hacskaylo"
@ -52,6 +68,28 @@
"height": 1024, "height": 1024,
"prevSize": 12, "prevSize": 12,
"icons": [ "icons": [
{
"id": 10,
"paths": [
"M853.333 0h-682.667c-94.135 0.302-170.364 76.532-170.667 170.638l-0 0.029v682.667c0.302 94.135 76.532 170.364 170.638 170.667l0.029 0h256v-341.333l-341.333-341.333h853.333l-341.333 341.333 1.067 341.333h254.933c94.135-0.302 170.364-76.532 170.667-170.638l0-0.029v-682.667c-0.302-94.135-76.532-170.364-170.638-170.667l-0.029-0z"
],
"attrs": [],
"grid": 0,
"tags": [
"icon12-filter"
]
},
{
"id": 11,
"paths": [
"M853.333 0h-682.667c-94.135 0.302-170.364 76.532-170.667 170.638l-0 0.029v682.667c0.302 94.135 76.532 170.364 170.638 170.667l0.029 0h682.667c94.135-0.302 170.364-76.532 170.667-170.638l0-0.029v-682.667c-0.302-94.135-76.532-170.364-170.638-170.667l-0.029-0zM170.933 853.333h-0.267v-512l256 256v256zM853.067 853.333h-255.2l-0.533-256 256-256v511.733zM853.333 341.333h-682.667v-170.4h682.667z"
],
"attrs": [],
"grid": 0,
"tags": [
"icon12-filter-outline"
]
},
{ {
"id": 6, "id": 6,
"paths": [ "paths": [
@ -60,26 +98,11 @@
"M597.333 768h-170.667v256h170.667v-256z", "M597.333 768h-170.667v256h170.667v-256z",
"M256 426.667h-256v170.667h256v-170.667z" "M256 426.667h-256v170.667h256v-170.667z"
], ],
"attrs": [ "attrs": [],
{},
{},
{},
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 0, "grid": 0,
"tags": [ "tags": [
"icon12-crosshair" "icon12-crosshair"
], ]
"colorPermutations": {
"1161751": [
{},
{},
{},
{}
]
}
}, },
{ {
"id": 8, "id": 8,
@ -95,39 +118,12 @@
"M744.773 511.867c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z", "M744.773 511.867c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z",
"M744.773 791.36c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z" "M744.773 791.36c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z"
], ],
"attrs": [ "attrs": [],
{},
{},
{},
{},
{},
{},
{},
{},
{},
{}
],
"width": 745, "width": 745,
"isMulticolor": false,
"isMulticolor2": false,
"grid": 0, "grid": 0,
"tags": [ "tags": [
"icon12-grippy" "icon12-grippy"
], ]
"colorPermutations": {
"1161751": [
{},
{},
{},
{},
{},
{},
{},
{},
{},
{}
]
}
}, },
{ {
"id": 7, "id": 7,
@ -136,24 +132,11 @@
"M0 426.667h1024v170.667h-1024v-170.667z", "M0 426.667h1024v170.667h-1024v-170.667z",
"M0 853.333h1024v170.667h-1024v-170.667z" "M0 853.333h1024v170.667h-1024v-170.667z"
], ],
"attrs": [ "attrs": [],
{},
{},
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 0, "grid": 0,
"tags": [ "tags": [
"icon12-list-view" "icon12-list-view"
], ]
"colorPermutations": {
"1161751": [
{},
{},
{}
]
}
}, },
{ {
"id": 3, "id": 3,
@ -162,40 +145,14 @@
"M85.333 426.667h853.333c47.128 0 85.333 38.205 85.333 85.333v426.667c0 47.128-38.205 85.333-85.333 85.333h-853.333c-47.128 0-85.333-38.205-85.333-85.333v-426.667c0-47.128 38.205-85.333 85.333-85.333z" "M85.333 426.667h853.333c47.128 0 85.333 38.205 85.333 85.333v426.667c0 47.128-38.205 85.333-85.333 85.333h-853.333c-47.128 0-85.333-38.205-85.333-85.333v-426.667c0-47.128 38.205-85.333 85.333-85.333z"
], ],
"attrs": [], "attrs": [],
"isMulticolor": false,
"grid": 0, "grid": 0,
"tags": [ "tags": [
"icon12-folder" "icon12-folder"
], ]
"colorPermutations": {
"1161751": [
{
"f": 0
},
{
"f": 0
}
]
}
} }
], ],
"invisible": false, "invisible": false,
"colorThemes": [ "colorThemes": [],
[
[
0,
0,
0,
1
],
[
0,
161,
75,
1
]
]
],
"colorThemeIdx": 0 "colorThemeIdx": 0
} }
], ],
@ -206,9 +163,9 @@
"showQuickUse2": true, "showQuickUse2": true,
"showSVGs": true, "showSVGs": true,
"fontPref": { "fontPref": {
"prefix": "icon-", "prefix": "openmct-symbols-",
"metadata": { "metadata": {
"fontFamily": "openmct-symbols-12px", "fontFamily": "Open-MCT-Symbols-12px",
"majorVersion": 1, "majorVersion": 1,
"minorVersion": 0 "minorVersion": 0
}, },
@ -217,7 +174,12 @@
"baseline": 6.25, "baseline": 6.25,
"whitespace": 50 "whitespace": 50
}, },
"embed": false "embed": false,
"noie8": true,
"ie7": false,
"showMetadata": false,
"includeMetadata": false,
"showMetrics": true
}, },
"imagePref": { "imagePref": {
"prefix": "icon-", "prefix": "icon-",

View File

@ -1,8 +1,8 @@
{ {
"metadata": { "metadata": {
"name": "Open MCT Symbols", "name": "Open MCT Symbols 16px",
"lastOpened": 0, "lastOpened": 0,
"created": 1541830438012 "created": 1561482854927
}, },
"iconSets": [ "iconSets": [
{ {
@ -317,13 +317,29 @@
"code": 59685, "code": 59685,
"tempChar": "" "tempChar": ""
}, },
{
"order": 161,
"id": 142,
"name": "icon-filter",
"prevSize": 32,
"code": 59686,
"tempChar": ""
},
{
"order": 162,
"id": 141,
"name": "icon-filter-outline",
"prevSize": 32,
"code": 59687,
"tempChar": ""
},
{ {
"order": 27, "order": 27,
"id": 105, "id": 105,
"name": "icon-arrows-right-left", "name": "icon-arrows-right-left",
"prevSize": 32, "prevSize": 32,
"code": 59904, "code": 59904,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 26, "order": 26,
@ -331,7 +347,7 @@
"name": "icon-arrows-up-down", "name": "icon-arrows-up-down",
"prevSize": 32, "prevSize": 32,
"code": 59905, "code": 59905,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 68, "order": 68,
@ -339,7 +355,7 @@
"name": "icon-bullet", "name": "icon-bullet",
"prevSize": 32, "prevSize": 32,
"code": 59906, "code": 59906,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 150, "order": 150,
@ -347,7 +363,7 @@
"prevSize": 32, "prevSize": 32,
"code": 59907, "code": 59907,
"name": "icon-calendar", "name": "icon-calendar",
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 45, "order": 45,
@ -355,7 +371,7 @@
"name": "icon-chain-links", "name": "icon-chain-links",
"prevSize": 32, "prevSize": 32,
"code": 59908, "code": 59908,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 73, "order": 73,
@ -363,7 +379,7 @@
"name": "icon-download", "name": "icon-download",
"prevSize": 32, "prevSize": 32,
"code": 59909, "code": 59909,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 39, "order": 39,
@ -371,7 +387,7 @@
"name": "icon-duplicate", "name": "icon-duplicate",
"prevSize": 32, "prevSize": 32,
"code": 59910, "code": 59910,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 50, "order": 50,
@ -379,7 +395,7 @@
"name": "icon-folder-new", "name": "icon-folder-new",
"prevSize": 32, "prevSize": 32,
"code": 59911, "code": 59911,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 138, "order": 138,
@ -387,7 +403,7 @@
"name": "icon-fullscreen-collapse", "name": "icon-fullscreen-collapse",
"prevSize": 32, "prevSize": 32,
"code": 59912, "code": 59912,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 139, "order": 139,
@ -395,7 +411,7 @@
"name": "icon-fullscreen-expand", "name": "icon-fullscreen-expand",
"prevSize": 32, "prevSize": 32,
"code": 59913, "code": 59913,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 122, "order": 122,
@ -403,7 +419,7 @@
"name": "icon-layers", "name": "icon-layers",
"prevSize": 32, "prevSize": 32,
"code": 59914, "code": 59914,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 151, "order": 151,
@ -411,7 +427,7 @@
"name": "icon-line-horz", "name": "icon-line-horz",
"prevSize": 32, "prevSize": 32,
"code": 59915, "code": 59915,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 100, "order": 100,
@ -419,7 +435,7 @@
"name": "icon-magnify", "name": "icon-magnify",
"prevSize": 32, "prevSize": 32,
"code": 59916, "code": 59916,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 99, "order": 99,
@ -427,7 +443,7 @@
"name": "icon-magnify-in", "name": "icon-magnify-in",
"prevSize": 32, "prevSize": 32,
"code": 59917, "code": 59917,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 101, "order": 101,
@ -435,7 +451,7 @@
"name": "icon-magnify-out-v2", "name": "icon-magnify-out-v2",
"prevSize": 32, "prevSize": 32,
"code": 59918, "code": 59918,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 103, "order": 103,
@ -443,7 +459,7 @@
"name": "icon-menu", "name": "icon-menu",
"prevSize": 32, "prevSize": 32,
"code": 59919, "code": 59919,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 124, "order": 124,
@ -451,7 +467,7 @@
"name": "icon-move", "name": "icon-move",
"prevSize": 32, "prevSize": 32,
"code": 59920, "code": 59920,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 7, "order": 7,
@ -459,7 +475,7 @@
"name": "icon-new-window", "name": "icon-new-window",
"prevSize": 32, "prevSize": 32,
"code": 59921, "code": 59921,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 63, "order": 63,
@ -467,7 +483,7 @@
"name": "icon-paint-bucket-v2", "name": "icon-paint-bucket-v2",
"prevSize": 32, "prevSize": 32,
"code": 59922, "code": 59922,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 15, "order": 15,
@ -475,7 +491,7 @@
"name": "icon-pencil", "name": "icon-pencil",
"prevSize": 32, "prevSize": 32,
"code": 59923, "code": 59923,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 54, "order": 54,
@ -483,7 +499,7 @@
"name": "icon-pencil-edit-in-place", "name": "icon-pencil-edit-in-place",
"prevSize": 32, "prevSize": 32,
"code": 59924, "code": 59924,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 40, "order": 40,
@ -491,7 +507,7 @@
"name": "icon-play", "name": "icon-play",
"prevSize": 32, "prevSize": 32,
"code": 59925, "code": 59925,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 125, "order": 125,
@ -499,7 +515,7 @@
"name": "icon-pause", "name": "icon-pause",
"prevSize": 32, "prevSize": 32,
"code": 59926, "code": 59926,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 119, "order": 119,
@ -507,7 +523,7 @@
"name": "icon-plot-resource", "name": "icon-plot-resource",
"prevSize": 32, "prevSize": 32,
"code": 59927, "code": 59927,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 48, "order": 48,
@ -515,7 +531,7 @@
"name": "icon-pointer-left", "name": "icon-pointer-left",
"prevSize": 32, "prevSize": 32,
"code": 59928, "code": 59928,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 47, "order": 47,
@ -523,7 +539,7 @@
"name": "icon-pointer-right", "name": "icon-pointer-right",
"prevSize": 32, "prevSize": 32,
"code": 59929, "code": 59929,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 85, "order": 85,
@ -531,7 +547,7 @@
"name": "icon-refresh", "name": "icon-refresh",
"prevSize": 32, "prevSize": 32,
"code": 59930, "code": 59930,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 55, "order": 55,
@ -539,7 +555,7 @@
"name": "icon-save", "name": "icon-save",
"prevSize": 32, "prevSize": 32,
"code": 59931, "code": 59931,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 56, "order": 56,
@ -547,7 +563,7 @@
"name": "icon-save-as", "name": "icon-save-as",
"prevSize": 32, "prevSize": 32,
"code": 59932, "code": 59932,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 58, "order": 58,
@ -555,7 +571,7 @@
"name": "icon-sine", "name": "icon-sine",
"prevSize": 32, "prevSize": 32,
"code": 59933, "code": 59933,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 113, "order": 113,
@ -563,7 +579,7 @@
"name": "icon-font", "name": "icon-font",
"prevSize": 32, "prevSize": 32,
"code": 59934, "code": 59934,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 41, "order": 41,
@ -571,7 +587,7 @@
"name": "icon-thumbs-strip", "name": "icon-thumbs-strip",
"prevSize": 32, "prevSize": 32,
"code": 59935, "code": 59935,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 146, "order": 146,
@ -579,7 +595,7 @@
"name": "icon-two-parts-both", "name": "icon-two-parts-both",
"prevSize": 32, "prevSize": 32,
"code": 59936, "code": 59936,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 145, "order": 145,
@ -587,7 +603,7 @@
"name": "icon-two-parts-one-only", "name": "icon-two-parts-one-only",
"prevSize": 32, "prevSize": 32,
"code": 59937, "code": 59937,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 82, "order": 82,
@ -595,7 +611,7 @@
"name": "icon-resync", "name": "icon-resync",
"prevSize": 32, "prevSize": 32,
"code": 59938, "code": 59938,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 86, "order": 86,
@ -603,7 +619,7 @@
"name": "icon-reset", "name": "icon-reset",
"prevSize": 32, "prevSize": 32,
"code": 59939, "code": 59939,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 61, "order": 61,
@ -611,7 +627,7 @@
"name": "icon-x-in-circle", "name": "icon-x-in-circle",
"prevSize": 32, "prevSize": 32,
"code": 59940, "code": 59940,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 84, "order": 84,
@ -619,7 +635,7 @@
"name": "icon-brightness", "name": "icon-brightness",
"prevSize": 32, "prevSize": 32,
"code": 59941, "code": 59941,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 83, "order": 83,
@ -627,7 +643,7 @@
"name": "icon-contrast", "name": "icon-contrast",
"prevSize": 32, "prevSize": 32,
"code": 59942, "code": 59942,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 87, "order": 87,
@ -635,7 +651,7 @@
"name": "icon-expand", "name": "icon-expand",
"prevSize": 32, "prevSize": 32,
"code": 59943, "code": 59943,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 89, "order": 89,
@ -643,7 +659,7 @@
"name": "icon-list-view", "name": "icon-list-view",
"prevSize": 32, "prevSize": 32,
"code": 59944, "code": 59944,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 133, "order": 133,
@ -651,7 +667,7 @@
"name": "icon-grid-snap-to", "name": "icon-grid-snap-to",
"prevSize": 32, "prevSize": 32,
"code": 59945, "code": 59945,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 132, "order": 132,
@ -659,7 +675,7 @@
"name": "icon-grid-snap-no", "name": "icon-grid-snap-no",
"prevSize": 32, "prevSize": 32,
"code": 59946, "code": 59946,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 94, "order": 94,
@ -667,7 +683,7 @@
"name": "icon-frame-show", "name": "icon-frame-show",
"prevSize": 32, "prevSize": 32,
"code": 59947, "code": 59947,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 95, "order": 95,
@ -675,7 +691,7 @@
"name": "icon-frame-hide", "name": "icon-frame-hide",
"prevSize": 32, "prevSize": 32,
"code": 59948, "code": 59948,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 97, "order": 97,
@ -683,7 +699,7 @@
"name": "icon-import", "name": "icon-import",
"prevSize": 32, "prevSize": 32,
"code": 59949, "code": 59949,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 96, "order": 96,
@ -691,7 +707,7 @@
"name": "icon-export", "name": "icon-export",
"prevSize": 32, "prevSize": 32,
"code": 59950, "code": 59950,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 114, "order": 114,
@ -699,7 +715,7 @@
"name": "icon-font-size", "name": "icon-font-size",
"prevSize": 32, "prevSize": 32,
"code": 59951, "code": 59951,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 144, "order": 144,
@ -707,7 +723,7 @@
"name": "icon-activity", "name": "icon-activity",
"prevSize": 32, "prevSize": 32,
"code": 60160, "code": 60160,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 104, "order": 104,
@ -715,7 +731,7 @@
"name": "icon-activity-mode", "name": "icon-activity-mode",
"prevSize": 32, "prevSize": 32,
"code": 60161, "code": 60161,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 137, "order": 137,
@ -723,7 +739,7 @@
"name": "icon-autoflow-tabular", "name": "icon-autoflow-tabular",
"prevSize": 32, "prevSize": 32,
"code": 60162, "code": 60162,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 115, "order": 115,
@ -731,7 +747,7 @@
"name": "icon-clock", "name": "icon-clock",
"prevSize": 32, "prevSize": 32,
"code": 60163, "code": 60163,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 2, "order": 2,
@ -739,7 +755,7 @@
"name": "icon-database", "name": "icon-database",
"prevSize": 32, "prevSize": 32,
"code": 60164, "code": 60164,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 3, "order": 3,
@ -747,7 +763,7 @@
"name": "icon-database-query", "name": "icon-database-query",
"prevSize": 32, "prevSize": 32,
"code": 60165, "code": 60165,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 67, "order": 67,
@ -755,7 +771,7 @@
"name": "icon-dataset", "name": "icon-dataset",
"prevSize": 32, "prevSize": 32,
"code": 60166, "code": 60166,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 59, "order": 59,
@ -763,7 +779,7 @@
"name": "icon-datatable", "name": "icon-datatable",
"prevSize": 32, "prevSize": 32,
"code": 60167, "code": 60167,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 136, "order": 136,
@ -771,7 +787,7 @@
"name": "icon-dictionary", "name": "icon-dictionary",
"prevSize": 32, "prevSize": 32,
"code": 60168, "code": 60168,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 51, "order": 51,
@ -779,7 +795,7 @@
"name": "icon-folder", "name": "icon-folder",
"prevSize": 32, "prevSize": 32,
"code": 60169, "code": 60169,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 147, "order": 147,
@ -787,7 +803,7 @@
"name": "icon-image", "name": "icon-image",
"prevSize": 32, "prevSize": 32,
"code": 60170, "code": 60170,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 4, "order": 4,
@ -795,7 +811,7 @@
"name": "icon-layout", "name": "icon-layout",
"prevSize": 32, "prevSize": 32,
"code": 60171, "code": 60171,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 24, "order": 24,
@ -803,7 +819,7 @@
"name": "icon-object", "name": "icon-object",
"prevSize": 32, "prevSize": 32,
"code": 60172, "code": 60172,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 52, "order": 52,
@ -811,7 +827,7 @@
"name": "icon-object-unknown", "name": "icon-object-unknown",
"prevSize": 32, "prevSize": 32,
"code": 60173, "code": 60173,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 105, "order": 105,
@ -819,7 +835,7 @@
"name": "icon-packet", "name": "icon-packet",
"prevSize": 32, "prevSize": 32,
"code": 60174, "code": 60174,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 126, "order": 126,
@ -827,7 +843,7 @@
"name": "icon-page", "name": "icon-page",
"prevSize": 32, "prevSize": 32,
"code": 60175, "code": 60175,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 130, "order": 130,
@ -835,7 +851,7 @@
"name": "icon-plot-overlay", "name": "icon-plot-overlay",
"prevSize": 32, "prevSize": 32,
"code": 60176, "code": 60176,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 80, "order": 80,
@ -843,7 +859,7 @@
"name": "icon-plot-stacked", "name": "icon-plot-stacked",
"prevSize": 32, "prevSize": 32,
"code": 60177, "code": 60177,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 134, "order": 134,
@ -851,7 +867,7 @@
"name": "icon-session", "name": "icon-session",
"prevSize": 32, "prevSize": 32,
"code": 60178, "code": 60178,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 109, "order": 109,
@ -859,7 +875,7 @@
"name": "icon-tabular", "name": "icon-tabular",
"prevSize": 32, "prevSize": 32,
"code": 60179, "code": 60179,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 107, "order": 107,
@ -867,7 +883,7 @@
"name": "icon-tabular-lad", "name": "icon-tabular-lad",
"prevSize": 32, "prevSize": 32,
"code": 60180, "code": 60180,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 106, "order": 106,
@ -875,7 +891,7 @@
"name": "icon-tabular-lad-set", "name": "icon-tabular-lad-set",
"prevSize": 32, "prevSize": 32,
"code": 60181, "code": 60181,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 70, "order": 70,
@ -883,7 +899,7 @@
"name": "icon-tabular-realtime", "name": "icon-tabular-realtime",
"prevSize": 32, "prevSize": 32,
"code": 60182, "code": 60182,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 60, "order": 60,
@ -891,7 +907,7 @@
"name": "icon-tabular-scrolling", "name": "icon-tabular-scrolling",
"prevSize": 32, "prevSize": 32,
"code": 60183, "code": 60183,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 131, "order": 131,
@ -899,7 +915,7 @@
"name": "icon-telemetry", "name": "icon-telemetry",
"prevSize": 32, "prevSize": 32,
"code": 60184, "code": 60184,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 108, "order": 108,
@ -907,7 +923,7 @@
"name": "icon-timeline", "name": "icon-timeline",
"prevSize": 32, "prevSize": 32,
"code": 60185, "code": 60185,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 81, "order": 81,
@ -915,7 +931,7 @@
"name": "icon-timer", "name": "icon-timer",
"prevSize": 32, "prevSize": 32,
"code": 60186, "code": 60186,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 69, "order": 69,
@ -923,7 +939,7 @@
"name": "icon-topic", "name": "icon-topic",
"prevSize": 32, "prevSize": 32,
"code": 60187, "code": 60187,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 79, "order": 79,
@ -931,7 +947,7 @@
"name": "icon-box-with-dashed-lines-v2", "name": "icon-box-with-dashed-lines-v2",
"prevSize": 32, "prevSize": 32,
"code": 60188, "code": 60188,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 90, "order": 90,
@ -939,7 +955,7 @@
"name": "icon-summary-widget", "name": "icon-summary-widget",
"prevSize": 32, "prevSize": 32,
"code": 60189, "code": 60189,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 92, "order": 92,
@ -947,7 +963,7 @@
"name": "icon-notebook", "name": "icon-notebook",
"prevSize": 32, "prevSize": 32,
"code": 60190, "code": 60190,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 118, "order": 118,
@ -955,7 +971,7 @@
"name": "icon-tabs-view", "name": "icon-tabs-view",
"prevSize": 32, "prevSize": 32,
"code": 60191, "code": 60191,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 117, "order": 117,
@ -963,7 +979,7 @@
"name": "icon-flexible-layout", "name": "icon-flexible-layout",
"prevSize": 32, "prevSize": 32,
"code": 60192, "code": 60192,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 152, "order": 152,
@ -971,7 +987,7 @@
"name": "icon-generator-sine", "name": "icon-generator-sine",
"prevSize": 32, "prevSize": 32,
"code": 60193, "code": 60193,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 153, "order": 153,
@ -979,7 +995,7 @@
"name": "icon-generator-event", "name": "icon-generator-event",
"prevSize": 32, "prevSize": 32,
"code": 60194, "code": 60194,
"tempChar": "" "tempChar": ""
}, },
{ {
"order": 160, "order": 160,
@ -987,7 +1003,7 @@
"name": "icon-gauge", "name": "icon-gauge",
"prevSize": 32, "prevSize": 32,
"code": 60195, "code": 60195,
"tempChar": "" "tempChar": ""
} }
], ],
"id": 0, "id": 0,
@ -1602,6 +1618,46 @@
] ]
} }
}, },
{
"id": 142,
"paths": [
"M896 0h-768c-70.601 0.227-127.773 57.399-128 127.978l-0 0.022v768c0.227 70.601 57.399 127.773 127.978 128l0.022 0h256v-512l-192-192h640l-192 192v512h256c70.601-0.227 127.773-57.399 128-127.978l0-0.022v-768c-0.227-70.601-57.399-127.773-127.978-128l-0.022-0z"
],
"attrs": [
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 1,
"tags": [
"icon-filter"
],
"colorPermutations": {
"11841841841": [
{}
]
}
},
{
"id": 141,
"paths": [
"M896 0h-768c-70.601 0.227-127.773 57.399-128 127.978l-0 0.022v768c0.227 70.601 57.399 127.773 127.978 128l0.022 0h768c70.601-0.227 127.773-57.399 128-127.978l0-0.022v-768c-0.227-70.601-57.399-127.773-127.978-128l-0.022-0zM896 895.8h-256v-383.8l192-192h-640l192 192v384h-256v-767.8h768z"
],
"attrs": [
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 1,
"tags": [
"icon-filter-outline"
],
"colorPermutations": {
"11841841841": [
{}
]
}
},
{ {
"id": 105, "id": 105,
"paths": [ "paths": [

Binary file not shown.

Binary file not shown.

BIN
src/styles-new/fonts/Open-MCT-Symbols-16px.ttf Executable file → Normal file

Binary file not shown.

BIN
src/styles-new/fonts/Open-MCT-Symbols-16px.woff Executable file → Normal file

Binary file not shown.

View File

@ -123,7 +123,12 @@
.c-click-icon, .c-click-icon,
.c-button { .c-button {
// Shrink buttons a bit when they appear in a frame // Shrink buttons a bit when they appear in a frame
font-size: 0.8em; align-items: baseline;
font-size: 0.85em;
padding: 3px 5px;
&:before {
font-size: 0.8em;
}
} }
} }
</style> </style>

View File

@ -34,10 +34,10 @@ export default {
this.currentObject = this.object; this.currentObject = this.object;
this.updateView(); this.updateView();
this.$el.addEventListener('dragover', this.onDragOver); this.$el.addEventListener('dragover', this.onDragOver);
this.$el.addEventListener('drop', this.addObjectToParent);
this.$el.addEventListener('drop', this.editIfEditable, { this.$el.addEventListener('drop', this.editIfEditable, {
capture: true capture: true
}); });
this.$el.addEventListener('drop', this.addObjectToParent);
}, },
methods: { methods: {
clear() { clear() {
@ -57,6 +57,12 @@ export default {
this.removeSelectable(); this.removeSelectable();
delete this.removeSelectable; delete this.removeSelectable;
} }
if (this.composition) {
this.composition._destroy();
}
this.openmct.objectViews.off('clearData', this.clearData);
}, },
invokeEditModeHandler(editMode) { invokeEditModeHandler(editMode) {
this.currentView.onEditModeChange(editMode); this.currentView.onEditModeChange(editMode);
@ -70,6 +76,13 @@ export default {
if (!this.currentObject) { if (!this.currentObject) {
return; return;
} }
this.composition = this.openmct.composition.get(this.currentObject);
if (this.composition) {
this.composition._synchronize();
this.loadComposition();
}
this.viewContainer = document.createElement('div'); this.viewContainer = document.createElement('div');
this.viewContainer.classList.add('c-object-view','u-contents'); this.viewContainer.classList.add('c-object-view','u-contents');
this.$el.append(this.viewContainer); this.$el.append(this.viewContainer);
@ -101,6 +114,8 @@ export default {
this.removeSelectable = openmct.selection.selectable( this.removeSelectable = openmct.selection.selectable(
this.$el, this.getSelectionContext(), true); this.$el, this.getSelectionContext(), true);
} }
this.openmct.objectViews.on('clearData', this.clearData);
}, },
show(object, viewKey, immediatelySelect) { show(object, viewKey, immediatelySelect) {
if (this.unlisten) { if (this.unlisten) {
@ -112,6 +127,10 @@ export default {
delete this.removeSelectable; delete this.removeSelectable;
} }
if (this.composition) {
this.composition._destroy();
}
this.currentObject = object; this.currentObject = object;
this.unlisten = this.openmct.objects.observe(this.currentObject, '*', (mutatedObject) => { this.unlisten = this.openmct.objects.observe(this.currentObject, '*', (mutatedObject) => {
this.currentObject = mutatedObject; this.currentObject = mutatedObject;
@ -120,6 +139,9 @@ export default {
this.viewKey = viewKey; this.viewKey = viewKey;
this.updateView(immediatelySelect); this.updateView(immediatelySelect);
}, },
loadComposition() {
return this.composition.load();
},
getSelectionContext() { getSelectionContext() {
if (this.currentView.getSelectionContext) { if (this.currentView.getSelectionContext) {
return this.currentView.getSelectionContext(); return this.currentView.getSelectionContext();
@ -133,10 +155,12 @@ export default {
} }
}, },
addObjectToParent(event) { addObjectToParent(event) {
if (this.hasComposableDomainObject(event)) { if (this.hasComposableDomainObject(event) && this.composition) {
let composableDomainObject = this.getComposableDomainObject(event); let composableDomainObject = this.getComposableDomainObject(event);
this.currentObject.composition.push(composableDomainObject.identifier); this.loadComposition().then(() => {
this.openmct.objects.mutate(this.currentObject, 'composition', this.currentObject.composition); this.composition.add(composableDomainObject);
});
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} }
@ -155,6 +179,7 @@ export default {
editIfEditable(event) { editIfEditable(event) {
let provider = this.getViewProvider(); let provider = this.getViewProvider();
if (provider && if (provider &&
provider.canEdit &&
provider.canEdit(this.currentObject) && provider.canEdit(this.currentObject) &&
!this.openmct.editor.isEditing()) { !this.openmct.editor.isEditing()) {
this.openmct.editor.edit(); this.openmct.editor.edit();
@ -166,6 +191,22 @@ export default {
getComposableDomainObject(event) { getComposableDomainObject(event) {
let serializedDomainObject = event.dataTransfer.getData('openmct/composable-domain-object'); let serializedDomainObject = event.dataTransfer.getData('openmct/composable-domain-object');
return JSON.parse(serializedDomainObject); return JSON.parse(serializedDomainObject);
},
clearData(domainObject) {
if (domainObject) {
let clearKeyString = this.openmct.objects.makeKeyString(domainObject.identifier),
currentObjectKeyString = this.openmct.objects.makeKeyString(this.currentObject.identifier);
if (clearKeyString === currentObjectKeyString) {
if (this.currentView.onClearData) {
this.currentView.onClearData();
}
}
} else {
if (this.currentView.onClearData) {
this.currentView.onClearData();
}
}
} }
} }
} }

View File

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

View File

@ -2,9 +2,15 @@
<div class="l-shell" :class="{ <div class="l-shell" :class="{
'is-editing': isEditing 'is-editing': isEditing
}"> }">
<div class="l-shell__head"> <div class="l-shell__head" :class="{
'l-shell__head--expanded': headExpanded,
'l-shell__head--minify-indicators': !headExpanded
}">
<CreateButton class="l-shell__create-button"></CreateButton> <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>
<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" <button class="c-icon-button c-icon-button--major icon-new-window" title="Open in a new browser tab"
@click="openInNewTab" @click="openInNewTab"
target="_blank"> target="_blank">
@ -15,6 +21,8 @@
</button> </button>
</div> </div>
<app-logo></app-logo> <app-logo></app-logo>
<button class="l-shell__head__collapse-button c-button"
@click="toggleShellHead"></button>
</div> </div>
<multipane class="l-shell__main" <multipane class="l-shell__main"
type="horizontal"> type="horizontal">
@ -44,9 +52,6 @@
<Inspector :isEditing="isEditing" ref="inspector"></Inspector> <Inspector :isEditing="isEditing" ref="inspector"></Inspector>
</pane> </pane>
</multipane> </multipane>
<div class="l-shell__status">
<StatusBar></StatusBar>
</div>
</div> </div>
</template> </template>
@ -61,12 +66,6 @@
flex-flow: column nowrap; flex-flow: column nowrap;
overflow: hidden; overflow: hidden;
&__status {
background: $colorStatusBarBg;
color: $colorStatusBarFg;
padding: $interiorMarginSm;
}
&__pane-tree { &__pane-tree {
width: 40%; width: 40%;
@ -160,14 +159,52 @@
} }
&__head { &__head {
align-items: center; align-items: stretch;
background: $colorHeadBg; background: $colorHeadBg;
justify-content: space-between; justify-content: space-between;
padding: $interiorMargin; padding: $interiorMargin $interiorMargin + 2;
> [class*="__"] + [class*="__"] { > [class*="__"] + [class*="__"] {
margin-left: $interiorMargin; margin-left: $interiorMargin;
} }
[class*='__head__collapse-button'] {
align-self: start;
$p: 6px;
padding-left: $p !important;
padding-right: $p !important;
&:before {
content: $glyph-icon-arrow-down;
font-size: 1.1em;
}
}
&-section {
// Subdivides elements across the head
display: flex;
flex: 0 1 auto;
padding: 0 $interiorMargin;
}
&--expanded {
.c-indicator__label {
transition: none !important;
}
[class*='__head__collapse-button'] {
&:before {
transform: rotate(180deg);
}
}
}
}
&__controls {
$brdr: 1px solid $colorInteriorBorder;
border-right: $brdr;
border-left: $brdr;
align-items: start;
} }
&__create-button, &__create-button,
@ -175,11 +212,17 @@
flex: 0 0 auto; flex: 0 0 auto;
} }
&__controls { &__create-button { margin-right: $interiorMarginLg; }
flex: 1 1 100%;
display: flex; &__indicators {
justify-content: flex-end; //@include test();
margin-right: 2.5%; flex: 1 1 auto;
flex-wrap: wrap;
[class*='indicator-clock'] { order: 90; }
.c-indicator .label {
font-size: 0.9em;
}
} }
/******************************* MAIN AREA */ /******************************* MAIN AREA */
@ -266,9 +309,10 @@
import multipane from './multipane.vue'; import multipane from './multipane.vue';
import pane from './pane.vue'; import pane from './pane.vue';
import BrowseBar from './BrowseBar.vue'; import BrowseBar from './BrowseBar.vue';
import StatusBar from './status-bar/StatusBar.vue';
import Toolbar from '../toolbar/Toolbar.vue'; import Toolbar from '../toolbar/Toolbar.vue';
import AppLogo from './AppLogo.vue'; import AppLogo from './AppLogo.vue';
import Indicators from './status-bar/Indicators.vue';
import NotificationBanner from './status-bar/NotificationBanner.vue';
var enterFullScreen = () => { var enterFullScreen = () => {
var docElm = document.documentElement; var docElm = document.documentElement;
@ -309,9 +353,10 @@
multipane, multipane,
pane, pane,
BrowseBar, BrowseBar,
StatusBar,
Toolbar, Toolbar,
AppLogo AppLogo,
Indicators,
NotificationBanner
}, },
mounted() { mounted() {
this.openmct.editor.on('isEditing', (isEditing)=>{ this.openmct.editor.on('isEditing', (isEditing)=>{
@ -321,11 +366,18 @@
this.openmct.selection.on('change', this.toggleHasToolbar); this.openmct.selection.on('change', this.toggleHasToolbar);
}, },
data: function () { data: function () {
let storedHeadProps = window.localStorage.getItem('openmct-shell-head');
let headExpanded = true;
if (storedHeadProps) {
headExpanded = JSON.parse(storedHeadProps).expanded;
}
return { return {
fullScreen: false, fullScreen: false,
conductorComponent: undefined, conductorComponent: undefined,
isEditing: false, isEditing: false,
hasToolbar: false hasToolbar: false,
headExpanded
} }
}, },
computed: { computed: {
@ -334,6 +386,18 @@
} }
}, },
methods: { methods: {
toggleShellHead() {
this.headExpanded = !this.headExpanded;
window.localStorage.setItem(
'openmct-shell-head',
JSON.stringify(
{
expanded: this.headExpanded
}
)
);
},
fullScreenToggle() { fullScreenToggle() {
if (this.fullScreen) { if (this.fullScreen) {
this.fullScreen = false; this.fullScreen = false;

View File

@ -148,21 +148,6 @@
font-size: floor(12px * .9); 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 { &__label {
// Name of the pane // Name of the pane
@include ellipsize(); @include ellipsize();

View File

@ -17,10 +17,137 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<template> <template>
<span id='status' class='status-holder'></span>
</template> </template>
<style lang="scss"> <style lang="scss">
@import "~styles/sass-base";
.c-indicator {
@include cControl();
@include cClickIconButtonLayout();
button { text-transform: uppercase; }
background: none !important;
border-radius: $controlCr;
overflow: visible;
position: relative;
text-transform: uppercase;
&.no-minify {
// For items that cannot be minified
display: flex;
flex-flow: row nowrap;
align-items: center;
> *,
&:before {
flex: 1 1 auto;
}
&:before {
margin-right: $interiorMarginSm;
}
}
&:not(.no-minify) {
&:before {
margin-right: 0 !important;
}
}
}
.c-indicator__label {
// Label element. Appears as a hover bubble element when Indicators are minified;
// Appears as an inline element when not.
display: inline-block;
transition:none;
white-space: nowrap;
a,
button,
s-button,
.c-button {
// Make <a> in label look like buttons
transition: $transIn;
background: transparent;
border: 1px solid rgba($colorIndicatorMenuFg, 0.5);
border-radius: $controlCr;
box-sizing: border-box;
color: inherit;
font-size: inherit;
height: auto;
line-height: normal;
padding: 0 2px;
&:hover {
background: rgba($colorIndicatorMenuFg, 0.1);
border-color: rgba($colorIndicatorMenuFg, 0.75);
color: $colorIndicatorMenuFgHov;
}
}
[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;
}
}
}
.c-indicator__count {
display: none; // Only displays when Indicator is minified, see below
}
[class*='minify-indicators'] {
// All styles for minified Indicators should go in here
.c-indicator:not(.no-minify) {
@include hover() {
background: $colorIndicatorBgHov;
.c-indicator__label {
box-shadow: $colorIndicatorMenuBgShdw;
transform: scale(1.0);
transition: transform 100ms ease-out 100ms;
}
}
.c-indicator__label {
transition: transform 250ms ease-in 200ms;
background: $colorIndicatorMenuBg;
color: $colorIndicatorMenuFg;
border-radius: $controlCr;
left: 0;
top: 130%;
padding: $interiorMargin $interiorMargin;
position: absolute;
transform-origin: 10px 0;
transform: scale(0.0);
overflow: visible;
z-index: 50;
&:before {
// Infobubble-style arrow element
content: '';
display: block;
position: absolute;
bottom: 100%;
@include triangle('up', $size: 4px, $ratio: 1, $color: $colorIndicatorMenuBg);
}
}
.c-indicator__count {
display: inline-block;
margin-left: $interiorMarginSm;
}
}
}
/* Mobile */
// Hide the clock indicator when we're phone portrait
body.phone.portrait {
.c-indicator.t-indicator-clock {
display: none;
}
}
</style> </style>
<script> <script>
@ -29,12 +156,7 @@
mounted() { mounted() {
this.openmct.indicators.indicatorObjects.forEach((indicator) => { this.openmct.indicators.indicatorObjects.forEach((indicator) => {
// So that we can consistently position indicator elements, this.$el.appendChild(indicator.element);
// 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);
}); });
} }
} }

View File

@ -57,20 +57,19 @@
.c-message-banner { .c-message-banner {
$closeBtnSize: 7px; $closeBtnSize: 7px;
$m: 1px;
border-radius: $controlCr; border-radius: $controlCr;
@include statusBannerColors($colorStatusDefault, $colorStatusFg); @include statusBannerColors($colorStatusDefault, $colorStatusFg);
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
left: 50%; left: 50%;
top: 50%;
max-width: 50%; max-width: 50%;
padding: $interiorMargin $interiorMargin $interiorMargin $interiorMarginLg; max-height: 25px;
padding: $interiorMarginSm $interiorMargin $interiorMarginSm $interiorMarginLg;
position: absolute; position: absolute;
transform: translateX(-50%); transform: translate(-50%, -50%);
bottom: $m;
z-index: 2; z-index: 2;
> * + * { > * + * {

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

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
/*global console */ /*global console */
define([], function () { define(['EventEmitter'], function (EventEmitter) {
const DEFAULT_VIEW_PRIORITY = 100; const DEFAULT_VIEW_PRIORITY = 100;
/** /**
@ -31,9 +31,12 @@ define([], function () {
* @memberof module:openmct * @memberof module:openmct
*/ */
function ViewRegistry() { function ViewRegistry() {
EventEmitter.apply(this);
this.providers = {}; this.providers = {};
} }
ViewRegistry.prototype = Object.create(EventEmitter.prototype);
/** /**
* @private for platform-internal use * @private for platform-internal use

View File

@ -24,6 +24,7 @@ const webpackConfig = {
output: { output: {
filename: '[name].js', filename: '[name].js',
library: '[name]', library: '[name]',
libraryTarget: 'umd',
path: path.resolve(__dirname, 'dist') path: path.resolve(__dirname, 'dist')
}, },
resolve: { resolve: {
@ -35,6 +36,7 @@ const webpackConfig = {
"bourbon": "bourbon.scss", "bourbon": "bourbon.scss",
"vue": path.join(__dirname, "node_modules/vue/dist/vue.js"), "vue": path.join(__dirname, "node_modules/vue/dist/vue.js"),
"d3-scale": path.join(__dirname, "node_modules/d3-scale/build/d3-scale.min.js"), "d3-scale": path.join(__dirname, "node_modules/d3-scale/build/d3-scale.min.js"),
"printj": path.join(__dirname, "node_modules/printj/dist/printj.min.js"),
"styles": path.join(__dirname, "src/styles-new") "styles": path.join(__dirname, "src/styles-new")
} }
}, },