Compare commits

..

27 Commits

Author SHA1 Message Date
d663788e77 working annotate and new entry contextual 2018-09-10 15:22:29 -07:00
100c9af72d add overlay header with timestamp 2018-09-07 13:54:49 -07:00
ccc5969b28 working preview action in embed context menu 2018-09-06 15:23:23 -07:00
748b4bf4d1 abstract Embed Controller, working context menu, working remove embed 2018-09-06 14:38:05 -07:00
755f0ac126 fix snapshot object 2018-09-05 15:32:35 -07:00
0173c3cfab working snapshotOverlay 2018-09-05 15:03:04 -07:00
9ca6ef428e keep deleteEntry local to entryController 2018-09-05 10:16:56 -07:00
5842bbc821 abstract EntryController, working search, drag and drop 2018-09-04 13:01:52 -07:00
a2f499abf6 working search 2018-08-30 09:45:23 -07:00
634aaefb48 snapshot action partial 2018-08-09 15:45:22 -07:00
80368b38b5 embed ability with drag into entry 2018-07-23 15:41:27 -07:00
17c60d1d4d componentize entries 2018-07-23 11:07:38 -07:00
5bca13aeff working entries... wip 2018-07-20 15:20:36 -07:00
cc22fd4e9d Broaden selector to handle wrapped links/buttons (#2118)
Fixes #2116
- When buttons or links with icons are wrapped,
let the styling set margin and size properly;
2018-07-17 17:07:42 -07:00
4a046b3eea Adapter consolidation & fix incorrect export to window (#2114)
* Merge adapter bundles

Merge adapter bundles into consistent location.

Update telemetry API to not incorrectly expose the format service
on window.

* openmct available on window for developers

* remove offending comma
2018-07-17 13:57:38 -07:00
b0702ceff5 [Table] Check alarms for all values (#2115)
Check alarm status for all values, not just ranges.  This allows
limit evaluators to apply styling to additional columns if
desired by developers.
2018-07-17 13:55:35 -07:00
5af14cd3b9 [Notebook] Snapshot overlay - fixes issues #2098 #2102 #2108 #2110 (#2113)
* snapshot - when taking a snapshot of overlay, select object-holder vs the entire overlay. This fixes plots not being captured

* remove new Notebook entry from context menu - fixes issue #2098

* add licence information for Painterro - fixes issue #2108

* add watch for defaultSort - fixes issue 2102

* dont allow snapshot action from imagery

* fix checkstyle error
2018-07-17 11:30:01 -07:00
d7eb4c17ca Added div element to force horz scroll in table body (#2111)
Fixes #2103
- Added div that is set to the calculated table width. When
filtering is such that no rows are present in the tbody
of the table, this element will still force a horizontal
scroll in that area, allowing the user to horz scroll the headers;
2018-07-16 15:25:02 -07:00
8c9fe2d36b [Tables] Address performance issues observed in testathon (#2112)
* Use datum key of 'utc' for timestamp

* Do not try to get column values for data that does not have those values

* Collapse current time system columns

* Noop parsing for numbers in LocalTimeFormat

* Do not attempt to provide data for columns that object does not have telemetry for. Fixes #2027
2018-07-16 15:23:34 -07:00
3246480f82 Indicators api (#1837)
* [API] Added Indicators API

* [Indicators] Converted Follow Indicator to new Indicators API

* [Indicators] Converted URL Indicator to new Indicators API

* [Indicators] Changes to some legacy indicators for compatibility with new Indicators API

* [Indicators] Addressed code review items from https://github.com/nasa/openmct/pull/1837

* Updated tests for URL Indicator

* Adding Indicator API spec

* Address linting issues

* Switched to direct DOM manipulation rather than template compilation to avoid an unnecessary extra holder element

* Updated documentation to reflect changes to API

* Indicators api styling (#2076)

Updated styling for Indicators

* Update API TOC

* Fix color of items w-mct-example areas of Style Guide

Fixes #1837

* Status class refactoring and cleanups

Fixes #1837
- Significant cleanups and name refactoring to allow more
concise selector definitions, with changes in js, html and scss files;
- Updates in Style Guide > Status page, with some content
reorganization and clarification;

* Corrected out of date API

* de-zeptoed Indicator API test spec

* Remove promise from URLIndicator

* Separated legacy indicators support

* Updated Indicator specs to Jasmine 3

* Fixed checkstyle and lint issues

* Moved legacy indicators support to adapter folder

* Various fixes for Indicators

Fixes #1837
- Added SASS constants for Indicator colors;
- Removed commented code;
- Removed unused indicator classes from _messages.scss
- Fixed missing s-status-on class;

* Significant revisions to Style Guide Indicators content

Fixes #1837
- Better documentation including recommendations;
- Better and more concrete examples;

* Style Guide example tweaks

Fixes #1837

* Refinement to Style Guide Status and Limits content

Fixes #1837
- More detail and clarification on Status and Limits;

* Cleanup code

Fixes #1837
- Remove commented styles;
- Line return refinements;
2018-07-16 15:21:38 -07:00
8055e050b6 Merge pull request #2109 from nasa/snapshot-2105
Fix for table overflow problem in html2canvas
2018-07-03 17:24:02 -07:00
b0f73fff0d Add overflow: auto when snapshotting table for Notebook
Fixes #2105
2018-07-03 16:57:26 -07:00
755cf21d3f Local control tweaks
Fixes #2094
- Adjust timing;
- Added hover effects for .s-notebook-entry;
2018-07-03 14:33:35 -07:00
95457a1981 View control fixes
Fixes #2094
- Add position: relative to .view-control main class;
- Add correct classes to markup;
- Fix Sum Widget field size while I was in there;
2018-07-03 14:25:39 -07:00
da0ed95662 Merge pull request #2097 from nasa/matching-historical-states
use correct duration for historical states
2018-07-03 12:47:15 -07:00
c8919708bb Fixes for main search input (#2106)
* Fix for input width

* Margin added to right of c-search-btn-wrapper element

Fixes #2094
2018-07-03 12:36:24 -07:00
cc5bfda730 use correct duration for historical states 2018-07-02 14:18:29 -07:00
98 changed files with 3995 additions and 2711 deletions

88
API.md
View File

@ -37,7 +37,7 @@
- [Time Systems and Bounds](#time-systems-and-bounds)
- [Defining and Registering Time Systems](#defining-and-registering-time-systems)
- [Getting and Setting the Active Time System](#getting-and-setting-the-active-time-system)
- [Time Bounds](#time-bounds)
- [Time Bounds](#time-bounds)
- [Clocks](#clocks)
- [Defining and registering clocks](#defining-and-registering-clocks)
- [Getting and setting active clock](#getting-and-setting-active-clock)
@ -48,6 +48,10 @@
- [The Time Conductor](#the-time-conductor)
- [Time Conductor Configuration](#time-conductor-configuration)
- [Example conductor configuration](#example-conductor-configuration)
- [Indicators](#indicators)
- [The URL Status Indicator](#the-url-status-indicator)
- [Creating a Simple Indicator](#creating-a-simple-indicator)
- [Custom Indicators](#custom-indicators)
- [Included Plugins](#included-plugins)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -688,7 +692,7 @@ openmct.time.timeSystem(utcTimeSystem, bounds);
Setting the active time system will trigger a [`'timeSystem'`](#time-events)
event. If you supplied bounds, a [`'bounds'`](#time-events) event will be triggered afterwards with your newly supplied bounds.
### Time Bounds
#### Time Bounds
The TimeAPI provides a getter/setter for querying and setting time bounds. Time
bounds are simply an object with a `start` and an end `end` attribute.
@ -977,6 +981,72 @@ openmct.install(openmct.plugins.Conductor({
}));
```
## Indicators
Indicators are small widgets that reside at the bottom of the screen and are visible from
every screen in Open MCT. They can be used to convey system state using an icon and text.
Typically an indicator will display an icon (customizable with a CSS class) that will
reveal additional information when the mouse cursor is hovered over it.
### The URL Status Indicator
A common use case for indicators is to convey the state of some external system such as a
persistence backend or HTTP server. So long as this system is accessible via HTTP request,
Open MCT provides a general purpose indicator to show whether the server is available and
returing a 2xx status code. The URL Status Indicator is made available as a default plugin. See
[Included Plugins](#included-plugins) below for details on how to install and configure the
URL Status Indicator.
### Creating a Simple Indicator
A simple indicator with an icon and some text can be created and added with minimal code. An indicator
of this type exposes functions for customizing the text, icon, and style of the indicator.
eg.
``` javascript
var myIndicator = openmct.indicators.simpleIndicator();
myIndicator.text("Hello World!");
openmct.indicators.add(myIndicator);
```
This will create a new indicator and add it to the bottom of the screen in Open MCT.
By default, the indicator will appear as an information icon. Hovering over the icon will
reveal the text set via the call to `.text()`. The Indicator object returned by the API
call exposes a number of functions for customizing the content and appearance of the indicator:
* `.text([text])`: Gets or sets the text shown when the user hovers over the indicator.
Accepts an __optional__ `string` argument that, if provided, will be used to set the text.
Hovering over the indicator will expand it to its full size, revealing this text alongside
the icon. Returns the currently set text as a `string`.
* `.description([description])`: Gets or sets the indicator's description. Accepts an
__optional__ `string` argument that, if provided, will be used to set the text. The description
allows for more detail to be provided in a tooltip when the user hovers over the indicator.
Returns the currently set text as a `string`.
* `.iconClass([className])`: Gets or sets the CSS class used to define the icon. Accepts an __optional__
`string` parameter to be used to set the class applied to the indicator. Any of
[the built-in glyphs](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home/glyphs?view=styleguide.glyphs)
may be used here, or a custom CSS class can be provided. Returns the currently defined CSS
class as a `string`.
* `.statusClass([className])`: Gets or sets the CSS class used to determine status. Accepts an __optional __
`string` parameter to be used to set a status class applied to the indicator. May be used to apply
different colors to indicate status.
### Custom Indicators
A completely custom indicator can be added by simple providing a DOM element to place alongside other indicators.
``` javascript
var domNode = document.createElement('div');
domNode.innerText = new Date().toString();
setInterval(function () {
domNode.innerText = new Date().toString();
}, 1000);
openmct.indicators.add({
element: domNode
});
```
## Included Plugins
Open MCT is packaged along with a few general-purpose plugins:
@ -1000,18 +1070,18 @@ openmct.install(openmct.plugins.CouchDB('http://localhost:9200'))
* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different
themes (dark and light) available for Open MCT. Note that at least one
of these themes must be installed for Open MCT to appear correctly.
* `openmct.plugins.URLIndicatorPlugin` adds an indicator which shows the
* `openmct.plugins.URLIndicator` adds an indicator which shows the
availability of a URL with the following options:
- `url` : URL to indicate the status of
- `cssClass`: Icon to show in the status bar, defaults to `icon-database`, [list of all icons](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home?view=items)
- `iconClass`: Icon to show in the status bar, defaults to `icon-database`, [list of all icons](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home?view=items)
- `interval`: Interval between checking the connection, defaults to `10000`
- `label` Name showing up as text in the status bar, defaults to url
```javascript
openmct.install(openmct.plugins.URLIndicatorPlugin({
url: 'http://google.com',
cssClass: 'check',
interval: 10000,
label: 'Google'
openmct.install(openmct.plugins.URLIndicator({
url: 'http://localhost:8080',
iconClass: 'check',
interval: 10000,
label: 'Localhost'
})
);
```

View File

@ -27,14 +27,8 @@ define([
) {
var RED = {
sin: 0.9,
cos: 0.9
},
YELLOW = {
sin: 0.5,
cos: 0.5
},
var RED = 0.9,
YELLOW = 0.5,
LIMITS = {
rh: {
cssClass: "s-limit-upr s-limit-red",
@ -73,18 +67,17 @@ define([
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
return {
evaluate: function (datum, valueMetadata) {
var range = valueMetadata && valueMetadata.key;
if (datum[range] > RED[range]) {
var range = valueMetadata ? valueMetadata.key : 'sin'
if (datum[range] > RED) {
return LIMITS.rh;
}
if (datum[range] < -RED[range]) {
if (datum[range] < -RED) {
return LIMITS.rl;
}
if (datum[range] > YELLOW[range]) {
if (datum[range] > YELLOW) {
return LIMITS.yh;
}
if (datum[range] < -YELLOW[range]) {
if (datum[range] < -YELLOW) {
return LIMITS.yl;
}
}

View File

@ -38,7 +38,8 @@ define([
"provides": "identityService",
"type": "provider",
"depends": [
"dialogService"
"dialogService",
"$q"
]
}
]

View File

@ -55,21 +55,37 @@ define(
* @implements {IdentityService}
* @memberof platform/identity
*/
function ExampleIdentityProvider(dialogService) {
// Handle rejected dialog messages by treating the
// current user as undefined.
function echo(v) { return v; }
function giveUndefined() { return undefined; }
function ExampleIdentityProvider(dialogService, $q) {
this.dialogService = dialogService;
this.$q = $q;
this.userPromise =
dialogService.getUserInput(DIALOG_STRUCTURE, DEFAULT_IDENTITY)
.then(echo, giveUndefined);
this.returnUser = this.returnUser.bind(this);
this.returnUndefined = this.returnUndefined.bind(this);
}
ExampleIdentityProvider.prototype.getUser = function () {
return this.userPromise;
if (this.user) {
return this.$q.when(this.user);
} else {
return this.dialogService.getUserInput(DIALOG_STRUCTURE, DEFAULT_IDENTITY)
.then(this.returnUser, this.returnUndefined);
}
};
/**
* @private
*/
ExampleIdentityProvider.prototype.returnUser = function (user) {
return this.user = user;
}
/**
* @private
*/
ExampleIdentityProvider.prototype.returnUndefined = function () {
return undefined;
}
return ExampleIdentityProvider;
}
);

View File

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

View File

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

View File

@ -58,7 +58,10 @@
position: relative;
}
.w-mct-example > div { margin-bottom: $interiorMarginLg; }
.w-mct-example {
color: #ccc;
> div { margin-bottom: $interiorMarginLg; }
}
code,
pre {

View File

@ -20,12 +20,12 @@
at runtime from the About dialog for additional information.
-->
<style>
.w-mct-example div[class*="s-limit"],
.w-mct-example div[class*="s-status"],
.w-mct-example div[class*="s-unsynced"],
.w-mct-example span[class*="s-status"],
.w-mct-example div[class*="s-limit"],
.w-mct-example span[class*="s-limit"] {
border-radius: 4px;
padding: 3px 7px;
border-radius: 3px;
padding: 2px 5px;
}
.w-mct-example table {
width: 100%;
@ -36,65 +36,12 @@
<h1>Status Indication</h1>
<div class="l-section">
<h2>Overview</h2>
<p>Many elements in Open MCT need to articulate a dynamic status; Open MCT provides the following styles and conventions to handle this:</p>
<ul>
<li><strong>Limits</strong>: when telemetry values exceed minimum or maximum values, they can be violating limits. Limit styles include both color and iconography; color is used to indicate severity while icons are used to indicate direction, upper or lower.</li>
<li><strong>Status</strong>: Open MCT also provides a number or built-in Status styles allowing telemetry or other displayed information to be visually classified by type. Common uses for these classes are to visually denote event records.</li>
<li><strong>Synchronization</strong>: When the system is displaying real-time data, it is very important that displays clearly indicate when they are not doing so, such as when a plot if frozen while panning or zooming. Open MCT provides a style for this.</li>
</ul>
</div>
<div class="l-section">
<h2>Limits</h2>
<h2>Status Classes</h2>
<div class="cols cols1-1">
<div class="col">
<p>Limit CSS classes can be applied to any block or inline element. Open MCT limit classes set color and optionally an icon, but don't effect other properties. Yellow and red limit classes can be used as is, or allow the application of any custom icon available in Open MCT's glyphs library. &quot;Level&quot; limit classes - upper and lower - always use an icon in addition to a color; Open MCT doesn't support level limits without color.</p>
<ul>
<li>Color only</li>
<ul>
<li><code>s-limit-yellow</code>: A yellow limit.</li>
<li><code>s-limit-red</code>: A red limit.</li>
</ul>
<li>Color and icon</li>
<ul>
<li><code>s-limit-yellow-icon</code>: A yellow limit with icon.</li>
<li><code>s-limit-red-icon</code>: A red limit with icon.</li>
</ul>
<li>Upper and lower limit indicators. Must be used with a color limit class to be visible.</li>
<ul>
<li><code>s-limit-upr</code>: Upper limit.
</li>
<li><code>s-limit-lwr</code>: Lower limit.
</li>
</ul>
</ul>
</div>
<mct-example><div class="s-limit-yellow">Yellow limit</div>
<div class="s-limit-red">Red limit</div>
<div class="s-limit-yellow-icon">Yellow limit with icon</div>
<div class="s-limit-red-icon">Red limit with icon</div>
<div class="s-limit-yellow s-limit-lwr">Lower yellow limit</div>
<div class="s-limit-red s-limit-upr">Upper red limit</div>
<div class="s-limit-red icon-bell">Red Limit with a custom icon</div>
<div>Some text with an <span class="s-limit-yellow-icon">inline element</span> showing a yellow limit.</div>
<!-- Limits applied in a table -->
<table>
<tr class='header'><td>Name</td><td>Value 1</td><td>Value 2</td></tr>
<tr><td>ENG_PWR 4991</td><td>7.023</td><td class="s-limit-yellow s-limit-upr">70.23</td></tr>
<tr><td>ENG_PWR 4992</td><td>49.784</td><td class="s-limit-red s-limit-lwr">-121.22</td></tr>
<tr><td>ENG_PWR 4993</td><td class="s-limit-yellow icon-bell">0.451</td><td>1.007</td></tr>
</table>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>Status</h2>
<div class="cols cols1-1">
<div class="col">
<p>Classes here can be applied to elements as needed.</p>
<p>Status classes allow any block or inline-block element to be decorated in order to articulate a
status. Provided classes include color-only and color plus icon; custom icons can easily be
employed by using a color-only status class in combination with an <a class="link" href="#/browse/styleguide:home/glyphs?tc.mode=local&tc.timeSystem=utc&tc.startDelta=1800000&tc.endDelta=0&view=styleguide.glyphs">glyph</a>.</p>
<ul>
<li>Color only</li>
<ul>
@ -106,37 +53,175 @@
</ul>
<li>Color and icon</li>
<ul>
<li><code>s-status-warning-hi-icon</code></li>
<li><code>s-status-warning-lo-icon</code></li>
<li><code>s-status-diagnostic-icon</code></li>
<li><code>s-status-info-icon</code></li>
<li><code>s-status-ok-icon</code></li>
<li><code>s-status-icon-warning-hi</code></li>
<li><code>s-status-icon-warning-lo</code></li>
<li><code>s-status-icon-diagnostic</code></li>
<li><code>s-status-icon-info</code></li>
<li><code>s-status-icon-ok</code></li>
</ul>
</ul>
</div>
<mct-example><div class="s-status-warning-hi">WARNING HI</div>
<mct-example><!-- Color alone examples -->
<div class="s-status-warning-hi">WARNING HI</div>
<div class="s-status-warning-lo">WARNING LOW</div>
<div class="s-status-diagnostic">DIAGNOSTIC</div>
<div class="s-status-info">INFO</div>
<div class="s-status-ok">OK</div>
<div class="s-status-warning-hi-icon">WARNING HI with icon</div>
<div class="s-status-warning-lo-icon">WARNING LOW with icon</div>
<div class="s-status-diagnostic-icon">DIAGNOSTIC with icon</div>
<div class="s-status-info-icon">INFO with icon</div>
<div class="s-status-ok-icon">OK with icon</div>
<div class="s-status-warning-hi icon-gear">WARNING HI with custom icon</div>
<!-- Color and icon examples -->
<div class="s-status-icon-warning-hi">WARNING HI with icon</div>
<div class="s-status-icon-warning-lo">WARNING LOW with icon</div>
<div class="s-status-icon-diagnostic">DIAGNOSTIC with icon</div>
<div class="s-status-icon-info">INFO with icon</div>
<div class="s-status-icon-ok">OK with icon</div>
<div class="s-status-warning-hi icon-alert-triangle">WARNING HI with custom icon</div>
<div>Some text with an <span class="s-status-icon-diagnostic">inline element</span> showing a Diagnostic status.</div>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>Synchronization</h2>
<h2>Limit Classes</h2>
<div class="cols cols1-1">
<div class="col">
<p>When the system is operating in real-time streaming mode, it is important for views that display real-time data to clearly articulate when they are not, such as when a user zooms or pans a plot view, freezing that view. In that case, the CSS class <code>s-unsynced</code> should be applied to that view.</p>
<p>Limit classes are a specialized form of status, specifically meant to be applied to telemetry
displays to indicate that a limit threshold has been violated. Open MCT provides both severity
and direction classes; severity (yellow and red) can be used alone or in combination
with direction (upper or lower). Direction classes cannot be used on their own.</p>
<p>Like Status classes, Limits can be used as color-only, or color plus icon. Custom icons can
be applied in the same fashion as described above.</p>
<ul>
<li>Severity color alone</li>
<ul>
<li><code>s-limit-yellow</code>: A yellow limit.</li>
<li><code>s-limit-red</code>: A red limit.</li>
</ul>
<li>Severity color and icon</li>
<ul>
<li><code>s-limit-icon-yellow</code>: A yellow limit with icon.</li>
<li><code>s-limit-icon-red</code>: A red limit with icon.</li>
</ul>
<li>Direction indicators. MUST be used with a &quot;color alone&quot; limit class. See
examples for more.</li>
<ul>
<li><code>s-limit-upr</code>: Upper limit.</li>
<li><code>s-limit-lwr</code>: Lower limit.</li>
</ul>
</ul>
</div>
<mct-example><div class="s-unsynced">This element is unsynced</div>
<mct-example><!-- Color alone examples -->
<div class="s-limit-yellow">Yellow limit</div>
<div class="s-limit-red">Red limit</div>
<!-- Color and icon examples -->
<div class="s-limit-icon-yellow">Yellow limit with icon</div>
<div class="s-limit-icon-red">Red limit with icon</div>
<div class="s-limit-red icon-alert-rect">Red Limit with a custom icon</div>
<div>Some text with an <span class="s-limit-icon-yellow">inline element</span> showing a yellow limit.</div>
<!-- Severity and direction examples -->
<div class="s-limit-yellow s-limit-lwr">Lower yellow limit</div>
<div class="s-limit-red s-limit-upr">Upper red limit</div>
<!-- Limits applied in a table -->
<table>
<tr class='header'><td>Name</td><td>Value 1</td><td>Value 2</td></tr>
<tr><td>ENG_PWR 4991</td><td>7.023</td><td class="s-limit-yellow s-limit-upr">70.23</td></tr>
<tr><td>ENG_PWR 4992</td><td>49.784</td><td class="s-limit-red s-limit-lwr">-121.22</td></tr>
<tr><td>ENG_PWR 4993</td><td class="s-limit-yellow icon-alert-triangle">0.451</td><td>1.007</td></tr>
</table>
</mct-example>
</div>
</div>
</div>
<div class="l-section">
<h2>Status Bar Indicators</h2>
<div class="cols cols1-1">
<div class="col">
<p>Indicators are small iconic notification elements that appear in the Status Bar area of
the application at the window's bottom. Indicators should be used to articulate the state of a
system and optionally provide gestures related to that system. They use a combination of icon and
color to identify themselves and articulate a state respectively.</p>
<h3>Recommendations</h3>
<ul>
<li><strong>Keep the icon consistent</strong>. The icon is the principal identifier of the system and is a valuable
recall aid for the user. Don't change the icon as a system's state changes, use color and
text for that purpose.</li>
<li><strong>Don't use the same icon more than once</strong>. Select meaningful and distinct icons so the user
will be able to quickly identify what they're looking for.</li>
</ul>
<h3>States</h3>
<ul>
<li><strong>Disabled</strong>: The system is not available to the user.</li>
<li><strong>Off / Available</strong>: The system is accessible to the user but is not currently
&quot;On&quot; or has not been configured. If the Indicator directly provides gestures
related to the system, such as opening a configuration dialog box, then use
&quot;Available&quot;; if the user must act elsewhere or the system isn't user-controllable,
use &quot;Off&quot;.</li>
<li><strong>On</strong>: The system is enabled or configured; it is having an effect on the larger application.</li>
<li><strong>Alert / Error</strong>: There has been a problem with the system. Generally, &quot;Alert&quot;
should be used to call attention to an issue that isn't critical, while &quot;Error&quot;
should be used to call attention to a problem that the user should really be aware of or do
something about.</li>
</ul>
<h3>Structure</h3>
<p>Indicators consist of a <code>.ls-indicator</code>
wrapper element with <code>.icon-*</code> classes for the type of thing they represent and
<code>.s-status-*</code> classes to articulate the current state. Title attributes should be used
to provide more verbose information about the thing and/or its status.</p>
<p>The wrapper encloses a <code>.label</code> element that is displayed on hover. This element should
include a brief statement of the current status, and can also include clickable elements
as <code>&lt;a&gt;</code> tags. An optional <code>.count</code> element can be included to display
information such as a number of messages.</p>
<p>Icon classes are as defined on the
<a class="link" href="#/browse/styleguide:home/glyphs?tc.mode=local&tc.timeSystem=utc&tc.startDelta=1800000&tc.endDelta=0&view=styleguide.glyphs">
Glyphs page</a>. Status classes applicable to Indicators are as follows:</p>
<ul>
<li><code>s-status-disabled</code></li>
<li><code>s-status-off</code></li>
<li><code>s-status-available</code></li>
<li><code>s-status-on</code></li>
<li><code>s-status-alert</code></li>
<li><code>s-status-error</code></li>
</ul>
</div>
<mct-example><div class="s-ue-bottom-bar status-holder s-status-bar">
<span class="ls-indicator icon-database s-status-disabled" title="The system is currently disabled.">
<span class="label">System not enabled.</span>
</span>
</div>
<div class="s-ue-bottom-bar status-holder s-status-bar">
<span class="ls-indicator icon-database s-status-available" title="Configure data connection.">
<span class="label">Data connection available
<a class="icon-gear">Configure</a>
</span>
</span>
</div>
<div class="s-ue-bottom-bar status-holder s-status-bar">
<span class="ls-indicator icon-database s-status-on" title="Data connected.">
<span class="label">Connected to Skynet
<a class="icon-gear">Change</a>
<a>Disconnect</a>
</span>
</span>
</div>
<div class="s-ue-bottom-bar status-holder s-status-bar">
<span class="ls-indicator icon-database s-status-alert" title="System is self-aware.">
<span class="label">Skynet at Turing Level 5</span>
</span>
<span class="ls-indicator icon-bell s-status-error" title="You have alerts.">
<span class="label">
<a class="icon-alert-triangle">View Alerts</a>
</span>
<span class="count">495</span>
</span>
</div>
</mct-example>
</div>
</div>
</div>

View File

@ -54,7 +54,7 @@ define(
return "icon-object-unknown";
},
getText: function () {
return latest;
return "" + latest;
},
getDescription: function () {
return "";

View File

@ -30,7 +30,7 @@
<script>
var THIRTY_MINUTES = 30 * 60 * 1000;
require(['openmct', './src/plugins/telemetryTable/plugin'], function (openmct, TelemetryTablePlugin) {
require(['openmct'], function (openmct) {
[
'example/eventGenerator',
'example/styleguide'
@ -70,10 +70,11 @@
}));
openmct.install(openmct.plugins.SummaryWidget());
openmct.install(openmct.plugins.Notebook());
openmct.install(TelemetryTablePlugin());
openmct.install(openmct.plugins.NotebookV2());
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc');
openmct.start();
window.openmct = openmct;
});
</script>
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">

View File

@ -1,68 +0,0 @@
* Delete old table
* Update new table type and test backward compatibility.
* re-evaluate TableConfiguration object. Name doesn't make sense right now, and some duplicated code for configuration handling in components.
* Rebase over refactor branch
* Move css to new table location
* Test (see list of issues below)
* Push WIP PR
* [X] Remove column sizing rows on object removal (should be trivial since tracking by object ID already).
* [X] Look at optimizing styles in telemetry-table-row
- Right now profiling does not highlight this as a bottleneck?
* [X] Add title to table cells
* [X] Add elipses for overflow on table cells
* [X] On entry, filter boxes need to remove magnifying glass.
* [X] auto-scroll
* [X] Show / hide columns (ie. table configuration)
* [X] Why aren't limits being applied until I scroll or do something?
* [X] Handle window resizing
* [X] Fix memory leaks
* [X] Remove isFromObject and hasColumn from TelemetryTableRow
* [X] Remove format caching
* [X] Add filtering
* If the new filter string starts with the old filter string, filter based on the list of previously filtered results, not the base list.
* Add the clear filter button
* [X] Cache formatted values for "just in time" formatting. I think cache on row. Opportunity to cache on column to benefit from multiple rows with the same value, but memory management becomes a problem then as cache could grow infinitely if the table is left to run.
* [X] Do some more testing with multiple objects. Not working properly right now.
* [X] Rows not being removed when object removed from composition
* [X] Subscribe to realtime data
* [X] Column widths should be done on receipt of FIRST DATA, not on receipt of historical data.
* [X] Filter subscription data
* [X] Export
* [X] Add loading spinner
* [X] in 'mounted', should not be necessary to bind to 'this'.
* [X] Stop Vue from decorating EVERYTHING (but especially the telemetry collection)
* [X] Need minimum width on tables. Provided by calcTableWidthPx in MCTTableController
* [X] Limits
* Benchmark - loading 1 million rows
- Old tables: ~90s
- New tables: ~11s
* 1 million rows in 11 secs vs 90s
To Test
* Multiple instances of tables
* Make sure time columns are being correctly merged
* Test with MSL data sources
* Test with tutorial data sources
* Behavior at different widths.
* Short tables
* Test with bounds / clock / time system changes.
* Memory leaks
Post WIP PR
* Fix jitter on auto-scroll
* Look at scroll-x again. Sounded like there might be some subtlety missing there (something to do with small columns?).
* Split TelemetryTableComponent into more components. It's too large now.
* Performance
* Don't wrap row on load, do it on scroll.
* On batch insert, check bounds once, rather than on each insert.
* See if sticky headers can be simplified (eg. can we combine headers table with content table?)
* Default sort behavior, and sticking to the bottom for realtime numerical
* Look at setting top on tbody, instead of each tr
* Replace all "mct-table" classes
* Consider making the sizing row a separate component. Encapsulate all sizing logic in there.
* consider making the header table a separate component.
* Test where no time column present (what will it sort by)
* [X] Optimization - don't both sorting filtered rows initially, just copy over values from bounded row collection which have already been sorted.

View File

@ -34,7 +34,6 @@ define([
"./src/controllers/ContextMenuController",
"./src/controllers/ClickAwayController",
"./src/controllers/ViewSwitcherController",
"./src/controllers/BottomBarController",
"./src/controllers/GetterSetterController",
"./src/controllers/SelectorController",
"./src/controllers/ObjectInspectorController",
@ -49,13 +48,14 @@ define([
"./src/directives/MCTSplitPane",
"./src/directives/MCTSplitter",
"./src/directives/MCTTree",
"./src/directives/MCTIndicators",
"./src/directives/MCTPreview",
"./src/actions/MCTPreviewAction",
"./src/filters/ReverseFilter",
"text!./res/templates/bottombar.html",
"text!./res/templates/controls/action-button.html",
"text!./res/templates/controls/input-filter.html",
"text!./res/templates/indicator.html",
"text!./res/templates/angular-indicator.html",
"text!./res/templates/message-banner.html",
"text!./res/templates/progress-bar.html",
"text!./res/templates/controls/time-controller.html",
@ -87,7 +87,6 @@ define([
ContextMenuController,
ClickAwayController,
ViewSwitcherController,
BottomBarController,
GetterSetterController,
SelectorController,
ObjectInspectorController,
@ -102,6 +101,7 @@ define([
MCTSplitPane,
MCTSplitter,
MCTTree,
MCTIndicators,
MCTPreview,
MCTPreviewAction,
ReverseFilter,
@ -281,13 +281,6 @@ define([
"$timeout"
]
},
{
"key": "BottomBarController",
"implementation": BottomBarController,
"depends": [
"indicators[]"
]
},
{
"key": "GetterSetterController",
"implementation": GetterSetterController,
@ -403,6 +396,11 @@ define([
"implementation": MCTTree,
"depends": ['gestureService', 'openmct']
},
{
"key": "mctIndicators",
"implementation": MCTIndicators,
"depends": ['openmct']
},
{
"key": "mctPreview",
"implementation": MCTPreview,

View File

@ -42,6 +42,7 @@
@import "controls/lists";
@import "controls/menus";
@import "controls/messages";
@import "controls/indicators";
@import "mobile/controls/menus";
/********************************* FORMS */

View File

@ -20,66 +20,120 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*************************************************** MIXINS */
@mixin formulateStatusColors($c) {
@mixin elementStatusColors($c) {
// Sets bg and icon colors for elements
background: rgba($c, 0.4) !important;
&:before { color: $c !important; }
}
/*************************************************** GENERAL */
.s-limit-yellow,
.s-limit-red,
.s-limit-yellow-icon,
.s-limit-red-icon,
.s-status-warning-lo,
.s-status-warning-hi,
.s-status-diagnostic,
.s-status-command,
.s-status-info,
.s-status-ok,
.s-status-warning-lo-icon,
.s-status-warning-hi-icon,
.s-status-diagnostic-icon,
.s-status-command-icon,
.s-status-info-icon,
.s-status-ok-icon {
@include trans-prop-nice($props: background, $dur: 500ms);
background: rgba($c, 0.5) !important;
&:before {
content:'';
font-family: symbolsfont;
font-size: 0.8em;
margin-right: $interiorMarginSm;
color: $c !important;
}
}
@mixin indicatorStatusColors($c) {
&:before, .count {
color: $c;
}
}
/*************************************************** GENERAL */
.s-limit-upr,
.s-limit-lwr,
[class*='s-limit-icon'],
[class*='s-status-icon'] {
&:before {
content:'';
display: inline-block;
font-family: symbolsfont;
font-size: 0.8em;
}
}
/*************************************************** LIMITS */
.s-limit-yellow, .s-limit-yellow-icon {
@include formulateStatusColors($colorWarningLo);
[class*='s-limit'] {
&[class*='icon-'] {
&:before {
margin-right: $interiorMargin;
}
}
}
.s-limit-red, .s-limit-red-icon {
@include formulateStatusColors($colorWarningHi);
.s-limit-yellow, .s-limit-icon-yellow {
@include elementStatusColors($colorWarningLo);
}
.s-limit-upr:before { content: $glyph-icon-arrow-double-up; }
.s-limit-lwr:before { content: $glyph-icon-arrow-double-down; }
.s-limit-yellow-icon:before,
.s-limit-red-icon:before { content: $glyph-icon-alert-triangle; }
.s-limit-red, .s-limit-icon-red {
@include elementStatusColors($colorWarningHi);
}
.s-limit {
&-upr,
&-lwr {
&:before {
margin-right: $interiorMargin;
}
}
&-upr:before { content: $glyph-icon-arrow-double-up; }
&-lwr:before { content: $glyph-icon-arrow-double-down; }
&-icon-yellow,
&-icon-red {
&:before {
content: $glyph-icon-alert-triangle;
}
}
}
/*************************************************** STATUS */
.s-status-warning-hi, .s-status-warning-hi-icon { @include formulateStatusColors($colorWarningHi); }
.s-status-warning-lo, .s-status-warning-lo-icon { @include formulateStatusColors($colorWarningLo); }
.s-status-diagnostic, .s-status-diagnostic-icon { @include formulateStatusColors($colorDiagnostic); }
.s-status-info, .s-status-info-icon { @include formulateStatusColors($colorInfo); }
.s-status-ok, .s-status-ok-icon { @include formulateStatusColors($colorOk); }
[class*='s-status'] {
&[class*='icon-'] {
&:before {
margin-right: $interiorMargin;
}
}
}
.s-status-warning-hi-icon:before { content: $glyph-icon-alert-triangle; }
.s-status-warning-lo-icon:before { content: $glyph-icon-alert-rect; }
.s-status-diagnostic-icon:before { content: $glyph-icon-eye-open; }
.s-status-info-icon:before { content: $glyph-icon-info; }
.s-status-ok-icon:before { content: $glyph-icon-check; }
.s-status-warning-hi, .s-status-icon-warning-hi { @include elementStatusColors($colorWarningHi); }
.s-status-warning-lo, .s-status-icon-warning-lo { @include elementStatusColors($colorWarningLo); }
.s-status-diagnostic, .s-status-icon-diagnostic { @include elementStatusColors($colorDiagnostic); }
.s-status-info, .s-status-icon-info { @include elementStatusColors($colorInfo); }
.s-status-ok, .s-status-icon-ok { @include elementStatusColors($colorOk); }
.s-status-icon-warning-hi:before { content: $glyph-icon-alert-triangle; }
.s-status-icon-warning-lo:before { content: $glyph-icon-alert-rect; }
.s-status-icon-diagnostic:before { content: $glyph-icon-eye-open; }
.s-status-icon-info:before { content: $glyph-icon-info; }
.s-status-icon-ok:before { content: $glyph-icon-check; }
/*************************************************** INDICATOR COLORING */
.ls-indicator {
&.s-status-info {
@include indicatorStatusColors($colorInfo);
}
&.s-status-disabled {
@include indicatorStatusColors($colorIndicatorDisabled);
}
&.s-status-available {
@include indicatorStatusColors($colorIndicatorAvailable);
}
&.s-status-on,
&.s-status-enabled {
@include indicatorStatusColors($colorIndicatorOn);
}
&.s-status-off {
@include indicatorStatusColors($colorIndicatorOff);
}
&.s-status-caution,
&.s-status-warning,
&.s-status-alert {
@include indicatorStatusColors($colorStatusAlert);
}
&.s-status-error {
@include indicatorStatusColors($colorStatusError);
}
}

View File

@ -0,0 +1,147 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/* Indicators are generally only displayed in the ue-bottom-bar element of the main interface */
.h-indicator,
mct-indicators mct-include {
display: inline; // Fallback for 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
&:before {
display: inline-block;
}
.label {
display: inline-block;
a,
button,
.s-button {
// Make <a> in label look like buttons
@include trans-prop-nice($props: all, $dur: 100ms);
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;
}
}
button { text-transform: uppercase !important; }
}
&.no-collapse {
display: flex;
flex-flow: row nowrap;
align-items: center;
> *,
&:before {
flex: 1 1 auto;
}
&:before {
margin-right: $interiorMarginSm;
}
}
&:not(.no-collapse) {
z-index: 0;
&: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;
&:before {
// Infobubble-style arrow element
content: '';
display: block;
position: absolute;
top: 100%;
@include triangle('down', $size: 4px, $ratio: 1, $color: $hbg);
}
}
&:hover {
background: $bg;
z-index: 1;
.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;
}
}

View File

@ -19,9 +19,9 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/******************************************************************* STATUS BLOCK ELEMS */
@mixin statusBannerColors($bg, $fg: $colorStatusFg) {
$bgPb: 30%;
$bgPb: 10%;
$bgPbD: 10%;
background-color: darken($bg, $bgPb);
color: $fg;
@ -36,110 +36,6 @@
}
}
// Status coloring
.ok, .info {
.status-indicator {
color: $colorInfo;
}
}
.alert, .caution, .warning {
.status-indicator, .count {
color: $colorStatusAlert;
}
}
.error, .err {
.status-indicator, .count {
color: $colorStatusError;
}
}
.available {
.status-indicator, .count {
color: $colorStatusAvailable;
}
}
.subdued {
.status-indicator {
color: pullForward($colorStatusBarBg, 40%);
}
}
.status-block-holder {
// Applied to mct-include element
// Contains status.block elements
$transDelay: 1.5s;
$transSpeed: .25s;
display: inline-block;
&.clickable { cursor: pointer; }
&:not(.clickable) { cursor: default; }
&.no-icon .status.block {
.status-indicator {
display: none;
}
}
&.float-right {
float: right;
}
&:not(.no-collapse) .status.block {
.label {
// Max-width silliness is necessary for width transition
@include trans-prop-nice(max-width, $transSpeed, $transDelay);
overflow: hidden;
max-width: 0px;
}
&:hover {
.label {
@include trans-prop-nice(max-width, $transSpeed, 0s);
max-width: 600px;
width: auto;
}
.count {
@include trans-prop-nice(max-width, $transSpeed, 0s);
opacity: 0;
}
}
}
}
.status.block {
$transDelay: 1.5s;
$transSpeed: .25s;
color: $colorStatusDefault;
display: inline-block;
margin-right: $interiorMargin;
.status-indicator,
.label,
.count {
display: inline-block;
vertical-align: top;
}
.status-indicator {
background: none !important;
margin-right: $interiorMarginSm;
&[class*='s-status']:before {
font-size: 1em;
}
}
.count {
@include trans-prop-nice(opacity, $transSpeed, $transDelay);
font-weight: bold;
opacity: 1;
}
.s-button {
background: $colorStatusBtnBg;
padding: 0 $interiorMargin;
height: auto;
line-height: inherit;
}
}
/******************************************************************* MESSAGE BANNERS */
.message {
&.block {
@ -289,7 +185,6 @@
> div,
> span {
//@include test(red);
margin-bottom: $interiorMargin;
}

View File

@ -47,6 +47,18 @@
}
}
.status-holder {
text-transform: uppercase;
}
.s-ue-bottom-bar {
background: $colorStatusBarBg;
color: $colorStatusBarFg;
cursor: default;
font-size: .7rem;
}
.user-environ {
height: 100%;
width: 100%;
@ -81,21 +93,17 @@
}
}
.ue-bottom-bar {
@include absPosDefault(0);// New status bar design
.l-ue-bottom-bar {
$m: $interiorMarginSm;
@include absPosDefault(0, $overflow: visible);// New status bar design
top: auto;
height: $ueFooterH;
line-height: $ueFooterH - ($interiorMargin * 2);
background: $colorStatusBarBg;
color: lighten($colorBodyBg, 30%);
font-size: .7rem;
line-height: $ueFooterH - ($m * 2);
.status-holder {
box-sizing: border-box;
@include absPosDefault($interiorMargin);
@include ellipsize();
@include absPosDefault($m, $overflow: visible);
right: 120px;
text-transform: uppercase;
z-index: 1;
z-index: 10;
}
.app-logo {
background-position: right center;

View File

@ -20,14 +20,8 @@
at runtime from the About dialog for additional information.
-->
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div class='status block'
<div class="ls-indicator {{ngModel.getCssClass()}}"
title="{{ngModel.getDescription()}}"
ng-click='ngModel.configure()'
ng-show="ngModel.getText().length > 0">
<span class="status-indicator {{ngModel.getCssClass()}}"></span><span class="label"
ng-class='ngModel.getTextClass()'>
{{ngModel.getText()}}
<a class="s-button icon-gear" ng-if="ngModel.configure"></a>
</span><span class="count">
</span>
<span class="label">{{ngModel.getText()}}</span>
</div>

View File

@ -19,14 +19,9 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class='abs bottom-bar ue-bottom-bar mobile-disable-select' ng-controller="BottomBarController as bar">
<div class='abs bottom-bar l-ue-bottom-bar s-ue-bottom-bar mobile-disable-select'>
<div id='status' class='status-holder'>
<mct-include ng-repeat="indicator in bar.getIndicators()"
ng-model="indicator.ngModel"
key="indicator.template"
class="status-block-holder"
ng-class='indicator.ngModel.getGlyphClass()'>
</mct-include>
<mct-indicators></mct-indicators>
</div>
<mct-include key="'message-banner'"></mct-include>
<mct-include key="'about-logo'"></mct-include>

View File

@ -20,20 +20,21 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'csv',
'saveAs'
], function (CSV, saveAs) {
class CSVExporter {
export(rows, options) {
let headers = (options && options.headers) ||
(Object.keys((rows[0] || {})).sort());
let filename = (options && options.filename) || "export.csv";
let csvText = new CSV(rows, { header: headers }).encode();
let blob = new Blob([csvText], { type: "text/csv" });
saveAs(blob, filename);
define(
[],
function () {
function MCTIndicators(openmct) {
return {
restrict: "E",
link: function link(scope, element) {
openmct.indicators.indicatorElements
.forEach(function (indicatorElement) {
element.append(indicatorElement);
});
}
};
}
}
return CSVExporter;
});
return MCTIndicators;
}
);

View File

@ -1,76 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/controllers/BottomBarController"],
function (BottomBarController) {
describe("The bottom bar controller", function () {
var testIndicators,
testIndicatorA,
testIndicatorB,
testIndicatorC,
mockIndicator,
controller;
beforeEach(function () {
mockIndicator = jasmine.createSpyObj(
"indicator",
["getGlyph", "getCssClass", "getText"]
);
testIndicatorA = {};
testIndicatorB = function () {
return mockIndicator;
};
testIndicatorC = { template: "someTemplate" };
testIndicators = [
testIndicatorA,
testIndicatorB,
testIndicatorC
];
controller = new BottomBarController(testIndicators);
});
it("exposes one indicator description per extension", function () {
expect(controller.getIndicators().length)
.toEqual(testIndicators.length);
});
it("uses template field provided, or its own default", function () {
// "indicator" is the default;
// only testIndicatorC overrides this.
var indicators = controller.getIndicators();
expect(indicators[0].template).toEqual("indicator");
expect(indicators[1].template).toEqual("indicator");
expect(indicators[2].template).toEqual("someTemplate");
});
it("instantiates indicators given as constructors", function () {
// testIndicatorB constructs to mockIndicator
expect(controller.getIndicators()[1].ngModel).toBe(mockIndicator);
});
});
}
);

View File

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

View File

@ -3,6 +3,8 @@ $colorBodyBg: #333;
$colorBodyFg: #999;
$colorGenBg: #222;
$colorStatusBarBg: #000;
$colorStatusBarFg: #999;
$colorStatusBarFgHov: #aaa;
$colorKey: #0099cc;
$colorKeySelectedBg: #005177;
$colorKeyFg: #fff;
@ -55,7 +57,7 @@ $colorTransLucBg: #666; // Used as a visual blocking element over variable backg
// Foundation Colors
$colorAlt1: #ffc700;
$colorAlert: #ff3c00;
$colorAlert: #ff9900;
$colorWarningHi: #cc0000;
$colorWarningLo: #ff9900;
$colorDiagnostic: #a4b442;
@ -72,7 +74,6 @@ $colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: lighten($colorObjHdrTxt, 20%);
$colorTick: rgba(white, 0.2);
$colorSelectableSelectedPrimary: $colorKey;
//$colorSelectableSelectedSecondary: pushBack($colorSelectableSelectedPrimary, 20%);
$colorSelectableHov: rgba($colorBodyFg, 0.3);
// Menu colors
@ -114,11 +115,10 @@ $colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #ccc;
$colorStatusDefault: #ccc;
$colorStatusDefault: #999;
$colorStatusInfo: $colorInfo;
$colorStatusAlert: $colorAlert;
$colorStatusError: #d4585c;
$colorStatusAvailable: $colorKey;
$colorStatusError: #da0004;
$colorStatusBtnBg: $colorBtnBg;
$colorProgressBarOuter: rgba(#000, 0.1);
$colorProgressBarAmt: $colorKey;
@ -127,6 +127,12 @@ $progressBarStripeW: 20px;
$shdwStatusIc: rgba(black, 0.4) 0 1px 2px;
$animPausedPulseDur: 500ms;
// Indicator colors
$colorIndicatorAvailable: $colorKey;
$colorIndicatorDisabled: #444;
$colorIndicatorOn: $colorOk;
$colorIndicatorOff: #666;
// Selects
$colorSelectBg: $colorBtnBg;
$colorSelectFg: $colorBtnFg;

View File

@ -3,6 +3,8 @@ $colorBodyBg: #fcfcfc;
$colorBodyFg: #666;
$colorGenBg: #fff;
$colorStatusBarBg: #000;
$colorStatusBarFg: #999;
$colorStatusBarFgHov: #aaa;
$colorKey: #0099cc;
$colorKeySelectedBg: $colorKey;
$colorKeyFg: #fff;
@ -72,7 +74,6 @@ $colorObjHdrTxt: $colorBodyFg;
$colorObjHdrIc: lighten($colorObjHdrTxt, 30%);
$colorTick: rgba(black, 0.2);
$colorSelectableSelectedPrimary: $colorKey;
//$colorSelectableSelectedSecondary: pushBack($colorSelectableSelectedPrimary, 20%);
$colorSelectableHov: rgba($colorBodyFg, 0.4);
// Menu colors
@ -117,8 +118,7 @@ $colorStatusFg: #999;
$colorStatusDefault: #ccc;
$colorStatusInfo: #60ba7b;
$colorStatusAlert: #ffb66c;
$colorStatusError: #c96b68;
$colorStatusAvailable: $colorKey;
$colorStatusError: #da0004;
$colorStatusBtnBg: #666;
$colorProgressBarOuter: rgba(#000, 0.1);
$colorProgressBarAmt: #0a0;
@ -127,6 +127,12 @@ $progressBarStripeW: 20px;
$shdwStatusIc: rgba(white, 0.8) 0 0px 5px;
$animPausedPulseDur: 1s;
// Indicator colors
$colorIndicatorAvailable: $colorKey;
$colorIndicatorDisabled: #444;
$colorIndicatorOn: $colorOk;
$colorIndicatorOff: #666;
// Selects
$colorSelectBg: $colorBtnBg;
$colorSelectFg: $colorBtnFg;

View File

@ -55,7 +55,6 @@ define([
timerTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/clock", {
"name": "Clocks/Timers",
"descriptions": "Domain objects for displaying current & relative times.",
@ -86,11 +85,6 @@ define([
"CLOCK_INDICATOR_FORMAT"
],
"priority": "preferred"
},
{
"implementation": FollowIndicator,
"depends": ["timerService"],
"priority": "fallback"
}
],
"services": [
@ -305,6 +299,10 @@ define([
}
}
],
"runs": [{
"implementation": FollowIndicator,
"depends": ["openmct", "timerService"]
}],
"licenses": [
{
"name": "moment-duration-format",

View File

@ -45,11 +45,11 @@ define(
}
ClockIndicator.prototype.getGlyphClass = function () {
return "no-collapse float-right subdued";
return "";
};
ClockIndicator.prototype.getCssClass = function () {
return "icon-clock";
return "t-indicator-clock icon-clock no-collapse float-right";
};
ClockIndicator.prototype.getText = function () {

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* Open MCT, Copyright (c) 2009-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -20,38 +20,32 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['moment'],
function (moment) {
var NO_TIMER = "No timer being followed";
define([], function () {
/**
* Indicator that displays the active timer, as well as its
* current state.
* @implements {Indicator}
* @memberof platform/features/clock
*/
function FollowIndicator(timerService) {
this.timerService = timerService;
/**
* Indicator that displays the active timer, as well as its
* current state.
* @memberof platform/features/clock
*/
return function installFollowIndicator(openmct, timerService) {
var indicator = openmct.indicators.simpleIndicator();
var timer = timerService.getTimer();
setIndicatorStatus(timer);
function setIndicatorStatus(newTimer) {
if (newTimer !== undefined) {
indicator.iconClass('icon-timer');
indicator.statusClass('s-status-on');
indicator.text('Following timer ' + newTimer.name);
} else {
indicator.iconClass('icon-timer');
indicator.statusClass('s-status-disabled');
indicator.text('No timer being followed');
}
}
FollowIndicator.prototype.getGlyphClass = function () {
return "";
};
timerService.on('change', setIndicatorStatus);
FollowIndicator.prototype.getCssClass = function () {
return (this.timerService.getTimer()) ? "icon-timer s-status-ok" : "icon-timer";
};
FollowIndicator.prototype.getText = function () {
var timer = this.timerService.getTimer();
return timer ? ('Following timer ' + timer.name) : NO_TIMER;
};
FollowIndicator.prototype.getDescription = function () {
return "";
};
return FollowIndicator;
}
);
openmct.indicators.add(indicator);
};
});

View File

@ -44,7 +44,7 @@ define(['EventEmitter'], function (EventEmitter) {
*/
TimerService.prototype.setTimer = function (timer) {
this.timer = timer;
this.emit('change');
this.emit('change', timer);
if (this.stopObserving) {
this.stopObserving();

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* Open MCT, Copyright (c) 2009-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -20,39 +20,77 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) {
var TIMER_SERVICE_METHODS =
['setTimer', 'getTimer', 'clearTimer', 'on', 'off'];
define([
"../../src/indicators/FollowIndicator",
"../../src/services/TimerService",
"../../../../../src/MCT",
'zepto'
], function (
FollowIndicator,
TimerService,
MCT,
$
) {
describe("The timer-following indicator", function () {
var mockTimerService;
var indicator;
var timerService;
var openmct;
beforeEach(function () {
mockTimerService =
jasmine.createSpyObj('timerService', TIMER_SERVICE_METHODS);
indicator = new FollowIndicator(mockTimerService);
openmct = new MCT();
timerService = new TimerService(openmct);
spyOn(openmct.indicators, "add");
});
it("implements the Indicator interface", function () {
expect(indicator.getGlyphClass()).toEqual(jasmine.any(String));
expect(indicator.getCssClass()).toEqual(jasmine.any(String));
expect(indicator.getText()).toEqual(jasmine.any(String));
expect(indicator.getDescription()).toEqual(jasmine.any(String));
it("adds an indicator when installed", function () {
FollowIndicator(openmct, timerService);
expect(openmct.indicators.add).toHaveBeenCalled();
});
it("indicates that no timer is being followed", function () {
FollowIndicator(openmct, timerService);
var simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
var element = simpleIndicator.element;
var text = $('.indicator-text', element).text().trim();
expect(text).toEqual('No timer being followed');
});
describe("when a timer is set", function () {
var testObject;
var simpleIndicator;
beforeEach(function () {
testObject = { name: "some timer!" };
mockTimerService.getTimer.and.returnValue(testObject);
testObject = {
identifier: {
namespace: 'namespace',
key: 'key'
},
name: "some timer!"
};
timerService.setTimer(testObject);
FollowIndicator(openmct, timerService);
simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
});
it("displays the timer's name", function () {
expect(indicator.getText().indexOf(testObject.name))
.not.toEqual(-1);
var element = simpleIndicator.element;
var text = $('.indicator-text', element).text().trim();
expect(text).toEqual('Following timer ' + testObject.name);
});
it("displays the timer's name when it changes", function () {
var secondTimer = {
identifier: {
namespace: 'namespace',
key: 'key2'
},
name: "Some other timer"
};
var element = simpleIndicator.element;
timerService.setTimer(secondTimer);
var text = $('.indicator-text', element).text().trim();
expect(text).toEqual('Following timer ' + secondTimer.name);
});
});
});
});

View File

@ -48,247 +48,247 @@ define([
"description": "Create and save timestamped notes with embedded object snapshots.",
"extensions":
{
"types": [
{
"key": "notebook",
"name": "Notebook",
"cssClass": "icon-notebook",
"description": "Create and save timestamped notes with embedded object snapshots.",
"features": ["creation"],
"model": {
"entries": [],
"composition": [],
"entryTypes": []
},
"properties": [
{
"key": "defaultSort",
"name": "Default Sort",
"control": "select",
"options": [
{
"name": "Newest First",
"value": "-createdOn"
},
{
"name": "Oldest First",
"value": "createdOn"
}
],
"cssClass": "l-inline"
}
]
}
],
"views": [
{
"key": "notebook.view",
"type": "notebook",
"cssClass": "icon-notebook",
"name": "notebook",
"template": notebookTemplate,
"editable": false,
"uses": [
"composition",
"action"
],
"gestures": [
"drop"
]
}
],
"controllers": [
{
"key": "NotebookController",
"implementation": NotebookController,
"depends": [
"$scope",
"dialogService",
"popupService",
"agentService",
"objectService",
"navigationService",
"now",
"actionService",
"$timeout",
"$element",
"$sce"
]
},
{
"key": "NewEntryController",
"implementation": NewEntryController,
"depends": ["$scope",
"$rootScope"
]
},
{
"key": "selectSnapshotController",
"implementation": SelectSnapshotController,
"depends": ["$scope",
"$rootScope"
]
},
{
"key": "LayoutNotebookController",
"implementation": LayoutNotebookController,
"depends": ["$scope"]
}
],
"representations": [
{
"key": "draggedEntry",
"template": entryTemplate
},
{
"key": "frameLayoutNotebook",
"template": frameTemplate
}
],
"templates": [
{
"key": "annotate-snapshot",
"template": annotationTemplate
},
{
"key": "notificationTemplate",
"template": notificationsTemplate
}
],
"directives": [
{
"key": "mctSnapshot",
"implementation": MCTSnapshot,
"depends": [
"$rootScope",
"$document",
"exportImageService",
"dialogService",
"notificationService"
]
},
{
"key": "mctEntryDnd",
"implementation": MCTEntryDnd,
"depends": [
"$rootScope",
"$compile",
"dndService",
"typeService",
"notificationService"
]
}
],
"actions": [
{
"key": "view-snapshot",
"implementation": ViewSnapshotAction,
"name": "View Snapshot",
"description": "View the large image in a modal",
"category": "embed",
"depends": [
"$compile"
]
},
{
"key": "annotate-snapshot",
"implementation": AnnotateSnapshotAction,
"name": "Annotate Snapshot",
"cssClass": "icon-pencil labeled",
"description": "Annotate embed's snapshot",
"category": "embed",
"depends": [
"dialogService",
"dndService",
"$rootScope"
]
},
// "types": [
// {
// "key": "notebook",
// "name": "Notebook",
// "cssClass": "icon-notebook",
// "description": "Create and save timestamped notes with embedded object snapshots.",
// "features": ["creation"],
// "model": {
// "entries": [],
// "composition": [],
// "entryTypes": [],
// "defaultSort": "-createdOn"
// },
// "properties": [
// {
// "key": "defaultSort",
// "name": "Default Sort",
// "control": "select",
// "options": [
// {
// "name": "Newest First",
// "value": "-createdOn"
// },
// {
// "name": "Oldest First",
// "value": "createdOn"
// }
// ],
// "cssClass": "l-inline"
// }
// ]
// }
// ],
// "views": [
// {
// "key": "notebook.view",
// "type": "notebook",
// "cssClass": "icon-notebook",
// "name": "notebook",
// "template": notebookTemplate,
// "editable": false,
// "uses": [
// "composition",
// "action"
// ],
// "gestures": [
// "drop"
// ]
// }
// ],
// "controllers": [
// {
// "key": "NotebookController",
// "implementation": NotebookController,
// "depends": [
// "$scope",
// "dialogService",
// "popupService",
// "agentService",
// "objectService",
// "navigationService",
// "now",
// "actionService",
// "$timeout",
// "$element",
// "$sce"
// ]
// },
// {
// "key": "NewEntryController",
// "implementation": NewEntryController,
// "depends": ["$scope",
// "$rootScope"
// ]
// },
// {
// "key": "selectSnapshotController",
// "implementation": SelectSnapshotController,
// "depends": ["$scope",
// "$rootScope"
// ]
// },
// {
// "key": "LayoutNotebookController",
// "implementation": LayoutNotebookController,
// "depends": ["$scope"]
// }
// ],
// "representations": [
// {
// "key": "draggedEntry",
// "template": entryTemplate
// },
// {
// "key": "frameLayoutNotebook",
// "template": frameTemplate
// }
// ],
// "templates": [
// {
// "key": "annotate-snapshot",
// "template": annotationTemplate
// },
// {
// "key": "notificationTemplate",
// "template": notificationsTemplate
// }
// ],
// "directives": [
// {
// "key": "mctSnapshot",
// "implementation": MCTSnapshot,
// "depends": [
// "$rootScope",
// "$document",
// "exportImageService",
// "dialogService",
// "notificationService"
// ]
// },
// {
// "key": "mctEntryDnd",
// "implementation": MCTEntryDnd,
// "depends": [
// "$rootScope",
// "$compile",
// "dndService",
// "typeService",
// "notificationService"
// ]
// }
// ],
// "actions": [
// {
// "key": "view-snapshot",
// "implementation": ViewSnapshotAction,
// "name": "View Snapshot",
// "description": "View the large image in a modal",
// "category": "embed",
// "depends": [
// "$compile"
// ]
// },
// {
// "key": "annotate-snapshot",
// "implementation": AnnotateSnapshotAction,
// "name": "Annotate Snapshot",
// "cssClass": "icon-pencil labeled",
// "description": "Annotate embed's snapshot",
// "category": "embed",
// "depends": [
// "dialogService",
// "dndService",
// "$rootScope"
// ]
// },
{
"key": "remove-embed",
"implementation": RemoveEmbedAction,
"name": "Remove...",
"cssClass": "icon-trash labeled",
"description": "Remove this embed",
"category": [
"embed",
"embed-no-snap"
],
"depends": [
"dialogService"
]
},
{
"key": "remove-snapshot",
"implementation": RemoveSnapshotAction,
"name": "Remove Snapshot",
"cssClass": "icon-trash labeled",
"description": "Remove Snapshot of the embed",
"category": "embed",
"depends": [
"dialogService"
]
},
{
"key": "notebook-new-entry",
"implementation": newEntryAction,
"name": "New Notebook Entry",
"cssClass": "icon-notebook labeled",
"description": "Add a new Notebook entry",
"category": [
"contextual",
"view-control"
],
"depends": [
"$compile",
"$rootScope",
"dialogService",
"notificationService",
"linkService"
],
"priority": "preferred"
}
],
"licenses": [
{
"name": "painterro",
"version": "4.1.0",
"author": "Mike Bostock",
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
"website": "https://d3js.org/",
"copyright": "Copyright 2010-2016 Mike Bostock",
"license": "BSD-3-Clause",
"link": "https://github.com/d3/d3/blob/master/LICENSE"
}
],
"capabilities": [
{
"key": "notebook",
"name": "Notebook Capability",
"description": "Provides a capability for looking for a notebook domain object",
"implementation": NotebookCapability,
"depends": [
"typeService"
]
}
],
"policies": [
{
"category": "composition",
"implementation": CompositionPolicy,
"message": "Objects of this type cannot contain objects of that type."
}
],
"controls": [
{
"key": "embed-control",
"template": embedControlTemplate
},
{
"key": "snapshot-select",
"template": snapSelectTemplate
}
],
// {
// "key": "remove-embed",
// "implementation": RemoveEmbedAction,
// "name": "Remove...",
// "cssClass": "icon-trash labeled",
// "description": "Remove this embed",
// "category": [
// "embed",
// "embed-no-snap"
// ],
// "depends": [
// "dialogService"
// ]
// },
// {
// "key": "remove-snapshot",
// "implementation": RemoveSnapshotAction,
// "name": "Remove Snapshot",
// "cssClass": "icon-trash labeled",
// "description": "Remove Snapshot of the embed",
// "category": "embed",
// "depends": [
// "dialogService"
// ]
// },
// {
// "key": "notebook-new-entry",
// "implementation": newEntryAction,
// "name": "New Notebook Entry",
// "cssClass": "icon-notebook labeled",
// "description": "Add a new Notebook entry",
// "category": [
// "view-control"
// ],
// "depends": [
// "$compile",
// "$rootScope",
// "dialogService",
// "notificationService",
// "linkService"
// ],
// "priority": "preferred"
// }
// ],
// "licenses": [
// {
// "name": "painterro",
// "version": "0.2.65",
// "author": "Ivan Borshchov",
// "description": "Painterro is JavaScript paint widget which allows editing images directly in a browser.",
// "website": "https://github.com/ivictbor/painterro",
// "copyright": "Copyright 2017 Ivan Borshchov",
// "license": "MIT",
// "link": "https://github.com/ivictbor/painterro/blob/master/LICENSE"
// }
// ],
// "capabilities": [
// {
// "key": "notebook",
// "name": "Notebook Capability",
// "description": "Provides a capability for looking for a notebook domain object",
// "implementation": NotebookCapability,
// "depends": [
// "typeService"
// ]
// }
// ],
// "policies": [
// {
// "category": "composition",
// "implementation": CompositionPolicy,
// "message": "Objects of this type cannot contain objects of that type."
// }
// ],
// "controls": [
// {
// "key": "embed-control",
// "template": embedControlTemplate
// },
// {
// "key": "snapshot-select",
// "template": snapSelectTemplate
// }
// ],
"stylesheets": [
{
"stylesheetUrl": "css/notebook.css"

View File

@ -185,7 +185,18 @@ define(
NewEntryContextual.appliesTo = function (context) {
var domainObject = context.domainObject;
return !!(domainObject && domainObject.getModel().type !== 'notebook');
if (domainObject) {
if (domainObject.getModel().type === 'Notebook') {
// do not allow in context of a notebook
return false;
} else if (domainObject.getModel().type.includes('imagery')) {
// do not allow in the context of an object with imagery
// (because of cross domain issue with snapshot)
return false;
}
}
return true;
};
return NewEntryContextual;

View File

@ -42,7 +42,7 @@ define(
) {
$scope.entriesEl = $(document.body).find('.t-entries-list');
$scope.sortEntries = $scope.domainObject.getModel().defaultSort || "-createdOn";
$scope.sortEntries = $scope.domainObject.getModel().defaultSort;
$scope.showTime = "0";
$scope.editEntry = false;
$scope.entrySearch = '';
@ -147,7 +147,7 @@ define(
});
};
$scope.textFocus = function ($event, entryId) {
$scope.textFocus = function ($event) {
if ($event.srcElement) {
$scope.currentEntryValue = $event.srcElement.innerText;
} else {
@ -354,6 +354,11 @@ define(
$scope.$watchCollection("composition", refreshComp);
$scope.$watch('domainObject.getModel().defaultSort', function (newDefaultSort, oldDefaultSort) {
if (newDefaultSort !== oldDefaultSort) {
$scope.sortEntries = newDefaultSort;
}
});
$scope.$on('$destroy', function () {});

View File

@ -25,7 +25,7 @@ define(['zepto'], function ($) {
var document = $document[0];
function link($scope, $element, $attrs) {
var objectElement = $(document.body).find('.overlay')[0] || $(document.body).find("[key='representation.selected.key']")[0],
var objectElement = $(document.body).find(".overlay .object-holder")[0] || $(document.body).find("[key='representation.selected.key']")[0],
takeSnapshot,
makeImg,
saveImg;

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
mct-table {
.mct-sizing-table {
z-index: -1;
visibility: hidden;
@ -57,13 +57,18 @@
overflow: hidden;
box-sizing: border-box;
display: inline-block;
text-overflow: ellipsis;
}
}
}
mct-table {
.l-control-bar {
margin-bottom: 3px;
}
}
.mct-table-scroll-forcer {
// Force horz scroll when needed; width set via JS
font-size: 0;
height: 1px; // Height 0 won't force scroll properly
position: relative;
}

View File

@ -59,6 +59,10 @@
</tbody>
</table>
<div class="l-tabular-body t-scrolling vscroll--persist" mct-resize="resize()" mct-scroll-x="scroll.x">
<div class="mct-table-scroll-forcer"
ng-style="{
width: totalWidth
}"></div>
<table class="mct-table"
ng-style="{
height: totalHeight + 'px',

View File

@ -50,9 +50,7 @@ define(function () {
};
TableColumn.prototype.getValue = function (telemetryDatum, limitEvaluator) {
var isValueColumn = !!(this.metadatum.hints.y || this.metadatum.hints.range);
var alarm = isValueColumn &&
limitEvaluator &&
var alarm = limitEvaluator &&
limitEvaluator.evaluate(telemetryDatum, this.metadatum);
var value = {
text: this.formatter.format(telemetryDatum),

View File

@ -33,20 +33,24 @@ define(
var CONNECTED = {
text: "Connected",
glyphClass: "ok",
statusClass: "s-status-ok",
description: "Connected to the domain object database."
},
DISCONNECTED = {
text: "Disconnected",
glyphClass: "err",
statusClass: "s-status-caution",
description: "Unable to connect to the domain object database."
},
SEMICONNECTED = {
text: "Unavailable",
glyphClass: "caution",
statusClass: "s-status-caution",
description: "Database does not exist or is unavailable."
},
PENDING = {
text: "Checking connection..."
text: "Checking connection...",
statusClass: "s-status-caution"
};
/**
@ -96,7 +100,7 @@ define(
}
CouchIndicator.prototype.getCssClass = function () {
return "icon-database";
return "icon-database " + this.state.statusClass;
};
CouchIndicator.prototype.getGlyphClass = function () {

View File

@ -57,7 +57,7 @@ define(
});
it("has a database icon", function () {
expect(indicator.getCssClass()).toEqual("icon-database");
expect(indicator.getCssClass()).toEqual("icon-database s-status-caution");
});
it("consults the database at the configured path", function () {

View File

@ -32,11 +32,13 @@ define(
var CONNECTED = {
text: "Connected",
glyphClass: "ok",
statusClass: "s-status-ok",
description: "Connected to the domain object database."
},
DISCONNECTED = {
text: "Disconnected",
glyphClass: "err",
statusClass: "s-status-caution",
description: "Unable to connect to the domain object database."
},
PENDING = {

View File

@ -26,7 +26,7 @@ define(
var LOCAL_STORAGE_WARNING = [
"Using browser local storage for persistence.",
"Anything you create or change will be visible only",
"Anything you create or change will only be saved",
"in this browser on this machine."
].join(' ');
@ -41,7 +41,7 @@ define(
}
LocalStorageIndicator.prototype.getCssClass = function () {
return "icon-database";
return "icon-database s-status-caution";
};
LocalStorageIndicator.prototype.getGlyphClass = function () {
return 'caution';

View File

@ -38,7 +38,7 @@ define(
});
it("has a database icon", function () {
expect(indicator.getCssClass()).toEqual("icon-database");
expect(indicator.getCssClass()).toEqual("icon-database s-status-caution");
});
it("has a 'caution' class to draw attention", function () {

View File

@ -30,7 +30,8 @@ define([
'./plugins/plugins',
'./ui/ViewRegistry',
'./ui/InspectorViewRegistry',
'./ui/ToolbarRegistry'
'./ui/ToolbarRegistry',
'./adapter/indicators/legacy-indicators-plugin'
], function (
EventEmitter,
legacyRegistry,
@ -41,7 +42,8 @@ define([
plugins,
ViewRegistry,
InspectorViewRegistry,
ToolbarRegistry
ToolbarRegistry,
LegacyIndicatorsPlugin
) {
/**
* Open MCT is an extensible web application for building mission
@ -192,6 +194,15 @@ define([
*/
this.telemetry = new api.TelemetryAPI(this);
/**
* An interface for creating new indicators and changing them dynamically.
*
* @type {module:openmct.IndicatorAPI}
* @memberof module:openmct.MCT#
* @name indicators
*/
this.indicators = new api.IndicatorAPI(this);
this.Dialog = api.Dialog;
}
@ -266,6 +277,9 @@ define([
legacyRegistry.register('adapter', this.legacyBundle);
legacyRegistry.enable('adapter');
this.install(LegacyIndicatorsPlugin());
/**
* Fired by [MCT]{@link module:openmct.MCT} when the application
* is started.

View File

@ -32,7 +32,9 @@ define([
'./policies/AdaptedViewPolicy',
'./runs/AlternateCompositionInitializer',
'./runs/TimeSettingsURLHandler',
'./runs/TypeDeprecationChecker'
'./runs/TypeDeprecationChecker',
'./runs/LegacyTelemetryProvider',
'./services/LegacyObjectAPIInterceptor'
], function (
legacyRegistry,
ActionDialogDecorator,
@ -45,7 +47,9 @@ define([
AdaptedViewPolicy,
AlternateCompositionInitializer,
TimeSettingsURLHandler,
TypeDeprecationChecker
TypeDeprecationChecker,
LegacyTelemetryProvider,
LegacyObjectAPIInterceptor
) {
legacyRegistry.register('src/adapter', {
"extensions": {
@ -94,6 +98,18 @@ define([
provides: "modelService",
implementation: MissingModelCompatibilityDecorator,
depends: ["openmct"]
},
{
provides: "objectService",
type: "decorator",
priority: "mandatory",
implementation: LegacyObjectAPIInterceptor,
depends: [
"openmct",
"roots[]",
"instantiate",
"topic"
]
}
],
policies: [
@ -126,6 +142,13 @@ define([
);
},
depends: ["openmct", "$location", "$rootScope"]
},
{
implementation: LegacyTelemetryProvider,
depends: [
"openmct",
"instantiate"
]
}
],
licenses: [

View File

@ -0,0 +1,68 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
var LEGACY_INDICATOR_TEMPLATE =
'<mct-include ' +
' ng-model="indicator" ' +
' class="h-indicator" ' +
' key="template">' +
' </mct-include>';
return function () {
return function (openmct) {
openmct.legacyExtension('runs', {
depends: ['indicators[]'],
implementation: addLegacyIndicators
});
function addLegacyIndicators(legacyIndicators) {
legacyIndicators.forEach(function (legacyIndicatorDef) {
var legacyIndicator = initializeIfNeeded(legacyIndicatorDef);
var legacyIndicatorElement = buildLegacyIndicator(legacyIndicator, legacyIndicatorDef.template);
openmct.indicators.add({
element: legacyIndicatorElement
});
});
}
function initializeIfNeeded(LegacyIndicatorDef) {
var legacyIndicator;
if (typeof LegacyIndicatorDef === 'function') {
legacyIndicator = new LegacyIndicatorDef();
} else {
legacyIndicator = LegacyIndicatorDef;
}
return legacyIndicator;
}
function buildLegacyIndicator(legacyIndicator, template) {
var $compile = openmct.$injector.get('$compile');
var $rootScope = openmct.$injector.get('$rootScope');
var scope = $rootScope.$new(true);
scope.indicator = legacyIndicator;
scope.template = template || 'indicator';
return $compile(LEGACY_INDICATOR_TEMPLATE)(scope)[0];
}
};
};
});

View File

@ -0,0 +1,114 @@
/*****************************************************************************
* 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(
[
"../../MCT",
"../../../platform/commonUI/general/src/directives/MCTIndicators",
"./legacy-indicators-plugin"
],
function (
MCT,
MCTIndicators,
LegacyIndicatorsPlugin
) {
var openmct;
var directive;
var holderElement;
var legacyExtensionFunction = MCT.prototype.legacyExtension;
var legacyIndicatorsRunsFunction;
describe('The legacy indicators plugin', function () {
beforeEach(function () {
mockLegacyExtensionFunction();
openmct = new MCT();
directive = new MCTIndicators(openmct);
holderElement = document.createElement('div');
mockAngularComponents();
LegacyIndicatorsPlugin()(openmct);
});
afterEach(function () {
MCT.prototype.legacyExtension = legacyExtensionFunction;
});
function mockLegacyExtensionFunction() {
spyOn(MCT.prototype, "legacyExtension");
MCT.prototype.legacyExtension.and.callFake(function (extensionName, definition) {
if (extensionName === 'runs') {
legacyIndicatorsRunsFunction = definition.implementation;
}
});
}
function mockAngularComponents() {
var mockInjector = jasmine.createSpyObj('$injector', ['get']);
var mockCompile = jasmine.createSpy('$compile');
var mockRootScope = jasmine.createSpyObj('rootScope', ['$new']);
var mockScope = {};
mockRootScope.$new.and.returnValue(mockScope);
mockInjector.get.and.callFake(function (service) {
return {
'$compile': mockCompile,
'$rootScope': mockRootScope
}[service];
});
openmct.$injector = mockInjector;
mockCompile.and.callFake(function () {
return function () {
return [document.createElement('div')];
};
});
}
it("Displays any legacy indicators ", function () {
var legacyIndicators = [{},{},{},{}];
legacyIndicatorsRunsFunction(legacyIndicators);
renderIndicators();
expect(holderElement.children.length).toBe(legacyIndicators.length);
});
it("If legacy indicator is defined as a constructor function, executes function ", function () {
var mockConstructorFunction = jasmine.createSpy('mockIndicatorConstructor');
var legacyIndicators = [{}, mockConstructorFunction];
mockConstructorFunction.and.returnValue({});
legacyIndicatorsRunsFunction(legacyIndicators);
renderIndicators();
expect(holderElement.children.length).toBe(legacyIndicators.length);
expect(mockConstructorFunction).toHaveBeenCalled();
});
function renderIndicators() {
directive.link({}, holderElement);
}
});
}
);

View File

@ -21,7 +21,7 @@
*****************************************************************************/
define([
'../objects/object-utils'
'../../api/objects/object-utils'
], function (
utils
) {

View File

@ -21,7 +21,7 @@
*****************************************************************************/
define([
'./object-utils'
'../../api/objects/object-utils'
], function (
utils
) {

View File

@ -27,7 +27,8 @@ define([
'./types/TypeRegistry',
'./ui/Dialog',
'./ui/GestureAPI',
'./telemetry/TelemetryAPI'
'./telemetry/TelemetryAPI',
'./indicators/IndicatorAPI'
], function (
TimeAPI,
ObjectAPI,
@ -35,7 +36,8 @@ define([
TypeRegistry,
Dialog,
GestureAPI,
TelemetryAPI
TelemetryAPI,
IndicatorAPI
) {
return {
TimeAPI: TimeAPI,
@ -44,6 +46,7 @@ define([
Dialog: Dialog,
TypeRegistry: TypeRegistry,
GestureAPI: GestureAPI,
TelemetryAPI: TelemetryAPI
TelemetryAPI: TelemetryAPI,
IndicatorAPI: IndicatorAPI
};
});

View File

@ -0,0 +1,68 @@
/*****************************************************************************
* 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([
'./SimpleIndicator',
'lodash'
], function (
SimpleIndicator,
_
) {
function IndicatorAPI(openmct) {
this.openmct = openmct;
this.indicatorElements = [];
}
IndicatorAPI.prototype.simpleIndicator = function () {
return new SimpleIndicator(this.openmct);
};
/**
* Accepts an indicator object, which is a simple object
* with a single attribute, 'element' which has an HTMLElement
* as its value.
*
* We provide .simpleIndicator() as a convenience function
* which will create a default Open MCT indicator that can
* be passed to .add(indicator). This indicator also exposes
* functions for changing its appearance to support customization
* and dynamic behavior.
*
* Eg.
* var myIndicator = openmct.indicators.simpleIndicator();
* openmct.indicators.add(myIndicator);
*
* myIndicator.text("Hello World!");
* myIndicator.iconClass("icon-info");
*
*/
IndicatorAPI.prototype.add = function (indicator) {
// So that we can consistently position indicator elements,
// guarantee that they are wrapped in an element we control
var wrapperNode = document.createElement('div');
wrapperNode.className = 'h-indicator';
wrapperNode.appendChild(indicator.element);
this.indicatorElements.push(wrapperNode);
};
return IndicatorAPI;
});

View File

@ -0,0 +1,115 @@
/*****************************************************************************
* 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(
[
"../../MCT",
"../../../platform/commonUI/general/src/directives/MCTIndicators"
],
function (
MCT,
MCTIndicators
) {
describe("The Indicator API", function () {
var openmct;
var directive;
var holderElement;
beforeEach(function () {
openmct = new MCT();
directive = new MCTIndicators(openmct);
holderElement = document.createElement('div');
});
describe("The simple indicator", function () {
var simpleIndicator;
beforeEach(function () {
simpleIndicator = openmct.indicators.simpleIndicator();
openmct.indicators.add(simpleIndicator);
renderIndicators();
});
it("applies the set icon class", function () {
simpleIndicator.iconClass('testIconClass');
expect(getIconElement().classList.contains('testIconClass')).toBe(true);
simpleIndicator.iconClass('anotherIconClass');
expect(getIconElement().classList.contains('testIconClass')).toBe(false);
expect(getIconElement().classList.contains('anotherIconClass')).toBe(true);
});
it("applies the set status class", function () {
simpleIndicator.statusClass('testStatusClass');
expect(getIconElement().classList.contains('testStatusClass')).toBe(true);
simpleIndicator.statusClass('anotherStatusClass');
expect(getIconElement().classList.contains('testStatusClass')).toBe(false);
expect(getIconElement().classList.contains('anotherStatusClass')).toBe(true);
});
it("displays the set text", function () {
simpleIndicator.text('some test text');
expect(getTextElement().textContent.trim()).toEqual('some test text');
});
it("sets the indicator's title", function () {
simpleIndicator.description('a test description');
expect(getIndicatorElement().getAttribute('title')).toEqual('a test description');
});
it("Hides indicator icon if no text is set", function () {
simpleIndicator.text('');
expect(getIndicatorElement().classList.contains('hidden')).toBe(true);
});
function getIconElement() {
return holderElement.querySelector('.ls-indicator');
}
function getIndicatorElement() {
return holderElement.querySelector('.ls-indicator');
}
function getTextElement() {
return holderElement.querySelector('.indicator-text');
}
});
it("Supports registration of a completely custom indicator", function () {
var customIndicator = document.createElement('div');
customIndicator.classList.add('customIndicator');
customIndicator.textContent = 'A custom indicator';
openmct.indicators.add({element: customIndicator});
renderIndicators();
expect(holderElement.querySelector('.customIndicator').textContent.trim()).toEqual('A custom indicator');
});
function renderIndicators() {
directive.link({}, holderElement);
}
});
});

View File

@ -0,0 +1,96 @@
/*****************************************************************************
* 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(['zepto', 'text!./res/indicator-template.html'],
function ($, indicatorTemplate) {
var DEFAULT_ICON_CLASS = 'icon-info';
function SimpleIndicator(openmct) {
this.openmct = openmct;
this.element = $(indicatorTemplate)[0];
this.textElement = this.element.querySelector('.indicator-text');
//Set defaults
this.text('New Indicator');
this.description('A simple indicator');
this.iconClass(DEFAULT_ICON_CLASS);
this.statusClass('');
}
SimpleIndicator.prototype.text = function (text) {
if (text !== undefined && text !== this.textValue) {
this.textValue = text;
this.textElement.innerText = text;
if (!text) {
this.element.classList.add('hidden');
} else {
this.element.classList.remove('hidden');
}
}
return this.textValue;
};
SimpleIndicator.prototype.description = function (description) {
if (description !== undefined && description !== this.descriptionValue) {
this.descriptionValue = description;
this.element.title = description;
}
return this.descriptionValue;
};
SimpleIndicator.prototype.iconClass = function (iconClass) {
if (iconClass !== undefined && iconClass !== this.iconClassValue) {
// element.classList is precious and throws errors if you try and add
// or remove empty strings
if (this.iconClassValue) {
this.element.classList.remove(this.iconClassValue);
}
if (iconClass) {
this.element.classList.add(iconClass);
}
this.iconClassValue = iconClass;
}
return this.iconClassValue;
};
SimpleIndicator.prototype.statusClass = function (statusClass) {
if (statusClass !== undefined && statusClass !== this.statusClassValue) {
if (this.statusClassValue) {
this.element.classList.remove(this.statusClassValue);
}
if (statusClass) {
this.element.classList.add(statusClass);
}
this.statusClassValue = statusClass;
}
return this.statusClassValue;
};
return SimpleIndicator;
}
);

View File

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

View File

@ -1,55 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./TelemetryAPI',
'./LegacyTelemetryProvider',
'legacyRegistry'
], function (
TelemetryAPI,
LegacyTelemetryProvider,
legacyRegistry
) {
legacyRegistry.register('src/api/telemetry', {
name: 'Telemetry API',
description: 'The public Telemetry API',
extensions: {
runs: [
{
key: "TelemetryAPI",
implementation: TelemetryAPI,
depends: [
'formatService'
]
},
{
key: "LegacyTelemetryAdapter",
implementation: LegacyTelemetryProvider,
depends: [
"openmct",
"instantiate"
]
}
]
}
});
});

View File

@ -24,8 +24,6 @@ define([
'legacyRegistry',
'../src/adapter/bundle',
'../src/api/objects/bundle',
'../src/api/telemetry/bundle',
'../example/builtins/bundle',
'../example/composite/bundle',
@ -93,8 +91,6 @@ define([
var DEFAULTS = [
'src/adapter',
'src/api/objects',
'src/api/telemetry',
'platform/framework',
'platform/core',
'platform/representation',

View File

@ -0,0 +1,215 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"./src/controllers/NotebookController",
"./src/controllers/NewEntryController",
"./src/controllers/SelectSnapshotController",
"./src/actions/NewEntryContextual",
"./src/actions/AnnotateSnapshot",
"./src/directives/MCTSnapshot",
"./src/directives/EntryDnd",
"text!./res/templates/controls/snapSelect.html",
"text!./res/templates/controls/embedControl.html",
"text!./res/templates/annotation.html",
"text!./res/templates/draggedEntry.html"
], function (
NotebookController,
NewEntryController,
SelectSnapshotController,
newEntryAction,
AnnotateSnapshotAction,
MCTSnapshotDirective,
EntryDndDirective,
snapSelectTemplate,
embedControlTemplate,
annotationTemplate,
draggedEntryTemplate
) {
var installed = false;
function NotebookV2Plugin() {
return function install(openmct) {
if (installed) {
return;
}
installed = true;
openmct.legacyRegistry.register('notebookV2', {
name: 'Notebook Plugin',
extensions: {
types: [
{
key: 'notebookV2',
name: 'NotebookV2',
cssClass: 'icon-notebook',
description: 'Create and save timestamped notes with embedded object snapshots.',
features: 'creation',
model: {
entries: [],
composition: [],
entryTypes: [],
defaultSort: '-createdOn'
},
properties: [
{
key: 'defaultSort',
name: 'Default Sort',
control: 'select',
options: [
{
name: 'Newest First',
value: "-createdOn",
},
{
name: 'Oldest First',
value: "createdOn"
}
],
cssClass: 'l-inline'
}
]
}
],
actions: [
{
"key": "notebook-new-entry",
"implementation": newEntryAction,
"name": "New Notebook Entry",
"cssClass": "icon-notebook labeled",
"description": "Add a new Notebook entry",
"category": [
"view-control"
],
"depends": [
"$compile",
"$rootScope",
"dialogService",
"notificationService",
"linkService"
],
"priority": "preferred"
},
{
"key": "annotate-snapshot",
"implementation": AnnotateSnapshotAction,
"name": "Annotate Snapshot",
"cssClass": "icon-pencil labeled",
"description": "Annotate embed's snapshot",
"category": "embed",
"depends": [
"dialogService",
"dndService",
"$rootScope"
]
}
],
controllers: [
{
"key": "NewEntryController",
"implementation": NewEntryController,
"depends": ["$scope",
"$rootScope"
]
},
{
"key": "selectSnapshotController",
"implementation": SelectSnapshotController,
"depends": ["$scope",
"$rootScope"
]
}
],
controls: [
{
"key": "snapshot-select",
"template": snapSelectTemplate
},
{
"key": "embed-control",
"template": embedControlTemplate
}
],
templates: [
{
"key": "annotate-snapshot",
"template": annotationTemplate
}
],
directives: [
{
"key": "mctSnapshot",
"implementation": MCTSnapshotDirective,
"depends": [
"$rootScope",
"$document",
"exportImageService",
"dialogService",
"notificationService"
]
},
{
"key": "mctEntryDnd",
"implementation": EntryDndDirective,
"depends": [
"$rootScope",
"$compile",
"dndService",
"typeService",
"notificationService"
]
}
],
representations: [
{
"key": "draggedEntry",
"template": draggedEntryTemplate
}
]
}
});
openmct.legacyRegistry.enable('notebookV2');
openmct.objectViews.addProvider({
key: 'notebook-vue',
name: 'Notebook View',
cssClass: 'icon-notebook',
canView: function (domainObject) {
return domainObject.type === 'notebookV2';
},
view: function (domainObject) {
var controller = new NotebookController (openmct, domainObject);
return {
show: controller.show,
destroy: controller.destroy
};
}
});
};
}
return NotebookV2Plugin;
});

View File

@ -0,0 +1,294 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, 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.
*****************************************************************************/
.w-notebook {
font-size: 0.8rem;
overflow: hidden;
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
width: auto;
height: auto;
}
.notebook-view-controls.l-flex-row {
> * {
// filter and sort selects
&:not(:first-child) { margin-left: $interiorMargin; }
}
}
.l-notebook-drag-area {
padding: 10px;
font-style: italic;
cursor: pointer;
&:before { margin-right: 7px !important; }
.label {
@include ellipsize();
}
}
.frame {
.icon-notebook {
margin-right: 5px;
}
}
.overlay.l-dialog .title{
white-space: normal;
}
.w-notebook-entries {
padding-right: $interiorMarginSm;
position: relative;
overflow-x: hidden;
overflow-y: scroll;
.t-entries-list {
}
}
.l-notebook-entry {
$p: $interiorMarginSm;
box-sizing: border-box;
margin-bottom: $p;
padding: $p $interiorMargin;
.s-notebook-entry-time,
.s-notebook-entry-text,
.notebook-entry-delete {
padding-top: $p;
padding-bottom: $p;
}
.s-notebook-entry-time {
border: 1px solid transparent; // Needed to maintain vertical alignment with s-notebook-entry-text
}
.l-notebook-entry-content{
.s-notebook-entry-text {
// Contenteditable div that holds text
min-height: 24px; // Needed in Firefox when field is blank
white-space: pre-wrap;
}
.entry-embeds{
flex-wrap: wrap;
}
.snap-thumb {
cursor: pointer;
}
}
}
.l-entry-embed {
$m: $interiorMarginSm;
position: relative;
margin: $m $m 0 0;
padding: $interiorMarginSm;
&.has-snapshot {
&:before {
position: absolute;
text-shadow: rgba(black, 0.7) 0 1px 5px;
z-index: 2;
}
}
.snap-thumb {
$d: 50px;
width: $d;
height: $d;
border-radius: 5px;
overflow: hidden;
img {
height: 100%;
width: 100%;
}
}
.embed-info {
margin-left: $interiorMargin;
.embed-title {
font-weight: bold;
}
}
}
.t-contents,
.snap-annotation {
// Todo: don't write this to t-contents, add a l- class
overflow: hidden;
}
.notebook-filters {
.select {
margin-left: $interiorMargin;
}
}
.s-status-taking-snapshot,
.overlay.snapshot {
// Handle overflow-y issues with tables and html2canvas
.l-sticky-headers .l-tabular-body { overflow: auto; }
}
/********************************************* MOBILE */
body.mobile {
// Hide the start entry area, and disable ability to edit or delete an entry in mobile context
.l-notebook-drag-area {
display: none;
}
.l-notebook-entry {
pointer-events: none;
}
&.phone.portrait {
.w-notebook-entry-time-and-content {
flex-direction: column !important;
}
.s-notebook-entry-time,
.notebook-entry-delete {
padding-top: 0;
padding-bottom: 0;
}
}
}
body.phone.portrait {
.l-notebook-head.l-flex-row {
flex-direction: column !important;
> * {
&:not(:first-child) { margin-top: $interiorMargin; }
}
}
}
/********************************************* PAINTERRO OVERRIDES */
.annotation-dialog .abs.editor {
border-radius: 0;
}
#snap-annotation {
display: flex;
flex-direction: column;
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
}
#snap-annotation-wrapper,
#snap-annotation-bar {
position: relative;
top: auto; right: auto; bottom: auto; left: auto;
}
#snap-annotation-wrapper {
order: 2;
flex: 10 0 auto;
}
#snap-annotation-bar {
order: 1;
flex: 0 0 auto;
height: auto;
background-color: transparent !important;
margin-bottom: $interiorMargin;
> div,
> div > span,
.ptro-icon-btn,
.ptro-named-btn,
.ptro-color-btn,
.ptro-bordered-btn,
.ptro-tool-ctl-name,
.ptro-color-btn,
.tool-controls,
.ptro-input {
// Lot of resets for crappy CSS in Painterro
&:first-child {
margin-left: 0 !important;
}
$h: $btnToolbarH;
display: inline-block;
font-family: inherit;
font-size: auto;
height: $h !important;
margin: 0 0 0 5px;
position: relative;
width: auto !important;
line-height: $h !important;
top: auto;
right: auto;
bottom: auto;
left: auto;
vertical-align: top;
}
.ptro-tool-ctl-name {
border-radius: 0;
background: none;
top: auto;
font-family: inherit;
padding: 0;
}
.ptro-color-btn {
width: $btnToolbarH !important;
}
.ptro-icon-btn,
.ptro-named-btn {
// .s-button class is added via JS in AnnotateSnapshot.js
// TODO: redo this so that we don't need to use Zepto and JS
i {
font-size: 1.25em !important;
}
}
.tool-controls {
font-size: 0.8rem !important;
}
.ptro-info,
.ptro-btn-color-checkers-bar,
*[title="Font name"],
*[title="Stroke color"],
*[title="Stroke width"],
*[data-id="fontName"],
*[data-id="fontStrokeSize"],
*[data-id="stroke"] {
display: none;
}
}
/********************************************* NO IDEA WHAT THERE ARE APPLYING TO */
.context-available {
outline: none;
}
.menu-element.menu-view{
z-index: 999;
}
.overlay.l-dialog .abs.editor {
padding-right: 0;
}

View File

@ -0,0 +1,94 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
.l-notebook-drag-area {
border: 1px dashed rgba($colorKey, 0.7);
border-radius: $controlCr;
color: rgba($colorBodyFg, 0.7);
&:hover {
background: rgba($colorKey, 0.2);
color: $colorBodyFg;
}
&.drag-active{
border-color: $colorKey;
}
}
.s-notebook-entry {
transition: background-color 500ms ease-out;
background-color: rgba($colorBodyFg, 0.1);
border-radius: $basicCr;
&:hover {
transition: background-color 50ms ease-in;
background-color: rgba($colorBodyFg, 0.2);
}
.s-notebook-entry-time {
color: rgba($colorBodyFg, 0.5);
}
}
.l-entry-embed {
border-radius: $controlCr;
background-color: rgba($colorBodyFg, 0.1);
&.has-snapshot {
&:before {
color: $colorBodyFg;
}
}
}
.s-snapshot-datetime {
color: rgba($colorBodyFg, 0.4);
font-size: 0.8em;
}
.snap-thumb {
border: 1px solid $colorInteriorBorder;
}
.s-status-taking-snapshot,
.overlay.snapshot {
// Applied to an object view when it's in the process of being snapshotted
background: $colorBodyBg;
}
/********************************************* PAINTERRO OVERRIDES */
.ptro-wrapper {
background: rgba($colorBodyBg, 0.3) !important;
}
#snap-annotation-bar {
.tool-controls {
color: $colorBodyFg !important;
}
}
.s-button.ptro-color-active-control {
background: $colorBtnMajorBg !important;
color: $colorBtnMajorFg !important;
&:hover {
background: $colorBtnMajorBgHov !important;
color: $colorBtnMajorFgHov !important;
}
}

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -19,21 +19,12 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
$output-bourbon-deprecation-warnings: false;
@import "bourbon";
define([
'./TelemetryTableViewProvider',
'./TableConfigurationViewProvider',
'./TelemetryTableType'
], function (
TelemetryTableViewProvider,
TableConfigurationViewProvider,
TelemetryTableType
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct));
openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct));
openmct.types.addType('vue-table', TelemetryTableType());
};
};
});
@import "../../../../commonUI/general/res/sass/constants";
@import "../../../../commonUI/general/res/sass/mixins";
@import "../../../../commonUI/general/res/sass/glyphs";
@import "../../../../commonUI/themes/espresso/res/sass/constants";
@import "../../../../commonUI/themes/espresso/res/sass/mixins";
@import "notebook-thematic";

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -19,33 +19,12 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
$output-bourbon-deprecation-warnings: false;
@import "bourbon";
define([
'./LegacyObjectAPIInterceptor',
'legacyRegistry'
], function (
LegacyObjectAPIInterceptor,
legacyRegistry
) {
legacyRegistry.register('src/api/objects', {
name: 'Object API',
description: 'The public Objects API',
extensions: {
components: [
{
provides: "objectService",
type: "decorator",
priority: "mandatory",
implementation: LegacyObjectAPIInterceptor,
depends: [
"openmct",
"roots[]",
"instantiate",
"topic"
]
}
]
}
});
});
@import "../../../../commonUI/general/res/sass/constants";
@import "../../../../commonUI/general/res/sass/mixins";
@import "../../../../commonUI/general/res/sass/glyphs";
@import "../../../../commonUI/themes/snow/res/sass/constants";
@import "../../../../commonUI/themes/snow/res/sass/mixins";
@import "notebook-thematic";

View File

@ -0,0 +1,28 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2015, 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.
*****************************************************************************/
$output-bourbon-deprecation-warnings: false;
@import "bourbon";
@import "../../../../commonUI/general/res/sass/constants";
@import "../../../../commonUI/general/res/sass/mixins";
@import "../../../../commonUI/general/res/sass/mobile/constants";
@import "../../../../commonUI/general/res/sass/mobile/mixins";
@import "notebook-base";

View File

@ -0,0 +1,2 @@
<div class="snap-annotation" id="snap-annotation" ng-controller="ngModel.controller">
</div>

View File

@ -0,0 +1,51 @@
<!--
Open MCT, Copyright (c) 2009-2016, 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.
-->
<!--
This element appears in the overlay dialog when initiating a new Notebook Entry from a view's Notebook button -->
<div class='form-control'>
<ng-form name="mctControl">
<div class='fields' ng-controller="NewEntryController">
<div class="l-flex-row new-notebook-entry-embed l-entry-embed {{cssClass}}"
ng-class="{ 'has-snapshot' : snapToggle }">
<div class="holder flex-elem snap-thumb"
ng-if="snapToggle">
<img ng-src="{{snapshot.src}}" alt="{{snapshot.modified}}">
</div>
<div class="holder flex-elem embed-info">
<div class="embed-title">{{objectName}}</div>
<div class="embed-date"
ng-if="snapToggle">{{snapshot.modified| date:'yyyy-MM-dd HH:mm:ss'}}</div>
</div>
<div class="holder flex-elem annotate-new"
ng-if="snapToggle">
<a class="s-button flex-elem icon-pencil "
title="Annotate this snapshot"
ng-click="annotateSnapshot()">
<span class="title-label">Annotate</span>
</a>
</div>
</div>
</div>
</ng-form>
</div>

View File

@ -0,0 +1,29 @@
<!--
Open MCT, Copyright (c) 2014-2017, 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.
-->
<div class='form-control select' ng-controller="selectSnapshotController">
<select
ng-model="selectModel"
ng-options="opt.value as opt.name for opt in options"
ng-required="ngRequired"
name="mctControl">
</select>
</div>

View File

@ -0,0 +1,38 @@
<!--
Open MCT, Copyright (c) 2014-2017, 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.
-->
<div class="frame snap-frame frame-template t-frame-inner abs t-object-type-{{ representation.selected.key }}">
<div class="abs object-browse-bar l-flex-row">
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<mct-representation
key="'switcher'"
ng-model="representation"
mct-object="domainObject">
</mct-representation>
</div>
</div>
<div class="abs object-holder" data-entry = "{{parameters.entry}}" data-embed = "{{parameters.embed}}" mct-snapshot ng-if="representation.selected.key">
<mct-representation
key="representation.selected.key"
mct-object="representation.selected.key && domainObject">
</mct-representation>
</div>
</div>

View File

@ -0,0 +1,36 @@
<div
class="l-flex-row l-entry-embed"
v-bind:class="[embed.cssClass]">
<div
class="snap-thumb"
v-if="embed.snapshot"
v-on:click="openSnapshot">
<img v-bind:src="embed.snapshot.src">
</div>
<div class="embed-info l-flex-col">
<div class="embed-title object-header">
<a v-on:click="navigate(embed.type)">{{embed.name}}</a>
<a class='context-available' v-on:click="toggleActionMenu"></a>
</div>
<div class="hide-menu hidden">
<div class="menu-element context-menu-wrapper mobile-disable-select">
<div class="menu context-menu">
<ul>
<li v-for="(action) in actions"
v-bind:class="[action.cssClass]"
v-on:click="action.perform(embed, entry)">
{{ action.name }}
</li>
</ul>
</div>
</div>
</div>
<div class="embed-date" v-if="embed.snapshot">
{{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
<li class="l-flex-row has-local-controls l-notebook-entry s-notebook-entry"
v-on:drop="dropOnEntry(entry.id)"
v-on:dragover="dragoverOnEntry"
>
<div class="holder flex-elem l-flex-row grows w-notebook-entry-time-and-content">
<div class="holder flex-elem s-notebook-entry-time">
<span>{{formatTime(entry.createdOn, 'YYYY-MM-DD')}}</span>
<span>{{formatTime(entry.createdOn, 'HH:mm:ss')}}</span>
</div>
<div class="holder flex-elem l-flex-col grows l-notebook-entry-content">
<div class="flex-elem s-input-inline t-notebook-entry-input s-notebook-entry-text"
contenteditable="true"
ref="contenteditable"
v-on:blur="textBlur($event, entry.id)"
v-on:focus="textFocus($event, entry.id)"
v-bind:key="entry.id"
v-html="entry.text">
</div>
<div class="flex-elem entry-embeds l-flex-row">
<notebook-embed
v-for="(embed, index) in entry.embeds"
v-bind:embed="embed"
v-bind:entry="entry"
></notebook-embed>
</div>
</div>
<div class="holder flex-elem local-control local-controls-hidden notebook-entry-delete">
<a class="s-icon-button icon-trash" title="Delete Entry" v-on:click="deleteEntry"></a>
</div>
</div>
</li>

View File

@ -0,0 +1,46 @@
<div class="mct-notebook w-notebook l-flex-col">
<div class="l-notebook-head holder l-flex-row flex-elem">
<div class="c-search flex-elem holder grows">
<input class="c-search__search-input"
type="text" tabindex="10000"
v-model="entrySearch"
v-on:keyup="search($event)"/>
<a class="c-search__clear-input clear-icon icon-x-in-circle"
v-bind:class="[entrySearch ? 'show': '']"
v-on:click="entrySearch = ''; search($event)"></a>
</div>
<div class="notebook-view-controls l-flex-row flex-elem holder">
<div class="select notebook-view-controls__filter-time">
<select v-model="showTime">
<option value="0" selected="selected">Show all</option>
<option value="1">Last hour</option>
<option value="8">Last 8 hours</option>
<option value="24">Last 24 hours</option>
</select>
</div>
<div class="select notebook-view-controls__sort">
<select v-model="sortEntries">
<option value="-createdOn" selected="selected">Newest first</option>
<option value="createdOn">Oldest first</option>
</select>
</div>
</div>
</div>
<!-- drag area -->
<div class="holder flex-elem l-flex-row icon-plus labeled l-notebook-drag-area" v-on:click="newEntry($event)"
id="newEntry" mct-entry-dnd>
<span class="label">To start a new entry, click here or drag and drop any object</span>
</div>
<!-- entries -->
<div class="holder flex-elem grows w-notebook-entries t-entries-list" ng-mouseover="handleActive()">
<ul>
<notebook-entry
v-for="entry in filterBySearch(entries, entrySearch)"
v-bind:entry="entry"
></notebook-entry>
</ul>
</div>
</div>

View File

@ -0,0 +1,50 @@
<div class="abs overlay l-large-view">
<div class="abs blocker" v-on:click="close"></div>
<div class="abs outer-holder">
<a
class="close icon-x-in-circle"
v-on:click="close">
</a>
<div class="abs inner-holder l-flex-col">
<div class="t-contents flex-elem holder grows">
<div class="t-snapshot abs l-view-header">
<div class="abs object-browse-bar l-flex-row">
<div class="left flex-elem l-flex-row grows">
<div class="object-header flex-elem l-flex-row grows">
<div class="type-icon flex-elem embed-icon holder" v-bind:class="embed.cssClass"></div>
<div class="title-label flex-elem holder flex-can-shrink">{{embed.name}}</div>
</div>
</div>
</div>
</div>
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div>
<a class="s-button icon-pencil" title="Annotate">
<span class="title-label">Annotate</span>
</a>
</div>
<div class="abs object-holder t-image-holder s-image-holder">
<div
class="image-main s-image-main"
v-bind:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }">
</div>
</div>
</div>
<div
class="bottom-bar flex-elem holder"
v-on:click="close">
<a class="t-done s-button major">Done</a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,165 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/**
* Module defining viewSnapshot (Originally NewWindowAction). Created by vwoeltje on 11/18/14.
*/
define(
["painterro", "zepto"],
function (Painterro, $) {
var annotationStruct = {
title: "Annotate Snapshot",
template: "annotate-snapshot",
options: [{
name: "OK",
key: "ok",
description: "save annotation"
},
{
name: "Cancel",
key: "cancel",
description: "cancel editing"
}]
};
function AnnotateSnapshot(dialogService, dndService, $rootScope, context) {
context = context || {};
// Choose the object to be opened into a new tab
this.domainObject = context.selectedObject || context.domainObject;
this.dialogService = dialogService;
this.dndService = dndService;
this.$rootScope = $rootScope;
}
AnnotateSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId) {
var DOMAIN_OBJECT = this.domainObject;
var ROOTSCOPE = this.$rootScope;
var painterro;
var save = false;
var controller = ['$scope', '$timeout', function PainterroController($scope, $timeout) {
$(document.body).find('.l-dialog .outer-holder').addClass('annotation-dialog');
// Timeout is necessary because Painterro uses document.getElementById, and mct-include
// hasn't added the dialog to the DOM yet.
$timeout(function () {
painterro = Painterro({
id: 'snap-annotation',
activeColor: '#ff0000',
activeColorAlpha: 1.0,
activeFillColor: '#fff',
activeFillColorAlpha: 0.0,
backgroundFillColor: '#000',
backgroundFillColorAlpha: 0.0,
defaultFontSize: 16,
defaultLineWidth: 2,
defaultTool: 'ellipse',
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
translation: {
name: 'en',
strings: {
lineColor: 'Line',
fillColor: 'Fill',
lineWidth: 'Size',
textColor: 'Color',
fontSize: 'Size',
fontStyle: 'Style'
}
},
saveHandler: function (image, done) {
if (save) {
if (entryId && embedId) {
var elementPos = DOMAIN_OBJECT.model.entries.map(function (x) {
return x.createdOn;
}).indexOf(entryId);
var entryEmbeds = DOMAIN_OBJECT.model.entries[elementPos].embeds;
var embedPos = entryEmbeds.map(function (x) {
return x.id;
}).indexOf(embedId);
saveSnap(image.asBlob(), embedPos, elementPos, DOMAIN_OBJECT);
}else {
ROOTSCOPE.snapshot = {'src': image.asDataURL('image/png'),
'modified': Date.now()};
}
}
done(true);
}
}).show(snapshot);
$(document.body).find('.ptro-icon-btn').addClass('s-button');
$(document.body).find('.ptro-input').addClass('s-button');
});
}];
annotationStruct.model = {'controller': controller};
function saveNotes(param) {
if (param === 'ok') {
save = true;
}else {
save = false;
ROOTSCOPE.snapshot = "annotationCancelled";
}
painterro.save();
}
function rejectNotes() {
save = false;
ROOTSCOPE.snapshot = "annotationCancelled";
painterro.save();
}
function saveSnap(url, embedPos, entryPos, domainObject) {
var snap = false;
if (embedPos !== -1 && entryPos !== -1) {
var reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
snap = reader.result;
domainObject.useCapability('mutation', function (model) {
if (model.entries[entryPos]) {
model.entries[entryPos].embeds[embedPos].snapshot = {
'src': snap,
'type': url.type,
'size': url.size,
'modified': Date.now()
};
model.entries[entryPos].embeds[embedPos].id = Date.now();
}
});
};
}
}
this.dialogService.getUserChoice(annotationStruct)
.then(saveNotes, rejectNotes);
};
return AnnotateSnapshot;
}
);

View File

@ -0,0 +1,204 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
var SNAPSHOT_TEMPLATE = '<mct-representation key="\'draggedEntry\'"' +
'class="t-rep-frame holder"' +
'mct-object="selObj">' +
'</mct-representation>';
var NEW_TASK_FORM = {
name: "Create a Notebook Entry",
hint: "Please select one Notebook",
sections: [{
rows: [{
name: 'Entry',
key: 'entry',
control: 'textarea',
required: false,
"cssClass": "l-textarea-sm"
},
{
name: 'Embed Type',
key: 'withSnapshot',
control: 'snapshot-select',
"options": [
{
"name": "Link and Snapshot",
"value": true
},
{
"name": "Link only",
"value": false
}
]
},
{
name: 'Embed',
key: 'embedObject',
control: 'embed-control'
},
{
name: 'Save in Notebook',
key: 'saveNotebook',
control: 'locator',
validate: validateLocation
}]
}]
};
function NewEntryContextual($compile, $rootScope, dialogService, notificationService, linkService, context) {
context = context || {};
this.domainObject = context.selectedObject || context.domainObject;
this.dialogService = dialogService;
this.notificationService = notificationService;
this.linkService = linkService;
this.$rootScope = $rootScope;
this.$compile = $compile;
}
function validateLocation(newParentObj) {
return newParentObj.model.type === 'notebookV2';
}
NewEntryContextual.prototype.perform = function () {
var self = this;
var domainObj = this.domainObject;
var notification = this.notificationService;
var dialogService = this.dialogService;
var rootScope = this.$rootScope;
rootScope.newEntryText = '';
// // Create the overlay element and add it to the document's body
this.$rootScope.selObj = domainObj;
this.$rootScope.selValue = "";
var newScope = rootScope.$new();
newScope.selObj = domainObj;
newScope.selValue = "";
this.$compile(SNAPSHOT_TEMPLATE)(newScope);
this.$rootScope.$watch("snapshot", setSnapshot);
function setSnapshot(value) {
if (value === "annotationCancelled") {
rootScope.snapshot = rootScope.lastValue;
rootScope.lastValue = '';
} else if (value && value !== rootScope.lastValue) {
var overlayModel = {
title: NEW_TASK_FORM.name,
message: NEW_TASK_FORM.message,
structure: NEW_TASK_FORM,
value: {'entry': rootScope.newEntryText || ""}
};
rootScope.currentDialog = overlayModel;
dialogService.getDialogResponse(
"overlay-dialog",
overlayModel,
function () {
return overlayModel.value;
}
).then(addNewEntry);
rootScope.lastValue = value;
}
}
function addNewEntry(options) {
options.selectedModel = options.embedObject.getModel();
options.cssClass = options.embedObject.getCapability('type').typeDef.cssClass;
if (self.$rootScope.snapshot) {
options.snapshot = self.$rootScope.snapshot;
self.$rootScope.snapshot = undefined;
}else {
options.snapshot = undefined;
}
if (!options.withSnapshot) {
options.snapshot = '';
}
createSnap(options);
}
function createSnap(options) {
options.saveNotebook.useCapability('mutation', function (model) {
var entries = model.entries;
var lastEntry = entries[entries.length - 1];
var date = Date.now();
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
model.entries.push({
'id': date,
'createdOn': date,
'text': options.entry,
'embeds': [{'type': options.embedObject.getId(),
'id': '' + date,
'cssClass': options.cssClass,
'name': options.selectedModel.name,
'snapshot': options.snapshot
}]
});
}else {
model.entries[entries.length - 1] = {
'id': date,
'createdOn': date,
'text': options.entry,
'embeds': [{'type': options.embedObject.getId(),
'id': '' + date,
'cssClass': options.cssClass,
'name': options.selectedModel.name,
'snapshot': options.snapshot
}]
};
}
});
notification.info({
title: "Notebook Entry created"
});
}
};
NewEntryContextual.appliesTo = function (context) {
var domainObject = context.domainObject;
if (domainObject) {
if (domainObject.getModel().type === 'notebookV2') {
// do not allow in context of a notebook
return false;
} else if (domainObject.getModel().type.includes('imagery')) {
// do not allow in the context of an object with imagery
// (because of cross domain issue with snapshot)
return false;
}
}
return true;
};
return NewEntryContextual;
}
);

View File

@ -0,0 +1,130 @@
/*****************************************************************************
* 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(
['zepto'],
function ($) {
function SnapshotAction (exportImageService, dialogService, context) {
this.exportImageService = exportImageService;
this.dialogService = dialogService;
this.domainObject = context.domainObject;
}
SnapshotAction.prototype.perform = function () {
var elementToSnapshot =
$(document.body).find(".overlay .object-holder")[0] ||
$(document.body).find("[key='representation.selected.key']")[0];
$(elementToSnapshot).addClass("s-status-taking-snapshot");
this.exportImageService.exportPNGtoSRC(elementToSnapshot).then(function (blob) {
$(elementToSnapshot).removeClass("s-status-taking-snapshot");
if (blob) {
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function () {
this.saveSnapshot(reader.result, blob.type, blob.size);
}.bind(this);
}
}.bind(this));
};
SnapshotAction.prototype.saveSnapshot = function (imageURL, imageType, imageSize) {
var taskForm = this.generateTaskForm(),
domainObject = this.domainObject,
domainObjectId = domainObject.getId(),
cssClass = domainObject.getCapability('type').typeDef.cssClass,
name = domainObject.model.name;
this.dialogService.getDialogResponse(
'overlay-dialog',
taskForm,
function() {
return taskForm.value;
}
).then(function (options) {
var snapshotObject = {
src: imageURL,
type: imageType,
size: imageSize
};
options.notebook.useCapability('mutation', function (model) {
var date = Date.now();
model.entries.push({
id: 'entry-' + date,
createdOn: date,
text: options.entry,
embeds: [{
name: name,
cssClass: cssClass,
type: domainObjectId,
id: 'embed-' + date,
createdOn: date,
snapshot: snapshotObject
}]
});
});
});
};
SnapshotAction.prototype.generateTaskForm = function () {
var taskForm = {
name: "Create a Notebook Entry",
hint: "Please select a Notebook",
sections: [{
rows: [{
name: 'Entry',
key: 'entry',
control: 'textarea',
required: false,
"cssClass": "l-textarea-sm"
},
{
name: 'Save in Notebook',
key: 'notebook',
control: 'locator',
validate: validateLocation
}]
}]
};
var overlayModel = {
title: taskForm.name,
message: 'AHAHAH',
structure: taskForm,
value: {'entry': ""}
};
function validateLocation (newParentObj) {
return newParentObj.model.type === 'notebookV2';
}
return overlayModel;
};
return SnapshotAction;
}
);

View File

@ -0,0 +1,198 @@
/*****************************************************************************
* 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([
'moment',
'zepto',
'../utils/SnapshotOverlay',
],
function (
Moment,
$,
SnapshotOverlay
) {
function EmbedController (openmct, domainObject) {
this.openmct = openmct;
this.domainObject = domainObject;
this.objectService = openmct.$injector.get('objectService');
this.navigationService = openmct.$injector.get('navigationService');
this.popupService = openmct.$injector.get('popupService');
this.agentService = openmct.$injector.get('agentService');
this.dialogService = openmct.$injector.get('dialogService');
this.navigate = this.navigate.bind(this);
this.exposedData = this.exposedData.bind(this);
this.exposedMethods = this.exposedMethods.bind(this);
this.toggleActionMenu = this.toggleActionMenu.bind(this);
}
EmbedController.prototype.navigate = function (embedType) {
this.objectService.getObjects([embedType]).then(function (objects) {
this.navigationService.setNavigation(objects[embedType]);
}.bind(this));
};
EmbedController.prototype.openSnapshot = function () {
if (!this.snapshotOverlay) {
this.snapShotOverlay = new SnapshotOverlay(this.embed, this.formatTime);
} else {
this.snapShotOverlay = undefined;
}
};
EmbedController.prototype.formatTime = function (unixTime, timeFormat) {
return Moment(unixTime).format(timeFormat);
};
EmbedController.prototype.findInArray = function (array, id) {
var foundId = -1;
array.forEach(function (element, index) {
if (element.id === id) {
foundId = index;
return;
}
});
return foundId;
};
EmbedController.prototype.actionToMenuDecorator = function (action) {
return {
name: action.getMetadata().name,
cssClass: action.getMetadata().cssClass,
perform: action.perform
};
};
EmbedController.prototype.populateActionMenu = function (objectService, actionService) {
return function () {
var self = this;
objectService.getObjects([self.embed.type]).then(function (resp) {
var domainObject = resp[self.embed.type],
previewAction = actionService.getActions({key: 'mct-preview-action', domainObject: domainObject})[0];
self.actions.push(self.actionToMenuDecorator(previewAction));
});
};
};
EmbedController.prototype.removeEmbedAction = function () {
var self = this;
return {
name: 'Remove Embed',
cssClass: 'icon-trash',
perform: function (embed, entry) {
var entryPosition = self.findInArray(self.domainObject.entries, entry.id),
embedPosition = self.findInArray(entry.embeds, embed.id);
var warningDialog = self.dialogService.showBlockingMessage({
severity: "error",
title: "This action will permanently delete this embed. Do you wish to continue?",
options: [{
label: "OK",
callback: function () {
entry.embeds.splice(embedPosition, 1);
var dirString = 'entries[' + entryPosition + '].embeds';
self.openmct.objects.mutate(self.domainObject, dirString, entry.embeds);
warningDialog.dismiss();
}
},{
label: "Cancel",
callback: function () {
warningDialog.dismiss();
}
}]
});
}
};
};
EmbedController.prototype.toggleActionMenu = function (event) {
event.preventDefault();
var body = $(document.body),
container = $(event.target.parentElement.parentElement),
initiatingEvent = this.agentService.isMobile() ?
'touchstart' : 'mousedown',
menu = container.find('.menu-element'),
dismissExistingMenu;
// Remove the context menu
function dismiss() {
container.find('.hide-menu').append(menu);
body.off(initiatingEvent, dismiss);
menu.off(initiatingEvent, menuClickHandler);
dismissExistingMenu = undefined;
}
function menuClickHandler(e) {
e.stopPropagation();
window.setTimeout(dismiss, 300);
}
// Dismiss any menu which was already showing
if (dismissExistingMenu) {
dismissExistingMenu();
}
// ...and record the presence of this menu.
dismissExistingMenu = dismiss;
this.popupService.display(menu, [event.pageX,event.pageY], {
marginX: 0,
marginY: -50
});
// Stop propagation so that clicks or touches on the menu do not close the menu
menu.on(initiatingEvent, menuClickHandler);
body.on(initiatingEvent, dismiss);
};
EmbedController.prototype.exposedData = function () {
return {
actions: [this.removeEmbedAction()],
showActionMenu: false
};
};
EmbedController.prototype.exposedMethods = function () {
var self = this;
return {
navigate: self.navigate,
openSnapshot: self.openSnapshot,
formatTime: self.formatTime,
toggleActionMenu: self.toggleActionMenu,
actionToMenuDecorator: self.actionToMenuDecorator
};
};
return EmbedController;
});

View File

@ -0,0 +1,150 @@
/*****************************************************************************
* 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([
'moment'
],
function (
Moment
) {
function EntryController (openmct, domainObject) {
this.openmct = openmct;
this.domainObject = domainObject;
this.dndService = this.openmct.$injector.get('dndService');
this.dialogService = this.openmct.$injector.get('dialogService');
this.currentEntryValue = '';
this.exposedMethods = this.exposedMethods.bind(this);
this.exposedData = this.exposedData.bind(this);
}
EntryController.prototype.entryPosById = function (entryId) {
var foundId = -1;
this.domainObject.entries.forEach(function (element, index) {
if (element.id === entryId) {
foundId = index;
return;
}
});
return foundId;
};
EntryController.prototype.textFocus = function ($event) {
if ($event.target) {
this.currentEntryValue = $event.target.innerText;
} else {
$event.target.innerText = '';
}
};
EntryController.prototype.textBlur = function ($event, entryId) {
if ($event.target) {
var entryPos = this.entryPosById(entryId);
if (this.currentEntryValue !== $event.target.innerText) {
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].text', $event.target.innerText);
}
}
};
EntryController.prototype.formatTime = function (unixTime, timeFormat) {
return Moment(unixTime).format(timeFormat);
};
EntryController.prototype.deleteEntry = function () {
var entryPos = this.entryPosById(this.entry.id),
domainObject = this.domainObject,
openmct = this.openmct;
if (entryPos !== -1) {
var errorDialog = this.dialogService.showBlockingMessage({
severity: "error",
title: "This action will permanently delete this Notebook entry. Do you wish to continue?",
options: [{
label: "OK",
callback: function () {
domainObject.entries.splice(entryPos, 1);
openmct.objects.mutate(domainObject, 'entries', domainObject.entries);
errorDialog.dismiss();
}
},{
label: "Cancel",
callback: function () {
errorDialog.dismiss();
}
}]
});
}
};
EntryController.prototype.dropOnEntry = function (entryId) {
var selectedObject = this.dndService.getData('mct-domain-object'),
selectedObjectId = selectedObject.getId(),
selectedModel = selectedObject.getModel(),
cssClass = selectedObject.getCapability('type').typeDef.cssClass,
entryPos = this.entryPosById(entryId),
currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
newEmbed = {
type: selectedObjectId,
id: '' + Date.now(),
cssClass: cssClass,
name: selectedModel.name,
snapshot: ''
};
currentEntryEmbeds.push(newEmbed);
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
};
EntryController.prototype.dragoverOnEntry = function () {
};
EntryController.prototype.exposedData = function () {
return {
openmct: this.openmct,
domainObject: this.domainObject,
dndService: this.dndService,
dialogService: this.dialogService,
currentEntryValue: this.currentEntryValue
};
};
EntryController.prototype.exposedMethods = function () {
return {
entryPosById: this.entryPosById,
textFocus: this.textFocus,
textBlur: this.textBlur,
formatTime: this.formatTime,
deleteEntry: this.deleteEntry,
dropOnEntry: this.dropOnEntry,
dragoverOnEntry: this.dragoverOnEntry
};
};
return EntryController;
});

View File

@ -0,0 +1,65 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/**
* Module defining NewEntryController. */
define(
[],
function () {
function NewEntryController($scope,$rootScope) {
$scope.snapshot = undefined;
$scope.snapToggle = true;
$scope.entryText = '';
var annotateAction = $rootScope.selObj.getCapability('action').getActions({key: 'annotate-snapshot'})[0];
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = $rootScope.selObj;
$scope.objectName = $rootScope.selObj.getModel().name;
$scope.cssClass = $rootScope.selObj.getCapability('type').typeDef.cssClass;
$scope.annotateSnapshot = function ($event) {
if ($rootScope.currentDialog.value) {
$rootScope.newEntryText = $scope.$parent.$parent.ngModel.entry;
$rootScope.currentDialog.cancel();
annotateAction.perform($event, $rootScope.snapshot.src);
$rootScope.currentDialog = undefined;
}
};
function updateSnapshot(img) {
$scope.snapshot = img;
}
// Update set of actions whenever the action capability
// changes or becomes available.
$rootScope.$watch("snapshot", updateSnapshot);
$rootScope.$watch("selValue", toggleEmbed);
function toggleEmbed(value) {
$scope.snapToggle = value;
}
}
return NewEntryController;
}
);

View File

@ -0,0 +1,174 @@
/*****************************************************************************
* 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([
'vue',
'./EntryController',
'./EmbedController',
'text!../../res/templates/notebook.html',
'text!../../res/templates/entry.html',
'text!../../res/templates/embed.html'
],
function (
Vue,
EntryController,
EmbedController,
NotebookTemplate,
EntryTemplate,
EmbedTemplate
) {
function NotebookController(openmct, domainObject) {
this.openmct = openmct;
this.domainObject = domainObject;
this.entrySearch = '';
this.objectService = openmct.$injector.get('objectService');
this.actionService = openmct.$injector.get('actionService');
this.show = this.show.bind(this);
this.destroy = this.destroy.bind(this);
this.newEntry = this.newEntry.bind(this);
this.entryPosById = this.entryPosById.bind(this);
}
NotebookController.prototype.initializeVue = function (container){
var self = this,
entryController = new EntryController(this.openmct, this.domainObject),
embedController = new EmbedController(this.openmct, this.domainObject);
this.container = container;
var notebookEmbed = {
props:['embed', 'entry'],
template: EmbedTemplate,
data: embedController.exposedData,
methods: embedController.exposedMethods(),
beforeMount: embedController.populateActionMenu(self.objectService, self.actionService)
};
var entryComponent = {
props:['entry'],
template: EntryTemplate,
components: {
'notebook-embed': notebookEmbed
},
data: entryController.exposedData,
methods: entryController.exposedMethods(),
mounted: self.focusOnEntry
};
var notebookVue = Vue.extend({
template: NotebookTemplate,
components: {
'notebook-entry': entryComponent
},
data: function () {
return {
entrySearch: self.entrySearch,
showTime: '0',
sortEntries: '-createdOn',
entries: self.domainObject.entries,
currentEntryValue: ''
};
},
methods: {
search: function (event) {
if (event.target.value) {
this.entrySearch = event.target.value;
}
},
newEntry: self.newEntry,
filterBySearch: self.filterBySearch
}
});
this.NotebookVue = new notebookVue();
container.appendChild(this.NotebookVue.$mount().$el);
};
NotebookController.prototype.newEntry = function (event) {
var entries = this.domainObject.entries,
lastEntryIndex = entries.length - 1,
lastEntry = entries[lastEntryIndex],
date = Date.now();
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds.length) {
var createdEntry = {'id': 'entry-' + date, 'createdOn': date, 'embeds':[]};
entries.push(createdEntry);
this.openmct.objects.mutate(this.domainObject, 'entries', entries);
} else {
lastEntry.createdOn = date;
this.openmct.objects.mutate(this.domainObject, 'entries[entries.length-1]', lastEntry);
this.focusOnEntry.bind(this.NotebookVue.$children[lastEntryIndex])();
}
this.entrySearch = '';
};
NotebookController.prototype.entryPosById = function (entryId) {
var foundId = -1;
this.domainObject.entries.forEach(function (element, index) {
if (element.id === entryId) {
foundId = index;
return;
}
});
return foundId;
};
NotebookController.prototype.focusOnEntry = function () {
if (!this.entry.text) {
this.$refs.contenteditable.focus();
}
};
NotebookController.prototype.filterBySearch = function (entryArray, filterString) {
if (filterString) {
var lowerCaseFilterString = filterString.toLowerCase();
return entryArray.filter(function (entry) {
if (entry.text) {
return entry.text.toLowerCase().includes(lowerCaseFilterString);
} else {
return false;
}
});
} else {
return entryArray;
}
};
NotebookController.prototype.show = function (container) {
this.initializeVue(container);
};
NotebookController.prototype.destroy = function (container) {
this.NotebookVue.$destroy(true);
};
return NotebookController;
});

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -20,18 +20,25 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(function () {
function TelemetryTableType() {
return {
name: 'Vue Telemetry Table',
description: 'Display telemetry values for the current time bounds in tabular form. Supports filtering and sorting.',
creatable: true,
cssClass: 'icon-tabular-realtime',
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {};
/**
* Module defining SelectSnapshotController. */
define(
[],
function () {
function SelectSnapshotController($scope,$rootScope) {
$scope.selectModel = true;
function selectprint(value) {
$rootScope.selValue = value;
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = value;
}
$scope.$watch("selectModel", selectprint);
}
return SelectSnapshotController;
}
return TelemetryTableType;
});
);

View File

@ -0,0 +1,126 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, 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(['zepto'], function ($) {
function EntryDnd($rootScope,$compile,dndService,typeService,notificationService) {
function link($scope, $element) {
function drop(e) {
var selectedObject = dndService.getData('mct-domain-object');
var selectedModel = selectedObject.getModel();
var cssClass = selectedObject.getCapability('type').typeDef.cssClass;
var entryId = -1;
var embedId = -1;
$scope.clearSearch();
if ($element[0].id === 'newEntry') {
entryId = $scope.domainObject.model.entries.length;
embedId = 0;
var lastEntry = $scope.domainObject.model.entries[entryId - 1];
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries.push({'createdOn': +Date.now(),
'id': +Date.now(),
'embeds': [{'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
}]
});
});
}else {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entryId - 1] =
{'createdOn': +Date.now(),
'embeds': [{'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
}]
};
});
}
$scope.scrollToTop();
notificationService.info({
title: "Notebook Entry created"
});
}else {
entryId = $scope.findEntryPositionById(Number($element[0].id.replace('entry_', '')));
if (!$scope.domainObject.model.entries[entryId].embeds) {
$scope.domainObject.model.entries[entryId].embeds = [];
}
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entryId].embeds.push({'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
});
});
embedId = $scope.domainObject.model.entries[entryId].embeds.length - 1;
if (selectedObject) {
e.preventDefault();
}
}
if ($(e.currentTarget).hasClass('drag-active')) {
$(e.currentTarget).removeClass('drag-active');
}
}
function dragover(e) {
if (!$(e.currentTarget).hasClass('drag-active')) {
$(e.currentTarget).addClass('drag-active');
}
}
// Listen for the drop itself
$element.on('dragover', dragover);
$element.on('drop', drop);
$scope.$on('$destroy', function () {
$element.off('dragover', dragover);
$element.off('drop', drop);
});
}
return {
restrict: 'A',
link: link
};
}
return EntryDnd;
});

View File

@ -0,0 +1,86 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, 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(['zepto'], function ($) {
function MCTSnapshot($rootScope, $document, exportImageService, dialogService, notificationService) {
var document = $document[0];
function link($scope, $element, $attrs) {
var objectElement = $(document.body).find(".overlay .object-holder")[0] || $(document.body).find("[key='representation.selected.key']")[0],
takeSnapshot,
makeImg,
saveImg;
$(objectElement).addClass("s-status-taking-snapshot");
saveImg = function (url, entryId, embedId) {
$scope.$parent.$parent.$parent.saveSnap(url, embedId, entryId);
};
makeImg = function (el) {
var scope = $scope;
exportImageService.exportPNGtoSRC(el).then(function (img) {
$(objectElement).removeClass("s-status-taking-snapshot");
if (img) {
if ($element[0].dataset.entry && $element[0].dataset.embed) {
saveImg(img, +$element[0].dataset.entry, +$element[0].dataset.embed);
} else {
var reader = new window.FileReader();
reader.readAsDataURL(img);
reader.onloadend = function () {
$($element[0]).attr("data-snapshot", reader.result);
$rootScope.snapshot = {
'src': reader.result,
'type': img.type,
'size': img.size,
'modified': Date.now()
};
scope.$destroy();
};
}
}
});
};
takeSnapshot = function () {
makeImg(objectElement);
};
takeSnapshot();
$scope.$on('$destroy', function () {
$element.remove();
});
}
return {
restrict: 'A',
link: link
};
}
return MCTSnapshot;
});

View File

@ -20,40 +20,48 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Controller for the bottombar template. Exposes
* available indicators (of extension category "indicators")
* @memberof platform/commonUI/general
* @constructor
*/
function BottomBarController(indicators) {
// Utility function used to make indicators presentable
// for display.
function present(Indicator) {
define([
'vue',
'text!../../res/templates/viewSnapshot.html'
], function (
Vue,
snapshotOverlayTemplate
) {
function SnapshotOverlay (embedObject, formatTime) {
this.embedObject = embedObject;
this.snapshotOverlayVue = new Vue({
template: snapshotOverlayTemplate,
data: function () {
return {
template: Indicator.template || "indicator",
ngModel: typeof Indicator === 'function' ?
new Indicator() : Indicator
embed: embedObject
};
},
methods: {
close: this.close.bind(this),
formatTime: formatTime
}
this.indicators = indicators.map(present);
}
/**
* Get all indicators to display.
* @returns {Indicator[]} all indicators
* to display in the bottom bar.
* @memberof platform/commonUI/general.BottomBarController#
*/
BottomBarController.prototype.getIndicators = function () {
return this.indicators;
};
return BottomBarController;
});
this.open();
}
);
SnapshotOverlay.prototype.open = function () {
this.overlay = document.createElement('div');
this.overlay.id = 'snapshot';
document.body.appendChild(this.overlay);
this.overlay.appendChild(this.snapshotOverlayVue.$mount().$el);
};
SnapshotOverlay.prototype.close = function (event) {
event.stopPropagation();
this.snapshotOverlayVue.$destroy();
this.overlay.parentNode.removeChild(this.overlay);
};
return SnapshotOverlay;
});

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -21,8 +21,8 @@
*****************************************************************************/
define(
[],
function () {
['zepto'],
function ($) {
// Set of connection states; changing among these states will be
// reflected in the indicator's appearance.
@ -30,68 +30,78 @@ define(
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
// PENDING: Still trying to connect, and haven't failed yet.
var CONNECTED = {
glyphClass: "ok"
statusClass: "s-status-ok"
},
PENDING = {
glyphClass: 'caution'
statusClass: "s-status-warning-lo"
},
DISCONNECTED = {
glyphClass: "err"
statusClass: "s-status-warning-hi"
};
function URLIndicator($http, $interval) {
var self = this;
this.cssClass = this.options.cssClass ? this.options.cssClass : "icon-database";
this.URLpath = this.options.url;
this.label = this.options.label ? this.options.label : this.options.url;
this.interval = this.options.interval || 10000;
this.state = PENDING;
function URLIndicator(options, simpleIndicator) {
this.bindMethods();
this.count = 0;
function handleError(e) {
self.state = DISCONNECTED;
}
function handleResponse() {
self.state = CONNECTED;
}
function updateIndicator() {
$http.get(self.URLpath).then(handleResponse, handleError);
}
updateIndicator();
$interval(updateIndicator, self.interval, 0, false);
this.indicator = simpleIndicator;
this.setDefaultsFromOptions(options);
this.setIndicatorToState(PENDING);
this.fetchUrl();
setInterval(this.fetchUrl, this.interval);
}
URLIndicator.prototype.getCssClass = function () {
return this.cssClass;
};
URLIndicator.prototype.getGlyphClass = function () {
return this.state.glyphClass;
};
URLIndicator.prototype.getText = function () {
switch (this.state) {
URLIndicator.prototype.setIndicatorToState = function (state) {
switch (state) {
case CONNECTED: {
return this.label + " is connected";
this.indicator.text(this.label + " is connected");
this.indicator.description(this.label + " is online, checking status every " + this.interval + " milliseconds.");
break;
}
case PENDING: {
return "Checking status of " + this.label + " please stand by...";
this.indicator.text("Checking status of " + this.label + " please stand by...");
this.indicator.description("Checking status of " + this.label + " please stand by...");
break;
}
case DISCONNECTED: {
return this.label + " is offline";
this.indicator.text(this.label + " is offline");
this.indicator.description(this.label + " is offline, checking status every " + this.interval + " milliseconds");
break;
}
}
this.indicator.statusClass(state.statusClass);
};
URLIndicator.prototype.getDescription = function () {
switch (this.state) {
case CONNECTED: {
return this.label + " is online, checking status every " +
this.interval + " milliseconds.";
}
case PENDING: {
return "Checking status of " + this.label + " please stand by...";
}
case DISCONNECTED: {
return this.label + " is offline, checking status every " +
this.interval + " milliseconds";
}
}
URLIndicator.prototype.fetchUrl = function () {
$.ajax({
type: 'GET',
url: this.URLpath,
success: this.handleSuccess,
error: this.handleError
});
};
URLIndicator.prototype.handleError = function (e) {
this.setIndicatorToState(DISCONNECTED);
};
URLIndicator.prototype.handleSuccess = function () {
this.setIndicatorToState(CONNECTED);
};
URLIndicator.prototype.setDefaultsFromOptions = function (options) {
this.URLpath = options.url;
this.label = options.label || options.url;
this.interval = options.interval || 10000;
this.indicator.iconClass(options.iconClass || 'icon-chain-links');
};
URLIndicator.prototype.bindMethods = function () {
this.fetchUrl = this.fetchUrl.bind(this);
this.handleSuccess = this.handleSuccess.bind(this);
this.handleError = this.handleError.bind(this);
this.setIndicatorToState = this.setIndicatorToState.bind(this);
};
return URLIndicator;
});

View File

@ -1,20 +1,35 @@
define(
[
'./URLIndicator'
],
function URLIndicatorPlugin(URLIndicator) {
return function (opts) {
// Wrap the plugin in a function so we can apply the arguments.
function URLIndicatorWrapper() {
this.options = opts;
URLIndicator.apply(this, arguments);
}
URLIndicatorWrapper.prototype = Object.create(URLIndicator.prototype);
return function install(openmct) {
openmct.legacyExtension('indicators', {
"implementation": URLIndicatorWrapper,
"depends": ["$http", "$interval"]
});
/*****************************************************************************
* 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(['./URLIndicator'],
function URLIndicatorPlugin(URLIndicator) {
return function (opts) {
return function install(openmct) {
var simpleIndicator = openmct.indicators.simpleIndicator();
var urlIndicator = new URLIndicator(opts, simpleIndicator);
openmct.indicators.add(simpleIndicator);
return urlIndicator;
};
};
};
});
}
);

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -21,137 +21,115 @@
*****************************************************************************/
define(
["./URLIndicator"],
function (URLIndicator) {
[
"./URLIndicator",
"./URLIndicatorPlugin",
"../../MCT",
"zepto"
],
function (
URLIndicator,
URLIndicatorPlugin,
MCT,
$
) {
var defaultAjaxFunction = $.ajax;
describe("The URLIndicator", function () {
var mockHttp,
mockInterval,
mockPromise,
opts,
Indicator,
indicatorWrapper;
var openmct;
var indicatorElement;
var pluginOptions;
var ajaxOptions;
var urlIndicator;
beforeEach(function () {
mockHttp = jasmine.createSpyObj("$http", ["get"]);
mockInterval = jasmine.createSpy("$interval");
mockPromise = jasmine.createSpyObj("promise", ["then"]);
opts = {
url: "http://localhost:8080",
interval: 1337 //some number
};
mockHttp.get.and.returnValue(mockPromise);
Indicator = function () {
this.options = opts;
URLIndicator.call(this, mockHttp, mockInterval);
};
Indicator.prototype = Object.create(URLIndicator.prototype);
indicatorWrapper = new Indicator();
});
it("polls for changes", function () {
expect(mockInterval).toHaveBeenCalledWith(
jasmine.any(Function),
opts.interval,
0,
false
);
jasmine.clock().install();
openmct = new MCT();
spyOn(openmct.indicators, 'add');
spyOn($, 'ajax');
$.ajax.and.callFake(function (options) {
ajaxOptions = options;
});
});
it("has a database cssClass as default", function () {
expect(indicatorWrapper.getCssClass()).toEqual("icon-database");
afterEach(function () {
$.ajax = defaultAjaxFunction;
jasmine.clock().uninstall();
});
it("consults the url with the path supplied", function () {
expect(mockHttp.get).toHaveBeenCalledWith(opts.url);
describe("on initialization", function () {
describe("with default options", function () {
beforeEach(function () {
pluginOptions = {
url: "someURL"
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
it("has a default icon class if none supplied", function () {
expect(indicatorElement.classList.contains('icon-chain-links')).toBe(true);
});
it("defaults to the URL if no label supplied", function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.url) >= 0).toBe(true);
});
});
describe("with custom options", function () {
beforeEach(function () {
pluginOptions = {
url: "customURL",
interval: 1814,
iconClass: "iconClass-checked",
label: "custom label"
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
it("uses the custom iconClass", function () {
expect(indicatorElement.classList.contains('iconClass-checked')).toBe(true);
});
it("uses custom interval", function () {
expect($.ajax.calls.count()).toEqual(1);
jasmine.clock().tick(1);
expect($.ajax.calls.count()).toEqual(1);
jasmine.clock().tick(pluginOptions.interval + 1);
expect($.ajax.calls.count()).toEqual(2);
});
it("uses custom label if supplied in initialization", function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.label) >= 0).toBe(true);
});
});
});
it("changes when the database connection is nominal", function () {
var initialText = indicatorWrapper.getText(),
initialDescrption = indicatorWrapper.getDescription(),
initialGlyphClass = indicatorWrapper.getGlyphClass();
describe("when running", function () {
beforeEach(function () {
pluginOptions = {
url: "someURL",
interval: 100
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
// Nominal just means getting back an object, without
// an error field.
mockPromise.then.calls.mostRecent().args[0]({ data: {} });
it("requests the provided URL", function () {
jasmine.clock().tick(pluginOptions.interval + 1);
expect(ajaxOptions.url).toEqual(pluginOptions.url);
});
// Verify that these values changed;
// don't test for specific text.
expect(indicatorWrapper.getText()).not.toEqual(initialText);
expect(indicatorWrapper.getGlyphClass()).not.toEqual(initialGlyphClass);
expect(indicatorWrapper.getDescription()).not.toEqual(initialDescrption);
it("indicates success if connection is nominal", function () {
jasmine.clock().tick(pluginOptions.interval + 1);
ajaxOptions.success();
expect(indicatorElement.classList.contains('s-status-ok')).toBe(true);
});
// Do check for specific class
expect(indicatorWrapper.getGlyphClass()).toEqual("ok");
});
it("changes when the server cannot be reached", function () {
var initialText = indicatorWrapper.getText(),
initialDescrption = indicatorWrapper.getDescription(),
initialGlyphClass = indicatorWrapper.getGlyphClass();
// Nominal just means getting back an object, without
// an error field.
mockPromise.then.calls.mostRecent().args[1]({ data: {} });
// Verify that these values changed;
// don't test for specific text.
expect(indicatorWrapper.getText()).not.toEqual(initialText);
expect(indicatorWrapper.getGlyphClass()).not.toEqual(initialGlyphClass);
expect(indicatorWrapper.getDescription()).not.toEqual(initialDescrption);
// Do check for specific class
expect(indicatorWrapper.getGlyphClass()).toEqual("err");
});
it("has a customized cssClass if supplied in initialization", function () {
opts = {
url: "http://localhost:8080",
cssClass: "cssClass-checked",
interval: 10000
};
indicatorWrapper = new Indicator();
expect(indicatorWrapper.getCssClass()).toEqual("cssClass-checked");
});
it("has a customized interval if supplied in initialization", function () {
opts = {
url: "http://localhost:8080",
interval: 1814
};
indicatorWrapper = new Indicator();
expect(mockInterval).toHaveBeenCalledWith(
jasmine.any(Function),
1814,
0,
false
);
});
it("has a custom label if supplied in initialization", function () {
opts = {
url: "http://localhost:8080",
label: "Localhost"
};
indicatorWrapper = new Indicator();
expect(indicatorWrapper.getText()).toEqual("Checking status of Localhost please stand by...");
});
it("has a default label if not supplied in initialization", function () {
opts = {
url: "http://localhost:8080"
};
indicatorWrapper = new Indicator();
expect(indicatorWrapper.getText()).toEqual(
"Checking status of http://localhost:8080 please stand by..."
);
});
it("has a default interval if not supplied in initialization", function () {
opts = {
url: "http://localhost:8080"
};
indicatorWrapper = new Indicator();
expect(mockInterval).toHaveBeenCalledWith(
jasmine.any(Function),
10000,
0,
false
);
it("indicates an error when the server cannot be reached", function () {
jasmine.clock().tick(pluginOptions.interval + 1);
ajaxOptions.error();
expect(indicatorElement.classList.contains('s-status-warning-hi')).toBe(true);
});
});
});
}

View File

@ -33,7 +33,8 @@ define([
'./URLIndicatorPlugin/URLIndicatorPlugin',
'./telemetryMean/plugin',
'./plot/plugin',
'./staticRootPlugin/plugin'
'./staticRootPlugin/plugin',
'./NotebookV2/plugin'
], function (
_,
UTCTimeSystem,
@ -47,7 +48,8 @@ define([
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
StaticRootPlugin
StaticRootPlugin,
NotebookV2
) {
var bundleMap = {
CouchDB: 'platform/persistence/couch',
@ -139,7 +141,8 @@ define([
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicatorPlugin = URLIndicatorPlugin;
plugins.URLIndicator = URLIndicatorPlugin;
plugins.NotebookV2 = NotebookV2;
return plugins;
});

View File

@ -1,102 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'lodash',
'vue',
'text!./table-configuration.html',
'./TelemetryTableConfiguration'
],function (
_,
Vue,
TableConfigurationTemplate,
TelemetryTableConfiguration
) {
return function TableConfigurationComponent(domainObject, openmct) {
const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
let unlisteners = [];
unlisteners.push(openmct.objects.observe(domainObject, '*', (newDomainObject) => {
domainObject = newDomainObject;
}));
function defaultConfiguration(domainObject) {
let configuration = domainObject.configuration;
configuration.table = configuration.table || {
columns: {}
};
return configuration;
}
return new Vue({
template: TableConfigurationTemplate,
data: function () {
return {
headers: {},
configuration: defaultConfiguration(domainObject)
}
},
methods: {
updateHeaders: function (headers) {
this.headers = headers;
},
toggleColumn: function (key) {
let isVisible = this.configuration.table.columns[key];
if (isVisible === undefined) {
isVisible = true;
}
this.configuration.table.columns[key] = !isVisible;
openmct.objects.mutate(domainObject, "configuration", this.configuration);
},
addObject: function (domainObject) {
tableConfiguration.addColumnsForObject(domainObject, true);
this.updateHeaders(tableConfiguration.getHeaders());
},
removeObject: function (objectIdentifier) {
tableConfiguration.removeColumnsForObject(objectIdentifier, true);
this.updateHeaders(tableConfiguration.getHeaders());
}
},
mounted: function () {
let compositionCollection = openmct.composition.get(domainObject);
compositionCollection.load()
.then((composition) => {
tableConfiguration.addColumnsForAllObjects(composition);
this.updateHeaders(tableConfiguration.getHeaders());
compositionCollection.on('add', this.addObject);
unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
compositionCollection.on('remove', this.removeObject);
unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
});
},
destroyed: function () {
tableConfiguration.destroy();
unlisteners.forEach((unlisten) => unlisten());
}
});
}
});

View File

@ -1,85 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'../../api/objects/object-utils',
'./TableConfigurationComponent'
], function (
objectUtils,
TableConfigurationComponent
) {
function TableConfigurationViewProvider(openmct) {
let instantiateService;
function isBeingEdited(object) {
let oldStyleObject = getOldStyleObject(object);
return oldStyleObject.hasCapability('editor') &&
oldStyleObject.getCapability('editor').isEditContextRoot();
}
function getOldStyleObject(object) {
let oldFormatModel = objectUtils.toOldFormat(object);
let oldFormatId = objectUtils.makeKeyString(object.identifier);
return instantiate(oldFormatModel, oldFormatId);
}
function instantiate(model, id) {
if (!instantiateService) {
instantiateService = openmct.$injector.get('instantiate');
}
return instantiateService(model, id);
}
return {
key: 'table-configuration',
name: 'Telemetry Table Configuration',
canView: function (selection) {
let object = selection[0].context.item;
return selection.length > 0 &&
object.type === 'vue-table' &&
isBeingEdited(object);
},
view: function (selection) {
let component;
let domainObject = selection[0].context.item;
return {
show: function (element) {
component = TableConfigurationComponent(domainObject, openmct);
element.appendChild(component.$mount().$el);
},
destroy: function (element) {
component.$destroy();
element.removeChild(component.$el);
component = undefined;
}
}
},
priority: function () {
return 1;
}
}
}
return TableConfigurationViewProvider;
});

View File

@ -1,145 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'EventEmitter',
'lodash',
'./collections/BoundedTableRowCollection',
'./collections/FilteredTableRowCollection',
'./TelemetryTableRow',
'./TelemetryTableConfiguration'
], function (
EventEmitter,
_,
BoundedTableRowCollection,
FilteredTableRowCollection,
TelemetryTableRow,
TelemetryTableConfiguration
) {
class TelemetryTable extends EventEmitter {
constructor(domainObject, rowCount, openmct) {
super();
this.domainObject = domainObject;
this.openmct = openmct;
this.rowCount = rowCount;
this.subscriptions = {};
this.tableComposition = undefined;
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
this.addTelemetryObject = this.addTelemetryObject.bind(this);
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
this.createTableRowCollections();
this.loadComposition();
}
createTableRowCollections() {
this.boundedRows = new BoundedTableRowCollection(this.openmct);
//By default, sort by current time system, ascending.
this.filteredRows = new FilteredTableRowCollection(this.boundedRows);
this.filteredRows.sortBy({
key: this.openmct.time.timeSystem().key,
direction: 'asc'
});
}
loadComposition() {
this.tableComposition = this.openmct.composition.get(this.domainObject);
this.tableComposition.load().then((composition)=>{
this.configuration.addColumnsForAllObjects(composition);
composition.forEach(this.addTelemetryObject);
this.tableComposition.on('add', this.addTelemetryObject);
this.tableComposition.on('remove', this.removeTelemetryObject);
});
}
addTelemetryObject(telemetryObject) {
this.configuration.addColumnsForObject(telemetryObject, true);
this.requestDataFor(telemetryObject);
this.subscribeTo(telemetryObject);
this.emit('object-added', telemetryObject);
}
removeTelemetryObject(objectIdentifier) {
this.configuration.removeColumnsForObject(objectIdentifier, true);
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
this.boundedRows.removeAllRowsForObject(keyString);
this.unsubscribe(keyString);
this.emit('object-removed', objectIdentifier);
}
requestDataFor(telemetryObject) {
this.emit('loading-historical-data', true);
this.openmct.telemetry.request(telemetryObject)
.then(telemetryData => {
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
this.boundedRows.add(telemetryRows);
console.log('loaded ' + telemetryRows.length + ' rows');
this.emit('loading-historical-data', false);
});
}
getColumnMapForObject(objectKeyString) {
let columns = this.configuration.getColumns();
return columns[objectKeyString].reduce((map, column) => {
map[column.getKey()] = column;
return map;
}, {});
}
subscribeTo(telemetryObject) {
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
});
}
unsubscribe(keyString) {
this.subscriptions[keyString]();
delete this.subscriptions[keyString];
}
destroy() {
this.boundedRows.destroy();
this.filteredRows.destroy();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.tableComposition.off('add', this.addTelemetryObject);
this.tableComposition.off('remove', this.removeTelemetryObject);
}
}
return TelemetryTable;
});

View File

@ -1,57 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(function () {
class TelemetryTableColumn {
constructor (openmct, metadatum) {
this.metadatum = metadatum;
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
this.titleValue = this.metadatum.name;
}
getKey() {
return this.metadatum.key;
}
getTitle() {
return this.metadatum.name;
}
getMetadatum() {
return this.metadatum;
}
hasValueForDatum(telemetryDatum) {
return telemetryDatum.hasOwnProperty(this.metadatum.source);
}
getRawValue(telemetryDatum) {
return telemetryDatum[this.metadatum.source];
}
getFormattedValue(telemetryDatum) {
return this.formatter.format(telemetryDatum);
}
};
return TelemetryTableColumn;
});

View File

@ -1,309 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'lodash',
'vue',
'text!./telemetry-table.html',
'./TelemetryTable',
'./TelemetryTableRowComponent',
'../../exporters/CSVExporter'
],function (
_,
Vue,
TelemetryTableTemplate,
TelemetryTable,
TelemetryTableRowComponent,
CSVExporter
) {
const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
return function TelemetryTableComponent(domainObject, openmct) {
const csvExporter = new CSVExporter();
const table = new TelemetryTable(domainObject, VISIBLE_ROW_COUNT, openmct);
let processingScroll = false;
let observationUnlistener;
function defaultConfiguration(domainObject) {
let configuration = domainObject.configuration;
configuration.table = configuration.table || {
columns: {}
};
return configuration;
}
return new Vue({
template: TelemetryTableTemplate,
components: {
'telemetry-table-row': TelemetryTableRowComponent
},
data: function () {
return {
headers: {},
configuration: defaultConfiguration(domainObject),
headersCount: 0,
visibleRows: [],
columnWidths: [],
sizingRows: {},
rowHeight: ROW_HEIGHT,
scrollOffset: 0,
totalHeight: 0,
totalWidth: 0,
rowOffset: 0,
autoScroll: true,
sortOptions: {},
filters: {},
loading: false,
scrollable: undefined,
tableEl: undefined,
headersHolderEl: undefined,
calcTableWidth: '100%'
}
},
methods: {
updateVisibleRows: function () {
let start = 0;
let end = VISIBLE_ROW_COUNT;
let filteredRows = table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength;
} else {
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
let totalVisible = lastVisible - firstVisible;
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
} else if (end >= filteredRowsLength) {
end = filteredRowsLength;
start = end - VISIBLE_ROW_COUNT + 1;
}
}
this.rowOffset = start;
this.visibleRows = filteredRows.slice(start, end);
},
calculateFirstVisibleRow: function () {
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
},
calculateLastVisibleRow: function () {
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.floor(bottomScroll / this.rowHeight);
},
updateHeaders: function () {
let headers = table.configuration.getHeaders();
Object.keys(headers).forEach((headerKey) => {
if (this.configuration.table.columns[headerKey] === false) {
delete headers[headerKey];
}
});
this.headers = headers;
this.headersCount = Object.values(headers).length;
Vue.nextTick().then(this.calculateColumnWidths);
},
setSizingTableWidth: function () {
let scrollW = this.scrollable.offsetWidth - this.scrollable.clientWidth;
if (scrollW && scrollW > 0) {
this.calcTableWidth = 'calc(100% - ' + scrollW + 'px)';
}
},
calculateColumnWidths: function () {
let columnWidths = [];
let totalWidth = 0;
let sizingRowEl = this.sizingTable.children[0];
let sizingCells = Array.from(sizingRowEl.children);
sizingCells.forEach((cell) => {
let columnWidth = cell.offsetWidth;
columnWidths.push(columnWidth + 'px');
totalWidth += columnWidth;
});
this.columnWidths = columnWidths;
this.totalWidth = totalWidth;
},
sortBy: function (columnKey) {
// If sorting by the same column, flip the sort direction.
if (this.sortOptions.key === columnKey) {
if (this.sortOptions.direction === 'asc') {
this.sortOptions.direction = 'desc';
} else {
this.sortOptions.direction = 'asc';
}
} else {
this.sortOptions = {
key: columnKey,
direction: 'asc'
}
}
table.filteredRows.sortBy(this.sortOptions);
},
scroll: function() {
if (!processingScroll) {
processingScroll = true;
requestAnimationFrame(() => {
this.updateVisibleRows();
this.synchronizeScrollX();
if (this.shouldSnapToBottom()) {
// If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = true;
} else {
this.autoScroll = false;
}
processingScroll = false;
});
}
},
shouldSnapToBottom: function () {
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
},
scrollToBottom: function () {
this.scrollable.scrollTop = this.totalHeight;
},
synchronizeScrollX: function () {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
},
filterChanged: function (columnKey) {
table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
},
clearFilter: function (columnKey) {
this.filters[columnKey] = '';
table.filteredRows.setColumnFilter(columnKey, '');
},
rowsAdded: function (rows) {
let sizingRow;
if (Array.isArray(rows)) {
sizingRow = rows[0];
} else {
sizingRow = rows;
}
if (!this.sizingRows[sizingRow.objectKeyString]) {
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
Vue.nextTick().then(this.calculateColumnWidths);
}
this.updateVisibleRows();
if (this.autoScroll) {
this.scrollToBottom();
}
},
exportAsCSV: function () {
const justTheData = table.filteredRows.getRows()
.map(row => row.getFormattedDatum());
const headers = Object.keys(this.headers);
csvExporter.export(justTheData, {
filename: table.domainObject.name + '.csv',
headers: headers
});
},
loadingHistoricalData: function (loading) {
this.loading = loading;
},
calculateTableSize: function () {
this.setSizingTableWidth();
Vue.nextTick().then(this.calculateColumnWidths);
},
pollForResize: function () {
let el = this.$el;
let width = el.clientWidth;
let height = el.clientHeight;
this.resizePollHandle = setInterval(() => {
if (el.clientWidth !== width || el.clientHeight !== height) {
this.calculateTableSize();
width = el.clientWidth;
height = el.clientHeight;
}
}, RESIZE_POLL_INTERVAL);
},
updateConfiguration: function (configuration) {
this.configuration = configuration;
this.updateHeaders();
},
addObject: function () {
this.updateHeaders();
},
removeObject: function (objectIdentifier) {
let objectKeyString = openmct.objects.makeKeyString(objectIdentifier);
delete this.sizingRows[objectKeyString];
this.updateHeaders();
}
},
created: function () {
this.filterChanged = _.debounce(this.filterChanged, 500);
},
mounted: function () {
table.on('object-added', this.addObject);
table.on('object-removed', this.removeObject);
table.on('loading-historical-data', this.loadingHistoricalData);
table.filteredRows.on('add', this.rowsAdded);
table.filteredRows.on('remove', this.updateVisibleRows);
table.filteredRows.on('sort', this.updateVisibleRows);
table.filteredRows.on('filter', this.updateVisibleRows);
//Default sort
this.sortOptions = table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.t-scrolling');
this.sizingTable = this.$el.querySelector('.js-sizing-table');
this.headersHolderEl = this.$el.querySelector('.mct-table-headers-w');
observationUnlistener = openmct.objects.observe(domainObject, 'configuration', this.updateConfiguration);
this.calculateTableSize();
this.pollForResize();
},
destroyed: function () {
table.off('object-added', this.addObject);
table.off('object-removed', this.removeObject);
table.off('loading-historical-data', this.loadingHistoricalData);
table.filteredRows.off('add', this.updateVisibleRows);
table.filteredRows.off('remove', this.updateVisibleRows);
table.filteredRows.off('sort', this.updateVisibleRows);
table.filteredRows.off('filter', this.updateVisibleRows);
clearInterval(this.resizePollHandle);
observationUnlistener();
table.destroy();
}
});
}
});

View File

@ -1,94 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'EventEmitter',
'./TelemetryTableColumn',
], function (EventEmitter, TelemetryTableColumn) {
class TelemetryTableConfiguration extends EventEmitter{
constructor(domainObject, openmct) {
super();
this.domainObject = domainObject;
this.openmct = openmct;
this.columns = {};
this.addColumnsForObject = this.addColumnsForObject.bind(this);
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
}
addColumnsForAllObjects(objects) {
objects.forEach(composee => this.addColumnsForObject(composee, false));
}
addColumnsForObject(telemetryObject) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
let objectKeyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
this.columns[objectKeyString] = [];
metadataValues.forEach(metadatum => {
let column = new TelemetryTableColumn(this.openmct, metadatum);
this.columns[objectKeyString].push(column);
});
}
removeColumnsForObject(objectIdentifier) {
let objectKeyString = this.openmct.objects.makeKeyString(objectIdentifier);
let columnsToRemove = this.columns[objectKeyString];
delete this.columns[objectKeyString];
columnsToRemove.forEach((column) => {
//There may be more than one column with the same key (eg. time system columns)
if (!this.hasColumnWithKey(column.key)) {
// If there are no more columns with this key, delete any configuration, and trigger
// a column refresh.
delete this.domainObject.configuration.table.columns[column.getKey()];
}
});
}
hasColumnWithKey(columnKey) {
return _.flatten(Object.values(this.columns))
.findIndex(column => column.getKey() === columnKey) !== -1;
}
getColumns() {
return this.columns;
}
getHeaders() {
let flattenedColumns = _.flatten(Object.values(this.columns));
let headers = _.uniq(flattenedColumns, false, column => column.getKey())
.reduce(fromColumnsToHeadersMap, {});
function fromColumnsToHeadersMap(headersMap, column){
headersMap[column.getKey()] = column.getTitle();
return headersMap;
}
return headers;
}
}
return TelemetryTableConfiguration;
});

View File

@ -1,82 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
class TelemetryTableRow {
constructor(datum, columns, objectKeyString, limitEvaluator) {
this.columns = columns;
this.datum = createNormalizedDatum(datum, columns);
this.limitEvaluator = limitEvaluator;
this.objectKeyString = objectKeyString;
}
getFormattedDatum() {
return Object.values(this.columns)
.reduce((formattedDatum, column) => {
formattedDatum[column.getKey()] = this.getFormattedValue(column.getKey());
return formattedDatum;
}, {});
}
getFormattedValue(key) {
let column = this.columns[key];
return column.getFormattedValue(this.datum[key]);
}
getRowLimitClass() {
if (!this.rowLimitClass) {
let limitEvaluation = this.limitEvaluator.evaluate(this.datum);
this.rowLimitClass = limitEvaluation && limitEvaluation.cssClass;
}
return this.rowLimitClass;
}
getCellLimitClasses() {
if (!this.cellLimitClasses) {
this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => {
let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum());
alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass;
return alarmStateMap;
}, {});
}
return this.cellLimitClasses;
}
}
/**
* Normalize the structure of datums to assist sorting and merging of columns.
* Maps all sources to keys.
* @private
* @param {*} telemetryDatum
* @param {*} metadataValues
*/
function createNormalizedDatum(datum, columns) {
return Object.values(columns).reduce((normalizedDatum, column) => {
normalizedDatum[column.getKey()] = column.getRawValue(datum);
return normalizedDatum;
}, {});
}
return TelemetryTableRow;
});

View File

@ -1,90 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'text!./telemetry-table-row.html',
],function (
TelemetryTableRowTemplate
) {
return {
template: TelemetryTableRowTemplate,
data: function () {
return {
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
formattedRow: this.row.getFormattedDatum(),
rowLimitClass: this.row.getRowLimitClass(),
cellLimitClasses: this.row.getCellLimitClasses()
}
},
props: {
headers: {
type: Object,
required: true
},
row: {
type: Object,
required: true
},
columnWidths: {
type: Array,
required: false,
default: [],
},
rowIndex: {
type: Number,
required: false,
default: undefined
},
rowOffset: {
type: Number,
required: false,
default: 0
},
rowHeight: {
type: Number,
required: false,
default: 0
},
configuration: {
type: Object,
required: true
}
},
methods: {
calculateRowTop: function (rowOffset) {
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
},
formatRow: function (row) {
this.formattedRow = row.getFormattedDatum();
this.rowLimitClass = row.getRowLimitClass();
this.cellLimitClasses = row.getCellLimitClasses();
}
},
watch: {
rowOffset: 'calculateRowTop',
row: {
handler: 'formatRow',
deep: false
}
}
};
});

View File

@ -1,52 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./TelemetryTableComponent'], function (TelemetryTableComponent) {
function TelemetryTableViewProvider(openmct) {
return {
key: 'vue-table',
name: 'Telemetry Table',
editable: true,
canView: function (domainObject) {
return domainObject.type === 'vue-table';
},
view: function (domainObject) {
let component;
return {
show: function (element) {
component = TelemetryTableComponent(domainObject, openmct);
element.appendChild(component.$mount().$el);
},
destroy: function (element) {
component.$destroy();
element.removeChild(component.$el);
component = undefined;
}
}
},
priority: function () {
return 1;
}
}
}
return TelemetryTableViewProvider;
});

View File

@ -1,139 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[
'lodash',
'./SortedTableRowCollection'
],
function (
_,
SortedTableRowCollection
) {
class BoundedTableRowCollection extends SortedTableRowCollection {
constructor (openmct) {
super();
this.futureBuffer = new SortedTableRowCollection();
this.openmct = openmct;
this.sortByTimeSystem = this.sortByTimeSystem.bind(this)
this.bounds = this.bounds.bind(this)
this.sortByTimeSystem(openmct.time.timeSystem());
openmct.time.on('timeSystem', this.sortByTimeSystem);
this.lastBounds = openmct.time.bounds();
openmct.time.on('bounds', this.bounds);
}
addOne (item) {
// Insert into either in-bounds array, or the future buffer.
// Data in the future buffer will be re-evaluated for possible
// insertion on next bounds change
let beforeStartOfBounds = item.datum[this.sortOptions.key] < this.lastBounds.start;
let afterEndOfBounds = item.datum[this.sortOptions.key] > this.lastBounds.end;
if (!afterEndOfBounds && !beforeStartOfBounds) {
return super.addOne(item);
} else if (afterEndOfBounds) {
this.futureBuffer.addOne(item);
}
return false;
}
sortByTimeSystem(timeSystem) {
this.sortBy({key: timeSystem.key, direction: 'asc'});
this.futureBuffer.sortBy({key: timeSystem.key, direction: 'asc'});
}
/**
* This function is optimized for ticking - it assumes that start and end
* bounds will only increase and as such this cannot be used for decreasing
* bounds changes.
*
* An implication of this is that data will not be discarded that exceeds
* the given end bounds. For arbitrary bounds changes, it's assumed that
* a telemetry requery is performed anyway, and the collection is cleared
* and repopulated.
*
* @fires TelemetryCollection#added
* @fires TelemetryCollection#discarded
* @param bounds
*/
bounds (bounds) {
let startChanged = this.lastBounds.start !== bounds.start;
let endChanged = this.lastBounds.end !== bounds.end;
let startIndex = 0;
let endIndex = 0;
let discarded = [];
let added = [];
let testValue = {
datum: {}
};
this.lastBounds = bounds;
if (startChanged) {
testValue.datum[this.sortOptions.key] = bounds.start;
// Calculate the new index of the first item within the bounds
startIndex = this.sortedIndex(this.rows, testValue);
discarded = this.rows.splice(0, startIndex);
}
if (endChanged) {
testValue.datum[this.sortOptions.key] = bounds.end;
// Calculate the new index of the last item in bounds
endIndex = this.sortedLastIndex(this.futureBuffer.rows, testValue);
added = this.futureBuffer.rows.splice(0, endIndex);
added.forEach((datum) => this.rows.push(datum));
}
if (discarded && discarded.length > 0) {
/**
* A `discarded` event is emitted when telemetry data fall out of
* bounds due to a bounds change event
* @type {object[]} discarded the telemetry data
* discarded as a result of the bounds change
*/
this.emit('remove', discarded);
}
if (added && added.length > 0) {
/**
* An `added` event is emitted when a bounds change results in
* received telemetry falling within the new bounds.
* @type {object[]} added the telemetry data that is now within bounds
*/
this.emit('add', added);
}
}
destroy() {
this.openmct.time.off('timeSystem', this.sortByTimeSystem);
this.openmct.time.off('bounds', this.bounds);
}
}
return BoundedTableRowCollection;
});

View File

@ -1,112 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[
'./SortedTableRowCollection'
],
function (
SortedTableRowCollection
) {
class FilteredTableRowCollection extends SortedTableRowCollection {
constructor(masterCollection) {
super();
this.masterCollection = masterCollection;
this.columnFilters = {};
//Synchronize with master collection
this.masterCollection.on('add', this.add);
this.masterCollection.on('remove', this.remove);
//Default to master collection's sort options
this.sortOptions = masterCollection.sortBy();
}
setColumnFilter(columnKey, filter) {
filter = filter.trim().toLowerCase();
let rowsToFilter = this.getRowsToFilter(columnKey, filter);
if (filter.length === 0) {
delete this.columnFilters[columnKey];
} else {
this.columnFilters[columnKey] = filter;
}
this.rows = rowsToFilter.filter(this.matchesFilters, this);
this.emit('filter');
}
/**
* @private
*/
getRowsToFilter(columnKey, filter) {
if (this.isSubsetOfCurrentFilter(columnKey, filter)) {
return this.getRows();
} else {
return this.masterCollection.getRows();
}
}
/**
* @private
*/
isSubsetOfCurrentFilter(columnKey, filter) {
return this.columnFilters[columnKey] &&
filter.startsWith(this.columnFilters[columnKey]) &&
// startsWith check will otherwise fail when filter cleared
// because anyString.startsWith('') === true
filter !== '';
}
addOne(row) {
return this.matchesFilters(row) && super.addOne(row);
}
/**
* @private
*/
matchesFilters(row) {
let doesMatchFilters = true;
for (const key in this.columnFilters) {
if (!this.rowHasColumn(row, key)) {
return false;
} else {
let formattedValue = row.getFormattedValue(key).toLowerCase();
doesMatchFilters = doesMatchFilters &&
formattedValue.indexOf(this.columnFilters[key]) !== -1;
}
}
return doesMatchFilters;
}
rowHasColumn(row, key) {
return row.columns.hasOwnProperty(key);
}
destroy() {
this.masterCollection.off('add', this.add);
this.masterCollection.off('remove', this.remove);
}
}
return FilteredTableRowCollection;
});

View File

@ -1,212 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[
'lodash',
'EventEmitter'
],
function (
_,
EventEmitter
) {
const LESS_THAN = -1;
const EQUAL = 0;
const GREATER_THAN = 1;
/**
* @constructor
*/
class SortedTableRowCollection extends EventEmitter {
constructor () {
super();
this.dupeCheck = false;
this.rows = [];
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);
}
/**
* Add a datum or array of data to this telemetry collection
* @fires TelemetryCollection#added
* @param {object | object[]} rows
*/
add(rows) {
if (Array.isArray(rows)) {
this.dupeCheck = false;
let rowsAdded = rows.filter(this.addOne, this);
if (rowsAdded.length > 0) {
this.emit('add', rowsAdded);
}
this.dupeCheck = true;
} else {
let wasAdded = this.addOne(rows);
if (wasAdded) {
this.emit('add', rows);
}
}
}
/**
* @private
*/
addOne(row) {
if (this.sortOptions === undefined) {
throw 'Please specify sort options';
}
let isDuplicate = false;
// Going to check for duplicates. Bound the search problem to
// items around the given time. Use sortedIndex because it
// employs a binary search which is O(log n). Can use binary search
// based on time stamp because the array is guaranteed ordered due
// to sorted insertion.
let startIx = this.sortedIndex(this.rows, row);
let endIx = undefined;
if (this.dupeCheck && startIx !== this.rows.length) {
endIx = _.sortedLastIndex(this.rows, row);
// Create an array of potential dupes, based on having the
// same time stamp
let potentialDupes = this.rows.slice(startIx, endIx + 1);
// Search potential dupes for exact dupe
isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, row)) > -1;
}
if (!isDuplicate) {
this.rows.splice(endIx || startIx, 0, row);
return true;
}
return false;
}
sortedLastIndex(rows, testRow) {
return this.sortedIndex(rows, testRow, _.sortedLastIndex);
}
/**
* Finds the correct insertion point for the given row.
* Leverages lodash's `sortedIndex` function which implements a binary search.
* @private
*/
sortedIndex(rows, testRow, lodashFunction) {
const sortOptionsKey = this.sortOptions.key;
lodashFunction = lodashFunction || _.sortedIndex;
if (this.sortOptions.direction === 'asc') {
return lodashFunction(rows, testRow, (thisRow) => {
return thisRow.datum[sortOptionsKey];
});
} else {
const testRowValue = testRow.datum[this.sortOptions.key];
// Use a custom comparison function to support descending sort.
return lodashFunction(rows, testRow, (thisRow) => {
const thisRowValue = thisRow.datum[sortOptionsKey];
if (testRowValue === thisRowValue) {
return EQUAL;
} else if (testRowValue < thisRowValue) {
return LESS_THAN;
} else {
return GREATER_THAN;
}
});
}
}
/**
* Sorts the telemetry collection based on the provided sort field
* specifier. Subsequent inserts are sorted to maintain specified sport
* order.
*
* @example
* // First build some mock telemetry for the purpose of an example
* let now = Date.now();
* let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
* return {
* // define an object property to demonstrate nested paths
* timestamp: {
* ms: now - value * 1000,
* text:
* },
* value: value
* }
* });
* let collection = new TelemetryCollection();
*
* collection.add(telemetry);
*
* // Sort by telemetry value
* collection.sortBy({
* key: 'value', direction: 'asc'
* });
*
* // Sort by ms since epoch
* collection.sort({
* key: 'timestamp.ms',
* direction: 'asc'
* });
*
* // Sort by 'text' attribute, descending
* collection.sort("timestamp.text");
*
*
* @param {object} sortOptions An object specifying a sort key, and direction.
*/
sortBy(sortOptions) {
if (arguments.length > 0) {
this.sortOptions = sortOptions;
this.rows = _.sortByOrder(this.rows, 'datum.' + sortOptions.key, sortOptions.direction);
this.emit('sort');
}
// Return duplicate to avoid direct modification of underlying object
return Object.assign({}, this.sortOptions);
}
removeAllRowsForObject(objectKeyString) {
let removed = [];
this.rows = this.rows.filter(row => {
if (row.objectKeyString === objectKeyString) {
removed.push(row);
return false;
}
return true;
});
this.emit('remove', removed);
}
remove(removedRows) {
this.rows = this.rows.filter(row => {
return removedRows.indexOf(row) === -1;
});
this.emit('remove', removedRows);
}
getRows () {
return this.rows;
}
}
return SortedTableRowCollection;
});

View File

@ -1,11 +0,0 @@
<div class="grid-properties">
<!--form class="form" -->
<ul class="l-inspector-part">
<h2>Table Columns</h2>
<li class="grid-row" v-for="(title, key) in headers">
<div class="grid-cell label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
<div class="grid-cell value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.table.columns[key] !== false" @change="toggleColumn(key)"></div>
</li>
</ul>
<!--/form -->
</div>

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

@ -1,60 +0,0 @@
<div class="tabular-holder l-sticky-headers has-control-bar" :class="{'loading': loading}">
<div class="l-control-bar">
<a class="s-button t-export icon-download labeled"
v-on:click="exportAsCSV()"
title="Export This View's Data">
Export As CSV
</a>
</div>
<!-- Headers table -->
<div class="mct-table-headers-w">
<table class="mct-table l-tabular-headers filterable" :style="{ 'max-width': totalWidth + 'px'}">
<thead>
<tr>
<th v-for="(title, key, headerIndex) in headers"
v-on:click="sortBy(key)"
:class="['sortable', sortOptions.key === key ? 'sort' : '', sortOptions.direction].join(' ')"
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}">{{title}}</th>
</tr>
<tr class="s-filters">
<th v-for="(title, key, headerIndex) in headers"
:style="{
width: columnWidths[headerIndex],
'max-width': columnWidths[headerIndex],
}">
<div class="holder l-filter flex-elem grows" :class="{active: filters[key]}">
<input type="text" v-model="filters[key]" v-on:input="filterChanged(key)" />
<a class="clear-icon clear-input icon-x-in-circle" :class="{show: filters[key]}" @click="clearFilter(key)"></a>
</div>
</th>
</tr>
</thead>
</table>
</div>
<!-- Content table -->
<div v-on:scroll="scroll()" class="l-tabular-body t-scrolling vscroll--persist">
<table class="mct-table js-telemetry-table" :style="{ height: totalHeight + 'px', 'max-width': totalWidth + '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="mct-sizing-table t-sizing-table js-sizing-table" :style="{width: calcTableWidth}">
<tr>
<th v-for="(title, key, headerIndex) in headers">{{title}}</th>
</tr>
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
:headers="headers"
:row="sizingRowData">
</telemetry-table-row>
</table>
</div>