Compare commits

..

1 Commits

Author SHA1 Message Date
Victor Woeltjen
d75058d5bd [Slideshow] Add slideshow type
Candidate for EOFY17 demo
2017-09-28 13:00:53 -07:00
359 changed files with 11059 additions and 21641 deletions

View File

@@ -21,6 +21,5 @@
"shadow": "outer",
"strict": "implied",
"undef": true,
"unused": "vars",
"latedef": "nofunc"
"unused": "vars"
}

186
API.md
View File

@@ -1,57 +1,3 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [Building Applications With Open MCT](#building-applications-with-open-mct)
- [Scope and purpose of this document](#scope-and-purpose-of-this-document)
- [Building From Source](#building-from-source)
- [Starting an Open MCT application](#starting-an-open-mct-application)
- [Plugins](#plugins)
- [Defining and Installing a New Plugin](#defining-and-installing-a-new-plugin)
- [Domain Objects and Identifiers](#domain-objects-and-identifiers)
- [Object Attributes](#object-attributes)
- [Domain Object Types](#domain-object-types)
- [Root Objects](#root-objects)
- [Object Providers](#object-providers)
- [Composition Providers](#composition-providers)
- [Adding Composition Providers](#adding-composition-providers)
- [Default Composition Provider](#default-composition-provider)
- [Telemetry API](#telemetry-api)
- [Integrating Telemetry Sources](#integrating-telemetry-sources)
- [Telemetry Metadata](#telemetry-metadata)
- [Values](#values)
- [Value Hints](#value-hints)
- [The Time Conductor and Telemetry](#the-time-conductor-and-telemetry)
- [Telemetry Providers](#telemetry-providers)
- [Telemetry Requests and Responses.](#telemetry-requests-and-responses)
- [Request Strategies **draft**](#request-strategies-draft)
- [`latest` request strategy](#latest-request-strategy)
- [`minmax` request strategy](#minmax-request-strategy)
- [Telemetry Formats **draft**](#telemetry-formats-draft)
- [Registering Formats](#registering-formats)
- [Telemetry Data](#telemetry-data)
- [Telemetry Datums](#telemetry-datums)
- [Limit Evaluators **draft**](#limit-evaluators-draft)
- [Telemetry Consumer APIs **draft**](#telemetry-consumer-apis-draft)
- [Time API](#time-api)
- [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)
- [Clocks](#clocks)
- [Defining and registering clocks](#defining-and-registering-clocks)
- [Getting and setting active clock](#getting-and-setting-active-clock)
- [Stopping an active clock](#stopping-an-active-clock)
- [Clock Offsets](#clock-offsets)
- [Time Events](#time-events)
- [List of Time Events](#list-of-time-events)
- [The Time Conductor](#the-time-conductor)
- [Time Conductor Configuration](#time-conductor-configuration)
- [Example conductor configuration](#example-conductor-configuration)
- [Included Plugins](#included-plugins)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Building Applications With Open MCT
## Scope and purpose of this document
@@ -208,7 +154,7 @@ registry.
eg.
```javascript
openmct.types.addType('example.my-type', {
openmct.types.addType('my-type', {
name: "My Type",
description: "This is a type that I added!",
creatable: true
@@ -216,9 +162,8 @@ openmct.types.addType('example.my-type', {
```
The `addType` function accepts two arguments:
* A `string` key identifying the type. This key is used when specifying a type
for an object. We recommend prefixing your types with a namespace to avoid
conflicts with other plugins.
* A `string` key identifying the type. This key is used when specifying a type
for an object.
* An object type specification. An object type definition supports the following
attributes
* `name`: a `string` naming this object type
@@ -249,7 +194,7 @@ To do so, use the `addRoot` method of the object API.
eg.
```javascript
openmct.objects.addRoot({
namespace: "example.namespace",
namespace: "my-namespace",
key: "my-key"
});
```
@@ -290,12 +235,13 @@ It is expected that the `get` function will return a
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
that resolves with the object being requested.
In future, object providers will support other methods to enable other operations with persistence stores, such as creating, updating, and deleting objects.
In future, object providers will support other methods to enable other operations
with persistence stores, such as creating, updating, and deleting objects.
## Composition Providers
The _composition_ of a domain object is the list of objects it contains, as
shown (for example) in the tree for browsing. Open MCT provides a
The _composition_ of a domain object is the list of objects it contains, as shown
(for example) in the tree for browsing. Open MCT provides a
[default solution](#default-composition-provider) for composition, but there
may be cases where you want to provide the composition of a certain object
(or type of object) dynamically.
@@ -309,7 +255,7 @@ Composition Provider:
```javascript
openmct.composition.addProvider({
appliesTo: function (domainObject) {
return domainObject.type === 'example.my-type';
return domainObject.type === 'my-type';
},
load: function (domainObject) {
return Promise.resolve(myDomainObjects);
@@ -327,9 +273,8 @@ These identifiers will be used to fetch Domain Objects from an [Object Provider]
### Default Composition Provider
The default composition provider applies to any domain object with a
`composition` property. The value of `composition` should be an array of
identifiers, e.g.:
The default composition provider applies to any domain object with a `composition`
property. The value of `composition` should be an array of identifiers, e.g.:
```javascript
var domainObject = {
@@ -350,17 +295,13 @@ var domainObject = {
## Telemetry API
The Open MCT telemetry API provides two main sets of interfaces-- one for
integrating telemetry data into Open MCT, and another for developing Open MCT
visualization plugins utilizing the telemetry API.
The Open MCT telemetry API provides two main sets of interfaces-- one for integrating telemetry data into Open MCT, and another for developing Open MCT visualization plugins utilizing telemetry API.
The APIs for visualization plugins are still a work in progress and docs may
change at any time. However, the APIs for integrating telemetry metadata into
Open MCT are stable and documentation is included below.
The APIs for visualization plugins are still a work in progress and docs may change at any time. However, the APIs for integrating telemetry metadata into Open MCT are stable and documentation is included below.
### Integrating Telemetry Sources
There are two main tasks for integrating telemetry sources-- describing telemetry objects with relevant metadata, and then providing telemetry data for those objects. You'll use an [Object Provider](#object-providers) to provide objects with the necessary [Telemetry Metadata](#telemetry-metadata), and then register a [Telemetry Provider](#telemetry-providers) to retrieve telemetry data for those objects. Alternatively, you can register a telemetry metadata provider to provide the necessary telemetry metadata.
There are two main tasks for integrating telemetry sources-- describing telemetry objects with relevant metadata, and then providing telemetry data for those objects. You'll use an [Object Provider](#object-providers) to provide objects with the necessary [Telemetry Metadata](#telemetry-metadata), and then register a [Telemetry Provider](#telemetry-providers) to retrieve telemetry data for those objects.
For a step-by-step guide to building a telemetry adapter, please see the
[Open MCT Tutorials](https://github.com/nasa/openmct-tutorial).
@@ -414,7 +355,7 @@ attribute | type | flags | notes
--- | --- | --- | ---
`key` | string | required | unique identifier for this field.
`hints` | object | required | Hints allow views to intelligently select relevant attributes for display, and are required for most views to function. See section on "Value Hints" below.
`name` | string | optional | a human readable label for this field. If omitted, defaults to `key`.
`name` | string | optional | a human readible label for this field. If omitted, defaults to `key`.
`source` | string | optional | identifies the property of a datum where this value is stored. If omitted, defaults to `key`.
`format` | string | optional | a specific format identifier, mapping to a formatter. If omitted, uses a default formatter. For enumerations, use `enum`. For timestamps, use `utc` if you are using utc dates, otherwise use a key mapping to your custom date format.
`units` | string | optional | the units of this value, e.g. `km`, `seconds`, `parsecs`
@@ -442,18 +383,14 @@ In order for the time conductor to work, there will always be an active "time sy
#### Telemetry Providers
Telemetry providers are responsible for providing historical and real-time telemetry data for telemetry objects. Each telemetry provider determines which objects it can provide telemetry for, and then must implement methods to provide telemetry for those objects.
Telemetry providers are responsible for providing historical and real time telemetry data for telemetry objects. Each telemetry provider determines which objects it can provide telemetry for, and then must implement methods to provide telemetry for those objects.
A telemetry provider is a javascript object with up to four methods:
* `supportsSubscribe(domainObject, callback, options)` optional. Must be implemented to provide realtime telemetry. Should return `true` if the provider supports subscriptions for the given domain object (and request options).
* `subscribe(domainObject, callback, options)` required if `supportsSubscribe` is implemented. Establish a subscription for realtime data for the given domain object. Should invoke `callback` with a single telemetry datum every time data is received. Must return an unsubscribe function. Multiple views can subscribe to the same telemetry object, so it should always return a new unsubscribe function.
* `supportsRequest(domainObject, options)` optional. Must be implemented to provide historical telemetry. Should return `true` if the provider supports historical requests for the given domain object.
* `request(domainObject, options)` required if `supportsRequest` is implemented. Must return a promise for an array of telemetry datums that fulfills the request. The `options` argument will include a `start`, `end`, and `domain` attribute representing the query bounds. See [Telemetry Requests and Responses](#telemetry-requests-and-responses) for more info on how to respond to requests.
* `supportsMetadata(domainObject)` optional. Implement and return `true` for objects that you want to provide dynamic metadata for.
* `getMetadata(domainObject)` required if `supportsMetadata` is implemented. Must return a valid telemetry metadata definition that includes at least one valueMetadata definition.
* `supportsLimits(domainObject)` optional. Implement and return `true` for domain objects that you want to provide a limit evaluator for.
* `getLimitEvaluator(domainObject)` required if `supportsLimits` is implemented. Must return a valid LimitEvaluator for a given domain object.
* `request(domainObject, options)` required if `supportsRequest` is implemented. Must return a promise for an array of telemetry datums that fulfills the request. The `options` argument will include a `start`, `end`, and `domain` attribute representing the query bounds. For more request properties, see Request Properties below.
Telemetry providers are registered by calling `openmct.telemetry.addProvider(provider)`, e.g.
@@ -461,15 +398,14 @@ Telemetry providers are registered by calling `openmct.telemetry.addProvider(pro
openmct.telemetry.addProvider({
supportsRequest: function (domainObject, options) { /*...*/ },
request: function (domainObject, options) { /*...*/ },
supportsSubscribe: function (domainObject, callback, options) { /*...*/ },
subscribe: function (domainObject, callback, options) { /*...*/ }
})
```
Note: it is not required to implement all of the methods on every provider. Depending on the complexity of your implementation, it may be helpful to instantiate and register your realtime, historical, and metadata providers separately.
#### Telemetry Requests and Responses.
#### Telemetry Requests
Telemetry requests support time bounded queries. A call to a _Telemetry Provider_'s `request` function will include an `options` argument. These are simply javascript objects with attributes for the request parameters. An example of a telemetry request object with a start and end time is included below:
```javascript
{
start: 1487981997240,
@@ -478,53 +414,7 @@ Telemetry requests support time bounded queries. A call to a _Telemetry Provider
}
```
In this case, the `domain` is the currently selected time-system, and the start and end dates are valid dates in that time system.
A telemetry provider's `request` method should return a promise for an array of telemetry datums. These datums must be sorted by `domain` in ascending order.
#### Request Strategies **draft**
To improve performance views may request a certain strategy for data reduction. These are intended to improve visualization performance by reducing the amount of data needed to be sent to the client. These strategies will be indicated by additional parameters in the request options. You may choose to handle them or ignore them.
Note: these strategies are currently being tested in core plugins and may change based on developer feedback.
##### `latest` request strategy
This request is a "depth based" strategy. When a view is only capable of
displaying a single value (or perhaps the last ten values), then it can
use the `latest` request strategy with a `size` parameter that specifies
the number of results it desires. The `size` parameter is a hint; views
must not assume the response will have the exact number of results requested.
example:
```javascript
{
start: 1487981997240,
end: 1487982897240,
domain: 'utc',
strategy: 'latest',
size: 1
}
```
This strategy says "I want the lastest data point in this time range". A provider which recognizes this request should return only one value-- the latest-- in the requested time range. Depending on your back-end implementation, performing these queries in bulk can be a large performance increase. These are generally issued by views that are only capable of displaying a single value and only need to show the latest value.
##### `minmax` request strategy
example:
```javascript
{
start: 1487981997240,
end: 1487982897240,
domain: 'utc',
strategy: 'minmax',
size: 720
}
```
MinMax queries are issued by plots, and may be issued by other types as well. The aim is to reduce the amount of data returned but still faithfully represent the full extent of the data. In order to do this, the view calculates the maximum data resolution it can display (i.e. the number of horizontal pixels in a plot) and sends that as the `size`. The response should include at least one minimum and one maximum value per point of resolution.
#### Telemetry Formats **draft**
#### Telemetry Formats
Telemetry format objects define how to interpret and display telemetry data.
They have a simple structure:
@@ -594,17 +484,6 @@ The key-value pairs of this object are described by the telemetry metadata of
a domain object, as discussed in the [Telemetry Metadata](#telemetry-metadata)
section.
#### Limit Evaluators **draft**
Limit evaluators allow a telemetry integrator to define how limits should be
applied to telemetry from a given domain object. For an example of a limit
evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`.
### Telemetry Consumer APIs **draft**
The APIs for requesting telemetry from Open MCT -- e.g. for use in custom views -- are currently in draft state and are being revised. If you'd like to experiement with them before they are finalized, please contact the team via the contact-us link on our website.
## Time API
Open MCT provides API for managing the temporal state of the application.
@@ -712,7 +591,7 @@ openmct.time.bounds({start: now - ONE_HOUR, now);
To respond to bounds change events, listen for the [`'bounds'`](#time-events)
event.
### Clocks
## Clocks
The Time API can be set to follow a clock source which will cause the bounds
to be updated automatically whenever the clock source "ticks". A clock is simply
@@ -731,7 +610,7 @@ be defined to tick on some remote timing source.
The values provided by clocks are simple `number`s, which are interpreted in the
context of the active [Time System](#defining-and-registering-time-systems).
#### Defining and registering clocks
### Defining and registering clocks
A clock is an object that defines certain required metadata and functions:
@@ -845,7 +724,7 @@ __Note:__ Setting the clock offsets will trigger an immediate bounds change, as
new bounds will be calculated based on the `currentValue()` of the active clock.
Clock offsets are only relevant when a clock source is active.
### Time Events
## Time Events
The Time API is a standard event emitter; you can register callbacks for events using the `on` method and remove callbacks for events with the `off` method.
@@ -887,7 +766,7 @@ The events emitted by the Time API are:
* `clockOffsets`: The new [clock offsets](#clock-offsets).
### The Time Conductor
## The Time Conductor
The Time Conductor provides a user interface for managing time bounds in Open
MCT. It allows a user to select from configured time systems and clocks, and to set bounds and clock offsets.
@@ -1000,21 +879,6 @@ 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
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)
- `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.plugins.LocalStorage` provides persistence of user-created
objects in browser-local storage. This is particularly useful in
development environments.

View File

@@ -88,7 +88,7 @@ and [`gulp`](http://gulpjs.com/).
To build Open MCT for deployment:
`npm run prepare`
`npm run prepublish`
This will compile and minify JavaScript sources, as well as copy over assets.
The contents of the `dist` folder will contain a runnable Open MCT

View File

@@ -17,7 +17,7 @@
"screenfull": "^3.0.0",
"node-uuid": "^1.4.7",
"comma-separated-values": "^3.6.4",
"file-saver": "1.3.3",
"FileSaver.js": "^0.0.2",
"zepto": "^1.1.6",
"eventemitter3": "^1.2.0",
"lodash": "3.10.1",

View File

@@ -1,17 +1,15 @@
machine:
node:
version: 4.7.0
dependencies:
pre:
- npm install -g npm@latest
deployment:
production:
branch: master
commands:
- npm install canvas nomnoml
- ./build-docs.sh
- git fetch --unshallow
- git push git@heroku.com:openmctweb-demo.git $CIRCLE_SHA1:refs/heads/master
openmct-demo:
branch: live_demo
heroku:
appname: openmct-demo
openmctweb-staging-deux:
branch: mobile
heroku:
@@ -24,4 +22,4 @@ test:
general:
branches:
ignore:
- gh-pages
- gh-pages

View File

@@ -2283,7 +2283,7 @@ To install build dependencies (only needs to be run once):
To build:
`npm run prepare`
`npm run prepublish`
This will compile and minify JavaScript sources, as well as copy over assets.
The contents of the `dist` folder will contain a runnable Open MCT

View File

@@ -1,121 +0,0 @@
# Security Guide
Open MCT is a rich client with plugin support that executes as a single page
web application in a browser environment. Security concerns and
vulnerabilities associated with the web as a platform should be considered
before deploying Open MCT (or any other web application) for mission or
production usage.
This document describes several important points to consider when developing
for or deploying Open MCT securely. Other resources such as
[Open Web Application Security Project (OWASP)](https://www.owasp.org)
provide a deeper and more general overview of security for web applications.
## Security Model
Open MCT has been architected assuming the following deployment pattern:
* A tagged, tested Open MCT version will be used.
* Externally authored plugins will be installed.
* A server will provide persistent storage, telemetry, and other shared data.
* Authorization, authentication, and auditing will be handled by a server.
## Security Procedures
The Open MCT team secures our code base using a combination of code review,
dependency review, and periodic security reviews. Static analysis performed
during automated verification additionally safeguards against common
coding errors which may result in vulnerabilities.
### Code Review
All contributions are reviewed by internal team members. External
contributors receive increased scrutiny for security and quality,
and must sign a licensing agreement.
### Dependency Review
Before integrating third-party dependencies, they are reviewed for security
and quality, with consideration given to authors and users of these
dependencies, as well as review of open source code.
### Periodic Security Reviews
Open MCT's code, design, and architecture are periodically reviewed
(approximately annually) for common security issues, such as the
[OWASP Top Ten](https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project).
## Security Concerns
Certain security concerns deserve special attention when deploying Open MCT,
or when authoring plugins.
### Identity Spoofing
Open MCT issues calls to web services with the privileges of a logged in user.
Compromised sources (either for Open MCT itself or a plugin) could
therefore allow malicious code to execute with those privileges.
To avoid this:
* Serve Open MCT and other scripts over SSL (https rather than http)
to prevent man-in-the-middle attacks.
* Exercise precautions such as security reviews for any plugins or
applications built for or with Open MCT to reject malicious changes.
### Information Disclosure
If Open MCT is used to handle or display sensitive data, any components
(such as adapter plugins) must take care to avoid leaking or disclosing
this information. For example, avoid sending sensitive data to third-party
servers or insecure APIs.
### Data Tampering
The web application architecture leaves open the possibility that direct
calls will be made to back-end services, circumventing Open MCT entirely.
As such, Open MCT assumes that server components will perform any necessary
data validation during calls issues to the server.
Additionally, plugins which serialize and write data to the server must
escape that data to avoid database injection attacks, and similar.
### Repudiation
Open MCT assumes that servers log any relevant interactions and associates
these with a user identity; the specific user actions taken within the
application are assumed not to be of concern for auditing.
In the absence of server-side logging, users may disclaim (maliciously,
mistakenly, or otherwise) actions taken within the system without any
way to prove otherwise.
If keeping client-level interactions is important, this will need to be
implemented via a plugin.
### Denial-of-service
Open MCT assumes that server-side components will be insulated against
denial-of-service attacks. Services should only permit resource-intensive
tasks to be initiated by known or trusted users.
### Elevation of Privilege
Corollary to the assumption that servers guide against identity spoofing,
Open MCT assumes that services do not allow a user to act with
inappropriately escalated privileges. Open MCT cannot protect against
such escalation; in the clearest case, a malicious actor could interact
with web services directly to exploit such a vulnerability.
## Additional Reading
The following resources have been used as a basis for identifying potential
security threats to Open MCT deployments in preparation of this document:
* [STRIDE model](https://www.owasp.org/index.php/Threat_Risk_Modeling#STRIDE)
* [Attack Surface Analysis Cheat Sheet](https://www.owasp.org/index.php/Attack_Surface_Analysis_Cheat_Sheet)
* [XSS Prevention Cheat Sheet](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)

View File

@@ -1,108 +0,0 @@
define([
'lodash'
], function (
_
) {
var METADATA_BY_TYPE = {
'generator': {
values: [
{
key: "name",
name: "Name"
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},
{
key: "yesterday",
name: "Yesterday",
format: "utc",
hints: {
domain: 2
}
},
{
key: "sin",
name: "Sine",
hints: {
range: 1
}
},
{
key: "cos",
name: "Cosine",
hints: {
range: 2
}
}
]
},
'example.state-generator': {
values: [
{
key: "name",
name: "Name"
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},
{
key: "state",
source: "value",
name: "State",
format: "enum",
enumerations: [
{
value: 0,
string: "OFF"
},
{
value: 1,
string: "ON"
}
],
hints: {
range: 1
}
},
{
key: "value",
name: "Value",
hints: {
range: 2
}
}
]
}
}
function GeneratorMetadataProvider() {
}
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return METADATA_BY_TYPE.hasOwnProperty(domainObject.type);
};
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return _.extend(
{},
domainObject.telemetry,
METADATA_BY_TYPE[domainObject.type]
);
};
return GeneratorMetadataProvider;
});

View File

@@ -30,8 +30,7 @@ define([
amplitude: 1,
period: 10,
offset: 0,
dataRateInHz: 1,
phase: 0
dataRateInHz: 1
};
function GeneratorProvider() {
@@ -51,27 +50,24 @@ define([
'amplitude',
'period',
'offset',
'dataRateInHz',
'phase',
'dataRateInHz'
];
request = request || {};
var workerRequest = {};
props.forEach(function (prop) {
if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) {
workerRequest[prop] = domainObject.telemetry[prop];
}
if (request && request.hasOwnProperty(prop)) {
if (request.hasOwnProperty(prop)) {
workerRequest[prop] = request[prop];
}
if (!workerRequest.hasOwnProperty(prop)) {
if (!workerRequest[prop]) {
workerRequest[prop] = REQUEST_DEFAULTS[prop];
}
workerRequest[prop] = Number(workerRequest[prop]);
});
workerRequest.name = domainObject.name;
return workerRequest;
};

View File

@@ -0,0 +1,87 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
var RED = 0.9,
YELLOW = 0.5,
LIMITS = {
rh: {
cssClass: "s-limit-upr s-limit-red",
low: RED,
high: Number.POSITIVE_INFINITY,
name: "Red High"
},
rl: {
cssClass: "s-limit-lwr s-limit-red",
high: -RED,
low: Number.NEGATIVE_INFINITY,
name: "Red Low"
},
yh: {
cssClass: "s-limit-upr s-limit-yellow",
low: YELLOW,
high: RED,
name: "Yellow High"
},
yl: {
cssClass: "s-limit-lwr s-limit-yellow",
low: -RED,
high: -YELLOW,
name: "Yellow Low"
}
};
function SinewaveLimitCapability(domainObject) {
return {
limits: function (range) {
return LIMITS;
},
evaluate: function (datum, range) {
range = range || 'sin';
if (datum[range] > RED) {
return LIMITS.rh;
}
if (datum[range] < -RED) {
return LIMITS.rl;
}
if (datum[range] > YELLOW) {
return LIMITS.yh;
}
if (datum[range] < -YELLOW) {
return LIMITS.yl;
}
}
};
}
SinewaveLimitCapability.appliesTo = function (model) {
return model.type === 'generator';
};
return SinewaveLimitCapability;
}
);

View File

@@ -1,88 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define([
], function (
) {
var RED = 0.9,
YELLOW = 0.5,
LIMITS = {
rh: {
cssClass: "s-limit-upr s-limit-red",
low: RED,
high: Number.POSITIVE_INFINITY,
name: "Red High"
},
rl: {
cssClass: "s-limit-lwr s-limit-red",
high: -RED,
low: Number.NEGATIVE_INFINITY,
name: "Red Low"
},
yh: {
cssClass: "s-limit-upr s-limit-yellow",
low: YELLOW,
high: RED,
name: "Yellow High"
},
yl: {
cssClass: "s-limit-lwr s-limit-yellow",
low: -RED,
high: -YELLOW,
name: "Yellow Low"
}
};
function SinewaveLimitProvider() {
}
SinewaveLimitProvider.prototype.supportsLimits = function (domainObject) {
return domainObject.type === 'generator';
};
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
return {
evaluate: function (datum, valueMetadata) {
var range = valueMetadata ? valueMetadata.key : 'sin'
if (datum[range] > RED) {
return LIMITS.rh;
}
if (datum[range] < -RED) {
return LIMITS.rl;
}
if (datum[range] > YELLOW) {
return LIMITS.yh;
}
if (datum[range] < -YELLOW) {
return LIMITS.yl;
}
}
};
};
return SinewaveLimitProvider;
});

View File

@@ -1,80 +0,0 @@
/*****************************************************************************
* 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 (
) {
function StateGeneratorProvider() {
}
function pointForTimestamp(timestamp, duration, name) {
return {
name: name,
utc: Math.floor(timestamp / duration) * duration,
value: Math.floor(timestamp / duration) % 2
};
}
StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var duration = domainObject.telemetry.duration * 1000;
var interval = setInterval(function () {
var now = Date.now();
callback(pointForTimestamp(now, duration, domainObject.name));
}, duration);
return function () {
clearInterval(interval);
};
};
StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = options.end;
var duration = domainObject.telemetry.duration * 1000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;
}
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, duration, domainObject.name));
start += 5000;
}
return Promise.resolve(data);
};
return StateGeneratorProvider;
});

View File

@@ -44,7 +44,9 @@ define([
message = message.data;
var callback = this.callbacks[message.id];
if (callback) {
callback(message);
if (callback(message)) {
delete this.callbacks[message.id];
}
}
};
@@ -70,7 +72,6 @@ define([
deferred.resolve = resolve;
deferred.reject = reject;
});
var messageId;
function callback(message) {
if (message.error) {
@@ -78,27 +79,33 @@ define([
} else {
deferred.resolve(message.data);
}
delete this.callbacks[messageId];
return true;
}
messageId = this.dispatch('request', request, callback.bind(this));
this.dispatch('request', request, callback);
return promise;
};
WorkerInterface.prototype.subscribe = function (request, cb) {
function callback(message) {
var isCancelled = false;
var callback = function (message) {
if (isCancelled) {
return true;
}
cb(message.data);
};
var messageId = this.dispatch('subscribe', request, callback);
var messageId = this.dispatch('subscribe', request, callback)
return function () {
isCancelled = true;
this.dispatch('unsubscribe', {
id: messageId
});
delete this.callbacks[messageId];
}.bind(this);
};

View File

@@ -62,11 +62,10 @@
self.postMessage({
id: message.id,
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60*60*24*1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase)
sin: sin(nextStep, data.period, data.amplitude, data.offset),
cos: cos(nextStep, data.period, data.amplitude, data.offset)
}
});
nextStep += step;
@@ -83,22 +82,21 @@
}
function onRequest(message) {
var request = message.data;
if (request.end == undefined) {
request.end = Date.now();
var data = message.data;
if (data.end == undefined) {
data.end = Date.now();
}
if (request.start == undefined){
request.start = request.end - FIFTEEN_MINUTES;
if (data.start == undefined){
data.start = data.end - FIFTEEN_MINUTES;
}
var now = Date.now();
var start = request.start;
var end = request.end > now ? now : request.end;
var amplitude = request.amplitude;
var period = request.period;
var offset = request.offset;
var dataRateInHz = request.dataRateInHz;
var phase = request.phase;
var start = data.start;
var end = data.end > now ? now : data.end;
var amplitude = data.amplitude;
var period = data.period;
var offset = data.offset;
var dataRateInHz = data.dataRateInHz;
var step = 1000 / dataRateInHz;
var nextStep = start - (start % step) + step;
@@ -107,11 +105,10 @@
for (; nextStep < end && data.length < 5000; nextStep += step) {
data.push({
name: request.name,
utc: nextStep,
yesterday: nextStep - 60*60*24*1000,
sin: sin(nextStep, period, amplitude, offset, phase),
cos: cos(nextStep, period, amplitude, offset, phase)
sin: sin(nextStep, period, amplitude, offset),
cos: cos(nextStep, period, amplitude, offset)
});
}
self.postMessage({
@@ -120,14 +117,14 @@
});
}
function cos(timestamp, period, amplitude, offset, phase) {
function cos(timestamp, period, amplitude, offset) {
return amplitude *
Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
Math.cos(timestamp / period / 1000 * Math.PI * 2) + offset;
}
function sin(timestamp, period, amplitude, offset, phase) {
function sin(timestamp, period, amplitude, offset) {
return amplitude *
Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
Math.sin(timestamp / period / 1000 * Math.PI * 2) + offset;
}
function sendError(error, message) {

View File

@@ -23,46 +23,29 @@
define([
"./GeneratorProvider",
"./SinewaveLimitProvider",
"./StateGeneratorProvider",
"./GeneratorMetadataProvider"
"./SinewaveLimitCapability"
], function (
GeneratorProvider,
SinewaveLimitProvider,
StateGeneratorProvider,
GeneratorMetadataProvider
SinewaveLimitCapability
) {
return function(openmct){
openmct.types.addType("example.state-generator", {
name: "State Generator",
description: "For development use. Generates test enumerated telemetry by cycling through a given set of states",
cssClass: "icon-telemetry",
creatable: true,
form: [
{
name: "State Duration (seconds)",
control: "textfield",
cssClass: "l-input-sm l-numeric",
key: "duration",
required: true,
property: [
"telemetry",
"duration"
],
pattern: "^\\d*(\\.\\d*)?$"
}
],
initialize: function (object) {
object.telemetry = {
duration: 5
}
var legacyExtensions = {
"capabilities": [
{
"key": "limit",
"implementation": SinewaveLimitCapability
}
]
};
return function(openmct){
//Register legacy extensions for things not yet supported by the new API
Object.keys(legacyExtensions).forEach(function (type){
var extensionsOfType = legacyExtensions[type];
extensionsOfType.forEach(function (extension) {
openmct.legacyExtension(type, extension)
})
});
openmct.telemetry.addProvider(new StateGeneratorProvider());
openmct.types.addType("generator", {
name: "Sine Wave Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
@@ -71,58 +54,51 @@ define([
form: [
{
name: "Period",
control: "numberfield",
control: "textfield",
cssClass: "l-input-sm l-numeric",
key: "period",
required: true,
property: [
"telemetry",
"period"
]
],
pattern: "^\\d*(\\.\\d*)?$"
},
{
name: "Amplitude",
control: "numberfield",
control: "textfield",
cssClass: "l-input-sm l-numeric",
key: "amplitude",
required: true,
property: [
"telemetry",
"amplitude"
]
],
pattern: "^\\d*(\\.\\d*)?$"
},
{
name: "Offset",
control: "numberfield",
control: "textfield",
cssClass: "l-input-sm l-numeric",
key: "offset",
required: true,
property: [
"telemetry",
"offset"
]
],
pattern: "^\\d*(\\.\\d*)?$"
},
{
name: "Data Rate (hz)",
control: "numberfield",
control: "textfield",
cssClass: "l-input-sm l-numeric",
key: "dataRateInHz",
required: true,
property: [
"telemetry",
"dataRateInHz"
]
},
{
name: "Phase (radians)",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "phase",
required: true,
property: [
"telemetry",
"phase"
]
],
pattern: "^\\d*(\\.\\d*)?$"
}
],
initialize: function (object) {
@@ -131,14 +107,42 @@ define([
amplitude: 1,
offset: 0,
dataRateInHz: 1,
phase: 0
values: [
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},
{
key: "yesterday",
name: "Yesterday",
format: "utc",
hints: {
domain: 2
}
},
{
key: "sin",
name: "Sine",
hints: {
range: 1
}
},
{
key: "cos",
name: "Cosine",
hints: {
range: 2
}
}
]
};
}
});
openmct.telemetry.addProvider(new GeneratorProvider());
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
openmct.telemetry.addProvider(new SinewaveLimitProvider());
};
});

View File

@@ -48,9 +48,8 @@ define([
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
];
function pointForTimestamp(timestamp, name) {
function pointForTimestamp(timestamp) {
return {
name: name,
utc: Math.floor(timestamp / 5000) * 5000,
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
};
@@ -62,7 +61,7 @@ define([
},
subscribe: function (domainObject, callback) {
var interval = setInterval(function () {
callback(pointForTimestamp(Date.now(), domainObject.name));
callback(pointForTimestamp(Date.now()));
}, 5000);
return function (interval) {
@@ -80,8 +79,8 @@ define([
var start = options.start;
var end = options.end;
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, domainObject.name));
while (start < end && data.length < 5000) {
data.push(pointForTimestamp(start));
start += 5000;
}
return Promise.resolve(data);
@@ -94,7 +93,7 @@ define([
options.strategy === 'latest';
},
request: function (domainObject, options) {
return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name)]);
return Promise.resolve([pointForTimestamp(Date.now())]);
}
};
@@ -110,10 +109,6 @@ define([
initialize: function (object) {
object.telemetry = {
values: [
{
name: 'Name',
key: 'name'
},
{
name: 'Time',
key: 'utc',

View File

@@ -0,0 +1,146 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*global define*/
define([
'legacyRegistry',
'../../platform/commonUI/browse/src/InspectorRegion',
'../../platform/commonUI/regions/src/Region'
], function (
legacyRegistry,
InspectorRegion,
Region
) {
"use strict";
/**
* Add a 'plot options' region part to the inspector region for the
* Telemetry Plot type only. {@link InspectorRegion} is a default region
* implementation that is added automatically to all types. In order to
* customize what appears in the inspector region, you can start from a
* blank slate by using Region, or customize the default inspector
* region by using {@link InspectorRegion}.
*/
var plotInspector = new InspectorRegion(),
/**
* Two region parts are defined here. One that appears only in browse
* mode, and one that appears only in edit mode. For not they both point
* to the same representation, but a different key could be used here to
* include a customized representation for edit mode.
*/
plotOptionsBrowseRegion = new Region({
name: "plot-options",
title: "Plot Options",
modes: ['browse'],
content: {
key: "plot-options-browse"
}
}),
plotOptionsEditRegion = new Region({
name: "plot-options",
title: "Plot Options",
modes: ['edit'],
content: {
key: "plot-options-browse"
}
});
/**
* Both parts are added, and policies of type 'region' will determine
* which is shown based on domain object state. A default policy is
* provided which will check the 'modes' attribute of the region part
* definition.
*/
plotInspector.addRegion(plotOptionsBrowseRegion);
plotInspector.addRegion(plotOptionsEditRegion);
legacyRegistry.register("example/plotType", {
"name": "Plot Type",
"description": "Example illustrating registration of a new object type",
"extensions": {
"types": [
{
"key": "plot",
"name": "Example Telemetry Plot",
"cssClass": "icon-telemetry-panel",
"description": "For development use. A plot for displaying telemetry.",
"priority": 10,
"delegates": [
"telemetry"
],
"features": "creation",
"contains": [
{
"has": "telemetry"
}
],
"model": {
"composition": []
},
"inspector": plotInspector,
"telemetry": {
"source": "generator",
"domains": [
{
"key": "time",
"name": "Time"
},
{
"key": "yesterday",
"name": "Yesterday"
},
{
"key": "delta",
"name": "Delta",
"format": "example.delta"
}
],
"ranges": [
{
"key": "sin",
"name": "Sine"
},
{
"key": "cos",
"name": "Cosine"
}
]
},
"properties": [
{
"name": "Period",
"control": "textfield",
"cssClass": "l-input-sm l-numeric",
"key": "period",
"required": true,
"property": [
"telemetry",
"period"
],
"pattern": "^\\d*(\\.\\d*)?$"
}
]
}
]
}
});
});

View File

@@ -58,7 +58,11 @@
position: relative;
}
.w-mct-example > div { margin-bottom: $interiorMarginLg; }
.w-mct-example {
div {
margin-bottom: $interiorMarginLg;
}
}
code,
pre {
@@ -101,11 +105,11 @@
padding-top: 1em;
.cols {
display: flex;
flex-direction: row;
@include display(flex);
@include flex-direction(row);
.col {
flex: 1 1 auto;
@include flex(1 1 auto);
&:not(:last-child) {
$v: $interiorMargin * 4;
border-right: 1px solid $colorInteriorBorder;
@@ -199,7 +203,7 @@
border-radius: 15%;
position: absolute;
left: 50%;
transform: translateX(-50%);
@include transform(translateX(-50%));
}
}
}

View File

@@ -127,8 +127,7 @@
{ 'meaning': 'Timer object', 'cssClass': 'icon-timer', 'cssContent': 'e1127', 'htmlEntity': '&amp;#xe1127' },
{ 'meaning': 'Data Topic', 'cssClass': 'icon-topic', 'cssContent': 'e1128', 'htmlEntity': '&amp;#xe1128' },
{ 'meaning': 'Fixed Position object', 'cssClass': 'icon-box-with-dashed-lines', 'cssContent': 'e1129', 'htmlEntity': '&amp;#xe1129' },
{ 'meaning': 'Summary Widget', 'cssClass': 'icon-summary-widget', 'cssContent': 'e1130', 'htmlEntity': '&amp;#xe1130' },
{ 'meaning': 'Notebook object', 'cssClass': 'icon-notebook', 'cssContent': 'e1131', 'htmlEntity': '&amp;#xe1131' }
{ 'meaning': 'Summary Widget', 'cssClass': 'icon-summary-widget', 'cssContent': 'e1130', 'htmlEntity': '&amp;#xe1130' }
];
"></div>

View File

@@ -121,7 +121,7 @@
<h2>Palettes</h2>
<div class="cols cols1-1">
<div class="col">
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one. Selected palette choices should utilize the <code>selected</code> CSS class to visualize indicate that state.</p>
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one.</p>
<p>Note that while this example uses static markup for illustrative purposes, don't do this - use a front-end framework with repeaters to build the color choices.</p>
</div>
<mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
@@ -129,9 +129,9 @@
<div class="s-button s-menu-button menu-element t-color-palette icon-paint-bucket" ng-controller="ClickAwayController as toggle">
<span class="l-click-area" ng-click="toggle.toggle()"></span>
<span class="color-swatch" style="background: rgb(255, 0, 0);"></span>
<div class="menu l-palette l-color-palette" ng-show="toggle.isActive()">
<div class="menu l-color-palette" ng-show="toggle.isActive()">
<div class="l-palette-row l-option-row">
<div class="l-palette-item s-palette-item no-selection"></div>
<div class="l-palette-item s-palette-item " ng-click="ngModel[field] = 'transparent'"></div>
<span class="l-palette-item-label">None</span>
</div>
<div class="l-palette-row">
@@ -147,7 +147,7 @@
<div class="l-palette-item s-palette-item" style="background: rgb(255, 255, 255);"></div>
</div>
<div class="l-palette-row">
<div class="l-palette-item s-palette-item selected" style="background: rgb(255, 0, 0);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(136, 32, 32);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(224, 64, 64);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(240, 160, 72);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(255, 248, 96);"></div>

View File

@@ -22,8 +22,6 @@
/*global require,__dirname*/
require("v8-compile-cache");
var gulp = require('gulp'),
sourcemaps = require('gulp-sourcemaps'),
path = require('path'),
@@ -179,4 +177,4 @@ gulp.task('install', [ 'assets', 'scripts' ]);
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);
gulp.task('build', [ 'verify', 'install' ]);
gulp.task('build', [ 'verify', 'install' ]);

View File

@@ -25,7 +25,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title></title>
<script src="bower_components/requirejs/require.js"> </script>
<script src="bower_components/requirejs/require.js">
</script>
<script>
var THIRTY_MINUTES = 30 * 60 * 1000;
@@ -43,16 +44,13 @@
openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.ImportExport());
openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel"
}));
openmct.install(openmct.plugins.Conductor({
menuOptions: [
{
name: "Fixed",
timeSystem: 'utc',
bounds: {
start: Date.now() - THIRTY_MINUTES,
start: Date.now() - 30 * 60 * 1000,
end: Date.now()
}
},
@@ -67,7 +65,6 @@
}
]
}));
openmct.install(openmct.plugins.SummaryWidget());
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc');
openmct.start();

View File

@@ -36,14 +36,14 @@ module.exports = function(config) {
files: [
{pattern: 'bower_components/**/*.js', included: false},
{pattern: 'node_modules/d3-*/**/*.js', included: false},
{pattern: 'node_modules/vue/**/*.js', included: false},
{pattern: 'src/**/*', included: false},
{pattern: 'src/**/*.js', included: false},
{pattern: 'example/**/*.html', included: false},
{pattern: 'example/**/*.js', included: false},
{pattern: 'example/**/*.json', included: false},
{pattern: 'platform/**/*.js', included: false},
{pattern: 'warp/**/*.js', included: false},
{pattern: 'platform/**/*.html', included: false},
{pattern: 'src/**/*.html', included: false},
'test-main.js'
],
@@ -88,8 +88,7 @@ module.exports = function(config) {
"dist/reports/coverage",
check: {
global: {
lines: 80,
excludes: ['src/plugins/plot/**/*.js']
lines: 80
}
}
},

View File

@@ -33,14 +33,13 @@ requirejs.config({
"moment": "bower_components/moment/moment",
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
"moment-timezone": "bower_components/moment-timezone/builds/moment-timezone-with-data",
"saveAs": "bower_components/file-saver/FileSaver.min",
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
"vue": "node_modules/vue/dist/vue.min",
"zepto": "bower_components/zepto/zepto.min",
"lodash": "bower_components/lodash/lodash",
"d3-selection": "node_modules/d3-selection/dist/d3-selection.min",
"d3-selection": "node_modules/d3-selection/build/d3-selection.min",
"d3-scale": "node_modules/d3-scale/build/d3-scale.min",
"d3-axis": "node_modules/d3-axis/build/d3-axis.min",
"d3-array": "node_modules/d3-array/build/d3-array.min",
@@ -67,9 +66,6 @@ requirejs.config({
"moment-duration-format": {
"deps": ["moment"]
},
"saveAs": {
"exports": "saveAs"
},
"screenfull": {
"exports": "screenfull"
},
@@ -101,7 +97,6 @@ define([
var openmct = new MCT();
openmct.legacyRegistry = defaultRegistry;
openmct.install(openmct.plugins.Plot());
if (typeof BUILD_CONSTANTS !== 'undefined') {
openmct.install(buildInfo(BUILD_CONSTANTS));

View File

@@ -1,22 +1,21 @@
{
"name": "openmct",
"version": "0.14.0-SNAPSHOT",
"version": "0.12.1-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {
"d3-array": "1.2.x",
"d3-axis": "1.0.x",
"d3-collection": "1.0.x",
"d3-color": "1.0.x",
"d3-format": "1.2.x",
"d3-interpolate": "1.1.x",
"d3-scale": "1.0.x",
"d3-selection": "1.3.x",
"d3-time": "1.0.x",
"d3-time-format": "2.1.x",
"d3-array": "^1.0.2",
"d3-axis": "^1.0.4",
"d3-collection": "^1.0.2",
"d3-color": "^1.0.2",
"d3-format": "^1.0.2",
"d3-interpolate": "^1.1.3",
"d3-scale": "^1.0.4",
"d3-selection": "^1.0.3",
"d3-time": "^1.0.4",
"d3-time-format": "^2.0.3",
"express": "^4.13.1",
"minimist": "^1.1.1",
"request": "^2.69.0",
"vue": "^2.5.6"
"request": "^2.69.0"
},
"devDependencies": {
"bower": "^1.7.7",
@@ -28,7 +27,7 @@
"gulp-jshint-html-reporter": "^0.1.3",
"gulp-rename": "^1.2.2",
"gulp-requirejs-optimize": "^0.3.1",
"gulp-sass": "^3.1.0",
"gulp-sass": "^2.2.0",
"gulp-sourcemaps": "^1.6.0",
"jasmine-core": "^2.3.0",
"jscs-html-reporter": "^0.1.0",
@@ -50,8 +49,7 @@
"moment": "^2.11.1",
"node-bourbon": "^4.2.3",
"requirejs": "2.1.x",
"split": "^1.0.0",
"v8-compile-cache": "^1.1.0"
"split": "^1.0.0"
},
"scripts": {
"start": "node app.js",
@@ -61,7 +59,7 @@
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
"otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
"docs": "npm run jsdoc ; npm run otherdoc",
"prepare": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
"prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
},
"repository": {
"type": "git",

View File

@@ -57,12 +57,7 @@
</div>
<mct-representation key="representation.selected.key"
mct-object="representation.selected.key && domainObject"
class="abs flex-elem grows object-holder-main scroll"
mct-selectable="{
item: domainObject.useCapability('adapter'),
oldItem: domainObject
}"
mct-init-select>
class="abs flex-elem grows object-holder-main scroll">
</mct-representation>
</div>
</div>

View File

@@ -19,21 +19,12 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-controller="InspectorController as controller">
<div ng-controller="InspectorController">
<div ng-repeat="region in regions">
<mct-representation
key="'object-properties'"
mct-object="controller.selectedItem()"
key="region.content.key"
mct-object="domainObject"
ng-model="ngModel">
</mct-representation>
<div ng-if="!controller.hasProviderView()">
<mct-representation
key="inspectorKey"
mct-object="controller.selectedItem()"
ng-model="ngModel">
</mct-representation>
</div>
<div class='inspector-provider-view'>
</div>
</div>
</div>

View File

@@ -22,7 +22,7 @@
<span class='type-icon flex-elem {{type.getCssClass()}}'></span>
<span class="l-elem-wrapper l-flex-row flex-elem grows" ng-controller="ObjectHeaderController as controller">
<span ng-if="parameters.mode" class='action flex-elem'>{{parameters.mode}}</span>
<span ng-attr-contenteditable="{{ controller.editable ? true : undefined }}"
<span contenteditable="true"
class='title-label flex-elem holder flex-can-shrink s-input-inline'
ng-click="controller.edit()"
ng-blur="controller.updateName($event)"

View File

@@ -38,7 +38,8 @@
ng-class="{ last:($index + 1) === contextualParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-click="parent.getCapability('action').perform('navigate')"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item">
</mct-representation>
</span>
@@ -50,7 +51,8 @@
ng-class="{ last:($index + 1) === primaryParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-click="parent.getCapability('action').perform('navigate')"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item">
</mct-representation>
</span>

View File

@@ -32,8 +32,7 @@ define(
*/
function ObjectHeaderController($scope) {
this.$scope = $scope;
this.domainObject = $scope.domainObject;
this.editable = this.allowEdit();
$scope.editing = false;
}
/**
@@ -42,49 +41,33 @@ define(
* @param event the mouse event
*/
ObjectHeaderController.prototype.updateName = function (event) {
if (!event || !event.currentTarget) {
return;
}
if (event && (event.type === 'blur' || event.which === 13)) {
var name = event.currentTarget.innerHTML;
if (event.type === 'blur') {
this.updateModel(event);
} else if (event.which === 13) {
this.updateModel(event);
event.currentTarget.blur();
window.getSelection().removeAllRanges();
if (name.length === 0) {
name = "Unnamed " + this.$scope.domainObject.getCapability("type").typeDef.name;
event.currentTarget.innerHTML = name;
}
if (name !== this.$scope.domainObject.model.name) {
this.$scope.domainObject.getCapability('mutation').mutate(function (model) {
model.name = name;
});
}
this.$scope.editing = false;
if (event.which === 13) {
event.currentTarget.blur();
}
}
};
/**
* Updates the model.
*
* @param event the mouse event
* @param private
* Marks the status of the field as editing.
*/
ObjectHeaderController.prototype.updateModel = function (event) {
var name = event.currentTarget.textContent.replace(/\n/g, ' ');
if (name.length === 0) {
name = "Unnamed " + this.domainObject.getCapability("type").typeDef.name;
event.currentTarget.textContent = name;
}
if (name !== this.domainObject.getModel().name) {
this.domainObject.getCapability('mutation').mutate(function (model) {
model.name = name;
});
}
};
/**
* Checks if the domain object is editable.
*
* @private
* @return true if object is editable
*/
ObjectHeaderController.prototype.allowEdit = function () {
var type = this.domainObject && this.domainObject.getCapability('type');
return !!(type && type.hasFeature('creation'));
ObjectHeaderController.prototype.edit = function () {
this.$scope.editing = true;
};
return ObjectHeaderController;

View File

@@ -32,27 +32,22 @@ define(
mockTypeCapability,
mockEvent,
mockCurrentTarget,
model,
controller;
beforeEach(function () {
mockMutationCapability = jasmine.createSpyObj("mutation", ["mutate"]);
mockTypeCapability = jasmine.createSpyObj("type", ["typeDef", "hasFeature"]);
mockTypeCapability.typeDef = { name: ""};
mockTypeCapability.hasFeature.andCallFake(function (feature) {
return feature === 'creation';
});
mockTypeCapability = {
typeDef: {
name: ""
}
};
mockCapabilities = {
mutation: mockMutationCapability,
type: mockTypeCapability
};
model = {
name: "Test name"
};
mockDomainObject = jasmine.createSpyObj("domainObject", ["getCapability", "getModel"]);
mockDomainObject.getModel.andReturn(model);
mockDomainObject = jasmine.createSpyObj("domainObject", ["getCapability", "model"]);
mockDomainObject.model = {name: "Test name"};
mockDomainObject.getCapability.andCallFake(function (key) {
return mockCapabilities[key];
});
@@ -61,7 +56,7 @@ define(
domainObject: mockDomainObject
};
mockCurrentTarget = jasmine.createSpyObj("currentTarget", ["blur", "textContent"]);
mockCurrentTarget = jasmine.createSpyObj("currentTarget", ["blur", "innerHTML"]);
mockCurrentTarget.blur.andReturn(mockCurrentTarget);
mockEvent = {
@@ -75,7 +70,7 @@ define(
it("updates the model with new name on blur", function () {
mockEvent.type = "blur";
mockCurrentTarget.textContent = "New name";
mockCurrentTarget.innerHTML = "New name";
controller.updateName(mockEvent);
expect(mockMutationCapability.mutate).toHaveBeenCalled();
@@ -83,23 +78,23 @@ define(
it("updates the model with a default for blank names", function () {
mockEvent.type = "blur";
mockCurrentTarget.textContent = "";
mockCurrentTarget.innerHTML = "";
controller.updateName(mockEvent);
expect(mockCurrentTarget.textContent.length).not.toEqual(0);
expect(mockCurrentTarget.innerHTML.length).not.toEqual(0);
expect(mockMutationCapability.mutate).toHaveBeenCalled();
});
it("does not update the model if the same name", function () {
mockEvent.type = "blur";
mockCurrentTarget.textContent = mockDomainObject.getModel().name;
mockCurrentTarget.innerHTML = mockDomainObject.model.name;
controller.updateName(mockEvent);
expect(mockMutationCapability.mutate).not.toHaveBeenCalled();
});
it("updates the model on enter keypress event only", function () {
mockCurrentTarget.textContent = "New name";
mockCurrentTarget.innerHTML = "New name";
controller.updateName(mockEvent);
expect(mockMutationCapability.mutate).not.toHaveBeenCalled();
@@ -109,29 +104,17 @@ define(
expect(mockMutationCapability.mutate).toHaveBeenCalledWith(jasmine.any(Function));
mockMutationCapability.mutate.mostRecentCall.args[0](model);
mockMutationCapability.mutate.mostRecentCall.args[0](mockDomainObject.model);
expect(mockDomainObject.getModel().name).toBe("New name");
expect(mockDomainObject.model.name).toBe("New name");
});
it("blurs the field on enter key press", function () {
mockCurrentTarget.textContent = "New name";
mockEvent.which = 13;
controller.updateName(mockEvent);
expect(mockEvent.currentTarget.blur).toHaveBeenCalled();
});
it("allows editting name when object is creatable", function () {
expect(controller.allowEdit()).toBe(true);
});
it("disallows editting name when object is non-creatable", function () {
mockTypeCapability.hasFeature.andReturn(false);
expect(controller.allowEdit()).toBe(false);
});
});
}
);

View File

@@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<div class="abs top-bar">
<div class="dialog-title">{{ngModel.title}}</div>
<div class="title">{{ngModel.title}}</div>
<div class="hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
</div>
<div class='abs editor'>

View File

@@ -1,10 +1,11 @@
<div class="l-message"
ng-class="'message-severity-' + ngModel.severity">
<div class="w-message-contents">
<div class="ui-symbol type-icon message-type"></div>
<div class="message-contents">
<div class="top-bar">
<div class="title">{{ngModel.title}}</div>
<div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
</div>
<div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
<div class="message-body">
<div class="message-action">
{{ngModel.actionText}}
@@ -24,6 +25,8 @@
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</a>
</div>
</div>
</div>

View File

@@ -1,17 +1,17 @@
<mct-container key="overlay">
<div class="t-message-list">
<div class="top-bar">
<div class="dialog-title">{{ngModel.dialog.title}}</div>
<mct-container key="overlay" class="t-message-list">
<div class="message-contents">
<div class="abs top-bar">
<div class="title">{{ngModel.dialog.title}}</div>
<div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 ||
ngModel.dialog.messages.length == 0">s</span>
</div>
</div>
<div class="w-messages">
<div class="abs message-body">
<mct-include
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
key="'message'" ng-model="msg.model"></mct-include>
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
key="'message'" ng-model="msg.model"></mct-include>
</div>
<div class="bottom-bar">
<div class="abs bottom-bar">
<a ng-repeat="dialogAction in ngModel.dialog.actions"
class="s-button major"
ng-click="dialogAction.action()">

View File

@@ -21,7 +21,7 @@
-->
<mct-container key="overlay">
<div class="abs top-bar">
<div class="dialog-title">{{ngModel.dialog.title}}</div>
<div class="title">{{ngModel.dialog.title}}</div>
<div class="hint">{{ngModel.dialog.hint}}</div>
</div>
<div class='abs editor'>

View File

@@ -121,8 +121,7 @@ define([
"key": "ElementsController",
"implementation": ElementsController,
"depends": [
"$scope",
"openmct"
"$scope"
]
},
{
@@ -186,8 +185,9 @@ define([
"key": "remove",
"category": "contextual",
"implementation": RemoveAction,
"name": "Delete...",
"description": "Delete this object.",
"cssClass": "icon-trash",
"name": "Remove",
"description": "Remove this object from its containing object.",
"depends": [
"navigationService"
]
@@ -299,6 +299,9 @@ define([
{
"key": "edit-elements",
"template": elementsTemplate,
"uses": [
"composition"
],
"gestures": [
"drop"
]
@@ -382,10 +385,7 @@ define([
]
},
{
"implementation": EditToolbarRepresenter,
"depends": [
"openmct"
]
"implementation": EditToolbarRepresenter
}
],
"constants": [

View File

@@ -61,12 +61,7 @@
<mct-representation key="representation.selected.key"
mct-object="representation.selected.key && domainObject"
class="abs flex-elem grows object-holder-main scroll"
toolbar="toolbar"
mct-selectable="{
item: domainObject.useCapability('adapter'),
oldItem: domainObject
}"
mct-init-select>
toolbar="toolbar">
</mct-representation>
</div><!--/ l-object-wrapper-inner -->
</div>

View File

@@ -25,7 +25,7 @@
ng-model="filterBy">
</mct-include>
<div class="flex-elem grows vscroll">
<ul class="tree" ng-if="composition.length > 0">
<ul class="tree">
<li ng-repeat="containedObject in composition | filter:searchElements">
<span class="tree-item">
<mct-representation
@@ -36,6 +36,5 @@
</span>
</li>
</ul>
<div ng-if="composition.length === 0">No contained elements</div>
</div>
</div>

View File

@@ -101,15 +101,10 @@ define(
*/
EditorCapability.prototype.finish = function () {
var domainObject = this.domainObject;
if (this.transactionService.isActive()) {
return this.transactionService.cancel().then(function () {
domainObject.getCapability("status").set("editing", false);
return domainObject;
});
} else {
return Promise.resolve(domainObject);
}
return this.transactionService.cancel().then(function () {
domainObject.getCapability("status").set("editing", false);
return domainObject;
});
};
/**

View File

@@ -28,6 +28,16 @@ define(
[],
function () {
function isDirty(domainObject) {
var navigatedObject = domainObject,
editorCapability = navigatedObject &&
navigatedObject.getCapability("editor");
return editorCapability &&
editorCapability.isEditContextRoot() &&
editorCapability.dirty();
}
function cancelEditing(domainObject) {
var navigatedObject = domainObject,
editorCapability = navigatedObject &&
@@ -49,7 +59,10 @@ define(
var removeCheck = navigationService
.checkBeforeNavigation(function () {
return "Continuing will cause the loss of any unsaved changes.";
if (isDirty(domainObject)) {
return "Continuing will cause the loss of any unsaved changes.";
}
return false;
});
$scope.$on('$destroy', function () {

View File

@@ -29,11 +29,7 @@ define(
*
* @constructor
*/
function ElementsController($scope, openmct) {
this.scope = $scope;
this.scope.composition = [];
var self = this;
function ElementsController($scope) {
function filterBy(text) {
if (typeof text === 'undefined') {
return $scope.searchText;
@@ -51,58 +47,10 @@ define(
}
}
function setSelection(selection) {
if (!selection[0]) {
return;
}
if (self.mutationListener) {
self.mutationListener();
delete self.mutationListener;
}
var domainObject = selection[0].context.oldItem;
self.refreshComposition(domainObject);
if (domainObject) {
self.mutationListener = domainObject.getCapability('mutation')
.listen(self.refreshComposition.bind(self, domainObject));
}
}
$scope.filterBy = filterBy;
$scope.searchElements = searchElements;
openmct.selection.on('change', setSelection);
setSelection(openmct.selection.get());
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
});
}
/**
* Gets the composition for the selected object and populates the scope with it.
*
* @param domainObject the selected object
* @private
*/
ElementsController.prototype.refreshComposition = function (domainObject) {
var refreshTracker = {};
this.currentRefresh = refreshTracker;
var selectedObjectComposition = domainObject && domainObject.useCapability('composition');
if (selectedObjectComposition) {
selectedObjectComposition.then(function (composition) {
if (this.currentRefresh === refreshTracker) {
this.scope.composition = composition;
}
}.bind(this));
} else {
this.scope.composition = [];
}
};
return ElementsController;
}
);

View File

@@ -38,7 +38,7 @@ define(
* @constructor
* @implements {Representer}
*/
function EditToolbarRepresenter(openmct, scope, element, attrs) {
function EditToolbarRepresenter(scope, element, attrs) {
var self = this;
// Mark changes as ready to persist
@@ -88,6 +88,12 @@ define(
commit("Changes from toolbar.");
}
}
// Avoid attaching scope to this;
// http://errors.angularjs.org/1.2.26/ng/cpws
this.setSelection = function (s) {
scope.selection = s;
};
this.clearExposedToolbar = function () {
// Clear exposed toolbar state (if any)
if (attrs.toolbar) {
@@ -103,8 +109,6 @@ define(
this.updateSelection = updateSelection;
this.toolbar = undefined;
this.toolbarObject = {};
this.openmct = openmct;
this.scope = scope;
// If this representation exposes a toolbar, set up watches
// to synchronize with it.
@@ -125,23 +129,26 @@ define(
// Represent a domain object using this definition
EditToolbarRepresenter.prototype.represent = function (representation) {
// Get the newest toolbar definition from the view
var definition = (representation || {}).toolbar || {};
var definition = (representation || {}).toolbar || {},
self = this;
// If we have been asked to expose toolbar state...
if (this.attrs.toolbar) {
// Initialize toolbar object
this.toolbar = new EditToolbar(definition, this.commit);
// Ensure toolbar state is exposed
this.exposeToolbar();
// Initialize toolbar (expose object to parent scope)
function initialize(def) {
// If we have been asked to expose toolbar state...
if (self.attrs.toolbar) {
// Initialize toolbar object
self.toolbar = new EditToolbar(def, self.commit);
// Ensure toolbar state is exposed
self.exposeToolbar();
}
}
// Add toolbar selection to scope.
this.scope.selection = new EditToolbarSelection(
this.scope,
this.openmct
);
// Initialize toolbar to current selection
this.updateSelection(this.scope.selection.all());
// Expose the toolbar object to the parent scope
initialize(definition);
// Create a selection scope
this.setSelection(new EditToolbarSelection());
// Initialize toolbar to an empty selection
this.updateSelection([]);
};
// Destroy; remove toolbar object from parent scope

View File

@@ -38,37 +38,10 @@ define(
* @memberof platform/commonUI/edit
* @constructor
*/
function EditToolbarSelection($scope, openmct) {
function EditToolbarSelection() {
this.selection = [{}];
this.selecting = false;
this.selectedObj = undefined;
this.openmct = openmct;
var self = this;
function setSelection(selection) {
var selected = selection[0];
if (selected && selected.context.toolbar) {
self.select(selected.context.toolbar);
} else {
self.deselect();
}
if (selected && selected.context.viewProxy) {
self.proxy(selected.context.viewProxy);
}
setTimeout(function () {
$scope.$apply();
});
}
$scope.$on("$destroy", function () {
self.openmct.selection.off('change', setSelection);
});
this.openmct.selection.on('change', setSelection);
setSelection(this.openmct.selection.get());
}
/**

View File

@@ -62,7 +62,6 @@ define(
);
mockTransactionService.commit.andReturn(fastPromise());
mockTransactionService.cancel.andReturn(fastPromise());
mockTransactionService.isActive = jasmine.createSpy('isActive');
mockStatusCapability = jasmine.createSpyObj(
"statusCapability",
@@ -142,7 +141,6 @@ define(
describe("finish", function () {
beforeEach(function () {
mockTransactionService.isActive.andReturn(true);
capability.edit();
capability.finish();
});
@@ -154,23 +152,6 @@ define(
});
});
describe("finish", function () {
beforeEach(function () {
mockTransactionService.isActive.andReturn(false);
capability.edit();
});
it("does not cancel transaction when transaction is not active", function () {
capability.finish();
expect(mockTransactionService.cancel).not.toHaveBeenCalled();
});
it("returns a promise", function () {
expect(capability.finish() instanceof Promise).toBe(true);
});
});
describe("dirty", function () {
var model = {};

View File

@@ -104,10 +104,10 @@ define(
mockEditorCapability.isEditContextRoot.andReturn(false);
mockEditorCapability.dirty.andReturn(false);
expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
expect(checkFn()).toBe(false);
mockEditorCapability.isEditContextRoot.andReturn(true);
expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
expect(checkFn()).toBe(false);
mockEditorCapability.dirty.andReturn(true);
expect(checkFn())

View File

@@ -27,70 +27,11 @@ define(
describe("The Elements Pane controller", function () {
var mockScope,
mockOpenMCT,
mockSelection,
mockDomainObject,
mockMutationCapability,
mockCompositionCapability,
mockCompositionObjects,
mockComposition,
mockUnlisten,
selectable = [],
controller;
function mockPromise(value) {
return {
then: function (thenFunc) {
return mockPromise(thenFunc(value));
}
};
}
function createDomainObject() {
return {
useCapability: function () {
return mockCompositionCapability;
}
};
}
beforeEach(function () {
mockComposition = ["a", "b"];
mockCompositionObjects = mockComposition.map(createDomainObject);
mockCompositionCapability = mockPromise(mockCompositionObjects);
mockUnlisten = jasmine.createSpy('unlisten');
mockMutationCapability = jasmine.createSpyObj("mutationCapability", [
"listen"
]);
mockMutationCapability.listen.andReturn(mockUnlisten);
mockDomainObject = jasmine.createSpyObj("domainObject", [
"getCapability",
"useCapability"
]);
mockDomainObject.useCapability.andReturn(mockCompositionCapability);
mockDomainObject.getCapability.andReturn(mockMutationCapability);
mockScope = jasmine.createSpyObj("$scope", ['$on']);
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off',
'get'
]);
mockSelection.get.andReturn([]);
mockOpenMCT = {
selection: mockSelection
};
selectable[0] = {
context: {
oldItem: mockDomainObject
}
};
spyOn(ElementsController.prototype, 'refreshComposition').andCallThrough();
controller = new ElementsController(mockScope, mockOpenMCT);
mockScope = jasmine.createSpy("$scope");
controller = new ElementsController(mockScope);
});
function getModel(model) {
@@ -122,63 +63,6 @@ define(
expect(objects.filter(mockScope.searchElements).length).toBe(4);
});
it("refreshes composition on selection", function () {
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(ElementsController.prototype.refreshComposition).toHaveBeenCalledWith(mockDomainObject);
});
it("listens on mutation and refreshes composition", function () {
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(mockDomainObject.getCapability).toHaveBeenCalledWith('mutation');
expect(mockMutationCapability.listen).toHaveBeenCalled();
expect(ElementsController.prototype.refreshComposition.calls.length).toBe(1);
mockMutationCapability.listen.mostRecentCall.args[0](mockDomainObject);
expect(ElementsController.prototype.refreshComposition.calls.length).toBe(2);
});
it("cleans up mutation listener when selection changes", function () {
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(mockMutationCapability.listen).toHaveBeenCalled();
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(mockUnlisten).toHaveBeenCalled();
});
it("does not listen on mutation for element proxy selectable", function () {
selectable[0] = {
context: {
elementProxy: {}
}
};
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(mockDomainObject.getCapability).not.toHaveBeenCalledWith('mutation');
});
it("checks concurrent changes to composition", function () {
var secondMockComposition = ["a", "b", "c"],
secondMockCompositionObjects = secondMockComposition.map(createDomainObject),
firstCompositionCallback,
secondCompositionCallback;
spyOn(mockCompositionCapability, "then").andCallThrough();
controller.refreshComposition(mockDomainObject);
controller.refreshComposition(mockDomainObject);
firstCompositionCallback = mockCompositionCapability.then.calls[0].args[0];
secondCompositionCallback = mockCompositionCapability.then.calls[1].args[0];
secondCompositionCallback(secondMockCompositionObjects);
firstCompositionCallback(mockCompositionObjects);
expect(mockScope.composition).toBe(secondMockCompositionObjects);
});
});
}
);

View File

@@ -29,14 +29,12 @@ define(
mockElement,
testAttrs,
mockUnwatch,
representer,
mockOpenMCT,
mockSelection;
representer;
beforeEach(function () {
mockScope = jasmine.createSpyObj(
'$scope',
['$on', '$watch', '$watchCollection', "commit", "$apply"]
['$on', '$watch', '$watchCollection', "commit"]
);
mockElement = {};
testAttrs = { toolbar: 'testToolbar' };
@@ -48,18 +46,7 @@ define(
mockScope.$parent.$watchCollection.andReturn(mockUnwatch);
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off',
'get'
]);
mockSelection.get.andReturn([]);
mockOpenMCT = {
selection: mockSelection
};
representer = new EditToolbarRepresenter(
mockOpenMCT,
mockScope,
mockElement,
testAttrs

View File

@@ -28,31 +28,13 @@ define(
var testProxy,
testElement,
otherElement,
selection,
mockSelection,
mockOpenMCT,
mockScope;
selection;
beforeEach(function () {
testProxy = { someKey: "some value" };
testElement = { someOtherKey: "some other value" };
otherElement = { yetAnotherKey: 42 };
mockSelection = jasmine.createSpyObj("selection", [
// 'select',
'on',
'off',
'get'
]);
mockSelection.get.andReturn([]);
mockOpenMCT = {
selection: mockSelection
};
mockScope = jasmine.createSpyObj('$scope', [
'$on',
'$apply'
]);
selection = new EditToolbarSelection(mockScope, mockOpenMCT);
selection = new EditToolbarSelection();
selection.proxy(testProxy);
});
@@ -109,20 +91,6 @@ define(
expect(selection.all()).toEqual([testProxy]);
});
it("cleans up selection on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.mostRecentCall.args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
});
}
);

View File

@@ -121,9 +121,6 @@ define([
};
UTCTimeFormat.prototype.parse = function (text) {
if (typeof text === 'number') {
return text;
}
return moment.utc(text, DATE_FORMATS).valueOf();
};

View File

@@ -41,7 +41,6 @@ define([
"./src/controllers/BannerController",
"./src/directives/MCTContainer",
"./src/directives/MCTDrag",
"./src/directives/MCTSelectable",
"./src/directives/MCTClickElsewhere",
"./src/directives/MCTResize",
"./src/directives/MCTPopup",
@@ -91,7 +90,6 @@ define([
BannerController,
MCTContainer,
MCTDrag,
MCTSelectable,
MCTClickElsewhere,
MCTResize,
MCTPopup,
@@ -330,13 +328,6 @@ define([
"$document"
]
},
{
"key": "mctSelectable",
"implementation": MCTSelectable,
"depends": [
"openmct"
]
},
{
"key": "mctClickElsewhere",
"implementation": MCTClickElsewhere,

View File

@@ -2,7 +2,7 @@
"metadata": {
"name": "openmct-symbols-16px",
"lastOpened": 0,
"created": 1506973656040
"created": 1505151140023
},
"iconSets": [
{
@@ -899,14 +899,6 @@
"prevSize": 24,
"code": 921904,
"tempChar": ""
},
{
"order": 139,
"id": 117,
"name": "icon-notebook",
"prevSize": 24,
"code": 921905,
"tempChar": ""
}
],
"metadata": {
@@ -3532,29 +3524,6 @@
{}
]
}
},
{
"id": 117,
"paths": [
"M896 110.8c0-79.8-55.4-127.4-123-105.4l-773 250.6h896v-145.2z",
"M896 320h-896v576c0 70.4 57.6 128 128 128h768c70.4 0 128-57.6 128-128v-448c0-70.4-57.6-128-128-128zM832 832h-384v-320h384v320z"
],
"attrs": [
{},
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 16,
"tags": [
"icon-notebook"
],
"colorPermutations": {
"1161751207457516161751": [
{},
{}
]
}
}
],
"colorThemes": [

View File

@@ -118,5 +118,4 @@
<glyph unicode="&#xe1128;" glyph-name="icon-topic" d="M454.36 483.36l86.3 86.3c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c19.328-19.358 42.832-34.541 69.047-44.082l1.313 171.722-57.64 57.64c-34.407 34.33-81.9 55.558-134.35 55.558s-99.943-21.228-134.354-55.562l-86.296-86.297c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-28.674 28.654v-172.14c19.045-7.022 41.040-11.084 63.984-11.084 52.463 0 99.966 21.239 134.379 55.587zM505.64 412.64l-86.3-86.3c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-86.294 86.294c-2 2-4.2 4-6.36 6v-197.36c33.664-30.72 78.65-49.537 128.031-49.537 52.44 0 99.923 21.22 134.333 55.541l86.296 86.296c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c2-2 4.2-4 6.36-6v197.36c-33.664 30.72-78.65 49.537-128.031 49.537-52.44 0-99.923-21.22-134.333-55.541zM832 960h-128v-192h127.66l0.34-0.34v-639.32l-0.34-0.34h-127.66v-192h128c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM320 128h-127.66l-0.34 0.34v639.32l0.34 0.34h127.66v192h-128c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h128v192z" />
<glyph unicode="&#xe1129;" glyph-name="icon-box-with-dashed-lines" d="M0 576h128v-256h-128v256zM128 831.78l0.22 0.22h191.78v128h-192c-70.606-0.215-127.785-57.394-128-127.979v-192.021h128v191.78zM128 64.22v191.78h-128v-192c0.215-70.606 57.394-127.785 127.979-128h192.021v128h-191.78zM384 960h256v-128h-256v128zM896 64.22l-0.22-0.22h-191.78v-128h192c70.606 0.215 127.785 57.394 128 127.979v192.021h-128v-191.78zM896 960h-192v-128h191.78l0.22-0.22v-191.78h128v192c-0.215 70.606-57.394 127.785-127.979 128zM896 576h128v-256h-128v256zM384 64h256v-128h-256v128zM256 704h512v-512h-512v512z" />
<glyph unicode="&#xe1130;" glyph-name="icon-summary-widget" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM847.8 349.6l-82.6-143.2-189.6 131.6 19.2-230h-165.4l19.2 230-189.6-131.6-82.6 143.2 208.6 98.4-208.8 98.4 82.6 143.2 189.6-131.6-19.2 230h165.4l-19.2-230 189.6 131.6 82.6-143.2-208.6-98.4 208.8-98.4z" />
<glyph unicode="&#xe1131;" glyph-name="icon-notebook" d="M896 849.2c0 79.8-55.4 127.4-123 105.4l-773-250.6h896v145.2zM896 640h-896v-576c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v448c0 70.4-57.6 128-128 128zM832 128h-384v320h384v-320z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -1,91 +1,91 @@
@include keyframes(rotation) {
100% { transform: rotate(360deg); }
100% { @include transform(rotate(360deg)); }
}
@include keyframes(rotation-centered) {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands) {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands-sticky) {
0% {
transform: translate(-50%, -50%) rotate(0deg);
@include transform(translate(-50%, -50%) rotate(0deg));
}
7% {
transform: translate(-50%, -50%) rotate(0deg);
@include transform(translate(-50%, -50%) rotate(0deg));
}
8% {
transform: translate(-50%, -50%) rotate(30deg);
@include transform(translate(-50%, -50%) rotate(30deg));
}
15% {
transform: translate(-50%, -50%) rotate(30deg);
@include transform(translate(-50%, -50%) rotate(30deg));
}
16% {
transform: translate(-50%, -50%) rotate(60deg);
@include transform(translate(-50%, -50%) rotate(60deg));
}
24% {
transform: translate(-50%, -50%) rotate(60deg);
@include transform(translate(-50%, -50%) rotate(60deg));
}
25% {
transform: translate(-50%, -50%) rotate(90deg);
@include transform(translate(-50%, -50%) rotate(90deg));
}
32% {
transform: translate(-50%, -50%) rotate(90deg);
@include transform(translate(-50%, -50%) rotate(90deg));
}
33% {
transform: translate(-50%, -50%) rotate(120deg);
@include transform(translate(-50%, -50%) rotate(120deg));
}
40% {
transform: translate(-50%, -50%) rotate(120deg);
@include transform(translate(-50%, -50%) rotate(120deg));
}
41% {
transform: translate(-50%, -50%) rotate(150deg);
@include transform(translate(-50%, -50%) rotate(150deg));
}
49% {
transform: translate(-50%, -50%) rotate(150deg);
@include transform(translate(-50%, -50%) rotate(150deg));
}
50% {
transform: translate(-50%, -50%) rotate(180deg);
@include transform(translate(-50%, -50%) rotate(180deg));
}
57% {
transform: translate(-50%, -50%) rotate(180deg);
@include transform(translate(-50%, -50%) rotate(180deg));
}
58% {
transform: translate(-50%, -50%) rotate(210deg);
@include transform(translate(-50%, -50%) rotate(210deg));
}
65% {
transform: translate(-50%, -50%) rotate(210deg);
@include transform(translate(-50%, -50%) rotate(210deg));
}
66% {
transform: translate(-50%, -50%) rotate(240deg);
@include transform(translate(-50%, -50%) rotate(240deg));
}
74% {
transform: translate(-50%, -50%) rotate(240deg);
@include transform(translate(-50%, -50%) rotate(240deg));
}
75% {
transform: translate(-50%, -50%) rotate(270deg);
@include transform(translate(-50%, -50%) rotate(270deg));
}
82% {
transform: translate(-50%, -50%) rotate(270deg);
@include transform(translate(-50%, -50%) rotate(270deg));
}
83% {
transform: translate(-50%, -50%) rotate(300deg);
@include transform(translate(-50%, -50%) rotate(300deg));
}
90% {
transform: translate(-50%, -50%) rotate(300deg);
@include transform(translate(-50%, -50%) rotate(300deg));
}
91% {
transform: translate(-50%, -50%) rotate(330deg);
@include transform(translate(-50%, -50%) rotate(330deg));
}
99% {
transform: translate(-50%, -50%) rotate(330deg);
@include transform(translate(-50%, -50%) rotate(330deg));
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
@include transform(translate(-50%, -50%) rotate(360deg));
}
}

View File

@@ -1,11 +0,0 @@
// At the last, hide .l-splash-holder and show .holder-all
.l-splash-holder.fadeout {
@include trans-prop-nice($props: opacity, $dur: 1000ms);
opacity: 0;
pointer-events: none;
}
.user-environ .holder-all {
opacity: 1;
pointer-events: inherit;
}

View File

@@ -94,19 +94,19 @@
/********************************************* FLEX STYLES */
.l-flex-row,
.l-flex-col {
display: flex;
flex-wrap: nowrap;
@include display(flex);
@include flex-wrap(nowrap);
.flex-elem {
min-height: 0; // Needed to allow element to shrink within parent
position: relative;
&:not(.grows) {
flex: 0 0 auto;
@include flex(0 0 auto);
&.flex-can-shrink {
flex: 0 1 auto;
@include flex(0 1 auto);
}
}
&.grows {
flex: 1 1 auto;
@include flex(1 1 auto);
}
&.contents-align-right {
text-align: right;
@@ -114,43 +114,38 @@
}
.flex-container {
// Apply to wrapping elements, mct-includes, etc.
display: flex;
flex-wrap: nowrap;
flex: 1 1 auto;
@include display(flex);
@include flex-wrap(nowrap);
@include flex(1 1 auto);
min-height:0;
}
}
.l-flex-row {
flex-direction: row;
&.flex-elem { flex: 1 1 auto; }
@include flex-direction(row);
&.flex-elem { @include flex(1 1 auto); }
> .flex-elem {
min-width: 0;
&.holder:not(:last-child) { margin-right: $interiorMargin; }
}
.flex-container { flex-direction: row; }
.flex-container { @include flex-direction(row); }
}
.l-flex-col {
flex-direction: column;
@include flex-direction(column);
> .flex-elem {
min-height: 0;
&.holder:not(:last-child) { margin-bottom: $interiorMarginLg; }
}
&.l-flex-accordion .flex-accordion-holder {
display: flex;
flex-direction: column;
//overflow: hidden !important;
}
.flex-container { flex-direction: column; }
.flex-container { @include flex-direction(column); }
}
.flex-fixed {
flex: 0 0 auto;
@include flex(0 0 auto);
}
.flex-justify-end {
justify-content: flex-end;
@include justify-content(flex-end);
}
/********************************************* POPUPS */

View File

@@ -99,7 +99,7 @@ $plotXBarH: 32px;
$plotLegendH: 20px;
$plotSwatchD: 8px;
// 1: Top, 2: right, 3: bottom, 4: left
$plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW);
$plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH, $plotYBarW);
/* min plot height is based on user testing to find minimum useful height */
$plotMinH: 95px;
/*************** Bubbles */

View File

@@ -25,7 +25,6 @@
}
.l-fixed-position-item {
border-width: 1px;
position: absolute;
&.s-not-selected {
opacity: 0.8;

View File

@@ -40,7 +40,7 @@
* Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json
* to generate font files
*/
font-family: 'symbolsfont-12px';
font-family: 'symbolsfont 12px';
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot');
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot?#iefix') format('embedded-opentype'),
url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.woff') format('woff'),
@@ -180,20 +180,6 @@ a.disabled {
@include ellipsize();
}
.no-selection {
// aka selection = "None". Used in palettes and their menu buttons.
$c: red; $s: 48%; $e: 52%;
@include background-image(linear-gradient(-45deg,
transparent $s - 5%,
$c $s,
$c $e,
transparent $e + 5%
));
background-repeat: no-repeat;
background-size: contain;
}
.scrolling,
.scroll {
overflow: auto;
@@ -248,12 +234,6 @@ a.disabled {
color: rgba(#fff, 0.2);
}
.comma-list span {
&:not(:first-child) {
&:before { content: ', '; }
}
}
.test-stripes {
@include bgDiagonalStripes();
}

View File

@@ -146,7 +146,6 @@ $glyph-icon-timer: '\e1127';
$glyph-icon-topic: '\e1128';
$glyph-icon-box-with-dashed-lines: '\e1129';
$glyph-icon-summary-widget: '\e1130';
$glyph-icon-notebook: '\e1131';
/************************** 16 PX CLASSES */
@@ -261,7 +260,6 @@ $glyph-icon-notebook: '\e1131';
.icon-topic { @include glyphBefore($glyph-icon-topic); }
.icon-box-with-dashed-lines { @include glyphBefore($glyph-icon-box-with-dashed-lines); }
.icon-summary-widget { @include glyphBefore($glyph-icon-summary-widget); }
.icon-notebook { @include glyphBefore($glyph-icon-notebook); }
/************************** 12 PX CLASSES */
.icon-crosshair-12px { @include glyphBefore($glyph-icon-crosshair,'symbolsfont-12px'); }

View File

@@ -44,12 +44,6 @@
}
}
.t-alert-unsynced {
@extend .icon-alert-triangle;
color: $colorPausedBg;
}
.bar .ui-symbol {
display: inline-block;
}
@@ -83,9 +77,22 @@
height: auto; width: auto;
position: absolute;
left: 0; top: 0; right: 0; bottom: 20%;
transform-origin: bottom left;
transform: scale(0.3);
@include transform-origin(bottom left);
@include transform(scale(0.3));
z-index: 2;
}
/* .t-item-icon-glyph {
&:after {
color: $colorIconLink;
content: '\e921'; //$glyph-icon-link;
height: auto; width: auto;
position: absolute;
left: 0; top: 0; right: 0; bottom: 20%;
@include transform-origin(bottom left);
@include transform(scale(0.3));
z-index: 2;
}
}*/
}
}

View File

@@ -26,6 +26,5 @@
display: block;
height: 100%;
width: 100%;
border: none;
}
}

View File

@@ -78,7 +78,7 @@
}
}
.form-row {
align-items: center;
@include align-items(center);
border: none !important;
margin-bottom: 0 !important;
padding: $interiorMarginSm 0;

View File

@@ -37,7 +37,7 @@
/********************************* CONTROLS */
@import "controls/breadcrumb";
@import "controls/buttons";
@import "controls/palette";
@import "controls/color-palette";
@import "controls/controls";
@import "controls/lists";
@import "controls/menus";
@@ -70,17 +70,13 @@
@import "fixed-position";
@import "lists/tabular";
@import "plots/plots-main";
@import "plots/legend";
@import "iframe";
@import "views";
@import "items/item";
@import "mobile/item";
/********************************* TO BE MOVED */
@import "autoflow";
@import "features/imagery";
@import "features/time-display";
@import "widgets";
/********************************* APP STARTUP */
@import "app-start";

View File

@@ -382,7 +382,7 @@
/* This doesn't work on an element inside an element with absolute positioning that has height: auto */
//position: relative;
top: 50%;
transform: translateY(-50%);
@include transform(translateY(-50%));
}
@mixin verticalCenterBlock($holderH, $itemH) {

View File

@@ -22,8 +22,8 @@
// mct-representation surrounding an object-label key="'label'"
.rep-object-label {
flex-direction: row;
flex: 1 1 auto;
@include flex-direction(row);
@include flex(1 1 auto);
height: inherit;
line-height: inherit;
min-width: 0;

View File

@@ -50,6 +50,7 @@
content:'';
font-family: symbolsfont;
font-size: 0.8em;
display: inline;
margin-right: $interiorMarginSm;
}
}

View File

@@ -5,7 +5,6 @@
}
.l-view-section {
//@include test(orange, 0.1);
@include absPosDefault(0);
h2 {
color: #fff;

View File

@@ -1,306 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/************************************************************* WIDGET OBJECT */
.l-summary-widget {
// Widget layout classes here
@include ellipsize();
display: inline-block;
text-align: center;
.widget-label:before {
// Widget icon
font-size: 0.9em;
margin-right: $interiorMarginSm;
}
}
.s-summary-widget {
// Widget style classes here
@include boxShdw($shdwBtns);
border-radius: $basicCr;
border-style: solid;
border-width: 1px;
box-sizing: border-box;
cursor: default;
font-size: 0.8rem;
padding: $interiorMarginLg $interiorMarginLg * 2;
&[href] {
cursor: pointer;
}
}
.widget-edit-holder {
// Hide edit area when in browse mode
display: none;
}
.widget-rule-header {
@extend .l-flex-row;
align-items: center;
margin-bottom: $interiorMargin;
> .flex-elem {
&:not(:first-child) {
margin-left: $interiorMargin;
}
}
}
.widget-rules-wrapper,
.widget-rule-content,
.w-widget-test-data-content {
@include trans-prop-nice($props: (height, min-height, opacity), $dur: 250ms);
min-height: 0;
height: 0;
opacity: 0;
overflow: hidden;
pointer-events: none;
}
.widget-rules-wrapper {
flex: 1 1 auto !important;
}
.widget-rule-content.expanded {
overflow: visible !important;
min-height: 50px;
height: auto;
opacity: 1;
pointer-events: inherit;
}
.w-widget-test-data-content {
.l-enable {
padding: $interiorMargin 0;
}
.w-widget-test-data-items {
max-height: 20vh;
overflow-y: scroll !important;
padding-right: $interiorMargin;
}
}
.l-widget-thumb-wrapper,
.l-compact-form label {
$ruleLabelW: 40%;
$ruleLabelMaxW: 150px;
display: flex;
max-width: $ruleLabelMaxW;
width: $ruleLabelW;
}
.t-message-widget-no-data {
display: none;
}
/********************************************************** EDITING A WIDGET */
.s-status-editing > mct-view > .w-summary-widget {
// Classes for editor layout while editing a widget
// This selector is ugly and brittle, but needed to prevent interface from showing when widget is in a layout
// being edited.
@include absPosDefault();
@extend .l-flex-col;
> .l-summary-widget {
// Main view of the summary widget
// Give some airspace and center the widget in the area
margin: 30px auto;
}
.widget-edit-holder {
display: flex; // Overrides `display: none` during Browse mode
.flex-accordion-holder {
// Needed because otherwise accordion elements "creep" when contents expand and contract
display: block !important;
}
&.expanded-widget-test-data {
.w-widget-test-data-content {
min-height: 50px;
height: auto;
opacity: 1;
pointer-events: inherit;
}
&:not(.expanded-widget-rules) {
// Test data is expanded and rules are collapsed
// Make text data take up all the vertical space
.flex-accordion-holder { display: flex; }
.widget-test-data {
flex-grow: 999999;
}
.w-widget-test-data-items {
max-height: inherit;
}
}
}
&.expanded-widget-rules {
.widget-rules-wrapper {
min-height: 50px;
height: auto;
opacity: 1;
pointer-events: inherit;
}
}
}
&.s-status-no-data {
.widget-edit-holder {
opacity: 0.3;
pointer-events: none;
}
.t-message-widget-no-data {
display: flex;
}
}
.l-compact-form {
// Overrides on .l-compact-form
ul {
&:last-child { margin: 0; }
li {
align-items: flex-start;
flex-wrap: nowrap;
line-height: 230%; // Provide enough space when controls wrap
padding: 2px 0;
&:not(.widget-rule-header) {
&:not(.connects-to-previous) {
border-top: 1px solid $colorFormLines;
}
}
&.connects-to-previous {
padding: $interiorMargin 0;
}
> label {
display: block; // Needed to align text to right
text-align: right;
}
}
}
&.s-widget-test-data-item {
// Single line of ul li label span, etc.
ul {
li {
border: none !important;
> label {
display: inline-block;
width: auto;
text-align: left;
}
}
}
}
}
}
.widget-edit-holder {
font-size: 0.8rem;
}
.widget-rules-wrapper {
// Wrapper area that holds n rules
box-sizing: border-box;
overflow-y: scroll;
padding-right: $interiorMargin;
}
.l-widget-rule,
.l-widget-test-data-item {
box-sizing: border-box;
margin-bottom: $interiorMarginSm;
padding: $interiorMargin $interiorMarginLg;
}
.l-widget-thumb-wrapper {
@extend .l-flex-row;
align-items: center;
> span { display: block; }
.grippy-holder,
.view-control {
margin-right: $interiorMargin;
width: 1em;
height: 1em;
}
.widget-thumb {
flex: 1 1 auto;
width: 100%;
}
}
.rule-title {
flex: 0 1 auto;
color: pullForward($colorBodyFg, 50%);
}
.rule-description {
flex: 1 1 auto;
@include ellipsize();
color: pushBack($colorBodyFg, 20%);
}
.s-widget-rule,
.s-widget-test-data-item {
background-color: rgba($colorBodyFg, 0.1);
border-radius: $basicCr;
}
.widget-thumb {
@include ellipsize();
@extend .s-summary-widget;
@extend .l-summary-widget;
padding: $interiorMarginSm $interiorMargin;
}
// Hide and show elements in the rule-header on hover
.l-widget-rule,
.l-widget-test-data-item {
.grippy,
.l-rule-action-buttons-wrapper,
.l-condition-action-buttons-wrapper,
.l-widget-test-data-item-action-buttons-wrapper {
@include trans-prop-nice($props: opacity, $dur: 500ms);
opacity: 0;
}
&:hover {
.grippy,
.l-rule-action-buttons-wrapper,
.l-widget-test-data-item-action-buttons-wrapper {
@include trans-prop-nice($props: opacity, $dur: 0);
opacity: 1;
}
}
.l-rule-action-buttons-wrapper {
.t-delete {
margin-left: 10px;
}
}
.t-condition {
&:hover {
.l-condition-action-buttons-wrapper {
@include trans-prop-nice($props: opacity, $dur: 0);
opacity: 1;
}
}
}
}

View File

@@ -19,42 +19,60 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
.l-color-palette {
$d: 16px;
$colorsPerRow: 10;
$m: 1;
$colorSelectedColor: #fff;
define(
[],
function () {
box-sizing: border-box;
padding: $interiorMargin !important;
/**
* The mct-selectable directive allows selection functionality
* (click) to be attached to specific elements.
*
* @memberof platform/commonUI/general
* @constructor
*/
function MCTSelectable(openmct) {
.l-palette-row {
@include clearfix;
line-height: $d;
width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
// Link; install event handlers.
function link(scope, element, attrs) {
var removeSelectable = openmct.selection.selectable(
element[0],
scope.$eval(attrs.mctSelectable),
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
);
scope.$on("$destroy", function () {
removeSelectable();
});
.l-palette-item {
box-sizing: border-box;
@include txtShdwSubtle(0.8);
@include trans-prop-nice-fade(0.25s);
border: 1px solid transparent;
color: $colorSelectedColor;
display: block;
float: left;
height: $d; width: $d;
line-height: $d * 0.9;
margin: 0 ($m * 1px) ($m * 1px) 0;
text-align: center;
&:before {
// Check mark for selected items
font-size: 0.8em;
}
}
return {
// mct-selectable only makes sense as an attribute
restrict: "A",
// Link function, to install event handlers
link: link
};
.s-palette-item {
&:hover {
@include trans-prop-nice-fade(0);
border-color: $colorSelectedColor !important;
}
}
}
.l-palette-item-label {
margin-left: $interiorMargin;
}
return MCTSelectable;
}
);
&.l-option-row {
margin-bottom: $interiorMargin;
.s-palette-item {
border-color: $colorBodyFg;
}
}
}
}

View File

@@ -150,26 +150,6 @@
}
}
/******************************************************** VIEW CONTROLS */
// Expand/collapse > and v arrows, used in tree and plot legend
// Moved this over from a tree-only context 5/18/17
.view-control {
@extend .ui-symbol;
cursor: pointer;
height: 1em; width: 1em;
line-height: inherit;
&:before {
position: absolute;
@include trans-prop-nice(transform, 100ms);
content: $glyph-icon-arrow-right;
@include transform-origin(center);
}
&.expanded:before {
@include transform(rotate(90deg));
}
}
/******************************************************** CUSTOM CHECKBOXES */
label.checkbox.custom,
label.radio.custom {
@@ -281,7 +261,7 @@ input[type="number"] {
input[type="text"].lg { width: 100% !important; }
.l-input-med input[type="text"],
input[type="text"].med { width: 200px !important; }
input[type="text"].sm, input[type="number"].sm { width: 50px !important; }
input[type="text"].sm { width: 50px !important; }
.l-numeric input[type="text"],
input[type="text"].numeric { text-align: right; }
@@ -337,10 +317,14 @@ input[type="text"].s-input-inline,
.select {
@include btnSubtle($bg: $colorSelectBg);
@extend .icon-arrow-down; // Context arrow
@if $shdwBtns != none {
margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
}
display: inline-block;
padding: 0 $interiorMargin;
overflow: hidden;
position: relative;
line-height: $formInputH;
select {
@include appearance(none);
box-sizing: border-box;
@@ -356,13 +340,11 @@ input[type="text"].s-input-inline,
}
}
&:before {
transform: translateY(-50%);
pointer-events: none;
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
display: block;
pointer-events: none;
position: absolute;
right: $interiorMargin;
top: 50%;
right: $interiorMargin; top: 0;
}
}
@@ -414,7 +396,8 @@ input[type="text"].s-input-inline,
.l-elem-wrapper {
mct-representation {
// Holds the context-available item
// Must have min-width to make flex work properly in Safari
// Must have min-width to make flex work properly
// in Safari
min-width: 0.7em;
}
}
@@ -434,7 +417,7 @@ input[type="text"].s-input-inline,
.context-available {
font-size: 0.7em;
flex: 0 0 1;
@include flex(0 0 1);
}
.t-object-alert {
@@ -580,6 +563,7 @@ input[type="text"].s-input-inline,
height: $h;
margin-top: 1 + floor($h/2) * -1;
@include btnSubtle(pullForward($colorBtnBg, 10%));
//border-radius: 50% !important;
}
@mixin sliderKnobRound() {
@@ -594,6 +578,7 @@ input[type="text"].s-input-inline,
input[type="range"] {
// HTML5 range inputs
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
background: transparent; /* Otherwise white in Chrome */
&:focus {
@@ -675,14 +660,14 @@ input[type="range"] {
.l-calendar {
$colorMuted: pushBack($colorMenuFg, 30%);
ul.l-cal-row {
display: flex;
flex-flow: row nowrap;
@include display(flex);
@include flex-flow(row nowrap);
margin-top: 1px;
&:first-child {
margin-top: 0;
}
li {
flex: 1 0;
@include flex(1 0);
margin-left: 1px;
padding: $interiorMargin;
text-align: center;
@@ -751,30 +736,6 @@ textarea {
}
}
.view-switcher,
.t-btn-view-large {
@include trans-prop-nice-fade($controlFadeMs);
}
.view-control {
@extend .icon-arrow-right;
cursor: pointer;
font-size: 0.75em;
&:before {
position: absolute;
@include trans-prop-nice(transform, 100ms);
transform-origin: center;
}
&.expanded:before {
transform: rotate(90deg);
}
}
.grippy {
@extend .icon-grippy;
cursor: move;
}
/******************************************************** BROWSER ELEMENTS */
body.desktop {
::-webkit-scrollbar {

View File

@@ -29,27 +29,23 @@
}
.icon {
font-size: 16px;
font-size: 16px; //120%;
}
.title-label {
margin-left: $interiorMarginSm;
}
.icon-swatch,
.color-swatch {
// Used in color menu buttons in toolbar
$d: 10px;
display: inline-block;
border: 1px solid rgba($colorBtnFg, 0.2);
height: $d; width: $d;
line-height: $d;
height: $d;
width: $d;
vertical-align: middle;
margin-left: $interiorMarginSm;
margin-top: -2px;
&:not(.no-selection) {
border-color: transparent;
}
}
&:after {
@@ -77,14 +73,6 @@
position: relative;
}
.s-menu {
border-radius: $basicCr;
@include containerSubtle($colorMenuBg, $colorMenuFg);
@include boxShdw($shdwMenu);
@include txtShdw($shdwMenuText);
padding: $interiorMarginSm 0;
}
.menu {
border-radius: $basicCr;
@include containerSubtle($colorMenuBg, $colorMenuFg);

View File

@@ -19,7 +19,7 @@
* 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%;
$bgPbD: 10%;
@@ -120,11 +120,7 @@
}
.status-indicator {
background: none !important;
margin-right: $interiorMarginSm;
&[class*='s-status']:before {
font-size: 1em;
}
}
.count {
@@ -140,7 +136,7 @@
}
}
/******************************************************************* MESSAGE BANNERS */
/* Styles for messages and message banners */
.message {
&.block {
border-radius: $basicCr;
@@ -157,16 +153,16 @@
$lh: $ueFooterH - ($m*2) - 1;
box-sizing: border-box;
@include ellipsize();
display: flex;
flex-direction: row;
align-items: center;
@include display(flex);
@include flex-direction(row);
@include align-items(center);
position: absolute;
top: $m; right: auto; bottom: $m; left: 50%;
height: auto; width: auto;
line-height: $lh;
max-width: 300px;
padding: 0 $interiorMargin 0 $interiorMargin;
transform: translateX(-50%);
@include transform(translateX(-50%));
&.minimized {
@include transition-property(left, opacity);
@@ -185,7 +181,7 @@
}
.banner-elem {
flex: 0 1 auto;
@include flex(0 1 auto);
margin-left: $interiorMargin;
}
a {
@@ -196,6 +192,7 @@
padding: 0 $interiorMargin;
}
.close {
//@include test(red, 0.7);
cursor: pointer;
font-size: 7px;
width: 8px;
@@ -239,147 +236,132 @@
}
}
/******************************************************************* MESSAGES */
/* Contexts:
In .t-message-list
In .overlay as a singleton
Inline in the view area
*/
// Archetypal message
.l-message {
$iconW: 32px;
display: flex;
flex-direction: row;
align-items: stretch;
padding: $interiorMarginLg;
&:before {
// Icon
flex: 0 1 auto;
@mixin messageBlock($iconW: 32px) {
.type-icon.message-type {
@include txtShdw($shdwStatusIc);
@extend .icon-bell;
color: $colorStatusDefault;
font-size: $iconW;
padding: 1px;
width: $iconW + 2;
margin-right: $interiorMarginLg;
}
&.message-severity-info:before {
.message-severity-info .type-icon.message-type {
@extend .icon-info;
color: $colorInfo;
}
&.message-severity-alert:before {
.message-severity-alert .type-icon.message-type {
@extend .icon-bell;
color: $colorWarningLo;
}
&.message-severity-error:before {
.message-severity-error .type-icon.message-type {
@extend .icon-alert-rect;
color: $colorWarningHi;
}
}
/* Paths:
t-dialog | t-dialog-sm > t-message-single | t-message-list > overlay > holder > contents > l-message >
message-type > (icon)
message-contents >
top-bar >
title
hint
editor >
(if displaying list of messages)
ul > li > l-message >
... same as above
bottom-bar
*/
.w-message-contents {
flex: 1 1 auto;
display: flex;
flex-direction: column;
> div,
> span {
//@include test(red);
margin-bottom: $interiorMargin;
.l-message {
@include display(flex);
@include flex-direction(row);
@include align-items(stretch);
.type-icon.message-type {
@include flex(0 1 auto);
position: relative;
}
.message-contents {
@include flex(1 1 auto);
margin-left: $overlayMargin;
position: relative;
.message-body {
flex: 1 1 100%;
}
}
// Singleton in an overlay dialog
.t-message-single .l-message,
.t-message-single.l-message {
$iconW: 80px;
@include absPosDefault();
padding: 0;
&:before {
font-size: $iconW;
width: $iconW + 2;
}
.title {
font-size: 1.2em;
}
}
// Singleton inline in a view
.t-message-inline .l-message,
.t-message-inline.l-message {
border-radius: $controlCr;
&.message-severity-info { background-color: rgba($colorInfo, 0.3); }
&.message-severity-alert { background-color: rgba($colorWarningLo, 0.3); }
&.message-severity-error { background-color: rgba($colorWarningHi, 0.3); }
.w-message-contents.l-message-body-only {
.top-bar,
.message-body {
margin-top: $interiorMargin;
margin-bottom: $interiorMarginLg * 2;
}
}
}
// In a list
.t-message-list {
@include absPosDefault();
display: flex;
flex-direction: column;
> div,
> span {
margin-bottom: $interiorMargin;
// Message as singleton
.t-message-single {
@include messageBlock(80px);
}
body.desktop .t-message-single {
.l-message,
.bottom-bar {
@include absPosDefault();
}
.w-messages {
flex: 1 1 100%;
overflow-y: auto;
padding-right: $interiorMargin;
.bottom-bar {
top: auto;
height: $ovrFooterH;
}
// Each message
.l-message {
border-radius: $controlCr;
background: rgba($colorOvrFg, 0.1);
margin-bottom: $interiorMargin;
.hint,
.bottom-bar {
text-align: left;
}
}
}
@include phonePortrait {
.t-message-single .l-message,
.t-message-single.l-message {
flex-direction: column;
&:before {
margin-right: 0;
.t-message-single {
.l-message {
@include flex-direction(column);
.message-contents { margin-left: 0; }
}
.type-icon.message-type {
margin-bottom: $interiorMarginLg;
text-align: center;
width: 100%;
text-align: center;
}
.bottom-bar {
text-align: center;
.s-button {
display: block;
width: 100%;
text-align: center !important;
}
}
}
// Messages in list
.t-message-list {
@include messageBlock(32px);
.message-contents {
.l-message {
border-radius: $controlCr;
background: rgba($colorOvrFg, 0.1);
margin-bottom: $interiorMargin;
padding: $interiorMarginLg;
.message-contents,
.bottom-bar {
position: relative;
}
.message-contents {
font-size: 0.9em;
margin-left: $interiorMarginLg;
.message-action { color: pushBack($colorOvrFg, 20%); }
.bottom-bar { text-align: left; }
}
.top-bar,
.message-body {
margin-bottom: $interiorMarginLg;
}
}
}
}
body.desktop .t-message-list {
.w-message-contents { padding-right: $interiorMargin; }
.message-contents .l-message { margin-right: $interiorMarginLg; }
}
// Alert elements in views
@@ -398,6 +380,10 @@ body.desktop .t-message-list {
.object-header {
.t-object-alert {
display: inline;
&.t-alert-unsynced {
@extend .icon-alert-triangle;
color: $colorPausedBg;
}
}
}
}

View File

@@ -1,89 +0,0 @@
/*****************************************************************************
* 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-palette {
box-sizing: border-box;
padding: $interiorMargin !important;
}
.l-palette-row {
$d: 16px;
$m: 1;
$colorsPerRow: 10;
display: flex;
flex-wrap: wrap;
line-height: $d;
width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
&.l-option-row {
margin-bottom: $interiorMargin;
.s-palette-item {
border-color: $colorPaletteFg;
}
}
.l-palette-item {
box-sizing: border-box;
display: block;
height: $d; width: $d;
min-width: $d;
line-height: $d * 0.9;
margin: 0 ($m * 1px) ($m * 1px) 0;
position: relative;
text-align: center;
}
}
.s-palette-item {
border: 1px solid transparent;
color: $colorPaletteFg;
text-shadow: $shdwPaletteFg;
@include trans-prop-nice-fade(0.25s);
&:hover {
@include trans-prop-nice-fade(0);
border-color: $colorPaletteSelected !important;
}
&.selected {
border-color: $colorPaletteSelected;
box-shadow: $shdwPaletteSelected; //Needed to see selection rect on light colored swatches
}
}
.l-palette-item-label {
margin-left: $interiorMargin;
}
.l-inline-palette {
.l-palette-row {
width: 100%;
.l-palette-item {
//@include display(flex);
@include flex(1 0 auto);
margin: 1px;
min-width: auto;
width: auto;
&:before {
content: '';
padding-top: 75%;
}
}
}
}

View File

@@ -80,32 +80,23 @@
// Editing Grids
.l-grid-holder {
display: block;
.l-grid {
&.l-grid-x { @include bgTicks($colorGridLines, 'x'); }
&.l-grid-y { @include bgTicks($colorGridLines, 'y'); }
}
}
// Display grid when selected or selection parent.
.s-selected .l-grid-holder,
.s-selected-parent .l-grid-holder {
display: block;
// Prevent nested frames from showing their grids
.t-frame-outer .l-grid-holder { display: none !important; }
// Prevent nested elements from showing s-hover-border
.t-frame-outer .s-hover-border {
border: none !important;
}
// Display in nested frames...
.t-frame-outer {
// ...when drilled in or selection parent...
&.s-drilled-in, &.s-selected-parent {
.l-grid-holder {
display: block;
}
.t-frame-outer:not(.s-drilled-in) .l-grid-holder {
display: none;
}
}
// ...but hide otherwise.
.l-grid-holder {
display: none;
}
// Prevent nested frames from being selectable until we have proper sub-object editing
.t-frame-outer .t-frame-outer {
pointer-events: none;
}
}

View File

@@ -15,7 +15,7 @@
margin-bottom: $interiorMargin;
}
.l-image-main-controlbar {
&.l-flex-row { align-items: center; }
&.l-flex-row { @include align-items(center); }
}
}

View File

@@ -34,10 +34,13 @@
}
.btns-add-remove {
// background: rgba(#ff0000, 0.3);;
margin-top: 150px;
.s-button {
display: block;
//font-size: 1.5em;
margin-bottom: $interiorMargin;
//padding: 10px;
text-align: center;
}
}

View File

@@ -20,19 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.section-header {
border-radius: $basicCr;
background: $colorFormSectionHeader;
color: lighten($colorBodyFg, 20%);
font-size: inherit;
margin: $interiorMargin 0;
padding: $formTBPad $formLRPad;
text-transform: uppercase;
.view-control {
display: inline-block;
margin-right: $interiorMargin;
width: 1em;
height: 1em;
}
}
.form {
@@ -45,7 +33,7 @@
&.grows {
.l-section-body,
.form-row {
flex: 1 1 auto;
@include flex(1 1 auto);
.wrapper {
height: 100%;
}
@@ -53,6 +41,15 @@
}
}
.section-header {
border-radius: $basicCr;
background: $colorFormSectionHeader;
$c: lighten($colorBodyFg, 20%);
color: $c;
font-size: 0.8em;
padding: $formTBPad $formLRPad;
}
.form-row {
$m: $interiorMargin;
box-sizing: border-box;
@@ -60,6 +57,9 @@
margin-bottom: $interiorMarginLg * 2;
padding: $formTBPad 0;
position: relative;
//&ng-form {
// display: block;
//}
&.first {
border-top: none;
@@ -87,7 +87,7 @@
.controls {
order: 2;
position: relative;
flex: 1 1 auto;
@include flex(1 1 auto);
.l-composite-control {
&.l-checkbox {
@@ -124,16 +124,16 @@
>.label,
>.controls {
line-height: inherit;
min-height: inherit;
min-height: inherit;;
}
>.label {
flex: 1 1 auto;
@include flex(1 1 auto);
min-width: 0;
width: auto;
order: 2;
}
>.controls {
flex: 0 0 auto;
@include flex(0 0 auto);
margin-right: $interiorMargin;
order: 1;
}
@@ -141,7 +141,7 @@
.l-controls-under.l-flex-row {
// Change to use column layout
flex-direction: column;
@include flex-direction(column);
.flex-elem {
margin-bottom: $interiorMarginLg;
}
@@ -171,106 +171,3 @@
padding: $interiorMargin;
}
}
/**************************************************************************** COMPACT FORM */
// ul > li > label, control
// Make a new UL for each form section
// Allow control-first, controls-below
// TO-DO: migrate work in branch ch-plot-styling that users .inspector-config to use classes below instead
.l-compact-form .tree ul li,
.l-compact-form ul li {
padding: 2px 0;
}
.l-compact-form {
$labelW: 40%;
$minW: $labelW;
ul {
margin-bottom: $interiorMarginLg;
li {
display: flex;
flex-wrap: wrap;
align-items: center;
label,
.control {
display: flex;
}
label {
line-height: inherit;
width: $labelW;
}
.controls {
flex-grow: 1;
margin-left: $interiorMargin;
input[type="text"],
input[type="search"],
input[type="number"],
.select {
height: $btnStdH;
line-height: $btnStdH;
vertical-align: middle;
}
.e-control {
// Individual form controls
&:not(:first-child) {
margin-left: $interiorMarginSm;
}
}
}
&.connects-to-previous {
padding-top: 0;
}
&.section-header {
margin-top: $interiorMarginLg;
border-top: 1px solid $colorFormLines;
}
&.controls-first {
.control {
flex-grow: 0;
margin-right: $interiorMargin;
min-width: 0;
order: 1;
width: auto;
}
label {
flex-grow: 1;
order: 2;
width: auto;
}
}
&.controls-under {
display: block;
.control, label {
display: block;
width: auto;
}
ul li {
border-top: none !important;
padding: 0;
}
}
}
}
.form-error {
// Block element that visually flags an error and contains a message
background-color: $colorFormFieldErrorBg;
color: $colorFormFieldErrorFg;
border-radius: $basicCr;
display: block;
padding: 1px 6px;
&:before {
content: $glyph-icon-alert-triangle;
display: inline;
font-family: symbolsfont;
margin-right: $interiorMarginSm;
}
}
}

View File

@@ -101,7 +101,7 @@
line-height: inherit;
position: absolute;
top: 50%;
transform: translateY(-50%);
@include transform(translateY(-50%));
z-index: 1;
}

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
@mixin spinner($b: 5px, $c: $colorKey) {
transform-origin: center;
@include transform-origin(center);
@include animation-name(rotation-centered);
@include animation-duration(0.5s);
@include animation-iteration-count(infinite);

View File

@@ -85,14 +85,14 @@
z-index: 1;
.item-type,
.t-item-icon {
transform: translateX(-50%) translateY(-55%);
@include transform(translateX(-50%) translateY(-55%));
position: absolute;
top: 50%; left: 50%;
font-size: $iconD * 0.95;
&.l-icon-link {
.t-item-icon-glyph {
&:after {
transform: scale(0.25);
@include transform(scale(0.25));
}
}
}

View File

@@ -138,7 +138,7 @@ body.phone.portrait {
}
.pane.right.items {
left: 0 !important;
transform: translateX($proporMenuOnly);
@include transform(translateX($proporMenuOnly));
.holder-object-and-inspector {
opacity: 0;
}

View File

@@ -40,10 +40,10 @@ body.touch {
&:before {
content: $glyph-icon-arrow-down;
left: 50%;
transform: translateX(-50%) rotate(-90deg);
@include transform(translateX(-50%) rotate(-90deg));
}
&.expanded:before {
transform: translateX(-50%) rotate(0deg);
@include transform(translateX(-50%) rotate(0deg));
}
}
}

View File

@@ -79,7 +79,6 @@
// Dialog boxes, size constrained and centered in desktop/tablet
&.l-dialog {
font-size: 0.8rem;
.s-button {
&:not(.major) {
@include btnSubtle($bg: $colorOvrBtnBg, $bgHov: pullForward($colorOvrBtnBg, 10%), $fg: $colorOvrBtnFg, $fgHov: $colorOvrBtnFg, $ic: $colorOvrBtnFg, $icHov: $colorOvrBtnFg);
@@ -89,7 +88,7 @@
> .abs.outer-holder {
@include desktopandtablet {
$max: 1280px;
transform: translate(-50%, -50%);
@include transform(translateX(-50%) translateY(-50%));
border-radius: $overlayCr;
top: 50%; right: auto; bottom: auto; left: 50%;
width: 70%; height: 70%;
@@ -101,7 +100,7 @@
.editor .form .form-row.l-flex-row {
// Display elements in a columnar view
flex-direction: column;
@include flex-direction(column);
> .flex-elem {
&:not(:first-child) {
margin-top: $interiorMargin;
@@ -126,9 +125,9 @@
@include containerSubtle($colorOvrBg, $colorOvrFg);
}
.dialog-title {
.title {
@include ellipsize();
font-size: 1.5em;
font-size: 1.2em;
line-height: 120%;
margin-bottom: $interiorMargin;
}

View File

@@ -1,208 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
.gl-plot {
.gl-plot-legend {
min-height: $plotLegendH;
.view-control {
font-size: 1em;
margin-right: $interiorMarginSm;
}
table {
table-layout: fixed;
tr {
display: table-row;
}
th,
td {
@include ellipsize(); // Note: this won't work if table-layout uses anything other than fixed.
display: table-cell;
padding: 1px 3px; // Tighter than standard tabular padding
}
}
&.hover-on-plot {
// User is hovering over the plot to get a value at a point
.hover-value-enabled {
background-color: $legendHoverValueBg;
border-radius: $smallCr;
padding: 0 $interiorMarginSm;
&:before {
opacity: 0.5;
}
&.cursor-hover,
.value-to-display-nearestTimestamp,
.value-to-display-nearestValue
{
@extend .icon-crosshair-12px;
&:before {
font-size: 9px;
}
}
&.value-to-display-min:before {
content: 'MIN ';
}
&.value-to-display-max:before {
content: 'MAX ';
}
}
}
}
&.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; }
&.plot-legend-expanded .plot-wrapper-collapsed-legend { display: none; }
/***************** GENERAL STYLES, ALL STATES */
.plot-legend-item {
// General styles for legend items, both expanded and collapsed legend states
.plot-series-color-swatch {
border-radius: $smallCr;
border: 1px solid $colorBodyBg;
display: inline-block;
height: $plotSwatchD;
width: $plotSwatchD;
}
.plot-series-name {
display: inline;
}
.plot-series-value {
@include ellipsize();
}
}
/***************** GENERAL STYLES, COLLAPSED */
&.plot-legend-collapsed {
// .plot-legend-item is a span of spans.
&.plot-legend-top .gl-plot-legend { margin-bottom: $interiorMargin; }
&.plot-legend-bottom .gl-plot-legend { margin-top: $interiorMargin; }
&.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; }
&.plot-legend-left .gl-plot-legend { margin-right: $interiorMargin; }
.plot-legend-item {
display: flex;
align-items: center;
&:not(:first-child) {
margin-left: $interiorMarginLg;
}
.plot-series-swatch-and-name,
.plot-series-value {
@include ellipsize();
flex: 1 1 auto;
}
.plot-series-swatch-and-name {
margin-right: $interiorMarginSm;
}
.plot-series-value {
text-align: left;
width: 170px;
}
}
}
/***************** GENERAL STYLES, EXPANDED */
&.plot-legend-expanded {
.gl-plot-legend {
max-height: 70%;
}
.plot-wrapper-expanded-legend {
overflow-y: auto;
}
&.plot-legend-top .gl-plot-legend {
margin-bottom: $interiorMargin;
}
&.plot-legend-bottom .gl-plot-legend {
margin-top: $interiorMargin;
}
}
/***************** TOP OR BOTTOM */
&.plot-legend-top,
&.plot-legend-bottom {
// General styles when legend is on the top or bottom
@extend .l-flex-col;
&.plot-legend-collapsed {
// COLLAPSED ON TOP OR BOTTOM
.plot-wrapper-collapsed-legend {
display: flex;
flex: 1 1 auto;
overflow: hidden;
}
}
}
/***************** EITHER SIDE */
&.plot-legend-left,
&.plot-legend-right {
@extend .l-flex-row;
// If the legend is expanded, use flex-col instead so that the legend gets the width it needs.
&.plot-legend-expanded {
// EXPANDED, ON EITHER SIDE
@extend .l-flex-col;
}
&.plot-legend-collapsed {
// COLLAPSED, ON EITHER SIDE
.gl-plot-legend {
max-height: inherit;
width: 25%;
}
.plot-wrapper-collapsed-legend {
display: flex;
flex-flow: column nowrap;
min-width: 0;
flex: 1 1 auto;
overflow-y: auto;
}
.plot-legend-item {
margin-bottom: 1px;
margin-left: 0;
flex-wrap: wrap;
.plot-series-swatch-and-name {
flex: 0 1 auto;
min-width: 20%;
}
.plot-series-value {
flex: 0 1 auto;
width: auto;
}
}
}
}
/***************** ON BOTTOM OR RIGHT */
&.plot-legend-right:not(.plot-legend-expanded),
&.plot-legend-bottom {
.gl-plot-legend {
order: 2;
}
.plot-wrapper-axis-and-display-area {
order: 1;
}
}
}

View File

@@ -20,64 +20,18 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.abs.holder-plot {
right: $interiorMargin; // Fend off the scrollbar when less than min-height;
.t-object-alert.t-alert-unsynced {
display: none;
}
// Fend off the scrollbar when less than min-height;
right: $interiorMargin;
}
/********************************************* STACKED PLOT LAYOUT */
.t-plot-stacked {
.l-view-section {
// Make this a flex container
display: flex;
flex-flow: column nowrap;
.gl-plot.child-frame {
mct-plot {
display: flex;
flex: 1 1 auto;
height: 100%;
position: relative;
}
flex: 1 1 auto;
&:not(:first-child) {
margin-top: $interiorMargin;
}
}
}
.s-status-timeconductor-unsynced .holder-plot {
.t-object-alert.t-alert-unsynced {
display: block;
}
}
}
.gl-plot {
color: $colorPlotFg;
display: flex;
font-size: 0.7rem;
position: relative;
width: 100%;
height: 100%;
min-height: $plotMinH;
/********************************************* AXIS AND DISPLAY AREA */
.plot-wrapper-axis-and-display-area {
margin-top: $interiorMargin; // Keep the top tick label from getting clipped
position: relative;
flex: 1 1 auto;
.t-object-alert {
position: absolute;
display: block;
font-size: 1.5em;
top: $interiorMarginSm; left: $interiorMarginSm;
}
}
.gl-plot-wrapper-display-area-and-x-axis {
// Holds the plot area and the X-axis only
position: absolute;
@@ -95,6 +49,7 @@
}
.gl-plot-axis-area.gl-plot-x {
//@include test(green);
top: auto;
right: 0;
bottom: 0;
@@ -108,7 +63,7 @@
.gl-plot-axis-area {
position: absolute;
&.gl-plot-y {
top: nth($plotDisplayArea, 1);
top: $plotLegendH + $interiorMargin;
right: auto;
bottom: nth($plotDisplayArea, 3);
left: 0;
@@ -152,8 +107,8 @@
&.l-plot-y-label {
$x: -50%;
$r: -90deg;
transform-origin: 50% 0;
transform: translateX($x) rotate($r);
@include transform-origin(50% 0);
@include transform(translateX($x) rotate($r));
display: inline-block;
margin-left: $interiorMargin; // Kick off the left edge
left: 0;
@@ -172,13 +127,13 @@
}
.gl-plot-x-options {
transform: translateX(-50%);
@include transform(translateX(-50%));
bottom: 0;
left: 50%;
}
.gl-plot-y-options {
transform: translateY(-50%);
@include transform(translateY(-50%));
min-width: 150px; // Need this due to enclosure of .select
top: 50%;
left: $plotYLabelW + $interiorMargin * 2;
@@ -203,6 +158,17 @@
}
}
.gl-plot-legend {
position: absolute;
top: 0;
right: 0;
bottom: auto;
left: 0;
height: $plotLegendH;
overflow-x: hidden;
overflow-y: auto;
}
/****************************** Limits and Out-of-Bounds data */
.l-limit-bar,
@@ -269,6 +235,39 @@
border: 1px solid $colorPlotAreaBorder;
}
.gl-plot-legend,
.legend {
.plot-legend-item,
.legend-item {
display: inline-block;
margin-right: $interiorMarginLg;
margin-bottom: $interiorMarginSm;
span {
vertical-align: middle;
}
.plot-color-swatch,
.color-swatch {
border-radius: 2px;
display: inline-block;
height: $plotSwatchD;
width: $plotSwatchD;
}
}
}
.gl-plot-legend {
.plot-legend-item {
border-radius: $smallCr;
line-height: 1.5em;
padding: 0px $itemPadLR;
.plot-color-swatch {
border: 1px solid $colorBodyBg;
height: $plotSwatchD + 1;
width: $plotSwatchD + 1;
}
}
}
.tick {
position: absolute;
border: 0 $colorPlotHash solid;

View File

@@ -152,7 +152,7 @@
opacity: 1;
}
.load-more-button {
transform: translateX(-50%);
@include transform(translateX(-50%));
display: inline-block;
margin-top: $interiorMargin;
padding: 0 $interiorMarginLg;

View File

@@ -32,7 +32,11 @@ body, html {
}
.l-splash-holder {
// Main outer holder for splash.
// Main outer holder.
@include transition-property(opacity);
@include transition-duration(500ms);
@include transition-timing-function(ease-in-out);
@include transition-delay(1s);
position: absolute;
top: 0;
right: 0;
@@ -40,18 +44,16 @@ body, html {
left: 0;
z-index: 10000;
opacity: 1;
&.fadeout {
opacity: 0;
pointer-events: none;
}
.l-splash {
// The splash element.
@include splashElem();
}
}
.user-environ .holder-all {
// Gets shown again by main CSS, once loaded
opacity: 0;
pointer-events: none;
}
@media only screen and (max-device-width: 767px) {
.l-splash-holder .l-splash {
@include splashElem(0);

View File

@@ -23,7 +23,7 @@
ul.tree {
@include menuUlReset();
@include user-select(none);
> li {
li {
display: block;
position: relative;
}
@@ -52,11 +52,21 @@ ul.tree {
.view-control {
color: $colorItemTreeVC;
font-size: 0.75em;
margin-right: $interiorMargin;
height: 100%;
line-height: inherit;
width: $treeVCW;
&:before { display: block; }
&.no-children {
&:before { display: none; }
&.has-children {
&:before {
position: absolute;
@include trans-prop-nice(transform, 100ms);
content: "\e904";
@include transform-origin(center);
}
&.expanded:before {
@include transform(rotate(90deg));
}
}
}

View File

@@ -26,10 +26,12 @@
z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
&:not(.no-frame) {
background: $colorBodyBg;
border-color: $bc;
border: 1px solid $bc;
&:hover {
border-color: lighten($bc, 10%);
}
}
}
.object-browse-bar {
font-size: 0.75em;
height: $ohH;
@@ -42,8 +44,7 @@
&.t-object-type-timer,
&.t-object-type-clock,
&.t-object-type-hyperlink,
&.t-object-type-summary-widget {
&.t-object-type-hyperlink {
// Hide the right side buttons for objects where they don't make sense
// Note that this will hide the view Switcher button if applied
// to an object that has it.
@@ -90,9 +91,9 @@
&.no-frame {
background: transparent !important;
border-color: transparent;
border: none !important;
.object-browse-bar .right {
$m: 0;
$m: 0; // $interiorMarginSm;
background: rgba(black, 0.3);
border-radius: $basicCr;
padding: $interiorMarginSm;
@@ -102,7 +103,7 @@
}
&.t-frame-outer > .t-rep-frame {
&.contents {
$m: 0px;
$m: 2px;
top: $m;
right: $m;
bottom: $m;
@@ -113,7 +114,6 @@
display: none;
}
> .object-holder.abs {
overflow: hidden;
top: 0 !important;
}
}
@@ -125,24 +125,17 @@
pointer-events: none !important;
}
/********************************************************** OBJECT TYPES */
.t-object-type-hyperlink,
.t-object-type-summary-widget {
/********************************************************** OBJECT TYPES */
.t-object-type-hyperlink {
.object-holder {
overflow: hidden;
}
.w-summary-widget,
.l-summary-widget,
.l-hyperlink.s-button {
// Some object types expand to the full size of the object-holder.
// When a hyperlink is a button in a frame, make it expand to fill out to the object-holder
@extend .abs;
}
.l-summary-widget,
.l-hyperlink.s-button {
.label {
@include ellipsize();
transform: translateY(-50%);
@include transform(translateY(-50%));
padding: 0 $interiorMargin;
position: absolute;
min-width: 0;

View File

@@ -159,20 +159,20 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
&.toggle-tree.anchor-left {
left: 0;
transform: translateX(-1 * $paneExpandedOffset);
@include transform(translateX(-1 * $paneExpandedOffset));
&.collapsed {
transform: translateX(-1 * $ueCollapsedPaneEdgeM);
@include transform(translateX(-1 * $ueCollapsedPaneEdgeM));
}
}
&.toggle-inspect.anchor-right {
right: 0;
transform: translateX($paneExpandedOffset);
@include transform(translateX($paneExpandedOffset));
&.flush-right {
transform: translateX(($uePaneMiniTabW + ceil($splitterD / 2)));
@include transform(translateX(($uePaneMiniTabW + ceil($splitterD / 2))));
}
&.collapsed {
transform: translateX($ueCollapsedPaneEdgeM);
@include transform(translateX($ueCollapsedPaneEdgeM));
}
}
}

View File

@@ -20,52 +20,35 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.s-hover-border {
border: 1px solid transparent;
&:hover {
border-color: rgba($colorSelectableSelectedPrimary, 0.5) !important;
}
border: 1px dotted transparent;
}
.s-status-editing {
// Limit to editing mode
$o: 0.5;
$oHover: 0.8;
$bc: $colorSelectableSelectedPrimary;
// Limit to editing mode until we have sub-object selection
.s-hover-border {
// Show a border by default so user can see object bounds and empty objects
border-color: rgba($bc, $o) !important;
border-style: dotted !important;
border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
&:hover {
border-color: rgba($bc, $oHover) !important;
}
&.t-object-type-layout {
border-style: dashed !important;
border-color: rgba($colorSelectableSelectedPrimary, 0.7) !important;
}
}
.s-selected {
&.s-moveable {
&:not(.s-drilled-in) {
cursor: move;
.s-selected > .s-hover-border,
.s-selected.s-hover-border {
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
border-color: $colorSelectableSelectedPrimary !important;
@include boxShdwLarge();
// Show edit-corners if you got 'em
.edit-corner {
display: block;
&:hover {
background-color: rgba($colorKey, 1);
}
}
}
}
.s-selected > .s-hover-border,
.s-selected.s-hover-border {
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
border-color: $colorSelectableSelectedPrimary !important;
@include boxShdwLarge();
// Show edit-corners if you got 'em
.edit-corner {
display: block;
&:hover {
background-color: rgba($colorKey, 1);
}
.s-selected > .s-moveable,
.s-selected.s-moveable {
cursor: move;
}
}
}

View File

@@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<span ng-controller="DateTimeFieldController">
<input type="text" autocorrect="off" spellcheck="false"
<input type="text"
ng-model="textValue"
ng-blur="restoreTextValue(); ngBlur()"
ng-mouseup="ngMouseup()"

View File

@@ -40,7 +40,7 @@ define(
// Gets an array of the contextual parents/ancestors of the selected object
function getContextualPath() {
var currentObj = $scope.domainObject,
var currentObj = $scope.ngModel.selectedObject,
currentParent,
parents = [];
@@ -68,7 +68,7 @@ define(
// If this the the initial call of this recursive function
if (!current) {
current = $scope.domainObject;
current = $scope.ngModel.selectedObject;
$scope.primaryParents = [];
}
@@ -87,16 +87,16 @@ define(
// Gets the metadata for the selected object
function getMetadata() {
$scope.metadata = $scope.domainObject &&
$scope.domainObject.hasCapability('metadata') &&
$scope.domainObject.useCapability('metadata');
$scope.metadata = $scope.ngModel.selectedObject &&
$scope.ngModel.selectedObject.hasCapability('metadata') &&
$scope.ngModel.selectedObject.useCapability('metadata');
}
// Set scope variables when the selected object changes
$scope.$watch('domainObject', function () {
$scope.isLink = $scope.domainObject &&
$scope.domainObject.hasCapability('location') &&
$scope.domainObject.getCapability('location').isLink();
$scope.$watch('ngModel.selectedObject', function () {
$scope.isLink = $scope.ngModel.selectedObject &&
$scope.ngModel.selectedObject.hasCapability('location') &&
$scope.ngModel.selectedObject.getCapability('location').isLink();
if ($scope.isLink) {
getPrimaryPath();
@@ -108,11 +108,8 @@ define(
getMetadata();
});
var mutation = $scope.domainObject.getCapability('mutation');
var unlisten = mutation.listen(getMetadata);
$scope.$on('$destroy', unlisten);
}
return ObjectInspectorController;
}
);

View File

@@ -40,18 +40,10 @@ define([
return type.getCssClass();
}
function removePreviousIconClass(el) {
$(el).removeClass(function (index, className) {
return (className.match (/\bicon-\S+/g) || []).join(' ');
});
}
TreeLabelView.prototype.updateView = function (domainObject) {
var titleEl = this.el.find('.t-title-label'),
iconEl = this.el.find('.t-item-icon');
removePreviousIconClass(iconEl);
titleEl.text(domainObject ? domainObject.getModel().name : "");
iconEl.addClass(domainObject ? getClass(domainObject) : "");

View File

@@ -83,9 +83,9 @@ define([
this.activeObject = domainObject;
if (domainObject && domainObject.hasCapability('composition')) {
$(this.toggleView.elements()).removeClass('no-children');
$(this.toggleView.elements()).addClass('has-children');
} else {
$(this.toggleView.elements()).addClass('no-children');
$(this.toggleView.elements()).removeClass('has-children');
}
if (domainObject && domainObject.hasCapability('status')) {

View File

@@ -39,8 +39,10 @@ define(
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
["$watch", "$on"]
["$watch"]
);
mockScope.ngModel = {};
mockScope.ngModel.selectedObject = 'mock selected object';
mockObjectService = jasmine.createSpyObj(
"objectService",
@@ -67,27 +69,22 @@ define(
"location capability",
["isLink"]
);
mockDomainObject.getCapability.andCallFake(function (param) {
if (param === 'location') {
return mockLocationCapability;
} else if (param === 'context') {
return mockContextCapability;
} else if (param === 'mutation') {
return {
listen: function () {
return true;
}
};
}
});
mockScope.domainObject = mockDomainObject;
controller = new ObjectInspectorController(mockScope, mockObjectService);
// Change the selected object to trigger the watch call
mockScope.ngModel.selectedObject = mockDomainObject;
});
it("watches for changes to the selected object", function () {
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
expect(mockScope.$watch).toHaveBeenCalledWith('ngModel.selectedObject', jasmine.any(Function));
});
it("looks for contextual parent objects", function () {

View File

@@ -65,10 +65,6 @@ define(
options = Object.create(OPTIONS);
options.marginX = -bubbleSpaceLR;
// prevent bubble from appearing right under pointer,
// which causes hover callback to be called multiple times
options.offsetX = 1;
// On a phone, bubble takes up more screen real estate,
// so position it differently (toward the bottom)
if (this.agentService.isPhone()) {

Some files were not shown because too many files have changed in this diff Show More