mirror of
https://github.com/nasa/openmct.git
synced 2025-06-27 11:32:13 +00:00
Compare commits
132 Commits
pause-butt
...
plot-sync
Author | SHA1 | Date | |
---|---|---|---|
57abac96b2 | |||
204f6be9d0 | |||
c988c6c43b | |||
c143475f3e | |||
b248f57daa | |||
f4e8c5ecf6 | |||
11ef7c0ccd | |||
9c125e0454 | |||
439a654f6d | |||
9d32a3f1c3 | |||
705e25df8a | |||
787d6887dc | |||
1d516240a8 | |||
90ee0a309f | |||
72d05283da | |||
dc91a94f0e | |||
0243aa6584 | |||
e5d869f01e | |||
d4e3e6689c | |||
0363d0e8ad | |||
3669e776a9 | |||
5d3adc6a7f | |||
c1b2db848a | |||
5d19294c11 | |||
9b8d5f3f9c | |||
d03f323a9b | |||
54a453e5a0 | |||
14894cf197 | |||
0c6786198a | |||
6d077b775d | |||
144437a06e | |||
557cd91b21 | |||
39d3e92094 | |||
7529a86d01 | |||
d34e36831c | |||
aa8fa9168a | |||
3f1b7e0a87 | |||
5ec3b98d1c | |||
1ad5094b72 | |||
b54ee2257e | |||
fcef4274e5 | |||
744a5340d3 | |||
d140051054 | |||
8da74f2665 | |||
2390278b97 | |||
8a66731271 | |||
0a9ea48355 | |||
01d93306f3 | |||
0588f9190a | |||
1378b57567 | |||
9e12886c66 | |||
2d352ac574 | |||
284dec4903 | |||
5a0656c700 | |||
425655bae0 | |||
50b4d5cb28 | |||
bc62d7d5ae | |||
c0dcf4495e | |||
a51b9bc63f | |||
ff003c3dab | |||
de7c4d2ce3 | |||
4b07930305 | |||
5a49ac16b1 | |||
91b150c064 | |||
9506d309b0 | |||
c9bd60f50e | |||
cf15ff5c07 | |||
6bbdfcdfbe | |||
06e93ff520 | |||
550e7a15e6 | |||
71c54cd541 | |||
e81b8e53dc | |||
84e6928f54 | |||
ba688fe62c | |||
c533e10352 | |||
04f47b3db6 | |||
717fa5edf4 | |||
14f5f048fb | |||
72929500d3 | |||
471adde923 | |||
6c5d5f3d00 | |||
2262fef29b | |||
bda30f1475 | |||
bfec434369 | |||
14df350994 | |||
80582f5e8d | |||
7442768ced | |||
77c7bdfdec | |||
07d9769966 | |||
385b6177b2 | |||
7f68d26433 | |||
d7b44f8d09 | |||
c4cd36e15b | |||
618a6e7e8d | |||
63a8c91f71 | |||
e59020fec7 | |||
a2e424203a | |||
16853644cb | |||
2272766c57 | |||
1d9cdea2d4 | |||
715219c44d | |||
00dc2875bf | |||
8703f363b8 | |||
26210eaa50 | |||
a4a1cb5e05 | |||
9570f2f7a1 | |||
eb4ded39b3 | |||
7deb3cd025 | |||
06779e6cd9 | |||
7f43c0bf1a | |||
bfa3bbcdc7 | |||
2baf3f8bb0 | |||
10ac13ac5c | |||
138cb199bb | |||
374c363a78 | |||
e9cb5cd639 | |||
bc7d92ee0d | |||
78f49784a0 | |||
8754c438cc | |||
1419c75503 | |||
ca8cad0a74 | |||
a3a55d3b48 | |||
e66f818996 | |||
ae5ef33487 | |||
59c5430579 | |||
6d52f094d9 | |||
740db8da75 | |||
68abc15ed5 | |||
bb47feb517 | |||
469820fb0f | |||
92dd99b26c | |||
9fe1923189 |
@ -21,5 +21,6 @@
|
||||
"shadow": "outer",
|
||||
"strict": "implied",
|
||||
"undef": true,
|
||||
"unused": "vars"
|
||||
"unused": "vars",
|
||||
"latedef": "nofunc"
|
||||
}
|
||||
|
15
API.md
15
API.md
@ -879,6 +879,21 @@ 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.
|
||||
|
@ -88,7 +88,7 @@ and [`gulp`](http://gulpjs.com/).
|
||||
|
||||
To build Open MCT for deployment:
|
||||
|
||||
`npm run prepublish`
|
||||
`npm run prepare`
|
||||
|
||||
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
|
||||
|
@ -17,7 +17,7 @@
|
||||
"screenfull": "^3.0.0",
|
||||
"node-uuid": "^1.4.7",
|
||||
"comma-separated-values": "^3.6.4",
|
||||
"FileSaver.js": "^0.0.2",
|
||||
"file-saver": "^1.3.3",
|
||||
"zepto": "^1.1.6",
|
||||
"eventemitter3": "^1.2.0",
|
||||
"lodash": "3.10.1",
|
||||
|
14
circle.yml
14
circle.yml
@ -1,15 +1,17 @@
|
||||
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:
|
||||
|
@ -2283,7 +2283,7 @@ To install build dependencies (only needs to be run once):
|
||||
|
||||
To build:
|
||||
|
||||
`npm run prepublish`
|
||||
`npm run prepare`
|
||||
|
||||
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
|
||||
|
121
docs/src/guide/security.md
Normal file
121
docs/src/guide/security.md
Normal file
@ -0,0 +1,121 @@
|
||||
# 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)
|
@ -30,7 +30,8 @@ define([
|
||||
amplitude: 1,
|
||||
period: 10,
|
||||
offset: 0,
|
||||
dataRateInHz: 1
|
||||
dataRateInHz: 1,
|
||||
phase: 0
|
||||
};
|
||||
|
||||
function GeneratorProvider() {
|
||||
@ -50,16 +51,19 @@ define([
|
||||
'amplitude',
|
||||
'period',
|
||||
'offset',
|
||||
'dataRateInHz'
|
||||
'dataRateInHz',
|
||||
'phase',
|
||||
];
|
||||
|
||||
request = request || {};
|
||||
|
||||
var workerRequest = {};
|
||||
|
||||
props.forEach(function (prop) {
|
||||
if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) {
|
||||
workerRequest[prop] = domainObject.telemetry[prop];
|
||||
}
|
||||
if (request.hasOwnProperty(prop)) {
|
||||
if (request && request.hasOwnProperty(prop)) {
|
||||
workerRequest[prop] = request[prop];
|
||||
}
|
||||
if (!workerRequest[prop]) {
|
||||
@ -67,7 +71,7 @@ define([
|
||||
}
|
||||
workerRequest[prop] = Number(workerRequest[prop]);
|
||||
});
|
||||
|
||||
workerRequest.name = domainObject.name;
|
||||
return workerRequest;
|
||||
};
|
||||
|
||||
@ -78,8 +82,8 @@ define([
|
||||
return this.workerInterface.request(workerRequest);
|
||||
};
|
||||
|
||||
GeneratorProvider.prototype.subscribe = function (domainObject, callback, request) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, request);
|
||||
GeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, {});
|
||||
return this.workerInterface.subscribe(workerRequest, callback);
|
||||
};
|
||||
|
||||
|
80
example/generator/StateGeneratorProvider.js
Normal file
80
example/generator/StateGeneratorProvider.js
Normal file
@ -0,0 +1,80 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
|
||||
});
|
@ -44,9 +44,7 @@ define([
|
||||
message = message.data;
|
||||
var callback = this.callbacks[message.id];
|
||||
if (callback) {
|
||||
if (callback(message)) {
|
||||
delete this.callbacks[message.id];
|
||||
}
|
||||
callback(message);
|
||||
}
|
||||
};
|
||||
|
||||
@ -72,6 +70,7 @@ define([
|
||||
deferred.resolve = resolve;
|
||||
deferred.reject = reject;
|
||||
});
|
||||
var messageId;
|
||||
|
||||
function callback(message) {
|
||||
if (message.error) {
|
||||
@ -79,33 +78,27 @@ define([
|
||||
} else {
|
||||
deferred.resolve(message.data);
|
||||
}
|
||||
return true;
|
||||
delete this.callbacks[messageId];
|
||||
}
|
||||
|
||||
this.dispatch('request', request, callback);
|
||||
messageId = this.dispatch('request', request, callback.bind(this));
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
WorkerInterface.prototype.subscribe = function (request, cb) {
|
||||
var isCancelled = false;
|
||||
|
||||
var callback = function (message) {
|
||||
if (isCancelled) {
|
||||
return true;
|
||||
}
|
||||
function callback(message) {
|
||||
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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -62,10 +62,11 @@
|
||||
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),
|
||||
cos: cos(nextStep, data.period, data.amplitude, data.offset)
|
||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase),
|
||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase)
|
||||
}
|
||||
});
|
||||
nextStep += step;
|
||||
@ -82,21 +83,22 @@
|
||||
}
|
||||
|
||||
function onRequest(message) {
|
||||
var data = message.data;
|
||||
if (data.end == undefined) {
|
||||
data.end = Date.now();
|
||||
var request = message.data;
|
||||
if (request.end == undefined) {
|
||||
request.end = Date.now();
|
||||
}
|
||||
if (data.start == undefined){
|
||||
data.start = data.end - FIFTEEN_MINUTES;
|
||||
if (request.start == undefined){
|
||||
request.start = request.end - FIFTEEN_MINUTES;
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
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 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 step = 1000 / dataRateInHz;
|
||||
var nextStep = start - (start % step) + step;
|
||||
@ -105,10 +107,11 @@
|
||||
|
||||
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),
|
||||
cos: cos(nextStep, period, amplitude, offset)
|
||||
sin: sin(nextStep, period, amplitude, offset, phase),
|
||||
cos: cos(nextStep, period, amplitude, offset, phase)
|
||||
});
|
||||
}
|
||||
self.postMessage({
|
||||
@ -117,14 +120,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
function cos(timestamp, period, amplitude, offset) {
|
||||
function cos(timestamp, period, amplitude, offset, phase) {
|
||||
return amplitude *
|
||||
Math.cos(timestamp / period / 1000 * Math.PI * 2) + offset;
|
||||
Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
|
||||
}
|
||||
|
||||
function sin(timestamp, period, amplitude, offset) {
|
||||
function sin(timestamp, period, amplitude, offset, phase) {
|
||||
return amplitude *
|
||||
Math.sin(timestamp / period / 1000 * Math.PI * 2) + offset;
|
||||
Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
|
||||
}
|
||||
|
||||
function sendError(error, message) {
|
||||
|
@ -23,10 +23,12 @@
|
||||
|
||||
define([
|
||||
"./GeneratorProvider",
|
||||
"./SinewaveLimitCapability"
|
||||
"./SinewaveLimitCapability",
|
||||
"./StateGeneratorProvider"
|
||||
], function (
|
||||
GeneratorProvider,
|
||||
SinewaveLimitCapability
|
||||
SinewaveLimitCapability,
|
||||
StateGeneratorProvider
|
||||
) {
|
||||
|
||||
var legacyExtensions = {
|
||||
@ -46,6 +48,75 @@ define([
|
||||
openmct.legacyExtension(type, extension)
|
||||
})
|
||||
});
|
||||
|
||||
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,
|
||||
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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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.",
|
||||
@ -99,6 +170,18 @@ define([
|
||||
"dataRateInHz"
|
||||
],
|
||||
pattern: "^\\d*(\\.\\d*)?$"
|
||||
},
|
||||
{
|
||||
name: "Phase (radians)",
|
||||
control: "textfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "phase",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"phase"
|
||||
],
|
||||
pattern: "^\\d*(\\.\\d*)?$"
|
||||
}
|
||||
],
|
||||
initialize: function (object) {
|
||||
@ -107,7 +190,12 @@ define([
|
||||
amplitude: 1,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
phase: 0,
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
@ -142,6 +230,7 @@ define([
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
openmct.telemetry.addProvider(new GeneratorProvider());
|
||||
};
|
||||
|
||||
|
@ -48,8 +48,9 @@ define([
|
||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
||||
];
|
||||
|
||||
function pointForTimestamp(timestamp) {
|
||||
function pointForTimestamp(timestamp, name) {
|
||||
return {
|
||||
name: name,
|
||||
utc: Math.floor(timestamp / 5000) * 5000,
|
||||
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
||||
};
|
||||
@ -61,7 +62,7 @@ define([
|
||||
},
|
||||
subscribe: function (domainObject, callback) {
|
||||
var interval = setInterval(function () {
|
||||
callback(pointForTimestamp(Date.now()));
|
||||
callback(pointForTimestamp(Date.now(), domainObject.name));
|
||||
}, 5000);
|
||||
|
||||
return function (interval) {
|
||||
@ -79,8 +80,8 @@ define([
|
||||
var start = options.start;
|
||||
var end = options.end;
|
||||
var data = [];
|
||||
while (start < end && data.length < 5000) {
|
||||
data.push(pointForTimestamp(start));
|
||||
while (start <= end && data.length < 5000) {
|
||||
data.push(pointForTimestamp(start, domainObject.name));
|
||||
start += 5000;
|
||||
}
|
||||
return Promise.resolve(data);
|
||||
@ -93,7 +94,7 @@ define([
|
||||
options.strategy === 'latest';
|
||||
},
|
||||
request: function (domainObject, options) {
|
||||
return Promise.resolve([pointForTimestamp(Date.now())]);
|
||||
return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name)]);
|
||||
}
|
||||
};
|
||||
|
||||
@ -109,6 +110,10 @@ define([
|
||||
initialize: function (object) {
|
||||
object.telemetry = {
|
||||
values: [
|
||||
{
|
||||
name: 'Name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
name: 'Time',
|
||||
key: 'utc',
|
||||
|
@ -58,11 +58,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.w-mct-example {
|
||||
div {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
}
|
||||
}
|
||||
.w-mct-example > div { margin-bottom: $interiorMarginLg; }
|
||||
|
||||
code,
|
||||
pre {
|
||||
|
@ -127,7 +127,8 @@
|
||||
{ 'meaning': 'Timer object', 'cssClass': 'icon-timer', 'cssContent': 'e1127', 'htmlEntity': '&#xe1127' },
|
||||
{ 'meaning': 'Data Topic', 'cssClass': 'icon-topic', 'cssContent': 'e1128', 'htmlEntity': '&#xe1128' },
|
||||
{ 'meaning': 'Fixed Position object', 'cssClass': 'icon-box-with-dashed-lines', 'cssContent': 'e1129', 'htmlEntity': '&#xe1129' },
|
||||
{ 'meaning': 'Summary Widget', 'cssClass': 'icon-summary-widget', 'cssContent': 'e1130', 'htmlEntity': '&#xe1130' }
|
||||
{ 'meaning': 'Summary Widget', 'cssClass': 'icon-summary-widget', 'cssContent': 'e1130', 'htmlEntity': '&#xe1130' },
|
||||
{ 'meaning': 'Notebook object', 'cssClass': 'icon-notebook', 'cssContent': 'e1131', 'htmlEntity': '&#xe1131' }
|
||||
];
|
||||
"></div>
|
||||
|
||||
|
@ -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.</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. Selected palette choices should utilize the <code>selected</code> CSS class to visualize indicate that state.</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-color-palette" ng-show="toggle.isActive()">
|
||||
<div class="menu l-palette l-color-palette" ng-show="toggle.isActive()">
|
||||
<div class="l-palette-row l-option-row">
|
||||
<div class="l-palette-item s-palette-item " ng-click="ngModel[field] = 'transparent'"></div>
|
||||
<div class="l-palette-item s-palette-item no-selection"></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" style="background: rgb(136, 32, 32);"></div>
|
||||
<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(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>
|
||||
|
30
gulpfile.js
30
gulpfile.js
@ -22,6 +22,8 @@
|
||||
|
||||
/*global require,__dirname*/
|
||||
|
||||
require("v8-compile-cache");
|
||||
|
||||
var gulp = require('gulp'),
|
||||
sourcemaps = require('gulp-sourcemaps'),
|
||||
path = require('path'),
|
||||
@ -46,9 +48,22 @@ var gulp = require('gulp'),
|
||||
name: 'bower_components/almond/almond.js',
|
||||
include: paths.main.replace('.js', ''),
|
||||
wrap: {
|
||||
startFile: "src/start.frag",
|
||||
start: (function () {
|
||||
var buildVariables = {
|
||||
version: project.version,
|
||||
timestamp: moment.utc(Date.now()).format(),
|
||||
revision: fs.existsSync('.git') ? git.long() : 'Unknown',
|
||||
branch: fs.existsSync('.git') ? git.branch() : 'Unknown'
|
||||
};
|
||||
return fs.readFileSync("src/start.frag", 'utf-8')
|
||||
.replace(/@@(\w+)/g, function (match, key) {
|
||||
return buildVariables[key];
|
||||
});;
|
||||
}()),
|
||||
endFile: "src/end.frag"
|
||||
},
|
||||
optimize: 'uglify2',
|
||||
uglify2: { output: { comments: /@preserve/ } },
|
||||
mainConfigFile: paths.main,
|
||||
wrapShim: true
|
||||
},
|
||||
@ -58,14 +73,6 @@ var gulp = require('gulp'),
|
||||
},
|
||||
sass: {
|
||||
sourceComments: true
|
||||
},
|
||||
replace: {
|
||||
variables: {
|
||||
version: project.version,
|
||||
timestamp: moment.utc(Date.now()).format(),
|
||||
revision: fs.existsSync('.git') ? git.long() : 'Unknown',
|
||||
branch: fs.existsSync('.git') ? git.branch() : 'Unknown'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -76,16 +83,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
|
||||
gulp.task('scripts', function () {
|
||||
var requirejsOptimize = require('gulp-requirejs-optimize');
|
||||
var replace = require('gulp-replace-task');
|
||||
var header = require('gulp-header');
|
||||
var comment = fs.readFileSync('src/about.frag');
|
||||
|
||||
return gulp.src(paths.main)
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(requirejsOptimize(options.requirejsOptimize))
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(replace(options.replace))
|
||||
.pipe(header(comment, options.replace.variables))
|
||||
.pipe(gulp.dest(paths.dist));
|
||||
});
|
||||
|
||||
|
@ -25,8 +25,7 @@
|
||||
<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;
|
||||
|
||||
@ -44,13 +43,16 @@
|
||||
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() - 30 * 60 * 1000,
|
||||
start: Date.now() - THIRTY_MINUTES,
|
||||
end: Date.now()
|
||||
}
|
||||
},
|
||||
@ -65,6 +67,7 @@
|
||||
}
|
||||
]
|
||||
}));
|
||||
openmct.install(openmct.plugins.SummaryWidget());
|
||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.start();
|
||||
|
@ -36,6 +36,7 @@ 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/**/*.js', included: false},
|
||||
{pattern: 'example/**/*.html', included: false},
|
||||
{pattern: 'example/**/*.js', included: false},
|
||||
@ -88,7 +89,8 @@ module.exports = function(config) {
|
||||
"dist/reports/coverage",
|
||||
check: {
|
||||
global: {
|
||||
lines: 80
|
||||
lines: 80,
|
||||
excludes: ['src/plugins/plot/**/*.js']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
20
openmct.js
20
openmct.js
@ -19,7 +19,7 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global requirejs*/
|
||||
/*global requirejs,BUILD_CONSTANTS*/
|
||||
|
||||
requirejs.config({
|
||||
"paths": {
|
||||
@ -33,13 +33,14 @@ 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/FileSaver.js/FileSaver.min",
|
||||
"saveAs": "bower_components/file-saver/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/build/d3-selection.min",
|
||||
"d3-selection": "node_modules/d3-selection/dist/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",
|
||||
@ -66,6 +67,9 @@ requirejs.config({
|
||||
"moment-duration-format": {
|
||||
"deps": ["moment"]
|
||||
},
|
||||
"saveAs": {
|
||||
"exports": "saveAs"
|
||||
},
|
||||
"screenfull": {
|
||||
"exports": "screenfull"
|
||||
},
|
||||
@ -91,11 +95,17 @@ requirejs.config({
|
||||
define([
|
||||
'./platform/framework/src/Main',
|
||||
'./src/defaultRegistry',
|
||||
'./src/MCT'
|
||||
], function (Main, defaultRegistry, MCT) {
|
||||
'./src/MCT',
|
||||
'./src/plugins/buildInfo/plugin'
|
||||
], function (Main, defaultRegistry, MCT, buildInfo) {
|
||||
var openmct = new MCT();
|
||||
|
||||
openmct.legacyRegistry = defaultRegistry;
|
||||
openmct.install(openmct.plugins.Plot());
|
||||
|
||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||
openmct.install(buildInfo(BUILD_CONSTANTS));
|
||||
}
|
||||
|
||||
openmct.on('start', function () {
|
||||
return new Main().run(defaultRegistry);
|
||||
|
10
package.json
10
package.json
@ -15,19 +15,18 @@
|
||||
"d3-time-format": "^2.0.3",
|
||||
"express": "^4.13.1",
|
||||
"minimist": "^1.1.1",
|
||||
"request": "^2.69.0"
|
||||
"request": "^2.69.0",
|
||||
"vue": "^2.5.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bower": "^1.7.7",
|
||||
"git-rev-sync": "^1.4.0",
|
||||
"glob": ">= 3.0.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-header": "^1.8.8",
|
||||
"gulp-jscs": "^3.0.2",
|
||||
"gulp-jshint": "^2.0.0",
|
||||
"gulp-jshint-html-reporter": "^0.1.3",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-replace-task": "^0.11.0",
|
||||
"gulp-requirejs-optimize": "^0.3.1",
|
||||
"gulp-sass": "^2.2.0",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
@ -51,7 +50,8 @@
|
||||
"moment": "^2.11.1",
|
||||
"node-bourbon": "^4.2.3",
|
||||
"requirejs": "2.1.x",
|
||||
"split": "^1.0.0"
|
||||
"split": "^1.0.0",
|
||||
"v8-compile-cache": "^1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
@ -61,7 +61,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",
|
||||
"prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
|
||||
"prepare": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -26,6 +26,7 @@ define([
|
||||
"./src/InspectorPaneController",
|
||||
"./src/BrowseObjectController",
|
||||
"./src/MenuArrowController",
|
||||
"./src/ObjectHeaderController",
|
||||
"./src/navigation/NavigationService",
|
||||
"./src/navigation/NavigateAction",
|
||||
"./src/navigation/OrphanNavigationHandler",
|
||||
@ -36,6 +37,7 @@ define([
|
||||
"text!./res/templates/browse-object.html",
|
||||
"text!./res/templates/items/grid-item.html",
|
||||
"text!./res/templates/browse/object-header.html",
|
||||
"text!./res/templates/browse/object-header-frame.html",
|
||||
"text!./res/templates/menu-arrow.html",
|
||||
"text!./res/templates/back-arrow.html",
|
||||
"text!./res/templates/items/items.html",
|
||||
@ -48,6 +50,7 @@ define([
|
||||
InspectorPaneController,
|
||||
BrowseObjectController,
|
||||
MenuArrowController,
|
||||
ObjectHeaderController,
|
||||
NavigationService,
|
||||
NavigateAction,
|
||||
OrphanNavigationHandler,
|
||||
@ -58,6 +61,7 @@ define([
|
||||
browseObjectTemplate,
|
||||
gridItemTemplate,
|
||||
objectHeaderTemplate,
|
||||
objectHeaderFrameTemplate,
|
||||
menuArrowTemplate,
|
||||
backArrowTemplate,
|
||||
itemsTemplate,
|
||||
@ -140,6 +144,13 @@ define([
|
||||
"$location",
|
||||
"$attrs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ObjectHeaderController",
|
||||
"implementation": ObjectHeaderController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
]
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
@ -173,6 +184,13 @@ define([
|
||||
"type"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "object-header-frame",
|
||||
"template": objectHeaderFrameTemplate,
|
||||
"uses": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "menu-arrow",
|
||||
"template": menuArrowTemplate,
|
||||
|
@ -57,7 +57,12 @@
|
||||
</div>
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="abs flex-elem grows object-holder-main scroll">
|
||||
class="abs flex-elem grows object-holder-main scroll"
|
||||
mct-selectable="{
|
||||
item: domainObject.useCapability('adapter'),
|
||||
oldItem: domainObject
|
||||
}"
|
||||
mct-init-select>
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,12 +19,21 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div ng-controller="InspectorController">
|
||||
<div ng-repeat="region in regions">
|
||||
<div ng-controller="InspectorController as controller">
|
||||
<mct-representation
|
||||
key="region.content.key"
|
||||
mct-object="domainObject"
|
||||
key="'object-properties'"
|
||||
mct-object="controller.selectedItem()"
|
||||
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>
|
||||
|
@ -0,0 +1,31 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<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 class='title-label flex-elem holder flex-can-shrink s-input-inline'>{{model.name}}</span>
|
||||
<span class='t-object-alert t-alert-unsynced flex-elem holder' title='This object is not currently displaying real-time data'></span>
|
||||
<mct-representation
|
||||
key="'menu-arrow'"
|
||||
mct-object='domainObject'
|
||||
class="flex-elem context-available-w"></mct-representation>
|
||||
</span>
|
@ -20,9 +20,13 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span class='type-icon flex-elem {{type.getCssClass()}}'></span>
|
||||
<span class="l-elem-wrapper l-flex-row flex-elem grows">
|
||||
<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 class='title-label flex-elem holder flex-can-shrink'>{{model.name}}</span>
|
||||
<span ng-attr-contenteditable="{{ controller.editable ? true : undefined }}"
|
||||
class='title-label flex-elem holder flex-can-shrink s-input-inline'
|
||||
ng-click="controller.edit()"
|
||||
ng-blur="controller.updateName($event)"
|
||||
ng-keypress="controller.updateName($event)">{{model.name}}</span>
|
||||
<span class='t-object-alert t-alert-unsynced flex-elem holder' title='This object is not currently displaying real-time data'></span>
|
||||
<mct-representation
|
||||
key="'menu-arrow'"
|
||||
|
@ -38,8 +38,6 @@
|
||||
ng-class="{ last:($index + 1) === contextualParents.length }">
|
||||
<mct-representation key="'label'"
|
||||
mct-object="parent"
|
||||
ng-model="ngModel"
|
||||
ng-click="ngModel.selectedObject = parent"
|
||||
class="location-item">
|
||||
</mct-representation>
|
||||
</span>
|
||||
@ -51,8 +49,6 @@
|
||||
ng-class="{ last:($index + 1) === primaryParents.length }">
|
||||
<mct-representation key="'label'"
|
||||
mct-object="parent"
|
||||
ng-model="ngModel"
|
||||
ng-click="ngModel.selectedObject = parent"
|
||||
class="location-item">
|
||||
</mct-representation>
|
||||
</span>
|
||||
|
92
platform/commonUI/browse/src/ObjectHeaderController.js
Normal file
92
platform/commonUI/browse/src/ObjectHeaderController.js
Normal file
@ -0,0 +1,92 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
|
||||
/**
|
||||
* Controller to provide the ability to inline edit an object name.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/browse
|
||||
*/
|
||||
function ObjectHeaderController($scope) {
|
||||
this.$scope = $scope;
|
||||
this.domainObject = $scope.domainObject;
|
||||
this.editable = this.allowEdit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the object name on blur and enter keypress events.
|
||||
*
|
||||
* @param event the mouse event
|
||||
*/
|
||||
ObjectHeaderController.prototype.updateName = function (event) {
|
||||
if (!event || !event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type === 'blur') {
|
||||
this.updateModel(event);
|
||||
} else if (event.which === 13) {
|
||||
this.updateModel(event);
|
||||
event.currentTarget.blur();
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the model.
|
||||
*
|
||||
* @param event the mouse event
|
||||
* @param private
|
||||
*/
|
||||
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'));
|
||||
};
|
||||
|
||||
return ObjectHeaderController;
|
||||
}
|
||||
);
|
137
platform/commonUI/browse/test/ObjectHeaderControllerSpec.js
Normal file
137
platform/commonUI/browse/test/ObjectHeaderControllerSpec.js
Normal file
@ -0,0 +1,137 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
["../src/ObjectHeaderController"],
|
||||
function (ObjectHeaderController) {
|
||||
|
||||
describe("The object header controller", function () {
|
||||
var mockScope,
|
||||
mockDomainObject,
|
||||
mockCapabilities,
|
||||
mockMutationCapability,
|
||||
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';
|
||||
});
|
||||
|
||||
mockCapabilities = {
|
||||
mutation: mockMutationCapability,
|
||||
type: mockTypeCapability
|
||||
};
|
||||
|
||||
model = {
|
||||
name: "Test name"
|
||||
};
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", ["getCapability", "getModel"]);
|
||||
mockDomainObject.getModel.andReturn(model);
|
||||
mockDomainObject.getCapability.andCallFake(function (key) {
|
||||
return mockCapabilities[key];
|
||||
});
|
||||
|
||||
mockScope = {
|
||||
domainObject: mockDomainObject
|
||||
};
|
||||
|
||||
mockCurrentTarget = jasmine.createSpyObj("currentTarget", ["blur", "textContent"]);
|
||||
mockCurrentTarget.blur.andReturn(mockCurrentTarget);
|
||||
|
||||
mockEvent = {
|
||||
which: {},
|
||||
type: {},
|
||||
currentTarget: mockCurrentTarget
|
||||
};
|
||||
|
||||
controller = new ObjectHeaderController(mockScope);
|
||||
});
|
||||
|
||||
it("updates the model with new name on blur", function () {
|
||||
mockEvent.type = "blur";
|
||||
mockCurrentTarget.textContent = "New name";
|
||||
controller.updateName(mockEvent);
|
||||
|
||||
expect(mockMutationCapability.mutate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates the model with a default for blank names", function () {
|
||||
mockEvent.type = "blur";
|
||||
mockCurrentTarget.textContent = "";
|
||||
controller.updateName(mockEvent);
|
||||
|
||||
expect(mockCurrentTarget.textContent.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;
|
||||
controller.updateName(mockEvent);
|
||||
|
||||
expect(mockMutationCapability.mutate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates the model on enter keypress event only", function () {
|
||||
mockCurrentTarget.textContent = "New name";
|
||||
controller.updateName(mockEvent);
|
||||
|
||||
expect(mockMutationCapability.mutate).not.toHaveBeenCalled();
|
||||
|
||||
mockEvent.which = 13;
|
||||
controller.updateName(mockEvent);
|
||||
|
||||
expect(mockMutationCapability.mutate).toHaveBeenCalledWith(jasmine.any(Function));
|
||||
|
||||
mockMutationCapability.mutate.mostRecentCall.args[0](model);
|
||||
|
||||
expect(mockDomainObject.getModel().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);
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -20,7 +20,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="abs top-bar">
|
||||
<div class="title">{{ngModel.title}}</div>
|
||||
<div class="dialog-title">{{ngModel.title}}</div>
|
||||
<div class="hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
|
||||
</div>
|
||||
<div class='abs editor'>
|
||||
|
@ -1,11 +1,10 @@
|
||||
<div class="l-message"
|
||||
ng-class="'message-severity-' + ngModel.severity">
|
||||
<div class="ui-symbol type-icon message-type"></div>
|
||||
<div class="message-contents">
|
||||
<div class="w-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}}
|
||||
@ -25,8 +24,6 @@
|
||||
ng-click="ngModel.primaryOption.callback()">
|
||||
{{ngModel.primaryOption.label}}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,17 +1,17 @@
|
||||
<mct-container key="overlay" class="t-message-list">
|
||||
<div class="message-contents">
|
||||
<div class="abs top-bar">
|
||||
<div class="title">{{ngModel.dialog.title}}</div>
|
||||
<mct-container key="overlay">
|
||||
<div class="t-message-list">
|
||||
<div class="top-bar">
|
||||
<div class="dialog-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="abs message-body">
|
||||
<div class="w-messages">
|
||||
<mct-include
|
||||
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
|
||||
key="'message'" ng-model="msg.model"></mct-include>
|
||||
</div>
|
||||
<div class="abs bottom-bar">
|
||||
<div class="bottom-bar">
|
||||
<a ng-repeat="dialogAction in ngModel.dialog.actions"
|
||||
class="s-button major"
|
||||
ng-click="dialogAction.action()">
|
||||
|
@ -21,7 +21,7 @@
|
||||
-->
|
||||
<mct-container key="overlay">
|
||||
<div class="abs top-bar">
|
||||
<div class="title">{{ngModel.dialog.title}}</div>
|
||||
<div class="dialog-title">{{ngModel.dialog.title}}</div>
|
||||
<div class="hint">{{ngModel.dialog.hint}}</div>
|
||||
</div>
|
||||
<div class='abs editor'>
|
||||
|
@ -121,7 +121,8 @@ define([
|
||||
"key": "ElementsController",
|
||||
"implementation": ElementsController,
|
||||
"depends": [
|
||||
"$scope"
|
||||
"$scope",
|
||||
"openmct"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -299,9 +300,6 @@ define([
|
||||
{
|
||||
"key": "edit-elements",
|
||||
"template": elementsTemplate,
|
||||
"uses": [
|
||||
"composition"
|
||||
],
|
||||
"gestures": [
|
||||
"drop"
|
||||
]
|
||||
@ -385,7 +383,10 @@ define([
|
||||
]
|
||||
},
|
||||
{
|
||||
"implementation": EditToolbarRepresenter
|
||||
"implementation": EditToolbarRepresenter,
|
||||
"depends": [
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
|
@ -23,7 +23,7 @@
|
||||
<div class="s-menu-button major create-button" ng-click="createController.toggle()">
|
||||
<span class="title-label">Create</span>
|
||||
</div>
|
||||
<div class="menu super-menu" ng-show="createController.isActive()">
|
||||
<div class="menu super-menu l-create-menu" ng-show="createController.isActive()">
|
||||
<mct-representation mct-object="domainObject" key="'create-menu'">
|
||||
</mct-representation>
|
||||
</div>
|
||||
|
@ -19,8 +19,8 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="contents" ng-controller="CreateMenuController">
|
||||
<div class="pane left menu-items">
|
||||
<div class="w-menu" ng-controller="CreateMenuController">
|
||||
<div class="col menu-items">
|
||||
<ul>
|
||||
<li ng-repeat="createAction in createActions" ng-click="createAction.perform()">
|
||||
<a ng-mouseover="representation.activeMetadata = createAction.getMetadata()"
|
||||
@ -31,8 +31,9 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pane right menu-item-description">
|
||||
<div class="col menu-item-description">
|
||||
<div class="desc-area icon {{ representation.activeMetadata.cssClass }}"></div>
|
||||
<div class="w-title-desc">
|
||||
<div class="desc-area title">
|
||||
{{representation.activeMetadata.name}}
|
||||
</div>
|
||||
@ -41,3 +42,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -61,7 +61,12 @@
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="abs flex-elem grows object-holder-main scroll"
|
||||
toolbar="toolbar">
|
||||
toolbar="toolbar"
|
||||
mct-selectable="{
|
||||
item: domainObject.useCapability('adapter'),
|
||||
oldItem: domainObject
|
||||
}"
|
||||
mct-init-select>
|
||||
</mct-representation>
|
||||
</div><!--/ l-object-wrapper-inner -->
|
||||
</div>
|
||||
|
@ -25,7 +25,7 @@
|
||||
ng-model="filterBy">
|
||||
</mct-include>
|
||||
<div class="flex-elem grows vscroll">
|
||||
<ul class="tree">
|
||||
<ul class="tree" ng-if="composition.length > 0">
|
||||
<li ng-repeat="containedObject in composition | filter:searchElements">
|
||||
<span class="tree-item">
|
||||
<mct-representation
|
||||
@ -36,5 +36,6 @@
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="composition.length === 0">No contained elements</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -101,10 +101,15 @@ 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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -28,16 +28,6 @@ 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 &&
|
||||
@ -59,10 +49,7 @@ define(
|
||||
|
||||
var removeCheck = navigationService
|
||||
.checkBeforeNavigation(function () {
|
||||
if (isDirty(domainObject)) {
|
||||
return "Continuing will cause the loss of any unsaved changes.";
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
|
@ -29,7 +29,11 @@ define(
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function ElementsController($scope) {
|
||||
function ElementsController($scope, openmct) {
|
||||
this.scope = $scope;
|
||||
this.scope.composition = [];
|
||||
var self = this;
|
||||
|
||||
function filterBy(text) {
|
||||
if (typeof text === 'undefined') {
|
||||
return $scope.searchText;
|
||||
@ -47,10 +51,54 @@ 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 selectedObjectComposition = domainObject && domainObject.useCapability('composition');
|
||||
|
||||
if (selectedObjectComposition) {
|
||||
selectedObjectComposition.then(function (composition) {
|
||||
this.scope.composition = composition;
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.scope.composition = [];
|
||||
}
|
||||
};
|
||||
|
||||
return ElementsController;
|
||||
}
|
||||
);
|
||||
|
@ -38,7 +38,7 @@ define(
|
||||
* @constructor
|
||||
* @implements {Representer}
|
||||
*/
|
||||
function EditToolbarRepresenter(scope, element, attrs) {
|
||||
function EditToolbarRepresenter(openmct, scope, element, attrs) {
|
||||
var self = this;
|
||||
|
||||
// Mark changes as ready to persist
|
||||
@ -109,6 +109,7 @@ define(
|
||||
this.updateSelection = updateSelection;
|
||||
this.toolbar = undefined;
|
||||
this.toolbarObject = {};
|
||||
this.openmct = openmct;
|
||||
|
||||
// If this representation exposes a toolbar, set up watches
|
||||
// to synchronize with it.
|
||||
@ -146,7 +147,7 @@ define(
|
||||
// Expose the toolbar object to the parent scope
|
||||
initialize(definition);
|
||||
// Create a selection scope
|
||||
this.setSelection(new EditToolbarSelection());
|
||||
this.setSelection(new EditToolbarSelection(this.openmct));
|
||||
// Initialize toolbar to an empty selection
|
||||
this.updateSelection([]);
|
||||
};
|
||||
|
@ -38,10 +38,24 @@ define(
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditToolbarSelection() {
|
||||
function EditToolbarSelection(openmct) {
|
||||
this.selection = [{}];
|
||||
this.selecting = false;
|
||||
this.selectedObj = undefined;
|
||||
|
||||
openmct.selection.on('change', function (selection) {
|
||||
var selected = selection[0];
|
||||
|
||||
if (selected && selected.context.toolbar) {
|
||||
this.select(selected.context.toolbar);
|
||||
} else {
|
||||
this.deselect();
|
||||
}
|
||||
|
||||
if (selected && selected.context.viewProxy) {
|
||||
this.proxy(selected.context.viewProxy);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,6 +62,7 @@ define(
|
||||
);
|
||||
mockTransactionService.commit.andReturn(fastPromise());
|
||||
mockTransactionService.cancel.andReturn(fastPromise());
|
||||
mockTransactionService.isActive = jasmine.createSpy('isActive');
|
||||
|
||||
mockStatusCapability = jasmine.createSpyObj(
|
||||
"statusCapability",
|
||||
@ -141,6 +142,7 @@ define(
|
||||
|
||||
describe("finish", function () {
|
||||
beforeEach(function () {
|
||||
mockTransactionService.isActive.andReturn(true);
|
||||
capability.edit();
|
||||
capability.finish();
|
||||
});
|
||||
@ -152,6 +154,23 @@ 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 = {};
|
||||
|
||||
|
@ -104,10 +104,10 @@ define(
|
||||
mockEditorCapability.isEditContextRoot.andReturn(false);
|
||||
mockEditorCapability.dirty.andReturn(false);
|
||||
|
||||
expect(checkFn()).toBe(false);
|
||||
expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
|
||||
|
||||
mockEditorCapability.isEditContextRoot.andReturn(true);
|
||||
expect(checkFn()).toBe(false);
|
||||
expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
|
||||
|
||||
mockEditorCapability.dirty.andReturn(true);
|
||||
expect(checkFn())
|
||||
|
@ -27,11 +27,47 @@ define(
|
||||
|
||||
describe("The Elements Pane controller", function () {
|
||||
var mockScope,
|
||||
mockOpenMCT,
|
||||
mockSelection,
|
||||
mockDomainObject,
|
||||
mockMutationCapability,
|
||||
mockUnlisten,
|
||||
selectable = [],
|
||||
controller;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpy("$scope");
|
||||
controller = new ElementsController(mockScope);
|
||||
mockUnlisten = jasmine.createSpy('unlisten');
|
||||
mockMutationCapability = jasmine.createSpyObj("mutationCapability", [
|
||||
"listen"
|
||||
]);
|
||||
mockMutationCapability.listen.andReturn(mockUnlisten);
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", [
|
||||
"getCapability",
|
||||
"useCapability"
|
||||
]);
|
||||
mockDomainObject.useCapability.andCallThrough();
|
||||
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');
|
||||
|
||||
controller = new ElementsController(mockScope, mockOpenMCT);
|
||||
});
|
||||
|
||||
function getModel(model) {
|
||||
@ -63,6 +99,44 @@ 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');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -29,7 +29,9 @@ define(
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockUnwatch,
|
||||
representer;
|
||||
representer,
|
||||
mockOpenMCT,
|
||||
mockSelection;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
@ -46,7 +48,18 @@ 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
|
||||
|
@ -28,13 +28,25 @@ define(
|
||||
var testProxy,
|
||||
testElement,
|
||||
otherElement,
|
||||
selection;
|
||||
selection,
|
||||
mockSelection,
|
||||
mockOpenMCT;
|
||||
|
||||
beforeEach(function () {
|
||||
testProxy = { someKey: "some value" };
|
||||
testElement = { someOtherKey: "some other value" };
|
||||
otherElement = { yetAnotherKey: 42 };
|
||||
selection = new EditToolbarSelection();
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
// 'select',
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn([]);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
};
|
||||
selection = new EditToolbarSelection(mockOpenMCT);
|
||||
selection.proxy(testProxy);
|
||||
});
|
||||
|
||||
|
@ -121,6 +121,9 @@ define([
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.parse = function (text) {
|
||||
if (typeof text === 'number') {
|
||||
return text;
|
||||
}
|
||||
return moment.utc(text, DATE_FORMATS).valueOf();
|
||||
};
|
||||
|
||||
|
@ -41,6 +41,7 @@ define([
|
||||
"./src/controllers/BannerController",
|
||||
"./src/directives/MCTContainer",
|
||||
"./src/directives/MCTDrag",
|
||||
"./src/directives/MCTSelectable",
|
||||
"./src/directives/MCTClickElsewhere",
|
||||
"./src/directives/MCTResize",
|
||||
"./src/directives/MCTPopup",
|
||||
@ -90,6 +91,7 @@ define([
|
||||
BannerController,
|
||||
MCTContainer,
|
||||
MCTDrag,
|
||||
MCTSelectable,
|
||||
MCTClickElsewhere,
|
||||
MCTResize,
|
||||
MCTPopup,
|
||||
@ -328,6 +330,13 @@ define([
|
||||
"$document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctSelectable",
|
||||
"implementation": MCTSelectable,
|
||||
"depends": [
|
||||
"openmct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctClickElsewhere",
|
||||
"implementation": MCTClickElsewhere,
|
||||
|
@ -2,7 +2,7 @@
|
||||
"metadata": {
|
||||
"name": "openmct-symbols-16px",
|
||||
"lastOpened": 0,
|
||||
"created": 1505151140023
|
||||
"created": 1506973656040
|
||||
},
|
||||
"iconSets": [
|
||||
{
|
||||
@ -899,6 +899,14 @@
|
||||
"prevSize": 24,
|
||||
"code": 921904,
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 139,
|
||||
"id": 117,
|
||||
"name": "icon-notebook",
|
||||
"prevSize": 24,
|
||||
"code": 921905,
|
||||
"tempChar": ""
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
@ -3524,6 +3532,29 @@
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
|
Binary file not shown.
@ -118,4 +118,5 @@
|
||||
<glyph unicode="󡄨" 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="󡄩" 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="󡄰" 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="󡄱" 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 |
Binary file not shown.
Binary file not shown.
@ -137,6 +137,11 @@
|
||||
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 { @include flex-direction(column); }
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ $plotXBarH: 32px;
|
||||
$plotLegendH: 20px;
|
||||
$plotSwatchD: 8px;
|
||||
// 1: Top, 2: right, 3: bottom, 4: left
|
||||
$plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH, $plotYBarW);
|
||||
$plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW);
|
||||
/* min plot height is based on user testing to find minimum useful height */
|
||||
$plotMinH: 95px;
|
||||
/*************** Bubbles */
|
||||
@ -111,7 +111,9 @@ $bubbleMaxW: 300px;
|
||||
$reqSymbolW: 15px;
|
||||
$reqSymbolM: $interiorMargin * 2;
|
||||
$reqSymbolFontSize: 0.75em;
|
||||
$inputTextP: 3px 5px;
|
||||
$inputTextPTopBtm: 3px;
|
||||
$inputTextPLeftRight: 5px;
|
||||
$inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
|
||||
/*************** Wait Spinner Defaults */
|
||||
$waitSpinnerD: 32px;
|
||||
$waitSpinnerTreeD: 20px;
|
||||
|
@ -25,6 +25,7 @@
|
||||
}
|
||||
|
||||
.l-fixed-position-item {
|
||||
border-width: 1px;
|
||||
position: absolute;
|
||||
&.s-not-selected {
|
||||
opacity: 0.8;
|
||||
@ -52,6 +53,7 @@
|
||||
font-size: 0.8rem;
|
||||
$p: 1px;
|
||||
line-height: 100%;
|
||||
overflow: hidden;
|
||||
&.l-static-text {
|
||||
padding: $p;
|
||||
}
|
||||
|
@ -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,6 +180,20 @@ 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;
|
||||
@ -234,6 +248,12 @@ a.disabled {
|
||||
color: rgba(#fff, 0.2);
|
||||
}
|
||||
|
||||
.comma-list span {
|
||||
&:not(:first-child) {
|
||||
&:before { content: ', '; }
|
||||
}
|
||||
}
|
||||
|
||||
.test-stripes {
|
||||
@include bgDiagonalStripes();
|
||||
}
|
||||
|
@ -146,6 +146,7 @@ $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 */
|
||||
|
||||
@ -260,6 +261,7 @@ $glyph-icon-summary-widget: '\e1130';
|
||||
.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'); }
|
||||
|
@ -44,6 +44,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.t-alert-unsynced {
|
||||
@extend .icon-alert-triangle;
|
||||
color: $colorPausedBg;
|
||||
}
|
||||
|
||||
.bar .ui-symbol {
|
||||
display: inline-block;
|
||||
}
|
||||
@ -81,18 +87,5 @@
|
||||
@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;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
@ -26,5 +26,6 @@
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@
|
||||
.l-inspector-part {
|
||||
box-sizing: border-box;
|
||||
padding-right: $interiorMargin;
|
||||
|
||||
.tree .form {
|
||||
margin-left: $treeVCW + $interiorMarginLg;
|
||||
}
|
||||
@ -78,6 +79,7 @@
|
||||
}
|
||||
}
|
||||
.form-row {
|
||||
// To be replaced with .inspector-config, see below.
|
||||
@include align-items(center);
|
||||
border: none !important;
|
||||
margin-bottom: 0 !important;
|
||||
@ -99,15 +101,12 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
}
|
||||
|
||||
em.t-inspector-part-header {
|
||||
border-radius: $basicCr;
|
||||
background-color: $colorInspectorSectionHeaderBg;
|
||||
color: $colorInspectorSectionHeaderFg;
|
||||
margin-bottom: $interiorMargin;
|
||||
margin-top: $interiorMarginLg;
|
||||
//margin-bottom: $interiorMargin;
|
||||
padding: floor($formTBPad * .75) $formLRPad;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@ -201,3 +200,102 @@ mct-representation:not(.s-status-editing) .l-inspect {
|
||||
pointer-events: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
// NEW COMPACT FORM, FOR USE IN INSPECTOR
|
||||
// ul > li > label, control
|
||||
// Make a new UL for each form section
|
||||
// Allow control-first, controls-below
|
||||
|
||||
.l-inspect .tree ul li,
|
||||
.inspector-config ul li {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
|
||||
.inspector-config {
|
||||
$labelW: 40%;
|
||||
$minW: $labelW;
|
||||
ul {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
li {
|
||||
@include display(flex);
|
||||
@include flex-wrap(wrap);
|
||||
@include align-items(center);
|
||||
label,
|
||||
.control {
|
||||
@include display(flex);
|
||||
min-width: $minW;
|
||||
}
|
||||
label {
|
||||
line-height: inherit;
|
||||
padding: $interiorMarginSm 0;
|
||||
width: $labelW;
|
||||
}
|
||||
.control {
|
||||
@include flex-grow(1);
|
||||
}
|
||||
|
||||
&:not(.section-header) {
|
||||
&:not(.connects-to-previous) {
|
||||
//border-top: 1px solid $colorFormLines;
|
||||
}
|
||||
}
|
||||
|
||||
&.connects-to-previous {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
&.section-header {
|
||||
margin-top: $interiorMarginLg;
|
||||
border-top: 1px solid $colorFormLines;
|
||||
}
|
||||
|
||||
&.controls-first {
|
||||
.control {
|
||||
@include flex-grow(0);
|
||||
margin-right: $interiorMargin;
|
||||
min-width: 0;
|
||||
order: 1;
|
||||
width: auto;
|
||||
}
|
||||
label {
|
||||
@include 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tree .inspector-config {
|
||||
margin-left: $treeVCW + $interiorMarginLg;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@
|
||||
/********************************* CONTROLS */
|
||||
@import "controls/breadcrumb";
|
||||
@import "controls/buttons";
|
||||
@import "controls/color-palette";
|
||||
@import "controls/palette";
|
||||
@import "controls/controls";
|
||||
@import "controls/lists";
|
||||
@import "controls/menus";
|
||||
@ -70,6 +70,7 @@
|
||||
@import "fixed-position";
|
||||
@import "lists/tabular";
|
||||
@import "plots/plots-main";
|
||||
@import "plots/legend";
|
||||
@import "iframe";
|
||||
@import "views";
|
||||
@import "items/item";
|
||||
@ -80,3 +81,4 @@
|
||||
@import "autoflow";
|
||||
@import "features/imagery";
|
||||
@import "features/time-display";
|
||||
@import "widgets";
|
||||
|
@ -316,23 +316,28 @@
|
||||
text-shadow: $shdwItemText;
|
||||
}
|
||||
|
||||
@mixin input-base($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.6) 0 1px 3px) {
|
||||
@mixin input-base() {
|
||||
@include appearance(none);
|
||||
border-radius: $controlCr;
|
||||
box-sizing: border-box;
|
||||
box-shadow: inset $shdw;
|
||||
background: $bg;
|
||||
border: none;
|
||||
color: $fg;
|
||||
outline: none;
|
||||
&:focus { outline: 0; }
|
||||
&.error {
|
||||
background-color: $colorFormFieldErrorBg;
|
||||
color: $colorFormFieldErrorFg;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg) {
|
||||
@include input-base($bg, $fg);
|
||||
@mixin s-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.6) 0 1px 3px) {
|
||||
@include input-base();
|
||||
background: $bg;
|
||||
box-shadow: inset $shdw;
|
||||
color: $fg;
|
||||
}
|
||||
|
||||
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.6) 0 1px 3px) {
|
||||
@include s-input($bg, $fg, $shdw);
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@mixin contextArrow() {
|
||||
@ -344,7 +349,7 @@
|
||||
}
|
||||
|
||||
@mixin nice-textarea($bg: $colorBodyBg, $fg: $colorBodyFg) {
|
||||
@include input-base($bg, $fg);
|
||||
@include nice-input($bg, $fg);
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,6 @@
|
||||
content:'';
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.8em;
|
||||
display: inline;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
}
|
||||
|
||||
.l-view-section {
|
||||
//@include test(orange, 0.1);
|
||||
@include absPosDefault(0);
|
||||
h2 {
|
||||
color: #fff;
|
||||
|
306
platform/commonUI/general/res/sass/_widgets.scss
Normal file
306
platform/commonUI/general/res/sass/_widgets.scss
Normal file
@ -0,0 +1,306 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
@include 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;
|
||||
@include 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 {
|
||||
@include align-items(flex-start);
|
||||
@include 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;
|
||||
@include align-items(center);
|
||||
> span { display: block; }
|
||||
.grippy-holder,
|
||||
.view-control {
|
||||
margin-right: $interiorMargin;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.widget-thumb {
|
||||
@include flex(1 1 auto);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.rule-title {
|
||||
@include flex(0 1 auto);
|
||||
color: pullForward($colorBodyFg, 50%);
|
||||
}
|
||||
|
||||
.rule-description {
|
||||
@include 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,6 @@ $pad: $interiorMargin * $baseRatio;
|
||||
height: $btnStdH;
|
||||
line-height: $btnStdH;
|
||||
padding: 0 $pad;
|
||||
vertical-align: top;
|
||||
|
||||
&.labeled:before {
|
||||
// Icon when it's included
|
||||
|
@ -72,11 +72,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.s-hyperlink {
|
||||
// Hyperlink objects
|
||||
.s-hyperlink {
|
||||
.label {
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
&:not(.s-button) {
|
||||
color: $colorKey;
|
||||
font-size: 0.8rem;
|
||||
&:hover { color: $colorKeyHov; }
|
||||
}
|
||||
}
|
||||
@ -148,6 +150,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** 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 {
|
||||
@ -232,12 +254,16 @@ textarea {
|
||||
}
|
||||
|
||||
/******************************************************** INPUTS */
|
||||
%input-base {
|
||||
@include input-base();
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="search"],
|
||||
input[type="number"] {
|
||||
@include nice-input();
|
||||
vertical-align: baseline;
|
||||
padding: $inputTextP;
|
||||
padding: $inputTextPTopBtm $inputTextPLeftRight;
|
||||
&.numeric {
|
||||
text-align: right;
|
||||
}
|
||||
@ -255,7 +281,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 { width: 50px !important; }
|
||||
input[type="text"].sm, input[type="number"].sm { width: 50px !important; }
|
||||
.l-numeric input[type="text"],
|
||||
input[type="text"].numeric { text-align: right; }
|
||||
|
||||
@ -281,21 +307,44 @@ textarea.lg { position: relative; height: 300px; }
|
||||
}
|
||||
}
|
||||
|
||||
*[contenteditable].s-input-inline,
|
||||
input[type="text"].s-input-inline,
|
||||
.s-input-inline input[type="text"] {
|
||||
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
|
||||
@extend %input-base;
|
||||
@include trans-prop-nice((padding, box-shadow), 250ms);
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
min-width: 20px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
&:hover,
|
||||
&:focus {
|
||||
padding-left: $inputTextPLeftRight;
|
||||
padding-right: $inputTextPLeftRight;
|
||||
}
|
||||
&:hover {
|
||||
border-color: rgba($colorBodyFg, 0.2);
|
||||
}
|
||||
&:focus {
|
||||
@include s-input();
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** SELECTS */
|
||||
.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;
|
||||
&:focus { outline: 0; }
|
||||
background: none;
|
||||
color: $colorSelectFg;
|
||||
cursor: pointer;
|
||||
@ -307,12 +356,13 @@ textarea.lg { position: relative; height: 300px; }
|
||||
}
|
||||
}
|
||||
&:before {
|
||||
//@include contextArrow();
|
||||
pointer-events: none;
|
||||
color: rgba($colorSelectFg, percentToDecimal($contrastInvokeMenuPercent));
|
||||
@include transform(translateY(-50%));
|
||||
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: $interiorMargin; top: 0;
|
||||
right: $interiorMargin;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,8 +414,7 @@ textarea.lg { position: relative; height: 300px; }
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@ -531,7 +580,6 @@ textarea.lg { position: relative; height: 300px; }
|
||||
height: $h;
|
||||
margin-top: 1 + floor($h/2) * -1;
|
||||
@include btnSubtle(pullForward($colorBtnBg, 10%));
|
||||
//border-radius: 50% !important;
|
||||
}
|
||||
|
||||
@mixin sliderKnobRound() {
|
||||
@ -546,7 +594,6 @@ textarea.lg { position: relative; height: 300px; }
|
||||
|
||||
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 {
|
||||
@ -704,6 +751,30 @@ 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);
|
||||
@include transform-origin(center);
|
||||
}
|
||||
&.expanded:before {
|
||||
@include transform(rotate(90deg));
|
||||
}
|
||||
}
|
||||
|
||||
.grippy {
|
||||
@extend .icon-grippy;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
/******************************************************** BROWSER ELEMENTS */
|
||||
body.desktop {
|
||||
::-webkit-scrollbar {
|
||||
@ -721,11 +792,15 @@ body.desktop {
|
||||
}
|
||||
|
||||
.overlay ::-webkit-scrollbar-thumb {
|
||||
$lr: 15%;
|
||||
background: $scrollbarThumbColorOverlay;
|
||||
&:hover { background: $scrollbarThumbColorOverlayHov; }
|
||||
}
|
||||
|
||||
.menu ::-webkit-scrollbar-thumb {
|
||||
background: $scrollbarThumbColorMenu;
|
||||
&:hover { background: $scrollbarThumbColorMenuHov; }
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: $scrollbarTrackColorBg;
|
||||
}
|
||||
|
@ -29,22 +29,27 @@
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 16px; //120%;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.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;
|
||||
vertical-align: middle;
|
||||
margin-left: $interiorMarginSm;
|
||||
margin-top: -2px;
|
||||
&:not(.no-selection) {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
@ -72,17 +77,12 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.s-menu {
|
||||
.menu {
|
||||
border-radius: $basicCr;
|
||||
@include containerSubtle($colorMenuBg, $colorMenuFg);
|
||||
@include boxShdw($shdwMenu);
|
||||
@include txtShdw($shdwMenuText);
|
||||
padding: $interiorMarginSm 0;
|
||||
}
|
||||
|
||||
.menu {
|
||||
// TODO: reduce size of icons
|
||||
@extend .s-menu;
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
@ -92,7 +92,6 @@
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid pullForward($colorMenuBg, 10%);
|
||||
color: $colorMenuFg;
|
||||
//color: pullForward($colorMenuBg, 60%);
|
||||
line-height: $menuLineH;
|
||||
padding: $interiorMarginSm $interiorMargin * 2 $interiorMarginSm ($interiorMargin * 2) + $treeTypeIconW;
|
||||
position: relative;
|
||||
@ -165,27 +164,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
.super-menu {
|
||||
$w: 500px;
|
||||
$h: $w - 20;
|
||||
$plw: 50%;
|
||||
$prw: 50%;
|
||||
display: block;
|
||||
width: $w;
|
||||
height: $h;
|
||||
.contents {
|
||||
@include absPosDefault($interiorMargin);
|
||||
}
|
||||
.pane {
|
||||
.super-menu,
|
||||
.super-menu > mct-representation,
|
||||
.super-menu > .contents {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.super-menu {
|
||||
$plw: 50%;
|
||||
$prw: 100% - $plw;
|
||||
position: absolute;
|
||||
.w-menu {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: $interiorMarginLg;
|
||||
}
|
||||
.col {
|
||||
box-sizing: border-box;
|
||||
flex: 1 1 auto;
|
||||
overflow-x: hidden;
|
||||
&.menu-items {
|
||||
border-right: 1px solid pullForward($colorMenuBg, 10%);
|
||||
left: 0;
|
||||
padding-right: $interiorMargin;
|
||||
right: auto;
|
||||
width: $plw;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-right: $interiorMargin;
|
||||
width: $plw;
|
||||
ul {
|
||||
li {
|
||||
border-radius: $controlCr;
|
||||
@ -195,22 +200,19 @@
|
||||
}
|
||||
}
|
||||
&.menu-item-description {
|
||||
left: auto;
|
||||
right: 0;
|
||||
padding: $interiorMargin * 5;
|
||||
$p: $interiorMargin * 3;
|
||||
overflow-y: hidden;
|
||||
padding: $p $p 0 $p;
|
||||
width: $prw;
|
||||
|
||||
.desc-area {
|
||||
&.icon {
|
||||
color: $colorCreateMenuLgIcon;
|
||||
font-size: 8em;
|
||||
margin-bottom: $interiorMargin * 3;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
&.title {
|
||||
color: $colorCreateMenuText;
|
||||
font-size: 1.2em;
|
||||
margin-bottom: $interiorMargin * 2;
|
||||
}
|
||||
&.description {
|
||||
color: pushBack($colorCreateMenuText, 20%);
|
||||
@ -221,27 +223,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
.w-title-desc {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; // Height set in specific menu instances
|
||||
}
|
||||
|
||||
// Specific menu instances
|
||||
&.l-create-menu {
|
||||
width: 500px;
|
||||
.col {
|
||||
max-height: 70vh;
|
||||
}
|
||||
.w-title-desc {
|
||||
height: 190px;
|
||||
}
|
||||
.desc-area {
|
||||
&.icon {
|
||||
font-size: 8em;
|
||||
height: 135px;
|
||||
margin-bottom: $interiorMargin * 3;
|
||||
}
|
||||
&.title {
|
||||
font-size: 1.2em;
|
||||
margin-bottom: $interiorMargin * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mini {
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
.pane {
|
||||
.col {
|
||||
max-height: 50vh;
|
||||
&.menu-items {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
&.menu-item-description {
|
||||
padding: $interiorMargin * 3;
|
||||
$p: $interiorMargin * 2;
|
||||
padding: $p $p 0 $p;
|
||||
}
|
||||
}
|
||||
.w-title-desc {
|
||||
height: 180px;
|
||||
}
|
||||
.desc-area {
|
||||
&.icon {
|
||||
font-size: 4em;
|
||||
height: 70px;
|
||||
margin-bottom: $interiorMargin * 3;
|
||||
}
|
||||
&.title {
|
||||
font-size: 1em;
|
||||
margin-bottom: $interiorMargin * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
font-size: 0.80rem;
|
||||
}
|
||||
@ -280,5 +318,6 @@
|
||||
}
|
||||
|
||||
.menus-up .menu {
|
||||
bottom: $btnStdH; top: auto;
|
||||
bottom: $btnStdH;
|
||||
top: auto;
|
||||
}
|
||||
|
@ -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,7 +120,11 @@
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
background: none !important;
|
||||
margin-right: $interiorMarginSm;
|
||||
&[class*='s-status']:before {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
@ -136,7 +140,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles for messages and message banners */
|
||||
/******************************************************************* MESSAGE BANNERS */
|
||||
.message {
|
||||
&.block {
|
||||
border-radius: $basicCr;
|
||||
@ -192,7 +196,6 @@
|
||||
padding: 0 $interiorMargin;
|
||||
}
|
||||
.close {
|
||||
//@include test(red, 0.7);
|
||||
cursor: pointer;
|
||||
font-size: 7px;
|
||||
width: 8px;
|
||||
@ -236,132 +239,147 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin messageBlock($iconW: 32px) {
|
||||
.type-icon.message-type {
|
||||
/******************************************************************* MESSAGES */
|
||||
|
||||
/* Contexts:
|
||||
In .t-message-list
|
||||
In .overlay as a singleton
|
||||
Inline in the view area
|
||||
*/
|
||||
|
||||
// Archetypal message
|
||||
.l-message {
|
||||
$iconW: 32px;
|
||||
@include display(flex);
|
||||
@include flex-direction(row);
|
||||
@include align-items(stretch);
|
||||
padding: $interiorMarginLg;
|
||||
|
||||
&:before {
|
||||
// Icon
|
||||
@include flex(0 1 auto);
|
||||
@include txtShdw($shdwStatusIc);
|
||||
@extend .icon-bell;
|
||||
color: $colorStatusDefault;
|
||||
font-size: $iconW;
|
||||
padding: 1px;
|
||||
width: $iconW + 2;
|
||||
margin-right: $interiorMarginLg;
|
||||
}
|
||||
|
||||
.message-severity-info .type-icon.message-type {
|
||||
&.message-severity-info:before {
|
||||
@extend .icon-info;
|
||||
color: $colorInfo;
|
||||
}
|
||||
.message-severity-alert .type-icon.message-type {
|
||||
@extend .icon-bell;
|
||||
|
||||
&.message-severity-alert:before {
|
||||
color: $colorWarningLo;
|
||||
}
|
||||
.message-severity-error .type-icon.message-type {
|
||||
|
||||
&.message-severity-error:before {
|
||||
@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
|
||||
*/
|
||||
|
||||
.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 {
|
||||
|
||||
.w-message-contents {
|
||||
@include flex(1 1 auto);
|
||||
margin-left: $overlayMargin;
|
||||
position: relative;
|
||||
|
||||
.top-bar,
|
||||
.message-body {
|
||||
margin-bottom: $interiorMarginLg * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Message as singleton
|
||||
.t-message-single {
|
||||
@include messageBlock(80px);
|
||||
}
|
||||
|
||||
body.desktop .t-message-single {
|
||||
.l-message,
|
||||
.bottom-bar {
|
||||
@include absPosDefault();
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
top: auto;
|
||||
height: $ovrFooterH;
|
||||
}
|
||||
}
|
||||
|
||||
@include phonePortrait {
|
||||
.t-message-single {
|
||||
.l-message {
|
||||
@include display(flex);
|
||||
@include flex-direction(column);
|
||||
.message-contents { margin-left: 0; }
|
||||
}
|
||||
.type-icon.message-type {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
> div,
|
||||
> span {
|
||||
//@include test(red);
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
text-align: center !important;
|
||||
.message-body {
|
||||
@include 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 {
|
||||
.message-body {
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Messages in list
|
||||
// In a list
|
||||
.t-message-list {
|
||||
@include messageBlock(32px);
|
||||
@include absPosDefault();
|
||||
@include display(flex);
|
||||
@include flex-direction(column);
|
||||
|
||||
.message-contents {
|
||||
> div,
|
||||
> span {
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
|
||||
.w-messages {
|
||||
@include flex(1 1 100%);
|
||||
overflow-y: auto;
|
||||
padding-right: $interiorMargin;
|
||||
}
|
||||
// Each message
|
||||
.l-message {
|
||||
border-radius: $controlCr;
|
||||
background: rgba($colorOvrFg, 0.1);
|
||||
margin-bottom: $interiorMargin;
|
||||
padding: $interiorMarginLg;
|
||||
|
||||
.message-contents,
|
||||
.hint,
|
||||
.bottom-bar {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.message-contents {
|
||||
font-size: 0.9em;
|
||||
margin-left: $interiorMarginLg;
|
||||
.message-action { color: pushBack($colorOvrFg, 20%); }
|
||||
.bottom-bar { text-align: left; }
|
||||
}
|
||||
|
||||
.top-bar,
|
||||
.message-body {
|
||||
@include phonePortrait {
|
||||
.t-message-single .l-message,
|
||||
.t-message-single.l-message {
|
||||
@include flex-direction(column);
|
||||
&:before {
|
||||
margin-right: 0;
|
||||
margin-bottom: $interiorMarginLg;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
text-align: center;
|
||||
.s-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop .t-message-list {
|
||||
.message-contents .l-message { margin-right: $interiorMarginLg; }
|
||||
.w-message-contents { padding-right: $interiorMargin; }
|
||||
}
|
||||
|
||||
// Alert elements in views
|
||||
@ -380,10 +398,6 @@ body.desktop .t-message-list {
|
||||
.object-header {
|
||||
.t-object-alert {
|
||||
display: inline;
|
||||
&.t-alert-unsynced {
|
||||
@extend .icon-alert-triangle;
|
||||
color: $colorPausedBg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
89
platform/commonUI/general/res/sass/controls/_palette.scss
Normal file
89
platform/commonUI/general/res/sass/controls/_palette.scss
Normal file
@ -0,0 +1,89 @@
|
||||
/*****************************************************************************
|
||||
* 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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -80,23 +80,32 @@
|
||||
|
||||
// Editing Grids
|
||||
.l-grid-holder {
|
||||
display: block;
|
||||
.l-grid {
|
||||
&.l-grid-x { @include bgTicks($colorGridLines, 'x'); }
|
||||
&.l-grid-y { @include bgTicks($colorGridLines, 'y'); }
|
||||
}
|
||||
}
|
||||
|
||||
// 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 grid when selected or selection parent.
|
||||
.s-selected .l-grid-holder,
|
||||
.s-selected-parent .l-grid-holder {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Prevent nested frames from being selectable until we have proper sub-object editing
|
||||
.t-frame-outer .t-frame-outer {
|
||||
pointer-events: none;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,19 @@
|
||||
* 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 {
|
||||
@ -41,15 +53,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
@ -57,9 +60,6 @@
|
||||
margin-bottom: $interiorMarginLg * 2;
|
||||
padding: $formTBPad 0;
|
||||
position: relative;
|
||||
//&ng-form {
|
||||
// display: block;
|
||||
//}
|
||||
|
||||
&.first {
|
||||
border-top: none;
|
||||
@ -171,3 +171,106 @@
|
||||
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 {
|
||||
@include display(flex);
|
||||
@include flex-wrap(wrap);
|
||||
@include align-items(center);
|
||||
label,
|
||||
.control {
|
||||
@include display(flex);
|
||||
}
|
||||
label {
|
||||
line-height: inherit;
|
||||
width: $labelW;
|
||||
}
|
||||
.controls {
|
||||
@include 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 {
|
||||
@include flex-grow(0);
|
||||
margin-right: $interiorMargin;
|
||||
min-width: 0;
|
||||
order: 1;
|
||||
width: auto;
|
||||
}
|
||||
label {
|
||||
@include 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,9 +129,6 @@
|
||||
}
|
||||
|
||||
.s-filter {
|
||||
input[type="search"] {
|
||||
@include input-base();
|
||||
}
|
||||
.clear-icon,
|
||||
.menu-icon,
|
||||
&:before {
|
||||
|
@ -34,18 +34,7 @@ body.touch {
|
||||
line-height: $mobileTreeItemH !important;
|
||||
.view-control {
|
||||
font-size: 1em;
|
||||
margin-right: $interiorMargin;
|
||||
width: ceil($mobileTreeItemH * 0.75);
|
||||
&.has-children {
|
||||
&:before {
|
||||
content: $glyph-icon-arrow-down;
|
||||
left: 50%;
|
||||
@include transform(translateX(-50%) rotate(-90deg));
|
||||
}
|
||||
&.expanded:before {
|
||||
@include transform(translateX(-50%) rotate(0deg));
|
||||
}
|
||||
}
|
||||
width: ceil($mobileTreeItemH * 0.5);
|
||||
}
|
||||
.t-object-label {
|
||||
line-height: inherit;
|
||||
|
@ -79,6 +79,7 @@
|
||||
|
||||
// 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);
|
||||
@ -125,9 +126,9 @@
|
||||
@include containerSubtle($colorOvrBg, $colorOvrFg);
|
||||
}
|
||||
|
||||
.title {
|
||||
.dialog-title {
|
||||
@include ellipsize();
|
||||
font-size: 1.2em;
|
||||
font-size: 1.5em;
|
||||
line-height: 120%;
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
@ -156,6 +157,8 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow: auto;
|
||||
padding-right: $interiorMargin;
|
||||
padding-bottom: $interiorMargin;
|
||||
.field.l-input-med {
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
|
208
platform/commonUI/general/res/sass/plots/_legend.scss
Normal file
208
platform/commonUI/general/res/sass/plots/_legend.scss
Normal file
@ -0,0 +1,208 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -20,18 +20,64 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
.abs.holder-plot {
|
||||
// Fend off the scrollbar when less than min-height;
|
||||
right: $interiorMargin;
|
||||
right: $interiorMargin; // Fend off the scrollbar when less than min-height;
|
||||
.t-object-alert.t-alert-unsynced {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************* 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;
|
||||
@ -49,7 +95,6 @@
|
||||
}
|
||||
|
||||
.gl-plot-axis-area.gl-plot-x {
|
||||
//@include test(green);
|
||||
top: auto;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
@ -63,7 +108,7 @@
|
||||
.gl-plot-axis-area {
|
||||
position: absolute;
|
||||
&.gl-plot-y {
|
||||
top: $plotLegendH + $interiorMargin;
|
||||
top: nth($plotDisplayArea, 1);
|
||||
right: auto;
|
||||
bottom: nth($plotDisplayArea, 3);
|
||||
left: 0;
|
||||
@ -158,17 +203,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.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,
|
||||
@ -235,39 +269,6 @@
|
||||
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;
|
||||
|
@ -23,7 +23,7 @@
|
||||
ul.tree {
|
||||
@include menuUlReset();
|
||||
@include user-select(none);
|
||||
li {
|
||||
> li {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
@ -52,21 +52,11 @@ ul.tree {
|
||||
|
||||
.view-control {
|
||||
color: $colorItemTreeVC;
|
||||
font-size: 0.75em;
|
||||
margin-right: $interiorMargin;
|
||||
height: 100%;
|
||||
line-height: inherit;
|
||||
width: $treeVCW;
|
||||
&.has-children {
|
||||
&:before {
|
||||
position: absolute;
|
||||
@include trans-prop-nice(transform, 100ms);
|
||||
content: "\e904";
|
||||
@include transform-origin(center);
|
||||
}
|
||||
&.expanded:before {
|
||||
@include transform(rotate(90deg));
|
||||
}
|
||||
&:before { display: block; }
|
||||
&.no-children {
|
||||
&:before { display: none; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,12 +26,10 @@
|
||||
z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
|
||||
&:not(.no-frame) {
|
||||
background: $colorBodyBg;
|
||||
border: 1px solid $bc;
|
||||
&:hover {
|
||||
border-color: lighten($bc, 10%);
|
||||
}
|
||||
border-color: $bc;
|
||||
}
|
||||
}
|
||||
|
||||
.object-browse-bar {
|
||||
font-size: 0.75em;
|
||||
height: $ohH;
|
||||
@ -44,10 +42,11 @@
|
||||
|
||||
&.t-object-type-timer,
|
||||
&.t-object-type-clock,
|
||||
&.t-object-type-hyperlink {
|
||||
&.t-object-type-hyperlink,
|
||||
&.t-object-type-summary-widget {
|
||||
// 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 it.
|
||||
// to an object that has it.
|
||||
.object-browse-bar .right { display: none; }
|
||||
}
|
||||
|
||||
@ -91,9 +90,9 @@
|
||||
|
||||
&.no-frame {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
border-color: transparent;
|
||||
.object-browse-bar .right {
|
||||
$m: 0; // $interiorMarginSm;
|
||||
$m: 0;
|
||||
background: rgba(black, 0.3);
|
||||
border-radius: $basicCr;
|
||||
padding: $interiorMarginSm;
|
||||
@ -103,7 +102,7 @@
|
||||
}
|
||||
&.t-frame-outer > .t-rep-frame {
|
||||
&.contents {
|
||||
$m: 2px;
|
||||
$m: 0px;
|
||||
top: $m;
|
||||
right: $m;
|
||||
bottom: $m;
|
||||
@ -114,17 +113,33 @@
|
||||
display: none;
|
||||
}
|
||||
> .object-holder.abs {
|
||||
overflow: hidden;
|
||||
top: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.t-frame-outer .s-input-inline {
|
||||
// Prevent inline inputs from being edited when nested in a Layout
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/********************************************************** OBJECT TYPES */
|
||||
.t-object-type-hyperlink {
|
||||
.s-hyperlink.s-button {
|
||||
// When a hyperlink is a button in a frame, make it expand to fill out to the object-holder
|
||||
.t-object-type-hyperlink,
|
||||
.t-object-type-summary-widget {
|
||||
.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.
|
||||
@extend .abs;
|
||||
}
|
||||
|
||||
.l-summary-widget,
|
||||
.l-hyperlink.s-button {
|
||||
.label {
|
||||
@include ellipsize();
|
||||
@include transform(translateY(-50%));
|
||||
|
@ -240,7 +240,9 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
|
||||
.top-bar .buttons-main .s-button,
|
||||
.top-bar .s-menu-button,
|
||||
.tool-bar .s-button,
|
||||
.tool-bar .s-menu-button {
|
||||
.tool-bar .s-menu-button,
|
||||
.tool-bar .select,
|
||||
.tool-bar .input-labeled {
|
||||
$h: $btnToolbarH;
|
||||
height: $h;
|
||||
line-height: $h;
|
||||
|
@ -20,16 +20,36 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
.s-hover-border {
|
||||
border: 1px dotted transparent;
|
||||
border: 1px solid transparent;
|
||||
&:hover {
|
||||
border-color: rgba($colorSelectableSelectedPrimary, 0.5) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.s-status-editing {
|
||||
// Limit to editing mode until we have sub-object selection
|
||||
// Limit to editing mode
|
||||
$o: 0.5;
|
||||
$oHover: 0.8;
|
||||
$bc: $colorSelectableSelectedPrimary;
|
||||
.s-hover-border {
|
||||
// Show a border by default so user can see object bounds and empty objects
|
||||
border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
|
||||
border-color: rgba($bc, $o) !important;
|
||||
border-style: dotted !important;
|
||||
|
||||
&:hover {
|
||||
border-color: rgba($colorSelectableSelectedPrimary, 0.7) !important;
|
||||
border-color: rgba($bc, $oHover) !important;
|
||||
}
|
||||
|
||||
&.t-object-type-layout {
|
||||
border-style: dashed !important;
|
||||
}
|
||||
}
|
||||
.s-selected {
|
||||
&.s-moveable {
|
||||
&:not(.s-drilled-in) {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,8 +67,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
.s-selected > .s-moveable,
|
||||
.s-selected.s-moveable {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
.tool-bar {
|
||||
font-size: 0.7rem;
|
||||
&.btn-bar {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@ -30,9 +31,7 @@
|
||||
input[type="search"],
|
||||
input[type="number"] {
|
||||
box-sizing: border-box;
|
||||
font-size: .8em;
|
||||
height: $btnToolbarH;
|
||||
margin-bottom: 1px;
|
||||
position: relative;
|
||||
&.sm {
|
||||
width: $btnToolbarH;
|
||||
|
@ -20,7 +20,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span ng-controller="DateTimeFieldController">
|
||||
<input type="text"
|
||||
<input type="text" autocorrect="off" spellcheck="false"
|
||||
ng-model="textValue"
|
||||
ng-blur="restoreTextValue(); ngBlur()"
|
||||
ng-mouseup="ngMouseup()"
|
||||
|
@ -20,7 +20,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<!-- look at action-button for example -->
|
||||
<span class="t-filter l-filter s-filter"
|
||||
<span class="t-filter l-filter"
|
||||
ng-controller="GetterSetterController">
|
||||
<input type="search"
|
||||
class="t-filter-input"
|
||||
|
@ -40,7 +40,7 @@ define(
|
||||
|
||||
// Gets an array of the contextual parents/ancestors of the selected object
|
||||
function getContextualPath() {
|
||||
var currentObj = $scope.ngModel.selectedObject,
|
||||
var currentObj = $scope.domainObject,
|
||||
currentParent,
|
||||
parents = [];
|
||||
|
||||
@ -68,7 +68,7 @@ define(
|
||||
|
||||
// If this the the initial call of this recursive function
|
||||
if (!current) {
|
||||
current = $scope.ngModel.selectedObject;
|
||||
current = $scope.domainObject;
|
||||
$scope.primaryParents = [];
|
||||
}
|
||||
|
||||
@ -87,16 +87,16 @@ define(
|
||||
|
||||
// Gets the metadata for the selected object
|
||||
function getMetadata() {
|
||||
$scope.metadata = $scope.ngModel.selectedObject &&
|
||||
$scope.ngModel.selectedObject.hasCapability('metadata') &&
|
||||
$scope.ngModel.selectedObject.useCapability('metadata');
|
||||
$scope.metadata = $scope.domainObject &&
|
||||
$scope.domainObject.hasCapability('metadata') &&
|
||||
$scope.domainObject.useCapability('metadata');
|
||||
}
|
||||
|
||||
// Set scope variables when the selected object changes
|
||||
$scope.$watch('ngModel.selectedObject', function () {
|
||||
$scope.isLink = $scope.ngModel.selectedObject &&
|
||||
$scope.ngModel.selectedObject.hasCapability('location') &&
|
||||
$scope.ngModel.selectedObject.getCapability('location').isLink();
|
||||
$scope.$watch('domainObject', function () {
|
||||
$scope.isLink = $scope.domainObject &&
|
||||
$scope.domainObject.hasCapability('location') &&
|
||||
$scope.domainObject.getCapability('location').isLink();
|
||||
|
||||
if ($scope.isLink) {
|
||||
getPrimaryPath();
|
||||
@ -108,8 +108,11 @@ define(
|
||||
|
||||
getMetadata();
|
||||
});
|
||||
}
|
||||
|
||||
var mutation = $scope.domainObject.getCapability('mutation');
|
||||
var unlisten = mutation.listen(getMetadata);
|
||||
$scope.$on('$destroy', unlisten);
|
||||
}
|
||||
return ObjectInspectorController;
|
||||
}
|
||||
);
|
||||
|
60
platform/commonUI/general/src/directives/MCTSelectable.js
Normal file
60
platform/commonUI/general/src/directives/MCTSelectable.js
Normal file
@ -0,0 +1,60 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
|
||||
/**
|
||||
* The mct-selectable directive allows selection functionality
|
||||
* (click) to be attached to specific elements.
|
||||
*
|
||||
* @memberof platform/commonUI/general
|
||||
* @constructor
|
||||
*/
|
||||
function MCTSelectable(openmct) {
|
||||
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// mct-selectable only makes sense as an attribute
|
||||
restrict: "A",
|
||||
// Link function, to install event handlers
|
||||
link: link
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return MCTSelectable;
|
||||
}
|
||||
);
|
@ -83,9 +83,9 @@ define([
|
||||
this.activeObject = domainObject;
|
||||
|
||||
if (domainObject && domainObject.hasCapability('composition')) {
|
||||
$(this.toggleView.elements()).addClass('has-children');
|
||||
$(this.toggleView.elements()).removeClass('no-children');
|
||||
} else {
|
||||
$(this.toggleView.elements()).removeClass('has-children');
|
||||
$(this.toggleView.elements()).addClass('no-children');
|
||||
}
|
||||
|
||||
if (domainObject && domainObject.hasCapability('status')) {
|
||||
|
@ -39,10 +39,8 @@ define(
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
["$watch"]
|
||||
["$watch", "$on"]
|
||||
);
|
||||
mockScope.ngModel = {};
|
||||
mockScope.ngModel.selectedObject = 'mock selected object';
|
||||
|
||||
mockObjectService = jasmine.createSpyObj(
|
||||
"objectService",
|
||||
@ -69,22 +67,27 @@ 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('ngModel.selectedObject', jasmine.any(Function));
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("looks for contextual parent objects", function () {
|
||||
|
@ -65,6 +65,10 @@ 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()) {
|
||||
|
@ -38,7 +38,8 @@ define([
|
||||
"implementation": InspectorController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"policyService"
|
||||
"openmct",
|
||||
"$document"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@ -21,44 +21,73 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['../../browse/src/InspectorRegion'],
|
||||
function (InspectorRegion) {
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* The InspectorController adds region data for a domain object's type
|
||||
* to the scope.
|
||||
* The InspectorController listens for the selection changes and adds the selection
|
||||
* object to the scope.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function InspectorController($scope, policyService) {
|
||||
var domainObject = $scope.domainObject,
|
||||
typeCapability = domainObject.getCapability('type'),
|
||||
statusListener;
|
||||
function InspectorController($scope, openmct, $document) {
|
||||
var self = this;
|
||||
self.$scope = $scope;
|
||||
|
||||
/**
|
||||
* Filters region parts to only those allowed by region policies
|
||||
* @param regions
|
||||
* @returns {{}}
|
||||
* Callback handler for the selection change event.
|
||||
* Adds the selection object to the scope. If the selected item has an inspector view,
|
||||
* it puts the key in the scope. If provider view exists, it shows the view.
|
||||
*/
|
||||
function filterRegions(inspector) {
|
||||
//Dupe so we're not modifying the type definition.
|
||||
return inspector.regions && inspector.regions.filter(function (region) {
|
||||
return policyService.allow('region', region, domainObject);
|
||||
});
|
||||
function setSelection(selection) {
|
||||
if (selection[0]) {
|
||||
var view = openmct.inspectorViews.get(selection);
|
||||
var container = $document[0].querySelectorAll('.inspector-provider-view')[0];
|
||||
container.innerHTML = "";
|
||||
|
||||
if (view) {
|
||||
self.providerView = true;
|
||||
view.show(container);
|
||||
} else {
|
||||
self.providerView = false;
|
||||
var selectedItem = selection[0].context.oldItem;
|
||||
|
||||
if (selectedItem) {
|
||||
$scope.inspectorKey = selectedItem.getCapability("type").typeDef.inspector;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setRegions() {
|
||||
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
|
||||
self.$scope.selection = selection;
|
||||
}
|
||||
|
||||
statusListener = domainObject.getCapability("status").listen(setRegions);
|
||||
openmct.selection.on("change", setSelection);
|
||||
|
||||
setSelection(openmct.selection.get());
|
||||
|
||||
$scope.$on("$destroy", function () {
|
||||
statusListener();
|
||||
openmct.selection.off("change", setSelection);
|
||||
});
|
||||
|
||||
setRegions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the selected item.
|
||||
*
|
||||
* @returns a domain object
|
||||
*/
|
||||
InspectorController.prototype.selectedItem = function () {
|
||||
return this.$scope.selection[0].context.oldItem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a provider view exists.
|
||||
*
|
||||
* @returns 'true' if provider view exists, 'false' otherwise
|
||||
*/
|
||||
InspectorController.prototype.hasProviderView = function () {
|
||||
return this.providerView;
|
||||
};
|
||||
|
||||
return InspectorController;
|
||||
}
|
||||
);
|
||||
|
@ -27,82 +27,93 @@ define(
|
||||
describe("The inspector controller ", function () {
|
||||
var mockScope,
|
||||
mockDomainObject,
|
||||
mockTypeCapability,
|
||||
mockTypeDefinition,
|
||||
mockPolicyService,
|
||||
mockStatusCapability,
|
||||
capabilities = {},
|
||||
controller;
|
||||
mockOpenMCT,
|
||||
mockSelection,
|
||||
mockInspectorViews,
|
||||
mockTypeDef,
|
||||
controller,
|
||||
container,
|
||||
$document = [],
|
||||
selectable = [];
|
||||
|
||||
beforeEach(function () {
|
||||
mockTypeDefinition = {
|
||||
inspector:
|
||||
{
|
||||
'regions': [
|
||||
{'name': 'Part One'},
|
||||
{'name': 'Part Two'}
|
||||
]
|
||||
mockTypeDef = {
|
||||
typeDef: {
|
||||
inspector: "some-key"
|
||||
}
|
||||
};
|
||||
|
||||
mockTypeCapability = jasmine.createSpyObj('typeCapability', [
|
||||
'getDefinition'
|
||||
]);
|
||||
mockTypeCapability.getDefinition.andReturn(mockTypeDefinition);
|
||||
capabilities.type = mockTypeCapability;
|
||||
|
||||
mockStatusCapability = jasmine.createSpyObj('statusCapability', [
|
||||
'listen'
|
||||
]);
|
||||
capabilities.status = mockStatusCapability;
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
||||
'getCapability'
|
||||
]);
|
||||
mockDomainObject.getCapability.andCallFake(function (name) {
|
||||
return capabilities[name];
|
||||
});
|
||||
|
||||
mockPolicyService = jasmine.createSpyObj('policyService', [
|
||||
'allow'
|
||||
]);
|
||||
mockDomainObject.getCapability.andReturn(mockTypeDef);
|
||||
|
||||
mockScope = jasmine.createSpyObj('$scope',
|
||||
['$on']
|
||||
['$on', 'selection']
|
||||
);
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
selectable[0] = {
|
||||
context: {
|
||||
oldItem: mockDomainObject
|
||||
}
|
||||
};
|
||||
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andReturn(selectable);
|
||||
|
||||
mockInspectorViews = jasmine.createSpyObj('inspectorViews', ['get']);
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection,
|
||||
inspectorViews: mockInspectorViews
|
||||
};
|
||||
|
||||
container = jasmine.createSpy('container', ['innerHTML']);
|
||||
$document[0] = jasmine.createSpyObj("$document", ['querySelectorAll']);
|
||||
$document[0].querySelectorAll.andReturn([container]);
|
||||
|
||||
controller = new InspectorController(mockScope, mockOpenMCT, $document);
|
||||
});
|
||||
|
||||
it("filters out regions disallowed by region policy", function () {
|
||||
mockPolicyService.allow.andReturn(false);
|
||||
controller = new InspectorController(mockScope, mockPolicyService);
|
||||
expect(mockScope.regions.length).toBe(0);
|
||||
it("listens for selection change event", function () {
|
||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
||||
'change',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
expect(controller.selectedItem()).toEqual(mockDomainObject);
|
||||
|
||||
var mockItem = jasmine.createSpyObj('domainObject', [
|
||||
'getCapability'
|
||||
]);
|
||||
mockItem.getCapability.andReturn(mockTypeDef);
|
||||
selectable[0].context.oldItem = mockItem;
|
||||
|
||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
||||
|
||||
expect(controller.selectedItem()).toEqual(mockItem);
|
||||
});
|
||||
|
||||
it("does not filter out regions allowed by region policy", function () {
|
||||
mockPolicyService.allow.andReturn(true);
|
||||
controller = new InspectorController(mockScope, mockPolicyService);
|
||||
expect(mockScope.regions.length).toBe(2);
|
||||
it("cleans up on scope destroy", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
'$destroy',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
mockScope.$on.calls[0].args[1]();
|
||||
|
||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
||||
'change',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("Responds to status changes", function () {
|
||||
mockPolicyService.allow.andReturn(true);
|
||||
controller = new InspectorController(mockScope, mockPolicyService);
|
||||
expect(mockScope.regions.length).toBe(2);
|
||||
expect(mockStatusCapability.listen).toHaveBeenCalled();
|
||||
mockPolicyService.allow.andReturn(false);
|
||||
mockStatusCapability.listen.mostRecentCall.args[0]();
|
||||
expect(mockScope.regions.length).toBe(0);
|
||||
});
|
||||
|
||||
it("Unregisters status listener", function () {
|
||||
var mockListener = jasmine.createSpy('listener');
|
||||
mockStatusCapability.listen.andReturn(mockListener);
|
||||
controller = new InspectorController(mockScope, mockPolicyService);
|
||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
expect(mockListener).toHaveBeenCalled();
|
||||
it("adds selection object to scope", function () {
|
||||
expect(mockScope.selection).toEqual(selectable);
|
||||
expect(controller.selectedItem()).toEqual(mockDomainObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -181,6 +181,8 @@ $colorPlotHash: $colorTick;
|
||||
$stylePlotHash: dashed;
|
||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||
$legendCollapsedNameMaxW: 50%;
|
||||
$legendHoverValueBg: rgba($colorBodyFg, 0.1);
|
||||
|
||||
// Tree
|
||||
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
||||
@ -201,13 +203,15 @@ $shdwItemTreeIcon: 0.6;
|
||||
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
||||
|
||||
// Scrollbar
|
||||
$scrollbarTrackSize: 10px;
|
||||
$scrollbarTrackShdw: rgba(#000, 0.7) 0 1px 5px;
|
||||
$scrollbarTrackColorBg: rgba(#000, 0.4);
|
||||
$scrollbarTrackSize: 7px;
|
||||
$scrollbarTrackShdw: rgba(#000, 0.5) 0 1px 5px;
|
||||
$scrollbarTrackColorBg: transparent; //rgba(#000, 0.4);
|
||||
$scrollbarThumbColor: pullForward($colorBodyBg, 10%);
|
||||
$scrollbarThumbColorHov: pullForward($scrollbarThumbColor, 2%);
|
||||
$scrollbarThumbColorOverlay: pullForward($colorOvrBg, 10%);
|
||||
$scrollbarThumbColorOverlayHov: pullForward($scrollbarThumbColorOverlay, 2%);
|
||||
$scrollbarThumbColorMenu: pullForward($colorMenuBg, 20%);
|
||||
$scrollbarThumbColorMenuHov: pullForward($scrollbarThumbColorMenu, 2%);
|
||||
|
||||
// Splitter
|
||||
// All splitterD* values MUST both be either odd or even numbers
|
||||
@ -241,6 +245,12 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
|
||||
|
||||
// Palettes
|
||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
|
||||
$colorPaletteSelected: #fff;
|
||||
$shdwPaletteFg: black 0 0 2px;
|
||||
$shdwPaletteSelected: inset 0 0 0 1px #000;
|
||||
|
||||
// About Screen
|
||||
$colorAboutLink: #84b3ff;
|
||||
|
||||
|
@ -181,6 +181,8 @@ $colorPlotHash: $colorTick;
|
||||
$stylePlotHash: dashed;
|
||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||
$legendCollapsedNameMaxW: 50%;
|
||||
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
||||
|
||||
// Tree
|
||||
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
||||
@ -201,13 +203,15 @@ $shdwItemTreeIcon: none;
|
||||
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
||||
|
||||
// Scrollbar
|
||||
$scrollbarTrackSize: 10px;
|
||||
$scrollbarTrackSize: 7px;
|
||||
$scrollbarTrackShdw: rgba(#000, 0.2) 0 1px 2px;
|
||||
$scrollbarTrackColorBg: rgba(#000, 0.2);
|
||||
$scrollbarThumbColor: darken($colorBodyBg, 50%);
|
||||
$scrollbarThumbColorHov: $colorKey;
|
||||
$scrollbarThumbColorOverlay: darken($colorOvrBg, 50%);
|
||||
$scrollbarThumbColorOverlayHov: $scrollbarThumbColorHov;
|
||||
$scrollbarThumbColorMenu: pullForward($colorMenuBg, 10%);
|
||||
$scrollbarThumbColorMenuHov: pullForward($scrollbarThumbColorMenu, 2%);
|
||||
|
||||
// Splitter
|
||||
// All splitterD* values MUST both be either odd or even numbers
|
||||
@ -241,6 +245,12 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
|
||||
|
||||
// Palettes
|
||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
|
||||
$colorPaletteSelected: #333;
|
||||
$shdwPaletteFg: none;
|
||||
$shdwPaletteSelected: inset 0 0 0 1px #fff;
|
||||
|
||||
// About Screen
|
||||
$colorAboutLink: #84b3ff;
|
||||
|
||||
|
@ -94,31 +94,6 @@ define([
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"versions": [
|
||||
{
|
||||
"name": "Version",
|
||||
"value": "@@version",
|
||||
"priority": 999
|
||||
},
|
||||
{
|
||||
"name": "Built",
|
||||
"value": "@@timestamp",
|
||||
"description": "The date on which this version of the client was built.",
|
||||
"priority": 990
|
||||
},
|
||||
{
|
||||
"name": "Revision",
|
||||
"value": "@@revision",
|
||||
"description": "A unique revision identifier for the client sources.",
|
||||
"priority": 995
|
||||
},
|
||||
{
|
||||
"name": "Branch",
|
||||
"value": "@@branch",
|
||||
"description": "The name of the branch that was used during the build.",
|
||||
"priority": 994
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"provides": "objectService",
|
||||
|
@ -1,54 +0,0 @@
|
||||
define([
|
||||
'text!./res/templates/autoflow-tabular.html',
|
||||
'./src/AutoflowTabularController',
|
||||
'./src/MCTAutoflowTable'
|
||||
], function (
|
||||
autoflowTabularTemplate,
|
||||
AutoflowTabularController,
|
||||
MCTAutoflowTable
|
||||
) {
|
||||
return function (options) {
|
||||
return function (openmct) {
|
||||
openmct.legacyRegistry.register("platform/features/autoflow", {
|
||||
"name": "WARP Telemetry Adapter",
|
||||
"description": "Retrieves telemetry from the WARP Server and provides related types and views.",
|
||||
"resources": "res",
|
||||
"extensions": {
|
||||
"views": [
|
||||
{
|
||||
"key": "autoflow",
|
||||
"name": "Autoflow Tabular",
|
||||
"cssClass": "icon-packet",
|
||||
"description": "A tabular view of packet contents.",
|
||||
"template": autoflowTabularTemplate,
|
||||
"type": options && options.type,
|
||||
"needs": [
|
||||
"telemetry"
|
||||
],
|
||||
"delegation": true
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "AutoflowTabularController",
|
||||
"implementation": AutoflowTabularController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$timeout",
|
||||
"telemetrySubscriber"
|
||||
]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctAutoflowTable",
|
||||
"implementation": MCTAutoflowTable
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
openmct.legacyRegistry.enable("platform/features/autoflow");
|
||||
};
|
||||
};
|
||||
});
|
||||
|
@ -1,26 +0,0 @@
|
||||
<div class="items-holder abs contents autoflow obj-value-format"
|
||||
ng-controller="AutoflowTabularController as autoflow">
|
||||
<div class="abs l-flex-row holder t-autoflow-header l-autoflow-header">
|
||||
<mct-include key="'input-filter'"
|
||||
ng-model="autoflow.filter"
|
||||
class="flex-elem">
|
||||
</mct-include>
|
||||
<div class="flex-elem grows t-last-update" title="Last Update">{{autoflow.updated()}}</div>
|
||||
<a title="Change column width"
|
||||
class="s-button flex-elem icon-arrows-right-left change-column-width"
|
||||
ng-click="autoflow.increaseColumnWidth()"></a>
|
||||
</div>
|
||||
<div class="abs t-autoflow-items l-autoflow-items"
|
||||
mct-resize="autoflow.setBounds(bounds)"
|
||||
mct-resize-interval="50">
|
||||
<mct-autoflow-table values="autoflow.rangeValues()"
|
||||
objects="autoflow.getTelemetryObjects()"
|
||||
rows="autoflow.getRows()"
|
||||
classes="autoflow.classes()"
|
||||
updated="autoflow.updated()"
|
||||
column-width="autoflow.columnWidth()"
|
||||
counter="autoflow.counter()"
|
||||
>
|
||||
</mct-autoflow-table>
|
||||
</div>
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user